niuma-codex 0.0.1-beta.28 → 0.0.1-beta.29

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.
@@ -6820,6 +6820,45 @@ var init_push_preferences = __esm({
6820
6820
  }
6821
6821
  });
6822
6822
 
6823
+ // packages/protocol/src/thread-load-trace.ts
6824
+ function object8(value, required, optional = []) {
6825
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) throw new TypeError("invalid_load_report");
6826
+ const descriptors = Object.getOwnPropertyDescriptors(value);
6827
+ if (Reflect.ownKeys(value).some((key) => typeof key !== "string" || ![...required, ...optional].includes(key)) || required.some((key) => !Object.hasOwn(descriptors, key)) || Object.values(descriptors).some((d) => !d.enumerable || !("value" in d))) throw new TypeError("invalid_load_report");
6828
+ return value;
6829
+ }
6830
+ function timing(value) {
6831
+ if (!Number.isInteger(value.elapsed_ms) || value.elapsed_ms < 0 || value.elapsed_ms > 9e5 || value.occurred_at_ms !== void 0 && (!Number.isInteger(value.occurred_at_ms) || value.occurred_at_ms < 1 || value.occurred_at_ms > 864e13)) throw new TypeError("invalid_load_timing");
6832
+ }
6833
+ function parseThreadLoadReportV2(value) {
6834
+ const v = object8(value, ["version", "thread_id", "load_id", "client_class", "samples"], ["terminal"]);
6835
+ if (v.version !== 2 || typeof v.thread_id !== "string" || !v.thread_id.length || v.thread_id.length > 256 || typeof v.load_id !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(v.load_id) || !["web", "ios", "other"].includes(v.client_class) || !Array.isArray(v.samples) || v.samples.length > 6 || !v.samples.length && v.terminal === void 0) throw new TypeError("invalid_load_report");
6836
+ for (const sample of v.samples) {
6837
+ const s = object8(sample, ["phase", "elapsed_ms"], ["occurred_at_ms"]);
6838
+ timing(s);
6839
+ if (!CLIENT_LOAD_PHASES.includes(s.phase)) throw new TypeError("invalid_load_phase");
6840
+ }
6841
+ if (v.terminal !== void 0) {
6842
+ const t = object8(v.terminal, ["kind", "phase", "reason", "elapsed_ms"], ["occurred_at_ms"]);
6843
+ timing(t);
6844
+ const reasons = { failed: ["open_rejected", "history_failed"], cancelled: ["left_view", "user_cancelled"], superseded: ["stream_replaced"] };
6845
+ if (!["request_started", "snapshot_received", "history_ready"].includes(t.phase) || !Object.hasOwn(reasons, t.kind) || !reasons[t.kind]?.includes(t.reason) || v.samples.some((s) => s.phase === "content_presented")) throw new TypeError("invalid_load_terminal");
6846
+ }
6847
+ return v;
6848
+ }
6849
+ function reduceThreadLoadOutcome(current2, event) {
6850
+ if (event === "closed" || event === "expired") return current2.status === "loading" ? { ...current2, status: "incomplete" } : current2;
6851
+ if (current2.status === "loading" || current2.status === "incomplete" && ["cancelled", "superseded"].includes(event)) return { ...current2, status: event };
6852
+ return current2.status === event ? current2 : { ...current2, conflict: true };
6853
+ }
6854
+ var CLIENT_LOAD_PHASES;
6855
+ var init_thread_load_trace = __esm({
6856
+ "packages/protocol/src/thread-load-trace.ts"() {
6857
+ "use strict";
6858
+ CLIENT_LOAD_PHASES = ["selected", "restored", "request_started", "snapshot_received", "history_ready", "content_presented"];
6859
+ }
6860
+ });
6861
+
6823
6862
  // packages/protocol/src/index.ts
