niuma-codex 0.0.1-beta.27 → 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.27";
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) {
@@ -30086,6 +30185,11 @@ var init_types = __esm({
30086
30185
  });
30087
30186
 
30088
30187
  // apps/desktop/src/pairing-web/settings.ts
30188
+ function dispatchSettingsVersion(request2) {
30189
+ if (request2.url !== "/api/v1/settings/version" || request2.method !== "GET") throw new HttpApiError(404, 900005, "\u63A5\u53E3\u4E0D\u5B58\u5728");
30190
+ if (request2.headers["transfer-encoding"] || Number(request2.headers["content-length"] ?? 0) > 0) throw new HttpApiError(400, 100004, "\u67E5\u8BE2\u4E0D\u80FD\u643A\u5E26\u8BF7\u6C42\u4F53");
30191
+ return { daemon_version: desktopPresenceVersion() };
30192
+ }
30089
30193
  function projection(s) {
30090
30194
  return {
30091
30195
  supported: s.supported,
@@ -30150,6 +30254,7 @@ var init_settings = __esm({
30150
30254
  "use strict";
30151
30255
  init_response();
30152
30256
  init_types();
30257
+ init_release_version();
30153
30258
  }
30154
30259
  });
30155
30260
 
@@ -31088,8 +31193,7 @@ function invalidPushServerOrigin() {
31088
31193
  function resolvePushServerOrigin(environment) {
31089
31194
  const value = environment.NIUMA_PUSH_SERVER_ORIGIN;
31090
31195
  if (value === void 0 || isBlankConfigValue(value)) {
31091
- const release2 = true;
31092
- return release2 ? DEFAULT_PUSH_PUBLIC_URL : resolveLocalServerOrigin(environment);
31196
+ return DEFAULT_PUSH_PUBLIC_URL;
31093
31197
  }
31094
31198
  if (URL_CONTROL_OR_SPACE_PATTERN.test(value)) {
31095
31199
  throw invalidPushServerOrigin();
@@ -31191,10 +31295,11 @@ var init_server_client = __esm({
31191
31295
 
31192
31296
  // apps/desktop/src/daemon/thread-load-trace.ts
31193
31297
  import { z as z8 } from "zod";
31194
- var ClientLoadPhaseSchema, occurredAtSchema, LoadSampleSchema, ThreadLoadReportSchema, LoadingTraceSchema, ThreadLoadReports;
31195
- var init_thread_load_trace = __esm({
31298
+ var ClientLoadPhaseSchema, occurredAtSchema, LoadSampleSchema, ThreadLoadReportSchema, ThreadLoadReportV2Schema, LoadingTraceSchema, ThreadLoadReports;
31299
+ var init_thread_load_trace2 = __esm({
31196
31300
  "apps/desktop/src/daemon/thread-load-trace.ts"() {
31197
31301
  "use strict";
31302
+ init_src2();
31198
31303
  ClientLoadPhaseSchema = z8.enum(["selected", "restored", "request_started", "snapshot_received", "history_ready", "content_presented"]);
31199
31304
  occurredAtSchema = z8.number().int().min(1).max(864e13).optional();
31200
31305
  LoadSampleSchema = z8.object({
@@ -31207,6 +31312,20 @@ var init_thread_load_trace = __esm({
31207
31312
  load_id: z8.string().uuid(),
31208
31313
  samples: z8.array(LoadSampleSchema).min(1).max(6)
31209
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
+ });
31210
31329
  LoadingTraceSchema = z8.object({
31211
31330
  load_id: z8.string().uuid(),
31212
31331
  lease_id: z8.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u),
@@ -31216,27 +31335,128 @@ var init_thread_load_trace = __esm({
31216
31335
  occurred_at_ms: occurredAtSchema
31217
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));
31218
31337
  ThreadLoadReports = class {
31219
- constructor(now = Date.now) {
31338
+ constructor(now = Date.now, audit = () => void 0) {
31220
31339
  this.now = now;
31340
+ this.audit = audit;
31221
31341
  }
31222
31342
  now;
31343
+ audit;
31223
31344
  entries = /* @__PURE__ */ new Map();
31224
- register(loadId, threadId2, owner2, record25) {
31345
+ used = /* @__PURE__ */ new Set();
31346
+ clear() {
31347
+ this.entries.clear();
31348
+ }
31349
+ begin(input) {
31225
31350
  for (const [key, value] of this.entries) if (value.expires < this.now()) this.entries.delete(key);
31226
- if (!z8.string().uuid().safeParse(loadId).success || this.entries.size >= 256 || this.entries.has(loadId)) return () => {
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);
31227
31374
  };
31228
- const entry = { threadId: threadId2, owner: owner2, expires: this.now() + 9e5, record: record25, seen: /* @__PURE__ */ new Set() };
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) {
31423
+ for (const [key, value] of this.entries) if (value.expires < this.now()) this.entries.delete(key);
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 () => {
31431
+ };
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 };
31229
31440
  this.entries.set(loadId, entry);
31230
31441
  return () => {
31231
31442
  if (this.entries.get(loadId) === entry) this.entries.delete(loadId);
31232
31443
  };
31233
31444
  }
31234
31445
  report(input, owner2) {
31235
- 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
+ }
31236
31452
  if (!parsed.success) return { accepted: false };
31237
31453
  const { load_id, thread_id, samples } = parsed.data;
31238
31454
  const entry = this.entries.get(load_id);
31239
- 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
+ }
31240
31460
  for (const sample of samples) if (!entry.seen.has(sample.phase)) {
31241
31461
  entry.seen.add(sample.phase);
31242
31462
  try {
@@ -31256,7 +31476,7 @@ var nonEmptyString5, booleanQuery, authorizationMode, canonicalBase64, boundedIn
31256
31476
  var init_contracts2 = __esm({
31257
31477
  "apps/desktop/src/http/contracts.ts"() {
31258
31478
  "use strict";
31259
- init_thread_load_trace();
31479
+ init_thread_load_trace2();
31260
31480
  init_src2();
31261
31481
  nonEmptyString5 = z9.string().trim().min(1);
31262
31482
  booleanQuery = z9.enum(["true", "false"]).transform((value) => value === "true");
@@ -31471,6 +31691,8 @@ var init_contracts2 = __esm({
31471
31691
  range_resume_version: z9.literal("1").transform(() => 1).optional(),
31472
31692
  range_resume_token: z9.string().regex(/^[A-Za-z0-9_-]{43}$/).optional(),
31473
31693
  load_id: z9.string().uuid().optional(),
31694
+ // 恢复关联仍保留,只有首屏采样可显式关闭。
31695
+ load_trace: z9.literal("false").transform(() => false).optional(),
31474
31696
  include_execution: z9.literal("true").transform(() => true).optional(),
31475
31697
  include_message_times: z9.literal("true").transform(() => true).optional(),
31476
31698
  include_async_questions: z9.literal("true").transform(() => true).optional(),
@@ -32516,6 +32738,7 @@ function createThreadOpenSseProjection(rawQuery, options = {}) {
32516
32738
  ...query2.range_resume_version ? { rangeResumeVersion: 1 } : {},
32517
32739
  ...query2.range_resume_token ? { rangeResumeToken: query2.range_resume_token } : {},
32518
32740
  ...query2.load_id ? { loadId: query2.load_id } : {},
32741
+ ...query2.load_trace === false ? { loadTrace: false } : {},
32519
32742
  viewVersion: THREAD_VIEW_VERSION,
32520
32743
  ...query2.include_async_questions ? { includeAsyncQuestions: true } : {},
32521
32744
  ...query2.include_read_receipts ? { includeReadReceipts: true } : {},
@@ -33550,7 +33773,7 @@ async function dispatchRestRoute(request2, dependencies) {
33550
33773
  }
33551
33774
  }
33552
33775
  if (route === "POST /api/v1/thread/load/report") {
33553
- const body = ThreadLoadReportSchema.parse(request2.body);
33776
+ const body = request2.body?.version === 2 ? ThreadLoadReportV2Schema.parse(request2.body) : ThreadLoadReportSchema.parse(request2.body);
33554
33777
  const result = await daemonClient.request("thread/load/report", body, {
33555
33778
  ...daemonRequestOptions(5e3, request2.signal),
33556
33779
  ...request2.authenticatedMessageSubmissionScope !== void 0 ? { authenticatedMessageSubmissionScope: request2.authenticatedMessageSubmissionScope } : {}
@@ -44123,7 +44346,7 @@ var init_server3 = __esm({
44123
44346
  const api = raw.startsWith("/api/");
44124
44347
  authorizePairingRequest(request2, request2.socket.localPort, api);
44125
44348
  if (api) {
44126
- sendJson(response, 200, successResponse(await (raw.startsWith("/api/v1/settings/") && this.settings ? dispatchAutostartSettings(request2, this.settings) : dispatchPairingRoute(request2, this.service))));
44349
+ sendJson(response, 200, successResponse(await (raw === "/api/v1/settings/version" ? dispatchSettingsVersion(request2) : raw.startsWith("/api/v1/settings/") && this.settings ? dispatchAutostartSettings(request2, this.settings) : dispatchPairingRoute(request2, this.service))));
44127
44350
  return;
44128
44351
  }
44129
44352
  const asset = ASSETS[raw];
@@ -44256,8 +44479,8 @@ function createRelayAttachmentDataLink(options) {
44256
44479
  const child = { permit, lease: null, ws: null, receiver: null, closed: false, paired: false, ready: false, opening: null, cleanup: null, unregister: null };
44257
44480
  children.set(permit.transfer_id, child);
44258
44481
  const registrationStarted = Date.now();
44259
- 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 });
44260
- 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() });
44261
44484
  try {
44262
44485
  const reply = await options.control.requestFileControl("file.register", {
44263
44486
  version: 1,
@@ -44269,7 +44492,7 @@ function createRelayAttachmentDataLink(options) {
44269
44492
  max_wire_bytes: attachmentTransferWireBudget(permit.context.byte_size)
44270
44493
  });
44271
44494
  if (reply.code !== 0) {
44272
- 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() });
44273
44496
  const error2 = unavailable7();
44274
44497
  error2.cause = { stage: "file.register", relayCode: reply.code };
44275
44498
  throw error2;
@@ -48020,7 +48243,7 @@ var init_desktop_ingress = __esm({
48020
48243
  resetMethodValidity() {
48021
48244
  this.ipcMethodInvalidatedThreads.clear();
48022
48245
  }
48023
- 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) {
48024
48247
  this.ports.writeDebug({
48025
48248
  component: "daemon",
48026
48249
  channel: "ipc",
@@ -48030,7 +48253,7 @@ var init_desktop_ingress = __esm({
48030
48253
  attributes: {
48031
48254
  type: frame.type,
48032
48255
  ...typeof frame.method === "string" ? { method: frame.method } : {},
48033
- ...timing
48256
+ ...timing2
48034
48257
  }
48035
48258
  });
48036
48259
  this.ports.writeRawDebug({
@@ -48062,6 +48285,9 @@ var init_desktop_ingress = __esm({
48062
48285
  }
48063
48286
  const status2 = objectRecord(frame.params);
48064
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
+ }
48065
48291
  if (frame.method === "thread-stream-state-changed" && methodThreadId && this.ipcMethodInvalidatedThreads.has(methodThreadId) && objectRecord(status2.change).type !== "snapshot") {
48066
48292
  if (readDiagnostic) readDiagnostic.reason = "baseline_required";
48067
48293
  return;
@@ -48764,19 +48990,19 @@ var init_socket_client = __esm({
48764
48990
  this.events.on("requestDiagnostic", listener);
48765
48991
  return () => this.events.off("requestDiagnostic", listener);
48766
48992
  }
48767
- emitRequestDiagnostic(timing, phase, outcome) {
48993
+ emitRequestDiagnostic(timing2, phase, outcome) {
48768
48994
  this.events.emit("requestDiagnostic", {
48769
- requestId: timing.requestId,
48770
- method: timing.method,
48771
- threadId: timing.threadId,
48995
+ requestId: timing2.requestId,
48996
+ method: timing2.method,
48997
+ threadId: timing2.threadId,
48772
48998
  phase,
48773
48999
  outcome,
48774
- durationMs: Math.max(0, Math.floor(performance.now() - timing.startedAt))
49000
+ durationMs: Math.max(0, Math.floor(performance.now() - timing2.startedAt))
48775
49001
  });
48776
49002
  }
48777
49003
  pruneTimedOutRequests() {
48778
- for (const [id5, timing] of this.timedOutRequests) {
48779
- 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);
48780
49006
  }
48781
49007
  while (this.timedOutRequests.size > 128) this.timedOutRequests.delete(this.timedOutRequests.keys().next().value);
48782
49008
  }
@@ -48913,20 +49139,20 @@ var init_socket_client = __esm({
48913
49139
  this.pruneTimedOutRequests();
48914
49140
  const localParams = params && typeof params === "object" ? params : {};
48915
49141
  const threadId2 = typeof localParams.threadId === "string" ? localParams.threadId : typeof localParams.conversationId === "string" ? localParams.conversationId : void 0;
48916
- const timing = { requestId, method: method2, threadId: threadId2, startedAt: performance.now() };
49142
+ const timing2 = { requestId, method: method2, threadId: threadId2, startedAt: performance.now() };
48917
49143
  const responsePromise = new Promise((resolve5, reject3) => {
48918
49144
  const timer = setTimeout(() => {
48919
49145
  this.pending.delete(requestId);
48920
- this.timedOutRequests.set(requestId, timing);
49146
+ this.timedOutRequests.set(requestId, timing2);
48921
49147
  this.pruneTimedOutRequests();
48922
- this.emitRequestDiagnostic(timing, "timeout", "unknown");
49148
+ this.emitRequestDiagnostic(timing2, "timeout", "unknown");
48923
49149
  reject3(new NiumaError("UNKNOWN_RESULT", `IPC \u8BF7\u6C42\u8D85\u65F6: ${method2}`, { method: method2 }));
48924
49150
  }, options.timeoutMs ?? this.requestTimeoutMs);
48925
- this.pending.set(requestId, { timer, resolve: resolve5, reject: reject3, timing });
49151
+ this.pending.set(requestId, { timer, resolve: resolve5, reject: reject3, timing: timing2 });
48926
49152
  });
48927
49153
  try {
48928
49154
  await this.write(frame);
48929
- this.emitRequestDiagnostic(timing, "sent", "pending");
49155
+ this.emitRequestDiagnostic(timing2, "sent", "pending");
48930
49156
  } catch (error2) {
48931
49157
  this.clearPending(requestId);
48932
49158
  throw error2 instanceof Error ? new NiumaError("IPC_NOT_INITIALIZED", error2.message) : new NiumaError("IPC_NOT_INITIALIZED", String(error2));
@@ -49025,10 +49251,10 @@ var init_socket_client = __esm({
49025
49251
  this.pruneTimedOutRequests();
49026
49252
  const outcome = frame.resultType === "error" ? "error" : "success";
49027
49253
  if (!pending) {
49028
- const timing = this.timedOutRequests.get(requestId);
49029
- if (timing) {
49254
+ const timing2 = this.timedOutRequests.get(requestId);
49255
+ if (timing2) {
49030
49256
  this.timedOutRequests.delete(requestId);
49031
- this.emitRequestDiagnostic(timing, "late_response", outcome);
49257
+ this.emitRequestDiagnostic(timing2, "late_response", outcome);
49032
49258
  }
49033
49259
  return;
49034
49260
  }
@@ -59539,15 +59765,46 @@ function createHistoryHandlers(ports) {
59539
59765
  if (negotiated && (!handoffScope || !scope || scope.bindingId !== handoffScope.bindingId)) {
59540
59766
  throw new HttpApiError(200, 200004, "\u6B63\u6587\u4EA4\u63A5\u9700\u8981\u8BA4\u8BC1\u4F1A\u8BDD");
59541
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
+ };
59542
59775
  if (negotiated && typeof params.handoffTicket === "string") {
59543
- if (options.signal?.aborted || socket.destroyed || socket.writableEnded) return STREAMING_RESPONSE;
59544
- const consumer = ports.connectionHandoff().claim(
59545
- handoffScope,
59546
- params.handoffDescriptor,
59547
- params.handoffTicket,
59548
- { emit: (frame) => socket.write(`${JSON.stringify(successEnvelope(frame))}
59549
- `), onClose: () => socket.end() }
59550
- );
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
+ }
59551
59808
  let recoveryClosed = false;
59552
59809
  let unregisterRecovery2 = () => {
59553
59810
  };
@@ -59560,12 +59817,14 @@ function createHistoryHandlers(ports) {
59560
59817
  }) : () => {
59561
59818
  };
59562
59819
  } catch (error2) {
59820
+ loadAttempt?.close();
59563
59821
  consumer.close();
59564
59822
  throw error2;
59565
59823
  }
59566
59824
  let unregisterArchive2 = () => {
59567
59825
  };
59568
59826
  const close2 = () => {
59827
+ loadAttempt?.close();
59569
59828
  unregisterArchive2();
59570
59829
  recoveryClosed = true;
59571
59830
  unregisterRecovery2();
@@ -59592,7 +59851,6 @@ function createHistoryHandlers(ports) {
59592
59851
  `);
59593
59852
  return STREAMING_RESPONSE;
59594
59853
  }
59595
- const loadStartedAt = performance.now();
59596
59854
  let loadTrace = null;
59597
59855
  let loadLeaseId = null;
59598
59856
  let unregisterLoad = () => {
@@ -59602,7 +59860,9 @@ function createHistoryHandlers(ports) {
59602
59860
  const pendingLoad = [];
59603
59861
  const loadSeen = /* @__PURE__ */ new Set();
59604
59862
  const recordLoad = (phase, clock = "daemon", elapsed = Math.round(performance.now() - loadStartedAt), occurredAt4 = clock === "daemon" ? Date.now() : void 0) => {
59605
- 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);
59606
59866
  if (!loadLeaseId) {
59607
59867
  if (pendingLoad.length < 8) pendingLoad.push({ phase, elapsed, occurredAt: occurredAt4 });
59608
59868
  return;
@@ -59691,6 +59951,7 @@ function createHistoryHandlers(ports) {
59691
59951
  readReceiptTimer = null;
59692
59952
  }
59693
59953
  recordLoad("stream_closed");
59954
+ loadAttempt?.close();
59694
59955
  unregisterLoad();
59695
59956
  unregisterRecovery();
59696
59957
  closed = true;
@@ -59727,6 +59988,7 @@ function createHistoryHandlers(ports) {
59727
59988
  const close = () => {
59728
59989
  if (physicalClosed) return;
59729
59990
  physicalClosed = true;
59991
+ loadAttempt?.close();
59730
59992
  unregisterRecovery();
59731
59993
  options.signal?.removeEventListener("abort", close);
59732
59994
  publisher?.close();
@@ -59743,7 +60005,14 @@ function createHistoryHandlers(ports) {
59743
60005
  return STREAMING_RESPONSE;
59744
60006
  }
59745
60007
  if (!ports.store().getThread(threadId2, { tailCount: 0 })) {
59746
- 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
+ }
59747
60016
  }
59748
60017
  if (closed || socketIsClosed()) {
59749
60018
  closed = true;
@@ -59792,13 +60061,15 @@ function createHistoryHandlers(ports) {
59792
60061
  }
59793
60062
  for (const sample of pendingLoad) recordLoad(sample.phase, "daemon", sample.elapsed, sample.occurredAt);
59794
60063
  pendingLoad.length = 0;
60064
+ if (loadLeaseId && loadTrace) loadAttempt?.attachLease(loadLeaseId, loadTrace.traceId);
59795
60065
  recordLoad("lease_acquired");
59796
60066
  try {
59797
- if (loadLeaseId && loadTrace) unregisterLoad = ports.threadLoadReports().register(
60067
+ if (loadAttempt && loadLeaseId && loadTrace) unregisterLoad = ports.threadLoadReports().register(
59798
60068
  params.loadId,
59799
60069
  threadId2,
59800
60070
  scope ? `binding:${scope.bindingId.toLowerCase()}` : "local",
59801
- (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
59802
60073
  );
59803
60074
  } catch {
59804
60075
  }
@@ -59912,6 +60183,7 @@ function createHistoryHandlers(ports) {
59912
60183
  ports.requestThreadAuthorityRecovery(threadId2, true);
59913
60184
  ports.history().onHistoryLeaseStateChanged(threadId2, ports.activeThreadFollowerCoordinator()?.leaseKindsFor(threadId2) ?? []);
59914
60185
  unsubscribeHistoryWindow = historyWindow.subscribe(() => {
60186
+ if (historyWindow?.initialWindowState === "failed") loadAttempt?.finish("failed", { terminal_phase: "shell_sent", reason: "history_failed" });
59915
60187
  if (rangeClaim && historyWindow?.initialWindowState === "failed") {
59916
60188
  socket.write(`${JSON.stringify(errorEnvelope(new NiumaError("HISTORY_SNAPSHOT_EXPIRED", "\u5386\u53F2\u6062\u590D\u8303\u56F4\u4E0D\u53EF\u7528\u6216\u5DF2\u8FC7\u671F")))}
59917
60189
  `);
@@ -59986,6 +60258,7 @@ function createHistoryHandlers(ports) {
59986
60258
  `);
59987
60259
  }
59988
60260
  } catch (error2) {
60261
+ failOpen(error2);
59989
60262
  try {
59990
60263
  const sites = [
59991
60264
  ["historySourceAssertion", "Object.sourceCurrent"],
@@ -60067,20 +60340,20 @@ function text10(value) {
60067
60340
  return false;
60068
60341
  }
60069
60342
  }
60070
- function object9(value, keys3) {
60343
+ function object10(value, keys3) {
60071
60344
  if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return fail16("invalid");
60072
60345
  const own = Reflect.ownKeys(value);
60073
60346
  if (own.length !== keys3.length || !own.every((key) => typeof key === "string" && keys3.includes(key) && "value" in Object.getOwnPropertyDescriptor(value, key))) return fail16("invalid");
60074
60347
  return value;
60075
60348
  }
60076
60349
  function parseAsyncQuestionBatch(value) {
60077
- 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"]);
60078
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");
60079
60352
  const questionIds = Array.from(raw.question_ids);
60080
60353
  if (!questionIds.every(id3) || new Set(questionIds).size !== questionIds.length) return fail16("invalid");
60081
60354
  const seen = /* @__PURE__ */ new Set();
60082
60355
  const answers = Array.from(raw.answers, (value2) => {
60083
- const answer = object9(value2, ["question_id", "title", "answer"]);
60356
+ const answer = object10(value2, ["question_id", "title", "answer"]);
60084
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");
60085
60358
  seen.add(answer.question_id);
60086
60359
  return { question_id: answer.question_id, title: answer.title, answer: answer.answer };
@@ -62991,10 +63264,10 @@ var init_thread_history_window_loader = __esm({
62991
63264
  sourceRevision: 0
62992
63265
  };
62993
63266
  for (let attempt = 0; attempt < 2; attempt++) {
62994
- const timing = await this.timestampVersion(turn, signal);
63267
+ const timing2 = await this.timestampVersion(turn, signal);
62995
63268
  this.assertSource(epoch);
62996
63269
  signal.throwIfAborted();
62997
- const versionKey = JSON.stringify([epoch, description.contentVersion, timing]);
63270
+ const versionKey = JSON.stringify([epoch, description.contentVersion, timing2]);
62998
63271
  let cached = this.turns.get(identity6);
62999
63272
  if (cached?.current?.version === versionKey) {
63000
63273
  if (description.sourceRevision >= cached.sourceRevision && cached.nativeOrder !== description.nativeOrder) {
@@ -63011,7 +63284,7 @@ var init_thread_history_window_loader = __esm({
63011
63284
  this.assertSource(epoch);
63012
63285
  signal.throwIfAborted();
63013
63286
  if (enriched.turnId !== turn.id) throw new RangeError("\u7A97\u53E3\u65F6\u95F4\u8865\u9F50 Turn \u8EAB\u4EFD\u4E0D\u5339\u914D");
63014
- if (await this.timestampVersion(turn, signal) !== timing) continue;
63287
+ if (await this.timestampVersion(turn, signal) !== timing2) continue;
63015
63288
  this.assertSource(epoch);
63016
63289
  signal.throwIfAborted();
63017
63290
  const freeze = (value) => {
@@ -63069,11 +63342,11 @@ var init_thread_history_window_loader = __esm({
63069
63342
  const epoch = this.sourceEpoch;
63070
63343
  this.currentProtection.addTurnIds([turn.id]);
63071
63344
  const description = this.reader.describeTurn?.(turn) ?? { contentVersion: createHash26("sha256").update(JSON.stringify(turn)).digest("hex") };
63072
- const timing = await this.timestampVersion(turn, signal);
63345
+ const timing2 = await this.timestampVersion(turn, signal);
63073
63346
  this.assertSource(epoch);
63074
63347
  signal.throwIfAborted();
63075
63348
  const cached = this.turns.get(JSON.stringify([this.currentThreadId, turn.id]));
63076
- if (cached?.current?.version === JSON.stringify([epoch, description.contentVersion, timing])) {
63349
+ if (cached?.current?.version === JSON.stringify([epoch, description.contentVersion, timing2])) {
63077
63350
  this.holdVersion(this.currentProtection, cached.current);
63078
63351
  return cached.current.projection.displayItemCount;
63079
63352
  }
@@ -64334,17 +64607,17 @@ var init_app_server_thread_view_adapter = __esm({
64334
64607
  lastError: null
64335
64608
  }).status;
64336
64609
  const terminalStatus = failed || aborted || status2 === "completed" ? lifecycleStatus : "completed";
64337
- const timing = context.getTurnLifecycle?.(turnId);
64610
+ const timing2 = context.getTurnLifecycle?.(turnId);
64338
64611
  return [{
64339
64612
  type: "ThreadStatusChanged",
64340
64613
  thread_id: context.threadId,
64341
64614
  status: terminalStatus,
64342
64615
  turn_id: turnId,
64343
64616
  terminal_item_state: terminalStatus === "error" ? "failed" : "completed",
64344
- ...timing ? { process_lifecycle: {
64345
- startedAt: lifecycleTime(timing.startedAt),
64346
- endedAt: lifecycleTime(timing.endedAt),
64347
- outcome: timing.outcome
64617
+ ...timing2 ? { process_lifecycle: {
64618
+ startedAt: lifecycleTime(timing2.startedAt),
64619
+ endedAt: lifecycleTime(timing2.endedAt),
64620
+ outcome: timing2.outcome
64348
64621
  } } : {},
64349
64622
  last_error: terminalStatus === "error" ? {
64350
64623
  code: 900001,
@@ -64615,10 +64888,10 @@ var init_thread_history_window_projection = __esm({
64615
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);
64616
64889
  const timingById = new Map(sources.map((source2, index) => [source2.id, enriched[index]]));
64617
64890
  const items = projected.items.map((item) => {
64618
- const timing = timingById.get(item.id);
64891
+ const timing2 = timingById.get(item.id);
64619
64892
  const fact = item.process_fact;
64620
- if (!timing || !fact) return item;
64621
- const merged = mergeThreadHistoryTimelineLifecycle(timing, {
64893
+ if (!timing2 || !fact) return item;
64894
+ const merged = mergeThreadHistoryTimelineLifecycle(timing2, {
64622
64895
  ...fact.startedAt !== null ? { turnStartedAt: fact.startedAt } : {},
64623
64896
  ...fact.endedAt !== null ? { turnEndedAt: fact.endedAt } : {},
64624
64897
  ...fact.durationMs != null ? { turnDurationMs: fact.durationMs } : {},
@@ -65439,7 +65712,7 @@ function readStateDiagnostic(before, after, candidate = after.confirmed) {
65439
65712
  sameCycle: candidate ? candidate.cycle === after.cycle : null
65440
65713
  };
65441
65714
  }
65442
- function object10(value) {
65715
+ function object11(value) {
65443
65716
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
65444
65717
  }
65445
65718
  function field(value, present, count) {
@@ -65449,15 +65722,15 @@ function field(value, present, count) {
65449
65722
  }
65450
65723
  function unreadFrameDiagnostic(frame) {
65451
65724
  if (frame.method !== "thread-stream-state-changed") return null;
65452
- const params = object10(frame.params), change = object10(params.change);
65725
+ const params = object11(frame.params), change = object11(params.change);
65453
65726
  const kind = change.type === "snapshot" ? "snapshot" : change.type === "patches" ? "patch" : "invalid";
65454
- const values = kind === "snapshot" ? object10(change.conversationState) : {};
65727
+ const values = kind === "snapshot" ? object11(change.conversationState) : {};
65455
65728
  let unread = field(values.hasUnreadTurn, Object.hasOwn(values, "hasUnreadTurn"), false);
65456
65729
  let count = field(values.unreadMessageCount, Object.hasOwn(values, "unreadMessageCount"), true);
65457
65730
  let countValue = values.unreadMessageCount;
65458
65731
  if (kind === "patch") {
65459
65732
  for (const value of Array.isArray(change.patches) ? change.patches : []) {
65460
- const patch = object10(value), path55 = Array.isArray(patch.path) ? patch.path : [];
65733
+ const patch = object11(value), path55 = Array.isArray(patch.path) ? patch.path : [];
65461
65734
  const name2 = path55.length === 1 ? path55[0] : path55.length === 2 && path55[0] === "conversationState" ? path55[1] : null;
65462
65735
  if (name2 !== "hasUnreadTurn" && name2 !== "unreadMessageCount") continue;
65463
65736
  const state = patch.op === "remove" ? "removed" : ["add", "replace"].includes(String(patch.op)) ? field(patch.value, Object.hasOwn(patch, "value"), name2 === "unreadMessageCount") : "invalid";
@@ -65571,10 +65844,10 @@ var init_ingress_dispatch = __esm({
65571
65844
  }
65572
65845
  ports;
65573
65846
  /** 单帧入站与最终结果成对记录;原始字段与业务结果分开,拒绝帧同样留证。 */
65574
- 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) {
65575
65848
  const attributes = this.ports.options().debugLogger ? unreadFrameDiagnostic(frame) : null;
65576
65849
  const threadId2 = attributes ? extractThreadIdFromIpcFrame(frame) : null;
65577
- 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);
65578
65851
  const before = this.ports.catalog().storeValue.getThread(threadId2, { tailCount: 0 })?.unread;
65579
65852
  const diagnostic = { reason: "processed" };
65580
65853
  const metadata = { ...attributes, sequence: this.ports.diagnostics().threadReadDiagnosticSequenceValue++, generation: receivedIpcGeneration, currentGeneration: this.ports.desktopSession().ipcConnectionGenerationValue };
@@ -65587,7 +65860,7 @@ var init_ingress_dispatch = __esm({
65587
65860
  }
65588
65861
  });
65589
65862
  try {
65590
- await this.ports.desktopIngress().process(frame, ownClientId, receivedIpcGeneration, timing, diagnostic);
65863
+ await this.ports.desktopIngress().process(frame, ownClientId, receivedIpcGeneration, timing2, diagnostic);
65591
65864
  } catch (error2) {
65592
65865
  diagnostic.reason = "processing_failed";
65593
65866
  throw error2;
@@ -67399,11 +67672,11 @@ var init_projection_context = __esm({
67399
67672
  } : null),
67400
67673
  enrichTimelineItemTiming: (item) => this.ports.timelinePublication().enrichTimelineItemTiming(item, timelineItems),
67401
67674
  getTurnLifecycle: (turnId) => {
67402
- const timing = this.ports.timelinePublication().turnTiming(threadId2, turnId);
67403
- return timing ? {
67404
- startedAt: timing.startedAt,
67405
- endedAt: timing.endedAt ?? null,
67406
- 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
67407
67680
  } : null;
67408
67681
  },
67409
67682
  resolveKnownLocalImage: (sourcePath, itemId, partIndex) => this.ports.resolveThreadViewImage(threadId2, sourcePath, () => this.ports.isCurrentAppServerAsyncFence(threadId2, fence), itemId, partIndex)
@@ -68021,7 +68294,7 @@ var init_active_thread_follower_coordinator = __esm({
68021
68294
  init_errors();
68022
68295
  init_session_history_file();
68023
68296
  init_error_log();
68024
- ACTIVE_THREAD_SCAN_INTERVAL_MS = 1e4;
68297
+ ACTIVE_THREAD_SCAN_INTERVAL_MS = 6e4;
68025
68298
  ACTIVE_THREAD_READ_CONCURRENCY = 4;
68026
68299
  ACTIVE_FOLLOWER_SNAPSHOT_TIMEOUT_MS = 12e3;
68027
68300
  ACTIVE_FOLLOWER_RETRY_DELAYS_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
@@ -75114,7 +75387,7 @@ var init_thread_state_coordination = __esm({
75114
75387
  return token3;
75115
75388
  }
75116
75389
  /** 仅供已核验的同轮终态使用;同时收敛列表与详情,不伪造新的完成通知。 */
75117
- applyRecoveredTerminal(threadId2, turnId, outcome, occurredAt4, message, timing) {
75390
+ applyRecoveredTerminal(threadId2, turnId, outcome, occurredAt4, message, timing2) {
75118
75391
  const previous = this.ports.catalog().storeValue.getThread(threadId2, { tailCount: 0 });
75119
75392
  const mutation = this.ports.catalog().storeValue.applyActivitySignal({
75120
75393
  type: "snapshot",
@@ -75134,7 +75407,7 @@ var init_thread_state_coordination = __esm({
75134
75407
  turn_id: turnId,
75135
75408
  status: outcome === "aborted" ? "idle" : outcome === "failed" ? "error" : "completed",
75136
75409
  terminal_item_state: outcome === "failed" ? "failed" : "completed",
75137
- process_lifecycle: { startedAt: timing?.startedAt ?? null, endedAt: timing?.endedAt ?? null, outcome },
75410
+ process_lifecycle: { startedAt: timing2?.startedAt ?? null, endedAt: timing2?.endedAt ?? null, outcome },
75138
75411
  last_error: outcome === "failed" && message ? { code: 900001, message, occurred_at: occurredAt4, turn_id: turnId } : null
75139
75412
  },
75140
75413
  { type: "ThreadActivityChanged", thread_id: threadId2, turn_id: turnId, activity: null },
@@ -75394,15 +75667,15 @@ var init_timeline_publication = __esm({
75394
75667
  if (!item.turnId) {
75395
75668
  return item;
75396
75669
  }
75397
- const timing = this.liveTurnTimings.get(this.turnTimingKey(item.threadId, item.turnId));
75398
- if (!timing) {
75670
+ const timing2 = this.liveTurnTimings.get(this.turnTimingKey(item.threadId, item.turnId));
75671
+ if (!timing2) {
75399
75672
  return item;
75400
75673
  }
75401
75674
  return {
75402
75675
  ...item,
75403
- ...item.turnStartedAt === void 0 ? { turnStartedAt: timing.startedAt } : {},
75404
- ...item.turnEndedAt === void 0 && timing.endedAt !== void 0 ? { turnEndedAt: timing.endedAt } : {},
75405
- ...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 } : {}
75406
75679
  };
75407
75680
  }
75408
75681
  enrichTimelineItemTiming(item, historyItems = []) {
@@ -75422,9 +75695,9 @@ var init_timeline_publication = __esm({
75422
75695
  turnTimingKey(threadId2, turnId) {
75423
75696
  return `${threadId2}:${turnId}`;
75424
75697
  }
75425
- setLiveTurnTiming(key, timing) {
75698
+ setLiveTurnTiming(key, timing2) {
75426
75699
  this.liveTurnTimings.delete(key);
75427
- this.liveTurnTimings.set(key, timing);
75700
+ this.liveTurnTimings.set(key, timing2);
75428
75701
  while (this.liveTurnTimings.size > LIVE_TURN_TIMING_LIMIT) {
75429
75702
  const oldestKey = this.liveTurnTimings.keys().next().value;
75430
75703
  if (typeof oldestKey !== "string") break;
@@ -83906,8 +84179,7 @@ var init_native_admission = __esm({
83906
84179
  else if (kind === "ordinary") this.hidden.delete(thread4.id);
83907
84180
  const old = this.entries.get(thread4.id);
83908
84181
  this.failures.delete(thread4.id);
83909
- if (old && old.kind === kind) return old.token;
83910
- 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 };
83911
84183
  this.entries.set(thread4.id, { token: token3, kind });
83912
84184
  try {
83913
84185
  if (kind !== previous) this.ports.onChange?.(thread4.id, previous, kind);
@@ -83917,7 +84189,7 @@ var init_native_admission = __esm({
83917
84189
  }
83918
84190
  const pending = this.jobs.get(thread4.id);
83919
84191
  if (pending) {
83920
- this.finishWait(pending, kind);
84192
+ this.finishWait(pending, kind, thread4, token3);
83921
84193
  if (!pending.started) {
83922
84194
  this.removeQueued(pending);
83923
84195
  this.removeJob(pending);
@@ -83925,13 +84197,20 @@ var init_native_admission = __esm({
83925
84197
  }
83926
84198
  return token3;
83927
84199
  }
83928
- ensure(id5, executionGeneration) {
83929
- 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" });
83930
84209
  const known = this.kind(id5);
83931
- if (known !== "unknown") return Promise.resolve(known);
84210
+ if (known !== "unknown" && (!needSummary || known === "subagent")) return Promise.resolve({ kind: known });
83932
84211
  const pending = this.jobs.get(id5);
83933
- if (pending) return pending.execution === this.execution ? pending.promise : Promise.resolve("unknown");
83934
- 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" });
83935
84214
  let resolve5;
83936
84215
  const promise = new Promise((done) => {
83937
84216
  resolve5 = done;
@@ -83988,11 +84267,11 @@ var init_native_admission = __esm({
83988
84267
  removeJob(job) {
83989
84268
  if (this.jobs.get(job.id) === job) this.jobs.delete(job.id);
83990
84269
  }
83991
- finishWait(job, kind) {
84270
+ finishWait(job, kind, summary, token3) {
83992
84271
  if (job.settled) return;
83993
84272
  job.settled = true;
83994
84273
  job.cancel();
83995
- job.resolve(kind);
84274
+ job.resolve({ kind, ...summary ? { summary, token: token3 } : {} });
83996
84275
  }
83997
84276
  fail(job) {
83998
84277
  if (job.execution === this.execution) {
@@ -84026,7 +84305,7 @@ var init_native_admission = __esm({
84026
84305
  this.observe(result.thread, job.execution);
84027
84306
  const kind = this.kind(job.id);
84028
84307
  if (kind === "unknown") this.fail(job);
84029
- this.finishWait(job, kind);
84308
+ this.finishWait(job, kind, result.thread, this.token(job.id));
84030
84309
  }).catch(() => {
84031
84310
  if (!job.settled) {
84032
84311
  this.fail(job);
@@ -85022,19 +85301,19 @@ function withoutPersistentImageBytes(value, key = "", seen = /* @__PURE__ */ new
85022
85301
  if (seen.has(value)) return "[circular]";
85023
85302
  seen.add(value);
85024
85303
  try {
85025
- const object11 = value;
85026
- if (object11.type === "Buffer" && Array.isArray(object11.data)) {
85027
- 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 };
85028
85307
  }
85029
- if (object11.event === "app_server_stdout") {
85030
- const payload = object11.rawPayload;
85031
- 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)]));
85032
85311
  return { ...metadata, rawPayload: { type: "stdout_fragment", bytes: typeof payload?.bytes === "number" ? payload.bytes : typeof payload?.fragment === "string" ? Buffer.byteLength(payload.fragment) : 0 } };
85033
85312
  }
85034
85313
  if (Array.isArray(value)) return value.map((entry) => withoutPersistentImageBytes(entry, key, seen));
85035
- return Object.fromEntries(Object.entries(object11).map(([name2, entry]) => [
85314
+ return Object.fromEntries(Object.entries(object12).map(([name2, entry]) => [
85036
85315
  name2,
85037
- withoutPersistentImageBytes(entry, object11.type === "image" && name2 === "data" ? "base64" : name2, seen)
85316
+ withoutPersistentImageBytes(entry, object12.type === "image" && name2 === "data" ? "base64" : name2, seen)
85038
85317
  ]));
85039
85318
  } finally {
85040
85319
  seen.delete(value);
@@ -85139,6 +85418,7 @@ var init_debug_log = __esm({
85139
85418
  "ipc_recovery",
85140
85419
  "ipc_forwarding_flow",
85141
85420
  "notification_delivery",
85421
+ "ipc_thread_discovery",
85142
85422
  "startup_timing",
85143
85423
  "thread_settings_cache",
85144
85424
  "thread_list_cache",
@@ -85372,6 +85652,7 @@ var init_debug_log = __esm({
85372
85652
  "inFlight"
85373
85653
  ],
85374
85654
  // 启动性能仅保留固定阶段耗时和汇总计数,禁止记录路径、会话或异常原文。
85655
+ ipc_thread_discovery: ["durationMs"],
85375
85656
  startup_timing: [
85376
85657
  "durationMs",
85377
85658
  "threadCount",
@@ -85918,6 +86199,7 @@ var init_debug_log = __esm({
85918
86199
  "dropped_busy",
85919
86200
  "deadline_expired"
85920
86201
  ],
86202
+ ipc_thread_discovery: ["received", "ignored", "deduplicated", "capacity", "read_completed", "rejected", "stale", "published", "failed"],
85921
86203
  startup_timing: [
85922
86204
  "app_server_initialized",
85923
86205
  "thread_baseline_scanned",
@@ -86450,6 +86732,7 @@ var NiumaDaemonRuntime;
86450
86732
  var init_composition = __esm({
86451
86733
  "apps/desktop/src/daemon/runtime/composition.ts"() {
86452
86734
  "use strict";
86735
+ init_ipc_thread_discovery();
86453
86736
  init_legacy_question_operation();
86454
86737
  init_pending_response_operation();
86455
86738
  init_settings_update_operation();
@@ -86553,7 +86836,7 @@ var init_composition = __esm({
86553
86836
  init_settings_selection();
86554
86837
  init_thread_routing();
86555
86838
  init_device_name();
86556
- init_thread_load_trace();
86839
+ init_thread_load_trace2();
86557
86840
  init_paths2();
86558
86841
  init_message_submission_store();
86559
86842
  init_client2();
@@ -87183,6 +87466,7 @@ var init_composition = __esm({
87183
87466
  }
87184
87467
  desktopTrackers = new RuntimeDesktopIngressTrackers();
87185
87468
  desktopIngress = new RuntimeDesktopIngress({
87469
+ discoverThread: (id5) => this.ipcThreadDiscovery.discover(id5),
87186
87470
  nativeAdmission: () => this.nativeAdmission,
87187
87471
  ensureNativeAdmission: (id5) => this.ensureNativeAdmission(id5),
87188
87472
  requestReadFileRefresh: () => this.followerCoordination.desktopReadStateFileValue?.requestRefresh(),
@@ -87718,6 +88002,33 @@ var init_composition = __esm({
87718
88002
  }
87719
88003
  }
87720
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
+ });
87721
88032
  async ensureNativeAdmission(id5) {
87722
88033
  return !this.lifecycle.stoppingValue && !this.archiveState.isConfirmed(id5) && await this.nativeAdmission.ensure(id5, this.appServerLoads.connectionGeneration) === "ordinary";
87723
88034
  }
@@ -88404,7 +88715,7 @@ var init_composition = __esm({
88404
88715
  isBlocked: isStartBlockedByLifecycle
88405
88716
  });
88406
88717
  // 所有 runtime 与 coordinator 共用同一审计实例,确保 thread-scope 事件可关联当前 trace。
88407
- threadLoadReports = new ThreadLoadReports();
88718
+ threadLoadReports = new ThreadLoadReports(Date.now, () => this.followerOpenTraceAudit);
88408
88719
  followerOpenTraceAudit;
88409
88720
  // 异步选择只等待目录;端口在实际调用时读取,不能提前冻结Owner或缓存。
88410
88721
  settingsSelection = new RuntimeSettingsSelection({
@@ -88752,6 +89063,41 @@ var init_runtime2 = __esm({
88752
89063
  }
88753
89064
  });
88754
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
+
88755
89101
  // apps/desktop/src/daemon/follower-open-trace.ts
88756
89102
  import { randomUUID as randomUUID52 } from "node:crypto";
88757
89103
  import path52 from "node:path";
@@ -88776,6 +89122,9 @@ function isOptionalTraceTitle(value) {
88776
89122
  function hasOnlyKeys2(value, allowed) {
88777
89123
  return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).every((key) => allowed.includes(key));
88778
89124
  }
89125
+ function isRecent(value, nowMs) {
89126
+ return value <= nowMs && nowMs - value <= DAEMON_LOG_RETENTION_MS;
89127
+ }
88779
89128
  function dateFileName2(timestampMs) {
88780
89129
  return `${new Date(timestampMs).toISOString().slice(0, 10)}.ndjson`;
88781
89130
  }
@@ -88882,6 +89231,10 @@ function parseRecord2(line) {
88882
89231
  }
88883
89232
  if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
88884
89233
  const record25 = value;
89234
+ if (record25.schema_version === 3) {
89235
+ const parsed = LoadRecordSchema.safeParse(record25);
89236
+ return parsed.success ? parsed.data : null;
89237
+ }
88885
89238
  if (record25.schema_version === 1) return parseV1Record(record25);
88886
89239
  if (record25.schema_version === 2) return parseV2Record(record25);
88887
89240
  return null;
@@ -88917,6 +89270,46 @@ function readPrivateTraceFile(file, remainingBytes, storage) {
88917
89270
  return { bytesRead: error2 instanceof PrivateLogFileError ? error2.bytesRead : 0, incomplete: true };
88918
89271
  }
88919
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
+ }
88920
89313
  function readPersistedSequenceFloor(runtimeDir, storage) {
88921
89314
  let maximum = 0;
88922
89315
  let totalBytes = 0;
@@ -88935,14 +89328,65 @@ function readPersistedSequenceFloor(runtimeDir, storage) {
88935
89328
  }
88936
89329
  return maximum;
88937
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
+ }
88938
89381
  function createFollowerOpenTraceAudit(input) {
88939
89382
  return new FollowerOpenTraceAuditWriter(input.runtimeDir ?? daemonRuntimeDir(), input.nowMs ?? Date.now, input.files ?? privateAuditFiles);
88940
89383
  }
88941
- 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;
88942
89385
  var init_follower_open_trace = __esm({
88943
89386
  "apps/desktop/src/daemon/follower-open-trace.ts"() {
88944
89387
  "use strict";
88945
- init_thread_load_trace();
89388
+ init_thread_load_record();
89389
+ init_thread_load_trace2();
88946
89390
  init_private_log_files();
88947
89391
  init_log_retention();
88948
89392
  init_paths();
@@ -88951,6 +89395,7 @@ var init_follower_open_trace = __esm({
88951
89395
  MAX_TOTAL_BYTES2 = 20 * 1024 * 1024;
88952
89396
  MAX_LINE_BYTES2 = 64 * 1024;
88953
89397
  MAX_DIRECTORY_ENTRIES3 = 256;
89398
+ MAX_PROCESSED_LINES2 = 1e4;
88954
89399
  SUBSCRIPTION_TRACE_SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
88955
89400
  FOLLOWER_OPEN_TRACE_TITLE_MAX_LENGTH = 256;
88956
89401
  TRANSPORTS = /* @__PURE__ */ new Set(["local", "direct", "relay"]);
@@ -89104,6 +89549,30 @@ var init_follower_open_trace = __esm({
89104
89549
  nowMs;
89105
89550
  files;
89106
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
+ }
89107
89576
  traceDirectoryInitialized = false;
89108
89577
  /** 只缓存当前追加文件;日期切换或清空后重新发现已有分片。 */
89109
89578
  appendFilePath;
@@ -89237,6 +89706,7 @@ var init_follower_open_trace = __esm({
89237
89706
  for (const entry of matching) this.files.remove(entry.file);
89238
89707
  }
89239
89708
  this.appendFilePath = void 0;
89709
+ this.loadEpoch += 1;
89240
89710
  this.activeTraces.clear();
89241
89711
  this.activeSubscriptionTraces.clear();
89242
89712
  this.activeSubscriptionTraceHandles.clear();
@@ -89478,21 +89948,21 @@ var init_local_gateway_supervisor = __esm({
89478
89948
 
89479
89949
  // apps/desktop/src/daemon/async-input-notification-store.ts
89480
89950
  import { createHash as createHash41 } from "node:crypto";
89481
- import { z as z17 } from "zod";
89951
+ import { z as z18 } from "zod";
89482
89952
  var ASYNC_INPUT_NOTIFICATION_MAX_BYTES, identityText, entrySchema2, documentSchema2, AsyncInputNotificationStore;
89483
89953
  var init_async_input_notification_store = __esm({
89484
89954
  "apps/desktop/src/daemon/async-input-notification-store.ts"() {
89485
89955
  "use strict";
89486
89956
  init_src2();
89487
89957
  ASYNC_INPUT_NOTIFICATION_MAX_BYTES = 2 * 1024 * 1024;
89488
- identityText = z17.string().min(1).max(8e3);
89489
- entrySchema2 = z17.object({
89490
- 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}$/),
89491
89961
  threadId: identityText,
89492
89962
  turnId: identityText,
89493
- stage: z17.enum(["admitted", "sent"])
89963
+ stage: z18.enum(["admitted", "sent"])
89494
89964
  }).strict();
89495
- 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();
89496
89966
  AsyncInputNotificationStore = class _AsyncInputNotificationStore {
89497
89967
  constructor(journal, document) {
89498
89968
  this.journal = journal;