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.
package/dist/managed.js CHANGED
@@ -8485,9 +8485,42 @@ function parseAttachmentTaskV2StatusQuery(value) {
8485
8485
  return { ...parseAttachmentTaskStatusQuery(normalize(value, true)), version: 2 };
8486
8486
  }
8487
8487
 
8488
+ // packages/protocol/src/thread-load-trace.ts
8489
+ var CLIENT_LOAD_PHASES = ["selected", "restored", "request_started", "snapshot_received", "history_ready", "content_presented"];
8490
+ function object8(value, required, optional = []) {
8491
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) throw new TypeError("invalid_load_report");
8492
+ const descriptors = Object.getOwnPropertyDescriptors(value);
8493
+ 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");
8494
+ return value;
8495
+ }
8496
+ function timing(value) {
8497
+ 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");
8498
+ }
8499
+ function parseThreadLoadReportV2(value) {
8500
+ const v = object8(value, ["version", "thread_id", "load_id", "client_class", "samples"], ["terminal"]);
8501
+ 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");
8502
+ for (const sample of v.samples) {
8503
+ const s = object8(sample, ["phase", "elapsed_ms"], ["occurred_at_ms"]);
8504
+ timing(s);
8505
+ if (!CLIENT_LOAD_PHASES.includes(s.phase)) throw new TypeError("invalid_load_phase");
8506
+ }
8507
+ if (v.terminal !== void 0) {
8508
+ const t = object8(v.terminal, ["kind", "phase", "reason", "elapsed_ms"], ["occurred_at_ms"]);
8509
+ timing(t);
8510
+ const reasons = { failed: ["open_rejected", "history_failed"], cancelled: ["left_view", "user_cancelled"], superseded: ["stream_replaced"] };
8511
+ 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");
8512
+ }
8513
+ return v;
8514
+ }
8515
+ function reduceThreadLoadOutcome(current2, event) {
8516
+ if (event === "closed" || event === "expired") return current2.status === "loading" ? { ...current2, status: "incomplete" } : current2;
8517
+ if (current2.status === "loading" || current2.status === "incomplete" && ["cancelled", "superseded"].includes(event)) return { ...current2, status: event };
8518
+ return current2.status === event ? current2 : { ...current2, conflict: true };
8519
+ }
8520
+
8488
8521
  // apps/desktop/src/release-version.ts
8489
8522
  function desktopReleaseVersion() {
8490
- return false ? null : "0.0.1-beta.28";
8523
+ return false ? null : "0.0.1-beta.29";
8491
8524
  }