6824
6863
  var init_src2 = __esm({
6825
6864
  "packages/protocol/src/index.ts"() {
@@ -6875,13 +6914,14 @@ var init_src2 = __esm({
6875
6914
  init_attachment_local_cache_v1();
6876
6915
  init_thread_action_state_v1();
6877
6916
  init_push_preferences();
6917
+ init_thread_load_trace();
6878
6918
  }
6879
6919
  });
6880
6920
 
6881
6921
  // apps/desktop/src/release-version.ts
6882
6922
  import { readFileSync } from "node:fs";
6883
6923
  function desktopReleaseVersion() {
6884
- return false ? null : "0.0.1-beta.28";
6924
+ return false ? null : "0.0.1-beta.29";
6885
6925
  }
6886
6926
  function desktopPresenceVersion() {
6887
6927
  return desktopReleaseVersion() ?? sourcePresenceVersion;
@@ -11337,6 +11377,65 @@ var init_launcher = __esm({
11337
11377
  }
11338
11378
  });
11339
11379
 
11380
+ // apps/desktop/src/daemon/runtime/ipc-thread-discovery.ts
11381
+ var RuntimeIpcThreadDiscovery;
11382
+ var init_ipc_thread_discovery = __esm({
11383
+ "apps/desktop/src/daemon/runtime/ipc-thread-discovery.ts"() {
11384
+ "use strict";
11385
+ RuntimeIpcThreadDiscovery = class {
11386
+ constructor(ports) {
11387
+ this.ports = ports;
11388
+ }
11389
+ ports;
11390
+ pending = /* @__PURE__ */ new Map();
11391
+ discover(id5) {
11392
+ const started2 = Date.now();
11393
+ const report = (phase) => {
11394
+ try {
11395
+ this.ports.diagnostic(id5, phase, Date.now() - started2);
11396
+ } catch {
11397
+ }
11398
+ };
11399
+ if (!this.ports.eligible(id5)) {
11400
+ report("ignored");
11401
+ return;
11402
+ }
11403
+ if (this.pending.has(id5)) {
11404
+ report("deduplicated");
11405
+ return;
11406
+ }
11407
+ if (this.pending.size >= 66) {
11408
+ report("capacity");
11409
+ return;
11410
+ }
11411
+ const current2 = this.ports.capture();
11412
+ report("received");
11413
+ const task = this.run(id5, current2, report).catch(() => report("failed")).finally(() => {
11414
+ if (this.pending.get(id5) === task) this.pending.delete(id5);
11415
+ });
11416
+ this.pending.set(id5, task);
11417
+ }
11418
+ async run(id5, current2, report) {
11419
+ const result = await this.ports.read(id5);
11420
+ report("read_completed");
11421
+ const valid = () => current2() && this.ports.eligible(id5) && result.kind === "ordinary" && result.summary?.id === id5 && !!result.token && this.ports.current(result.token);
11422
+ if (!valid()) {
11423
+ report("rejected");
11424
+ return;
11425
+ }
11426
+ await this.ports.enqueue(() => {
11427
+ if (!valid()) {
11428
+ report("stale");
11429
+ return;
11430
+ }
11431
+ this.ports.commit(result.summary);
11432
+ report("published");
11433
+ });
11434
+ }
11435
+ };
11436
+ }
11437
+ });
11438
+
11340
11439
  // apps/desktop/src/daemon/async-question-response.ts
11341
11440
  import { createHash } from "node:crypto";
11342
11441
  function submissionId(input) {
@@ -14316,11 +14415,11 @@ var init_activity_publication = __esm({
14316
14415
  if (!thread4) {
14317
14416
  return null;
14318
14417
  }
14319
- const timing = this.ports.turnTiming(signal.threadId, signal.turnId);
14320
- const timingFields = timing ? {
14321
- turnStartedAt: timing.startedAt,
14322
- ...timing.endedAt !== void 0 ? { turnEndedAt: timing.endedAt } : {},
14323
- ...timing.outcome !== void 0 ? { turnOutcome: timing.outcome } : {}
14418
+ const timing2 = this.ports.turnTiming(signal.threadId, signal.turnId);
14419
+ const timingFields = timing2 ? {
14420
+ turnStartedAt: timing2.startedAt,
14421
+ ...timing2.endedAt !== void 0 ? { turnEndedAt: timing2.endedAt } : {},
14422
+ ...timing2.outcome !== void 0 ? { turnOutcome: timing2.outcome } : {}
14324
14423
  } : {};
14325
14424
  if (signal.type === "turn_aborted") {
14326
14425
  return {
@@ -20217,7 +20316,7 @@ var init_private_journal = __esm({
20217
20316
  function fail14(reason) {
20218
20317
  throw new MessageSubmissionStoreError(reason);
20219
20318
  }
20220
- function object8(value, keys3, optional = []) {
20319
+ function object9(value, keys3, optional = []) {
20221
20320
  if (!value || typeof value !== "object" || Array.isArray(value)) return fail14("invalid");
20222
20321
  const record25 = value;
20223
20322
  if (Object.keys(record25).some((key) => !keys3.includes(key) && !optional.includes(key)) || !keys3.every((key) => Object.hasOwn(record25, key))) return fail14("invalid");
@@ -20251,7 +20350,7 @@ function parseReceipt(value, clientMessageId) {
20251
20350
  return receipt;
20252
20351
  }
20253
20352
  function parsePost(value, clientMessageId) {
20254
- const record25 = object8(value, ["httpStatus", "code", "message", "data"]);
20353
+ const record25 = object9(value, ["httpStatus", "code", "message", "data"]);
20255
20354
  if (![200, 400, 404, 500].includes(record25.httpStatus) || !Number.isInteger(record25.code) || record25.code !== 0 && (record25.code < 1e5 || record25.code > 999999) || typeof record25.message !== "string" || record25.message.length === 0 || Buffer.byteLength(record25.message) > MAX_MESSAGE_BYTES || byteSize(record25.message) > MAX_MESSAGE_BYTES + 2) fail14("invalid");
20256
20355
  if (record25.code === 0) {
20257
20356
  if (record25.httpStatus !== 200) fail14("invalid");
@@ -20264,10 +20363,10 @@ function parsePost(value, clientMessageId) {
20264
20363
  } else if (record25.data !== null) {
20265
20364
  const data = record25.data;
20266
20365
  if (data.state === "unknown") {
20267
- object8(data, ["client_message_id", "state"]);
20366
+ object9(data, ["client_message_id", "state"]);
20268
20367
  if (data.client_message_id !== clientMessageId) fail14("invalid");
20269
20368
  } else {
20270
- object8(data, ["dispatch_state"]);
20369
+ object9(data, ["dispatch_state"]);
20271
20370
  if (data.dispatch_state !== "not_dispatched") fail14("invalid");
20272
20371
  }
20273
20372
  }
@@ -20300,13 +20399,13 @@ function parseDocument(raw, maxEntries) {
20300
20399
  } catch {
20301
20400
  return fail14("invalid");
20302
20401
  }
20303
- const document = object8(value, ["version", "entries"]);
20402
+ const document = object9(value, ["version", "entries"]);
20304
20403
  if (document.version !== 1 || !Array.isArray(document.entries)) fail14("invalid");
20305
20404
  if (document.entries.length > maxEntries) fail14("capacity");
20306
20405
  const entries = /* @__PURE__ */ new Map();
20307
20406
  const nativeItems = /* @__PURE__ */ new Set();
20308
20407
  for (const value2 of document.entries) {
20309
- const record25 = object8(value2, ["principal", "threadId", "clientMessageId", "fingerprint", "nativeToken", "receipt", "firstResult", "createdAt", "updatedAt"], ["questionId"]);
20408
+ const record25 = object9(value2, ["principal", "threadId", "clientMessageId", "fingerprint", "nativeToken", "receipt", "firstResult", "createdAt", "updatedAt"], ["questionId"]);
20310
20409
  if (typeof record25.nativeToken !== "boolean" || !Number.isSafeInteger(record25.createdAt) || record25.createdAt < 0 || !Number.isSafeInteger(record25.updatedAt) || record25.updatedAt < 0) fail14("invalid");
20311
20410
  const entry = { ...record25, nativeToken: record25.nativeToken ? record25.clientMessageId : null };
20312
20411
  validateReservation(entry);
@@ -20398,7 +20497,7 @@ async function openMessageSubmissionStore(options) {
20398
20497
  reserve(input) {
20399
20498
  const captured = clone(input);
20400
20499
  return enqueue(async () => {
20401
- object8(captured, ["principal", "threadId", "clientMessageId", "fingerprint", "nativeToken"], ["questionId"]);
20500
+ object9(captured, ["principal", "threadId", "clientMessageId", "fingerprint", "nativeToken"], ["questionId"]);
20402
20501
  validateReservation(captured);
20403
20502
  const existing = entries.get(captured.clientMessageId);
20404
20503
  if (existing) {
@@ -24313,8 +24412,8 @@ var init_transport = __esm({
24313
24412
  /** 慢响应只保留每分钟首条及抑制数;超时和协议失败仍逐次保留。 */
24314
24413
  auditSlowResponse(id5, pending, bytes3, failed) {
24315
24414
  if (!Number.isFinite(pending.startedAtMs)) return;
24316
- const timing = this.requestTiming(pending), now = performance.now();
24317
- if (timing.elapsedMs < 5e3) return;
24415
+ const timing2 = this.requestTiming(pending), now = performance.now();
24416
+ if (timing2.elapsedMs < 5e3) return;
24318
24417
  if (now - this.lastSlowAuditAtMs < 6e4) {
24319
24418
  this.suppressedSlowAudits++;
24320
24419
  return;
@@ -24325,7 +24424,7 @@ var init_transport = __esm({
24325
24424
  ...typeof id5 === "number" ? { requestId: id5 } : {},
24326
24425
  ...isSafeErrorAuditOperation(pending.method) ? { method: pending.method } : {},
24327
24426
  bytes: bytes3,
24328
- requestTiming: timing,
24427
+ requestTiming: timing2,
24329
24428
  responseDisposition: failed ? "error" : "success",
24330
24429
  suppressedCount: this.suppressedSlowAudits
24331
24430
  });
@@ -28763,7 +28862,7 @@ import { randomBytes as randomBytes3 } from "node:crypto";
28763
28862
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
28764
28863
  function logicalDescriptor(descriptor) {
28765
28864
  if (descriptor.pathname !== "/api/v1/thread/open") return descriptor;
28766
- const { load_id: _loadId, range_resume_token: _rangeToken, ...query2 } = descriptor.query;
28865
+ const { load_id: _loadId, load_trace: _loadTrace, range_resume_token: _rangeToken, ...query2 } = descriptor.query;
28767
28866
  return { pathname: descriptor.pathname, query: query2 };
28768
28867
  }
28769
28868
  function fail15(code, message) {
@@ -31094,8 +31193,7 @@ function invalidPushServerOrigin() {
31094
31193
  function resolvePushServerOrigin(environment) {
31095
31194
  const value = environment.NIUMA_PUSH_SERVER_ORIGIN;
31096
31195
  if (value === void 0 || isBlankConfigValue(value)) {
31097
- const release2 = true;
31098
- return release2 ? DEFAULT_PUSH_PUBLIC_URL : resolveLocalServerOrigin(environment);
31196
+ return DEFAULT_PUSH_PUBLIC_URL;
31099
31197
  }
31100
31198
  if (URL_CONTROL_OR_SPACE_PATTERN.test(value)) {
31101
31199
  throw invalidPushServerOrigin();
@@ -31197,10 +31295,11 @@ var init_server_client = __esm({
31197
31295
 
31198
31296
  // apps/desktop/src/daemon/thread-load-trace.ts
31199
31297
  import { z as z8 } from "zod";
31200
- var ClientLoadPhaseSchema, occurredAtSchema, LoadSampleSchema, ThreadLoadReportSchema, LoadingTraceSchema, ThreadLoadReports;
31201
- var init_thread_load_trace = __esm({
31298
+ var ClientLoadPhaseSchema, occurredAtSchema, LoadSampleSchema, ThreadLoadReportSchema, ThreadLoadReportV2Schema, LoadingTraceSchema, ThreadLoadReports;
31299
+ var init_thread_load_trace2 = __esm({
31202
31300
  "apps/desktop/src/daemon/thread-load-trace.ts"() {
31203
31301
  "use strict";
31302
+ init_src2();
31204
31303
  ClientLoadPhaseSchema = z8.enum(["selected", "restored", "request_started", "snapshot_received", "history_ready", "content_presented"]);
31205
31304
  occurredAtSchema = z8.number().int().min(1).max(864e13).optional();
31206
31305
  LoadSampleSchema = z8.object({
@@ -31213,6 +31312,20 @@ var init_thread_load_trace = __esm({
31213
31312
  load_id: z8.string().uuid(),
31214
31313
  samples: z8.array(LoadSampleSchema).min(1).max(6)
31215
31314
  }).strict();
31315
+ ThreadLoadReportV2Schema = z8.object({
31316
+ version: z8.literal(2),
31317
+ thread_id: z8.string().min(1).max(256),
31318
+ load_id: z8.string().uuid(),
31319
+ client_class: z8.enum(["web", "ios", "other"]),
31320
+ samples: z8.array(LoadSampleSchema).max(6),
31321
+ terminal: z8.object({ kind: z8.enum(["failed", "cancelled", "superseded"]), phase: z8.enum(["request_started", "snapshot_received", "history_ready"]), reason: z8.enum(["open_rejected", "history_failed", "left_view", "user_cancelled", "stream_replaced"]), elapsed_ms: z8.number().int().min(0).max(9e5), occurred_at_ms: occurredAtSchema }).strict().optional()
31322
+ }).strict().superRefine((value, context) => {
31323
+ try {
31324
+ parseThreadLoadReportV2(value);
31325
+ } catch {
31326
+ context.addIssue({ code: "custom", message: "\u52A0\u8F7D\u91C7\u6837\u7EC4\u5408\u65E0\u6548" });
31327
+ }
31328
+ });
31216
31329
  LoadingTraceSchema = z8.object({
31217
31330
  load_id: z8.string().uuid(),
31218
31331
  lease_id: z8.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u),
@@ -31222,27 +31335,128 @@ var init_thread_load_trace = __esm({
31222
31335
  occurred_at_ms: occurredAtSchema
31223
31336
  }).strict().refine((value) => value.clock === "browser" ? ClientLoadPhaseSchema.safeParse(value.phase).success : ["open_received", "identity_ready", "lease_acquired", "shell_sent", "history_sent", "stream_closed"].includes(value.phase));
31224
31337
  ThreadLoadReports = class {
31225
- constructor(now = Date.now) {
31338
+ constructor(now = Date.now, audit = () => void 0) {
31226
31339
  this.now = now;
31340
+ this.audit = audit;
31227
31341
  }
31228
31342
  now;
31343
+ audit;
31229
31344
  entries = /* @__PURE__ */ new Map();
31230
- register(loadId, threadId2, owner2, record25) {
31345
+ used = /* @__PURE__ */ new Set();
31346
+ clear() {
31347
+ this.entries.clear();
31348
+ }
31349
+ begin(input) {
31350
+ for (const [key, value] of this.entries) if (value.expires < this.now()) this.entries.delete(key);
31351
+ if (!z8.string().uuid().safeParse(input.loadId).success || this.entries.size >= 256 || this.used.size >= 1e4 || this.used.has(input.loadId)) return null;
31352
+ const id5 = input.loadId;
31353
+ const writer = this.audit();
31354
+ if (writer?.reserveLoadId && !writer.reserveLoadId(id5)) {
31355
+ this.used.add(id5);
31356
+ return null;
31357
+ }
31358
+ this.used.add(id5);
31359
+ const epoch = writer?.loadGeneration?.();
31360
+ let outcome = { status: "loading", conflict: false };
31361
+ let lease, trace;
31362
+ const entry = { threadId: input.threadId, owner: input.owner, expires: input.nowMs + 9e5, seen: /* @__PURE__ */ new Set(), record: () => {
31363
+ }, valid: () => this.entries.get(id5) === entry && writer?.loadGeneration?.() === epoch && this.now() <= entry.expires };
31364
+ const write = (record25) => {
31365
+ if (!entry.valid?.()) return;
31366
+ try {
31367
+ writer?.recordLoad?.({ ...record25, load_id: id5, thread_id: input.threadId, occurred_at_ms: this.now(), ...lease ? { lease_id: lease } : {}, ...trace ? { trace_id: trace } : {} });
31368
+ } catch {
31369
+ }
31370
+ };
31371
+ const end = (event) => {
31372
+ outcome = reduceThreadLoadOutcome(outcome, event);
31373
+ entry.expires = Math.min(entry.expires, this.now() + 3e4);
31374
+ };
31375
+ const elapsed = () => Math.max(0, Math.min(9e5, this.now() - input.nowMs));
31376
+ const handle = {
31377
+ markDaemon: (phase, elapsedMs, occurredAtMs = this.now()) => {
31378
+ if (!entry.valid?.() || entry.seen.has("daemon:" + phase)) return;
31379
+ entry.seen.add("daemon:" + phase);
31380
+ write({ record_type: "load_sample", clock: "daemon", phase, elapsed_ms: elapsedMs, event_at_ms: occurredAtMs });
31381
+ },
31382
+ attachLease: (leaseId, traceId) => {
31383
+ lease = leaseId;
31384
+ trace = traceId;
31385
+ },
31386
+ // 每种确定终态只记一次;保留不同证据供 reducer 标示冲突,不延长授权期限。
31387
+ finish: (kind, evidence = {}) => {
31388
+ if (!entry.valid?.() || entry.seen.has("terminal:" + kind)) return;
31389
+ entry.seen.add("terminal:" + kind);
31390
+ write({ record_type: "load_terminal", clock: "daemon", phase: kind, elapsed_ms: elapsed(), ...evidence });
31391
+ end(kind);
31392
+ },
31393
+ close: () => {
31394
+ if (!entry.valid?.() || entry.seen.has("closed")) return;
31395
+ entry.seen.add("closed");
31396
+ write({ record_type: "load_close", clock: "daemon", phase: "closed", elapsed_ms: elapsed() });
31397
+ end("closed");
31398
+ },
31399
+ outcome: () => outcome
31400
+ };
31401
+ entry.handle = handle;
31402
+ entry.accept = (samples, client, terminal) => {
31403
+ for (const sample of samples) if (!entry.seen.has("client:" + sample.phase)) {
31404
+ entry.seen.add("client:" + sample.phase);
31405
+ write({ record_type: "load_sample", clock: "client", phase: sample.phase, elapsed_ms: sample.elapsed_ms, ...sample.occurred_at_ms ? { event_at_ms: sample.occurred_at_ms } : {}, ...client ? { client_class: client } : {} });
31406
+ if (sample.phase === "content_presented") end("presented");
31407
+ try {
31408
+ entry.record(sample);
31409
+ } catch {
31410
+ }
31411
+ }
31412
+ if (terminal && !entry.seen.has("terminal")) {
31413
+ entry.seen.add("terminal");
31414
+ write({ record_type: "load_terminal", clock: "client", phase: terminal.kind, terminal_phase: terminal.phase, reason: terminal.reason, elapsed_ms: terminal.elapsed_ms, ...terminal.occurred_at_ms ? { event_at_ms: terminal.occurred_at_ms } : {}, ...client ? { client_class: client } : {} });
31415
+ end(terminal.kind);
31416
+ }
31417
+ };
31418
+ this.entries.set(id5, entry);
31419
+ write({ record_type: "load_start", clock: "daemon", phase: "open_received", elapsed_ms: 0, source_transport: input.transport });
31420
+ return handle;
31421
+ }
31422
+ register(loadId, threadId2, owner2, record25, attempt) {
31231
31423
  for (const [key, value] of this.entries) if (value.expires < this.now()) this.entries.delete(key);
31232
- if (!z8.string().uuid().safeParse(loadId).success || this.entries.size >= 256 || this.entries.has(loadId)) return () => {
31424
+ const existing = this.entries.get(loadId);
31425
+ if (existing?.handle && existing.handle === attempt && existing.threadId === threadId2 && existing.owner === owner2) {
31426
+ existing.record = record25;
31427
+ return () => {
31428
+ };
31429
+ }
31430
+ if (!z8.string().uuid().safeParse(loadId).success || this.entries.size >= 256 || this.used.size >= 1e4 || this.entries.has(loadId) || this.used.has(loadId)) return () => {
31233
31431
  };
31234
- const entry = { threadId: threadId2, owner: owner2, expires: this.now() + 9e5, record: record25, seen: /* @__PURE__ */ new Set() };
31432
+ const writer = this.audit(), epoch = writer?.loadGeneration?.();
31433
+ if (writer?.reserveLoadId && !writer.reserveLoadId(loadId)) {
31434
+ this.used.add(loadId);
31435
+ return () => {
31436
+ };
31437
+ }
31438
+ this.used.add(loadId);
31439
+ const entry = { threadId: threadId2, owner: owner2, expires: this.now() + 9e5, record: record25, seen: /* @__PURE__ */ new Set(), valid: () => this.entries.get(loadId) === entry && writer?.loadGeneration?.() === epoch && this.now() <= entry.expires };
31235
31440
  this.entries.set(loadId, entry);
31236
31441
  return () => {
31237
31442
  if (this.entries.get(loadId) === entry) this.entries.delete(loadId);
31238
31443
  };
31239
31444
  }
31240
31445
  report(input, owner2) {
31241
- const parsed = ThreadLoadReportSchema.safeParse(input);
31446
+ let parsed;
31447
+ try {
31448
+ parsed = input?.version === 2 ? { success: true, data: parseThreadLoadReportV2(input) } : ThreadLoadReportSchema.safeParse(input);
31449
+ } catch {
31450
+ parsed = { success: false };
31451
+ }
31242
31452
  if (!parsed.success) return { accepted: false };
31243
31453
  const { load_id, thread_id, samples } = parsed.data;
31244
31454
  const entry = this.entries.get(load_id);
31245
- if (!entry || entry.threadId !== thread_id || entry.owner !== owner2 || entry.expires < this.now()) return { accepted: false };
31455
+ if (!entry || entry.threadId !== thread_id || entry.owner !== owner2 || entry.expires < this.now() || entry.valid && !entry.valid()) return { accepted: false };
31456
+ if (entry.accept) {
31457
+ entry.accept(samples, parsed.data.client_class, parsed.data.terminal);
31458
+ return { accepted: true };
31459
+ }
31246
31460
  for (const sample of samples) if (!entry.seen.has(sample.phase)) {
31247
31461
  entry.seen.add(sample.phase);
31248
31462
  try {
@@ -31262,7 +31476,7 @@ var nonEmptyString5, booleanQuery, authorizationMode, canonicalBase64, boundedIn
31262
31476
  var init_contracts2 = __esm({
31263
31477
  "apps/desktop/src/http/contracts.ts"() {
31264
31478
  "use strict";
31265
- init_thread_load_trace();
31479
+ init_thread_load_trace2();
31266
31480
  init_src2();
31267
31481
  nonEmptyString5 = z9.string().trim().min(1);
31268
31482
  booleanQuery = z9.enum(["true", "false"]).transform((value) => value === "true");
@@ -31477,6 +31691,8 @@ var init_contracts2 = __esm({
31477
31691
  range_resume_version: z9.literal("1").transform(() => 1).optional(),
31478
31692
  range_resume_token: z9.string().regex(/^[A-Za-z0-9_-]{43}$/).optional(),
31479
31693
  load_id: z9.string().uuid().optional(),
31694
+ // 恢复关联仍保留,只有首屏采样可显式关闭。
31695
+ load_trace: z9.literal("false").transform(() => false).optional(),
31480
31696
  include_execution: z9.literal("true").transform(() => true).optional(),
31481
31697
  include_message_times: z9.literal("true").transform(() => true).optional(),
31482
31698
  include_async_questions: z9.literal("true").transform(() => true).optional(),
@@ -32522,6 +32738,7 @@ function createThreadOpenSseProjection(rawQuery, options = {}) {
32522
32738
  ...query2.range_resume_version ? { rangeResumeVersion: 1 } : {},
32523
32739
  ...query2.range_resume_token ? { rangeResumeToken: query2.range_resume_token } : {},
32524
32740
  ...query2.load_id ? { loadId: query2.load_id } : {},
32741
+ ...query2.load_trace === false ? { loadTrace: false } : {},
32525
32742
  viewVersion: THREAD_VIEW_VERSION,
32526
32743
  ...query2.include_async_questions ? { includeAsyncQuestions: true } : {},
32527
32744
  ...query2.include_read_receipts ? { includeReadReceipts: true } : {},
@@ -33556,7 +33773,7 @@ async function dispatchRestRoute(request2, dependencies) {
33556
33773
  }
33557
33774
  }
33558
33775
  if (route === "POST /api/v1/thread/load/report") {
33559
- const body = ThreadLoadReportSchema.parse(request2.body);
33776
+ const body = request2.body?.version === 2 ? ThreadLoadReportV2Schema.parse(request2.body) : ThreadLoadReportSchema.parse(request2.body);
33560
33777
  const result = await daemonClient.request("thread/load/report", body, {
33561
33778
  ...daemonRequestOptions(5e3, request2.signal),
33562
33779
  ...request2.authenticatedMessageSubmissionScope !== void 0 ? { authenticatedMessageSubmissionScope: request2.authenticatedMessageSubmissionScope } : {}
@@ -44262,8 +44479,8 @@ function createRelayAttachmentDataLink(options) {
44262
44479
  const child = { permit, lease: null, ws: null, receiver: null, closed: false, paired: false, ready: false, opening: null, cleanup: null, unregister: null };
44263
44480
  children.set(permit.transfer_id, child);
44264
44481
  const registrationStarted = Date.now();
44265
- const timing = () => ({ attach_remaining_ms: permit.attach_expires_at - Date.now(), transfer_remaining_ms: permit.expires_at - Date.now(), registration_elapsed_ms: Date.now() - registrationStarted });
44266
- recordAttachmentDiagnostic(options.diagnostic, { phase: "file.register", reason: "started", ...timing() });
44482
+ const timing2 = () => ({ attach_remaining_ms: permit.attach_expires_at - Date.now(), transfer_remaining_ms: permit.expires_at - Date.now(), registration_elapsed_ms: Date.now() - registrationStarted });
44483
+ recordAttachmentDiagnostic(options.diagnostic, { phase: "file.register", reason: "started", ...timing2() });
44267
44484
  try {
44268
44485
  const reply = await options.control.requestFileControl("file.register", {
44269
44486
  version: 1,
@@ -44275,7 +44492,7 @@ function createRelayAttachmentDataLink(options) {
44275
44492
  max_wire_bytes: attachmentTransferWireBudget(permit.context.byte_size)
44276
44493
  });
44277
44494
  if (reply.code !== 0) {
44278
- recordAttachmentDiagnostic(options.diagnostic, { phase: "file.register", reason: "failed", code: reply.code, ...timing() });
44495
+ recordAttachmentDiagnostic(options.diagnostic, { phase: "file.register", reason: "failed", code: reply.code, ...timing2() });
44279
44496
  const error2 = unavailable7();
44280
44497
  error2.cause = { stage: "file.register", relayCode: reply.code };
44281
44498
  throw error2;
@@ -48026,7 +48243,7 @@ var init_desktop_ingress = __esm({
48026
48243
  resetMethodValidity() {
48027
48244
  this.ipcMethodInvalidatedThreads.clear();
48028
48245
  }
48029
- async process(frame, ownClientId = this.ports.ipc()?.getClientId?.() ?? null, receivedIpcGeneration = this.ports.ipcConnectionGeneration(), timing, readDiagnostic) {
48246
+ async process(frame, ownClientId = this.ports.ipc()?.getClientId?.() ?? null, receivedIpcGeneration = this.ports.ipcConnectionGeneration(), timing2, readDiagnostic) {
48030
48247
  this.ports.writeDebug({
48031
48248
  component: "daemon",
48032
48249
  channel: "ipc",
@@ -48036,7 +48253,7 @@ var init_desktop_ingress = __esm({
48036
48253
  attributes: {
48037
48254
  type: frame.type,
48038
48255
  ...typeof frame.method === "string" ? { method: frame.method } : {},
48039
- ...timing
48256
+ ...timing2
48040
48257
  }
48041
48258
  });
48042
48259
  this.ports.writeRawDebug({
@@ -48068,6 +48285,9 @@ var init_desktop_ingress = __esm({
48068
48285
  }
48069
48286
  const status2 = objectRecord(frame.params);
48070
48287
  const methodThreadId = extractThreadIdFromIpcFrame(frame);
48288
+ if (!this.ports.stopping() && receivedIpcGeneration === this.ports.ipcConnectionGeneration() && this.ports.desktopIpcState() === "ready" && ownClientId && frame.type === "broadcast" && frame.sourceClientId && frame.sourceClientId !== ownClientId && status2.hostId === "local" && methodThreadId && (frame.method === "thread-stream-following-status-requested" || frame.method === "thread-stream-following-changed" && status2.following === true)) {
48289
+ this.ports.discoverThread(methodThreadId);
48290
+ }
48071
48291
  if (frame.method === "thread-stream-state-changed" && methodThreadId && this.ipcMethodInvalidatedThreads.has(methodThreadId) && objectRecord(status2.change).type !== "snapshot") {
48072
48292
  if (readDiagnostic) readDiagnostic.reason = "baseline_required";
48073
48293
  return;
@@ -48770,19 +48990,19 @@ var init_socket_client = __esm({
48770
48990
  this.events.on("requestDiagnostic", listener);
48771
48991
  return () => this.events.off("requestDiagnostic", listener);
48772
48992
  }
48773
- emitRequestDiagnostic(timing, phase, outcome) {
48993
+ emitRequestDiagnostic(timing2, phase, outcome) {
48774
48994
  this.events.emit("requestDiagnostic", {
48775
- requestId: timing.requestId,
48776
- method: timing.method,
48777
- threadId: timing.threadId,
48995
+ requestId: timing2.requestId,
48996
+ method: timing2.method,
48997
+ threadId: timing2.threadId,
48778
48998
  phase,
48779
48999
  outcome,
48780
- durationMs: Math.max(0, Math.floor(performance.now() - timing.startedAt))
49000
+ durationMs: Math.max(0, Math.floor(performance.now() - timing2.startedAt))
48781
49001
  });
48782
49002
  }
48783
49003
  pruneTimedOutRequests() {
48784
- for (const [id5, timing] of this.timedOutRequests) {
48785
- if (performance.now() - timing.startedAt > 12e4) this.timedOutRequests.delete(id5);
49004
+ for (const [id5, timing2] of this.timedOutRequests) {
49005
+ if (performance.now() - timing2.startedAt > 12e4) this.timedOutRequests.delete(id5);
48786
49006
  }
48787
49007
  while (this.timedOutRequests.size > 128) this.timedOutRequests.delete(this.timedOutRequests.keys().next().value);
48788
49008
  }
@@ -48919,20 +49139,20 @@ var init_socket_client = __esm({
48919
49139
  this.pruneTimedOutRequests();
48920
49140
  const localParams = params && typeof params === "object" ? params : {};
48921
49141
  const threadId2 = typeof localParams.threadId === "string" ? localParams.threadId : typeof localParams.conversationId === "string" ? localParams.conversationId : void 0;
48922
- const timing = { requestId, method: method2, threadId: threadId2, startedAt: performance.now() };
49142
+ const timing2 = { requestId, method: method2, threadId: threadId2, startedAt: performance.now() };
48923
49143
  const responsePromise = new Promise((resolve5, reject3) => {
48924
49144
  const timer = setTimeout(() => {
48925
49145
  this.pending.delete(requestId);
48926
- this.timedOutRequests.set(requestId, timing);
49146
+ this.timedOutRequests.set(requestId, timing2);
48927
49147
  this.pruneTimedOutRequests();
48928
- this.emitRequestDiagnostic(timing, "timeout", "unknown");
49148
+ this.emitRequestDiagnostic(timing2, "timeout", "unknown");
48929
49149
  reject3(new NiumaError("UNKNOWN_RESULT", `IPC \u8BF7\u6C42\u8D85\u65F6: ${method2}`, { method: method2 }));
48930
49150
  }, options.timeoutMs ?? this.requestTimeoutMs);
48931
- this.pending.set(requestId, { timer, resolve: resolve5, reject: reject3, timing });
49151
+ this.pending.set(requestId, { timer, resolve: resolve5, reject: reject3, timing: timing2 });
48932
49152
  });
48933
49153
  try {
48934
49154
  await this.write(frame);
48935
- this.emitRequestDiagnostic(timing, "sent", "pending");
49155
+ this.emitRequestDiagnostic(timing2, "sent", "pending");
48936
49156
  } catch (error2) {
48937
49157
  this.clearPending(requestId);
48938
49158
  throw error2 instanceof Error ? new NiumaError("IPC_NOT_INITIALIZED", error2.message) : new NiumaError("IPC_NOT_INITIALIZED", String(error2));
@@ -49031,10 +49251,10 @@ var init_socket_client = __esm({
49031
49251
  this.pruneTimedOutRequests();
49032
49252
  const outcome = frame.resultType === "error" ? "error" : "success";
49033
49253
  if (!pending) {
49034
- const timing = this.timedOutRequests.get(requestId);
49035
- if (timing) {
49254
+ const timing2 = this.timedOutRequests.get(requestId);
49255
+ if (timing2) {
49036
49256
  this.timedOutRequests.delete(requestId);
49037
- this.emitRequestDiagnostic(timing, "late_response", outcome);
49257
+ this.emitRequestDiagnostic(timing2, "late_response", outcome);
49038
49258
  }
49039
49259
  return;
49040
49260
  }
@@ -59545,15 +59765,46 @@ function createHistoryHandlers(ports) {
59545
59765
  if (negotiated && (!handoffScope || !scope || scope.bindingId !== handoffScope.bindingId)) {
59546
59766
  throw new HttpApiError(200, 200004, "\u6B63\u6587\u4EA4\u63A5\u9700\u8981\u8BA4\u8BC1\u4F1A\u8BDD");
59547
59767
  }
59768
+ const loadStartedAt = performance.now();
59769
+ const loadAttempt = params.loadTrace === false ? null : ports.threadLoadReports().begin?.({ loadId: params.loadId, threadId: threadId2, owner: scope ? `binding:${scope.bindingId.toLowerCase()}` : "local", transport: scope?.transport ?? "local", nowMs: Date.now() });
59770
+ const loadElapsed = () => Math.max(0, Math.min(9e5, Math.round(performance.now() - loadStartedAt)));
59771
+ let loadFailurePhase = "open_received";
59772
+ const failOpen = (error2) => {
59773
+ if (!options.signal?.aborted && normalizeHttpError(error2).httpStatus === 200) loadAttempt?.finish("failed", { terminal_phase: loadFailurePhase, reason: "open_rejected" });
59774
+ };
59548
59775
  if (negotiated && typeof params.handoffTicket === "string") {
59549
- if (options.signal?.aborted || socket.destroyed || socket.writableEnded) return STREAMING_RESPONSE;
59550
- const consumer = ports.connectionHandoff().claim(
59551
- handoffScope,
59552
- params.handoffDescriptor,
59553
- params.handoffTicket,
59554
- { emit: (frame) => socket.write(`${JSON.stringify(successEnvelope(frame))}
59555
- `), onClose: () => socket.end() }
59556
- );
59776
+ if (options.signal?.aborted || socket.destroyed || socket.writableEnded) {
59777
+ loadAttempt?.close();
59778
+ return STREAMING_RESPONSE;
59779
+ }
59780
+ let consumer;
59781
+ try {
59782
+ consumer = ports.connectionHandoff().claim(
59783
+ handoffScope,
59784
+ params.handoffDescriptor,
59785
+ params.handoffTicket,
59786
+ { emit: (frame) => {
59787
+ socket.write(`${JSON.stringify(successEnvelope(frame))}
59788
+ `);
59789
+ if (frame.type === "thread_view_snapshot") {
59790
+ loadFailurePhase = "shell_sent";
59791
+ loadAttempt?.markDaemon("shell_sent", loadElapsed());
59792
+ }
59793
+ const view = frame.data;
59794
+ if (view?.availability?.state === "ready") {
59795
+ loadFailurePhase = "history_sent";
59796
+ loadAttempt?.markDaemon("history_sent", loadElapsed());
59797
+ }
59798
+ }, onClose: () => {
59799
+ loadAttempt?.close();
59800
+ socket.end();
59801
+ } }
59802
+ );
59803
+ } catch (error2) {
59804
+ failOpen(error2);
59805
+ loadAttempt?.close();
59806
+ throw error2;
59807
+ }
59557
59808
  let recoveryClosed = false;
59558
59809
  let unregisterRecovery2 = () => {
59559
59810
  };
@@ -59566,12 +59817,14 @@ function createHistoryHandlers(ports) {
59566
59817
  }) : () => {
59567
59818
  };
59568
59819
  } catch (error2) {
59820
+ loadAttempt?.close();
59569
59821
  consumer.close();
59570
59822
  throw error2;
59571
59823
  }
59572
59824
  let unregisterArchive2 = () => {
59573
59825
  };
59574
59826
  const close2 = () => {
59827
+ loadAttempt?.close();
59575
59828
  unregisterArchive2();
59576
59829
  recoveryClosed = true;
59577
59830
  unregisterRecovery2();
@@ -59598,7 +59851,6 @@ function createHistoryHandlers(ports) {
59598
59851
  `);
59599
59852
  return STREAMING_RESPONSE;
59600
59853
  }
59601
- const loadStartedAt = performance.now();
59602
59854
  let loadTrace = null;
59603
59855
  let loadLeaseId = null;
59604
59856
  let unregisterLoad = () => {
@@ -59608,7 +59860,9 @@ function createHistoryHandlers(ports) {
59608
59860
  const pendingLoad = [];
59609
59861
  const loadSeen = /* @__PURE__ */ new Set();
59610
59862
  const recordLoad = (phase, clock = "daemon", elapsed = Math.round(performance.now() - loadStartedAt), occurredAt4 = clock === "daemon" ? Date.now() : void 0) => {
59611
- if (typeof params.loadId !== "string" || elapsed < 0 || elapsed > 9e5) return;
59863
+ if (clock === "daemon" && ["open_received", "identity_ready", "lease_acquired", "shell_sent", "history_sent"].includes(phase)) loadFailurePhase = phase;
59864
+ if (params.loadTrace === false || typeof params.loadId !== "string" || elapsed < 0 || elapsed > 9e5) return;
59865
+ if (clock === "daemon") loadAttempt?.markDaemon(phase, elapsed, occurredAt4);
59612
59866
  if (!loadLeaseId) {
59613
59867
  if (pendingLoad.length < 8) pendingLoad.push({ phase, elapsed, occurredAt: occurredAt4 });
59614
59868
  return;
@@ -59697,6 +59951,7 @@ function createHistoryHandlers(ports) {
59697
59951
  readReceiptTimer = null;
59698
59952
  }
59699
59953
  recordLoad("stream_closed");
59954
+ loadAttempt?.close();
59700
59955
  unregisterLoad();
59701
59956
  unregisterRecovery();
59702
59957
  closed = true;
@@ -59733,6 +59988,7 @@ function createHistoryHandlers(ports) {
59733
59988
  const close = () => {
59734
59989
  if (physicalClosed) return;
59735
59990
  physicalClosed = true;
59991
+ loadAttempt?.close();
59736
59992
  unregisterRecovery();
59737
59993
  options.signal?.removeEventListener("abort", close);
59738
59994
  publisher?.close();
@@ -59749,7 +60005,14 @@ function createHistoryHandlers(ports) {
59749
60005
  return STREAMING_RESPONSE;
59750
60006
  }
59751
60007
  if (!ports.store().getThread(threadId2, { tailCount: 0 })) {
59752
- await ports.verifyThreadForOpen(threadId2);
60008
+ try {
60009
+ await ports.verifyThreadForOpen(threadId2);
60010
+ } catch (error2) {
60011
+ failOpen(error2);
60012
+ loadAttempt?.close();
60013
+ detachClose();
60014
+ throw error2;
60015
+ }
59753
60016
  }
59754
60017
  if (closed || socketIsClosed()) {
59755
60018
  closed = true;
@@ -59798,13 +60061,15 @@ function createHistoryHandlers(ports) {
59798
60061
  }
59799
60062
  for (const sample of pendingLoad) recordLoad(sample.phase, "daemon", sample.elapsed, sample.occurredAt);
59800
60063
  pendingLoad.length = 0;
60064
+ if (loadLeaseId && loadTrace) loadAttempt?.attachLease(loadLeaseId, loadTrace.traceId);
59801
60065
  recordLoad("lease_acquired");
59802
60066
  try {
59803
- if (loadLeaseId && loadTrace) unregisterLoad = ports.threadLoadReports().register(
60067
+ if (loadAttempt && loadLeaseId && loadTrace) unregisterLoad = ports.threadLoadReports().register(
59804
60068
  params.loadId,
59805
60069
  threadId2,
59806
60070
  scope ? `binding:${scope.bindingId.toLowerCase()}` : "local",
59807
- (sample) => recordLoad(sample.phase, "browser", sample.elapsed_ms, sample.occurred_at_ms)
60071
+ (sample) => recordLoad(sample.phase, "browser", sample.elapsed_ms, sample.occurred_at_ms),
60072
+ loadAttempt
59808
60073
  );
59809
60074
  } catch {
59810
60075
  }
@@ -59918,6 +60183,7 @@ function createHistoryHandlers(ports) {
59918
60183
  ports.requestThreadAuthorityRecovery(threadId2, true);
59919
60184
  ports.history().onHistoryLeaseStateChanged(threadId2, ports.activeThreadFollowerCoordinator()?.leaseKindsFor(threadId2) ?? []);
59920
60185
  unsubscribeHistoryWindow = historyWindow.subscribe(() => {
60186
+ if (historyWindow?.initialWindowState === "failed") loadAttempt?.finish("failed", { terminal_phase: "shell_sent", reason: "history_failed" });
59921
60187
  if (rangeClaim && historyWindow?.initialWindowState === "failed") {
59922
60188
  socket.write(`${JSON.stringify(errorEnvelope(new NiumaError("HISTORY_SNAPSHOT_EXPIRED", "\u5386\u53F2\u6062\u590D\u8303\u56F4\u4E0D\u53EF\u7528\u6216\u5DF2\u8FC7\u671F")))}
59923
60189
  `);
@@ -59992,6 +60258,7 @@ function createHistoryHandlers(ports) {
59992
60258
  `);
59993
60259
  }
59994
60260
  } catch (error2) {
60261
+ failOpen(error2);
59995
60262
  try {
59996
60263
  const sites = [
59997
60264
  ["historySourceAssertion", "Object.sourceCurrent"],
@@ -60073,20 +60340,20 @@ function text10(value) {
60073
60340
  return false;
60074
60341
  }
60075
60342
  }
60076
- function object9(value, keys3) {
60343
+ function object10(value, keys3) {
60077
60344
  if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return fail16("invalid");
60078
60345
  const own = Reflect.ownKeys(value);
60079
60346
  if (own.length !== keys3.length || !own.every((key) => typeof key === "string" && keys3.includes(key) && "value" in Object.getOwnPropertyDescriptor(value, key))) return fail16("invalid");
60080
60347
  return value;
60081
60348
  }
60082
60349
  function parseAsyncQuestionBatch(value) {
60083
- const raw = object9(value, ["thread_id", "group_id", "revision", "idempotency_key", "question_ids", "answers"]);
60350
+ const raw = object10(value, ["thread_id", "group_id", "revision", "idempotency_key", "question_ids", "answers"]);
60084
60351
  if (!id3(raw.thread_id) || !id3(raw.group_id) || !id3(raw.idempotency_key) || !Number.isSafeInteger(raw.revision) || raw.revision < 0 || !Array.isArray(raw.question_ids) || !raw.question_ids.length || raw.question_ids.length > 256 || !Array.isArray(raw.answers) || raw.answers.length > raw.question_ids.length) return fail16("invalid");
60085
60352
  const questionIds = Array.from(raw.question_ids);
60086
60353
  if (!questionIds.every(id3) || new Set(questionIds).size !== questionIds.length) return fail16("invalid");
60087
60354
  const seen = /* @__PURE__ */ new Set();
60088
60355
  const answers = Array.from(raw.answers, (value2) => {
60089
- const answer = object9(value2, ["question_id", "title", "answer"]);
60356
+ const answer = object10(value2, ["question_id", "title", "answer"]);
60090
60357
  if (!id3(answer.question_id) || !questionIds.includes(answer.question_id) || seen.has(answer.question_id) || !text10(answer.title) || !text10(answer.answer)) return fail16("invalid");
60091
60358
  seen.add(answer.question_id);
60092
60359
  return { question_id: answer.question_id, title: answer.title, answer: answer.answer };
@@ -62997,10 +63264,10 @@ var init_thread_history_window_loader = __esm({
62997
63264
  sourceRevision: 0
62998
63265
  };
62999
63266
  for (let attempt = 0; attempt < 2; attempt++) {
63000
- const timing = await this.timestampVersion(turn, signal);
63267
+ const timing2 = await this.timestampVersion(turn, signal);
63001
63268
  this.assertSource(epoch);
63002
63269
  signal.throwIfAborted();
63003
- const versionKey = JSON.stringify([epoch, description.contentVersion, timing]);
63270
+ const versionKey = JSON.stringify([epoch, description.contentVersion, timing2]);
63004
63271
  let cached = this.turns.get(identity6);
63005
63272
  if (cached?.current?.version === versionKey) {
63006
63273
  if (description.sourceRevision >= cached.sourceRevision && cached.nativeOrder !== description.nativeOrder) {
@@ -63017,7 +63284,7 @@ var init_thread_history_window_loader = __esm({
63017
63284
  this.assertSource(epoch);
63018
63285
  signal.throwIfAborted();
63019
63286
  if (enriched.turnId !== turn.id) throw new RangeError("\u7A97\u53E3\u65F6\u95F4\u8865\u9F50 Turn \u8EAB\u4EFD\u4E0D\u5339\u914D");
63020
- if (await this.timestampVersion(turn, signal) !== timing) continue;
63287
+ if (await this.timestampVersion(turn, signal) !== timing2) continue;
63021
63288
  this.assertSource(epoch);
63022
63289
  signal.throwIfAborted();
63023
63290
  const freeze = (value) => {
@@ -63075,11 +63342,11 @@ var init_thread_history_window_loader = __esm({
63075
63342
  const epoch = this.sourceEpoch;
63076
63343
  this.currentProtection.addTurnIds([turn.id]);
63077
63344
  const description = this.reader.describeTurn?.(turn) ?? { contentVersion: createHash26("sha256").update(JSON.stringify(turn)).digest("hex") };
63078
- const timing = await this.timestampVersion(turn, signal);
63345
+ const timing2 = await this.timestampVersion(turn, signal);
63079
63346
  this.assertSource(epoch);
63080
63347
  signal.throwIfAborted();
63081
63348
  const cached = this.turns.get(JSON.stringify([this.currentThreadId, turn.id]));
63082
- if (cached?.current?.version === JSON.stringify([epoch, description.contentVersion, timing])) {
63349
+ if (cached?.current?.version === JSON.stringify([epoch, description.contentVersion, timing2])) {
63083
63350
  this.holdVersion(this.currentProtection, cached.current);
63084
63351
  return cached.current.projection.displayItemCount;
63085
63352
  }
@@ -64340,17 +64607,17 @@ var init_app_server_thread_view_adapter = __esm({
64340
64607
  lastError: null
64341
64608
  }).status;
64342
64609
  const terminalStatus = failed || aborted || status2 === "completed" ? lifecycleStatus : "completed";
64343
- const timing = context.getTurnLifecycle?.(turnId);
64610
+ const timing2 = context.getTurnLifecycle?.(turnId);
64344
64611
  return [{
64345
64612
  type: "ThreadStatusChanged",
64346
64613
  thread_id: context.threadId,
64347
64614
  status: terminalStatus,
64348
64615
  turn_id: turnId,
64349
64616
  terminal_item_state: terminalStatus === "error" ? "failed" : "completed",
64350
- ...timing ? { process_lifecycle: {
64351
- startedAt: lifecycleTime(timing.startedAt),
64352
- endedAt: lifecycleTime(timing.endedAt),
64353
- outcome: timing.outcome
64617
+ ...timing2 ? { process_lifecycle: {
64618
+ startedAt: lifecycleTime(timing2.startedAt),
64619
+ endedAt: lifecycleTime(timing2.endedAt),
64620
+ outcome: timing2.outcome
64354
64621
  } } : {},
64355
64622
  last_error: terminalStatus === "error" ? {
64356
64623
  code: 900001,
@@ -64621,10 +64888,10 @@ var init_thread_history_window_projection = __esm({
64621
64888
  const enriched = this.options.enrichTimeline ? await this.options.enrichTimeline(sources.map((source2) => source2.timeline), startedAt) : this.timestamps.enrichTimelineFromReplay(sources.map((source2) => source2.timeline), startedAt, replay);
64622
64889
  const timingById = new Map(sources.map((source2, index) => [source2.id, enriched[index]]));
64623
64890
  const items = projected.items.map((item) => {
64624
- const timing = timingById.get(item.id);
64891
+ const timing2 = timingById.get(item.id);
64625
64892
  const fact = item.process_fact;
64626
- if (!timing || !fact) return item;
64627
- const merged = mergeThreadHistoryTimelineLifecycle(timing, {
64893
+ if (!timing2 || !fact) return item;
64894
+ const merged = mergeThreadHistoryTimelineLifecycle(timing2, {
64628
64895
  ...fact.startedAt !== null ? { turnStartedAt: fact.startedAt } : {},
64629
64896
  ...fact.endedAt !== null ? { turnEndedAt: fact.endedAt } : {},
64630
64897
  ...fact.durationMs != null ? { turnDurationMs: fact.durationMs } : {},
@@ -65445,7 +65712,7 @@ function readStateDiagnostic(before, after, candidate = after.confirmed) {
65445
65712
  sameCycle: candidate ? candidate.cycle === after.cycle : null
65446
65713
  };
65447
65714
  }
65448
- function object10(value) {
65715
+ function object11(value) {
65449
65716
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
65450
65717
  }
65451
65718
  function field(value, present, count) {
@@ -65455,15 +65722,15 @@ function field(value, present, count) {
65455
65722
  }
65456
65723
  function unreadFrameDiagnostic(frame) {
65457
65724
  if (frame.method !== "thread-stream-state-changed") return null;
65458
- const params = object10(frame.params), change = object10(params.change);
65725
+ const params = object11(frame.params), change = object11(params.change);
65459
65726
  const kind = change.type === "snapshot" ? "snapshot" : change.type === "patches" ? "patch" : "invalid";
65460
- const values = kind === "snapshot" ? object10(change.conversationState) : {};
65727
+ const values = kind === "snapshot" ? object11(change.conversationState) : {};
65461
65728
  let unread = field(values.hasUnreadTurn, Object.hasOwn(values, "hasUnreadTurn"), false);
65462
65729
  let count = field(values.unreadMessageCount, Object.hasOwn(values, "unreadMessageCount"), true);
65463
65730
  let countValue = values.unreadMessageCount;
65464
65731
  if (kind === "patch") {
65465
65732
  for (const value of Array.isArray(change.patches) ? change.patches : []) {
65466
- const patch = object10(value), path55 = Array.isArray(patch.path) ? patch.path : [];
65733
+ const patch = object11(value), path55 = Array.isArray(patch.path) ? patch.path : [];
65467
65734
  const name2 = path55.length === 1 ? path55[0] : path55.length === 2 && path55[0] === "conversationState" ? path55[1] : null;
65468
65735
  if (name2 !== "hasUnreadTurn" && name2 !== "unreadMessageCount") continue;
65469
65736
  const state = patch.op === "remove" ? "removed" : ["add", "replace"].includes(String(patch.op)) ? field(patch.value, Object.hasOwn(patch, "value"), name2 === "unreadMessageCount") : "invalid";
@@ -65577,10 +65844,10 @@ var init_ingress_dispatch = __esm({
65577
65844
  }
65578
65845
  ports;
65579
65846
  /** 单帧入站与最终结果成对记录;原始字段与业务结果分开,拒绝帧同样留证。 */
65580
- async handleIpcFrame(frame, ownClientId = this.ports.desktopSession().ipcValue?.getClientId?.() ?? null, receivedIpcGeneration = this.ports.desktopSession().ipcConnectionGenerationValue, timing) {
65847
+ async handleIpcFrame(frame, ownClientId = this.ports.desktopSession().ipcValue?.getClientId?.() ?? null, receivedIpcGeneration = this.ports.desktopSession().ipcConnectionGenerationValue, timing2) {
65581
65848
  const attributes = this.ports.options().debugLogger ? unreadFrameDiagnostic(frame) : null;
65582
65849
  const threadId2 = attributes ? extractThreadIdFromIpcFrame(frame) : null;
65583
- if (!attributes || !threadId2) return this.ports.desktopIngress().process(frame, ownClientId, receivedIpcGeneration, timing);
65850
+ if (!attributes || !threadId2) return this.ports.desktopIngress().process(frame, ownClientId, receivedIpcGeneration, timing2);
65584
65851
  const before = this.ports.catalog().storeValue.getThread(threadId2, { tailCount: 0 })?.unread;
65585
65852
  const diagnostic = { reason: "processed" };
65586
65853
  const metadata = { ...attributes, sequence: this.ports.diagnostics().threadReadDiagnosticSequenceValue++, generation: receivedIpcGeneration, currentGeneration: this.ports.desktopSession().ipcConnectionGenerationValue };
@@ -65593,7 +65860,7 @@ var init_ingress_dispatch = __esm({
65593
65860
  }
65594
65861
  });
65595
65862
  try {
65596
- await this.ports.desktopIngress().process(frame, ownClientId, receivedIpcGeneration, timing, diagnostic);
65863
+ await this.ports.desktopIngress().process(frame, ownClientId, receivedIpcGeneration, timing2, diagnostic);
65597
65864
  } catch (error2) {
65598
65865
  diagnostic.reason = "processing_failed";
65599
65866
  throw error2;
@@ -67405,11 +67672,11 @@ var init_projection_context = __esm({
67405
67672
  } : null),
67406
67673
  enrichTimelineItemTiming: (item) => this.ports.timelinePublication().enrichTimelineItemTiming(item, timelineItems),
67407
67674
  getTurnLifecycle: (turnId) => {
67408
- const timing = this.ports.timelinePublication().turnTiming(threadId2, turnId);
67409
- return timing ? {
67410
- startedAt: timing.startedAt,
67411
- endedAt: timing.endedAt ?? null,
67412
- outcome: timing.outcome ?? null
67675
+ const timing2 = this.ports.timelinePublication().turnTiming(threadId2, turnId);
67676
+ return timing2 ? {
67677
+ startedAt: timing2.startedAt,
67678
+ endedAt: timing2.endedAt ?? null,
67679
+ outcome: timing2.outcome ?? null
67413
67680
  } : null;
67414
67681
  },
67415
67682
  resolveKnownLocalImage: (sourcePath, itemId, partIndex) => this.ports.resolveThreadViewImage(threadId2, sourcePath, () => this.ports.isCurrentAppServerAsyncFence(threadId2, fence), itemId, partIndex)
@@ -68027,7 +68294,7 @@ var init_active_thread_follower_coordinator = __esm({
68027
68294
  init_errors();
68028
68295
  init_session_history_file();
68029
68296
  init_error_log();
68030
- ACTIVE_THREAD_SCAN_INTERVAL_MS = 1e4;
68297
+ ACTIVE_THREAD_SCAN_INTERVAL_MS = 6e4;
68031
68298
  ACTIVE_THREAD_READ_CONCURRENCY = 4;
68032
68299
  ACTIVE_FOLLOWER_SNAPSHOT_TIMEOUT_MS = 12e3;
68033
68300
  ACTIVE_FOLLOWER_RETRY_DELAYS_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
@@ -75120,7 +75387,7 @@ var init_thread_state_coordination = __esm({
75120
75387
  return token3;
75121
75388
  }
75122
75389
  /** 仅供已核验的同轮终态使用;同时收敛列表与详情,不伪造新的完成通知。 */
75123
- applyRecoveredTerminal(threadId2, turnId, outcome, occurredAt4, message, timing) {
75390
+ applyRecoveredTerminal(threadId2, turnId, outcome, occurredAt4, message, timing2) {
75124
75391
  const previous = this.ports.catalog().storeValue.getThread(threadId2, { tailCount: 0 });
75125
75392
  const mutation = this.ports.catalog().storeValue.applyActivitySignal({
75126
75393
  type: "snapshot",
@@ -75140,7 +75407,7 @@ var init_thread_state_coordination = __esm({
75140
75407
  turn_id: turnId,
75141
75408
  status: outcome === "aborted" ? "idle" : outcome === "failed" ? "error" : "completed",
75142
75409
  terminal_item_state: outcome === "failed" ? "failed" : "completed",
75143
- process_lifecycle: { startedAt: timing?.startedAt ?? null, endedAt: timing?.endedAt ?? null, outcome },
75410
+ process_lifecycle: { startedAt: timing2?.startedAt ?? null, endedAt: timing2?.endedAt ?? null, outcome },
75144
75411
  last_error: outcome === "failed" && message ? { code: 900001, message, occurred_at: occurredAt4, turn_id: turnId } : null
75145
75412
  },
75146
75413
  { type: "ThreadActivityChanged", thread_id: threadId2, turn_id: turnId, activity: null },
@@ -75400,15 +75667,15 @@ var init_timeline_publication = __esm({
75400
75667
  if (!item.turnId) {
75401
75668
  return item;
75402
75669
  }
75403
- const timing = this.liveTurnTimings.get(this.turnTimingKey(item.threadId, item.turnId));
75404
- if (!timing) {
75670
+ const timing2 = this.liveTurnTimings.get(this.turnTimingKey(item.threadId, item.turnId));
75671
+ if (!timing2) {
75405
75672
  return item;
75406
75673
  }
75407
75674
  return {
75408
75675
  ...item,
75409
- ...item.turnStartedAt === void 0 ? { turnStartedAt: timing.startedAt } : {},
75410
- ...item.turnEndedAt === void 0 && timing.endedAt !== void 0 ? { turnEndedAt: timing.endedAt } : {},
75411
- ...item.turnOutcome === void 0 && timing.outcome !== void 0 ? { turnOutcome: timing.outcome } : {}
75676
+ ...item.turnStartedAt === void 0 ? { turnStartedAt: timing2.startedAt } : {},
75677
+ ...item.turnEndedAt === void 0 && timing2.endedAt !== void 0 ? { turnEndedAt: timing2.endedAt } : {},
75678
+ ...item.turnOutcome === void 0 && timing2.outcome !== void 0 ? { turnOutcome: timing2.outcome } : {}
75412
75679
  };
75413
75680
  }
75414
75681
  enrichTimelineItemTiming(item, historyItems = []) {
@@ -75428,9 +75695,9 @@ var init_timeline_publication = __esm({
75428
75695
  turnTimingKey(threadId2, turnId) {
75429
75696
  return `${threadId2}:${turnId}`;
75430
75697
  }
75431
- setLiveTurnTiming(key, timing) {
75698
+ setLiveTurnTiming(key, timing2) {
75432
75699
  this.liveTurnTimings.delete(key);
75433
- this.liveTurnTimings.set(key, timing);
75700
+ this.liveTurnTimings.set(key, timing2);
75434
75701
  while (this.liveTurnTimings.size > LIVE_TURN_TIMING_LIMIT) {
75435
75702
  const oldestKey = this.liveTurnTimings.keys().next().value;
75436
75703
  if (typeof oldestKey !== "string") break;
@@ -83912,8 +84179,7 @@ var init_native_admission = __esm({
83912
84179
  else if (kind === "ordinary") this.hidden.delete(thread4.id);
83913
84180
  const old = this.entries.get(thread4.id);
83914
84181
  this.failures.delete(thread4.id);
83915
- if (old && old.kind === kind) return old.token;
83916
- const token3 = { threadId: thread4.id, generation: ++this.sequence, executionGeneration };
84182
+ const token3 = old && old.kind === kind ? old.token : { threadId: thread4.id, generation: ++this.sequence, executionGeneration };
83917
84183
  this.entries.set(thread4.id, { token: token3, kind });
83918
84184
  try {
83919
84185
  if (kind !== previous) this.ports.onChange?.(thread4.id, previous, kind);
@@ -83923,7 +84189,7 @@ var init_native_admission = __esm({
83923
84189
  }
83924
84190
  const pending = this.jobs.get(thread4.id);
83925
84191
  if (pending) {
83926
- this.finishWait(pending, kind);
84192
+ this.finishWait(pending, kind, thread4, token3);
83927
84193
  if (!pending.started) {
83928
84194
  this.removeQueued(pending);
83929
84195
  this.removeJob(pending);
@@ -83931,13 +84197,20 @@ var init_native_admission = __esm({
83931
84197
  }
83932
84198
  return token3;
83933
84199
  }
83934
- ensure(id5, executionGeneration) {
83935
- if (executionGeneration !== this.execution) return Promise.resolve("unknown");
84200
+ async ensure(id5, executionGeneration) {
84201
+ return (await this.query(id5, executionGeneration, false)).kind;
84202
+ }
84203
+ /** 目录缺项时请求摘要,与未知准入共用单飞、退避和物理槽。 */
84204
+ ensureSummary(id5, executionGeneration) {
84205
+ return this.query(id5, executionGeneration, true);
84206
+ }
84207
+ query(id5, executionGeneration, needSummary) {
84208
+ if (executionGeneration !== this.execution) return Promise.resolve({ kind: "unknown" });
83936
84209
  const known = this.kind(id5);
83937
- if (known !== "unknown") return Promise.resolve(known);
84210
+ if (known !== "unknown" && (!needSummary || known === "subagent")) return Promise.resolve({ kind: known });
83938
84211
  const pending = this.jobs.get(id5);
83939
- if (pending) return pending.execution === this.execution ? pending.promise : Promise.resolve("unknown");
83940
- if ((this.failures.get(id5) ?? 0) > this.now() || this.queue.length >= 64) return Promise.resolve("unknown");
84212
+ if (pending) return pending.execution === this.execution ? pending.promise : Promise.resolve({ kind: "unknown" });
84213
+ if ((this.failures.get(id5) ?? 0) > this.now() || this.queue.length >= 64) return Promise.resolve({ kind: "unknown" });
83941
84214
  let resolve5;
83942
84215
  const promise = new Promise((done) => {
83943
84216
  resolve5 = done;
@@ -83994,11 +84267,11 @@ var init_native_admission = __esm({
83994
84267
  removeJob(job) {
83995
84268
  if (this.jobs.get(job.id) === job) this.jobs.delete(job.id);
83996
84269
  }
83997
- finishWait(job, kind) {
84270
+ finishWait(job, kind, summary, token3) {
83998
84271
  if (job.settled) return;
83999
84272
  job.settled = true;
84000
84273
  job.cancel();
84001
- job.resolve(kind);
84274
+ job.resolve({ kind, ...summary ? { summary, token: token3 } : {} });
84002
84275
  }
84003
84276
  fail(job) {
84004
84277
  if (job.execution === this.execution) {
@@ -84032,7 +84305,7 @@ var init_native_admission = __esm({
84032
84305
  this.observe(result.thread, job.execution);
84033
84306
  const kind = this.kind(job.id);
84034
84307
  if (kind === "unknown") this.fail(job);
84035
- this.finishWait(job, kind);
84308
+ this.finishWait(job, kind, result.thread, this.token(job.id));
84036
84309
  }).catch(() => {
84037
84310
  if (!job.settled) {
84038
84311
  this.fail(job);
@@ -85028,19 +85301,19 @@ function withoutPersistentImageBytes(value, key = "", seen = /* @__PURE__ */ new
85028
85301
  if (seen.has(value)) return "[circular]";
85029
85302
  seen.add(value);
85030
85303
  try {
85031
- const object11 = value;
85032
- if (object11.type === "Buffer" && Array.isArray(object11.data)) {
85033
- return { type: "binary_omitted", byteSize: object11.data.length };
85304
+ const object12 = value;
85305
+ if (object12.type === "Buffer" && Array.isArray(object12.data)) {
85306
+ return { type: "binary_omitted", byteSize: object12.data.length };
85034
85307
  }
85035
- if (object11.event === "app_server_stdout") {
85036
- const payload = object11.rawPayload;
85037
- const metadata = Object.fromEntries(Object.entries(object11).filter(([name2]) => name2 !== "rawPayload").map(([name2, entry]) => [name2, withoutPersistentImageBytes(entry, name2, seen)]));
85308
+ if (object12.event === "app_server_stdout") {
85309
+ const payload = object12.rawPayload;
85310
+ const metadata = Object.fromEntries(Object.entries(object12).filter(([name2]) => name2 !== "rawPayload").map(([name2, entry]) => [name2, withoutPersistentImageBytes(entry, name2, seen)]));
85038
85311
  return { ...metadata, rawPayload: { type: "stdout_fragment", bytes: typeof payload?.bytes === "number" ? payload.bytes : typeof payload?.fragment === "string" ? Buffer.byteLength(payload.fragment) : 0 } };
85039
85312
  }
85040
85313
  if (Array.isArray(value)) return value.map((entry) => withoutPersistentImageBytes(entry, key, seen));
85041
- return Object.fromEntries(Object.entries(object11).map(([name2, entry]) => [
85314
+ return Object.fromEntries(Object.entries(object12).map(([name2, entry]) => [
85042
85315
  name2,
85043
- withoutPersistentImageBytes(entry, object11.type === "image" && name2 === "data" ? "base64" : name2, seen)
85316
+ withoutPersistentImageBytes(entry, object12.type === "image" && name2 === "data" ? "base64" : name2, seen)
85044
85317
  ]));
85045
85318
  } finally {
85046
85319
  seen.delete(value);
@@ -85145,6 +85418,7 @@ var init_debug_log = __esm({
85145
85418
  "ipc_recovery",
85146
85419
  "ipc_forwarding_flow",
85147
85420
  "notification_delivery",
85421
+ "ipc_thread_discovery",
85148
85422
  "startup_timing",
85149
85423
  "thread_settings_cache",
85150
85424
  "thread_list_cache",
@@ -85378,6 +85652,7 @@ var init_debug_log = __esm({
85378
85652
  "inFlight"
85379
85653
  ],
85380
85654
  // 启动性能仅保留固定阶段耗时和汇总计数,禁止记录路径、会话或异常原文。
85655
+ ipc_thread_discovery: ["durationMs"],
85381
85656
  startup_timing: [
85382
85657
  "durationMs",
85383
85658
  "threadCount",
@@ -85924,6 +86199,7 @@ var init_debug_log = __esm({
85924
86199
  "dropped_busy",
85925
86200
  "deadline_expired"
85926
86201
  ],
86202
+ ipc_thread_discovery: ["received", "ignored", "deduplicated", "capacity", "read_completed", "rejected", "stale", "published", "failed"],
85927
86203
  startup_timing: [
85928
86204
  "app_server_initialized",
85929
86205
  "thread_baseline_scanned",
@@ -86456,6 +86732,7 @@ var NiumaDaemonRuntime;
86456
86732
  var init_composition = __esm({
86457
86733
  "apps/desktop/src/daemon/runtime/composition.ts"() {
86458
86734
  "use strict";
86735
+ init_ipc_thread_discovery();
86459
86736
  init_legacy_question_operation();
86460
86737
  init_pending_response_operation();
86461
86738
  init_settings_update_operation();
@@ -86559,7 +86836,7 @@ var init_composition = __esm({
86559
86836
  init_settings_selection();
86560
86837
  init_thread_routing();
86561
86838
  init_device_name();
86562
- init_thread_load_trace();
86839
+ init_thread_load_trace2();
86563
86840
  init_paths2();
86564
86841
  init_message_submission_store();
86565
86842
  init_client2();
@@ -87189,6 +87466,7 @@ var init_composition = __esm({
87189
87466
  }
87190
87467
  desktopTrackers = new RuntimeDesktopIngressTrackers();
87191
87468
  desktopIngress = new RuntimeDesktopIngress({
87469
+ discoverThread: (id5) => this.ipcThreadDiscovery.discover(id5),
87192
87470
  nativeAdmission: () => this.nativeAdmission,
87193
87471
  ensureNativeAdmission: (id5) => this.ensureNativeAdmission(id5),
87194
87472
  requestReadFileRefresh: () => this.followerCoordination.desktopReadStateFileValue?.requestRefresh(),
@@ -87724,6 +88002,33 @@ var init_composition = __esm({
87724
88002
  }
87725
88003
  }
87726
88004
  });
88005
+ ipcThreadDiscovery = new RuntimeIpcThreadDiscovery({
88006
+ eligible: (id5) => !this.lifecycle.stoppingValue && this.desktopSession.desktopIpcStateValue === "ready" && this.executionHost.appServerStateValue === "ready" && !this.archiveState.isConfirmed(id5) && !this.catalog.hasActiveTombstone(id5) && this.nativeAdmission.isVisible(id5) && !this.catalog.storeValue.getPublicThreadSummary(id5),
88007
+ capture: () => {
88008
+ const runtime = this.lifecycle.runtimeGenerationValue, ipc = this.desktopSession.ipcConnectionGenerationValue;
88009
+ const client = this.executionHost.appServerValue, generation = this.appServerLoads.connectionGeneration;
88010
+ return () => !this.lifecycle.stoppingValue && runtime === this.lifecycle.runtimeGenerationValue && ipc === this.desktopSession.ipcConnectionGenerationValue && client === this.executionHost.appServerValue && generation === this.appServerLoads.connectionGeneration;
88011
+ },
88012
+ read: (id5) => this.nativeAdmission.ensureSummary(id5, this.appServerLoads.connectionGeneration),
88013
+ current: (token3) => this.nativeAdmission.isCurrent(token3),
88014
+ enqueue: (run) => this.eventSequencer.enqueue(async () => {
88015
+ run();
88016
+ }),
88017
+ commit: (thread4) => {
88018
+ this.directoryAuthority.begin(false);
88019
+ this.catalog.upsertAuthoritativeThreadSummary(thread4);
88020
+ this.emitEvent(this.publication.threadUpdatedEvent(thread4.id, "app_server", nowSeconds()));
88021
+ this.activityPublication.publishGlobalStatusIfChanged();
88022
+ },
88023
+ diagnostic: (threadId2, phase, elapsedMs) => this.diagnostics.writeDebug({
88024
+ component: "daemon",
88025
+ channel: "ipc",
88026
+ event: "ipc_thread_discovery",
88027
+ threadId: threadId2,
88028
+ phase,
88029
+ attributes: { durationMs: elapsedMs }
88030
+ })
88031
+ });
87727
88032
  async ensureNativeAdmission(id5) {
87728
88033
  return !this.lifecycle.stoppingValue && !this.archiveState.isConfirmed(id5) && await this.nativeAdmission.ensure(id5, this.appServerLoads.connectionGeneration) === "ordinary";
87729
88034
  }
@@ -88410,7 +88715,7 @@ var init_composition = __esm({
88410
88715
  isBlocked: isStartBlockedByLifecycle
88411
88716
  });
88412
88717
  // 所有 runtime 与 coordinator 共用同一审计实例,确保 thread-scope 事件可关联当前 trace。
88413
- threadLoadReports = new ThreadLoadReports();
88718
+ threadLoadReports = new ThreadLoadReports(Date.now, () => this.followerOpenTraceAudit);
88414
88719
  followerOpenTraceAudit;
88415
88720
  // 异步选择只等待目录;端口在实际调用时读取,不能提前冻结Owner或缓存。
88416
88721
  settingsSelection = new RuntimeSettingsSelection({
@@ -88758,6 +89063,41 @@ var init_runtime2 = __esm({
88758
89063
  }
88759
89064
  });
88760
89065
 
89066
+ // apps/desktop/src/daemon/thread-load-record.ts
89067
+ import { z as z17 } from "zod";
89068
+ var LoadRecordSchema;
89069
+ var init_thread_load_record = __esm({
89070
+ "apps/desktop/src/daemon/thread-load-record.ts"() {
89071
+ "use strict";
89072
+ init_src2();
89073
+ LoadRecordSchema = z17.object({
89074
+ schema_version: z17.literal(3),
89075
+ record_type: z17.enum(["load_start", "load_sample", "load_terminal", "load_close"]),
89076
+ load_id: z17.string().uuid(),
89077
+ thread_id: z17.string().min(1).max(256),
89078
+ sequence: z17.number().int().nonnegative(),
89079
+ occurred_at_ms: z17.number().int().nonnegative().max(864e13),
89080
+ clock: z17.enum(["client", "daemon"]),
89081
+ phase: z17.enum([...CLIENT_LOAD_PHASES, "open_received", "identity_ready", "lease_acquired", "shell_sent", "history_sent", "stream_closed", "failed", "cancelled", "superseded", "closed", "expired"]),
89082
+ elapsed_ms: z17.number().int().min(0).max(9e5),
89083
+ event_at_ms: z17.number().int().min(1).max(864e13).optional(),
89084
+ source_transport: z17.enum(["local", "direct", "relay"]).optional(),
89085
+ client_class: z17.enum(["web", "ios", "other"]).optional(),
89086
+ title: z17.string().max(256).optional(),
89087
+ lease_id: z17.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u).optional(),
89088
+ trace_id: z17.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u).optional(),
89089
+ terminal_phase: z17.enum(["request_started", "snapshot_received", "history_ready", "open_received", "identity_ready", "lease_acquired", "shell_sent", "history_sent"]).optional(),
89090
+ reason: z17.enum(["open_rejected", "history_failed", "left_view", "user_cancelled", "stream_replaced"]).optional()
89091
+ }).strict().superRefine((v, ctx) => {
89092
+ const fail18 = () => ctx.addIssue({ code: "custom", message: "invalid_load_record" });
89093
+ if (v.record_type === "load_start" && (v.phase !== "open_received" || v.clock !== "daemon" || !v.source_transport)) fail18();
89094
+ if (v.record_type === "load_terminal" && !["failed", "cancelled", "superseded", "expired"].includes(v.phase)) fail18();
89095
+ if (v.record_type === "load_close" && (v.phase !== "closed" || v.clock !== "daemon")) fail18();
89096
+ if (v.record_type === "load_sample" && (v.clock === "client" ? !CLIENT_LOAD_PHASES.includes(v.phase) : !["open_received", "identity_ready", "lease_acquired", "shell_sent", "history_sent", "stream_closed"].includes(v.phase))) fail18();
89097
+ });
89098
+ }
89099
+ });
89100
+
88761
89101
  // apps/desktop/src/daemon/follower-open-trace.ts
88762
89102
  import { randomUUID as randomUUID52 } from "node:crypto";
88763
89103
  import path52 from "node:path";
@@ -88782,6 +89122,9 @@ function isOptionalTraceTitle(value) {
88782
89122
  function hasOnlyKeys2(value, allowed) {
88783
89123
  return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).every((key) => allowed.includes(key));
88784
89124
  }
89125
+ function isRecent(value, nowMs) {
89126
+ return value <= nowMs && nowMs - value <= DAEMON_LOG_RETENTION_MS;
89127
+ }
88785
89128
  function dateFileName2(timestampMs) {
88786
89129
  return `${new Date(timestampMs).toISOString().slice(0, 10)}.ndjson`;
88787
89130
  }
@@ -88888,6 +89231,10 @@ function parseRecord2(line) {
88888
89231
  }
88889
89232
  if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
88890
89233
  const record25 = value;
89234
+ if (record25.schema_version === 3) {
89235
+ const parsed = LoadRecordSchema.safeParse(record25);
89236
+ return parsed.success ? parsed.data : null;
89237
+ }
88891
89238
  if (record25.schema_version === 1) return parseV1Record(record25);
88892
89239
  if (record25.schema_version === 2) return parseV2Record(record25);
88893
89240
  return null;
@@ -88923,6 +89270,46 @@ function readPrivateTraceFile(file, remainingBytes, storage) {
88923
89270
  return { bytesRead: error2 instanceof PrivateLogFileError ? error2.bytesRead : 0, incomplete: true };
88924
89271
  }
88925
89272
  }
89273
+ function readRecords(runtimeDir, nowMs, storage) {
89274
+ if (!Number.isFinite(nowMs)) return { records: [], incomplete: true };
89275
+ const candidates = listPrivateTraceFiles(runtimeDir, storage);
89276
+ const records = [];
89277
+ let incomplete = candidates.incomplete;
89278
+ let totalBytes = 0;
89279
+ let processedLines = 0;
89280
+ for (const filePath of candidates.files) {
89281
+ if (totalBytes >= MAX_TOTAL_BYTES2) {
89282
+ incomplete = true;
89283
+ break;
89284
+ }
89285
+ const file = readPrivateTraceFile(filePath, MAX_TOTAL_BYTES2 - totalBytes, storage);
89286
+ totalBytes += file.bytesRead;
89287
+ if (file.incomplete) incomplete = true;
89288
+ if (file.content === void 0) continue;
89289
+ let end = file.content.endsWith("\n") ? file.content.length - 1 : file.content.length;
89290
+ while (end > 0 && processedLines < MAX_PROCESSED_LINES2) {
89291
+ const newline = file.content.lastIndexOf("\n", end - 1);
89292
+ const start = newline + 1;
89293
+ const length = end - start;
89294
+ processedLines += 1;
89295
+ if (length > 0 && length <= MAX_LINE_BYTES2) {
89296
+ const line = file.content.slice(start, end);
89297
+ if (Buffer.byteLength(line, "utf8") <= MAX_LINE_BYTES2) {
89298
+ const record25 = parseRecord2(line);
89299
+ if (record25 && timestampOf(record25) <= nowMs) records.push(record25);
89300
+ else incomplete = true;
89301
+ } else incomplete = true;
89302
+ } else if (length > 0) incomplete = true;
89303
+ end = Math.max(0, newline);
89304
+ }
89305
+ if (totalBytes >= MAX_TOTAL_BYTES2 || processedLines >= MAX_PROCESSED_LINES2) {
89306
+ if (end > 0 || filePath !== candidates.files.at(-1)) incomplete = true;
89307
+ break;
89308
+ }
89309
+ }
89310
+ records.reverse();
89311
+ return { records, incomplete: incomplete || hasIncompleteSubscriptionHistory(records, nowMs) };
89312
+ }
88926
89313
  function readPersistedSequenceFloor(runtimeDir, storage) {
88927
89314
  let maximum = 0;
88928
89315
  let totalBytes = 0;
@@ -88941,14 +89328,65 @@ function readPersistedSequenceFloor(runtimeDir, storage) {
88941
89328
  }
88942
89329
  return maximum;
88943
89330
  }
89331
+ function subscriptionIdentity(record25) {
89332
+ return `${record25.trace_id}\0${record25.thread_id}\0${record25.lease_epoch}`;
89333
+ }
89334
+ function occursAfterStart(record25, trace) {
89335
+ return record25.occurred_at_ms > trace.opened_at_ms || record25.occurred_at_ms === trace.opened_at_ms && record25.sequence > trace.sequence;
89336
+ }
89337
+ function occursBeforeClose(record25, close) {
89338
+ return record25.occurred_at_ms < close.occurred_at_ms || record25.occurred_at_ms === close.occurred_at_ms && record25.sequence < close.sequence;
89339
+ }
89340
+ function hasIncompleteSubscriptionHistory(records, nowMs) {
89341
+ const starts = /* @__PURE__ */ new Map();
89342
+ const eventsByIdentity = /* @__PURE__ */ new Map();
89343
+ const closesByIdentity = /* @__PURE__ */ new Map();
89344
+ for (const record25 of records) {
89345
+ if (record25.record_type === "subscription_start") {
89346
+ const identity6 = subscriptionIdentity(record25);
89347
+ if (starts.has(identity6)) return true;
89348
+ starts.set(identity6, record25);
89349
+ continue;
89350
+ }
89351
+ if (record25.record_type === "lease_event" || record25.record_type === "subscription_event") {
89352
+ const identity6 = subscriptionIdentity(record25);
89353
+ const events = eventsByIdentity.get(identity6) ?? [];
89354
+ events.push(record25);
89355
+ eventsByIdentity.set(identity6, events);
89356
+ continue;
89357
+ }
89358
+ if (record25.record_type === "subscription_close") {
89359
+ const identity6 = subscriptionIdentity(record25);
89360
+ const closes = closesByIdentity.get(identity6) ?? [];
89361
+ closes.push(record25);
89362
+ closesByIdentity.set(identity6, closes);
89363
+ }
89364
+ }
89365
+ for (const [identity6, events] of eventsByIdentity) {
89366
+ const start = starts.get(identity6);
89367
+ if (!start || events.some((event) => !occursAfterStart(event, start))) return true;
89368
+ }
89369
+ for (const start of starts.values()) {
89370
+ const identity6 = subscriptionIdentity(start);
89371
+ const closes = closesByIdentity.get(identity6) ?? [];
89372
+ if (closes.some((close2) => !occursAfterStart(close2, start))) return true;
89373
+ if (closes.length > 1) return true;
89374
+ const close = closes[0];
89375
+ if (close && (eventsByIdentity.get(identity6) ?? []).some((event) => !occursBeforeClose(event, close))) return true;
89376
+ if (!close && !isRecent(start.opened_at_ms, nowMs)) return true;
89377
+ }
89378
+ if ([...closesByIdentity.keys()].some((identity6) => !starts.has(identity6))) return true;
89379
+ return false;
89380
+ }
88944
89381
  function createFollowerOpenTraceAudit(input) {
88945
89382
  return new FollowerOpenTraceAuditWriter(input.runtimeDir ?? daemonRuntimeDir(), input.nowMs ?? Date.now, input.files ?? privateAuditFiles);
88946
89383
  }
88947
- var TRACE_DIRECTORY_NAME, MAX_FILE_BYTES2, MAX_TOTAL_BYTES2, MAX_LINE_BYTES2, MAX_DIRECTORY_ENTRIES3, SUBSCRIPTION_TRACE_SAFE_ID, FOLLOWER_OPEN_TRACE_TITLE_MAX_LENGTH, TRANSPORTS, CLIENT_CLASSES, PURPOSES, STAGES, OUTCOMES, SUBSCRIPTION_LEASE_ACTIONS, SUBSCRIPTION_LEASE_SOURCES, SUBSCRIPTION_LEASE_TRANSITIONS, SUBSCRIPTION_CLOSE_OUTCOMES, SUBSCRIPTION_SOURCE_BY_LEASE_KIND, LEASE_KINDS, FINAL_OUTCOMES, THREAD_SCOPE_STAGES, TRACE_SCOPE_STAGES, DURATION_STAGES, PURPOSE_STAGES, LEASE_KIND_STAGES, OUTCOMES_BY_STAGE, runtimeWriteFailures, persistedSequenceFloors, FollowerOpenTraceAuditWriter;
89384
+ var TRACE_DIRECTORY_NAME, MAX_FILE_BYTES2, MAX_TOTAL_BYTES2, MAX_LINE_BYTES2, MAX_DIRECTORY_ENTRIES3, MAX_PROCESSED_LINES2, SUBSCRIPTION_TRACE_SAFE_ID, FOLLOWER_OPEN_TRACE_TITLE_MAX_LENGTH, TRANSPORTS, CLIENT_CLASSES, PURPOSES, STAGES, OUTCOMES, SUBSCRIPTION_LEASE_ACTIONS, SUBSCRIPTION_LEASE_SOURCES, SUBSCRIPTION_LEASE_TRANSITIONS, SUBSCRIPTION_CLOSE_OUTCOMES, SUBSCRIPTION_SOURCE_BY_LEASE_KIND, LEASE_KINDS, FINAL_OUTCOMES, THREAD_SCOPE_STAGES, TRACE_SCOPE_STAGES, DURATION_STAGES, PURPOSE_STAGES, LEASE_KIND_STAGES, OUTCOMES_BY_STAGE, runtimeWriteFailures, persistedSequenceFloors, FollowerOpenTraceAuditWriter;
88948
89385
  var init_follower_open_trace = __esm({
88949
89386
  "apps/desktop/src/daemon/follower-open-trace.ts"() {
88950
89387
  "use strict";
88951
- init_thread_load_trace();
89388
+ init_thread_load_record();
89389
+ init_thread_load_trace2();
88952
89390
  init_private_log_files();
88953
89391
  init_log_retention();
88954
89392
  init_paths();
@@ -88957,6 +89395,7 @@ var init_follower_open_trace = __esm({
88957
89395
  MAX_TOTAL_BYTES2 = 20 * 1024 * 1024;
88958
89396
  MAX_LINE_BYTES2 = 64 * 1024;
88959
89397
  MAX_DIRECTORY_ENTRIES3 = 256;
89398
+ MAX_PROCESSED_LINES2 = 1e4;
88960
89399
  SUBSCRIPTION_TRACE_SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
88961
89400
  FOLLOWER_OPEN_TRACE_TITLE_MAX_LENGTH = 256;
88962
89401
  TRANSPORTS = /* @__PURE__ */ new Set(["local", "direct", "relay"]);
@@ -89110,6 +89549,30 @@ var init_follower_open_trace = __esm({
89110
89549
  nowMs;
89111
89550
  files;
89112
89551
  sequence;
89552
+ loadEpoch = 0;
89553
+ loadIds;
89554
+ reserveLoadId(id5) {
89555
+ if (!this.loadIds) {
89556
+ try {
89557
+ this.ensureTraceDirectory(path52.join(this.runtimeDir, TRACE_DIRECTORY_NAME));
89558
+ } catch {
89559
+ return false;
89560
+ }
89561
+ const read = readRecords(this.runtimeDir, this.nowMs(), this.files);
89562
+ if (read.incomplete) return false;
89563
+ this.loadIds = new Set(read.records.filter((r) => r.schema_version === 3).map((r) => r.load_id));
89564
+ }
89565
+ if (this.loadIds.size >= 1e4 || this.loadIds.has(id5)) return false;
89566
+ this.loadIds.add(id5);
89567
+ return true;
89568
+ }
89569
+ loadGeneration() {
89570
+ return this.loadEpoch;
89571
+ }
89572
+ recordLoad(input) {
89573
+ const parsed = LoadRecordSchema.safeParse({ ...input, schema_version: 3, sequence: this.nextSequence() });
89574
+ if (parsed.success) this.write(parsed.data);
89575
+ }
89113
89576
  traceDirectoryInitialized = false;
89114
89577
  /** 只缓存当前追加文件;日期切换或清空后重新发现已有分片。 */
89115
89578
  appendFilePath;
@@ -89243,6 +89706,7 @@ var init_follower_open_trace = __esm({
89243
89706
  for (const entry of matching) this.files.remove(entry.file);
89244
89707
  }
89245
89708
  this.appendFilePath = void 0;
89709
+ this.loadEpoch += 1;
89246
89710
  this.activeTraces.clear();
89247
89711
  this.activeSubscriptionTraces.clear();
89248
89712
  this.activeSubscriptionTraceHandles.clear();
@@ -89484,21 +89948,21 @@ var init_local_gateway_supervisor = __esm({
89484
89948
 
89485
89949
  // apps/desktop/src/daemon/async-input-notification-store.ts
89486
89950
  import { createHash as createHash41 } from "node:crypto";
89487
- import { z as z17 } from "zod";
89951
+ import { z as z18 } from "zod";
89488
89952
  var ASYNC_INPUT_NOTIFICATION_MAX_BYTES, identityText, entrySchema2, documentSchema2, AsyncInputNotificationStore;
89489
89953
  var init_async_input_notification_store = __esm({
89490
89954
  "apps/desktop/src/daemon/async-input-notification-store.ts"() {
89491
89955
  "use strict";
89492
89956
  init_src2();
89493
89957
  ASYNC_INPUT_NOTIFICATION_MAX_BYTES = 2 * 1024 * 1024;
89494
- identityText = z17.string().min(1).max(8e3);
89495
- entrySchema2 = z17.object({
89496
- key: z17.string().regex(/^[a-f0-9]{64}$/),
89958
+ identityText = z18.string().min(1).max(8e3);
89959
+ entrySchema2 = z18.object({
89960
+ key: z18.string().regex(/^[a-f0-9]{64}$/),
89497
89961
  threadId: identityText,
89498
89962
  turnId: identityText,
89499
- stage: z17.enum(["admitted", "sent"])
89963
+ stage: z18.enum(["admitted", "sent"])
89500
89964
  }).strict();
89501
- documentSchema2 = z17.object({ version: z17.literal(1), entries: z17.array(entrySchema2).max(4096) }).strict();
89965
+ documentSchema2 = z18.object({ version: z18.literal(1), entries: z18.array(entrySchema2).max(4096) }).strict();
89502
89966
  AsyncInputNotificationStore = class _AsyncInputNotificationStore {
89503
89967
  constructor(journal, document) {
89504
89968
  this.journal = journal;