@codexhost/cli-darwin-arm64 0.18.0-beta.3 → 0.18.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +1 -1
  2. package/app/codexhost-distribution.json +1 -1
  3. package/app/console-server.mjs +9 -4
  4. package/app/console-web.js +9 -4
  5. package/app/desktop-controller.mjs +9 -4
  6. package/app/host-runtime.mjs +4751 -2746
  7. package/app/plugins/antigravity/plugin.mjs +9 -4
  8. package/app/plugins/claude-code/plugin.mjs +129 -58
  9. package/app/plugins/codebuddy/plugin.mjs +9 -4
  10. package/app/plugins/codex-usage/plugin.mjs +9 -4
  11. package/app/plugins/copilot/plugin.mjs +9 -4
  12. package/app/plugins/cursor-cli/plugin.mjs +9 -4
  13. package/app/plugins/deepseek-harness/plugin.mjs +9 -4
  14. package/app/plugins/devin/plugin.mjs +9 -4
  15. package/app/plugins/grok/plugin.mjs +9 -4
  16. package/app/plugins/hermes/plugin.mjs +9 -4
  17. package/app/plugins/kimi-code/plugin.mjs +9 -4
  18. package/app/plugins/kiro-cli/plugin.mjs +9 -4
  19. package/app/plugins/omp/plugin.mjs +9 -4
  20. package/app/plugins/opencode/plugin.mjs +9 -4
  21. package/app/plugins/pi/plugin.mjs +9 -4
  22. package/app/plugins/qoder/plugin.mjs +9 -4
  23. package/app/plugins/qoder-cn/plugin.mjs +9 -4
  24. package/app/plugins/qwen-code/plugin.mjs +9 -4
  25. package/app/plugins/workbuddy/plugin.mjs +9 -4
  26. package/app/plugins/zcode/plugin.mjs +9 -4
  27. package/app/renderer-extension.js +9 -4
  28. package/app/web/assets/{index-aKvkfNJN.js → index-D1dOzBMI.js} +2 -2
  29. package/app/web/codexhost-settings.js +9 -4
  30. package/app/web/index.html +1 -1
  31. package/app/web/plugins/deepseek-ai__dsh-api-gateway/client.js +1 -1
  32. package/app/web/plugins/deepseek-ai__dsh-api-remotes/client.js +1 -1
  33. package/app/web/plugins/deepseek-ai__dsh-api-session-controller/client.js +3 -3
  34. package/app/web/plugins/deepseek-ai__dsh-client-connection/client.js +1 -1
  35. package/app/web/plugins/deepseek-ai__dsh-client-ui-codexhost/client.js +63 -63
  36. package/app/web/plugins/deepseek-ai__dsh-client-ui-conversation/client.js +8 -8
  37. package/app/web/plugins/deepseek-ai__dsh-client-ui-layout/client.js +2 -2
  38. package/app/web/plugins/deepseek-ai__dsh-client-ui-settings/client.js +1 -1
  39. package/app/web/plugins/deepseek-ai__dsh-client-ui-settings-general/client.js +2 -2
  40. package/app/web/plugins/deepseek-ai__dsh-client-ui-sidebar/client.js +2 -2
  41. package/app/web/plugins/deepseek-ai__dsh-client-ui-tool/client.js +1 -1
  42. package/app/web/plugins/deepseek-ai__dsh-client-ui-workspace/client.js +8 -12
  43. package/app/web/plugins.json +4 -0
  44. package/app/web-modules/server.mjs +374 -154
  45. package/bin/codexhost +0 -0
  46. package/libexec/codexhost-shim +0 -0
  47. package/libexec/codexhost-updater +0 -0
  48. package/package.json +1 -1
@@ -66895,7 +66895,7 @@ function hasOnlyKeys(value, required2, optional2) {
66895
66895
  }
66896
66896
 
66897
66897
  // apps/web-ui/packages/api/gateway/src/stream-server.ts