8492
8525
  var sourcePresenceVersion = (() => {
8493
8526
  if (true) return null;
@@ -11986,6 +12019,59 @@ function discoverDirectGatewayOrigins(host, port, interfaces = networkInterfaces
11986
12019
  // apps/desktop/src/daemon/launcher.ts
11987
12020
  var BACKGROUND_DAEMON_STARTUP_TIMEOUT_MS = Math.max(6e4, desktopPlatform().daemon.startupTimeoutMs);
11988
12021
 
12022
+ // apps/desktop/src/daemon/runtime/ipc-thread-discovery.ts
12023
+ var RuntimeIpcThreadDiscovery = class {
12024
+ constructor(ports) {
12025
+ this.ports = ports;
12026
+ }
12027
+ ports;
12028
+ pending = /* @__PURE__ */ new Map();
12029
+ discover(id5) {
12030
+ const started2 = Date.now();
12031
+ const report = (phase) => {
12032
+ try {
12033
+ this.ports.diagnostic(id5, phase, Date.now() - started2);
12034
+ } catch {
12035
+ }
12036
+ };
12037
+ if (!this.ports.eligible(id5)) {
12038
+ report("ignored");
12039
+ return;
12040
+ }
12041
+ if (this.pending.has(id5)) {
12042
+ report("deduplicated");
12043
+ return;
12044
+ }
12045
+ if (this.pending.size >= 66) {
12046
+ report("capacity");
12047
+ return;
12048
+ }
12049
+ const current2 = this.ports.capture();
12050
+ report("received");
12051
+ const task = this.run(id5, current2, report).catch(() => report("failed")).finally(() => {
12052
+ if (this.pending.get(id5) === task) this.pending.delete(id5);
12053
+ });
12054
+ this.pending.set(id5, task);
12055
+ }
12056
+ async run(id5, current2, report) {
12057
+ const result = await this.ports.read(id5);
12058
+ report("read_completed");
12059
+ const valid = () => current2() && this.ports.eligible(id5) && result.kind === "ordinary" && result.summary?.id === id5 && !!result.token && this.ports.current(result.token);
12060
+ if (!valid()) {
12061
+ report("rejected");
12062
+ return;
12063
+ }
12064
+ await this.ports.enqueue(() => {
12065
+ if (!valid()) {
12066
+ report("stale");
12067
+ return;
12068
+ }
12069
+ this.ports.commit(result.summary);
12070
+ report("published");
12071
+ });
12072
+ }
12073
+ };
12074
+
11989
12075
  // apps/desktop/src/daemon/async-question-response.ts
11990
12076
  import { createHash } from "node:crypto";
11991
12077
  function submissionId(input) {
@@ -14763,11 +14849,11 @@ var RuntimeActivityPublication = class {
14763
14849
  if (!thread4) {
14764
14850
  return null;
14765
14851
  }
14766
- const timing = this.ports.turnTiming(signal.threadId, signal.turnId);
14767
- const timingFields = timing ? {
14768
- turnStartedAt: timing.startedAt,
14769
- ...timing.endedAt !== void 0 ? { turnEndedAt: timing.endedAt } : {},
14770
- ...timing.outcome !== void 0 ? { turnOutcome: timing.outcome } : {}
14852
+ const timing2 = this.ports.turnTiming(signal.threadId, signal.turnId);
14853
+ const timingFields = timing2 ? {
14854
+ turnStartedAt: timing2.startedAt,
14855
+ ...timing2.endedAt !== void 0 ? { turnEndedAt: timing2.endedAt } : {},
14856
+ ...timing2.outcome !== void 0 ? { turnOutcome: timing2.outcome } : {}
14771
14857
  } : {};
14772
14858
  if (signal.type === "turn_aborted") {
14773
14859
  return {
@@ -20297,7 +20383,7 @@ function fail14(reason) {
20297
20383
  var clone = (value) => structuredClone(value);
20298
20384
  var diskEntry = (entry) => ({ ...entry, nativeToken: entry.nativeToken !== null });
20299
20385
  var byteSize = (value) => Buffer.byteLength(JSON.stringify(value));
20300
- function object8(value, keys3, optional = []) {
20386
+ function object9(value, keys3, optional = []) {
20301
20387
  if (!value || typeof value !== "object" || Array.isArray(value)) return fail14("invalid");
20302
20388
  const record25 = value;
20303
20389
  if (Object.keys(record25).some((key) => !keys3.includes(key) && !optional.includes(key)) || !keys3.every((key) => Object.hasOwn(record25, key))) return fail14("invalid");
@@ -20331,7 +20417,7 @@ function parseReceipt(value, clientMessageId) {
20331
20417
  return receipt;
20332
20418
  }
20333
20419
  function parsePost(value, clientMessageId) {
20334
- const record25 = object8(value, ["httpStatus", "code", "message", "data"]);
20420
+ const record25 = object9(value, ["httpStatus", "code", "message", "data"]);
20335
20421
  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");
20336
20422
  if (record25.code === 0) {
20337
20423
  if (record25.httpStatus !== 200) fail14("invalid");
@@ -20344,10 +20430,10 @@ function parsePost(value, clientMessageId) {
20344
20430
  } else if (record25.data !== null) {
20345
20431
  const data = record25.data;
20346
20432
  if (data.state === "unknown") {
20347
- object8(data, ["client_message_id", "state"]);
20433
+ object9(data, ["client_message_id", "state"]);
20348
20434
  if (data.client_message_id !== clientMessageId) fail14("invalid");
20349
20435
  } else {
20350
- object8(data, ["dispatch_state"]);
20436
+ object9(data, ["dispatch_state"]);
20351
20437
  if (data.dispatch_state !== "not_dispatched") fail14("invalid");
20352
20438
  }
20353
20439
  }
@@ -20380,13 +20466,13 @@ function parseDocument(raw, maxEntries) {
20380
20466
  } catch {
20381
20467
  return fail14("invalid");
20382
20468
  }
20383
- const document = object8(value, ["version", "entries"]);
20469
+ const document = object9(value, ["version", "entries"]);
20384
20470
  if (document.version !== 1 || !Array.isArray(document.entries)) fail14("invalid");
20385
20471
  if (document.entries.length > maxEntries) fail14("capacity");
20386
20472
  const entries = /* @__PURE__ */ new Map();
20387
20473
  const nativeItems = /* @__PURE__ */ new Set();
20388
20474
  for (const value2 of document.entries) {
20389
- const record25 = object8(value2, ["principal", "threadId", "clientMessageId", "fingerprint", "nativeToken", "receipt", "firstResult", "createdAt", "updatedAt"], ["questionId"]);
20475
+ const record25 = object9(value2, ["principal", "threadId", "clientMessageId", "fingerprint", "nativeToken", "receipt", "firstResult", "createdAt", "updatedAt"], ["questionId"]);
20390
20476
  if (typeof record25.nativeToken !== "boolean" || !Number.isSafeInteger(record25.createdAt) || record25.createdAt < 0 || !Number.isSafeInteger(record25.updatedAt) || record25.updatedAt < 0) fail14("invalid");
20391
20477
  const entry = { ...record25, nativeToken: record25.nativeToken ? record25.clientMessageId : null };
20392
20478
  validateReservation(entry);
@@ -20478,7 +20564,7 @@ async function openMessageSubmissionStore(options) {
20478
20564
  reserve(input) {
20479
20565
  const captured = clone(input);
20480
20566
  return enqueue(async () => {
20481
- object8(captured, ["principal", "threadId", "clientMessageId", "fingerprint", "nativeToken"], ["questionId"]);
20567
+ object9(captured, ["principal", "threadId", "clientMessageId", "fingerprint", "nativeToken"], ["questionId"]);
20482
20568
  validateReservation(captured);
20483
20569
  const existing = entries.get(captured.clientMessageId);
20484
20570
  if (existing) {
@@ -23728,8 +23814,8 @@ var AppServerTransport = class {
23728
23814
  /** 慢响应只保留每分钟首条及抑制数;超时和协议失败仍逐次保留。 */
23729
23815
  auditSlowResponse(id5, pending, bytes3, failed) {
23730
23816
  if (!Number.isFinite(pending.startedAtMs)) return;
23731
- const timing = this.requestTiming(pending), now = performance.now();
23732
- if (timing.elapsedMs < 5e3) return;
23817
+ const timing2 = this.requestTiming(pending), now = performance.now();
23818
+ if (timing2.elapsedMs < 5e3) return;
23733
23819
  if (now - this.lastSlowAuditAtMs < 6e4) {
23734
23820
  this.suppressedSlowAudits++;
23735
23821
  return;
@@ -23740,7 +23826,7 @@ var AppServerTransport = class {
23740
23826
  ...typeof id5 === "number" ? { requestId: id5 } : {},
23741
23827
  ...isSafeErrorAuditOperation(pending.method) ? { method: pending.method } : {},
23742
23828
  bytes: bytes3,
23743
- requestTiming: timing,
23829
+ requestTiming: timing2,
23744
23830
  responseDisposition: failed ? "error" : "success",
23745
23831
  suppressedCount: this.suppressedSlowAudits
23746
23832
  });
@@ -28051,7 +28137,7 @@ import { randomBytes as randomBytes3 } from "node:crypto";
28051
28137
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
28052
28138
  function logicalDescriptor(descriptor) {
28053
28139
  if (descriptor.pathname !== "/api/v1/thread/open") return descriptor;
28054
- const { load_id: _loadId, range_resume_token: _rangeToken, ...query2 } = descriptor.query;
28140
+ const { load_id: _loadId, load_trace: _loadTrace, range_resume_token: _rangeToken, ...query2 } = descriptor.query;
28055
28141
  return { pathname: descriptor.pathname, query: query2 };
28056
28142
  }
28057
28143
  function fail15(code, message) {
@@ -30258,8 +30344,7 @@ function invalidPushServerOrigin() {
30258
30344
  function resolvePushServerOrigin(environment) {
30259
30345
  const value = environment.NIUMA_PUSH_SERVER_ORIGIN;
30260
30346
  if (value === void 0 || isBlankConfigValue(value)) {
30261
- const release2 = true;
30262
- return release2 ? DEFAULT_PUSH_PUBLIC_URL : resolveLocalServerOrigin(environment);
30347
+ return DEFAULT_PUSH_PUBLIC_URL;
30263
30348
  }
30264
30349
  if (URL_CONTROL_OR_SPACE_PATTERN.test(value)) {
30265
30350
  throw invalidPushServerOrigin();
@@ -30362,6 +30447,20 @@ var ThreadLoadReportSchema = z8.object({
30362
30447
  load_id: z8.string().uuid(),
30363
30448
  samples: z8.array(LoadSampleSchema).min(1).max(6)
30364
30449
  }).strict();
30450
+ var ThreadLoadReportV2Schema = z8.object({
30451
+ version: z8.literal(2),
30452
+ thread_id: z8.string().min(1).max(256),
30453
+ load_id: z8.string().uuid(),
30454
+ client_class: z8.enum(["web", "ios", "other"]),
30455
+ samples: z8.array(LoadSampleSchema).max(6),
30456
+ 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()
30457
+ }).strict().superRefine((value, context) => {
30458
+ try {
30459
+ parseThreadLoadReportV2(value);
30460
+ } catch {
30461
+ context.addIssue({ code: "custom", message: "\u52A0\u8F7D\u91C7\u6837\u7EC4\u5408\u65E0\u6548" });
30462
+ }
30463
+ });
30365
30464
  var LoadingTraceSchema = z8.object({
30366
30465
  load_id: z8.string().uuid(),
30367
30466
  lease_id: z8.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u),
@@ -30371,27 +30470,128 @@ var LoadingTraceSchema = z8.object({
30371
30470
  occurred_at_ms: occurredAtSchema
30372
30471
  }).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));
30373
30472
  var ThreadLoadReports = class {
30374
- constructor(now = Date.now) {
30473
+ constructor(now = Date.now, audit = () => void 0) {
30375
30474
  this.now = now;
30475
+ this.audit = audit;
30376
30476
  }
30377
30477
  now;
30478
+ audit;
30378
30479
  entries = /* @__PURE__ */ new Map();
30379
- register(loadId, threadId2, owner2, record25) {
30480
+ used = /* @__PURE__ */ new Set();
30481
+ clear() {
30482
+ this.entries.clear();
30483
+ }
30484
+ begin(input) {
30485
+ for (const [key, value] of this.entries) if (value.expires < this.now()) this.entries.delete(key);
30486
+ if (!z8.string().uuid().safeParse(input.loadId).success || this.entries.size >= 256 || this.used.size >= 1e4 || this.used.has(input.loadId)) return null;
30487
+ const id5 = input.loadId;
30488
+ const writer = this.audit();
30489
+ if (writer?.reserveLoadId && !writer.reserveLoadId(id5)) {
30490
+ this.used.add(id5);
30491
+ return null;
30492
+ }
30493
+ this.used.add(id5);
30494
+ const epoch = writer?.loadGeneration?.();
30495
+ let outcome = { status: "loading", conflict: false };
30496
+ let lease, trace;
30497
+ const entry = { threadId: input.threadId, owner: input.owner, expires: input.nowMs + 9e5, seen: /* @__PURE__ */ new Set(), record: () => {
30498
+ }, valid: () => this.entries.get(id5) === entry && writer?.loadGeneration?.() === epoch && this.now() <= entry.expires };
30499
+ const write = (record25) => {
30500
+ if (!entry.valid?.()) return;
30501
+ try {
30502
+ writer?.recordLoad?.({ ...record25, load_id: id5, thread_id: input.threadId, occurred_at_ms: this.now(), ...lease ? { lease_id: lease } : {}, ...trace ? { trace_id: trace } : {} });
30503
+ } catch {
30504
+ }
30505
+ };
30506
+ const end = (event) => {
30507
+ outcome = reduceThreadLoadOutcome(outcome, event);
30508
+ entry.expires = Math.min(entry.expires, this.now() + 3e4);
30509
+ };
30510
+ const elapsed = () => Math.max(0, Math.min(9e5, this.now() - input.nowMs));
30511
+ const handle = {
30512
+ markDaemon: (phase, elapsedMs, occurredAtMs = this.now()) => {
30513
+ if (!entry.valid?.() || entry.seen.has("daemon:" + phase)) return;
30514
+ entry.seen.add("daemon:" + phase);
30515
+ write({ record_type: "load_sample", clock: "daemon", phase, elapsed_ms: elapsedMs, event_at_ms: occurredAtMs });
30516
+ },
30517
+ attachLease: (leaseId, traceId) => {
30518
+ lease = leaseId;
30519
+ trace = traceId;
30520
+ },
30521
+ // 每种确定终态只记一次;保留不同证据供 reducer 标示冲突,不延长授权期限。
30522
+ finish: (kind, evidence = {}) => {
30523
+ if (!entry.valid?.() || entry.seen.has("terminal:" + kind)) return;
30524
+ entry.seen.add("terminal:" + kind);
30525
+ write({ record_type: "load_terminal", clock: "daemon", phase: kind, elapsed_ms: elapsed(), ...evidence });
30526
+ end(kind);
30527
+ },
30528
+ close: () => {
30529
+ if (!entry.valid?.() || entry.seen.has("closed")) return;
30530
+ entry.seen.add("closed");
30531
+ write({ record_type: "load_close", clock: "daemon", phase: "closed", elapsed_ms: elapsed() });
30532
+ end("closed");
30533
+ },
30534
+ outcome: () => outcome
30535
+ };
30536
+ entry.handle = handle;
30537
+ entry.accept = (samples, client, terminal) => {
30538
+ for (const sample of samples) if (!entry.seen.has("client:" + sample.phase)) {
30539
+ entry.seen.add("client:" + sample.phase);
30540
+ 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 } : {} });
30541
+ if (sample.phase === "content_presented") end("presented");
30542
+ try {
30543
+ entry.record(sample);
30544
+ } catch {
30545
+ }
30546
+ }
30547
+ if (terminal && !entry.seen.has("terminal")) {
30548
+ entry.seen.add("terminal");
30549
+ 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 } : {} });
30550
+ end(terminal.kind);
30551
+ }
30552
+ };
30553
+ this.entries.set(id5, entry);
30554
+ write({ record_type: "load_start", clock: "daemon", phase: "open_received", elapsed_ms: 0, source_transport: input.transport });
30555
+ return handle;
30556
+ }
30557
+ register(loadId, threadId2, owner2, record25, attempt) {
30380
30558
  for (const [key, value] of this.entries) if (value.expires < this.now()) this.entries.delete(key);
30381
- if (!z8.string().uuid().safeParse(loadId).success || this.entries.size >= 256 || this.entries.has(loadId)) return () => {
30559
+ const existing = this.entries.get(loadId);
30560
+ if (existing?.handle && existing.handle === attempt && existing.threadId === threadId2 && existing.owner === owner2) {
30561
+ existing.record = record25;
30562
+ return () => {
30563
+ };
30564
+ }
30565
+ if (!z8.string().uuid().safeParse(loadId).success || this.entries.size >= 256 || this.used.size >= 1e4 || this.entries.has(loadId) || this.used.has(loadId)) return () => {
30382
30566
  };
30383
- const entry = { threadId: threadId2, owner: owner2, expires: this.now() + 9e5, record: record25, seen: /* @__PURE__ */ new Set() };
30567
+ const writer = this.audit(), epoch = writer?.loadGeneration?.();
30568
+ if (writer?.reserveLoadId && !writer.reserveLoadId(loadId)) {
30569
+ this.used.add(loadId);
30570
+ return () => {
30571
+ };
30572
+ }
30573
+ this.used.add(loadId);
30574
+ 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 };
30384
30575
  this.entries.set(loadId, entry);
30385
30576
  return () => {
30386
30577
  if (this.entries.get(loadId) === entry) this.entries.delete(loadId);
30387
30578
  };
30388
30579
  }
30389
30580
  report(input, owner2) {
30390
- const parsed = ThreadLoadReportSchema.safeParse(input);
30581
+ let parsed;
30582
+ try {
30583
+ parsed = input?.version === 2 ? { success: true, data: parseThreadLoadReportV2(input) } : ThreadLoadReportSchema.safeParse(input);
30584
+ } catch {
30585
+ parsed = { success: false };
30586
+ }
30391
30587
  if (!parsed.success) return { accepted: false };
30392
30588
  const { load_id, thread_id, samples } = parsed.data;
30393
30589
  const entry = this.entries.get(load_id);
30394
- if (!entry || entry.threadId !== thread_id || entry.owner !== owner2 || entry.expires < this.now()) return { accepted: false };
30590
+ if (!entry || entry.threadId !== thread_id || entry.owner !== owner2 || entry.expires < this.now() || entry.valid && !entry.valid()) return { accepted: false };
30591
+ if (entry.accept) {
30592
+ entry.accept(samples, parsed.data.client_class, parsed.data.terminal);
30593
+ return { accepted: true };
30594
+ }
30395
30595
  for (const sample of samples) if (!entry.seen.has(sample.phase)) {
30396
30596
  entry.seen.add(sample.phase);
30397
30597
  try {
@@ -30618,6 +30818,8 @@ var ThreadOpenQuerySchema = z9.object({
30618
30818
  range_resume_version: z9.literal("1").transform(() => 1).optional(),
30619
30819
  range_resume_token: z9.string().regex(/^[A-Za-z0-9_-]{43}$/).optional(),
30620
30820
  load_id: z9.string().uuid().optional(),
30821
+ // 恢复关联仍保留,只有首屏采样可显式关闭。
30822
+ load_trace: z9.literal("false").transform(() => false).optional(),
30621
30823
  include_execution: z9.literal("true").transform(() => true).optional(),
30622
30824
  include_message_times: z9.literal("true").transform(() => true).optional(),
30623
30825
  include_async_questions: z9.literal("true").transform(() => true).optional(),
@@ -31738,6 +31940,7 @@ function createThreadOpenSseProjection(rawQuery, options = {}) {
31738
31940
  ...query2.range_resume_version ? { rangeResumeVersion: 1 } : {},
31739
31941
  ...query2.range_resume_token ? { rangeResumeToken: query2.range_resume_token } : {},
31740
31942
  ...query2.load_id ? { loadId: query2.load_id } : {},
31943
+ ...query2.load_trace === false ? { loadTrace: false } : {},
31741
31944
  viewVersion: THREAD_VIEW_VERSION,
31742
31945
  ...query2.include_async_questions ? { includeAsyncQuestions: true } : {},
31743
31946
  ...query2.include_read_receipts ? { includeReadReceipts: true } : {},
@@ -32772,7 +32975,7 @@ async function dispatchRestRoute(request2, dependencies) {
32772
32975
  }
32773
32976
  }
32774
32977
  if (route === "POST /api/v1/thread/load/report") {
32775
- const body = ThreadLoadReportSchema.parse(request2.body);
32978
+ const body = request2.body?.version === 2 ? ThreadLoadReportV2Schema.parse(request2.body) : ThreadLoadReportSchema.parse(request2.body);
32776
32979
  const result = await daemonClient.request("thread/load/report", body, {
32777
32980
  ...daemonRequestOptions(5e3, request2.signal),
32778
32981
  ...request2.authenticatedMessageSubmissionScope !== void 0 ? { authenticatedMessageSubmissionScope: request2.authenticatedMessageSubmissionScope } : {}
@@ -43068,8 +43271,8 @@ function createRelayAttachmentDataLink(options) {
43068
43271
  const child = { permit, lease: null, ws: null, receiver: null, closed: false, paired: false, ready: false, opening: null, cleanup: null, unregister: null };
43069
43272
  children.set(permit.transfer_id, child);
43070
43273
  const registrationStarted = Date.now();
43071
- 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 });
43072
- recordAttachmentDiagnostic(options.diagnostic, { phase: "file.register", reason: "started", ...timing() });
43274
+ 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 });
43275
+ recordAttachmentDiagnostic(options.diagnostic, { phase: "file.register", reason: "started", ...timing2() });
43073
43276
  try {
43074
43277
  const reply = await options.control.requestFileControl("file.register", {
43075
43278
  version: 1,
@@ -43081,7 +43284,7 @@ function createRelayAttachmentDataLink(options) {
43081
43284
  max_wire_bytes: attachmentTransferWireBudget(permit.context.byte_size)
43082
43285
  });
43083
43286
  if (reply.code !== 0) {
43084
- recordAttachmentDiagnostic(options.diagnostic, { phase: "file.register", reason: "failed", code: reply.code, ...timing() });
43287
+ recordAttachmentDiagnostic(options.diagnostic, { phase: "file.register", reason: "failed", code: reply.code, ...timing2() });
43085
43288
  const error2 = unavailable7();
43086
43289
  error2.cause = { stage: "file.register", relayCode: reply.code };
43087
43290
  throw error2;
@@ -46646,7 +46849,7 @@ var RuntimeDesktopIngress = class {
46646
46849
  resetMethodValidity() {
46647
46850
  this.ipcMethodInvalidatedThreads.clear();
46648
46851
  }
46649
- async process(frame, ownClientId = this.ports.ipc()?.getClientId?.() ?? null, receivedIpcGeneration = this.ports.ipcConnectionGeneration(), timing, readDiagnostic) {
46852
+ async process(frame, ownClientId = this.ports.ipc()?.getClientId?.() ?? null, receivedIpcGeneration = this.ports.ipcConnectionGeneration(), timing2, readDiagnostic) {
46650
46853
  this.ports.writeDebug({
46651
46854
  component: "daemon",
46652
46855
  channel: "ipc",
@@ -46656,7 +46859,7 @@ var RuntimeDesktopIngress = class {
46656
46859
  attributes: {
46657
46860
  type: frame.type,
46658
46861
  ...typeof frame.method === "string" ? { method: frame.method } : {},
46659
- ...timing
46862
+ ...timing2
46660
46863
  }
46661
46864
  });
46662
46865
  this.ports.writeRawDebug({
@@ -46688,6 +46891,9 @@ var RuntimeDesktopIngress = class {
46688
46891
  }
46689
46892
  const status2 = objectRecord(frame.params);
46690
46893
  const methodThreadId = extractThreadIdFromIpcFrame(frame);
46894
+ 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)) {
46895
+ this.ports.discoverThread(methodThreadId);
46896
+ }
46691
46897
  if (frame.method === "thread-stream-state-changed" && methodThreadId && this.ipcMethodInvalidatedThreads.has(methodThreadId) && objectRecord(status2.change).type !== "snapshot") {
46692
46898
  if (readDiagnostic) readDiagnostic.reason = "baseline_required";
46693
46899
  return;
@@ -47343,19 +47549,19 @@ var DesktopIpcClient = class {
47343
47549
  this.events.on("requestDiagnostic", listener);
47344
47550
  return () => this.events.off("requestDiagnostic", listener);
47345
47551
  }
47346
- emitRequestDiagnostic(timing, phase, outcome) {
47552
+ emitRequestDiagnostic(timing2, phase, outcome) {
47347
47553
  this.events.emit("requestDiagnostic", {
47348
- requestId: timing.requestId,
47349
- method: timing.method,
47350
- threadId: timing.threadId,
47554
+ requestId: timing2.requestId,
47555
+ method: timing2.method,
47556
+ threadId: timing2.threadId,
47351
47557
  phase,
47352
47558
  outcome,
47353
- durationMs: Math.max(0, Math.floor(performance.now() - timing.startedAt))
47559
+ durationMs: Math.max(0, Math.floor(performance.now() - timing2.startedAt))
47354
47560
  });
47355
47561
  }
47356
47562
  pruneTimedOutRequests() {
47357
- for (const [id5, timing] of this.timedOutRequests) {
47358
- if (performance.now() - timing.startedAt > 12e4) this.timedOutRequests.delete(id5);
47563
+ for (const [id5, timing2] of this.timedOutRequests) {
47564
+ if (performance.now() - timing2.startedAt > 12e4) this.timedOutRequests.delete(id5);
47359
47565
  }
47360
47566
  while (this.timedOutRequests.size > 128) this.timedOutRequests.delete(this.timedOutRequests.keys().next().value);
47361
47567
  }
@@ -47492,20 +47698,20 @@ var DesktopIpcClient = class {
47492
47698
  this.pruneTimedOutRequests();
47493
47699
  const localParams = params && typeof params === "object" ? params : {};
47494
47700
  const threadId2 = typeof localParams.threadId === "string" ? localParams.threadId : typeof localParams.conversationId === "string" ? localParams.conversationId : void 0;
47495
- const timing = { requestId, method: method2, threadId: threadId2, startedAt: performance.now() };
47701
+ const timing2 = { requestId, method: method2, threadId: threadId2, startedAt: performance.now() };
47496
47702
  const responsePromise = new Promise((resolve5, reject3) => {
47497
47703
  const timer = setTimeout(() => {
47498
47704
  this.pending.delete(requestId);
47499
- this.timedOutRequests.set(requestId, timing);
47705
+ this.timedOutRequests.set(requestId, timing2);
47500
47706
  this.pruneTimedOutRequests();
47501
- this.emitRequestDiagnostic(timing, "timeout", "unknown");
47707
+ this.emitRequestDiagnostic(timing2, "timeout", "unknown");
47502
47708
  reject3(new NiumaError("UNKNOWN_RESULT", `IPC \u8BF7\u6C42\u8D85\u65F6: ${method2}`, { method: method2 }));
47503
47709
  }, options.timeoutMs ?? this.requestTimeoutMs);
47504
- this.pending.set(requestId, { timer, resolve: resolve5, reject: reject3, timing });
47710
+ this.pending.set(requestId, { timer, resolve: resolve5, reject: reject3, timing: timing2 });
47505
47711
  });
47506
47712
  try {
47507
47713
  await this.write(frame);
47508
- this.emitRequestDiagnostic(timing, "sent", "pending");
47714
+ this.emitRequestDiagnostic(timing2, "sent", "pending");
47509
47715
  } catch (error2) {
47510
47716
  this.clearPending(requestId);
47511
47717
  throw error2 instanceof Error ? new NiumaError("IPC_NOT_INITIALIZED", error2.message) : new NiumaError("IPC_NOT_INITIALIZED", String(error2));
@@ -47604,10 +47810,10 @@ var DesktopIpcClient = class {
47604
47810
  this.pruneTimedOutRequests();
47605
47811
  const outcome = frame.resultType === "error" ? "error" : "success";
47606
47812
  if (!pending) {
47607
- const timing = this.timedOutRequests.get(requestId);
47608
- if (timing) {
47813
+ const timing2 = this.timedOutRequests.get(requestId);
47814
+ if (timing2) {
47609
47815
  this.timedOutRequests.delete(requestId);
47610
- this.emitRequestDiagnostic(timing, "late_response", outcome);
47816
+ this.emitRequestDiagnostic(timing2, "late_response", outcome);
47611
47817
  }
47612
47818
  return;
47613
47819
  }
@@ -56320,15 +56526,46 @@ function createHistoryHandlers(ports) {
56320
56526
  if (negotiated && (!handoffScope || !scope || scope.bindingId !== handoffScope.bindingId)) {
56321
56527
  throw new HttpApiError(200, 200004, "\u6B63\u6587\u4EA4\u63A5\u9700\u8981\u8BA4\u8BC1\u4F1A\u8BDD");
56322
56528
  }
56529
+ const loadStartedAt = performance.now();
56530
+ 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() });
56531
+ const loadElapsed = () => Math.max(0, Math.min(9e5, Math.round(performance.now() - loadStartedAt)));
56532
+ let loadFailurePhase = "open_received";
56533
+ const failOpen = (error2) => {
56534
+ if (!options.signal?.aborted && normalizeHttpError(error2).httpStatus === 200) loadAttempt?.finish("failed", { terminal_phase: loadFailurePhase, reason: "open_rejected" });
56535
+ };
56323
56536
  if (negotiated && typeof params.handoffTicket === "string") {
56324
- if (options.signal?.aborted || socket.destroyed || socket.writableEnded) return STREAMING_RESPONSE;
56325
- const consumer = ports.connectionHandoff().claim(
56326
- handoffScope,
56327
- params.handoffDescriptor,
56328
- params.handoffTicket,
56329
- { emit: (frame) => socket.write(`${JSON.stringify(successEnvelope(frame))}
56330
- `), onClose: () => socket.end() }
56331
- );
56537
+ if (options.signal?.aborted || socket.destroyed || socket.writableEnded) {
56538
+ loadAttempt?.close();
56539
+ return STREAMING_RESPONSE;
56540
+ }
56541
+ let consumer;
56542
+ try {
56543
+ consumer = ports.connectionHandoff().claim(
56544
+ handoffScope,
56545
+ params.handoffDescriptor,
56546
+ params.handoffTicket,
56547
+ { emit: (frame) => {
56548
+ socket.write(`${JSON.stringify(successEnvelope(frame))}
56549
+ `);
56550
+ if (frame.type === "thread_view_snapshot") {
56551
+ loadFailurePhase = "shell_sent";
56552
+ loadAttempt?.markDaemon("shell_sent", loadElapsed());
56553
+ }
56554
+ const view = frame.data;
56555
+ if (view?.availability?.state === "ready") {
56556
+ loadFailurePhase = "history_sent";
56557
+ loadAttempt?.markDaemon("history_sent", loadElapsed());
56558
+ }
56559
+ }, onClose: () => {
56560
+ loadAttempt?.close();
56561
+ socket.end();
56562
+ } }
56563
+ );
56564
+ } catch (error2) {
56565
+ failOpen(error2);
56566
+ loadAttempt?.close();
56567
+ throw error2;
56568
+ }
56332
56569
  let recoveryClosed = false;
56333
56570
  let unregisterRecovery2 = () => {
56334
56571
  };
@@ -56341,12 +56578,14 @@ function createHistoryHandlers(ports) {
56341
56578
  }) : () => {
56342
56579
  };
56343
56580
  } catch (error2) {
56581
+ loadAttempt?.close();
56344
56582
  consumer.close();
56345
56583
  throw error2;
56346
56584
  }
56347
56585
  let unregisterArchive2 = () => {
56348
56586
  };
56349
56587
  const close2 = () => {
56588
+ loadAttempt?.close();
56350
56589
  unregisterArchive2();
56351
56590
  recoveryClosed = true;
56352
56591
  unregisterRecovery2();
@@ -56373,7 +56612,6 @@ function createHistoryHandlers(ports) {
56373
56612
  `);
56374
56613
  return STREAMING_RESPONSE;
56375
56614
  }
56376
- const loadStartedAt = performance.now();
56377
56615
  let loadTrace = null;
56378
56616
  let loadLeaseId = null;
56379
56617
  let unregisterLoad = () => {
@@ -56383,7 +56621,9 @@ function createHistoryHandlers(ports) {
56383
56621
  const pendingLoad = [];
56384
56622
  const loadSeen = /* @__PURE__ */ new Set();
56385
56623
  const recordLoad = (phase, clock = "daemon", elapsed = Math.round(performance.now() - loadStartedAt), occurredAt4 = clock === "daemon" ? Date.now() : void 0) => {
56386
- if (typeof params.loadId !== "string" || elapsed < 0 || elapsed > 9e5) return;
56624
+ if (clock === "daemon" && ["open_received", "identity_ready", "lease_acquired", "shell_sent", "history_sent"].includes(phase)) loadFailurePhase = phase;
56625
+ if (params.loadTrace === false || typeof params.loadId !== "string" || elapsed < 0 || elapsed > 9e5) return;
56626
+ if (clock === "daemon") loadAttempt?.markDaemon(phase, elapsed, occurredAt4);
56387
56627
  if (!loadLeaseId) {
56388
56628
  if (pendingLoad.length < 8) pendingLoad.push({ phase, elapsed, occurredAt: occurredAt4 });
56389
56629
  return;
@@ -56472,6 +56712,7 @@ function createHistoryHandlers(ports) {
56472
56712
  readReceiptTimer = null;
56473
56713
  }
56474
56714
  recordLoad("stream_closed");
56715
+ loadAttempt?.close();
56475
56716
  unregisterLoad();
56476
56717
  unregisterRecovery();
56477
56718
  closed = true;
@@ -56508,6 +56749,7 @@ function createHistoryHandlers(ports) {
56508
56749
  const close = () => {
56509
56750
  if (physicalClosed) return;
56510
56751
  physicalClosed = true;
56752
+ loadAttempt?.close();
56511
56753
  unregisterRecovery();
56512
56754
  options.signal?.removeEventListener("abort", close);
56513
56755
  publisher?.close();
@@ -56524,7 +56766,14 @@ function createHistoryHandlers(ports) {
56524
56766
  return STREAMING_RESPONSE;
56525
56767
  }
56526
56768
  if (!ports.store().getThread(threadId2, { tailCount: 0 })) {
56527
- await ports.verifyThreadForOpen(threadId2);
56769
+ try {
56770
+ await ports.verifyThreadForOpen(threadId2);
56771
+ } catch (error2) {
56772
+ failOpen(error2);
56773
+ loadAttempt?.close();
56774
+ detachClose();
56775
+ throw error2;
56776
+ }
56528
56777
  }
56529
56778
  if (closed || socketIsClosed()) {
56530
56779
  closed = true;
@@ -56573,13 +56822,15 @@ function createHistoryHandlers(ports) {
56573
56822
  }
56574
56823
  for (const sample of pendingLoad) recordLoad(sample.phase, "daemon", sample.elapsed, sample.occurredAt);
56575
56824
  pendingLoad.length = 0;
56825
+ if (loadLeaseId && loadTrace) loadAttempt?.attachLease(loadLeaseId, loadTrace.traceId);
56576
56826
  recordLoad("lease_acquired");
56577
56827
  try {
56578
- if (loadLeaseId && loadTrace) unregisterLoad = ports.threadLoadReports().register(
56828
+ if (loadAttempt && loadLeaseId && loadTrace) unregisterLoad = ports.threadLoadReports().register(
56579
56829
  params.loadId,
56580
56830
  threadId2,
56581
56831
  scope ? `binding:${scope.bindingId.toLowerCase()}` : "local",
56582
- (sample) => recordLoad(sample.phase, "browser", sample.elapsed_ms, sample.occurred_at_ms)
56832
+ (sample) => recordLoad(sample.phase, "browser", sample.elapsed_ms, sample.occurred_at_ms),
56833
+ loadAttempt
56583
56834
  );
56584
56835
  } catch {
56585
56836
  }
@@ -56693,6 +56944,7 @@ function createHistoryHandlers(ports) {
56693
56944
  ports.requestThreadAuthorityRecovery(threadId2, true);
56694
56945
  ports.history().onHistoryLeaseStateChanged(threadId2, ports.activeThreadFollowerCoordinator()?.leaseKindsFor(threadId2) ?? []);
56695
56946
  unsubscribeHistoryWindow = historyWindow.subscribe(() => {
56947
+ if (historyWindow?.initialWindowState === "failed") loadAttempt?.finish("failed", { terminal_phase: "shell_sent", reason: "history_failed" });
56696
56948
  if (rangeClaim && historyWindow?.initialWindowState === "failed") {
56697
56949
  socket.write(`${JSON.stringify(errorEnvelope(new NiumaError("HISTORY_SNAPSHOT_EXPIRED", "\u5386\u53F2\u6062\u590D\u8303\u56F4\u4E0D\u53EF\u7528\u6216\u5DF2\u8FC7\u671F")))}
56698
56950
  `);
@@ -56767,6 +57019,7 @@ function createHistoryHandlers(ports) {
56767
57019
  `);
56768
57020
  }
56769
57021
  } catch (error2) {
57022
+ failOpen(error2);
56770
57023
  try {
56771
57024
  const sites = [
56772
57025
  ["historySourceAssertion", "Object.sourceCurrent"],
@@ -56838,20 +57091,20 @@ function text10(value) {
56838
57091
  return false;
56839
57092
  }
56840
57093
  }
56841
- function object9(value, keys3) {
57094
+ function object10(value, keys3) {
56842
57095
  if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return fail16("invalid");
56843
57096
  const own = Reflect.ownKeys(value);
56844
57097
  if (own.length !== keys3.length || !own.every((key) => typeof key === "string" && keys3.includes(key) && "value" in Object.getOwnPropertyDescriptor(value, key))) return fail16("invalid");
56845
57098
  return value;
56846
57099
  }
56847
57100
  function parseAsyncQuestionBatch(value) {
56848
- const raw = object9(value, ["thread_id", "group_id", "revision", "idempotency_key", "question_ids", "answers"]);
57101
+ const raw = object10(value, ["thread_id", "group_id", "revision", "idempotency_key", "question_ids", "answers"]);
56849
57102
  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");
56850
57103
  const questionIds = Array.from(raw.question_ids);
56851
57104
  if (!questionIds.every(id3) || new Set(questionIds).size !== questionIds.length) return fail16("invalid");
56852
57105
  const seen = /* @__PURE__ */ new Set();
56853
57106
  const answers = Array.from(raw.answers, (value2) => {
56854
- const answer = object9(value2, ["question_id", "title", "answer"]);
57107
+ const answer = object10(value2, ["question_id", "title", "answer"]);
56855
57108
  if (!id3(answer.question_id) || !questionIds.includes(answer.question_id) || seen.has(answer.question_id) || !text10(answer.title) || !text10(answer.answer)) return fail16("invalid");
56856
57109
  seen.add(answer.question_id);
56857
57110
  return { question_id: answer.question_id, title: answer.title, answer: answer.answer };
@@ -59640,10 +59893,10 @@ var ThreadHistoryWindowLoader = class {
59640
59893
  sourceRevision: 0
59641
59894
  };
59642
59895
  for (let attempt = 0; attempt < 2; attempt++) {
59643
- const timing = await this.timestampVersion(turn, signal);
59896
+ const timing2 = await this.timestampVersion(turn, signal);
59644
59897
  this.assertSource(epoch);
59645
59898
  signal.throwIfAborted();
59646
- const versionKey = JSON.stringify([epoch, description.contentVersion, timing]);
59899
+ const versionKey = JSON.stringify([epoch, description.contentVersion, timing2]);
59647
59900
  let cached = this.turns.get(identity6);
59648
59901
  if (cached?.current?.version === versionKey) {
59649
59902
  if (description.sourceRevision >= cached.sourceRevision && cached.nativeOrder !== description.nativeOrder) {
@@ -59660,7 +59913,7 @@ var ThreadHistoryWindowLoader = class {
59660
59913
  this.assertSource(epoch);
59661
59914
  signal.throwIfAborted();
59662
59915
  if (enriched.turnId !== turn.id) throw new RangeError("\u7A97\u53E3\u65F6\u95F4\u8865\u9F50 Turn \u8EAB\u4EFD\u4E0D\u5339\u914D");
59663
- if (await this.timestampVersion(turn, signal) !== timing) continue;
59916
+ if (await this.timestampVersion(turn, signal) !== timing2) continue;
59664
59917
  this.assertSource(epoch);
59665
59918
  signal.throwIfAborted();
59666
59919
  const freeze = (value) => {
@@ -59718,11 +59971,11 @@ var ThreadHistoryWindowLoader = class {
59718
59971
  const epoch = this.sourceEpoch;
59719
59972
  this.currentProtection.addTurnIds([turn.id]);
59720
59973
  const description = this.reader.describeTurn?.(turn) ?? { contentVersion: createHash26("sha256").update(JSON.stringify(turn)).digest("hex") };
59721
- const timing = await this.timestampVersion(turn, signal);
59974
+ const timing2 = await this.timestampVersion(turn, signal);
59722
59975
  this.assertSource(epoch);
59723
59976
  signal.throwIfAborted();
59724
59977
  const cached = this.turns.get(JSON.stringify([this.currentThreadId, turn.id]));
59725
- if (cached?.current?.version === JSON.stringify([epoch, description.contentVersion, timing])) {
59978
+ if (cached?.current?.version === JSON.stringify([epoch, description.contentVersion, timing2])) {
59726
59979
  this.holdVersion(this.currentProtection, cached.current);
59727
59980
  return cached.current.projection.displayItemCount;
59728
59981
  }
@@ -60942,17 +61195,17 @@ var AppServerThreadViewAdapter = class {
60942
61195
  lastError: null
60943
61196
  }).status;
60944
61197
  const terminalStatus = failed || aborted || status2 === "completed" ? lifecycleStatus : "completed";
60945
- const timing = context.getTurnLifecycle?.(turnId);
61198
+ const timing2 = context.getTurnLifecycle?.(turnId);
60946
61199
  return [{
60947
61200
  type: "ThreadStatusChanged",
60948
61201
  thread_id: context.threadId,
60949
61202
  status: terminalStatus,
60950
61203
  turn_id: turnId,
60951
61204
  terminal_item_state: terminalStatus === "error" ? "failed" : "completed",
60952
- ...timing ? { process_lifecycle: {
60953
- startedAt: lifecycleTime(timing.startedAt),
60954
- endedAt: lifecycleTime(timing.endedAt),
60955
- outcome: timing.outcome
61205
+ ...timing2 ? { process_lifecycle: {
61206
+ startedAt: lifecycleTime(timing2.startedAt),
61207
+ endedAt: lifecycleTime(timing2.endedAt),
61208
+ outcome: timing2.outcome
60956
61209
  } } : {},
60957
61210
  last_error: terminalStatus === "error" ? {
60958
61211
  code: 900001,
@@ -61207,10 +61460,10 @@ var ThreadHistoryWindowProjection = class {
61207
61460
  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);
61208
61461
  const timingById = new Map(sources.map((source2, index) => [source2.id, enriched[index]]));
61209
61462
  const items = projected.items.map((item) => {
61210
- const timing = timingById.get(item.id);
61463
+ const timing2 = timingById.get(item.id);
61211
61464
  const fact = item.process_fact;
61212
- if (!timing || !fact) return item;
61213
- const merged = mergeThreadHistoryTimelineLifecycle(timing, {
61465
+ if (!timing2 || !fact) return item;
61466
+ const merged = mergeThreadHistoryTimelineLifecycle(timing2, {
61214
61467
  ...fact.startedAt !== null ? { turnStartedAt: fact.startedAt } : {},
61215
61468
  ...fact.endedAt !== null ? { turnEndedAt: fact.endedAt } : {},
61216
61469
  ...fact.durationMs != null ? { turnDurationMs: fact.durationMs } : {},
@@ -62059,7 +62312,7 @@ function readStateDiagnostic(before, after, candidate = after.confirmed) {
62059
62312
  sameCycle: candidate ? candidate.cycle === after.cycle : null
62060
62313
  };
62061
62314
  }
62062
- function object10(value) {
62315
+ function object11(value) {
62063
62316
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
62064
62317
  }
62065
62318
  function field(value, present, count) {
@@ -62069,15 +62322,15 @@ function field(value, present, count) {
62069
62322
  }
62070
62323
  function unreadFrameDiagnostic(frame) {
62071
62324
  if (frame.method !== "thread-stream-state-changed") return null;
62072
- const params = object10(frame.params), change = object10(params.change);
62325
+ const params = object11(frame.params), change = object11(params.change);
62073
62326
  const kind = change.type === "snapshot" ? "snapshot" : change.type === "patches" ? "patch" : "invalid";
62074
- const values = kind === "snapshot" ? object10(change.conversationState) : {};
62327
+ const values = kind === "snapshot" ? object11(change.conversationState) : {};
62075
62328
  let unread = field(values.hasUnreadTurn, Object.hasOwn(values, "hasUnreadTurn"), false);
62076
62329
  let count = field(values.unreadMessageCount, Object.hasOwn(values, "unreadMessageCount"), true);
62077
62330
  let countValue = values.unreadMessageCount;
62078
62331
  if (kind === "patch") {
62079
62332
  for (const value of Array.isArray(change.patches) ? change.patches : []) {
62080
- const patch = object10(value), path54 = Array.isArray(patch.path) ? patch.path : [];
62333
+ const patch = object11(value), path54 = Array.isArray(patch.path) ? patch.path : [];
62081
62334
  const name2 = path54.length === 1 ? path54[0] : path54.length === 2 && path54[0] === "conversationState" ? path54[1] : null;
62082
62335
  if (name2 !== "hasUnreadTurn" && name2 !== "unreadMessageCount") continue;
62083
62336
  const state = patch.op === "remove" ? "removed" : ["add", "replace"].includes(String(patch.op)) ? field(patch.value, Object.hasOwn(patch, "value"), name2 === "unreadMessageCount") : "invalid";
@@ -62119,10 +62372,10 @@ var RuntimeIngressDispatch = class {
62119
62372
  }
62120
62373
  ports;
62121
62374
  /** 单帧入站与最终结果成对记录;原始字段与业务结果分开,拒绝帧同样留证。 */
62122
- async handleIpcFrame(frame, ownClientId = this.ports.desktopSession().ipcValue?.getClientId?.() ?? null, receivedIpcGeneration = this.ports.desktopSession().ipcConnectionGenerationValue, timing) {
62375
+ async handleIpcFrame(frame, ownClientId = this.ports.desktopSession().ipcValue?.getClientId?.() ?? null, receivedIpcGeneration = this.ports.desktopSession().ipcConnectionGenerationValue, timing2) {
62123
62376
  const attributes = this.ports.options().debugLogger ? unreadFrameDiagnostic(frame) : null;
62124
62377
  const threadId2 = attributes ? extractThreadIdFromIpcFrame(frame) : null;
62125
- if (!attributes || !threadId2) return this.ports.desktopIngress().process(frame, ownClientId, receivedIpcGeneration, timing);
62378
+ if (!attributes || !threadId2) return this.ports.desktopIngress().process(frame, ownClientId, receivedIpcGeneration, timing2);
62126
62379
  const before = this.ports.catalog().storeValue.getThread(threadId2, { tailCount: 0 })?.unread;
62127
62380
  const diagnostic = { reason: "processed" };
62128
62381
  const metadata = { ...attributes, sequence: this.ports.diagnostics().threadReadDiagnosticSequenceValue++, generation: receivedIpcGeneration, currentGeneration: this.ports.desktopSession().ipcConnectionGenerationValue };
@@ -62135,7 +62388,7 @@ var RuntimeIngressDispatch = class {
62135
62388
  }
62136
62389
  });
62137
62390
  try {
62138
- await this.ports.desktopIngress().process(frame, ownClientId, receivedIpcGeneration, timing, diagnostic);
62391
+ await this.ports.desktopIngress().process(frame, ownClientId, receivedIpcGeneration, timing2, diagnostic);
62139
62392
  } catch (error2) {
62140
62393
  diagnostic.reason = "processing_failed";
62141
62394
  throw error2;
@@ -63863,11 +64116,11 @@ var RuntimeProjectionContext = class {
63863
64116
  } : null),
63864
64117
  enrichTimelineItemTiming: (item) => this.ports.timelinePublication().enrichTimelineItemTiming(item, timelineItems),
63865
64118
  getTurnLifecycle: (turnId) => {
63866
- const timing = this.ports.timelinePublication().turnTiming(threadId2, turnId);
63867
- return timing ? {
63868
- startedAt: timing.startedAt,
63869
- endedAt: timing.endedAt ?? null,
63870
- outcome: timing.outcome ?? null
64119
+ const timing2 = this.ports.timelinePublication().turnTiming(threadId2, turnId);
64120
+ return timing2 ? {
64121
+ startedAt: timing2.startedAt,
64122
+ endedAt: timing2.endedAt ?? null,
64123
+ outcome: timing2.outcome ?? null
63871
64124
  } : null;
63872
64125
  },
63873
64126
  resolveKnownLocalImage: (sourcePath, itemId, partIndex) => this.ports.resolveThreadViewImage(threadId2, sourcePath, () => this.ports.isCurrentAppServerAsyncFence(threadId2, fence), itemId, partIndex)
@@ -64396,7 +64649,7 @@ var DesktopThreadSettingsTracker = class {
64396
64649
 
64397
64650
  // apps/desktop/src/daemon/active-thread-follower-coordinator.ts
64398
64651
  init_session_history_file();
64399
- var ACTIVE_THREAD_SCAN_INTERVAL_MS = 1e4;
64652
+ var ACTIVE_THREAD_SCAN_INTERVAL_MS = 6e4;
64400
64653
  var ACTIVE_THREAD_READ_CONCURRENCY = 4;
64401
64654
  var ACTIVE_FOLLOWER_SNAPSHOT_TIMEOUT_MS = 12e3;
64402
64655
  var ACTIVE_FOLLOWER_RETRY_DELAYS_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
@@ -71378,7 +71631,7 @@ var RuntimeThreadStateCoordination = class {
71378
71631
  return token3;
71379
71632
  }
71380
71633
  /** 仅供已核验的同轮终态使用;同时收敛列表与详情,不伪造新的完成通知。 */
71381
- applyRecoveredTerminal(threadId2, turnId, outcome, occurredAt4, message, timing) {
71634
+ applyRecoveredTerminal(threadId2, turnId, outcome, occurredAt4, message, timing2) {
71382
71635
  const previous = this.ports.catalog().storeValue.getThread(threadId2, { tailCount: 0 });
71383
71636
  const mutation = this.ports.catalog().storeValue.applyActivitySignal({
71384
71637
  type: "snapshot",
@@ -71398,7 +71651,7 @@ var RuntimeThreadStateCoordination = class {
71398
71651
  turn_id: turnId,
71399
71652
  status: outcome === "aborted" ? "idle" : outcome === "failed" ? "error" : "completed",
71400
71653
  terminal_item_state: outcome === "failed" ? "failed" : "completed",
71401
- process_lifecycle: { startedAt: timing?.startedAt ?? null, endedAt: timing?.endedAt ?? null, outcome },
71654
+ process_lifecycle: { startedAt: timing2?.startedAt ?? null, endedAt: timing2?.endedAt ?? null, outcome },
71402
71655
  last_error: outcome === "failed" && message ? { code: 900001, message, occurred_at: occurredAt4, turn_id: turnId } : null
71403
71656
  },
71404
71657
  { type: "ThreadActivityChanged", thread_id: threadId2, turn_id: turnId, activity: null },
@@ -71637,15 +71890,15 @@ var RuntimeTimelinePublication = class {
71637
71890
  if (!item.turnId) {
71638
71891
  return item;
71639
71892
  }
71640
- const timing = this.liveTurnTimings.get(this.turnTimingKey(item.threadId, item.turnId));
71641
- if (!timing) {
71893
+ const timing2 = this.liveTurnTimings.get(this.turnTimingKey(item.threadId, item.turnId));
71894
+ if (!timing2) {
71642
71895
  return item;
71643
71896
  }
71644
71897
  return {
71645
71898
  ...item,
71646
- ...item.turnStartedAt === void 0 ? { turnStartedAt: timing.startedAt } : {},
71647
- ...item.turnEndedAt === void 0 && timing.endedAt !== void 0 ? { turnEndedAt: timing.endedAt } : {},
71648
- ...item.turnOutcome === void 0 && timing.outcome !== void 0 ? { turnOutcome: timing.outcome } : {}
71899
+ ...item.turnStartedAt === void 0 ? { turnStartedAt: timing2.startedAt } : {},
71900
+ ...item.turnEndedAt === void 0 && timing2.endedAt !== void 0 ? { turnEndedAt: timing2.endedAt } : {},
71901
+ ...item.turnOutcome === void 0 && timing2.outcome !== void 0 ? { turnOutcome: timing2.outcome } : {}
71649
71902
  };
71650
71903
  }
71651
71904
  enrichTimelineItemTiming(item, historyItems = []) {
@@ -71665,9 +71918,9 @@ var RuntimeTimelinePublication = class {
71665
71918
  turnTimingKey(threadId2, turnId) {
71666
71919
  return `${threadId2}:${turnId}`;
71667
71920
  }
71668
- setLiveTurnTiming(key, timing) {
71921
+ setLiveTurnTiming(key, timing2) {
71669
71922
  this.liveTurnTimings.delete(key);
71670
- this.liveTurnTimings.set(key, timing);
71923
+ this.liveTurnTimings.set(key, timing2);
71671
71924
  while (this.liveTurnTimings.size > LIVE_TURN_TIMING_LIMIT) {
71672
71925
  const oldestKey = this.liveTurnTimings.keys().next().value;
71673
71926
  if (typeof oldestKey !== "string") break;
@@ -79790,8 +80043,7 @@ var RuntimeNativeAdmission = class {
79790
80043
  else if (kind === "ordinary") this.hidden.delete(thread4.id);
79791
80044
  const old = this.entries.get(thread4.id);
79792
80045
  this.failures.delete(thread4.id);
79793
- if (old && old.kind === kind) return old.token;
79794
- const token3 = { threadId: thread4.id, generation: ++this.sequence, executionGeneration };
80046
+ const token3 = old && old.kind === kind ? old.token : { threadId: thread4.id, generation: ++this.sequence, executionGeneration };
79795
80047
  this.entries.set(thread4.id, { token: token3, kind });
79796
80048
  try {
79797
80049
  if (kind !== previous) this.ports.onChange?.(thread4.id, previous, kind);
@@ -79801,7 +80053,7 @@ var RuntimeNativeAdmission = class {
79801
80053
  }
79802
80054
  const pending = this.jobs.get(thread4.id);
79803
80055
  if (pending) {
79804
- this.finishWait(pending, kind);
80056
+ this.finishWait(pending, kind, thread4, token3);
79805
80057
  if (!pending.started) {
79806
80058
  this.removeQueued(pending);
79807
80059
  this.removeJob(pending);
@@ -79809,13 +80061,20 @@ var RuntimeNativeAdmission = class {
79809
80061
  }
79810
80062
  return token3;
79811
80063
  }
79812
- ensure(id5, executionGeneration) {
79813
- if (executionGeneration !== this.execution) return Promise.resolve("unknown");
80064
+ async ensure(id5, executionGeneration) {
80065
+ return (await this.query(id5, executionGeneration, false)).kind;
80066
+ }
80067
+ /** 目录缺项时请求摘要,与未知准入共用单飞、退避和物理槽。 */
80068
+ ensureSummary(id5, executionGeneration) {
80069
+ return this.query(id5, executionGeneration, true);
80070
+ }
80071
+ query(id5, executionGeneration, needSummary) {
80072
+ if (executionGeneration !== this.execution) return Promise.resolve({ kind: "unknown" });
79814
80073
  const known = this.kind(id5);
79815
- if (known !== "unknown") return Promise.resolve(known);
80074
+ if (known !== "unknown" && (!needSummary || known === "subagent")) return Promise.resolve({ kind: known });
79816
80075
  const pending = this.jobs.get(id5);
79817
- if (pending) return pending.execution === this.execution ? pending.promise : Promise.resolve("unknown");
79818
- if ((this.failures.get(id5) ?? 0) > this.now() || this.queue.length >= 64) return Promise.resolve("unknown");
80076
+ if (pending) return pending.execution === this.execution ? pending.promise : Promise.resolve({ kind: "unknown" });
80077
+ if ((this.failures.get(id5) ?? 0) > this.now() || this.queue.length >= 64) return Promise.resolve({ kind: "unknown" });
79819
80078
  let resolve5;
79820
80079
  const promise = new Promise((done) => {
79821
80080
  resolve5 = done;
@@ -79872,11 +80131,11 @@ var RuntimeNativeAdmission = class {
79872
80131
  removeJob(job) {
79873
80132
  if (this.jobs.get(job.id) === job) this.jobs.delete(job.id);
79874
80133
  }
79875
- finishWait(job, kind) {
80134
+ finishWait(job, kind, summary, token3) {
79876
80135
  if (job.settled) return;
79877
80136
  job.settled = true;
79878
80137
  job.cancel();
79879
- job.resolve(kind);
80138
+ job.resolve({ kind, ...summary ? { summary, token: token3 } : {} });
79880
80139
  }
79881
80140
  fail(job) {
79882
80141
  if (job.execution === this.execution) {
@@ -79910,7 +80169,7 @@ var RuntimeNativeAdmission = class {
79910
80169
  this.observe(result.thread, job.execution);
79911
80170
  const kind = this.kind(job.id);
79912
80171
  if (kind === "unknown") this.fail(job);
79913
- this.finishWait(job, kind);
80172
+ this.finishWait(job, kind, result.thread, this.token(job.id));
79914
80173
  }).catch(() => {
79915
80174
  if (!job.settled) {
79916
80175
  this.fail(job);
@@ -80273,6 +80532,7 @@ var DEBUG_EVENTS = [
80273
80532
  "ipc_recovery",
80274
80533
  "ipc_forwarding_flow",
80275
80534
  "notification_delivery",
80535
+ "ipc_thread_discovery",
80276
80536
  "startup_timing",
80277
80537
  "thread_settings_cache",
80278
80538
  "thread_list_cache",
@@ -80506,6 +80766,7 @@ var SAFE_ATTRIBUTE_KEYS = {
80506
80766
  "inFlight"
80507
80767
  ],
80508
80768
  // 启动性能仅保留固定阶段耗时和汇总计数,禁止记录路径、会话或异常原文。
80769
+ ipc_thread_discovery: ["durationMs"],
80509
80770
  startup_timing: [
80510
80771
  "durationMs",
80511
80772
  "threadCount",
@@ -81055,6 +81316,7 @@ var SAFE_PHASES = {
81055
81316
  "dropped_busy",
81056
81317
  "deadline_expired"
81057
81318
  ],
81319
+ ipc_thread_discovery: ["received", "ignored", "deduplicated", "capacity", "read_completed", "rejected", "stale", "published", "failed"],
81058
81320
  startup_timing: [
81059
81321
  "app_server_initialized",
81060
81322
  "thread_baseline_scanned",
@@ -81748,19 +82010,19 @@ function withoutPersistentImageBytes(value, key = "", seen = /* @__PURE__ */ new
81748
82010
  if (seen.has(value)) return "[circular]";
81749
82011
  seen.add(value);
81750
82012
  try {
81751
- const object11 = value;
81752
- if (object11.type === "Buffer" && Array.isArray(object11.data)) {
81753
- return { type: "binary_omitted", byteSize: object11.data.length };
82013
+ const object12 = value;
82014
+ if (object12.type === "Buffer" && Array.isArray(object12.data)) {
82015
+ return { type: "binary_omitted", byteSize: object12.data.length };
81754
82016
  }
81755
- if (object11.event === "app_server_stdout") {
81756
- const payload = object11.rawPayload;
81757
- const metadata = Object.fromEntries(Object.entries(object11).filter(([name2]) => name2 !== "rawPayload").map(([name2, entry]) => [name2, withoutPersistentImageBytes(entry, name2, seen)]));
82017
+ if (object12.event === "app_server_stdout") {
82018
+ const payload = object12.rawPayload;
82019
+ const metadata = Object.fromEntries(Object.entries(object12).filter(([name2]) => name2 !== "rawPayload").map(([name2, entry]) => [name2, withoutPersistentImageBytes(entry, name2, seen)]));
81758
82020
  return { ...metadata, rawPayload: { type: "stdout_fragment", bytes: typeof payload?.bytes === "number" ? payload.bytes : typeof payload?.fragment === "string" ? Buffer.byteLength(payload.fragment) : 0 } };
81759
82021
  }
81760
82022
  if (Array.isArray(value)) return value.map((entry) => withoutPersistentImageBytes(entry, key, seen));
81761
- return Object.fromEntries(Object.entries(object11).map(([name2, entry]) => [
82023
+ return Object.fromEntries(Object.entries(object12).map(([name2, entry]) => [
81762
82024
  name2,
81763
- withoutPersistentImageBytes(entry, object11.type === "image" && name2 === "data" ? "base64" : name2, seen)
82025
+ withoutPersistentImageBytes(entry, object12.type === "image" && name2 === "data" ? "base64" : name2, seen)
81764
82026
  ]));
81765
82027
  } finally {
81766
82028
  seen.delete(value);
@@ -82854,6 +83116,7 @@ var NiumaDaemonRuntime = class {
82854
83116
  }
82855
83117
  desktopTrackers = new RuntimeDesktopIngressTrackers();
82856
83118
  desktopIngress = new RuntimeDesktopIngress({
83119
+ discoverThread: (id5) => this.ipcThreadDiscovery.discover(id5),
82857
83120
  nativeAdmission: () => this.nativeAdmission,
82858
83121
  ensureNativeAdmission: (id5) => this.ensureNativeAdmission(id5),
82859
83122
  requestReadFileRefresh: () => this.followerCoordination.desktopReadStateFileValue?.requestRefresh(),
@@ -83389,6 +83652,33 @@ var NiumaDaemonRuntime = class {
83389
83652
  }
83390
83653
  }
83391
83654
  });
83655
+ ipcThreadDiscovery = new RuntimeIpcThreadDiscovery({
83656
+ 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),
83657
+ capture: () => {
83658
+ const runtime = this.lifecycle.runtimeGenerationValue, ipc = this.desktopSession.ipcConnectionGenerationValue;
83659
+ const client = this.executionHost.appServerValue, generation = this.appServerLoads.connectionGeneration;
83660
+ return () => !this.lifecycle.stoppingValue && runtime === this.lifecycle.runtimeGenerationValue && ipc === this.desktopSession.ipcConnectionGenerationValue && client === this.executionHost.appServerValue && generation === this.appServerLoads.connectionGeneration;
83661
+ },
83662
+ read: (id5) => this.nativeAdmission.ensureSummary(id5, this.appServerLoads.connectionGeneration),
83663
+ current: (token3) => this.nativeAdmission.isCurrent(token3),
83664
+ enqueue: (run) => this.eventSequencer.enqueue(async () => {
83665
+ run();
83666
+ }),
83667
+ commit: (thread4) => {
83668
+ this.directoryAuthority.begin(false);
83669
+ this.catalog.upsertAuthoritativeThreadSummary(thread4);
83670
+ this.emitEvent(this.publication.threadUpdatedEvent(thread4.id, "app_server", nowSeconds()));
83671
+ this.activityPublication.publishGlobalStatusIfChanged();
83672
+ },
83673
+ diagnostic: (threadId2, phase, elapsedMs) => this.diagnostics.writeDebug({
83674
+ component: "daemon",
83675
+ channel: "ipc",
83676
+ event: "ipc_thread_discovery",
83677
+ threadId: threadId2,
83678
+ phase,
83679
+ attributes: { durationMs: elapsedMs }
83680
+ })
83681
+ });
83392
83682
  async ensureNativeAdmission(id5) {
83393
83683
  return !this.lifecycle.stoppingValue && !this.archiveState.isConfirmed(id5) && await this.nativeAdmission.ensure(id5, this.appServerLoads.connectionGeneration) === "ordinary";
83394
83684
  }
@@ -84075,7 +84365,7 @@ var NiumaDaemonRuntime = class {
84075
84365
  isBlocked: isStartBlockedByLifecycle
84076
84366
  });
84077
84367
  // 所有 runtime 与 coordinator 共用同一审计实例,确保 thread-scope 事件可关联当前 trace。
84078
- threadLoadReports = new ThreadLoadReports();
84368
+ threadLoadReports = new ThreadLoadReports(Date.now, () => this.followerOpenTraceAudit);
84079
84369
  followerOpenTraceAudit;
84080
84370
  // 异步选择只等待目录;端口在实际调用时读取,不能提前冻结Owner或缓存。
84081
84371
  settingsSelection = new RuntimeSettingsSelection({
@@ -84413,6 +84703,34 @@ var NiumaDaemonRuntime = class {
84413
84703
  });
84414
84704
  };
84415
84705
 
84706
+ // apps/desktop/src/daemon/thread-load-record.ts
84707
+ import { z as z17 } from "zod";
84708
+ var LoadRecordSchema = z17.object({
84709
+ schema_version: z17.literal(3),
84710
+ record_type: z17.enum(["load_start", "load_sample", "load_terminal", "load_close"]),
84711
+ load_id: z17.string().uuid(),
84712
+ thread_id: z17.string().min(1).max(256),
84713
+ sequence: z17.number().int().nonnegative(),
84714
+ occurred_at_ms: z17.number().int().nonnegative().max(864e13),
84715
+ clock: z17.enum(["client", "daemon"]),
84716
+ phase: z17.enum([...CLIENT_LOAD_PHASES, "open_received", "identity_ready", "lease_acquired", "shell_sent", "history_sent", "stream_closed", "failed", "cancelled", "superseded", "closed", "expired"]),
84717
+ elapsed_ms: z17.number().int().min(0).max(9e5),
84718
+ event_at_ms: z17.number().int().min(1).max(864e13).optional(),
84719
+ source_transport: z17.enum(["local", "direct", "relay"]).optional(),
84720
+ client_class: z17.enum(["web", "ios", "other"]).optional(),
84721
+ title: z17.string().max(256).optional(),
84722
+ lease_id: z17.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u).optional(),
84723
+ trace_id: z17.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u).optional(),
84724
+ terminal_phase: z17.enum(["request_started", "snapshot_received", "history_ready", "open_received", "identity_ready", "lease_acquired", "shell_sent", "history_sent"]).optional(),
84725
+ reason: z17.enum(["open_rejected", "history_failed", "left_view", "user_cancelled", "stream_replaced"]).optional()
84726
+ }).strict().superRefine((v, ctx) => {
84727
+ const fail18 = () => ctx.addIssue({ code: "custom", message: "invalid_load_record" });
84728
+ if (v.record_type === "load_start" && (v.phase !== "open_received" || v.clock !== "daemon" || !v.source_transport)) fail18();
84729
+ if (v.record_type === "load_terminal" && !["failed", "cancelled", "superseded", "expired"].includes(v.phase)) fail18();
84730
+ if (v.record_type === "load_close" && (v.phase !== "closed" || v.clock !== "daemon")) fail18();
84731
+ 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();
84732
+ });
84733
+
84416
84734
  // apps/desktop/src/daemon/follower-open-trace.ts
84417
84735
  import { randomUUID as randomUUID52 } from "node:crypto";
84418
84736
  import path51 from "node:path";
@@ -84421,6 +84739,7 @@ var MAX_FILE_BYTES2 = 5 * 1024 * 1024;
84421
84739
  var MAX_TOTAL_BYTES2 = 20 * 1024 * 1024;
84422
84740
  var MAX_LINE_BYTES2 = 64 * 1024;
84423
84741
  var MAX_DIRECTORY_ENTRIES3 = 256;
84742
+ var MAX_PROCESSED_LINES2 = 1e4;
84424
84743
  var SUBSCRIPTION_TRACE_SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
84425
84744
  var FOLLOWER_OPEN_TRACE_TITLE_MAX_LENGTH = 256;
84426
84745
  var TRANSPORTS = /* @__PURE__ */ new Set(["local", "direct", "relay"]);
@@ -84582,6 +84901,9 @@ function isOptionalTraceTitle(value) {
84582
84901
  function hasOnlyKeys2(value, allowed) {
84583
84902
  return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).every((key) => allowed.includes(key));
84584
84903
  }
84904
+ function isRecent(value, nowMs) {
84905
+ return value <= nowMs && nowMs - value <= DAEMON_LOG_RETENTION_MS;
84906
+ }
84585
84907
  function dateFileName2(timestampMs) {
84586
84908
  return `${new Date(timestampMs).toISOString().slice(0, 10)}.ndjson`;
84587
84909
  }
@@ -84688,6 +85010,10 @@ function parseRecord2(line) {
84688
85010
  }
84689
85011
  if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
84690
85012
  const record25 = value;
85013
+ if (record25.schema_version === 3) {
85014
+ const parsed = LoadRecordSchema.safeParse(record25);
85015
+ return parsed.success ? parsed.data : null;
85016
+ }
84691
85017
  if (record25.schema_version === 1) return parseV1Record(record25);
84692
85018
  if (record25.schema_version === 2) return parseV2Record(record25);
84693
85019
  return null;
@@ -84723,6 +85049,46 @@ function readPrivateTraceFile(file, remainingBytes, storage) {
84723
85049
  return { bytesRead: error2 instanceof PrivateLogFileError ? error2.bytesRead : 0, incomplete: true };
84724
85050
  }
84725
85051
  }
85052
+ function readRecords(runtimeDir, nowMs, storage) {
85053
+ if (!Number.isFinite(nowMs)) return { records: [], incomplete: true };
85054
+ const candidates = listPrivateTraceFiles(runtimeDir, storage);
85055
+ const records = [];
85056
+ let incomplete = candidates.incomplete;
85057
+ let totalBytes = 0;
85058
+ let processedLines = 0;
85059
+ for (const filePath of candidates.files) {
85060
+ if (totalBytes >= MAX_TOTAL_BYTES2) {
85061
+ incomplete = true;
85062
+ break;
85063
+ }
85064
+ const file = readPrivateTraceFile(filePath, MAX_TOTAL_BYTES2 - totalBytes, storage);
85065
+ totalBytes += file.bytesRead;
85066
+ if (file.incomplete) incomplete = true;
85067
+ if (file.content === void 0) continue;
85068
+ let end = file.content.endsWith("\n") ? file.content.length - 1 : file.content.length;
85069
+ while (end > 0 && processedLines < MAX_PROCESSED_LINES2) {
85070
+ const newline = file.content.lastIndexOf("\n", end - 1);
85071
+ const start = newline + 1;
85072
+ const length = end - start;
85073
+ processedLines += 1;
85074
+ if (length > 0 && length <= MAX_LINE_BYTES2) {
85075
+ const line = file.content.slice(start, end);
85076
+ if (Buffer.byteLength(line, "utf8") <= MAX_LINE_BYTES2) {
85077
+ const record25 = parseRecord2(line);
85078
+ if (record25 && timestampOf(record25) <= nowMs) records.push(record25);
85079
+ else incomplete = true;
85080
+ } else incomplete = true;
85081
+ } else if (length > 0) incomplete = true;
85082
+ end = Math.max(0, newline);
85083
+ }
85084
+ if (totalBytes >= MAX_TOTAL_BYTES2 || processedLines >= MAX_PROCESSED_LINES2) {
85085
+ if (end > 0 || filePath !== candidates.files.at(-1)) incomplete = true;
85086
+ break;
85087
+ }
85088
+ }
85089
+ records.reverse();
85090
+ return { records, incomplete: incomplete || hasIncompleteSubscriptionHistory(records, nowMs) };
85091
+ }
84726
85092
  function readPersistedSequenceFloor(runtimeDir, storage) {
84727
85093
  let maximum = 0;
84728
85094
  let totalBytes = 0;
@@ -84741,6 +85107,56 @@ function readPersistedSequenceFloor(runtimeDir, storage) {
84741
85107
  }
84742
85108
  return maximum;
84743
85109
  }
85110
+ function subscriptionIdentity(record25) {
85111
+ return `${record25.trace_id}\0${record25.thread_id}\0${record25.lease_epoch}`;
85112
+ }
85113
+ function occursAfterStart(record25, trace) {
85114
+ return record25.occurred_at_ms > trace.opened_at_ms || record25.occurred_at_ms === trace.opened_at_ms && record25.sequence > trace.sequence;
85115
+ }
85116
+ function occursBeforeClose(record25, close) {
85117
+ return record25.occurred_at_ms < close.occurred_at_ms || record25.occurred_at_ms === close.occurred_at_ms && record25.sequence < close.sequence;
85118
+ }
85119
+ function hasIncompleteSubscriptionHistory(records, nowMs) {
85120
+ const starts = /* @__PURE__ */ new Map();
85121
+ const eventsByIdentity = /* @__PURE__ */ new Map();
85122
+ const closesByIdentity = /* @__PURE__ */ new Map();
85123
+ for (const record25 of records) {
85124
+ if (record25.record_type === "subscription_start") {
85125
+ const identity6 = subscriptionIdentity(record25);
85126
+ if (starts.has(identity6)) return true;
85127
+ starts.set(identity6, record25);
85128
+ continue;
85129
+ }
85130
+ if (record25.record_type === "lease_event" || record25.record_type === "subscription_event") {
85131
+ const identity6 = subscriptionIdentity(record25);
85132
+ const events = eventsByIdentity.get(identity6) ?? [];
85133
+ events.push(record25);
85134
+ eventsByIdentity.set(identity6, events);
85135
+ continue;
85136
+ }
85137
+ if (record25.record_type === "subscription_close") {
85138
+ const identity6 = subscriptionIdentity(record25);
85139
+ const closes = closesByIdentity.get(identity6) ?? [];
85140
+ closes.push(record25);
85141
+ closesByIdentity.set(identity6, closes);
85142
+ }
85143
+ }
85144
+ for (const [identity6, events] of eventsByIdentity) {
85145
+ const start = starts.get(identity6);
85146
+ if (!start || events.some((event) => !occursAfterStart(event, start))) return true;
85147
+ }
85148
+ for (const start of starts.values()) {
85149
+ const identity6 = subscriptionIdentity(start);
85150
+ const closes = closesByIdentity.get(identity6) ?? [];
85151
+ if (closes.some((close2) => !occursAfterStart(close2, start))) return true;
85152
+ if (closes.length > 1) return true;
85153
+ const close = closes[0];
85154
+ if (close && (eventsByIdentity.get(identity6) ?? []).some((event) => !occursBeforeClose(event, close))) return true;
85155
+ if (!close && !isRecent(start.opened_at_ms, nowMs)) return true;
85156
+ }
85157
+ if ([...closesByIdentity.keys()].some((identity6) => !starts.has(identity6))) return true;
85158
+ return false;
85159
+ }
84744
85160
  var FollowerOpenTraceAuditWriter = class {
84745
85161
  constructor(runtimeDir, nowMs, files) {
84746
85162
  this.runtimeDir = runtimeDir;
@@ -84754,6 +85170,30 @@ var FollowerOpenTraceAuditWriter = class {
84754
85170
  nowMs;
84755
85171
  files;
84756
85172
  sequence;
85173
+ loadEpoch = 0;
85174
+ loadIds;
85175
+ reserveLoadId(id5) {
85176
+ if (!this.loadIds) {
85177
+ try {
85178
+ this.ensureTraceDirectory(path51.join(this.runtimeDir, TRACE_DIRECTORY_NAME));
85179
+ } catch {
85180
+ return false;
85181
+ }
85182
+ const read = readRecords(this.runtimeDir, this.nowMs(), this.files);
85183
+ if (read.incomplete) return false;
85184
+ this.loadIds = new Set(read.records.filter((r) => r.schema_version === 3).map((r) => r.load_id));
85185
+ }
85186
+ if (this.loadIds.size >= 1e4 || this.loadIds.has(id5)) return false;
85187
+ this.loadIds.add(id5);
85188
+ return true;
85189
+ }
85190
+ loadGeneration() {
85191
+ return this.loadEpoch;
85192
+ }
85193
+ recordLoad(input) {
85194
+ const parsed = LoadRecordSchema.safeParse({ ...input, schema_version: 3, sequence: this.nextSequence() });
85195
+ if (parsed.success) this.write(parsed.data);
85196
+ }
84757
85197
  traceDirectoryInitialized = false;
84758
85198
  /** 只缓存当前追加文件;日期切换或清空后重新发现已有分片。 */
84759
85199
  appendFilePath;
@@ -84887,6 +85327,7 @@ var FollowerOpenTraceAuditWriter = class {
84887
85327
  for (const entry of matching) this.files.remove(entry.file);
84888
85328
  }
84889
85329
  this.appendFilePath = void 0;
85330
+ this.loadEpoch += 1;
84890
85331
  this.activeTraces.clear();
84891
85332
  this.activeSubscriptionTraces.clear();
84892
85333
  this.activeSubscriptionTraceHandles.clear();
@@ -85126,16 +85567,16 @@ import { dirname as dirname5, join as join3 } from "node:path";
85126
85567
 
85127
85568
  // apps/desktop/src/daemon/async-input-notification-store.ts
85128
85569
  import { createHash as createHash41 } from "node:crypto";
85129
- import { z as z17 } from "zod";
85570
+ import { z as z18 } from "zod";
85130
85571
  var ASYNC_INPUT_NOTIFICATION_MAX_BYTES = 2 * 1024 * 1024;
85131
- var identityText = z17.string().min(1).max(8e3);
85132
- var entrySchema2 = z17.object({
85133
- key: z17.string().regex(/^[a-f0-9]{64}$/),
85572
+ var identityText = z18.string().min(1).max(8e3);
85573
+ var entrySchema2 = z18.object({
85574
+ key: z18.string().regex(/^[a-f0-9]{64}$/),
85134
85575
  threadId: identityText,
85135
85576
  turnId: identityText,
85136
- stage: z17.enum(["admitted", "sent"])
85577
+ stage: z18.enum(["admitted", "sent"])
85137
85578
  }).strict();
85138
- var documentSchema2 = z17.object({ version: z17.literal(1), entries: z17.array(entrySchema2).max(4096) }).strict();
85579
+ var documentSchema2 = z18.object({ version: z18.literal(1), entries: z18.array(entrySchema2).max(4096) }).strict();
85139
85580
  var AsyncInputNotificationStore = class _AsyncInputNotificationStore {
85140
85581
  constructor(journal, document) {
85141
85582
  this.journal = journal;