pwragent 1.3.10 → 1.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/federation-core.js +1827 -31
- package/package.json +4 -3
- package/src/federation-backend.js +38 -124
- package/src/federation-core.ts +3 -0
- package/src/federation.js +3 -3
package/dist/federation-core.js
CHANGED
|
@@ -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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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",
|
|
@@ -9187,6 +9187,152 @@ var import_websocket = __toESM(require_websocket(), 1);
|
|
|
9187
9187
|
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
9188
9188
|
var wrapper_default = import_websocket.default;
|
|
9189
9189
|
|
|
9190
|
+
// ../shared/src/contracts/navigation.ts
|
|
9191
|
+
var WORKTREE_IMAGE_MAX_BYTES = 16 * 1024 * 1024;
|
|
9192
|
+
function buildThreadIdentityKey(backend, threadId) {
|
|
9193
|
+
return `${backend}:${threadId}`;
|
|
9194
|
+
}
|
|
9195
|
+
var NAVIGATION_QUERY_PROTOCOL_VERSION = 2;
|
|
9196
|
+
var NAVIGATION_QUERY_MAX_PAGE_ROWS = 100;
|
|
9197
|
+
var NAVIGATION_QUERY_MAX_RESULT_BYTES = 252 * 1024;
|
|
9198
|
+
|
|
9199
|
+
// ../shared/src/thread-pins.ts
|
|
9200
|
+
var KEEP_AT_TOP_RANK_BOUNDARY = -(2 ** 39);
|
|
9201
|
+
var KEEP_AT_TOP_RANK_BASE = -(2 ** 40);
|
|
9202
|
+
function comparePinnedThreads(left, right) {
|
|
9203
|
+
const rankComparison = comparePinRanks(left.pinnedRank, right.pinnedRank);
|
|
9204
|
+
if (rankComparison !== 0) return rankComparison;
|
|
9205
|
+
const updatedComparison = (right.updatedAt ?? 0) - (left.updatedAt ?? 0);
|
|
9206
|
+
if (updatedComparison !== 0) return updatedComparison;
|
|
9207
|
+
return left.id.localeCompare(right.id);
|
|
9208
|
+
}
|
|
9209
|
+
function comparePinRanks(left, right) {
|
|
9210
|
+
const leftNumber = parsePinRank(left);
|
|
9211
|
+
const rightNumber = parsePinRank(right);
|
|
9212
|
+
if (leftNumber !== rightNumber) return leftNumber - rightNumber;
|
|
9213
|
+
return (left ?? "").localeCompare(right ?? "");
|
|
9214
|
+
}
|
|
9215
|
+
function parsePinRank(rank) {
|
|
9216
|
+
if (!rank?.trim()) return Number.POSITIVE_INFINITY;
|
|
9217
|
+
const parsed = Number(rank);
|
|
9218
|
+
return Number.isFinite(parsed) ? parsed : Number.POSITIVE_INFINITY;
|
|
9219
|
+
}
|
|
9220
|
+
|
|
9221
|
+
// ../shared/src/directory-navigation.ts
|
|
9222
|
+
function pathBaseName(value) {
|
|
9223
|
+
if (!value) {
|
|
9224
|
+
return value ?? "";
|
|
9225
|
+
}
|
|
9226
|
+
const normalized = value.replace(/[\\/]+$/, "");
|
|
9227
|
+
const parts = normalized.split(/[\\/]/).filter(Boolean);
|
|
9228
|
+
return parts.at(-1) ?? normalized;
|
|
9229
|
+
}
|
|
9230
|
+
function canonicalizeNavigationPath(value) {
|
|
9231
|
+
return value.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
9232
|
+
}
|
|
9233
|
+
function normalizeGitOriginUrl(value) {
|
|
9234
|
+
const trimmed = value?.trim();
|
|
9235
|
+
if (!trimmed) {
|
|
9236
|
+
return void 0;
|
|
9237
|
+
}
|
|
9238
|
+
const sshMatch = trimmed.match(/^git@([^:]+):(.+)$/i);
|
|
9239
|
+
let candidate = sshMatch ? `${sshMatch[1]}/${sshMatch[2]}` : trimmed.replace(/^[a-z]+:\/\//i, "");
|
|
9240
|
+
if (!sshMatch && /^[a-z]+:\/\//i.test(trimmed) && !/^file:/i.test(trimmed)) {
|
|
9241
|
+
const url = trimmed.match(/^[a-z]+:\/\/(?:[^/?#]*@)?([^/?#]+)(\/[^?#]*)/i);
|
|
9242
|
+
if (!url) {
|
|
9243
|
+
return void 0;
|
|
9244
|
+
}
|
|
9245
|
+
candidate = `${url[1]}${url[2]}`;
|
|
9246
|
+
}
|
|
9247
|
+
const normalized = candidate.replace(/\.git$/i, "").replace(/^ssh\//i, "").replace(/^\/+/, "").replace(/\/+$/, "").toLowerCase();
|
|
9248
|
+
return normalized || void 0;
|
|
9249
|
+
}
|
|
9250
|
+
function isInternalDirectoryLabel(value) {
|
|
9251
|
+
return Boolean(value?.startsWith("directory:") || value?.startsWith("workspace:"));
|
|
9252
|
+
}
|
|
9253
|
+
function displayDirectoryLabel(params) {
|
|
9254
|
+
const label = params.label?.trim();
|
|
9255
|
+
if (label && !isInternalDirectoryLabel(label)) {
|
|
9256
|
+
return label;
|
|
9257
|
+
}
|
|
9258
|
+
if (params.kind === "workspace") {
|
|
9259
|
+
return "Workspaces";
|
|
9260
|
+
}
|
|
9261
|
+
return pathBaseName(params.path) || label || "Directory";
|
|
9262
|
+
}
|
|
9263
|
+
function matchScratchProjectsRoot(value) {
|
|
9264
|
+
const scratchWorkspaceRootMatch = value.match(
|
|
9265
|
+
/^(.*[\\/]\.pwrag(?:ent|nt)(?:[\\/]profiles[\\/][^\\/]+)?[\\/]projects)$/
|
|
9266
|
+
);
|
|
9267
|
+
if (scratchWorkspaceRootMatch) {
|
|
9268
|
+
return scratchWorkspaceRootMatch[1];
|
|
9269
|
+
}
|
|
9270
|
+
const scratchWorkspaceMatch = value.match(
|
|
9271
|
+
/^(.*[\\/]\.pwrag(?:ent|nt)(?:[\\/]profiles[\\/][^\\/]+)?[\\/]projects)[\\/][^\\/]+$/
|
|
9272
|
+
);
|
|
9273
|
+
return scratchWorkspaceMatch?.[1];
|
|
9274
|
+
}
|
|
9275
|
+
function matchCodexChatsRoot(value) {
|
|
9276
|
+
const codexChatsRootMatch = value.match(
|
|
9277
|
+
/^(.*[\\/]Documents[\\/]Codex)(?:[\\/]\d{4}-\d{2}-\d{2}(?:[\\/].*)?)?$/
|
|
9278
|
+
);
|
|
9279
|
+
return codexChatsRootMatch?.[1];
|
|
9280
|
+
}
|
|
9281
|
+
function classifyDirectory(directory) {
|
|
9282
|
+
const path11 = canonicalizeNavigationPath(directory.path);
|
|
9283
|
+
const scratchProjectsRoot = matchScratchProjectsRoot(path11);
|
|
9284
|
+
if (scratchProjectsRoot) {
|
|
9285
|
+
return {
|
|
9286
|
+
key: `workspace:${scratchProjectsRoot}`,
|
|
9287
|
+
kind: "workspace",
|
|
9288
|
+
label: "Workspaces",
|
|
9289
|
+
path: scratchProjectsRoot
|
|
9290
|
+
};
|
|
9291
|
+
}
|
|
9292
|
+
const codexChatsRoot = matchCodexChatsRoot(path11);
|
|
9293
|
+
if (codexChatsRoot) {
|
|
9294
|
+
return {
|
|
9295
|
+
key: `directory:${codexChatsRoot}`,
|
|
9296
|
+
kind: "directory",
|
|
9297
|
+
label: "Codex Chats",
|
|
9298
|
+
path: codexChatsRoot
|
|
9299
|
+
};
|
|
9300
|
+
}
|
|
9301
|
+
const repoWorktreeMatch = path11.match(
|
|
9302
|
+
/^(.*)[\\/]\.worktrees[\\/][^\\/]+(?:[\\/].*)?$/
|
|
9303
|
+
);
|
|
9304
|
+
if (directory.kind === "worktree" && repoWorktreeMatch) {
|
|
9305
|
+
const canonicalPath = repoWorktreeMatch[1];
|
|
9306
|
+
return {
|
|
9307
|
+
key: `directory:${canonicalPath}`,
|
|
9308
|
+
kind: "directory",
|
|
9309
|
+
label: pathBaseName(canonicalPath),
|
|
9310
|
+
path: canonicalPath
|
|
9311
|
+
};
|
|
9312
|
+
}
|
|
9313
|
+
const codexWorktreeMatch = path11.match(
|
|
9314
|
+
/^(?:[A-Za-z]:)?[\\/].*[\\/]\.codex(?:[\\/]profiles[\\/][^\\/]+)?[\\/]worktrees[\\/][^\\/]+[\\/]([^\\/]+)(?:[\\/].*)?$/
|
|
9315
|
+
);
|
|
9316
|
+
if (codexWorktreeMatch) {
|
|
9317
|
+
return {
|
|
9318
|
+
key: `directory:${path11}`,
|
|
9319
|
+
kind: "directory",
|
|
9320
|
+
label: codexWorktreeMatch[1],
|
|
9321
|
+
path: path11
|
|
9322
|
+
};
|
|
9323
|
+
}
|
|
9324
|
+
return {
|
|
9325
|
+
key: `directory:${path11}`,
|
|
9326
|
+
kind: "directory",
|
|
9327
|
+
label: displayDirectoryLabel({
|
|
9328
|
+
kind: "directory",
|
|
9329
|
+
label: directory.label,
|
|
9330
|
+
path: path11
|
|
9331
|
+
}),
|
|
9332
|
+
path: path11
|
|
9333
|
+
};
|
|
9334
|
+
}
|
|
9335
|
+
|
|
9190
9336
|
// ../shared/src/contracts/agent-tools.ts
|
|
9191
9337
|
var PWRAGENT_TOOL_NAMESPACE = "pwragent";
|
|
9192
9338
|
|
|
@@ -9349,6 +9495,15 @@ function isFederationInstanceId(value) {
|
|
|
9349
9495
|
}
|
|
9350
9496
|
return true;
|
|
9351
9497
|
}
|
|
9498
|
+
function isRemoteFederationTarget(target) {
|
|
9499
|
+
return target.scope === "remote";
|
|
9500
|
+
}
|
|
9501
|
+
function federationTargetKey(target) {
|
|
9502
|
+
return isRemoteFederationTarget(target) ? `remote:${target.instanceId}` : "local";
|
|
9503
|
+
}
|
|
9504
|
+
function federatedThreadIdentityKey(ref) {
|
|
9505
|
+
return `${federationTargetKey(ref.target)}:${ref.backend}:${ref.threadId}`;
|
|
9506
|
+
}
|
|
9352
9507
|
|
|
9353
9508
|
// ../shared/src/contracts/messaging-tools.ts
|
|
9354
9509
|
var PWRAGENT_MESSAGING_TOOL_NAMESPACE = "pwragent_messaging";
|
|
@@ -9372,6 +9527,47 @@ var PWRAGENT_MESSAGING_CALLABLE_OPERATION_NAMES = [
|
|
|
9372
9527
|
...PWRAGENT_MESSAGING_LEGACY_OPERATION_NAMES
|
|
9373
9528
|
];
|
|
9374
9529
|
|
|
9530
|
+
// ../shared/src/navigation-star-map-filters.ts
|
|
9531
|
+
var NAVIGATION_STAR_MAP_FACETS = {
|
|
9532
|
+
attention: ["attention", "approval", "pr", "unpushed"],
|
|
9533
|
+
pinned: ["pinned"],
|
|
9534
|
+
agent: ["agent"]
|
|
9535
|
+
};
|
|
9536
|
+
function passesNavigationStarMapFilters(signals, selection, exceptFacet) {
|
|
9537
|
+
for (const [facet, keys] of Object.entries(NAVIGATION_STAR_MAP_FACETS)) {
|
|
9538
|
+
if (facet === exceptFacet) continue;
|
|
9539
|
+
let hasInclude = false;
|
|
9540
|
+
let matchedInclude = false;
|
|
9541
|
+
for (const key of keys) {
|
|
9542
|
+
if (selection[key] === "exclude" && signals[key]) return false;
|
|
9543
|
+
if (selection[key] === "include") {
|
|
9544
|
+
hasInclude = true;
|
|
9545
|
+
matchedInclude ||= signals[key];
|
|
9546
|
+
}
|
|
9547
|
+
}
|
|
9548
|
+
if (hasInclude && !matchedInclude) return false;
|
|
9549
|
+
}
|
|
9550
|
+
return true;
|
|
9551
|
+
}
|
|
9552
|
+
function countNavigationStarMapFacets(signals, selection) {
|
|
9553
|
+
const counts = {
|
|
9554
|
+
matches: { attention: 0, approval: 0, pr: 0, unpushed: 0, pinned: 0, agent: 0 },
|
|
9555
|
+
active: 0,
|
|
9556
|
+
unread: 0
|
|
9557
|
+
};
|
|
9558
|
+
for (const signal of signals) {
|
|
9559
|
+
for (const [facet, keys] of Object.entries(NAVIGATION_STAR_MAP_FACETS)) {
|
|
9560
|
+
if (!passesNavigationStarMapFilters(signal, selection, facet)) continue;
|
|
9561
|
+
for (const key of keys) if (signal[key]) counts.matches[key] += 1;
|
|
9562
|
+
if (facet === "attention") {
|
|
9563
|
+
if (signal.active) counts.active += 1;
|
|
9564
|
+
if (signal.unread) counts.unread += 1;
|
|
9565
|
+
}
|
|
9566
|
+
}
|
|
9567
|
+
}
|
|
9568
|
+
return counts;
|
|
9569
|
+
}
|
|
9570
|
+
|
|
9375
9571
|
// ../shared/src/contracts/thread-todos.ts
|
|
9376
9572
|
var THREAD_TODO_TITLE_MAX_LENGTH = 120;
|
|
9377
9573
|
var THREAD_TODO_ORIGIN_ID_MAX_LENGTH = 200;
|
|
@@ -9385,25 +9581,6 @@ function threadTodoMessageOriginFromUnknown(value) {
|
|
|
9385
9581
|
return { id, ...title ? { title } : {} };
|
|
9386
9582
|
}
|
|
9387
9583
|
|
|
9388
|
-
// ../shared/src/directory-navigation.ts
|
|
9389
|
-
function normalizeGitOriginUrl(value) {
|
|
9390
|
-
const trimmed = value?.trim();
|
|
9391
|
-
if (!trimmed) {
|
|
9392
|
-
return void 0;
|
|
9393
|
-
}
|
|
9394
|
-
const sshMatch = trimmed.match(/^git@([^:]+):(.+)$/i);
|
|
9395
|
-
let candidate = sshMatch ? `${sshMatch[1]}/${sshMatch[2]}` : trimmed.replace(/^[a-z]+:\/\//i, "");
|
|
9396
|
-
if (!sshMatch && /^[a-z]+:\/\//i.test(trimmed) && !/^file:/i.test(trimmed)) {
|
|
9397
|
-
const url = trimmed.match(/^[a-z]+:\/\/(?:[^/?#]*@)?([^/?#]+)(\/[^?#]*)/i);
|
|
9398
|
-
if (!url) {
|
|
9399
|
-
return void 0;
|
|
9400
|
-
}
|
|
9401
|
-
candidate = `${url[1]}${url[2]}`;
|
|
9402
|
-
}
|
|
9403
|
-
const normalized = candidate.replace(/\.git$/i, "").replace(/^ssh\//i, "").replace(/^\/+/, "").replace(/\/+$/, "").toLowerCase();
|
|
9404
|
-
return normalized || void 0;
|
|
9405
|
-
}
|
|
9406
|
-
|
|
9407
9584
|
// ../shared/src/nested-worktree-pathspec.ts
|
|
9408
9585
|
var EXCLUDE_MAGIC = ":(exclude,top,literal)";
|
|
9409
9586
|
function normalizeWorktreePath(value) {
|
|
@@ -9486,6 +9663,212 @@ 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
|
+
|
|
9846
|
+
// ../shared/src/text-graphemes.ts
|
|
9847
|
+
var segmenter;
|
|
9848
|
+
function* clusters(value) {
|
|
9849
|
+
if (typeof Intl.Segmenter !== "function") {
|
|
9850
|
+
yield* value;
|
|
9851
|
+
return;
|
|
9852
|
+
}
|
|
9853
|
+
segmenter ??= new Intl.Segmenter("en", { granularity: "grapheme" });
|
|
9854
|
+
for (const { segment } of segmenter.segment(value)) {
|
|
9855
|
+
yield segment;
|
|
9856
|
+
}
|
|
9857
|
+
}
|
|
9858
|
+
function graphemePrefix(value, maxLength) {
|
|
9859
|
+
if (value.length <= maxLength) {
|
|
9860
|
+
return value;
|
|
9861
|
+
}
|
|
9862
|
+
let end = 0;
|
|
9863
|
+
for (const cluster of clusters(value)) {
|
|
9864
|
+
if (end + cluster.length > maxLength) {
|
|
9865
|
+
break;
|
|
9866
|
+
}
|
|
9867
|
+
end += cluster.length;
|
|
9868
|
+
}
|
|
9869
|
+
return value.slice(0, end);
|
|
9870
|
+
}
|
|
9871
|
+
|
|
9489
9872
|
// ../shared/src/thread-titles.ts
|
|
9490
9873
|
var MAX_DERIVED_THREAD_TITLE_LENGTH = 72;
|
|
9491
9874
|
var MIN_PREFERRED_BREAK_INDEX = 36;
|
|
@@ -9538,7 +9921,7 @@ function shortenDerivedThreadTitle(value) {
|
|
|
9538
9921
|
if (wordBreak >= MIN_PREFERRED_BREAK_INDEX) {
|
|
9539
9922
|
return `${trimTrailingPunctuation(candidate.slice(0, wordBreak))}...`;
|
|
9540
9923
|
}
|
|
9541
|
-
return `${trimTrailingPunctuation(candidate
|
|
9924
|
+
return `${trimTrailingPunctuation(graphemePrefix(candidate, MAX_DERIVED_THREAD_TITLE_LENGTH))}...`;
|
|
9542
9925
|
}
|
|
9543
9926
|
|
|
9544
9927
|
// ../shared/src/messaging-causal-context.ts
|
|
@@ -13088,10 +13471,10 @@ var FederationPtyService = class {
|
|
|
13088
13471
|
async list(peerId, request) {
|
|
13089
13472
|
await this.ready;
|
|
13090
13473
|
if (this.options.allowOpen?.() === false) throw new Error("Remote shells are disabled.");
|
|
13091
|
-
const
|
|
13474
|
+
const threadKey2 = `${request.backend}:${request.threadId}`;
|
|
13092
13475
|
return [
|
|
13093
13476
|
...[...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 ===
|
|
13477
|
+
...[...this.archived.values()].filter((record) => record.peerId === peerId && record.summary.threadKey === threadKey2).map((record) => record.summary)
|
|
13095
13478
|
];
|
|
13096
13479
|
}
|
|
13097
13480
|
async snapshot(peerId, request) {
|
|
@@ -18790,7 +19173,9 @@ var ThreadInstanceHandoffService = class {
|
|
|
18790
19173
|
// ../federation/src/git-instance-handoff.ts
|
|
18791
19174
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
18792
19175
|
import { realpath as realpath3, stat as stat2 } from "node:fs/promises";
|
|
19176
|
+
import os2 from "node:os";
|
|
18793
19177
|
import path6 from "node:path";
|
|
19178
|
+
var DISABLE_HOOKS = ["-c", `core.hooksPath=${os2.devNull}`];
|
|
18794
19179
|
function createGitHandoffService(runGitCommand) {
|
|
18795
19180
|
function git(cwd, args) {
|
|
18796
19181
|
return runGitCommand(cwd, args, { maxBuffer: 1024 * 1024 });
|
|
@@ -18887,7 +19272,7 @@ function createGitHandoffService(runGitCommand) {
|
|
|
18887
19272
|
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
19273
|
const ref = `refs/pwragent/handoffs/${randomUUID6()}`;
|
|
18889
19274
|
try {
|
|
18890
|
-
await git(repository, [
|
|
19275
|
+
await git(repository, [...DISABLE_HOOKS, "fetch", "--no-tags", "--no-write-fetch-head", "--", remote, `${snapshot.ref}:${ref}`]).catch((error) => {
|
|
18891
19276
|
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
19277
|
});
|
|
18893
19278
|
const fetchedHead = (await git(repository, ["rev-parse", `${ref}^{commit}`])).stdout.trim();
|
|
@@ -18929,11 +19314,11 @@ function createGitHandoffService(runGitCommand) {
|
|
|
18929
19314
|
switchBranch = branchHead ? ["--no-guess", branch] : ["-c", branch, snapshot.head];
|
|
18930
19315
|
}
|
|
18931
19316
|
}
|
|
18932
|
-
await git(repository, [
|
|
19317
|
+
await git(repository, [...DISABLE_HOOKS, "worktree", "add", "--detach", worktree, snapshot.head]);
|
|
18933
19318
|
created = true;
|
|
18934
19319
|
if (switchBranch && branch) {
|
|
18935
19320
|
try {
|
|
18936
|
-
await git(worktree, [
|
|
19321
|
+
await git(worktree, [...DISABLE_HOOKS, "switch", ...switchBranch]);
|
|
18937
19322
|
if (switchBranch[0] === "-c") createdBranch = branch;
|
|
18938
19323
|
} catch (error) {
|
|
18939
19324
|
const ref = `refs/heads/${branch}`;
|
|
@@ -19283,7 +19668,7 @@ import path10 from "node:path";
|
|
|
19283
19668
|
|
|
19284
19669
|
// ../agent-tools/src/local-file-access.ts
|
|
19285
19670
|
import { realpath as realpath4 } from "node:fs/promises";
|
|
19286
|
-
import
|
|
19671
|
+
import os3 from "node:os";
|
|
19287
19672
|
import path8 from "node:path";
|
|
19288
19673
|
var PRIVATE_STORAGE_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
19289
19674
|
"archived_sessions",
|
|
@@ -19346,7 +19731,7 @@ async function canonicalizeRoots(roots) {
|
|
|
19346
19731
|
}
|
|
19347
19732
|
function configuredPrivateStorageRoots(privateStorageRoots) {
|
|
19348
19733
|
return [
|
|
19349
|
-
path8.join(
|
|
19734
|
+
path8.join(os3.homedir(), ".codex"),
|
|
19350
19735
|
process.env.CODEX_HOME?.trim(),
|
|
19351
19736
|
...privateStorageRoots
|
|
19352
19737
|
].filter((root) => Boolean(root));
|
|
@@ -20103,6 +20488,1414 @@ var FEDERATION_BACKEND_METHOD_CAPABILITIES = {
|
|
|
20103
20488
|
// instance — the same trust the materialize-launchpad path carries.
|
|
20104
20489
|
[FEDERATION_BACKEND_METHODS.starMapIntake]: "environment_actions"
|
|
20105
20490
|
};
|
|
20491
|
+
|
|
20492
|
+
// ../owner-core/src/navigation-query-store.ts
|
|
20493
|
+
import { createHash as createHash8, randomUUID as randomUUID8 } from "node:crypto";
|
|
20494
|
+
|
|
20495
|
+
// ../owner-core/src/navigation-query-projection.ts
|
|
20496
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
20497
|
+
|
|
20498
|
+
// ../owner-core/src/navigation-attention-order.ts
|
|
20499
|
+
function observeNavigationAttentionTurn(params) {
|
|
20500
|
+
const previous = params.previous.members.get(params.key);
|
|
20501
|
+
const observedTurnIds = previous?.observedTurnIds ?? (previous?.eventTurnId ? [previous.eventTurnId] : []);
|
|
20502
|
+
if (params.turnId && params.turnId !== previous?.eventTurnId && observedTurnIds.includes(params.turnId)) return params.previous;
|
|
20503
|
+
if (previous?.active === params.active && (!params.turnId || previous.eventTurnId === params.turnId)) return params.previous;
|
|
20504
|
+
if (!params.active && previous?.active && previous.eventTurnId && params.turnId && previous.eventTurnId !== params.turnId) return params.previous;
|
|
20505
|
+
const newTurn = Boolean(params.turnId && previous?.eventTurnId !== params.turnId);
|
|
20506
|
+
const move = !previous || (params.active ? !previous.active || newTurn && Boolean(previous.eventTurnId) : !previous.active && newTurn || params.promoteOnTurnEnd);
|
|
20507
|
+
const rank = move ? params.previous.nextRank : previous.rank;
|
|
20508
|
+
const members = new Map(params.previous.members);
|
|
20509
|
+
members.set(params.key, {
|
|
20510
|
+
active: params.active,
|
|
20511
|
+
rank,
|
|
20512
|
+
updatedAt: previous?.updatedAt ?? 0,
|
|
20513
|
+
observedTurnIds: params.turnId && !observedTurnIds.includes(params.turnId) ? [...observedTurnIds, params.turnId] : observedTurnIds,
|
|
20514
|
+
eventTurnId: params.active ? params.turnId : params.turnId ?? previous?.eventTurnId,
|
|
20515
|
+
eventTracked: true,
|
|
20516
|
+
awaitingBaseline: true,
|
|
20517
|
+
...params.observedAt !== void 0 ? { observedAt: params.observedAt } : {}
|
|
20518
|
+
});
|
|
20519
|
+
let removed = params.previous.removed;
|
|
20520
|
+
if (removed?.has(params.key)) {
|
|
20521
|
+
removed = new Map(removed);
|
|
20522
|
+
removed.delete(params.key);
|
|
20523
|
+
}
|
|
20524
|
+
return {
|
|
20525
|
+
members,
|
|
20526
|
+
nextRank: params.previous.nextRank + Number(move),
|
|
20527
|
+
...removed?.size ? { removed } : {},
|
|
20528
|
+
...params.previous.listedAt !== void 0 ? { listedAt: params.previous.listedAt } : {}
|
|
20529
|
+
};
|
|
20530
|
+
}
|
|
20531
|
+
function removeNavigationAttentionMember(params) {
|
|
20532
|
+
const members = new Map(params.previous.members);
|
|
20533
|
+
members.delete(params.key);
|
|
20534
|
+
const removed = new Map(params.previous.removed);
|
|
20535
|
+
removed.set(params.key, params.observedAt);
|
|
20536
|
+
return { ...params.previous, members, removed };
|
|
20537
|
+
}
|
|
20538
|
+
function navigationAttentionOrderFollows(order, observedAt) {
|
|
20539
|
+
if ((order.listedAt ?? 0) > observedAt) return true;
|
|
20540
|
+
for (const member of order.members.values()) if ((member.observedAt ?? 0) > observedAt) return true;
|
|
20541
|
+
for (const at2 of order.removed?.values() ?? []) if (at2 > observedAt) return true;
|
|
20542
|
+
return false;
|
|
20543
|
+
}
|
|
20544
|
+
function navigationAttentionIdentity(thread) {
|
|
20545
|
+
return JSON.stringify([
|
|
20546
|
+
thread.federation?.ref.target.scope === "remote" ? thread.federation.ref.target.instanceId : null,
|
|
20547
|
+
thread.source,
|
|
20548
|
+
thread.id
|
|
20549
|
+
]);
|
|
20550
|
+
}
|
|
20551
|
+
function isLive(thread) {
|
|
20552
|
+
return thread.threadStatus === "active" || thread.hasActiveSubAgent === true || thread.hasBackgroundTasks === true;
|
|
20553
|
+
}
|
|
20554
|
+
function reconcileNavigationAttentionOrder(params) {
|
|
20555
|
+
const { observedAt } = params;
|
|
20556
|
+
const follows = (at2) => observedAt !== void 0 && at2 !== void 0 && at2 > observedAt;
|
|
20557
|
+
const members = params.complete === false ? new Map(params.previous?.members) : /* @__PURE__ */ new Map();
|
|
20558
|
+
for (const [key, member] of params.previous?.members ?? []) {
|
|
20559
|
+
if (member.awaitingBaseline || follows(member.observedAt)) members.set(key, member);
|
|
20560
|
+
}
|
|
20561
|
+
const removed = /* @__PURE__ */ new Map();
|
|
20562
|
+
for (const [key, at2] of params.previous?.removed ?? []) {
|
|
20563
|
+
if (follows(at2)) removed.set(key, at2);
|
|
20564
|
+
}
|
|
20565
|
+
const decides = (key) => {
|
|
20566
|
+
const member = params.previous?.members.get(key);
|
|
20567
|
+
return !follows(member?.observedAt) && !removed.has(key) && (member !== void 0 || !follows(params.previous?.listedAt));
|
|
20568
|
+
};
|
|
20569
|
+
for (const thread of params.threads) {
|
|
20570
|
+
const key = navigationAttentionIdentity(thread);
|
|
20571
|
+
const previous = params.previous?.members.get(key);
|
|
20572
|
+
if (decides(key) && (thread.codexNativeSubAgent || !isLive(thread) && !thread.inbox.inInbox && (!previous?.awaitingBaseline || !previous.active))) members.delete(key);
|
|
20573
|
+
}
|
|
20574
|
+
let nextRank = params.previous?.nextRank ?? 1;
|
|
20575
|
+
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)));
|
|
20576
|
+
for (const thread of eligible) {
|
|
20577
|
+
const key = navigationAttentionIdentity(thread);
|
|
20578
|
+
const previous = params.previous?.members.get(key);
|
|
20579
|
+
const observedActive = thread.threadStatus === "active";
|
|
20580
|
+
const active = previous?.awaitingBaseline ? previous.active : observedActive;
|
|
20581
|
+
const updatedAt = thread.updatedAt ?? 0;
|
|
20582
|
+
const started = active && previous?.active === false;
|
|
20583
|
+
const finished = !active && (previous?.active === true || previous?.active === false && !previous.eventTracked && updatedAt > previous.updatedAt);
|
|
20584
|
+
const rank = !previous || started || finished && params.promoteOnTurnEnd ? nextRank++ : previous.rank;
|
|
20585
|
+
const awaitingBaseline = previous?.awaitingBaseline && observedActive !== active;
|
|
20586
|
+
const memberObservedAt = awaitingBaseline ? previous?.observedAt : observedAt ?? previous?.observedAt;
|
|
20587
|
+
members.set(key, {
|
|
20588
|
+
active,
|
|
20589
|
+
rank,
|
|
20590
|
+
updatedAt,
|
|
20591
|
+
...previous?.eventTurnId ? { eventTurnId: previous.eventTurnId } : {},
|
|
20592
|
+
...previous?.observedTurnIds ? { observedTurnIds: previous.observedTurnIds } : {},
|
|
20593
|
+
...previous?.eventTracked ? { eventTracked: true } : {},
|
|
20594
|
+
...awaitingBaseline ? { awaitingBaseline: true } : {},
|
|
20595
|
+
...memberObservedAt !== void 0 ? { observedAt: memberObservedAt } : {}
|
|
20596
|
+
});
|
|
20597
|
+
}
|
|
20598
|
+
if (observedAt !== void 0) {
|
|
20599
|
+
for (const key of params.previous?.members.keys() ?? []) {
|
|
20600
|
+
if (!members.has(key)) removed.set(key, observedAt);
|
|
20601
|
+
}
|
|
20602
|
+
}
|
|
20603
|
+
const listedAt = params.complete !== false && observedAt !== void 0 && !follows(params.previous?.listedAt) ? observedAt : params.previous?.listedAt;
|
|
20604
|
+
return { members, nextRank, ...removed.size ? { removed } : {}, ...listedAt !== void 0 ? { listedAt } : {} };
|
|
20605
|
+
}
|
|
20606
|
+
function navigationAttentionOrderBytes(order) {
|
|
20607
|
+
return Buffer.byteLength(JSON.stringify({
|
|
20608
|
+
members: [...order.members],
|
|
20609
|
+
nextRank: order.nextRank,
|
|
20610
|
+
removed: [...order.removed ?? []],
|
|
20611
|
+
listedAt: order.listedAt
|
|
20612
|
+
}), "utf8");
|
|
20613
|
+
}
|
|
20614
|
+
|
|
20615
|
+
// ../owner-core/src/navigation-query-projection.ts
|
|
20616
|
+
var MAX_ROW_NESTED_RECORDS = 16;
|
|
20617
|
+
function hashValue(value) {
|
|
20618
|
+
return createHash7("sha256").update(JSON.stringify(value)).digest("base64url");
|
|
20619
|
+
}
|
|
20620
|
+
function navigationIdentity(thread) {
|
|
20621
|
+
return {
|
|
20622
|
+
backend: thread.source,
|
|
20623
|
+
threadId: thread.id,
|
|
20624
|
+
...thread.federation?.ref.target.scope === "remote" ? { ownerInstanceId: thread.federation.ref.target.instanceId } : {}
|
|
20625
|
+
};
|
|
20626
|
+
}
|
|
20627
|
+
function identityKey(identity) {
|
|
20628
|
+
return [
|
|
20629
|
+
identity.ownerInstanceId ?? "local",
|
|
20630
|
+
identity.backend,
|
|
20631
|
+
identity.threadId
|
|
20632
|
+
].join("\0");
|
|
20633
|
+
}
|
|
20634
|
+
function threadKey(thread) {
|
|
20635
|
+
return identityKey(navigationIdentity(thread));
|
|
20636
|
+
}
|
|
20637
|
+
function parentIdentity(thread, candidatesByOwner) {
|
|
20638
|
+
if (!thread.parentThreadId) {
|
|
20639
|
+
return void 0;
|
|
20640
|
+
}
|
|
20641
|
+
let ownerInstanceId = thread.parentThreadInstanceId ?? (thread.federation?.ref.target.scope === "remote" ? thread.federation.ref.target.instanceId : void 0);
|
|
20642
|
+
if (!ownerInstanceId && thread.handoffOrigin?.groupingMode === "subthread" && !(candidatesByOwner.get(JSON.stringify([null, thread.parentThreadId])) ?? []).some((candidate) => candidate.source === (thread.parentThreadBackend ?? thread.source))) {
|
|
20643
|
+
const origin = thread.handoffOrigin;
|
|
20644
|
+
const launcher = (candidatesByOwner.get(JSON.stringify([null, origin.sourceThreadId])) ?? []).find((candidate) => candidate.source === origin.sourceBackend);
|
|
20645
|
+
if (launcher?.parentThreadId === thread.parentThreadId && (launcher.parentThreadBackend ?? launcher.source) === (thread.parentThreadBackend ?? thread.source)) {
|
|
20646
|
+
ownerInstanceId = launcher.parentThreadInstanceId;
|
|
20647
|
+
}
|
|
20648
|
+
}
|
|
20649
|
+
const candidates = candidatesByOwner.get(JSON.stringify([ownerInstanceId ?? null, thread.parentThreadId])) ?? [];
|
|
20650
|
+
const parent = !thread.parentThreadBackend ? candidates.find((candidate) => candidate.source === thread.source) ?? (candidates.length === 1 ? candidates[0] : void 0) : void 0;
|
|
20651
|
+
const backend = thread.parentThreadBackend ?? parent?.source ?? thread.source;
|
|
20652
|
+
const resolvedParent = candidates.find((candidate) => candidate.source === backend);
|
|
20653
|
+
if (resolvedParent) ownerInstanceId = navigationIdentity(resolvedParent).ownerInstanceId;
|
|
20654
|
+
return {
|
|
20655
|
+
backend,
|
|
20656
|
+
threadId: thread.parentThreadId,
|
|
20657
|
+
...ownerInstanceId ? { ownerInstanceId } : {}
|
|
20658
|
+
};
|
|
20659
|
+
}
|
|
20660
|
+
function isOrdinaryThread(thread) {
|
|
20661
|
+
return thread.codexNativeSubAgent === void 0;
|
|
20662
|
+
}
|
|
20663
|
+
function lensRootResolver(params) {
|
|
20664
|
+
const roots = /* @__PURE__ */ new Map();
|
|
20665
|
+
return (thread) => {
|
|
20666
|
+
const known = roots.get(thread);
|
|
20667
|
+
if (known) return known;
|
|
20668
|
+
let root = thread;
|
|
20669
|
+
const visited = /* @__PURE__ */ new Set([thread]);
|
|
20670
|
+
for (let depth = 0; depth < 32; depth += 1) {
|
|
20671
|
+
const parent = parentIdentity(root, params.parentCandidates);
|
|
20672
|
+
const parentThread = parent ? params.threadsByIdentity.get(identityKey(parent)) : void 0;
|
|
20673
|
+
if (!parentThread || visited.has(parentThread)) break;
|
|
20674
|
+
visited.add(parentThread);
|
|
20675
|
+
root = parentThread;
|
|
20676
|
+
}
|
|
20677
|
+
roots.set(thread, root);
|
|
20678
|
+
return root;
|
|
20679
|
+
};
|
|
20680
|
+
}
|
|
20681
|
+
function availableParentIdentity(thread, candidatesByOwner, coverage) {
|
|
20682
|
+
const parent = parentIdentity(thread, candidatesByOwner);
|
|
20683
|
+
if (!parent) return void 0;
|
|
20684
|
+
if (coverage && coverage.state !== "complete") return parent;
|
|
20685
|
+
const candidates = candidatesByOwner.get(JSON.stringify([parent.ownerInstanceId ?? null, parent.threadId])) ?? [];
|
|
20686
|
+
return candidates.some((candidate) => candidate.source === parent.backend) ? parent : void 0;
|
|
20687
|
+
}
|
|
20688
|
+
function directoryParentResolver(members, parentCandidates) {
|
|
20689
|
+
const memberKeys = new Set(members.map(threadKey));
|
|
20690
|
+
return (thread) => {
|
|
20691
|
+
const parent = parentIdentity(thread, parentCandidates);
|
|
20692
|
+
return parent && memberKeys.has(identityKey(parent)) ? parent : void 0;
|
|
20693
|
+
};
|
|
20694
|
+
}
|
|
20695
|
+
function directoryMemberThreads(directory, threadsByLegacyKey) {
|
|
20696
|
+
return directory?.threadKeys.map((key) => threadsByLegacyKey.get(key)).filter((thread) => Boolean(thread)) ?? [];
|
|
20697
|
+
}
|
|
20698
|
+
function isActive(thread) {
|
|
20699
|
+
return thread.threadStatus === "active" || thread.hasActiveSubAgent === true || thread.hasBackgroundTasks === true;
|
|
20700
|
+
}
|
|
20701
|
+
function countsForThreads(threads, options = {}) {
|
|
20702
|
+
const distinct = /* @__PURE__ */ new Map();
|
|
20703
|
+
for (const thread of threads) {
|
|
20704
|
+
if (!isOrdinaryThread(thread)) {
|
|
20705
|
+
continue;
|
|
20706
|
+
}
|
|
20707
|
+
distinct.set(threadKey(thread), thread);
|
|
20708
|
+
}
|
|
20709
|
+
let active = 0;
|
|
20710
|
+
let activeRemote = 0;
|
|
20711
|
+
let pinned = 0;
|
|
20712
|
+
let unread = 0;
|
|
20713
|
+
let review = 0;
|
|
20714
|
+
for (const thread of distinct.values()) {
|
|
20715
|
+
const threadActive = isActive(thread);
|
|
20716
|
+
const threadUnread = thread.inbox.inInbox;
|
|
20717
|
+
if (threadActive) active += 1;
|
|
20718
|
+
if (threadActive && thread.federation?.ref.target.scope === "remote") activeRemote += 1;
|
|
20719
|
+
if (thread.pinnedRank) pinned += 1;
|
|
20720
|
+
if (threadUnread) unread += 1;
|
|
20721
|
+
if (threadUnread && !threadActive) review += 1;
|
|
20722
|
+
}
|
|
20723
|
+
const backends = options.backends ? [...new Set(
|
|
20724
|
+
[...distinct.values()].map((thread) => thread.source)
|
|
20725
|
+
)].sort() : void 0;
|
|
20726
|
+
return {
|
|
20727
|
+
total: distinct.size,
|
|
20728
|
+
active,
|
|
20729
|
+
...activeRemote ? { activeRemote } : {},
|
|
20730
|
+
...pinned ? { pinned } : {},
|
|
20731
|
+
unread,
|
|
20732
|
+
review,
|
|
20733
|
+
...backends ? { backends } : {}
|
|
20734
|
+
};
|
|
20735
|
+
}
|
|
20736
|
+
function limitRecords(items) {
|
|
20737
|
+
if (!items || items.length === 0) {
|
|
20738
|
+
return { truncated: false };
|
|
20739
|
+
}
|
|
20740
|
+
return {
|
|
20741
|
+
items: items.slice(0, MAX_ROW_NESTED_RECORDS),
|
|
20742
|
+
truncated: items.length > MAX_ROW_NESTED_RECORDS
|
|
20743
|
+
};
|
|
20744
|
+
}
|
|
20745
|
+
function projectNavigationRow(params) {
|
|
20746
|
+
const { thread } = params;
|
|
20747
|
+
const linkedDirectories = limitRecords(thread.federation ? thread.linkedDirectories : withRepositoryIdentities(thread.linkedDirectories, params.repositories));
|
|
20748
|
+
const reactions = limitRecords(thread.reactions);
|
|
20749
|
+
const prs = limitRecords(thread.prs);
|
|
20750
|
+
const messagingBindings = limitRecords(thread.messagingBindings);
|
|
20751
|
+
const prCiWatches = limitRecords(thread.prCiWatches);
|
|
20752
|
+
const nativeSubAgentCount = thread.nativeSubAgentCount ?? thread.codexNativeSubAgents?.length ?? 0;
|
|
20753
|
+
const projected = {
|
|
20754
|
+
ref: navigationIdentity(thread),
|
|
20755
|
+
id: thread.id,
|
|
20756
|
+
source: thread.source,
|
|
20757
|
+
title: thread.title,
|
|
20758
|
+
titleSource: thread.titleSource,
|
|
20759
|
+
...thread.createdAt !== void 0 ? { createdAt: thread.createdAt } : {},
|
|
20760
|
+
...thread.updatedAt !== void 0 ? { updatedAt: thread.updatedAt } : {},
|
|
20761
|
+
...thread.archivedAt !== void 0 ? { archivedAt: thread.archivedAt } : {},
|
|
20762
|
+
...thread.threadStatus ? { threadStatus: thread.threadStatus } : {},
|
|
20763
|
+
...thread.hasActiveSubAgent ? { hasActiveSubAgent: true } : {},
|
|
20764
|
+
...thread.hasBackgroundTasks ? { hasBackgroundTasks: true } : {},
|
|
20765
|
+
...thread.hasAgentCommands ? { hasAgentCommands: true } : {},
|
|
20766
|
+
inbox: thread.inbox,
|
|
20767
|
+
...thread.projectKey ? { projectKey: thread.projectKey } : {},
|
|
20768
|
+
linkedDirectories: linkedDirectories.items ?? [],
|
|
20769
|
+
...linkedDirectories.truncated ? { linkedDirectoriesTruncated: true } : {},
|
|
20770
|
+
...thread.gitBranch ? { gitBranch: thread.gitBranch } : {},
|
|
20771
|
+
...thread.gitOriginUrl ? { gitOriginUrl: thread.gitOriginUrl } : {},
|
|
20772
|
+
...thread.observedGitBranch ? { observedGitBranch: thread.observedGitBranch } : {},
|
|
20773
|
+
...thread.gitWorkingState ? { gitWorkingState: thread.gitWorkingState } : {},
|
|
20774
|
+
...thread.gitWorkingStateFetchedAt !== void 0 ? { gitWorkingStateFetchedAt: thread.gitWorkingStateFetchedAt } : {},
|
|
20775
|
+
...thread.primaryGitRepository ? { primaryGitRepository: thread.primaryGitRepository } : {},
|
|
20776
|
+
...thread.federation ? {
|
|
20777
|
+
federation: {
|
|
20778
|
+
ref: thread.federation.ref,
|
|
20779
|
+
instanceLabel: thread.federation.instanceLabel,
|
|
20780
|
+
...thread.federation.peerStatus ? { peerStatus: thread.federation.peerStatus } : {},
|
|
20781
|
+
...thread.federation.capabilities ? { capabilities: thread.federation.capabilities } : {},
|
|
20782
|
+
...thread.federation.derivedFromMountedParent ? { derivedFromMountedParent: true } : {},
|
|
20783
|
+
...thread.federation.celestialIcon ? { celestialIcon: thread.federation.celestialIcon } : {}
|
|
20784
|
+
}
|
|
20785
|
+
} : {},
|
|
20786
|
+
...thread.pinnedRank ? { pinnedRank: thread.pinnedRank } : {},
|
|
20787
|
+
...thread.parentThreadId ? { parentThreadId: thread.parentThreadId } : {},
|
|
20788
|
+
...thread.parentThreadBackend ? { parentThreadBackend: thread.parentThreadBackend } : {},
|
|
20789
|
+
...thread.parentThreadInstanceId ? { parentThreadInstanceId: thread.parentThreadInstanceId } : {},
|
|
20790
|
+
ordinaryChildCount: params.childCount,
|
|
20791
|
+
...params.viewerChildCount !== void 0 ? { viewerChildCount: params.viewerChildCount } : {},
|
|
20792
|
+
nativeSubAgentGroupPresent: nativeSubAgentCount > 0,
|
|
20793
|
+
...nativeSubAgentCount > 0 ? { nativeSubAgentCount } : {},
|
|
20794
|
+
...thread.subthreadsCollapsed !== void 0 ? { subthreadsCollapsed: thread.subthreadsCollapsed } : {},
|
|
20795
|
+
...reactions.items ? { reactions: reactions.items } : {},
|
|
20796
|
+
...reactions.truncated ? { reactionsTruncated: true } : {},
|
|
20797
|
+
...thread.lock ? { lock: thread.lock } : {},
|
|
20798
|
+
...prs.items ? { prs: prs.items } : {},
|
|
20799
|
+
...prs.truncated ? { prsTruncated: true } : {},
|
|
20800
|
+
...messagingBindings.items ? { messagingBindings: messagingBindings.items } : {},
|
|
20801
|
+
...messagingBindings.truncated ? { messagingBindingsTruncated: true } : {},
|
|
20802
|
+
...thread.automationSummary ? { automationSummary: thread.automationSummary } : {},
|
|
20803
|
+
...thread.agentChange ? { agentChange: thread.agentChange } : {},
|
|
20804
|
+
...thread.agent ? {
|
|
20805
|
+
agent: {
|
|
20806
|
+
name: thread.agent.name,
|
|
20807
|
+
instructionLineCount: thread.agent.instructionLineCount,
|
|
20808
|
+
instructionsTooLong: thread.agent.instructionsTooLong,
|
|
20809
|
+
updatedAt: thread.agent.updatedAt
|
|
20810
|
+
}
|
|
20811
|
+
} : {},
|
|
20812
|
+
...thread.executionMode ? { executionMode: thread.executionMode } : {},
|
|
20813
|
+
...thread.model ? { model: thread.model } : {},
|
|
20814
|
+
...thread.serviceTier ? { serviceTier: thread.serviceTier } : {},
|
|
20815
|
+
...thread.reasoningEffort ? { reasoningEffort: thread.reasoningEffort } : {},
|
|
20816
|
+
...thread.fastMode !== void 0 ? { fastMode: thread.fastMode } : {},
|
|
20817
|
+
...thread.workspaceHandoff ? { workspaceHandoff: thread.workspaceHandoff } : {},
|
|
20818
|
+
needsInput: params.needsInput,
|
|
20819
|
+
queueCount: thread.queuedTurns?.length ?? 0,
|
|
20820
|
+
queueState: thread.queuedTurns ? "ready" : "unknown",
|
|
20821
|
+
...thread.queuedExecutionMode ? { queuedExecutionMode: thread.queuedExecutionMode } : {},
|
|
20822
|
+
...thread.prAutoDispatchEnabled !== void 0 ? { prAutoDispatchEnabled: thread.prAutoDispatchEnabled } : {},
|
|
20823
|
+
...prCiWatches.items ? { prCiWatches: prCiWatches.items } : {},
|
|
20824
|
+
...thread.scheduledStart ? { scheduledStart: thread.scheduledStart } : {},
|
|
20825
|
+
...params.homeProject ? { homeProject: params.homeProject } : {}
|
|
20826
|
+
};
|
|
20827
|
+
return {
|
|
20828
|
+
...projected,
|
|
20829
|
+
rowRevision: hashValue(projected)
|
|
20830
|
+
};
|
|
20831
|
+
}
|
|
20832
|
+
function projectDirectoryGitStatus(directory) {
|
|
20833
|
+
const status = directory.gitStatus;
|
|
20834
|
+
if (!status) {
|
|
20835
|
+
return void 0;
|
|
20836
|
+
}
|
|
20837
|
+
return {
|
|
20838
|
+
...status.currentBranch ? { currentBranch: status.currentBranch } : {},
|
|
20839
|
+
...status.defaultBranch ? { defaultBranch: status.defaultBranch } : {},
|
|
20840
|
+
...status.upstreamBranch ? { upstreamBranch: status.upstreamBranch } : {},
|
|
20841
|
+
...status.ahead !== void 0 ? { ahead: status.ahead } : {},
|
|
20842
|
+
...status.behind !== void 0 ? { behind: status.behind } : {},
|
|
20843
|
+
...status.syncState ? { syncState: status.syncState } : {},
|
|
20844
|
+
...status.statusUnavailableReason ? { statusUnavailableReason: status.statusUnavailableReason } : {},
|
|
20845
|
+
...status.worktreeCreationAvailable !== void 0 ? { worktreeCreationAvailable: status.worktreeCreationAvailable } : {},
|
|
20846
|
+
...status.worktreeCreationUnavailableReason ? {
|
|
20847
|
+
worktreeCreationUnavailableReason: status.worktreeCreationUnavailableReason
|
|
20848
|
+
} : {}
|
|
20849
|
+
};
|
|
20850
|
+
}
|
|
20851
|
+
function buildDirectoryRows(params) {
|
|
20852
|
+
return params.snapshot.directories.map((directory) => {
|
|
20853
|
+
const memberThreads = directoryMemberThreads(directory, params.threadsByLegacyKey);
|
|
20854
|
+
const parentOf = directoryParentResolver(memberThreads, params.parentCandidates);
|
|
20855
|
+
const rootThreads = memberThreads.filter((thread) => !parentOf(thread));
|
|
20856
|
+
const pinnedRootCount = rootThreads.filter((thread) => thread.pinnedRank).length;
|
|
20857
|
+
const gitStatus = projectDirectoryGitStatus(directory);
|
|
20858
|
+
const repositoryKey = directory.gitStatus?.originRepository ?? directory.repositoryKey;
|
|
20859
|
+
const repositoryKeys = directory.gitStatus?.remoteRepositories?.length ? directory.gitStatus.remoteRepositories : directory.repositoryKeys;
|
|
20860
|
+
return {
|
|
20861
|
+
key: directory.key,
|
|
20862
|
+
kind: directory.kind,
|
|
20863
|
+
label: directory.label,
|
|
20864
|
+
...directory.path ? { path: directory.path } : {},
|
|
20865
|
+
...repositoryKey ? { repositoryKey } : {},
|
|
20866
|
+
...repositoryKeys?.length ? { repositoryKeys } : {},
|
|
20867
|
+
...directory.localAvailability ? { localAvailability: directory.localAvailability } : {},
|
|
20868
|
+
counts: countsForThreads(memberThreads),
|
|
20869
|
+
pinnedRootCount,
|
|
20870
|
+
unpinnedRootCount: rootThreads.length - pinnedRootCount,
|
|
20871
|
+
...directory.latestUpdatedAt !== void 0 ? { latestUpdatedAt: directory.latestUpdatedAt } : {},
|
|
20872
|
+
...directory.pinnedRank ? { pinnedRank: directory.pinnedRank } : {},
|
|
20873
|
+
...directory.directoryThreadsCollapsed !== void 0 ? { directoryThreadsCollapsed: directory.directoryThreadsCollapsed } : {},
|
|
20874
|
+
// A placeholder row stands for a peer's project. It shows that
|
|
20875
|
+
// project's setting only when its threads name one project on one peer.
|
|
20876
|
+
...directory.forgeAutomation && (!directory.localAvailability || directory.peerProject) ? { forgeAutomation: directory.forgeAutomation } : {},
|
|
20877
|
+
...directory.localAvailability && directory.peerProject ? { peerProject: directory.peerProject } : {},
|
|
20878
|
+
...gitStatus ? { gitStatus } : {},
|
|
20879
|
+
launchpadPresent: Boolean(directory.launchpad),
|
|
20880
|
+
...directory.launchpad?.backend ? { launchpadBackend: directory.launchpad.backend } : {}
|
|
20881
|
+
};
|
|
20882
|
+
});
|
|
20883
|
+
}
|
|
20884
|
+
function repositoryIdentitiesByPath(index) {
|
|
20885
|
+
const identities = /* @__PURE__ */ new Map();
|
|
20886
|
+
for (const directory of index.directories) {
|
|
20887
|
+
const path11 = directory.path;
|
|
20888
|
+
const repositoryKey = directory.gitStatus?.originRepository;
|
|
20889
|
+
const repositoryKeys = directory.gitStatus?.remoteRepositories;
|
|
20890
|
+
if (!path11 || !repositoryKey && !repositoryKeys?.length) continue;
|
|
20891
|
+
identities.set(path11, {
|
|
20892
|
+
...repositoryKey ? { repositoryKey } : {},
|
|
20893
|
+
...repositoryKeys?.length ? { repositoryKeys } : {}
|
|
20894
|
+
});
|
|
20895
|
+
}
|
|
20896
|
+
return identities;
|
|
20897
|
+
}
|
|
20898
|
+
function withRepositoryIdentities(directories, repositories) {
|
|
20899
|
+
if (!repositories.size) return directories;
|
|
20900
|
+
return directories.map((directory) => {
|
|
20901
|
+
const descriptor = classifyDirectory(directory);
|
|
20902
|
+
if (descriptor.kind !== "directory") return directory;
|
|
20903
|
+
const identity = (descriptor.path ? repositories.get(descriptor.path) : void 0) ?? repositories.get(directory.path);
|
|
20904
|
+
return identity ? { ...directory, ...identity } : directory;
|
|
20905
|
+
});
|
|
20906
|
+
}
|
|
20907
|
+
function buildProjectGeometry(index) {
|
|
20908
|
+
const projects = /* @__PURE__ */ new Map();
|
|
20909
|
+
const launchpads = new Set(index.directories.filter((directory) => directory.launchpad).map((directory) => directory.key));
|
|
20910
|
+
const repositories = repositoryIdentitiesByPath(index);
|
|
20911
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20912
|
+
for (const thread of index.threads) {
|
|
20913
|
+
if (!isStarMapOwnerThread(thread) || seen.has(threadKey(thread))) continue;
|
|
20914
|
+
seen.add(threadKey(thread));
|
|
20915
|
+
const primary = thread.linkedDirectories[0];
|
|
20916
|
+
const descriptor = primary ? classifyDirectory(primary) : void 0;
|
|
20917
|
+
const key = descriptor?.key ?? "__no-project__";
|
|
20918
|
+
let project = projects.get(key);
|
|
20919
|
+
if (!project) {
|
|
20920
|
+
const segments = (descriptor?.path ?? primary?.path)?.split(/[\\/]/).filter(Boolean);
|
|
20921
|
+
const repositoryKey = (descriptor?.path ? repositories.get(descriptor.path)?.repositoryKey : void 0) ?? (primary?.path ? repositories.get(primary.path)?.repositoryKey : void 0);
|
|
20922
|
+
project = {
|
|
20923
|
+
key,
|
|
20924
|
+
kind: descriptor?.kind ?? "unlinked",
|
|
20925
|
+
label: descriptor ? descriptor.label !== primary?.label ? descriptor.label : segments?.at(-1) ?? descriptor.label : "No project",
|
|
20926
|
+
path: descriptor?.path,
|
|
20927
|
+
...repositoryKey ? { repositoryKey } : {},
|
|
20928
|
+
counts: { total: 0, active: 0, unread: 0, review: 0 },
|
|
20929
|
+
pinnedRootCount: 0,
|
|
20930
|
+
unpinnedRootCount: 0,
|
|
20931
|
+
launchpadPresent: launchpads.has(key)
|
|
20932
|
+
};
|
|
20933
|
+
projects.set(key, project);
|
|
20934
|
+
}
|
|
20935
|
+
project.counts.total += 1;
|
|
20936
|
+
const active = isActive(thread);
|
|
20937
|
+
if (active) project.counts.active += 1;
|
|
20938
|
+
if (thread.inbox.inInbox) {
|
|
20939
|
+
project.counts.unread += 1;
|
|
20940
|
+
if (!active) project.counts.review += 1;
|
|
20941
|
+
}
|
|
20942
|
+
if (!thread.parentThreadId) {
|
|
20943
|
+
if (thread.pinnedRank !== void 0) project.pinnedRootCount += 1;
|
|
20944
|
+
else project.unpinnedRootCount += 1;
|
|
20945
|
+
}
|
|
20946
|
+
project.latestUpdatedAt = Math.max(project.latestUpdatedAt ?? 0, thread.updatedAt ?? 0);
|
|
20947
|
+
}
|
|
20948
|
+
return [...projects.values()].sort((left, right) => left.key.localeCompare(right.key));
|
|
20949
|
+
}
|
|
20950
|
+
function normalizeQuery(query) {
|
|
20951
|
+
if (query.kind === "group-members") return { ...query, roots: [...query.roots].sort((left, right) => identityKey(left).localeCompare(identityKey(right))) };
|
|
20952
|
+
if (query.kind === "messaging-threads" || query.kind === "messaging-projects") {
|
|
20953
|
+
return {
|
|
20954
|
+
...query,
|
|
20955
|
+
filter: query.filter?.trim().toLowerCase() || void 0,
|
|
20956
|
+
...query.allowedBackends ? { allowedBackends: [...new Set(query.allowedBackends)].sort() } : {}
|
|
20957
|
+
};
|
|
20958
|
+
}
|
|
20959
|
+
if (query.kind === "star-map") {
|
|
20960
|
+
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))) };
|
|
20961
|
+
}
|
|
20962
|
+
if (query.kind === "lens" || query.kind === "directory-index") {
|
|
20963
|
+
return {
|
|
20964
|
+
...query,
|
|
20965
|
+
...query.kind === "directory-index" && query.keys ? { keys: [...new Set(query.keys)].sort() } : {},
|
|
20966
|
+
...query.kind === "directory-index" && query.paths ? { paths: [...new Set(query.paths)].sort() } : {},
|
|
20967
|
+
...query.filter?.trim().toLowerCase() ? { filter: query.filter.trim().toLowerCase() } : { filter: void 0 }
|
|
20968
|
+
};
|
|
20969
|
+
}
|
|
20970
|
+
if (query.kind === "search") {
|
|
20971
|
+
return { ...query, text: query.text.trim().toLowerCase() };
|
|
20972
|
+
}
|
|
20973
|
+
if (query.kind === "directory") {
|
|
20974
|
+
return {
|
|
20975
|
+
...query,
|
|
20976
|
+
roots: query.roots ?? "all",
|
|
20977
|
+
disclosedParentThreadKeys: [
|
|
20978
|
+
...new Set(query.disclosedParentThreadKeys ?? [])
|
|
20979
|
+
].sort()
|
|
20980
|
+
};
|
|
20981
|
+
}
|
|
20982
|
+
if (query.kind === "exact") {
|
|
20983
|
+
return {
|
|
20984
|
+
...query,
|
|
20985
|
+
identities: [...query.identities].sort((left, right) => identityKey(left).localeCompare(identityKey(right)))
|
|
20986
|
+
};
|
|
20987
|
+
}
|
|
20988
|
+
return query;
|
|
20989
|
+
}
|
|
20990
|
+
function navigationQueryKey(request) {
|
|
20991
|
+
return hashValue({
|
|
20992
|
+
inventory: request.inventory ?? "owner",
|
|
20993
|
+
backend: request.backend ?? "all",
|
|
20994
|
+
attentionView: request.attentionView,
|
|
20995
|
+
query: normalizeQuery(request.query)
|
|
20996
|
+
});
|
|
20997
|
+
}
|
|
20998
|
+
function rowOrderKey(index) {
|
|
20999
|
+
return index.toString(36).padStart(10, "0");
|
|
21000
|
+
}
|
|
21001
|
+
function compareUpdated(left, right) {
|
|
21002
|
+
return (right.updatedAt ?? 0) - (left.updatedAt ?? 0) || threadKey(left).localeCompare(threadKey(right));
|
|
21003
|
+
}
|
|
21004
|
+
function compareCreated(left, right) {
|
|
21005
|
+
return (right.createdAt ?? 0) - (left.createdAt ?? 0) || threadKey(left).localeCompare(threadKey(right));
|
|
21006
|
+
}
|
|
21007
|
+
function compareDirectoryMembers(left, right) {
|
|
21008
|
+
if (Boolean(left.pinnedRank) !== Boolean(right.pinnedRank)) {
|
|
21009
|
+
return left.pinnedRank ? -1 : 1;
|
|
21010
|
+
}
|
|
21011
|
+
if (left.pinnedRank && right.pinnedRank) {
|
|
21012
|
+
const pinned = comparePinnedThreads(left, right);
|
|
21013
|
+
if (pinned !== 0) return pinned;
|
|
21014
|
+
}
|
|
21015
|
+
return compareCreated(left, right);
|
|
21016
|
+
}
|
|
21017
|
+
function isStarMapOwnerThread(thread) {
|
|
21018
|
+
return isOrdinaryThread(thread) && thread.archivedAt === void 0 && thread.federation?.ref.target.scope !== "remote";
|
|
21019
|
+
}
|
|
21020
|
+
function starMapProjectKey(thread) {
|
|
21021
|
+
const primary = thread.linkedDirectories[0];
|
|
21022
|
+
return primary ? classifyDirectory(primary).key : "__no-project__";
|
|
21023
|
+
}
|
|
21024
|
+
function starMapSignals(thread, index) {
|
|
21025
|
+
const active = isActive(thread);
|
|
21026
|
+
const unread = thread.inbox.inInbox && thread.inbox.reason === "updated-since-seen";
|
|
21027
|
+
return {
|
|
21028
|
+
active,
|
|
21029
|
+
unread,
|
|
21030
|
+
attention: active || unread,
|
|
21031
|
+
approval: !thread.federation && (index.inputRequestThreadKeys?.has(buildThreadIdentityKey(thread.source, thread.id)) ?? false),
|
|
21032
|
+
pr: thread.prs?.some((pr2) => pr2.state !== "merged" && pr2.state !== "closed" && pr2.lifecycleState !== "merged" && pr2.lifecycleState !== "closed") ?? false,
|
|
21033
|
+
unpushed: (thread.gitWorkingState?.unpushedCommits ?? 0) > 0,
|
|
21034
|
+
pinned: thread.pinnedRank !== void 0,
|
|
21035
|
+
agent: thread.agent !== void 0
|
|
21036
|
+
};
|
|
21037
|
+
}
|
|
21038
|
+
function selectQueryThreads(params) {
|
|
21039
|
+
const ordinaryThreads = params.index.threads.filter(isOrdinaryThread);
|
|
21040
|
+
const query = params.query;
|
|
21041
|
+
if (query.kind === "messaging-threads" || query.kind === "messaging-projects") {
|
|
21042
|
+
const directory = query.directoryKey ? params.index.directories.find((candidate) => candidate.key === query.directoryKey || candidate.path === query.directoryKey) : void 0;
|
|
21043
|
+
const members = directory ? new Set(directory.threadKeys.flatMap((key) => {
|
|
21044
|
+
const thread = params.threadsByLegacyKey.get(key);
|
|
21045
|
+
return thread ? [threadKey(thread)] : [];
|
|
21046
|
+
})) : void 0;
|
|
21047
|
+
const filter = query.kind === "messaging-threads" ? query.filter?.trim().toLowerCase() : void 0;
|
|
21048
|
+
return ordinaryThreads.filter((thread) => {
|
|
21049
|
+
if (query.agentOnly && !thread.agent) return false;
|
|
21050
|
+
if (query.excludeFullAccess && thread.executionMode === "full-access") return false;
|
|
21051
|
+
if (query.allowedBackends && !query.allowedBackends.includes(thread.source)) return false;
|
|
21052
|
+
if (query.directoryKey && (!members || !members.has(threadKey(thread)))) return false;
|
|
21053
|
+
return !filter || [
|
|
21054
|
+
thread.id,
|
|
21055
|
+
thread.title,
|
|
21056
|
+
thread.summary,
|
|
21057
|
+
thread.agent?.name,
|
|
21058
|
+
thread.agent?.instructions,
|
|
21059
|
+
thread.projectKey,
|
|
21060
|
+
...thread.linkedDirectories.flatMap((linked) => [linked.label, linked.path, linked.worktreePath])
|
|
21061
|
+
].some((value) => value?.toLowerCase().includes(filter));
|
|
21062
|
+
}).sort(compareUpdated);
|
|
21063
|
+
}
|
|
21064
|
+
if (query.kind === "directory-index" || query.kind === "star-map-geometry" || query.kind === "model-inventory") {
|
|
21065
|
+
return [];
|
|
21066
|
+
}
|
|
21067
|
+
if (query.kind === "star-map") {
|
|
21068
|
+
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) => {
|
|
21069
|
+
if (left.pinnedRank !== void 0 !== (right.pinnedRank !== void 0)) return left.pinnedRank !== void 0 ? -1 : 1;
|
|
21070
|
+
return left.pinnedRank !== void 0 && right.pinnedRank !== void 0 ? comparePinnedThreads(left, right) : compareUpdated(left, right);
|
|
21071
|
+
});
|
|
21072
|
+
}
|
|
21073
|
+
if (query.kind === "lens") {
|
|
21074
|
+
const filter = query.filter?.trim().toLowerCase();
|
|
21075
|
+
const rootOf = params.lensRootOf;
|
|
21076
|
+
const threads = ordinaryThreads.filter((thread) => {
|
|
21077
|
+
if (query.lens === "attention") {
|
|
21078
|
+
if (!isActive(thread) && !thread.inbox.inInbox) return false;
|
|
21079
|
+
}
|
|
21080
|
+
if (rootOf && rootOf(thread).pinnedRank !== void 0 !== (query.roots === "pinned")) return false;
|
|
21081
|
+
if (!filter) return true;
|
|
21082
|
+
return thread.title.toLowerCase().includes(filter) || thread.linkedDirectories.some((directory) => directory.path.toLowerCase().includes(filter));
|
|
21083
|
+
});
|
|
21084
|
+
if (rootOf && query.roots === "pinned") {
|
|
21085
|
+
return threads.sort((left, right) => {
|
|
21086
|
+
const leftRoot = rootOf(left);
|
|
21087
|
+
const rightRoot = rootOf(right);
|
|
21088
|
+
if (leftRoot !== rightRoot) return comparePinnedThreads(leftRoot, rightRoot);
|
|
21089
|
+
if (left === leftRoot !== (right === rightRoot)) return left === leftRoot ? -1 : 1;
|
|
21090
|
+
return compareCreated(left, right);
|
|
21091
|
+
});
|
|
21092
|
+
}
|
|
21093
|
+
return threads.sort(query.lens === "recents" ? compareCreated : compareUpdated);
|
|
21094
|
+
}
|
|
21095
|
+
if (query.kind === "directory") {
|
|
21096
|
+
const directory = params.index.directories.find(
|
|
21097
|
+
(candidate) => candidate.key === query.directoryKey
|
|
21098
|
+
);
|
|
21099
|
+
if (!directory) return [];
|
|
21100
|
+
const disclosedParents = new Set(query.disclosedParentThreadKeys ?? []);
|
|
21101
|
+
const members = directoryMemberThreads(directory, params.threadsByLegacyKey);
|
|
21102
|
+
const parentOf = directoryParentResolver(members, params.parentCandidates);
|
|
21103
|
+
return members.filter((thread) => {
|
|
21104
|
+
const parent = parentOf(thread);
|
|
21105
|
+
if (parent) return disclosedParents.has(buildThreadIdentityKey(parent.backend, parent.threadId));
|
|
21106
|
+
return query.roots === "pinned" ? thread.pinnedRank !== void 0 : query.roots === "unpinned" ? thread.pinnedRank === void 0 : true;
|
|
21107
|
+
}).sort(compareDirectoryMembers);
|
|
21108
|
+
}
|
|
21109
|
+
if (query.kind === "group-members") {
|
|
21110
|
+
const ordinary = new Set(ordinaryThreads);
|
|
21111
|
+
const children = /* @__PURE__ */ new Map();
|
|
21112
|
+
for (const thread of ordinaryThreads) {
|
|
21113
|
+
const parent = parentIdentity(thread, params.parentCandidates);
|
|
21114
|
+
if (!parent) continue;
|
|
21115
|
+
const key = identityKey(parent);
|
|
21116
|
+
const members = children.get(key) ?? [];
|
|
21117
|
+
members.push(thread);
|
|
21118
|
+
children.set(key, members);
|
|
21119
|
+
}
|
|
21120
|
+
const result = [];
|
|
21121
|
+
const visited = /* @__PURE__ */ new Set();
|
|
21122
|
+
const visiting2 = /* @__PURE__ */ new Set();
|
|
21123
|
+
const visit = (ref, depth) => {
|
|
21124
|
+
const key = identityKey(ref);
|
|
21125
|
+
if (visited.has(key)) return;
|
|
21126
|
+
if (visiting2.has(key) || depth > 32) throw new Error("Thread group has a cycle or exceeds the 32-level group budget.");
|
|
21127
|
+
visiting2.add(key);
|
|
21128
|
+
for (const child of children.get(key) ?? []) visit(navigationIdentity(child), depth + 1);
|
|
21129
|
+
visiting2.delete(key);
|
|
21130
|
+
visited.add(key);
|
|
21131
|
+
const thread = params.threadsByIdentity.get(key);
|
|
21132
|
+
if (thread && ordinary.has(thread)) result.push(thread);
|
|
21133
|
+
};
|
|
21134
|
+
for (const root of query.roots) visit(root, 0);
|
|
21135
|
+
return result;
|
|
21136
|
+
}
|
|
21137
|
+
if (query.kind === "children") {
|
|
21138
|
+
const parentThread = params.threadsByIdentity.get(identityKey(query.parent));
|
|
21139
|
+
const children = ordinaryThreads.filter((thread) => {
|
|
21140
|
+
const parent = parentIdentity(thread, params.parentCandidates);
|
|
21141
|
+
return parent && identityKey(parent) === identityKey(query.parent);
|
|
21142
|
+
});
|
|
21143
|
+
return sortSubthreadSummaries(parentThread ?? {}, children);
|
|
21144
|
+
}
|
|
21145
|
+
if (query.kind === "search") {
|
|
21146
|
+
const text = query.text.trim().toLowerCase();
|
|
21147
|
+
if (!text) return [];
|
|
21148
|
+
return rankThreadJumpMatches(ordinaryThreads, text);
|
|
21149
|
+
}
|
|
21150
|
+
const selected = /* @__PURE__ */ new Map();
|
|
21151
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
21152
|
+
const addWithAncestry = (thread) => {
|
|
21153
|
+
const key = threadKey(thread);
|
|
21154
|
+
if (selected.has(key) || visiting.has(key)) return;
|
|
21155
|
+
visiting.add(key);
|
|
21156
|
+
const parent = parentIdentity(thread, params.parentCandidates);
|
|
21157
|
+
if (query.includeAncestry && parent) {
|
|
21158
|
+
const parentThread = params.threadsByIdentity.get(identityKey(parent));
|
|
21159
|
+
if (parentThread) addWithAncestry(parentThread);
|
|
21160
|
+
}
|
|
21161
|
+
selected.set(key, thread);
|
|
21162
|
+
visiting.delete(key);
|
|
21163
|
+
};
|
|
21164
|
+
for (const identity of query.identities.slice(0, NAVIGATION_QUERY_MAX_PAGE_ROWS)) {
|
|
21165
|
+
const thread = params.threadsByIdentity.get(identityKey(identity));
|
|
21166
|
+
if (thread) addWithAncestry(thread);
|
|
21167
|
+
}
|
|
21168
|
+
return [...selected.values()];
|
|
21169
|
+
}
|
|
21170
|
+
function buildModelInventory(threads) {
|
|
21171
|
+
const groups = /* @__PURE__ */ new Map();
|
|
21172
|
+
for (const thread of threads) {
|
|
21173
|
+
const model = thread.model?.trim() || void 0;
|
|
21174
|
+
const key = JSON.stringify([thread.source, model, thread.modelMigrationRevision]);
|
|
21175
|
+
const group = groups.get(key) ?? { backend: thread.source, model, modelMigrationRevision: thread.modelMigrationRevision, threadCount: 0, fastThreadCount: 0 };
|
|
21176
|
+
group.threadCount += 1;
|
|
21177
|
+
if (thread.fastMode === true || thread.serviceTier === "ultrafast") group.fastThreadCount += 1;
|
|
21178
|
+
groups.set(key, group);
|
|
21179
|
+
}
|
|
21180
|
+
return [...groups].sort(([left], [right]) => left.localeCompare(right)).map(([, group]) => group);
|
|
21181
|
+
}
|
|
21182
|
+
function buildHomeProjects(directories, threadsByLegacyKey) {
|
|
21183
|
+
const homes = /* @__PURE__ */ new Map();
|
|
21184
|
+
for (const directory of directories) {
|
|
21185
|
+
if (directory.kind !== "directory" || !directory.key.startsWith("directory:") || directory.localAvailability) continue;
|
|
21186
|
+
const home = {
|
|
21187
|
+
directoryKey: directory.key,
|
|
21188
|
+
...directory.forgeAutomation ? { forgeAutomation: directory.forgeAutomation } : {}
|
|
21189
|
+
};
|
|
21190
|
+
for (const key of directory.threadKeys) {
|
|
21191
|
+
const thread = threadsByLegacyKey.get(key);
|
|
21192
|
+
if (thread && !thread.federation && !homes.has(thread)) homes.set(thread, home);
|
|
21193
|
+
}
|
|
21194
|
+
}
|
|
21195
|
+
return homes;
|
|
21196
|
+
}
|
|
21197
|
+
function buildThreadsByLegacyKey(index) {
|
|
21198
|
+
return new Map(
|
|
21199
|
+
index.threads.map((thread) => [
|
|
21200
|
+
thread.federation?.ref.target.scope === "remote" ? federatedThreadIdentityKey(thread.federation.ref) : buildThreadIdentityKey(thread.source, thread.id),
|
|
21201
|
+
thread
|
|
21202
|
+
])
|
|
21203
|
+
);
|
|
21204
|
+
}
|
|
21205
|
+
function buildParentCandidates(index) {
|
|
21206
|
+
const parentCandidates = /* @__PURE__ */ new Map();
|
|
21207
|
+
for (const thread of index.threads) {
|
|
21208
|
+
const ref = navigationIdentity(thread);
|
|
21209
|
+
const key = JSON.stringify([ref.ownerInstanceId ?? null, ref.threadId]);
|
|
21210
|
+
const candidates = parentCandidates.get(key) ?? [];
|
|
21211
|
+
candidates.push(thread);
|
|
21212
|
+
parentCandidates.set(key, candidates);
|
|
21213
|
+
if (!ref.ownerInstanceId && index.localInstanceId) {
|
|
21214
|
+
parentCandidates.set(JSON.stringify([index.localInstanceId, ref.threadId]), candidates);
|
|
21215
|
+
}
|
|
21216
|
+
}
|
|
21217
|
+
return parentCandidates;
|
|
21218
|
+
}
|
|
21219
|
+
function explainMissingNavigationAnchor(params) {
|
|
21220
|
+
const { anchor, query } = params.request;
|
|
21221
|
+
if (anchor?.kind !== "thread") {
|
|
21222
|
+
return anchor && !params.index.directories.some((directory2) => directory2.key === anchor.key) ? "directory-not-indexed" : "filtered";
|
|
21223
|
+
}
|
|
21224
|
+
const thread = params.index.threads.find((candidate) => threadKey(candidate) === identityKey(anchor.ref));
|
|
21225
|
+
if (!thread) {
|
|
21226
|
+
const sameId = params.index.threads.filter((candidate) => candidate.source === anchor.ref.backend && candidate.id === anchor.ref.threadId);
|
|
21227
|
+
if (anchor.ref.ownerInstanceId && anchor.ref.ownerInstanceId === params.index.localInstanceId && sameId.some((candidate) => !navigationIdentity(candidate).ownerInstanceId)) return "ref-mismatch";
|
|
21228
|
+
return sameId.length ? "other-owner" : "not-indexed";
|
|
21229
|
+
}
|
|
21230
|
+
if (query.kind !== "directory") return thread.archivedAt !== void 0 ? "archived" : "filtered";
|
|
21231
|
+
const directory = params.index.directories.find((candidate) => candidate.key === query.directoryKey);
|
|
21232
|
+
if (!directory) return "directory-not-indexed";
|
|
21233
|
+
const threadsByLegacyKey = buildThreadsByLegacyKey(params.index);
|
|
21234
|
+
if (!directory.threadKeys.some((key) => threadsByLegacyKey.get(key) === thread)) return "other-directory";
|
|
21235
|
+
const parent = directoryParentResolver(directoryMemberThreads(directory, threadsByLegacyKey), buildParentCandidates(params.index))(thread);
|
|
21236
|
+
if (parent) {
|
|
21237
|
+
return query.disclosedParentThreadKeys?.includes(buildThreadIdentityKey(parent.backend, parent.threadId)) ? "ref-mismatch" : "child";
|
|
21238
|
+
}
|
|
21239
|
+
if (query.roots === "unpinned" && thread.pinnedRank !== void 0) return "pinned";
|
|
21240
|
+
if (query.roots === "pinned" && thread.pinnedRank === void 0) return "unpinned";
|
|
21241
|
+
return "ref-mismatch";
|
|
21242
|
+
}
|
|
21243
|
+
function projectNavigationQuery(params) {
|
|
21244
|
+
const query = params.request.query;
|
|
21245
|
+
if (query.kind === "model-inventory") {
|
|
21246
|
+
const owned = params.index.threads.filter((thread) => isStarMapOwnerThread(thread) && (!params.request.backend || params.request.backend === "all" || params.request.backend === thread.source));
|
|
21247
|
+
return { coverage: params.index.coverage ?? { state: "complete" }, counts: countsForThreads(owned), directories: [], entries: [], modelGroups: buildModelInventory(owned), queryKey: navigationQueryKey(params.request) };
|
|
21248
|
+
}
|
|
21249
|
+
const threadsByIdentity = new Map(
|
|
21250
|
+
params.index.threads.map((thread) => [threadKey(thread), thread])
|
|
21251
|
+
);
|
|
21252
|
+
const threadsByLegacyKey = buildThreadsByLegacyKey(params.index);
|
|
21253
|
+
const parentCandidates = buildParentCandidates(params.index);
|
|
21254
|
+
const repositories = repositoryIdentitiesByPath(params.index);
|
|
21255
|
+
const childCountByParent = /* @__PURE__ */ new Map();
|
|
21256
|
+
const viewerChildCountByParent = /* @__PURE__ */ new Map();
|
|
21257
|
+
for (const thread of params.index.threads) {
|
|
21258
|
+
const parent = parentIdentity(thread, parentCandidates);
|
|
21259
|
+
if (!parent || !isOrdinaryThread(thread)) continue;
|
|
21260
|
+
const key = identityKey(parent);
|
|
21261
|
+
childCountByParent.set(key, (childCountByParent.get(key) ?? 0) + 1);
|
|
21262
|
+
if (parent.ownerInstanceId && navigationIdentity(thread).ownerInstanceId !== parent.ownerInstanceId) {
|
|
21263
|
+
viewerChildCountByParent.set(key, (viewerChildCountByParent.get(key) ?? 0) + 1);
|
|
21264
|
+
}
|
|
21265
|
+
}
|
|
21266
|
+
const lensRootOf = query.kind === "lens" && query.roots ? lensRootResolver({ threadsByIdentity, parentCandidates }) : void 0;
|
|
21267
|
+
const selectedThreads = selectQueryThreads({
|
|
21268
|
+
query,
|
|
21269
|
+
index: params.index,
|
|
21270
|
+
threadsByIdentity,
|
|
21271
|
+
threadsByLegacyKey,
|
|
21272
|
+
parentCandidates,
|
|
21273
|
+
lensRootOf
|
|
21274
|
+
});
|
|
21275
|
+
if ((query.kind === "lens" && query.lens === "attention" || query.kind === "star-map" && query.filters.attention === "include") && params.attentionOrder) {
|
|
21276
|
+
const members = params.attentionOrder.members;
|
|
21277
|
+
selectedThreads.sort((left, right) => {
|
|
21278
|
+
if (query.kind === "star-map") {
|
|
21279
|
+
if (left.pinnedRank !== void 0 !== (right.pinnedRank !== void 0)) return left.pinnedRank !== void 0 ? -1 : 1;
|
|
21280
|
+
if (left.pinnedRank !== void 0 && right.pinnedRank !== void 0) return comparePinnedThreads(left, right);
|
|
21281
|
+
}
|
|
21282
|
+
return (members.get(navigationAttentionIdentity(right))?.rank ?? 0) - (members.get(navigationAttentionIdentity(left))?.rank ?? 0);
|
|
21283
|
+
});
|
|
21284
|
+
}
|
|
21285
|
+
if (query.kind === "messaging-projects") {
|
|
21286
|
+
const eligibleKeys = new Set(selectedThreads.map(threadKey));
|
|
21287
|
+
const eligibleByLegacyKey = new Map([...threadsByLegacyKey].filter(([, thread]) => eligibleKeys.has(threadKey(thread))));
|
|
21288
|
+
let sourceDirectories = params.index.directories;
|
|
21289
|
+
if (query.scratchpadFirst) {
|
|
21290
|
+
const scratchpads = sourceDirectories.filter((directory) => directory.kind === "workspace").sort((left, right) => (right.latestUpdatedAt ?? 0) - (left.latestUpdatedAt ?? 0) || left.key.localeCompare(right.key));
|
|
21291
|
+
if (scratchpads.length > 1) {
|
|
21292
|
+
sourceDirectories = [
|
|
21293
|
+
{ ...scratchpads[0], threadKeys: [...new Set(scratchpads.flatMap((directory) => directory.threadKeys))] },
|
|
21294
|
+
...sourceDirectories.filter((directory) => directory.kind !== "workspace")
|
|
21295
|
+
];
|
|
21296
|
+
}
|
|
21297
|
+
}
|
|
21298
|
+
const filter = query.filter?.trim().toLowerCase();
|
|
21299
|
+
const directories = buildDirectoryRows({ snapshot: { ...params.index, directories: sourceDirectories }, threadsByLegacyKey: eligibleByLegacyKey, parentCandidates }).filter((directory) => !filter || [
|
|
21300
|
+
directory.key,
|
|
21301
|
+
directory.label,
|
|
21302
|
+
directory.path,
|
|
21303
|
+
query.scratchpadFirst && directory.kind === "workspace" ? "Workspaces Scratchpad" : void 0
|
|
21304
|
+
].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));
|
|
21305
|
+
return {
|
|
21306
|
+
coverage: params.index.coverage ?? { state: "complete" },
|
|
21307
|
+
counts: countsForThreads(selectedThreads),
|
|
21308
|
+
collectionSize: directories.length,
|
|
21309
|
+
collectionKind: "directories",
|
|
21310
|
+
directories,
|
|
21311
|
+
entries: [],
|
|
21312
|
+
queryKey: navigationQueryKey(params.request)
|
|
21313
|
+
};
|
|
21314
|
+
}
|
|
21315
|
+
const attentionMemberKeys = query.kind === "lens" && query.lens === "attention" ? new Set(selectedThreads.map(threadKey)) : void 0;
|
|
21316
|
+
const directoryMembers = query.kind === "directory" ? directoryMemberThreads(params.index.directories.find((directory) => directory.key === query.directoryKey), threadsByLegacyKey) : void 0;
|
|
21317
|
+
const directoryParentOf = directoryMembers ? directoryParentResolver(directoryMembers, parentCandidates) : void 0;
|
|
21318
|
+
const homeProjects = query.kind === "group-members" ? buildHomeProjects(params.index.directories, threadsByLegacyKey) : void 0;
|
|
21319
|
+
const entries = selectedThreads.map((thread, index) => {
|
|
21320
|
+
const parent = parentIdentity(thread, parentCandidates);
|
|
21321
|
+
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;
|
|
21322
|
+
return {
|
|
21323
|
+
row: projectNavigationRow({
|
|
21324
|
+
childCount: childCountByParent.get(threadKey(thread)) ?? 0,
|
|
21325
|
+
...params.request.inventory === "viewer" && navigationIdentity(thread).ownerInstanceId ? { viewerChildCount: viewerChildCountByParent.get(threadKey(thread)) ?? 0 } : {},
|
|
21326
|
+
needsInput: starMapSignals(thread, params.index).approval,
|
|
21327
|
+
repositories,
|
|
21328
|
+
thread: parent?.ownerInstanceId && !thread.parentThreadInstanceId ? { ...thread, parentThreadInstanceId: parent.ownerInstanceId } : thread,
|
|
21329
|
+
homeProject: homeProjects?.get(thread)
|
|
21330
|
+
}),
|
|
21331
|
+
orderKey: rowOrderKey(index),
|
|
21332
|
+
...params.attentionOrder?.members.has(navigationAttentionIdentity(thread)) ? { attentionRank: params.attentionOrder.members.get(navigationAttentionIdentity(thread)).rank } : {},
|
|
21333
|
+
placement: placementParent ? { kind: "child", parent: placementParent } : { kind: "root" }
|
|
21334
|
+
};
|
|
21335
|
+
});
|
|
21336
|
+
const includeDirectories = query.kind === "directory-index" || query.kind === "star-map-geometry";
|
|
21337
|
+
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;
|
|
21338
|
+
return {
|
|
21339
|
+
coverage: params.index.coverage ?? { state: "complete" },
|
|
21340
|
+
// Both pages count every owner thread, so both can say which providers
|
|
21341
|
+
// the owner uses. Provider chips hide while that is a single provider.
|
|
21342
|
+
counts: countsForThreads(countsThreads, {
|
|
21343
|
+
backends: query.kind === "directory-index" || query.kind === "star-map" && query.projectKey === void 0
|
|
21344
|
+
}),
|
|
21345
|
+
...query.kind === "directory" ? {
|
|
21346
|
+
selectionDirectory: buildDirectoryRows({ snapshot: {
|
|
21347
|
+
...params.index,
|
|
21348
|
+
directories: params.index.directories.filter((directory) => directory.key === query.directoryKey)
|
|
21349
|
+
}, threadsByLegacyKey, parentCandidates })[0]
|
|
21350
|
+
} : {},
|
|
21351
|
+
...query.kind === "exact" && selectedThreads.length ? {
|
|
21352
|
+
// Resolve the canonical home from the complete compact index. A linked
|
|
21353
|
+
// worktree path is not necessarily the directory key shown by the viewer.
|
|
21354
|
+
selectionDirectory: buildDirectoryRows({ snapshot: {
|
|
21355
|
+
...params.index,
|
|
21356
|
+
directories: params.index.directories.filter((directory) => directory.threadKeys.some((key) => selectedThreads[0] === threadsByLegacyKey.get(key)))
|
|
21357
|
+
}, threadsByLegacyKey, parentCandidates })[0]
|
|
21358
|
+
} : {},
|
|
21359
|
+
// The split's acknowledgment as well as its size: an owner that predates
|
|
21360
|
+
// `roots` sets no collection size on a lens page, so the viewer knows to
|
|
21361
|
+
// filter that page itself.
|
|
21362
|
+
...lensRootOf ? { collectionSize: selectedThreads.filter((thread) => lensRootOf(thread) === thread).length } : {},
|
|
21363
|
+
...query.kind === "messaging-threads" ? {
|
|
21364
|
+
collectionSize: entries.length,
|
|
21365
|
+
...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) } : {}
|
|
21366
|
+
} : {},
|
|
21367
|
+
...query.kind === "star-map" ? {
|
|
21368
|
+
facets: countNavigationStarMapFacets(
|
|
21369
|
+
[...new Map(params.index.threads.filter(isStarMapOwnerThread).map((thread) => [threadKey(thread), thread])).values()].map((thread) => starMapSignals(thread, params.index)),
|
|
21370
|
+
query.filters
|
|
21371
|
+
)
|
|
21372
|
+
} : {},
|
|
21373
|
+
...includeDirectories ? { collectionKind: "directories" } : {},
|
|
21374
|
+
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 ?? ""))) : [],
|
|
21375
|
+
entries,
|
|
21376
|
+
queryKey: navigationQueryKey(params.request)
|
|
21377
|
+
};
|
|
21378
|
+
}
|
|
21379
|
+
|
|
21380
|
+
// ../owner-core/src/navigation-observation-clock.ts
|
|
21381
|
+
var observation = 0;
|
|
21382
|
+
function nextNavigationObservation() {
|
|
21383
|
+
return ++observation;
|
|
21384
|
+
}
|
|
21385
|
+
function currentNavigationObservation() {
|
|
21386
|
+
return observation;
|
|
21387
|
+
}
|
|
21388
|
+
|
|
21389
|
+
// ../owner-core/src/navigation-query-store.ts
|
|
21390
|
+
var NAVIGATION_QUERY_CURSOR_IDLE_MS = 6e4;
|
|
21391
|
+
var NAVIGATION_QUERY_MAX_GENERATIONS = 8;
|
|
21392
|
+
var NAVIGATION_QUERY_MAX_RETAINED_BYTES = 32 * 1024 * 1024;
|
|
21393
|
+
var NAVIGATION_ATTENTION_MAX_VIEWS = 64;
|
|
21394
|
+
var NAVIGATION_ATTENTION_MAX_BYTES = 8 * 1024 * 1024;
|
|
21395
|
+
var NavigationQueryError = class extends Error {
|
|
21396
|
+
code;
|
|
21397
|
+
constructor(code, message) {
|
|
21398
|
+
super(`[${code}] ${message}`);
|
|
21399
|
+
this.name = "NavigationQueryError";
|
|
21400
|
+
this.code = code;
|
|
21401
|
+
}
|
|
21402
|
+
};
|
|
21403
|
+
function serializedBytes(value) {
|
|
21404
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
21405
|
+
}
|
|
21406
|
+
function encodeCursor(cursor) {
|
|
21407
|
+
return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
|
|
21408
|
+
}
|
|
21409
|
+
function decodeCursor(value) {
|
|
21410
|
+
try {
|
|
21411
|
+
const parsed = JSON.parse(
|
|
21412
|
+
Buffer.from(value, "base64url").toString("utf8")
|
|
21413
|
+
);
|
|
21414
|
+
if (typeof parsed.generation !== "string" || typeof parsed.offset !== "number" || !Number.isSafeInteger(parsed.offset) || parsed.offset < 0 || typeof parsed.queryKey !== "string" || typeof parsed.scopeKey !== "string") {
|
|
21415
|
+
throw new Error("invalid cursor fields");
|
|
21416
|
+
}
|
|
21417
|
+
return parsed;
|
|
21418
|
+
} catch {
|
|
21419
|
+
throw new NavigationQueryError(
|
|
21420
|
+
"navigation_invalid_request",
|
|
21421
|
+
"Navigation cursor is malformed."
|
|
21422
|
+
);
|
|
21423
|
+
}
|
|
21424
|
+
}
|
|
21425
|
+
function validateRequest(request) {
|
|
21426
|
+
if (!request || request.protocol !== NAVIGATION_QUERY_PROTOCOL_VERSION) {
|
|
21427
|
+
throw new NavigationQueryError(
|
|
21428
|
+
"navigation_invalid_request",
|
|
21429
|
+
`Navigation query protocol ${NAVIGATION_QUERY_PROTOCOL_VERSION} is required.`
|
|
21430
|
+
);
|
|
21431
|
+
}
|
|
21432
|
+
if (request.inventory !== void 0 && request.inventory !== "owner" && request.inventory !== "viewer") {
|
|
21433
|
+
throw new NavigationQueryError("navigation_invalid_request", "Navigation inventory must be owner or viewer.");
|
|
21434
|
+
}
|
|
21435
|
+
if (request.inventory === "viewer" && request.federationTarget?.scope === "remote") {
|
|
21436
|
+
throw new NavigationQueryError("navigation_invalid_request", "Viewer navigation inventory is available only on this machine.");
|
|
21437
|
+
}
|
|
21438
|
+
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")) {
|
|
21439
|
+
throw new NavigationQueryError(
|
|
21440
|
+
"navigation_invalid_request",
|
|
21441
|
+
"Navigation Attention requires a bounded view identity and promotion policy."
|
|
21442
|
+
);
|
|
21443
|
+
}
|
|
21444
|
+
if (request.pageSize !== void 0 && (!Number.isSafeInteger(request.pageSize) || request.pageSize < 1 || request.pageSize > NAVIGATION_QUERY_MAX_PAGE_ROWS)) {
|
|
21445
|
+
throw new NavigationQueryError(
|
|
21446
|
+
"navigation_invalid_request",
|
|
21447
|
+
"Navigation page size must be between 1 and 100."
|
|
21448
|
+
);
|
|
21449
|
+
}
|
|
21450
|
+
const range = request.retainedRange;
|
|
21451
|
+
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))) {
|
|
21452
|
+
throw new NavigationQueryError("navigation_invalid_request", "Invalid retained navigation range.");
|
|
21453
|
+
}
|
|
21454
|
+
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)) {
|
|
21455
|
+
throw new NavigationQueryError("navigation_invalid_request", "Navigation rebaseline requires one explicit anchor without a cursor or unchanged baseline.");
|
|
21456
|
+
}
|
|
21457
|
+
if (request.query.kind === "star-map") {
|
|
21458
|
+
if (request.query.projectKey !== void 0 && (typeof request.query.projectKey !== "string" || !request.query.projectKey || request.query.projectKey.length > 4096)) {
|
|
21459
|
+
throw new NavigationQueryError("navigation_invalid_request", "Invalid Star Map project key.");
|
|
21460
|
+
}
|
|
21461
|
+
const keys = /* @__PURE__ */ new Set(["attention", "approval", "pr", "unpushed", "pinned", "agent"]);
|
|
21462
|
+
if (!request.query.filters || Object.entries(request.query.filters).some(([key, value]) => !keys.has(key) || !["neutral", "include", "exclude"].includes(value))) {
|
|
21463
|
+
throw new NavigationQueryError("navigation_invalid_request", "Invalid Star Map filter selection.");
|
|
21464
|
+
}
|
|
21465
|
+
}
|
|
21466
|
+
if (request.query.kind === "messaging-threads" || request.query.kind === "messaging-projects") {
|
|
21467
|
+
const query = request.query;
|
|
21468
|
+
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)) {
|
|
21469
|
+
throw new NavigationQueryError("navigation_invalid_request", "Messaging queries accept at most 64 backends and a 256-character filter.");
|
|
21470
|
+
}
|
|
21471
|
+
}
|
|
21472
|
+
if (request.query.kind === "directory-index") {
|
|
21473
|
+
const { keys, paths } = request.query;
|
|
21474
|
+
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)) {
|
|
21475
|
+
throw new NavigationQueryError("navigation_invalid_request", "Exact directory metadata accepts at most 100 keys or paths.");
|
|
21476
|
+
}
|
|
21477
|
+
}
|
|
21478
|
+
if (request.query.kind === "directory" && request.query.roots !== void 0 && !["all", "pinned", "unpinned"].includes(request.query.roots)) {
|
|
21479
|
+
throw new NavigationQueryError("navigation_invalid_request", "Invalid directory root disclosure.");
|
|
21480
|
+
}
|
|
21481
|
+
if (request.query.kind === "lens" && request.query.roots !== void 0 && !["pinned", "unpinned"].includes(request.query.roots)) {
|
|
21482
|
+
throw new NavigationQueryError("navigation_invalid_request", "Invalid lens root split.");
|
|
21483
|
+
}
|
|
21484
|
+
if (request.query.kind === "exact" && request.query.identities.length > 100) {
|
|
21485
|
+
throw new NavigationQueryError(
|
|
21486
|
+
"navigation_invalid_request",
|
|
21487
|
+
"An exact navigation query accepts at most 100 identities."
|
|
21488
|
+
);
|
|
21489
|
+
}
|
|
21490
|
+
if (request.query.kind === "exact" && request.query.resolveFromOwners && (request.inventory !== "viewer" || request.federationTarget?.scope === "remote")) {
|
|
21491
|
+
throw new NavigationQueryError(
|
|
21492
|
+
"navigation_invalid_request",
|
|
21493
|
+
"Owner-resolved navigation is available only to this machine's viewer inventory."
|
|
21494
|
+
);
|
|
21495
|
+
}
|
|
21496
|
+
if (request.query.kind === "group-members" && (!Array.isArray(request.query.roots) || !request.query.roots.length || request.query.roots.length > 100)) {
|
|
21497
|
+
throw new NavigationQueryError("navigation_invalid_request", "Group discovery accepts one to 100 root identities.");
|
|
21498
|
+
}
|
|
21499
|
+
if (request.query.kind === "directory" && (request.query.disclosedParentThreadKeys?.length ?? 0) > 100) {
|
|
21500
|
+
throw new NavigationQueryError(
|
|
21501
|
+
"navigation_invalid_request",
|
|
21502
|
+
"A directory navigation query accepts at most 100 disclosures."
|
|
21503
|
+
);
|
|
21504
|
+
}
|
|
21505
|
+
}
|
|
21506
|
+
function fingerprintNavigationMaterialization(materialization) {
|
|
21507
|
+
const hash = createHash8("sha256");
|
|
21508
|
+
let retainedBytes = 0;
|
|
21509
|
+
const append = (value) => {
|
|
21510
|
+
retainedBytes += Buffer.byteLength(value, "utf8");
|
|
21511
|
+
if (retainedBytes > NAVIGATION_QUERY_MAX_RETAINED_BYTES) {
|
|
21512
|
+
throw new NavigationQueryError("navigation_busy", "Navigation query exceeds the process retained-memory budget.");
|
|
21513
|
+
}
|
|
21514
|
+
hash.update(value);
|
|
21515
|
+
};
|
|
21516
|
+
append("{");
|
|
21517
|
+
let first = true;
|
|
21518
|
+
for (const [key, value] of Object.entries(materialization)) {
|
|
21519
|
+
if (value === void 0) continue;
|
|
21520
|
+
if (!first) append(",");
|
|
21521
|
+
first = false;
|
|
21522
|
+
append(JSON.stringify(key));
|
|
21523
|
+
append(":");
|
|
21524
|
+
if (Array.isArray(value)) {
|
|
21525
|
+
append("[");
|
|
21526
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
21527
|
+
if (index) append(",");
|
|
21528
|
+
append(JSON.stringify(value[index]) ?? "null");
|
|
21529
|
+
}
|
|
21530
|
+
append("]");
|
|
21531
|
+
} else append(JSON.stringify(value));
|
|
21532
|
+
}
|
|
21533
|
+
append("}");
|
|
21534
|
+
return { revision: hash.digest("base64url"), retainedBytes };
|
|
21535
|
+
}
|
|
21536
|
+
function pageBase(params) {
|
|
21537
|
+
const { generation } = params;
|
|
21538
|
+
return {
|
|
21539
|
+
protocol: NAVIGATION_QUERY_PROTOCOL_VERSION,
|
|
21540
|
+
queryKey: generation.materialization.queryKey,
|
|
21541
|
+
generation: generation.generation,
|
|
21542
|
+
ownerEpoch: params.ownerEpoch,
|
|
21543
|
+
countsRevision: generation.completeRevision,
|
|
21544
|
+
coverage: generation.materialization.coverage,
|
|
21545
|
+
counts: generation.materialization.counts,
|
|
21546
|
+
...generation.materialization.collectionSize !== void 0 ? { collectionSize: generation.materialization.collectionSize } : {},
|
|
21547
|
+
...generation.materialization.selectionDirectory ? { selectionDirectory: generation.materialization.selectionDirectory } : {},
|
|
21548
|
+
...generation.materialization.facets ? { facets: generation.materialization.facets } : {}
|
|
21549
|
+
};
|
|
21550
|
+
}
|
|
21551
|
+
var NavigationQueryStore = class {
|
|
21552
|
+
constructor(options = {}) {
|
|
21553
|
+
this.options = options;
|
|
21554
|
+
}
|
|
21555
|
+
options;
|
|
21556
|
+
ownerEpoch = randomUUID8();
|
|
21557
|
+
attentionLifetimes = /* @__PURE__ */ new Map();
|
|
21558
|
+
generations = /* @__PURE__ */ new Map();
|
|
21559
|
+
currentGenerationByScopeAndQuery = /* @__PURE__ */ new Map();
|
|
21560
|
+
attentionViews = /* @__PURE__ */ new Map();
|
|
21561
|
+
async readPage(params) {
|
|
21562
|
+
validateRequest(params.request);
|
|
21563
|
+
const now = this.options.now?.() ?? Date.now();
|
|
21564
|
+
this.expireIdle(now);
|
|
21565
|
+
const attentionKey = params.request.attentionView ? JSON.stringify([params.scopeKey, params.request.attentionView.id]) : void 0;
|
|
21566
|
+
let lifetime = attentionKey ? this.attentionLifetimes.get(attentionKey) : void 0;
|
|
21567
|
+
if (attentionKey && !lifetime) {
|
|
21568
|
+
const bytes = [...this.attentionLifetimes.keys(), attentionKey].reduce((total, key) => total + serializedBytes(key) + 32, 0);
|
|
21569
|
+
if (this.attentionLifetimes.size >= 256 || bytes > 256 * 1024) throw new NavigationQueryError("navigation_busy", "Attention lifetime admission is occupied.");
|
|
21570
|
+
lifetime = {};
|
|
21571
|
+
this.attentionLifetimes.set(attentionKey, lifetime);
|
|
21572
|
+
}
|
|
21573
|
+
if (lifetime?.closedAt !== void 0) throw new NavigationQueryError("navigation_invalid_request", "This Attention view has closed. Open a new view lifetime.");
|
|
21574
|
+
const queryKey = navigationQueryKey(params.request);
|
|
21575
|
+
let generation;
|
|
21576
|
+
let newCurrentKey;
|
|
21577
|
+
let offset = 0;
|
|
21578
|
+
let anchorIndex;
|
|
21579
|
+
if (params.request.cursor) {
|
|
21580
|
+
const cursor = decodeCursor(params.request.cursor);
|
|
21581
|
+
if (cursor.scopeKey !== params.scopeKey || cursor.queryKey !== queryKey) {
|
|
21582
|
+
throw new NavigationQueryError(
|
|
21583
|
+
"navigation_invalid_request",
|
|
21584
|
+
"Navigation cursor does not belong to this requester and query."
|
|
21585
|
+
);
|
|
21586
|
+
}
|
|
21587
|
+
const retained = this.generations.get(cursor.generation);
|
|
21588
|
+
if (!retained) {
|
|
21589
|
+
throw new NavigationQueryError(
|
|
21590
|
+
"navigation_cursor_expired",
|
|
21591
|
+
"Navigation cursor expired; rebaseline around the visible anchor."
|
|
21592
|
+
);
|
|
21593
|
+
}
|
|
21594
|
+
if (retained.scopeKey !== params.scopeKey || retained.materialization.queryKey !== queryKey) {
|
|
21595
|
+
throw new NavigationQueryError(
|
|
21596
|
+
"navigation_invalid_request",
|
|
21597
|
+
"Navigation generation does not belong to this requester and query."
|
|
21598
|
+
);
|
|
21599
|
+
}
|
|
21600
|
+
this.options.onCursorHit?.();
|
|
21601
|
+
generation = retained;
|
|
21602
|
+
offset = cursor.offset;
|
|
21603
|
+
} else {
|
|
21604
|
+
const readStartedAt = currentNavigationObservation();
|
|
21605
|
+
const index = await params.loadIndex();
|
|
21606
|
+
if (lifetime?.closedAt !== void 0) throw new NavigationQueryError("navigation_invalid_request", "This Attention view closed during its owner read.");
|
|
21607
|
+
anchorIndex = index;
|
|
21608
|
+
const attentionOrder = this.reconcileAttentionView(params.scopeKey, params.request, index, index.observedAt ?? readStartedAt);
|
|
21609
|
+
const materialization = projectNavigationQuery({
|
|
21610
|
+
index,
|
|
21611
|
+
request: params.request,
|
|
21612
|
+
attentionOrder
|
|
21613
|
+
});
|
|
21614
|
+
const { revision, retainedBytes } = fingerprintNavigationMaterialization(materialization);
|
|
21615
|
+
const currentKey = `${params.scopeKey}\0${queryKey}`;
|
|
21616
|
+
const currentId = this.currentGenerationByScopeAndQuery.get(currentKey);
|
|
21617
|
+
const current = currentId ? this.generations.get(currentId) : void 0;
|
|
21618
|
+
if (current?.completeRevision === revision) {
|
|
21619
|
+
generation = current;
|
|
21620
|
+
} else {
|
|
21621
|
+
generation = {
|
|
21622
|
+
completeRevision: revision,
|
|
21623
|
+
createdAt: now,
|
|
21624
|
+
generation: randomUUID8(),
|
|
21625
|
+
lastAccessedAt: now,
|
|
21626
|
+
materialization,
|
|
21627
|
+
retainedBytes,
|
|
21628
|
+
scopeKey: params.scopeKey,
|
|
21629
|
+
attentionViewId: params.request.attentionView?.id
|
|
21630
|
+
};
|
|
21631
|
+
if (generation.retainedBytes > NAVIGATION_QUERY_MAX_RETAINED_BYTES) {
|
|
21632
|
+
throw new NavigationQueryError("navigation_busy", "Navigation query exceeds the process retained-memory budget.");
|
|
21633
|
+
}
|
|
21634
|
+
newCurrentKey = currentKey;
|
|
21635
|
+
}
|
|
21636
|
+
if (params.request.completeBaselineRevision && params.request.completeBaselineRevision === generation.completeRevision) {
|
|
21637
|
+
const unchanged = {
|
|
21638
|
+
...pageBase({ generation, ownerEpoch: this.ownerEpoch }),
|
|
21639
|
+
entries: [],
|
|
21640
|
+
directories: [],
|
|
21641
|
+
complete: true,
|
|
21642
|
+
unchanged: true
|
|
21643
|
+
};
|
|
21644
|
+
this.assertPageBudget(unchanged);
|
|
21645
|
+
generation.lastAccessedAt = now;
|
|
21646
|
+
return unchanged;
|
|
21647
|
+
}
|
|
21648
|
+
}
|
|
21649
|
+
const range = params.request.retainedRange;
|
|
21650
|
+
if (range && range.revision === generation.completeRevision && range.ownerEpoch === this.ownerEpoch) {
|
|
21651
|
+
const size = generation.materialization.modelGroups?.length ?? (generation.materialization.directories.length || generation.materialization.entries.length);
|
|
21652
|
+
if (range.start + range.count > size) {
|
|
21653
|
+
throw new NavigationQueryError("navigation_invalid_request", "Retained range exceeds its matching generation.");
|
|
21654
|
+
}
|
|
21655
|
+
const page2 = this.buildPage({
|
|
21656
|
+
generation,
|
|
21657
|
+
offset: range.start + range.count,
|
|
21658
|
+
ownerEpoch: this.ownerEpoch,
|
|
21659
|
+
pageSize: 0
|
|
21660
|
+
});
|
|
21661
|
+
page2.rangeStart = range.start;
|
|
21662
|
+
page2.rangeUnchanged = { start: range.start, count: range.count };
|
|
21663
|
+
this.assertPageBudget(page2);
|
|
21664
|
+
generation.lastAccessedAt = now;
|
|
21665
|
+
if (newCurrentKey && page2.nextCursor) {
|
|
21666
|
+
this.retainGeneration(generation);
|
|
21667
|
+
this.currentGenerationByScopeAndQuery.set(newCurrentKey, generation.generation);
|
|
21668
|
+
}
|
|
21669
|
+
return page2;
|
|
21670
|
+
}
|
|
21671
|
+
if (params.request.anchor) {
|
|
21672
|
+
const anchor = params.request.anchor;
|
|
21673
|
+
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);
|
|
21674
|
+
if (offset < 0) {
|
|
21675
|
+
const reason = anchorIndex ? explainMissingNavigationAnchor({ index: anchorIndex, request: params.request }) : "filtered";
|
|
21676
|
+
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.`);
|
|
21677
|
+
}
|
|
21678
|
+
}
|
|
21679
|
+
generation.lastAccessedAt = now;
|
|
21680
|
+
const page = this.buildPage({
|
|
21681
|
+
generation,
|
|
21682
|
+
offset,
|
|
21683
|
+
ownerEpoch: this.ownerEpoch,
|
|
21684
|
+
pageSize: params.request.pageSize ?? NAVIGATION_QUERY_MAX_PAGE_ROWS
|
|
21685
|
+
});
|
|
21686
|
+
if (newCurrentKey && page.nextCursor) {
|
|
21687
|
+
this.retainGeneration(generation);
|
|
21688
|
+
this.currentGenerationByScopeAndQuery.set(newCurrentKey, generation.generation);
|
|
21689
|
+
}
|
|
21690
|
+
return page;
|
|
21691
|
+
}
|
|
21692
|
+
/** Window teardown releases order lifetime; page expiry deliberately does not. */
|
|
21693
|
+
releaseAttentionView(scopeKey, viewId) {
|
|
21694
|
+
const lifetime = this.attentionLifetimes.get(JSON.stringify([scopeKey, viewId]));
|
|
21695
|
+
if (lifetime) lifetime.closedAt = this.options.now?.() ?? Date.now();
|
|
21696
|
+
for (const [key, generation] of this.generations) {
|
|
21697
|
+
if (generation.scopeKey === scopeKey && generation.attentionViewId === viewId) this.generations.delete(key);
|
|
21698
|
+
}
|
|
21699
|
+
for (const [key, generationId] of this.currentGenerationByScopeAndQuery) {
|
|
21700
|
+
if (!this.generations.has(generationId)) this.currentGenerationByScopeAndQuery.delete(key);
|
|
21701
|
+
}
|
|
21702
|
+
this.attentionViews.delete(JSON.stringify([scopeKey, viewId]));
|
|
21703
|
+
}
|
|
21704
|
+
/**
|
|
21705
|
+
* Reads for one view overlap, and a read can be served an index whose
|
|
21706
|
+
* listing began before a boundary the store already applied. The index is
|
|
21707
|
+
* served and reconciled, never reread: the reducer leaves alone whatever
|
|
21708
|
+
* the order observed after that listing began (see the observation clock).
|
|
21709
|
+
*/
|
|
21710
|
+
reconcileAttentionView(scopeKey, request, index, observedAt) {
|
|
21711
|
+
if (!request.attentionView) return void 0;
|
|
21712
|
+
const { id, promoteOnTurnEnd } = request.attentionView;
|
|
21713
|
+
const key = JSON.stringify([scopeKey, id]);
|
|
21714
|
+
const previous = this.attentionViews.get(key);
|
|
21715
|
+
if (previous?.failed) throw new NavigationQueryError("navigation_busy", "Attention metadata exceeded its budget. Close this view and open a new one.");
|
|
21716
|
+
if (!previous && this.attentionViews.size >= NAVIGATION_ATTENTION_MAX_VIEWS) {
|
|
21717
|
+
throw new NavigationQueryError("navigation_busy", "Attention view budget is occupied.");
|
|
21718
|
+
}
|
|
21719
|
+
const order = reconcileNavigationAttentionOrder({
|
|
21720
|
+
previous: previous?.order,
|
|
21721
|
+
threads: index.threads,
|
|
21722
|
+
complete: !index.coverage || index.coverage.state === "complete",
|
|
21723
|
+
promoteOnTurnEnd,
|
|
21724
|
+
observedAt
|
|
21725
|
+
});
|
|
21726
|
+
if (previous && navigationAttentionOrderFollows(previous.order, observedAt)) {
|
|
21727
|
+
this.options.onOvertakenRead?.();
|
|
21728
|
+
}
|
|
21729
|
+
const remoteMembers = index.coverage && index.coverage.state !== "complete" ? new Set(previous?.remoteMembers) : /* @__PURE__ */ new Set();
|
|
21730
|
+
for (const thread of index.threads) if (thread.federation?.ref.target.scope === "remote") remoteMembers.add(navigationAttentionIdentity(thread));
|
|
21731
|
+
const bytes = navigationAttentionOrderBytes(order) + serializedBytes([...remoteMembers]);
|
|
21732
|
+
let retainedBytes = bytes;
|
|
21733
|
+
for (const [otherKey, view] of this.attentionViews) {
|
|
21734
|
+
if (otherKey !== key) retainedBytes += view.bytes;
|
|
21735
|
+
}
|
|
21736
|
+
if (retainedBytes > NAVIGATION_ATTENTION_MAX_BYTES) {
|
|
21737
|
+
throw new NavigationQueryError("navigation_busy", "Attention metadata exceeds its retained budget.");
|
|
21738
|
+
}
|
|
21739
|
+
this.attentionViews.set(key, { order, bytes, promoteOnTurnEnd, backend: request.backend, remoteMembers });
|
|
21740
|
+
return order;
|
|
21741
|
+
}
|
|
21742
|
+
/** Boundary events maintain session ranks even while the viewer browses another lens. */
|
|
21743
|
+
observeAttentionEvent(event) {
|
|
21744
|
+
const method = event.notification.method;
|
|
21745
|
+
const status = "status" in event.notification.params ? event.notification.params.status : void 0;
|
|
21746
|
+
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;
|
|
21747
|
+
const remove = method === "thread/archived";
|
|
21748
|
+
const seen = method === "navigation/thread/seen";
|
|
21749
|
+
if (active === void 0 && !remove && !seen) return;
|
|
21750
|
+
const params = event.notification.params;
|
|
21751
|
+
if (typeof params.threadId !== "string") return;
|
|
21752
|
+
const owner = event.federationTarget?.scope === "remote" ? event.federationTarget.instanceId : null;
|
|
21753
|
+
const key = JSON.stringify([owner, event.backend, params.threadId]);
|
|
21754
|
+
if (owner && ![...this.attentionViews.values()].some((view) => view.remoteMembers.has(key))) return;
|
|
21755
|
+
const observedAt = nextNavigationObservation();
|
|
21756
|
+
let retainedBytes = [...this.attentionViews.values()].reduce((sum, view) => sum + view.bytes, 0);
|
|
21757
|
+
for (const view of this.attentionViews.values()) {
|
|
21758
|
+
if (view.failed || view.backend && view.backend !== "all" && view.backend !== event.backend || owner && !view.remoteMembers.has(key)) continue;
|
|
21759
|
+
const member = view.order.members.get(key);
|
|
21760
|
+
let order = view.order;
|
|
21761
|
+
if (remove || seen && !member?.active) {
|
|
21762
|
+
order = removeNavigationAttentionMember({ previous: order, key, observedAt });
|
|
21763
|
+
} else if (active !== void 0) {
|
|
21764
|
+
order = observeNavigationAttentionTurn({
|
|
21765
|
+
previous: order,
|
|
21766
|
+
key,
|
|
21767
|
+
active,
|
|
21768
|
+
turnId: params.turnId ?? params.turn?.id,
|
|
21769
|
+
promoteOnTurnEnd: view.promoteOnTurnEnd,
|
|
21770
|
+
observedAt
|
|
21771
|
+
});
|
|
21772
|
+
}
|
|
21773
|
+
if (order === view.order) continue;
|
|
21774
|
+
const bytes = navigationAttentionOrderBytes(order) + serializedBytes([...view.remoteMembers]);
|
|
21775
|
+
if (retainedBytes - view.bytes + bytes > NAVIGATION_ATTENTION_MAX_BYTES) {
|
|
21776
|
+
view.failed = true;
|
|
21777
|
+
continue;
|
|
21778
|
+
}
|
|
21779
|
+
retainedBytes += bytes - view.bytes;
|
|
21780
|
+
view.order = order;
|
|
21781
|
+
view.bytes = bytes;
|
|
21782
|
+
}
|
|
21783
|
+
}
|
|
21784
|
+
buildPage(params) {
|
|
21785
|
+
const materialization = params.generation.materialization;
|
|
21786
|
+
const collectionKind = materialization.modelGroups ? "models" : materialization.collectionKind === "directories" || materialization.directories.length > 0 ? "directories" : "entries";
|
|
21787
|
+
const collection = materialization.modelGroups ?? (collectionKind === "directories" ? materialization.directories : materialization.entries);
|
|
21788
|
+
if (params.offset > collection.length) {
|
|
21789
|
+
throw new NavigationQueryError(
|
|
21790
|
+
"navigation_invalid_request",
|
|
21791
|
+
"Navigation cursor offset is outside its generation."
|
|
21792
|
+
);
|
|
21793
|
+
}
|
|
21794
|
+
const page = {
|
|
21795
|
+
...pageBase({
|
|
21796
|
+
generation: params.generation,
|
|
21797
|
+
ownerEpoch: params.ownerEpoch
|
|
21798
|
+
}),
|
|
21799
|
+
entries: [],
|
|
21800
|
+
...params.offset ? { rangeStart: params.offset } : {},
|
|
21801
|
+
...collectionKind === "models" ? { modelGroups: [] } : collectionKind === "directories" ? { directories: [] } : {},
|
|
21802
|
+
complete: false
|
|
21803
|
+
};
|
|
21804
|
+
let nextOffset = params.offset;
|
|
21805
|
+
for (; nextOffset < collection.length && nextOffset - params.offset < params.pageSize; nextOffset += 1) {
|
|
21806
|
+
const item = collection[nextOffset];
|
|
21807
|
+
const candidate = collectionKind === "models" ? { ...page, modelGroups: [...page.modelGroups ?? [], item] } : collectionKind === "directories" ? {
|
|
21808
|
+
...page,
|
|
21809
|
+
directories: [...page.directories ?? [], item]
|
|
21810
|
+
} : {
|
|
21811
|
+
...page,
|
|
21812
|
+
entries: [...page.entries, item]
|
|
21813
|
+
};
|
|
21814
|
+
const candidateNextOffset = nextOffset + 1;
|
|
21815
|
+
if (candidateNextOffset < collection.length) {
|
|
21816
|
+
candidate.nextCursor = encodeCursor({
|
|
21817
|
+
generation: params.generation.generation,
|
|
21818
|
+
offset: candidateNextOffset,
|
|
21819
|
+
queryKey: params.generation.materialization.queryKey,
|
|
21820
|
+
scopeKey: params.generation.scopeKey
|
|
21821
|
+
});
|
|
21822
|
+
}
|
|
21823
|
+
if (serializedBytes(candidate) > NAVIGATION_QUERY_MAX_RESULT_BYTES) {
|
|
21824
|
+
if (nextOffset === params.offset) {
|
|
21825
|
+
throw new NavigationQueryError(
|
|
21826
|
+
"navigation_item_too_large",
|
|
21827
|
+
"One navigation row exceeds the result budget; read its exact detail."
|
|
21828
|
+
);
|
|
21829
|
+
}
|
|
21830
|
+
break;
|
|
21831
|
+
}
|
|
21832
|
+
if (collectionKind === "models") {
|
|
21833
|
+
page.modelGroups.push(item);
|
|
21834
|
+
} else if (collectionKind === "directories") {
|
|
21835
|
+
page.directories.push(item);
|
|
21836
|
+
} else {
|
|
21837
|
+
page.entries.push(item);
|
|
21838
|
+
}
|
|
21839
|
+
}
|
|
21840
|
+
page.complete = nextOffset >= collection.length;
|
|
21841
|
+
if (!page.complete) {
|
|
21842
|
+
page.nextCursor = encodeCursor({
|
|
21843
|
+
generation: params.generation.generation,
|
|
21844
|
+
offset: nextOffset,
|
|
21845
|
+
queryKey: params.generation.materialization.queryKey,
|
|
21846
|
+
scopeKey: params.generation.scopeKey
|
|
21847
|
+
});
|
|
21848
|
+
}
|
|
21849
|
+
this.assertPageBudget(page);
|
|
21850
|
+
return page;
|
|
21851
|
+
}
|
|
21852
|
+
retainGeneration(generation) {
|
|
21853
|
+
if (generation.retainedBytes > NAVIGATION_QUERY_MAX_RETAINED_BYTES) {
|
|
21854
|
+
throw new NavigationQueryError(
|
|
21855
|
+
"navigation_busy",
|
|
21856
|
+
"Navigation query exceeds the process retained-memory budget."
|
|
21857
|
+
);
|
|
21858
|
+
}
|
|
21859
|
+
let retainedBytes = [...this.generations.values()].reduce((total, item) => total + item.retainedBytes, 0);
|
|
21860
|
+
const currentIds = new Set(this.currentGenerationByScopeAndQuery.values());
|
|
21861
|
+
const evictable = [...this.generations.values()].sort((left, right) => Number(currentIds.has(left.generation)) - Number(currentIds.has(right.generation)) || left.lastAccessedAt - right.lastAccessedAt);
|
|
21862
|
+
while (this.generations.size >= NAVIGATION_QUERY_MAX_GENERATIONS || retainedBytes + generation.retainedBytes > NAVIGATION_QUERY_MAX_RETAINED_BYTES) {
|
|
21863
|
+
const oldest = evictable.shift();
|
|
21864
|
+
if (!oldest) break;
|
|
21865
|
+
this.generations.delete(oldest.generation);
|
|
21866
|
+
retainedBytes -= oldest.retainedBytes;
|
|
21867
|
+
for (const [key, id] of this.currentGenerationByScopeAndQuery) {
|
|
21868
|
+
if (id === oldest.generation) this.currentGenerationByScopeAndQuery.delete(key);
|
|
21869
|
+
}
|
|
21870
|
+
}
|
|
21871
|
+
this.generations.set(generation.generation, generation);
|
|
21872
|
+
return generation;
|
|
21873
|
+
}
|
|
21874
|
+
expireIdle(now) {
|
|
21875
|
+
for (const [key, lifetime] of this.attentionLifetimes) {
|
|
21876
|
+
if (lifetime.closedAt !== void 0 && now - lifetime.closedAt > NAVIGATION_QUERY_CURSOR_IDLE_MS) this.attentionLifetimes.delete(key);
|
|
21877
|
+
}
|
|
21878
|
+
for (const generation of this.generations.values()) {
|
|
21879
|
+
if (now - generation.lastAccessedAt <= NAVIGATION_QUERY_CURSOR_IDLE_MS) {
|
|
21880
|
+
continue;
|
|
21881
|
+
}
|
|
21882
|
+
this.generations.delete(generation.generation);
|
|
21883
|
+
}
|
|
21884
|
+
for (const [key, generationId] of this.currentGenerationByScopeAndQuery) {
|
|
21885
|
+
if (!this.generations.has(generationId)) {
|
|
21886
|
+
this.currentGenerationByScopeAndQuery.delete(key);
|
|
21887
|
+
}
|
|
21888
|
+
}
|
|
21889
|
+
}
|
|
21890
|
+
assertPageBudget(page) {
|
|
21891
|
+
if (serializedBytes(page) > NAVIGATION_QUERY_MAX_RESULT_BYTES) {
|
|
21892
|
+
throw new NavigationQueryError(
|
|
21893
|
+
"navigation_item_too_large",
|
|
21894
|
+
"Navigation response exceeds the result budget."
|
|
21895
|
+
);
|
|
21896
|
+
}
|
|
21897
|
+
}
|
|
21898
|
+
};
|
|
20106
21899
|
export {
|
|
20107
21900
|
AgentToolRouter,
|
|
20108
21901
|
FEDERATION_BACKEND_METHOD_CAPABILITIES,
|
|
@@ -20140,6 +21933,8 @@ export {
|
|
|
20140
21933
|
FederationRouter,
|
|
20141
21934
|
FederationRpcEndpoint,
|
|
20142
21935
|
FederationSocketKeepalive,
|
|
21936
|
+
NAVIGATION_QUERY_MAX_RESULT_BYTES,
|
|
21937
|
+
NavigationQueryStore,
|
|
20143
21938
|
NoiseIKHandshake,
|
|
20144
21939
|
PWRAGENT_MESSAGING_UNAVAILABLE_MESSAGE,
|
|
20145
21940
|
PWRAGENT_MODEL_DIRECTED_PDF_OPERATION_NAMES,
|
|
@@ -20158,6 +21953,7 @@ export {
|
|
|
20158
21953
|
buildPwrAgentMessagingPdfToolRouter,
|
|
20159
21954
|
buildPwrAgentMessagingToolDefinitions,
|
|
20160
21955
|
buildPwrAgentMessagingToolRouter,
|
|
21956
|
+
buildThreadIdentityKey,
|
|
20161
21957
|
completeFederationEnrollment,
|
|
20162
21958
|
connectFederationClient,
|
|
20163
21959
|
createFederationEnrollmentInvite,
|