66898
- var MAX_MISSED_HEARTBEATS = 2, RemoteStreamMuxServer = class {
66898
+ var HEARTBEAT_TIMEOUT_MS = 10 * 6e4, RemoteStreamMuxServer = class {
66899
66899
  /**
66900
66900
  * @param open - Gateway stream dispatcher.
66901
66901
  * @param failure - Gateway error-to-wire mapper.
@@ -66914,7 +66914,7 @@ var MAX_MISSED_HEARTBEATS = 2, RemoteStreamMuxServer = class {
66914
66914
  streamInboxBytes;
66915
66915
  server = new import_websocket_server2.default({ noServer: !0 });
66916
66916
  connections = /* @__PURE__ */ new Set();
66917
- missedHeartbeats = /* @__PURE__ */ new WeakMap();
66917
+ lastPongAt = /* @__PURE__ */ new WeakMap();
66918
66918
  heartbeatTimer;
66919
66919
  /**
66920
66920
  * Upgrade one admitted request and begin serving its logical streams. Every
@@ -66929,8 +66929,8 @@ var MAX_MISSED_HEARTBEATS = 2, RemoteStreamMuxServer = class {
66929
66929
  this.server.handleUpgrade(req, socket, head, (websocket) => {
66930
66930
  let release = bindPeer(websocket, peer);
66931
66931
  if (release === void 0) return;
66932
- this.missedHeartbeats.set(websocket, 0), websocket.on("pong", () => {
66933
- this.missedHeartbeats.set(websocket, 0);
66932
+ this.lastPongAt.set(websocket, performance.now()), websocket.on("pong", () => {
66933
+ this.lastPongAt.set(websocket, performance.now());
66934
66934
  }), this.startHeartbeat();
66935
66935
  let bound = (endpoint, payload, uplink, control, window2) => this.open(endpoint, payload, uplink, peer, control, window2), done = new RemoteStreamMuxConnection(
66936
66936
  websocket,
@@ -66955,17 +66955,16 @@ var MAX_MISSED_HEARTBEATS = 2, RemoteStreamMuxServer = class {
66955
66955
  /** Start one `unref()` timer after the first upgrade; it spans empty-client periods until close(). */
66956
66956
  startHeartbeat() {
66957
66957
  this.heartbeatTimer === void 0 && (this.heartbeatTimer = setInterval(() => {
66958
- for (let socket of this.server.clients) {
66959
- if (socket.readyState !== wrapper_default.OPEN) continue;
66960
- let missed = this.missedHeartbeats.get(socket);
66961
- if (missed >= MAX_MISSED_HEARTBEATS) {
66962
- setImmediate(() => {
66963
- this.missedHeartbeats.get(socket) >= MAX_MISSED_HEARTBEATS && socket.terminate();
66964
- });
66965
- continue;
66958
+ for (let socket of this.server.clients)
66959
+ if (socket.readyState === wrapper_default.OPEN) {
66960
+ if (performance.now() - this.lastPongAt.get(socket) >= HEARTBEAT_TIMEOUT_MS) {
66961
+ setImmediate(() => {
66962
+ socket.readyState === wrapper_default.OPEN && performance.now() - this.lastPongAt.get(socket) >= HEARTBEAT_TIMEOUT_MS && socket.terminate();
66963
+ });
66964
+ continue;
66965
+ }
66966
+ socket.ping();
66966
66967
  }
66967
- this.missedHeartbeats.set(socket, missed + 1), socket.ping();
66968
- }
66969
66968
  }, this.heartbeatIntervalMs), this.heartbeatTimer.unref());
66970
66969
  }
66971
66970
  }, RemoteStreamMuxConnection = class {
@@ -68385,12 +68384,17 @@ var clientThreadPinsSchema = external_exports.object({ threadIds: external_expor
68385
68384
  });
68386
68385
 
68387
68386
  // packages/shared-contracts/dist/thread-catalog.js
68388
- var THREAD_CATALOG_READ_METHOD = "codexhost/thread/catalog/read", threadCatalogReadParamsSchema = external_exports.object({ includeArchived: external_exports.boolean().optional() }).strict(), threadCatalogReadResultSchema = external_exports.object({
68387
+ var THREAD_CATALOG_READ_METHOD = "codexhost/thread/catalog/read", threadCatalogReadParamsSchema = external_exports.object({
68388
+ includeArchived: external_exports.boolean().optional(),
68389
+ /** Absent means first page; null means this branch is exhausted. Native cursors only. */
68390
+ officialCursor: external_exports.object({ live: external_exports.string().nullable(), archived: external_exports.string().nullable().optional() }).strict().optional()
68391
+ }).strict(), threadCatalogReadResultSchema = external_exports.object({
68389
68392
  threads: external_exports.array(jsonObjectSchema),
68390
68393
  ownership: external_exports.array(threadOwnershipSchema),
68391
68394
  /** Known archived IDs, including visible local external records even when their rows are
68392
- * excluded. Native archived IDs are complete only when includeArchived is true. */
68393
- archivedThreadIds: external_exports.array(hostThreadIdSchema)
68395
+ * excluded. Native archived IDs describe only the returned page, never a complete set. */
68396
+ archivedThreadIds: external_exports.array(hostThreadIdSchema),
68397
+ officialCursor: external_exports.object({ live: external_exports.string().nullable(), archived: external_exports.string().nullable().optional() }).strict().optional()
68394
68398
  }).strict();
68395
68399
 
68396
68400
  // packages/shared-contracts/dist/voice.js
@@ -68611,7 +68615,7 @@ var IDLE_RELEASE_TIMEOUT_MINUTES_MIN = 5, IDLE_RELEASE_TIMEOUT_MINUTES_MAX = 144
68611
68615
  timeoutMinutes: external_exports.number().int().min(IDLE_RELEASE_TIMEOUT_MINUTES_MIN).max(IDLE_RELEASE_TIMEOUT_MINUTES_MAX)
68612
68616
  }), DEFAULT_IDLE_RELEASE_SETTINGS = Object.freeze({
68613
68617
  enabled: !1,
68614
- timeoutMinutes: 30
68618
+ timeoutMinutes: 10
68615
68619
  });
68616
68620
 
68617
68621
  // packages/shared-contracts/dist/harness-preferences.js
@@ -74354,6 +74358,111 @@ var KEYS_FILE = "push-keys.json", SUBSCRIPTIONS_FILE = "push-subscriptions.json"
74354
74358
  }
74355
74359
  };
74356
74360
 
74361
+ // packages/web-server/src/ch-thread-title.ts
74362
+ import { basename as basename2, win32 as win322 } from "node:path";
74363
+ function threadListTitle(row) {
74364
+ let name2 = row.name?.trim();
74365
+ if (name2) return name2;
74366
+ let preview = row.preview?.replace(/\s+/gu, " ").trim();
74367
+ if (preview) {
74368
+ let characters = Array.from(preview);
74369
+ return characters.length > 80 ? `${characters.slice(0, 80).join("")}\u2026` : preview;
74370
+ }
74371
+ let cwd = row.cwd ?? "", project2 = /^[a-z]:[\\/]|^\\\\/iu.test(cwd) ? win322.basename(cwd) : basename2(cwd);
74372
+ return project2.trim() ? project2 : row.id;
74373
+ }
74374
+
74375
+ // packages/web-server/src/ch-push-notifications.ts
74376
+ var INTERACTION_METHODS = /* @__PURE__ */ new Set([
74377
+ "item/commandExecution/requestApproval",
74378
+ "item/fileChange/requestApproval",
74379
+ "item/tool/requestUserInput",
74380
+ "mcpServer/elicitation/request",
74381
+ "codexhost/thread/questions/updated"
74382
+ ]), ChPushNotifications = class {
74383
+ constructor(host, push2) {
74384
+ this.host = host;
74385
+ this.push = push2;
74386
+ this.unsubscribe = host.subscribe((event) => this.receive(event));
74387
+ }
74388
+ host;
74389
+ push;
74390
+ unsubscribe;
74391
+ reads = /* @__PURE__ */ new Map();
74392
+ delivered = /* @__PURE__ */ new Set();
74393
+ epoch;
74394
+ sequence = 0;
74395
+ generation = 0;
74396
+ online = !1;
74397
+ closed = !1;
74398
+ receive(event) {
74399
+ if (this.closed) return;
74400
+ if (event.type === "connection") {
74401
+ this.online = event.online, event.online || this.generation++;
74402
+ return;
74403
+ }
74404
+ if (event.type === "hello") {
74405
+ this.epoch !== event.cursor.epoch && (this.epoch = event.cursor.epoch, this.delivered.clear(), this.sequence = 0, this.generation++), event.reset && (this.sequence = event.cursor.sequence);
74406
+ return;
74407
+ }
74408
+ if (event.type !== "changed" || event.cursor.epoch !== this.epoch || event.cursor.sequence <= this.sequence || (this.sequence = event.cursor.sequence, this.push.subscriptionCount === 0 || event.method !== "turn/completed" && !INTERACTION_METHODS.has(event.method)))
74409
+ return;
74410
+ let generation = this.generation, reading = (this.reads.get(event.threadId) ?? Promise.resolve()).then(() => this.reconcile(event, generation)).catch((error51) => {
74411
+ console.error("[push] Host notification failed", event.threadId, error51);
74412
+ }).finally(() => {
74413
+ this.reads.get(event.threadId) === reading && this.reads.delete(event.threadId);
74414
+ });
74415
+ this.reads.set(event.threadId, reading);
74416
+ }
74417
+ current(generation) {
74418
+ return !this.closed && this.online && this.generation === generation;
74419
+ }
74420
+ async reconcile(event, generation) {
74421
+ if (!this.current(generation) || this.push.subscriptionCount === 0) return;
74422
+ let snapshot = await this.host.snapshot(event.threadId, { truncateToolOutput: !0 });
74423
+ if (!this.current(generation) || snapshot.cursor.epoch !== event.cursor.epoch) return;
74424
+ let title = threadListTitle({ ...snapshot.thread, id: event.threadId }), messages2 = [];
74425
+ if (event.method === "turn/completed") {
74426
+ let turn = snapshot.turnsPage.data.find((value) => value.status !== "inProgress");
74427
+ turn && (turn.status === "completed" || turn.status === "failed") && messages2.push({
74428
+ key: JSON.stringify([event.cursor.epoch, event.threadId, "turn", turn.id]),
74429
+ message: {
74430
+ kind: turn.status === "failed" ? "error" : "turn",
74431
+ title,
74432
+ body: turn.status === "failed" ? "\u8FD0\u884C\u5931\u8D25" : "\u5DF2\u56DE\u590D"
74433
+ }
74434
+ });
74435
+ }
74436
+ for (let pending of snapshot.interactions)
74437
+ messages2.push({
74438
+ key: JSON.stringify([event.cursor.epoch, event.threadId, "interaction", pending.requestId]),
74439
+ message: {
74440
+ kind: pending.kind,
74441
+ title,
74442
+ body: pending.kind === "approval" ? "\u5F85\u6388\u6743" : "\u5F85\u56DE\u7B54"
74443
+ }
74444
+ });
74445
+ for (let { key, message: message2 } of messages2) {
74446
+ if (!this.current(generation)) return;
74447
+ if (!this.delivered.has(key)) {
74448
+ if (this.delivered.add(key), this.delivered.size > 512) {
74449
+ let oldest = this.delivered.values().next().value;
74450
+ oldest !== void 0 && this.delivered.delete(oldest);
74451
+ }
74452
+ await this.push.notify({
74453
+ ...message2,
74454
+ tag: event.threadId,
74455
+ sessionId: event.threadId,
74456
+ url: `./?session=${encodeURIComponent(event.threadId)}`
74457
+ });
74458
+ }
74459
+ }
74460
+ }
74461
+ close() {
74462
+ this.closed = !0, this.unsubscribe(), this.reads.clear(), this.delivered.clear();
74463
+ }
74464
+ };
74465
+
74357
74466
  // packages/web-server/src/plugins.ts
74358
74467
  var CLIENT_PLUGINS = [
74359
74468
  "@deepseek-ai/dsh-api-gateway",
@@ -74589,20 +74698,6 @@ function projectGroupRoot(snapshot, threadId, cwd) {
74589
74698
  return matches2.length ? matches2.sort((a, b) => b.length - a.length)[0] : project2?.rootPaths[0];
74590
74699
  }
74591
74700
 
74592
- // packages/web-server/src/ch-thread-title.ts
74593
- import { basename as basename2, win32 as win322 } from "node:path";
74594
- function threadListTitle(row) {
74595
- let name2 = row.name?.trim();
74596
- if (name2) return name2;
74597
- let preview = row.preview?.replace(/\s+/gu, " ").trim();
74598
- if (preview) {
74599
- let characters = Array.from(preview);
74600
- return characters.length > 80 ? `${characters.slice(0, 80).join("")}\u2026` : preview;
74601
- }
74602
- let cwd = row.cwd ?? "", project2 = /^[a-z]:[\\/]|^\\\\/iu.test(cwd) ? win322.basename(cwd) : basename2(cwd);
74603
- return project2.trim() ? project2 : row.id;
74604
- }
74605
-
74606
74701
  // packages/web-server/src/ch-file-operations.ts
74607
74702
  function conversationItems(turn) {
74608
74703
  if (turn.codexhostFileOperations === void 0) return turn.items;
@@ -75390,7 +75485,7 @@ var TurnProjector = class {
75390
75485
  this.step += 1, this.stepOpen = !0, this.stepHasTools = !1, this.messageCommitted = !1, this.blocks = [], this.blockByItem.clear(), this.log.append("step/start", { turn: this.turn, step: this.step });
75391
75486
  }
75392
75487
  closeStep() {
75393
- this.stepOpen && (!this.messageCommitted && this.blocks.some((block) => block.text !== "") ? this.commitMessage([]) : this.log.endAttempt(void 0), this.log.append("step/end", { turn: this.turn, step: this.step }), this.stepOpen = !1);
75488
+ this.stepOpen && (!this.messageCommitted && this.blocks.some((block) => block.text !== "") ? this.commitMessage([]) : this.log.endAttempt(void 0), [...this.tools.values()].some((tool) => tool.step === this.step) || this.log.append("step/end", { turn: this.turn, step: this.step }), this.stepOpen = !1);
75394
75489
  }
75395
75490
  ensureAttempt() {
75396
75491
  this.options.live && this.log.activeAttempt === void 0 && this.log.startAttempt(this.turn, this.step);
@@ -75482,6 +75577,7 @@ var TurnProjector = class {
75482
75577
  arguments: argumentsJson
75483
75578
  }), early = this.earlyOutput.get(item.itemId) ?? "";
75484
75579
  this.earlyOutput.delete(item.itemId), this.tools.set(item.itemId, {
75580
+ step: this.step,
75485
75581
  callId,
75486
75582
  name: call.name,
75487
75583
  args: call.args,
@@ -75519,7 +75615,7 @@ var TurnProjector = class {
75519
75615
  let call = toolCallOf(tool.item, this.options.cwd);
75520
75616
  tool.name = call.name, tool.args = call.args, this.log.append("tool/call", {
75521
75617
  turn: this.turn,
75522
- step: this.step,
75618
+ step: tool.step,
75523
75619
  callId: tool.callId,
75524
75620
  name: call.name,
75525
75621
  arguments: JSON.stringify(call.args)
@@ -75553,7 +75649,7 @@ var TurnProjector = class {
75553
75649
  let meta3 = item.type === "fileChange" ? { diffs: item.changes.map(diffTexts) } : isError ? void 0 : editMeta(tool.name, tool.args);
75554
75650
  this.log.append("tool/result", {
75555
75651
  turn: this.turn,
75556
- step: this.step,
75652
+ step: tool.step,
75557
75653
  message: {
75558
75654
  role: "tool",
75559
75655
  source: { kind: "tool", callId: tool.callId },
@@ -75570,7 +75666,7 @@ var TurnProjector = class {
75570
75666
  }
75571
75667
  } : {},
75572
75668
  ...meta3 === void 0 ? {} : { meta: meta3 }
75573
- }, { surfaceOp: "append", sourceEventSeqs: [tool.callSeq] });
75669
+ }, { surfaceOp: "append", sourceEventSeqs: [tool.callSeq] }), (tool.step !== this.step || !this.stepOpen) && ![...this.tools.values()].some((pending) => pending.step === tool.step) && this.log.append("step/end", { turn: this.turn, step: tool.step });
75574
75670
  }
75575
75671
  /** Close the Turn, optionally carrying the source's final reply identity. */
75576
75672
  finish(outcome2, finalAnswerItemId) {
@@ -75676,6 +75772,9 @@ function itemOf(item, reasoningPreviewIds) {
75676
75772
  return;
75677
75773
  }
75678
75774
  }
75775
+ function userMessageIdentity(item) {
75776
+ return JSON.stringify([item.content, item.clientId ?? null]);
75777
+ }
75679
75778
  function nativeTime(seconds) {
75680
75779
  return typeof seconds == "number" && Number.isFinite(seconds) && seconds >= 0 ? seconds * 1e3 : void 0;
75681
75780
  }
@@ -75786,8 +75885,23 @@ var ChThreadView = class _ChThreadView {
75786
75885
  for (let turn of thread.turns) {
75787
75886
  let view = this.turns.get(turn.id);
75788
75887
  if (!view) continue;
75789
- let previous = this.thread.turns.find((item) => item.id === turn.id), ids = new Set(conversationItems(turn).map((item) => item.id));
75790
- if (!view.done && [...view.items.keys()].some((id) => !ids.has(id)) || JSON.stringify(previous?.items.filter((item) => item.type === "userMessage")) !== JSON.stringify(turn.items.filter((item) => item.type === "userMessage")))
75888
+ let items = conversationItems(turn), ids = new Set(items.map((item) => item.id)), users = items.filter((item) => item.type === "userMessage"), published = [...view.started], lastUser = published.findLastIndex((id) => view.userMessages.has(id)), reasoningPreviewIds = new Set(
75889
+ items.filter((item) => item.type === "reasoning").map((item) => item.id)
75890
+ ), projections = new Map(
75891
+ items.map((item) => [item.id, itemOf(item, reasoningPreviewIds)])
75892
+ ), retained = items.filter((item) => {
75893
+ let projected = projections.get(item.id);
75894
+ return view.started.has(item.id) || projected && (projected.type !== "fileChange" || hasFileChangePath(projected));
75895
+ }), firstNewUser = items.findIndex(
75896
+ (item) => item.type === "userMessage" && !view.userMessages.has(item.id)
75897
+ );
75898
+ if (!view.done && [...view.items.keys()].some((id) => !ids.has(id)) || published.slice(0, lastUser + 1).some((id, index) => retained[index]?.id !== id) || [...view.userMessages].some(([id, identity], index) => {
75899
+ let user = users[index];
75900
+ return !user || user.id !== id || userMessageIdentity(user) !== identity;
75901
+ }) || view.done && (users.length !== view.userMessages.size || items.some((item) => {
75902
+ let projected = projections.get(item.id);
75903
+ return projected && (JSON.stringify(projected) !== JSON.stringify(view.items.get(item.id)) || typeof item.status == "string" && ["completed", "failed", "declined", "interrupted"].includes(item.status) && !view.completed.has(item.id));
75904
+ })) || firstNewUser !== -1 && items.slice(firstNewUser + 1).some((item) => view.started.has(item.id)))
75791
75905
  throw new SessionRuntimeError("host/items-replaced", "CH replaced already projected Items.");
75792
75906
  }
75793
75907
  Object.assign(this.thread, thread), this.log.setProjection("title", threadListTitle(thread)), this.log.setProjection("codexhostImportSource", thread.codexhostImportSource ?? null), this.log.setProjection("sessionListMetadata", {
@@ -75857,7 +75971,7 @@ var ChThreadView = class _ChThreadView {
75857
75971
  projectTurns(thread) {
75858
75972
  let previousEnd = thread.createdAt * 1e3;
75859
75973
  for (let turn of thread.turns) {
75860
- let view = this.turns.get(turn.id), complete = finished(turn.status), start = nativeTime(turn.startedAt) ?? (complete ? previousEnd : Date.now()), end = Math.max(
75974
+ let items = conversationItems(turn), view = this.turns.get(turn.id), complete = finished(turn.status), start = nativeTime(turn.startedAt) ?? (complete ? previousEnd : Date.now()), end = Math.max(
75861
75975
  start,
75862
75976
  nativeTime(turn.completedAt) ?? (view && !view.done ? Date.now() : start)
75863
75977
  );
@@ -75870,25 +75984,31 @@ var ChThreadView = class _ChThreadView {
75870
75984
  onToolOutput: (id, output) => {
75871
75985
  output === void 0 ? this.liveOutput.delete(id) : this.liveOutput.set(id, output.slice(-8e3));
75872
75986
  }
75873
- }), input = turn.items.filter((item) => item.type === "userMessage").flatMap((item) => Array.isArray(item.content) ? item.content : []), clientId = turn.items.find(
75874
- (item) => item.type === "userMessage" && typeof item.clientId == "string"
75875
- )?.clientId;
75876
- projector.begin(
75877
- this.projectInput(input),
75878
- typeof clientId == "string" ? clientId : void 0
75879
- ), view = {
75987
+ }), opening = items[0]?.type === "userMessage" ? items[0] : void 0;
75988
+ opening ? projector.begin(
75989
+ this.projectInput(Array.isArray(opening.content) ? opening.content : []),
75990
+ typeof opening.clientId == "string" ? opening.clientId : void 0
75991
+ ) : projector.beginAutonomous([]), view = {
75880
75992
  projector,
75881
75993
  items: /* @__PURE__ */ new Map(),
75882
- started: /* @__PURE__ */ new Set(),
75994
+ userMessages: new Map(opening ? [[opening.id, userMessageIdentity(opening)]] : []),
75995
+ started: new Set(opening ? [opening.id] : []),
75883
75996
  completed: /* @__PURE__ */ new Set(),
75884
75997
  done: !1
75885
75998
  }, this.turns.set(turn.id, view);
75886
75999
  }
75887
76000
  if (complete || (this.log.clock = void 0), view.done) continue;
75888
- let items = conversationItems(turn), reasoningPreviewIds = new Set(
76001
+ let reasoningPreviewIds = new Set(
75889
76002
  items.filter((item) => item.type === "reasoning").map((item) => item.id)
75890
76003
  );
75891
76004
  for (let item of items) {
76005
+ if (item.type === "userMessage") {
76006
+ view.userMessages.has(item.id) || (view.projector.steer(
76007
+ this.projectInput(Array.isArray(item.content) ? item.content : []),
76008
+ typeof item.clientId == "string" ? item.clientId : void 0
76009
+ ), view.userMessages.set(item.id, userMessageIdentity(item)), view.started.add(item.id));
76010
+ continue;
76011
+ }
75892
76012
  let projected = itemOf(item, reasoningPreviewIds);
75893
76013
  if (!projected || view.completed.has(item.id)) continue;
75894
76014
  let old = view.items.get(item.id);
@@ -76421,35 +76541,49 @@ async function forkChThread(host, view, atSeq) {
76421
76541
  let threadId = view.thread.id, owner = threadInspectionSchema.parse(
76422
76542
  await host.request("codexhost/thread/inspect", { threadId })
76423
76543
  );
76424
- if (owner.owner !== "external" || !owner.history.fork)
76544
+ if (owner.history?.fork === !1)
76425
76545
  return forkUnavailable(threadId, "This Thread does not support native forking.");
76426
76546
  let lastTurnId = view.forkTurnId(atSeq);
76427
- return lastTurnId ? { sessionId: externalThreadForkResultSchema.parse(
76547
+ if (!lastTurnId)
76548
+ return forkUnavailable(
76549
+ threadId,
76550
+ "No completed Turn exists at this fork point. Reopen the conversation."
76551
+ );
76552
+ if (owner.owner === "codex") {
76553
+ let id = (await host.request("thread/fork", {
76554
+ threadId,
76555
+ lastTurnId,
76556
+ excludeTurns: !0
76557
+ }))?.thread?.id;
76558
+ if (typeof id != "string" || !id.trim() || id === threadId)
76559
+ throw new SessionRuntimeError(
76560
+ "session/fork-unconfirmed",
76561
+ "Codex did not confirm a distinct forked Thread. Do not retry automatically.",
76562
+ { sessionId: threadId }
76563
+ );
76564
+ return { sessionId: id };
76565
+ }
76566
+ return { sessionId: externalThreadForkResultSchema.parse(
76428
76567
  await host.request("codexhost/thread/fork", { threadId, lastTurnId })
76429
- ).threadId } : forkUnavailable(
76430
- threadId,
76431
- "No completed Turn exists at this fork point. Reopen the conversation."
76432
- );
76568
+ ).threadId };
76433
76569
  }
76434
76570
 
76435
76571
  // packages/web-server/src/ch-pinned-threads.ts
76436
76572
  var ChPinnedThreads = class {
76437
- constructor(host, workspaces2, visible) {
76573
+ constructor(host, workspaces2) {
76438
76574
  this.host = host;
76439
76575
  this.workspaces = workspaces2;
76440
- this.visible = visible;
76441
76576
  workspaces2.setReferencePins([]);
76442
76577
  }
76443
76578
  host;
76444
76579
  workspaces;
76445
- visible;
76446
76580
  pending = Promise.resolve();
76447
76581
  refreshing;
76448
76582
  closed = !1;
76449
76583
  sync(change) {
76450
76584
  let next = this.pending.then(async () => {
76451
76585
  if (this.closed) return [];
76452
- let ids = (await this.host.pins(change)).filter(this.visible);
76586
+ let ids = await this.host.pins(change);
76453
76587
  return this.closed || this.workspaces.setReferencePins(ids), ids;
76454
76588
  });
76455
76589
  return this.pending = next.catch(() => {
@@ -77159,28 +77293,22 @@ function record3(value) {
77159
77293
  function presentation(pending) {
77160
77294
  let interaction = pending.interaction;
77161
77295
  if (interaction.type === "approval") {
77162
- let labels = interaction.actions.map(
77296
+ let nativeApproval = pending.request.method === "item/commandExecution/requestApproval" || pending.request.method === "item/fileChange/requestApproval", labels = interaction.actions.map(
77163
77297
  (action, index) => interaction.actions.filter((other) => other.label === action.label).length > 1 ? `${action.label} (${index + 1})` : action.label
77164
77298
  );
77165
77299
  return {
77166
77300
  questions: [
77167
77301
  {
77168
77302
  id: "approval",
77169
- header: interaction.title,
77170
- question: [
77171
- interaction.title,
77172
- interaction.description,
77173
- interaction.subject ? JSON.stringify(interaction.subject, null, 2) : void 0
77174
- ].filter(Boolean).join(`
77175
-
77176
- `),
77303
+ question: interaction.title,
77304
+ ...!nativeApproval && interaction.description ? { detail: interaction.description } : {},
77177
77305
  options: labels.map((label) => ({ label })),
77178
77306
  multiSelect: !1,
77179
77307
  allowOther: !1
77180
77308
  }
77181
77309
  ],
77182
77310
  result(value) {
77183
- let nativeApproval = pending.request.method === "item/commandExecution/requestApproval" || pending.request.method === "item/fileChange/requestApproval", answer = value?.answers?.find(
77311
+ let answer = value?.answers?.find(
77184
77312
  (entry) => entry.id === "approval"
77185
77313
  );
77186
77314
  if (value == null || answer?.selected?.length === 0 && !answer.custom) {
@@ -77395,6 +77523,49 @@ function readToolOutput(log, ref) {
77395
77523
  return { text: text2 };
77396
77524
  }
77397
77525
 
77526
+ // packages/web-server/src/ch-session-page.ts
77527
+ function decodeCatalogCursor(cursor, includeArchived) {
77528
+ try {
77529
+ let value = threadCatalogReadParamsSchema.parse(JSON.parse(cursor));
77530
+ if (value.includeArchived !== includeArchived || !value.officialCursor)
77531
+ throw new Error("scope mismatch");
77532
+ return value.officialCursor;
77533
+ } catch {
77534
+ throw new SessionRuntimeError("session/list-invalid", "Invalid session list cursor or archive scope.");
77535
+ }
77536
+ }
77537
+ function nextCatalogCursor(page, includeArchived) {
77538
+ let cursor = page.officialCursor;
77539
+ return cursor && (cursor.live !== null || includeArchived && cursor.archived != null) ? JSON.stringify({ includeArchived, officialCursor: cursor }) : null;
77540
+ }
77541
+ async function readSessionPage(host, timing, includeArchived, cursor, readPins = () => host.pins()) {
77542
+ let officialCursor = cursor === void 0 ? void 0 : decodeCatalogCursor(cursor, includeArchived), [page, pinned] = await Promise.all([
77543
+ timing.measure(
77544
+ "host_catalog",
77545
+ () => host.request(THREAD_CATALOG_READ_METHOD, {
77546
+ includeArchived,
77547
+ ...officialCursor ? { officialCursor } : {}
77548
+ })
77549
+ ),
77550
+ timing.measure("pins", readPins)
77551
+ ]), present = new Set(page.threads.map((row) => row.id)), missing = [...new Set(pinned)].filter((id) => !present.has(id)), offset = 0;
77552
+ return await Promise.all(
77553
+ Array.from({ length: Math.min(4, missing.length) }, async () => {
77554
+ for (; offset < missing.length; ) {
77555
+ let id = missing[offset++];
77556
+ if (id === void 0) break;
77557
+ let [result, owner] = await Promise.all([
77558
+ host.request("thread/read", { threadId: id, includeTurns: !1 }),
77559
+ host.request("codexhost/thread/ownership/list", { threadIds: [id] }).then((value) => threadOwnershipSchema.parse(value.threads[0]))
77560
+ ]);
77561
+ if (result.thread.id !== id || owner.threadId !== id)
77562
+ throw new Error("Pinned Thread identity mismatch");
77563
+ page.threads.push(result.thread), page.ownership.push(owner);
77564
+ }
77565
+ })
77566
+ ), { page, pinned };
77567
+ }
77568
+
77398
77569
  // packages/web-server/src/ch-sessions.ts
77399
77570
  function globalProjectionValues(values2) {
77400
77571
  return Object.fromEntries(
@@ -77420,11 +77591,13 @@ var ChSessions = class {
77420
77591
  "Subagent file uploads are unavailable."
77421
77592
  );
77422
77593
  return id;
77423
- }) : void 0, this.pins = new ChPinnedThreads(host, workspaces2, (id) => this.threads.hasHarness(id)), workspaces2.setReferenceArchives([]), this.interactions = host.realtime ? new ChInteractions(host.realtime, events2, (id, message2) => {
77594
+ }) : void 0, this.pins = new ChPinnedThreads(host, workspaces2), workspaces2.setReferenceArchives([]), this.interactions = host.realtime ? new ChInteractions(host.realtime, events2, (id, message2) => {
77424
77595
  this.threads.view(id)?.log.setProjection("nativeInteractionError", message2);
77425
77596
  }) : void 0, this.realtime = host.realtime ? new ChRealtime(host.realtime, {
77426
77597
  thread: (id) => this.view(id),
77427
- catalog: () => this.refresh(),
77598
+ catalog: async () => {
77599
+ await this.refresh();
77600
+ },
77428
77601
  archived: (id, archived) => this.updateArchive(id, archived),
77429
77602
  connection: (online) => {
77430
77603
  this.hostOnline = online, online || this.interactions?.clear();
@@ -77462,8 +77635,7 @@ var ChSessions = class {
77462
77635
  fileInputs;
77463
77636
  rows = /* @__PURE__ */ new Map();
77464
77637
  publishedCatalog;
77465
- /** Everything derived per Thread. Rows (replaced as a whole by each catalog read)
77466
- * and drafts (not yet Host Threads) keep their own lifecycles. */
77638
+ /** Loaded rows accumulate across partial official pages; drafts are not Host Threads. */
77467
77639
  threads = new WebThreadStates();
77468
77640
  drafts = /* @__PURE__ */ new Map();
77469
77641
  controls = /* @__PURE__ */ new Set();
@@ -77479,51 +77651,54 @@ var ChSessions = class {
77479
77651
  interactions;
77480
77652
  hostOnline = !0;
77481
77653
  hostEpoch;
77482
- notifier;
77483
77654
  id(id) {
77484
77655
  return this.drafts.get(id)?.canonicalId ?? id;
77485
77656
  }
77486
- async refresh(timing, includeArchived = !1) {
77657
+ async refresh(timing, includeArchived = !1, cursor) {
77487
77658
  if (this.closed) return;
77488
- if (this.refreshing && (!this.refreshingCatalog || includeArchived && !this.refreshingArchives)) {
77659
+ if (this.refreshing && (cursor !== void 0 || !this.refreshingCatalog || includeArchived !== this.refreshingArchives)) {
77489
77660
  let stage = this.refreshingCatalog ? "catalog_wait" : "workspace_wait", pending2 = this.refreshing.catch(() => {
77490
77661
  });
77491
- return await (timing ? timing.measure(stage, () => pending2) : pending2), this.refresh(timing, includeArchived);
77662
+ return await (timing ? timing.measure(stage, () => pending2) : pending2), this.refresh(timing, includeArchived, cursor);
77492
77663
  }
77493
77664
  let mode = this.refreshing ? "joined" : "started";
77494
77665
  if (!this.refreshing) {
77495
- this.refreshingCatalog = !0, this.refreshingArchives = includeArchived, this.refreshingTiming = new SessionListTiming();
77666
+ this.refreshingCatalog = cursor === void 0, this.refreshingArchives = includeArchived, this.refreshingTiming = new SessionListTiming();
77496
77667
  let archiveChanges = /* @__PURE__ */ new Map();
77497
77668
  this.refreshingArchiveChanges = archiveChanges, this.refreshing = this.listAll(
77498
77669
  this.refreshingTiming,
77499
77670
  includeArchived,
77500
- archiveChanges
77671
+ archiveChanges,
77672
+ cursor
77501
77673
  ).finally(() => {
77502
77674
  this.refreshing = void 0, this.refreshingCatalog = !1, this.refreshingArchives = !1, this.refreshingArchiveChanges = void 0, this.refreshingTiming = void 0;
77503
77675
  });
77504
77676
  }
77505
77677
  let pending = this.refreshing, catalogTiming = this.refreshingTiming;
77506
77678
  try {
77507
- await (timing ? timing.measure("catalog_wait", () => pending) : pending);
77679
+ return await (timing ? timing.measure("catalog_wait", () => pending) : pending);
77508
77680
  } finally {
77509
77681
  timing && catalogTiming && (timing.catalog = { mode, ...catalogTiming.snapshot() });
77510
77682
  }
77511
77683
  }
77512
- async listAll(timing, includeArchived, archiveChanges) {
77513
- let known = new Set(this.rows.keys()), snapshot = await timing.measure(
77514
- "host_catalog",
77515
- () => this.host.request(
77516
- THREAD_CATALOG_READ_METHOD,
77517
- { includeArchived }
77518
- )
77519
- );
77684
+ async listAll(timing, includeArchived, archiveChanges, cursor) {
77685
+ let known = new Set(this.rows.keys()), [{ page: snapshot }, definitions] = await Promise.all([
77686
+ readSessionPage(
77687
+ this.host,
77688
+ timing,
77689
+ includeArchived,
77690
+ cursor,
77691
+ () => this.pins.refresh().catch((error51) => (console.error("[session/list] native pins unavailable", error51), [...this.workspaces.pinnedSessionIds()]))
77692
+ ),
77693
+ timing.measure("project_definitions", () => this.host.projects([]))
77694
+ ]);
77520
77695
  if (this.closed) return;
77521
- let found = new Map(snapshot.threads.map((row) => [row.id, row])), projects = await timing.measure(
77696
+ let found = new Map(snapshot.threads.map((row) => [row.id, row])), projects = { ...await timing.measure(
77522
77697
  "projects",
77523
77698
  () => this.host.projects([.../* @__PURE__ */ new Set([...found.keys(), ...this.rows.keys()])])
77524
- );
77699
+ ), projects: definitions.projects };
77525
77700
  if (this.closed) return;
77526
- let snapshotArchives = new Set(snapshot.archivedThreadIds), archivedIds = new Set(includeArchived ? [] : this.workspaces.archivedSessionIds());
77701
+ let snapshotArchives = new Set(snapshot.archivedThreadIds), archivedIds = new Set(this.workspaces.archivedSessionIds());
77527
77702
  for (let id of snapshotArchives) archivedIds.add(id);
77528
77703
  for (let id of found.keys()) snapshotArchives.has(id) || archivedIds.delete(id);
77529
77704
  for (let [id, archived] of archiveChanges)
@@ -77541,27 +77716,13 @@ var ChSessions = class {
77541
77716
  let rows = new Map([...found].filter(([id]) => ownership.has(id)));
77542
77717
  for (let [id, row] of this.rows) {
77543
77718
  let harnessId = this.threads.harnessId(id);
77544
- if (!(found.has(id) || !harnessId)) {
77545
- if (known.has(id) && !archiveChanges.has(id) && (includeArchived || !archivedIds.has(id))) {
77546
- if (harnessId === "codex")
77547
- try {
77548
- let refreshed = await this.host.request("thread/read", {
77549
- threadId: id,
77550
- includeTurns: !1
77551
- });
77552
- refreshed.thread.id === id && (rows.set(id, refreshed.thread), ownership.set(id, harnessId));
77553
- } catch {
77554
- }
77555
- continue;
77556
- }
77557
- rows.set(id, row), ownership.set(id, harnessId);
77558
- }
77719
+ found.has(id) || !harnessId || harnessId !== "codex" && known.has(id) && !archiveChanges.has(id) && (includeArchived || !archivedIds.has(id)) || (rows.set(id, row), ownership.set(id, harnessId));
77559
77720
  }
77560
- this.threads.replaceHarnesses(ownership), this.rows = rows, timing.count("visibleRows", rows.size), await this.syncWorkspace(timing, projects), !this.closed && (timing.measureSync(
77561
- "archive_sync",
77562
- () => this.workspaces.setReferenceArchives([...archivedIds].sort())
77563
- ), await timing.measure("catalog_publish", () => this.publishCatalogChanges()), this.pins.refresh().catch(() => {
77564
- }), this.catalogReadAt = Date.now());
77721
+ if (this.threads.replaceHarnesses(ownership), this.rows = rows, timing.count("visibleRows", rows.size), await this.syncWorkspace(timing, projects), !this.closed)
77722
+ return timing.measureSync(
77723
+ "archive_sync",
77724
+ () => this.workspaces.setReferenceArchives([...archivedIds].sort())
77725
+ ), await timing.measure("catalog_publish", () => this.publishCatalogChanges(cursor !== void 0)), this.catalogReadAt = Date.now(), snapshot;
77565
77726
  }
77566
77727
  async syncWorkspace(timing = new SessionListTiming(), snapshot) {
77567
77728
  let projects = snapshot ?? await timing.measure("projects", () => this.host.projects([...this.rows.keys()]));
@@ -77581,7 +77742,7 @@ var ChSessions = class {
77581
77742
  }
77582
77743
  for (let draft of this.drafts.values())
77583
77744
  draft.canonicalId || (groups.set(draft.id, draft.cwd), draft.cwd || projectless.add(draft.id));
77584
- timing.count("workspaceReferences", references.length), this.workspaces.syncReferencedSessions(references, groups, projectless);
77745
+ timing.count("workspaceReferences", references.length), this.workspaces.syncReferencedSessions(references, groups, projectless, projects.projects);
77585
77746
  });
77586
77747
  }
77587
77748
  /** Only fields used by the list summary; projections have their own
@@ -77600,9 +77761,11 @@ var ChSessions = class {
77600
77761
  this.threads.harnessId(row.id)
77601
77762
  ]);
77602
77763
  }
77603
- async publishCatalogChanges() {
77764
+ async publishCatalogChanges(explicitPage = !1) {
77604
77765
  let before = this.publishedCatalog, next = new Map([...this.rows].map(([id, row]) => [id, this.catalogKey(row)])), changed = before === void 0 ? [] : await Promise.all(
77605
- [...this.rows.values()].filter((row) => before.get(row.id) !== next.get(row.id)).map((row) => this.summary(row))
77766
+ [...this.rows.values()].filter(
77767
+ (row) => before.get(row.id) !== next.get(row.id) && (!explicitPage || before.has(row.id))
77768
+ ).map((row) => this.summary(row))
77606
77769
  );
77607
77770
  this.publishedCatalog = next;
77608
77771
  for (let [id, row] of this.rows) {
@@ -78004,20 +78167,49 @@ var ChSessions = class {
78004
78167
  return { pinnedSessionIds: await this.pins.sync({ threadId: id, pinned }) };
78005
78168
  });
78006
78169
  rpc2.register("session/list", async (args, context) => {
78007
- let { includeArchived = !1 } = requestOf(args);
78170
+ let {
78171
+ includeArchived = !1,
78172
+ cursor,
78173
+ sessionId
78174
+ } = requestOf(args);
78175
+ if (sessionId !== void 0) {
78176
+ if (typeof sessionId != "string" || !sessionId || cursor !== void 0)
78177
+ throw new SessionRuntimeError("session/list-invalid", "Invalid direct-link identity.");
78178
+ let draft = this.drafts.get(sessionId);
78179
+ if (draft && !draft.canonicalId)
78180
+ return {
78181
+ items: [await this.draftSummary(draft, !1)],
78182
+ archivedSessionIds: this.workspaces.archivedSessionIds()
78183
+ };
78184
+ let id = this.id(sessionId), owner = await this.owner(id), { thread } = await this.host.request("thread/read", {
78185
+ threadId: id,
78186
+ includeTurns: !1
78187
+ });
78188
+ if (thread.id !== id || owner.owner === "external" && thread.modelProvider !== "codexhost")
78189
+ throw new SessionRuntimeError("session/ownership-unconfirmed", "Thread identity was not confirmed.");
78190
+ return this.rows.set(thread.id, thread), this.threads.setHarness(thread.id, owner.harnessId), {
78191
+ items: [await this.summary(thread)],
78192
+ archivedSessionIds: this.workspaces.archivedSessionIds()
78193
+ };
78194
+ }
78008
78195
  if (typeof includeArchived != "boolean")
78009
78196
  throw new SessionRuntimeError("session/list-invalid", "includeArchived must be a boolean.");
78010
78197
  let timing = context.sessionListTiming ?? new SessionListTiming();
78011
- await this.refresh(timing, includeArchived);
78012
- let archivedSessionIds = this.workspaces.archivedSessionIds(), archived = new Set(archivedSessionIds), items = await timing.measure("summaries", async () => [
78198
+ if (cursor !== void 0 && typeof cursor != "string")
78199
+ throw new SessionRuntimeError("session/list-invalid", "cursor must be a string.");
78200
+ let page = await this.refresh(timing, includeArchived, cursor), pageIds = new Set(page?.threads.map((row) => row.id)), archivedSessionIds = this.workspaces.archivedSessionIds(), archived = new Set(archivedSessionIds), items = await timing.measure("summaries", async () => [
78013
78201
  ...await Promise.all(
78014
- [...this.rows.values()].filter((row) => includeArchived || !archived.has(row.id)).map((row) => this.summary(row))
78202
+ [...this.rows.values()].filter((row) => pageIds.has(row.id) && (includeArchived || !archived.has(row.id))).map((row) => this.summary(row))
78015
78203
  ),
78016
78204
  ...await Promise.all(
78017
78205
  [...this.drafts.values()].filter((d) => !d.canonicalId).map((d) => this.draftSummary(d, !1))
78018
78206
  )
78019
78207
  ]);
78020
- return timing.count("resultRows", items.length), { items, archivedSessionIds };
78208
+ return timing.count("resultRows", items.length), {
78209
+ items,
78210
+ archivedSessionIds,
78211
+ nextCursor: page ? nextCatalogCursor(page, includeArchived) : null
78212
+ };
78021
78213
  }), rpc2.register("session/create", (args) => this.create(args)), rpc2.register("session/fork", async (args) => {
78022
78214
  let request2 = requestOf(args);
78023
78215
  if (typeof request2.sessionId != "string" || !request2.sessionId || request2.atSeq !== void 0 && (!Number.isSafeInteger(request2.atSeq) || request2.atSeq < 0))
@@ -78084,7 +78276,10 @@ var ChSessions = class {
78084
78276
  };
78085
78277
  return state.view?.log.setProjection("modelSelection", { lastUsed: selection, next: selection }), configuration2;
78086
78278
  }, configuration = publish(
78087
- await this.host.request("codexhost/thread/model/select", {
78279
+ owner.owner === "codex" && owner.effectiveModel?.id === request2.model ? {
78280
+ effectiveModel: owner.effectiveModel,
78281
+ effectiveThinkingOptionId: owner.effectiveThinkingOptionId
78282
+ } : await this.host.request("codexhost/thread/model/select", {
78088
78283
  threadId: id,
78089
78284
  model: { id: request2.model }
78090
78285
  })
@@ -78124,8 +78319,6 @@ var ChSessions = class {
78124
78319
  }) : await this.host.request("turn/start", {
78125
78320
  threadId,
78126
78321
  input,
78127
- delivery: "queue",
78128
- presentationInput: presentation2,
78129
78322
  ...request2.requestId ? { clientUserMessageId: request2.requestId } : {}
78130
78323
  });
78131
78324
  } else
@@ -78213,7 +78406,6 @@ var ChSessions = class {
78213
78406
  let view = await this.view(request2.sessionId);
78214
78407
  return { title: request2.title, seq: view.log.lastSeq };
78215
78408
  }), rpc2.register("session/search", async (args) => {
78216
- await this.refresh();
78217
78409
  let needle = requestOf(args).query.toLowerCase();
78218
78410
  return {
78219
78411
  items: [...this.rows.values()].map((row) => ({ sessionId: row.id, snippet: threadListTitle(row) })).filter((item) => item.snippet.toLowerCase().includes(needle)).slice(0, 20),
@@ -81467,6 +81659,7 @@ var exec3 = promisify5(execFile5), METHODS = /* @__PURE__ */ new Set([
81467
81659
  "thread/revert",
81468
81660
  "thread/start",
81469
81661
  "thread/resume",
81662
+ "thread/fork",
81470
81663
  "thread/name/set",
81471
81664
  "thread/archive",
81472
81665
  "thread/unarchive",
@@ -83858,7 +84051,7 @@ var MAX_TIMER_DELAY_MS = 2147483647;
83858
84051
 
83859
84052
  // apps/web-ui/packages/api/gateway/src/index.ts
83860
84053
  import z3 from "@deepseek-ai/schemastery";
83861
- var NEVER_ABORTED_SIGNAL = new AbortController().signal, DEFAULT_WEBSOCKET_HEARTBEAT_INTERVAL_MS = 2e3, DEFAULT_STREAM_INBOX_BYTES = 262144, EMPTY_ASYNC_ITERABLE = {
84054
+ var NEVER_ABORTED_SIGNAL = new AbortController().signal, DEFAULT_WEBSOCKET_HEARTBEAT_INTERVAL_MS = 3e4, DEFAULT_STREAM_INBOX_BYTES = 262144, EMPTY_ASYNC_ITERABLE = {
83862
84055
  [Symbol.asyncIterator]: () => ({ next: () => Promise.resolve({ value: void 0, done: !0 }) })
83863
84056
  }, UPLINK_DONE2 = { value: void 0, done: !0 }, SRC_JSON_CODEC = { mode: "src-json" }, TypertGatewayError = class extends RemoteError {
83864
84057
  /** Canonical `<namespace>/<method>` endpoint. */
@@ -93459,6 +93652,7 @@ import { execFileSync } from "node:child_process";
93459
93652
 
93460
93653
  // apps/web-ui/packages/subprocess/subprocess-local/src/windows-inspector.ts
93461
93654
  import { spawnSync } from "node:child_process";
93655
+ import { win32 as win326 } from "node:path";
93462
93656
  var requireKoffi = createLazyRequire("koffi", import.meta.url);
93463
93657
  function windowsProcessTree(entries, rootPid, started) {
93464
93658
  let root = new Map(entries.map((entry) => [entry.pid, entry])).get(rootPid);
@@ -93516,10 +93710,16 @@ function createWindowsProcessInspector(internals = defaultWindowsProcessInternal
93516
93710
  return new WindowsProcessInspector(internals);
93517
93711
  }
93518
93712
  function taskkillTree(pid, force) {
93519
- pid <= 0 || spawnSync("taskkill", ["/PID", String(pid), "/T", ...force ? ["/F"] : []], {
93520
- stdio: "ignore",
93521
- windowsHide: !0
93522
- });
93713
+ if (pid <= 0) return;
93714
+ let systemRoot = process.env.SystemRoot ?? process.env.windir;
93715
+ if (!systemRoot || !/^[A-Za-z]:[\\/]/u.test(systemRoot))
93716
+ throw new Error("Windows SystemRoot is unavailable or invalid");
93717
+ let result = spawnSync(
93718
+ win326.join(systemRoot, "System32", "taskkill.exe"),
93719
+ ["/PID", String(pid), "/T", ...force ? ["/F"] : []],
93720
+ { stdio: "ignore", windowsHide: !0 }
93721
+ );
93722
+ if (result.error) throw result.error;
93523
93723
  }
93524
93724
  function isInvalidHandle(value) {
93525
93725
  if (value == null) return !0;
@@ -95337,20 +95537,14 @@ var LocalTerminalHandle = class {
95337
95537
  this.shellActivity = shellActivity;
95338
95538
  this.onQuiescence = onQuiescence;
95339
95539
  this.observeShellExit = observeShellExit;
95340
- this.pid = terminal.pid;
95341
- try {
95342
- this.rootIdentity = inspector.snapshot().tree(this.pid).find((member) => member.pid === this.pid);
95343
- } catch {
95344
- this.rootIdentity = void 0;
95345
- }
95346
- this.done = this.outcome.promise;
95540
+ this.captureProcessIdentity(), this.done = this.outcome.promise;
95347
95541
  let resume = () => {
95348
95542
  this.outputPaused && (this.outputPaused = !1, this.exited || terminal.resume());
95349
95543
  };
95350
95544
  this.output.on("drain", resume), this.output.once("close", () => {
95351
95545
  this.output.off("drain", resume);
95352
95546
  }), this.dataDisposable = terminal.onData((data2) => {
95353
- !this.output.write(Buffer2.from(data2, "utf8")) && this.cleanup === void 0 && !this.outputPaused && (this.outputPaused = !0, terminal.pause());
95547
+ this.captureProcessIdentity(), !this.output.write(Buffer2.from(data2, "utf8")) && this.cleanup === void 0 && !this.outputPaused && (this.outputPaused = !0, terminal.pause());
95354
95548
  }), this.exitDisposable = terminal.onExit(({ exitCode, signal: exitSignal }) => {
95355
95549
  if (this.exited) return;
95356
95550
  this.exited = !0, this.managedOwner !== void 0 && this.observeShellExit && this.managedOwner.waitForExit().then(() => {
@@ -95377,7 +95571,10 @@ var LocalTerminalHandle = class {
95377
95571
  shellActivity;
95378
95572
  onQuiescence;
95379
95573
  observeShellExit;
95380
- pid;
95574
+ processId = 0;
95575
+ get pid() {
95576
+ return this.processId;
95577
+ }
95381
95578
  output = new PassThrough();
95382
95579
  done;
95383
95580
  outcome = Promise.withResolvers();
@@ -95394,6 +95591,16 @@ var LocalTerminalHandle = class {
95394
95591
  managedRangeEmpty = !1;
95395
95592
  /** The spawned shell's start identity; scans stop adopting members once the root pid no longer carries it. */
95396
95593
  rootIdentity;
95594
+ captureProcessIdentity() {
95595
+ if (!(this.processId > 0 || this.exited || this.terminal.pid <= 0)) {
95596
+ this.processId = this.terminal.pid;
95597
+ try {
95598
+ this.rootIdentity = this.inspector.snapshot().tree(this.processId).find((member) => member.pid === this.processId);
95599
+ } catch {
95600
+ this.rootIdentity = void 0;
95601
+ }
95602
+ }
95603
+ }
95397
95604
  /** Whether node-pty has not yet published the top-level exit event. */
95398
95605
  get running() {
95399
95606
  return !this.exited;
@@ -95412,7 +95619,7 @@ var LocalTerminalHandle = class {
95412
95619
  // Local inspection is synchronous; the seam returns a promise for remote transports.
95413
95620
  // oxlint-disable-next-line typescript/require-await -- Preserve promise rejection semantics at the async provider contract.
95414
95621
  async inspectForeground() {
95415
- this.descendants(this.inspector.snapshot());
95622
+ this.captureProcessIdentity(), this.descendants(this.inspector.snapshot());
95416
95623
  let processGroupId = this.inspector.foregroundPgid(this.pid);
95417
95624
  if (processGroupId !== void 0)
95418
95625
  return {
@@ -95422,6 +95629,7 @@ var LocalTerminalHandle = class {
95422
95629
  }
95423
95630
  // oxlint-disable-next-line typescript/require-await -- Local inspection is synchronous; SSH shares this promise interface.
95424
95631
  async inspectActivity() {
95632
+ this.captureProcessIdentity();
95425
95633
  let state = this.quiescent ? "idle" : "unknown", revision = 0;
95426
95634
  if (!this.quiescent)
95427
95635
  try {
@@ -95482,7 +95690,7 @@ var LocalTerminalHandle = class {
95482
95690
  * event. This does not claim quiescence and does not replace terminate().
95483
95691
  */
95484
95692
  terminateForHostExit() {
95485
- this.forceStopDescendants(), this.forceStopShell(), this.forceStopDescendants(), this.managedOwner?.terminateForHostExit();
95693
+ this.captureProcessIdentity(), this.forceStopDescendants(), this.forceStopShell(), this.forceStopDescendants(), this.managedOwner?.terminateForHostExit();
95486
95694
  }
95487
95695
  forceStopShell() {
95488
95696
  if (!this.exited) {
@@ -95597,6 +95805,12 @@ var LocalTerminalHandle = class {
95597
95805
  }
95598
95806
  }
95599
95807
  async closeOnce() {
95808
+ if (this.platform === "win32" && this.processId === 0 && !this.exited) {
95809
+ let deadline = Date.now() + 5e3;
95810
+ for (; this.terminal.pid <= 0 && !this.exited && Date.now() < deadline; ) await delay(10);
95811
+ if (this.captureProcessIdentity(), this.processId === 0 && !this.exited)
95812
+ throw new Error("terminal cleanup failed; ConPTY process is not ready");
95813
+ }
95600
95814
  if (this.managedOwner !== void 0) {
95601
95815
  try {
95602
95816
  await this.closeManagedRange(this.managedOwner), this.dataDisposable.dispose(), this.exitDisposable.dispose();
@@ -97756,6 +97970,7 @@ var FILE3 = "workspaces.json", Workspaces = class {
97756
97970
  referenceGroups;
97757
97971
  referenceProjectless = /* @__PURE__ */ new Set();
97758
97972
  referencePins;
97973
+ referenceProjectRoots = /* @__PURE__ */ new Set();
97759
97974
  referenceArchives;
97760
97975
  save() {
97761
97976
  this.data.writeJson(FILE3, this.state);
@@ -97776,7 +97991,7 @@ var FILE3 = "workspaces.json", Workspaces = class {
97776
97991
  );
97777
97992
  return items.filter((workspace) => {
97778
97993
  let legacyReference = workspace.origin === void 0 && workspace.sessionIds.length > 0 && workspace.sessionIds.some((id) => references.has(id)) && workspace.sessionIds.every((id) => references.has(id) || id.startsWith("draft-"));
97779
- return workspace.origin === "web" || !(workspace.origin === "codexhost" || legacyReference) || allowedRoots.has(workspace.path);
97994
+ return workspace.origin === "web" || !(workspace.origin === "codexhost" || legacyReference) || allowedRoots.has(workspace.path) || this.referenceProjectRoots.has(workspace.path);
97780
97995
  }).map((workspace) => ({
97781
97996
  ...workspace,
97782
97997
  sessionIds: workspace.sessionIds.filter(
@@ -97873,13 +98088,28 @@ var FILE3 = "workspaces.json", Workspaces = class {
97873
98088
  return title && workspace.origin !== "web" && workspace.title !== title && (workspace.title = title, this.touch(workspace)), workspace.sessionIds.includes(sessionId) || (workspace.sessionIds.push(sessionId), this.touch(workspace)), workspace;
97874
98089
  }
97875
98090
  /** Mirror one complete CH workspace snapshot, saving and publishing only final changes. */
97876
- syncReferencedSessions(sessions2, groups, projectless = /* @__PURE__ */ new Set()) {
98091
+ syncReferencedSessions(sessions2, groups, projectless = /* @__PURE__ */ new Set(), projects = []) {
97877
98092
  let before = new Map(
97878
98093
  this.list().map((workspace) => [workspace.workspaceId, JSON.stringify(workspace)])
97879
98094
  ), items = this.state.items.map((workspace) => ({
97880
98095
  ...workspace,
97881
98096
  sessionIds: [...workspace.sessionIds]
97882
- })), byPath = new Map(items.map((workspace) => [workspace.path, workspace])), members = /* @__PURE__ */ new Map(), now = (/* @__PURE__ */ new Date()).toISOString();
98097
+ })), byPath = new Map(items.map((workspace) => [workspace.path, workspace])), members = /* @__PURE__ */ new Map(), now = (/* @__PURE__ */ new Date()).toISOString(), projectRoots = /* @__PURE__ */ new Set();
98098
+ for (let project2 of projects)
98099
+ for (let root of project2.rootPaths) {
98100
+ let path8 = resolve17(root);
98101
+ projectRoots.add(path8);
98102
+ let workspace = byPath.get(path8);
98103
+ workspace ? workspace.origin !== "web" && (workspace.title = project2.name) : (workspace = {
98104
+ workspaceId: randomUUID14(),
98105
+ path: path8,
98106
+ title: project2.name,
98107
+ sessionIds: [],
98108
+ createdAt: now,
98109
+ updatedAt: now,
98110
+ origin: "codexhost"
98111
+ }, items.push(workspace), byPath.set(path8, workspace));
98112
+ }
97883
98113
  for (let { sessionId, cwd, title } of sessions2) {
97884
98114
  let workspace = byPath.get(resolve17(cwd));
97885
98115
  if (!workspace) {
@@ -97914,7 +98144,7 @@ var FILE3 = "workspaces.json", Workspaces = class {
97914
98144
  let state = { ...this.state, items };
97915
98145
  this.data.writeJson(FILE3, state), this.state = state;
97916
98146
  }
97917
- this.referenceGroups = groups, this.referenceProjectless = projectless;
98147
+ this.referenceProjectRoots = projectRoots, this.referenceGroups = groups, this.referenceProjectless = projectless;
97918
98148
  let after = this.list(), ids = new Set(after.map((workspace) => workspace.workspaceId));
97919
98149
  for (let workspaceId of before.keys())
97920
98150
  ids.has(workspaceId) || this.broadcast({ type: "remove", workspaceId });
@@ -98264,17 +98494,7 @@ var settings = new Settings(data, events), push = new PushNotifier(data);
98264
98494
  accessTokens !== void 0 && (push.retainOwners((owner) => owner === LOCAL_CREDENTIAL || accessTokens.has(owner)), accessTokens.onRevoked((id) => {
98265
98495
  connections.revoke(id), push.dropOwner(id);
98266
98496
  }));
98267
- sessions.notifier = (notification) => {
98268
- push.notify({
98269
- kind: notification.kind,
98270
- title: notification.title,
98271
- body: notification.body,
98272
- tag: notification.sessionId,
98273
- sessionId: notification.sessionId,
98274
- url: `./?session=${encodeURIComponent(notification.sessionId)}`
98275
- });
98276
- };
98277
- var devWebUi = values["dev-web-ui"] === void 0 ? void 0 : resolve19(values["dev-web-ui"]), assetOptions = PACKED && devWebUi === void 0 ? {
98497
+ var pushNotifications = chHost.realtime ? new ChPushNotifications(chHost.realtime, push) : void 0, devWebUi = values["dev-web-ui"] === void 0 ? void 0 : resolve19(values["dev-web-ui"]), assetOptions = PACKED && devWebUi === void 0 ? {
98278
98498
  packedRoot: PACKED_WEB,
98279
98499
  plugins: [],
98280
98500
  globals: CLIENT_GLOBALS,
@@ -98649,7 +98869,7 @@ server.listen(Number(values.port), values.host, async () => {
98649
98869
  });
98650
98870
  });
98651
98871
  async function shutdown(exitCode = 0) {
98652
- relay.close(), shellReload?.close(), accessTokens?.close(), server.close(), desktopScreenshots.close(), await sidebarBrowser.close(), await dsh?.close(), await extensions2.cleanup(!0), await sessions.close(), await preferences?.close(), displaySettings.close(), process.exit(exitCode);
98872
+ relay.close(), shellReload?.close(), accessTokens?.close(), server.close(), desktopScreenshots.close(), await sidebarBrowser.close(), await dsh?.close(), await extensions2.cleanup(!0), pushNotifications?.close(), await sessions.close(), await preferences?.close(), displaySettings.close(), process.exit(exitCode);
98653
98873
  }
98654
98874
  process.on("SIGINT", () => {
98655
98875
  shutdown();