@evident-ai/cli 3.6.1-dev.4f2b317 → 3.6.1-dev.5d5fa2a

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/index.js CHANGED
@@ -1256,6 +1256,36 @@ function effectiveCorrelatedRun(messages, userMessageId) {
1256
1256
  function extractReplyTextForMessage(messages, opencodeUserMessageId) {
1257
1257
  return lastText(effectiveCorrelatedRun(messages, opencodeUserMessageId));
1258
1258
  }
1259
+ function isRecord(value) {
1260
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1261
+ }
1262
+ function projectV2ToolContent(content) {
1263
+ if (!isRecord(content) || content.type !== "tool" || typeof content.name !== "string" || !isRecord(content.state)) {
1264
+ return null;
1265
+ }
1266
+ const { state } = content;
1267
+ const status2 = state.status;
1268
+ if (status2 !== "streaming" && status2 !== "running" && status2 !== "completed" && status2 !== "error") {
1269
+ return null;
1270
+ }
1271
+ const projectedState = {
1272
+ status: status2 === "streaming" ? "pending" : status2
1273
+ };
1274
+ if (status2 !== "streaming" && isRecord(state.input)) projectedState.input = state.input;
1275
+ const output = Array.isArray(state.content) ? state.content.filter(
1276
+ (entry) => isRecord(entry) && entry.type === "text" && typeof entry.text === "string"
1277
+ ).map((entry) => entry.text) : [];
1278
+ if (output.length > 0) projectedState.output = output.join("\n");
1279
+ if (status2 === "error" && isRecord(state.error) && typeof state.error.message === "string") {
1280
+ projectedState.error = state.error.message;
1281
+ }
1282
+ return {
1283
+ type: "tool",
1284
+ tool: content.name,
1285
+ ...typeof content.id === "string" ? { callID: content.id } : {},
1286
+ state: projectedState
1287
+ };
1288
+ }
1259
1289
 
1260
1290
  // ../../packages/types/src/telemetry/index.ts
1261
1291
  var TelemetryEventTypes = {
@@ -1445,7 +1475,7 @@ import ora4 from "ora";
1445
1475
  import { select as select4 } from "@inquirer/prompts";
1446
1476
 
1447
1477
  // src/lib/telemetry.ts
1448
- var CLI_VERSION = (true ? "3.6.1-dev.4f2b317" : void 0) ?? process.env.npm_package_version ?? "unknown";
1478
+ var CLI_VERSION = (true ? "3.6.1-dev.5d5fa2a" : void 0) ?? process.env.npm_package_version ?? "unknown";
1449
1479
  function getCliVersion() {
1450
1480
  return CLI_VERSION;
1451
1481
  }
@@ -3075,7 +3105,7 @@ function buildSessionArchive(input) {
3075
3105
  };
3076
3106
  return { ...archive, content_fingerprint: fingerprintArchive(archive) };
3077
3107
  }
3078
- function isRecord(value) {
3108
+ function isRecord2(value) {
3079
3109
  return typeof value === "object" && value !== null && !Array.isArray(value);
3080
3110
  }
3081
3111
  function errorMessage2(error2) {
@@ -3213,7 +3243,7 @@ async function exportV2SessionMessages(client, sessionId, signal) {
3213
3243
  }
3214
3244
  const path = `${basePath}?${params.toString()}`;
3215
3245
  const { body } = await requestPage(client, sessionId, path, signal);
3216
- if (!isRecord(body) || !Array.isArray(body.data) || !isRecord(body.cursor)) {
3246
+ if (!isRecord2(body) || !Array.isArray(body.data) || !isRecord2(body.cursor)) {
3217
3247
  throw invalidResponse("v2", sessionId, path, "expected a {data: [], cursor: {\u2026}} envelope");
3218
3248
  }
3219
3249
  messages.push(...body.data);
@@ -3260,7 +3290,7 @@ function readWatermarks(path, log3) {
3260
3290
  }
3261
3291
  try {
3262
3292
  const parsed = JSON.parse(content);
3263
- if (!isRecord2(parsed)) throw new Error("invalid watermark object");
3293
+ if (!isRecord3(parsed)) throw new Error("invalid watermark object");
3264
3294
  const entries = Object.entries(parsed);
3265
3295
  if (!entries.every(([sessionId, value]) => sessionId.length > 0 && isWatermark(value))) {
3266
3296
  throw new Error("invalid watermark entry");
@@ -3283,12 +3313,12 @@ function recordUpload(path, sessionId, entry, watermarks) {
3283
3313
  watermarks.clear();
3284
3314
  for (const [id, watermark] of updated) watermarks.set(id, watermark);
3285
3315
  }
3286
- function isRecord2(value) {
3316
+ function isRecord3(value) {
3287
3317
  return typeof value === "object" && value !== null && !Array.isArray(value);
3288
3318
  }
3289
3319
  function isWatermark(value) {
3290
- if (!isRecord2(value)) return false;
3291
- return typeof value.fingerprint === "string" && typeof value.uploaded_at === "string" && typeof value.key === "string";
3320
+ if (!isRecord3(value)) return false;
3321
+ return typeof value.fingerprint === "string" && typeof value.uploaded_at === "string" && typeof value.key === "string" && (!("conversation_id" in value) || value.conversation_id === null || typeof value.conversation_id === "string");
3292
3322
  }
3293
3323
  function isMissingFile3(error2) {
3294
3324
  return error2 instanceof Error && "code" in error2 && error2.code === "ENOENT";
@@ -3310,7 +3340,7 @@ var shell_contract_default = {
3310
3340
  "session-db-verify",
3311
3341
  "archive-put"
3312
3342
  ],
3313
- archiveStdinMaxBytes: 16777216
3343
+ archiveStdinMaxBytes: 33554432
3314
3344
  };
3315
3345
 
3316
3346
  // src/lib/runner-synchroniser.ts
@@ -3450,9 +3480,15 @@ function runSynchroniser(args, opts) {
3450
3480
  var MAX_SESSION_ARCHIVE_BYTES = MAX_ARCHIVE_STDIN_BYTES;
3451
3481
  var EXIT_ARCHIVE_CONFIG_MISSING = 50;
3452
3482
  var EXIT_ARCHIVE_ACCESS_DENIED = 51;
3453
- function transportFailure(bytes, reason) {
3483
+ function transportFailure(bytes, reason, exitCode) {
3454
3484
  const message = reason === "missing-executable" ? "runner-synchroniser is unavailable; session archival requires a hosted runner." : reason === "timeout" ? "runner-synchroniser timed out before the archive upload completed." : "session archive upload did not complete.";
3455
- return { kind: "transport-failed", bytes, reason, message };
3485
+ return {
3486
+ kind: "transport-failed",
3487
+ bytes,
3488
+ reason,
3489
+ message,
3490
+ ...exitCode !== void 0 ? { exitCode } : {}
3491
+ };
3456
3492
  }
3457
3493
  function isJson(body) {
3458
3494
  try {
@@ -3497,10 +3533,157 @@ async function uploadSessionArchive(key, body, options) {
3497
3533
  }
3498
3534
  return transportFailure(
3499
3535
  bytes.byteLength,
3500
- result.code === null ? "spawn-failed" : "process-failed"
3536
+ result.code === null ? "spawn-failed" : "process-failed",
3537
+ result.code ?? void 0
3501
3538
  );
3502
3539
  }
3503
3540
 
3541
+ // src/lib/opencode/session-binding-resolution.ts
3542
+ var SESSION_BINDING_PROJECTION_CHUNK_SIZE = 500;
3543
+ var SESSION_BINDING_PROJECTION_TIMEOUT_MS = 1e4;
3544
+ function isRecord4(value) {
3545
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3546
+ }
3547
+ function isProjectionSource(value) {
3548
+ return value === "current" || value === "invocation" || value === "history";
3549
+ }
3550
+ function emptySessionBindingProjection() {
3551
+ return {
3552
+ current: /* @__PURE__ */ new Map(),
3553
+ invocation: /* @__PURE__ */ new Map(),
3554
+ history: /* @__PURE__ */ new Map(),
3555
+ conflicts: /* @__PURE__ */ new Set()
3556
+ };
3557
+ }
3558
+ function requestedSessionIds(sessions) {
3559
+ const ids = /* @__PURE__ */ new Set();
3560
+ for (const session of sessions) {
3561
+ if (session.id.length > 0) ids.add(session.id);
3562
+ if (typeof session.parentID === "string" && session.parentID.length > 0) {
3563
+ ids.add(session.parentID);
3564
+ }
3565
+ }
3566
+ return [...ids];
3567
+ }
3568
+ function parseProjectionResponse(value, requestedIds) {
3569
+ if (!isRecord4(value) || !Array.isArray(value.bindings) || !Array.isArray(value.conflicts)) {
3570
+ return null;
3571
+ }
3572
+ const projection = emptySessionBindingProjection();
3573
+ for (const candidate of value.bindings) {
3574
+ if (!isRecord4(candidate) || typeof candidate.opencode_session_id !== "string" || typeof candidate.conversation_id !== "string" || !isProjectionSource(candidate.source) || !requestedIds.has(candidate.opencode_session_id) || projection.conflicts.has(candidate.opencode_session_id)) {
3575
+ return null;
3576
+ }
3577
+ const bindings = projection[candidate.source];
3578
+ if (bindings.has(candidate.opencode_session_id)) return null;
3579
+ bindings.set(candidate.opencode_session_id, candidate.conversation_id);
3580
+ }
3581
+ for (const conflict of value.conflicts) {
3582
+ if (typeof conflict !== "string" || !requestedIds.has(conflict) || projection.conflicts.has(conflict) || projection.current.has(conflict) || projection.invocation.has(conflict) || projection.history.has(conflict)) {
3583
+ return null;
3584
+ }
3585
+ projection.conflicts.add(conflict);
3586
+ }
3587
+ return projection;
3588
+ }
3589
+ async function fetchSessionBindingProjection({
3590
+ apiUrl,
3591
+ runnerId,
3592
+ authHeader,
3593
+ sessions,
3594
+ signal,
3595
+ fetchImpl = fetch
3596
+ }) {
3597
+ const ids = requestedSessionIds(sessions);
3598
+ const projection = emptySessionBindingProjection();
3599
+ const failures = [];
3600
+ if (ids.length === 0) return { projection, complete: true, failures };
3601
+ for (let offset = 0; offset < ids.length; offset += SESSION_BINDING_PROJECTION_CHUNK_SIZE) {
3602
+ const chunk = ids.slice(offset, offset + SESSION_BINDING_PROJECTION_CHUNK_SIZE);
3603
+ const chunkIndex = Math.floor(offset / SESSION_BINDING_PROJECTION_CHUNK_SIZE) + 1;
3604
+ try {
3605
+ const response = await fetchImpl(
3606
+ `${apiUrl}/runners/${runnerId}/conversations/session-bindings`,
3607
+ {
3608
+ method: "POST",
3609
+ headers: {
3610
+ Authorization: authHeader,
3611
+ "Content-Type": "application/json"
3612
+ },
3613
+ body: JSON.stringify({ opencode_session_ids: chunk }),
3614
+ signal: signal ?? AbortSignal.timeout(SESSION_BINDING_PROJECTION_TIMEOUT_MS)
3615
+ }
3616
+ );
3617
+ if (!response.ok) {
3618
+ failures.push({ chunk: chunkIndex, kind: "http", status: response.status });
3619
+ continue;
3620
+ }
3621
+ const result = parseProjectionResponse(await response.json(), new Set(chunk));
3622
+ if (result === null) {
3623
+ failures.push({ chunk: chunkIndex, kind: "invalid-response" });
3624
+ continue;
3625
+ }
3626
+ for (const source of ["current", "invocation", "history"]) {
3627
+ for (const [sessionId, conversationId] of result[source]) {
3628
+ projection[source].set(sessionId, conversationId);
3629
+ }
3630
+ }
3631
+ for (const conflict of result.conflicts) projection.conflicts.add(conflict);
3632
+ } catch (error2) {
3633
+ failures.push({
3634
+ chunk: chunkIndex,
3635
+ kind: "network",
3636
+ message: error2 instanceof Error ? error2.message : String(error2)
3637
+ });
3638
+ }
3639
+ }
3640
+ return { projection, complete: failures.length === 0, failures };
3641
+ }
3642
+ function directResolution(sessionId, sources) {
3643
+ const localBinding = sources.ledger.get(sessionId);
3644
+ if (localBinding) return { conversation_id: localBinding, source: "ledger" };
3645
+ const watermarkedBinding = sources.watermarks.get(sessionId)?.conversation_id;
3646
+ if (typeof watermarkedBinding === "string" && watermarkedBinding.length > 0) {
3647
+ return { conversation_id: watermarkedBinding, source: "watermark" };
3648
+ }
3649
+ if (sources.projection.conflicts.has(sessionId)) return { reason: "conflict" };
3650
+ for (const source of ["current", "invocation", "history"]) {
3651
+ const conversationId = sources.projection[source].get(sessionId);
3652
+ if (conversationId) return { conversation_id: conversationId, source };
3653
+ }
3654
+ return null;
3655
+ }
3656
+ function isResolved(resolution) {
3657
+ return "conversation_id" in resolution;
3658
+ }
3659
+ function resolveSessionBindings(sessions, sources) {
3660
+ const resolutions = /* @__PURE__ */ new Map();
3661
+ for (const session of sessions) {
3662
+ resolutions.set(session.id, directResolution(session.id, sources) ?? { reason: "unmapped" });
3663
+ }
3664
+ let assignedParent = true;
3665
+ while (assignedParent) {
3666
+ assignedParent = false;
3667
+ for (const session of sessions) {
3668
+ const resolution = resolutions.get(session.id);
3669
+ if (isResolved(resolution) || resolution.reason !== "unmapped") continue;
3670
+ if (typeof session.parentID !== "string" || session.parentID.length === 0) continue;
3671
+ const parent = resolutions.get(session.parentID) ?? directResolution(session.parentID, sources) ?? { reason: "unmapped" };
3672
+ if (isResolved(parent)) {
3673
+ resolutions.set(session.id, {
3674
+ conversation_id: parent.conversation_id,
3675
+ source: "parent"
3676
+ });
3677
+ assignedParent = true;
3678
+ } else if (parent.reason === "conflict" || parent.reason === "parent-conflict") {
3679
+ resolutions.set(session.id, { reason: "parent-conflict" });
3680
+ assignedParent = true;
3681
+ }
3682
+ }
3683
+ }
3684
+ return resolutions;
3685
+ }
3686
+
3504
3687
  // src/lib/opencode/session-archive-run.ts
3505
3688
  var SESSION_ARCHIVE_UPLOAD_TIMEOUT_MS = 6e4;
3506
3689
  function updatedAt(session) {
@@ -3513,6 +3696,12 @@ function sessionArchiveKey(prefix, sessionId) {
3513
3696
  function failureMessage(error2) {
3514
3697
  return error2 instanceof Error ? error2.message : String(error2);
3515
3698
  }
3699
+ function countMessageParts(messages) {
3700
+ return messages.reduce((count, message) => {
3701
+ if (typeof message !== "object" || message === null || !("parts" in message)) return count;
3702
+ return count + (Array.isArray(message.parts) ? message.parts.length : 0);
3703
+ }, 0);
3704
+ }
3516
3705
  function isBudgetExpired(deadline, now, signal) {
3517
3706
  return signal?.aborted === true || now() >= deadline;
3518
3707
  }
@@ -3552,6 +3741,19 @@ async function runSessionArchive({
3552
3741
  skippedBudgetIds: [],
3553
3742
  neverWatermarkedFailures: 0,
3554
3743
  unmappedIds: [],
3744
+ conflictedIds: [],
3745
+ unresolved: 0,
3746
+ conflicts: 0,
3747
+ reconciliationComplete: deps.reconciliationComplete ?? true,
3748
+ reconciliationIncomplete: 0,
3749
+ bindingSourceCounts: {
3750
+ ledger: 0,
3751
+ watermark: 0,
3752
+ current: 0,
3753
+ invocation: 0,
3754
+ history: 0,
3755
+ parent: 0
3756
+ },
3555
3757
  archivedSessionIds: [],
3556
3758
  failures: [],
3557
3759
  staleSince: null
@@ -3560,6 +3762,22 @@ async function runSessionArchive({
3560
3762
  const upload = deps.upload ?? uploadSessionArchive;
3561
3763
  const exportMessages = deps.exportMessages ?? exportSessionMessages;
3562
3764
  const writeWatermark = deps.recordWatermark ?? recordUpload;
3765
+ const resolutions = resolveSessionBindings(sessions, {
3766
+ ledger: deps.bindings,
3767
+ watermarks: deps.watermarks,
3768
+ projection: deps.projection ?? emptySessionBindingProjection()
3769
+ });
3770
+ for (const [sessionId, resolution] of resolutions) {
3771
+ if ("conversation_id" in resolution) {
3772
+ summary.bindingSourceCounts[resolution.source]++;
3773
+ } else if (resolution.reason === "conflict" || resolution.reason === "parent-conflict") {
3774
+ summary.conflictedIds.push(sessionId);
3775
+ } else {
3776
+ summary.unmappedIds.push(sessionId);
3777
+ }
3778
+ }
3779
+ summary.unresolved = summary.unmappedIds.length + summary.conflictedIds.length;
3780
+ summary.conflicts = summary.conflictedIds.length;
3563
3781
  const failedWatermarks = [];
3564
3782
  const recordFailedWatermark = (watermark) => {
3565
3783
  if (watermark) failedWatermarks.push(watermark.uploaded_at);
@@ -3573,8 +3791,22 @@ async function runSessionArchive({
3573
3791
  summary.skippedBudget = summary.skippedBudgetIds.length;
3574
3792
  break;
3575
3793
  }
3576
- const conversationId = deps.bindings.get(session.id) ?? null;
3577
- if (conversationId === null) summary.unmappedIds.push(session.id);
3794
+ const resolution = resolutions.get(session.id) ?? { reason: "unmapped" };
3795
+ const resolved = "conversation_id" in resolution;
3796
+ const conversationId = resolved ? resolution.conversation_id : null;
3797
+ const bindingSource = resolved ? resolution.source : "unmapped";
3798
+ if (!resolved && !summary.reconciliationComplete) {
3799
+ summary.failed++;
3800
+ summary.reconciliationIncomplete++;
3801
+ summary.failures.push({
3802
+ sessionId: session.id,
3803
+ outcome: "reconciliation-incomplete",
3804
+ reason: resolution.reason,
3805
+ archived: false
3806
+ });
3807
+ recordFailedWatermark(deps.watermarks.get(session.id));
3808
+ continue;
3809
+ }
3578
3810
  const exportBudget = createBudgetSignal(deps.signal, remainingMs);
3579
3811
  let messages;
3580
3812
  try {
@@ -3611,7 +3843,7 @@ async function runSessionArchive({
3611
3843
  messages,
3612
3844
  apiVersion: deps.client.version,
3613
3845
  conversationId,
3614
- bindingSource: conversationId === null ? "unmapped" : "ledger",
3846
+ bindingSource,
3615
3847
  runnerId: deps.runnerId,
3616
3848
  archivedAt
3617
3849
  });
@@ -3648,6 +3880,11 @@ async function runSessionArchive({
3648
3880
  outcome: outcome.kind,
3649
3881
  ...reason !== void 0 ? { reason } : {},
3650
3882
  ..."message" in outcome ? { message: outcome.message } : {},
3883
+ bytes: outcome.bytes,
3884
+ byteLimit: MAX_SESSION_ARCHIVE_BYTES,
3885
+ messageCount: messages.length,
3886
+ partCount: countMessageParts(messages),
3887
+ ..."exitCode" in outcome && outcome.exitCode !== void 0 ? { exitCode: outcome.exitCode } : {},
3651
3888
  archived: false
3652
3889
  });
3653
3890
  recordFailedWatermark(existing);
@@ -3663,7 +3900,8 @@ async function runSessionArchive({
3663
3900
  const watermark = {
3664
3901
  fingerprint: snapshot.content_fingerprint,
3665
3902
  uploaded_at: new Date(now()).toISOString(),
3666
- key
3903
+ key,
3904
+ conversation_id: conversationId
3667
3905
  };
3668
3906
  try {
3669
3907
  writeWatermark(deps.watermarkPath, session.id, watermark, deps.watermarks);
@@ -4620,8 +4858,15 @@ function collectSubagentSessions(messages, userMessageId) {
4620
4858
  for (const message of assistants) {
4621
4859
  const parts = Array.isArray(message.parts) ? message.parts : [];
4622
4860
  for (const part of parts) {
4623
- if (!part || typeof part !== "object" || part.type !== "tool" || part.tool !== "task")
4861
+ if (!part || typeof part !== "object" || part.type !== "tool") continue;
4862
+ const childSession = part.childSession;
4863
+ if (childSession) {
4864
+ if (seen.has(childSession.sessionId)) continue;
4865
+ seen.add(childSession.sessionId);
4866
+ refs.push(childSession);
4624
4867
  continue;
4868
+ }
4869
+ if (part.tool !== "task") continue;
4625
4870
  const state = part.state;
4626
4871
  if (!state || typeof state !== "object") continue;
4627
4872
  const metadata = state.metadata;
@@ -5223,14 +5468,14 @@ async function probeOpenCodeRequestReadiness(port, client) {
5223
5468
 
5224
5469
  // src/lib/opencode/session-v2.ts
5225
5470
  var ALL_HTTP_STATUSES2 = Array.from({ length: 500 }, (_, index) => index + 100);
5226
- function isRecord3(value) {
5471
+ function isRecord5(value) {
5227
5472
  return typeof value === "object" && value !== null && !Array.isArray(value);
5228
5473
  }
5229
5474
  function finiteNumber2(value) {
5230
5475
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
5231
5476
  }
5232
5477
  function adaptTime(value) {
5233
- if (!isRecord3(value)) return void 0;
5478
+ if (!isRecord5(value)) return void 0;
5234
5479
  const created = finiteNumber2(value.created);
5235
5480
  const completed = finiteNumber2(value.completed);
5236
5481
  if (created === void 0 && completed === void 0) return void 0;
@@ -5240,11 +5485,11 @@ function adaptTime(value) {
5240
5485
  };
5241
5486
  }
5242
5487
  function adaptTokens(value) {
5243
- if (!isRecord3(value)) return void 0;
5488
+ if (!isRecord5(value)) return void 0;
5244
5489
  const input = finiteNumber2(value.input);
5245
5490
  const output = finiteNumber2(value.output);
5246
5491
  const reasoning = finiteNumber2(value.reasoning);
5247
- const cache = isRecord3(value.cache) ? {
5492
+ const cache = isRecord5(value.cache) ? {
5248
5493
  ...finiteNumber2(value.cache.read) !== void 0 ? { read: finiteNumber2(value.cache.read) } : {},
5249
5494
  ...finiteNumber2(value.cache.write) !== void 0 ? { write: finiteNumber2(value.cache.write) } : {}
5250
5495
  } : void 0;
@@ -5268,7 +5513,7 @@ function adaptMessageInfo(value, role) {
5268
5513
  if (typeof value.finish === "string") info.finish = value.finish;
5269
5514
  if ("error" in value) info.error = value.error;
5270
5515
  if (typeof value.agent === "string") info.agent = value.agent;
5271
- if (isRecord3(value.model)) {
5516
+ if (isRecord5(value.model)) {
5272
5517
  if (typeof value.model.id === "string") info.modelID = value.model.id;
5273
5518
  if (typeof value.model.providerID === "string") info.providerID = value.model.providerID;
5274
5519
  }
@@ -5277,8 +5522,16 @@ function adaptMessageInfo(value, role) {
5277
5522
  if (tokens) info.tokens = tokens;
5278
5523
  return info;
5279
5524
  }
5525
+ function v2ChildSessionEvidence(content) {
5526
+ const state = isRecord5(content.state) ? content.state : null;
5527
+ const metadata = state && isRecord5(state.metadata) ? state.metadata : null;
5528
+ const sessionId = typeof metadata?.sessionID === "string" && metadata.sessionID.length > 0 ? metadata.sessionID : typeof metadata?.sessionId === "string" && metadata.sessionId.length > 0 ? metadata.sessionId : null;
5529
+ if (sessionId === null) return null;
5530
+ const time = isRecord5(content.time) ? content.time : null;
5531
+ return { sessionId, startedAtMs: finiteNumber2(time?.created) ?? null };
5532
+ }
5280
5533
  function adaptV2Message(value) {
5281
- if (!isRecord3(value) || typeof value.id !== "string" || typeof value.type !== "string") {
5534
+ if (!isRecord5(value) || typeof value.id !== "string" || typeof value.type !== "string") {
5282
5535
  return null;
5283
5536
  }
5284
5537
  if (value.type === "user") {
@@ -5291,7 +5544,13 @@ function adaptV2Message(value) {
5291
5544
  if (value.type !== "assistant" || !Array.isArray(value.content)) return null;
5292
5545
  const parts = [];
5293
5546
  for (const content of value.content) {
5294
- if (!isRecord3(content) || typeof content.type !== "string") return null;
5547
+ if (!isRecord5(content) || typeof content.type !== "string") return null;
5548
+ const toolPart = projectV2ToolContent(content);
5549
+ if (toolPart) {
5550
+ const childSession = v2ChildSessionEvidence(content);
5551
+ parts.push(childSession ? { ...toolPart, childSession } : toolPart);
5552
+ continue;
5553
+ }
5295
5554
  if (content.type === "text") {
5296
5555
  if (typeof content.text !== "string") return null;
5297
5556
  parts.push({ type: "text", text: content.text });
@@ -5305,23 +5564,23 @@ function adaptV2Message(value) {
5305
5564
  };
5306
5565
  }
5307
5566
  function adaptFormTool(value) {
5308
- if (!isRecord3(value) || typeof value.messageID !== "string" || typeof value.id !== "string") {
5567
+ if (!isRecord5(value) || typeof value.messageID !== "string" || typeof value.id !== "string") {
5309
5568
  return void 0;
5310
5569
  }
5311
5570
  return { messageID: value.messageID, callID: value.id };
5312
5571
  }
5313
5572
  function adaptV2FormWire(value) {
5314
- if (!isRecord3(value) || typeof value.id !== "string" || typeof value.sessionID !== "string") {
5573
+ if (!isRecord5(value) || typeof value.id !== "string" || typeof value.sessionID !== "string") {
5315
5574
  return null;
5316
5575
  }
5317
5576
  return value;
5318
5577
  }
5319
5578
  function adaptV2FormField(value, header) {
5320
- if (!isRecord3(value)) return null;
5579
+ if (!isRecord5(value)) return null;
5321
5580
  const question = typeof value.title === "string" ? value.title : typeof value.question === "string" ? value.question : typeof value.key === "string" ? value.key : null;
5322
5581
  if (!question) return null;
5323
5582
  const options = Array.isArray(value.options) ? value.options.flatMap((option) => {
5324
- if (!isRecord3(option)) return [];
5583
+ if (!isRecord5(option)) return [];
5325
5584
  const label = typeof option.label === "string" ? option.label : typeof option.value === "string" ? option.value : null;
5326
5585
  if (!label) return [];
5327
5586
  return [
@@ -5339,7 +5598,7 @@ function adaptV2Form(value) {
5339
5598
  const header = typeof form.title === "string" ? form.title : "";
5340
5599
  const questions = form.fields.map((field) => adaptV2FormField(field, header)).filter((question) => question !== null);
5341
5600
  if (questions.length === 0) return null;
5342
- const tool = isRecord3(form.metadata) ? adaptFormTool(form.metadata.tool) : void 0;
5601
+ const tool = isRecord5(form.metadata) ? adaptFormTool(form.metadata.tool) : void 0;
5343
5602
  return {
5344
5603
  id: form.id,
5345
5604
  sessionID: form.sessionID,
@@ -5349,7 +5608,7 @@ function adaptV2Form(value) {
5349
5608
  };
5350
5609
  }
5351
5610
  function adaptV2FormList(value) {
5352
- if (!isRecord3(value) || !Array.isArray(value.data)) return null;
5611
+ if (!isRecord5(value) || !Array.isArray(value.data)) return null;
5353
5612
  return value.data.map(adaptV2Form).filter((form) => form !== null);
5354
5613
  }
5355
5614
  function adaptPattern(value) {
@@ -5360,7 +5619,7 @@ function adaptPattern(value) {
5360
5619
  return void 0;
5361
5620
  }
5362
5621
  function adaptV2PermissionWire(value) {
5363
- if (!isRecord3(value) || typeof value.id !== "string" || typeof value.sessionID !== "string") {
5622
+ if (!isRecord5(value) || typeof value.id !== "string" || typeof value.sessionID !== "string") {
5364
5623
  return null;
5365
5624
  }
5366
5625
  if (typeof value.permission !== "string" && typeof value.action !== "string") return null;
@@ -5372,12 +5631,12 @@ function adaptV2Permission(value) {
5372
5631
  const type = permission.permission ?? permission.action;
5373
5632
  if (!type) return null;
5374
5633
  const pattern = adaptPattern(permission.pattern) ?? adaptPattern(permission.patterns) ?? adaptPattern(permission.resources);
5375
- const time = isRecord3(permission.time) ? finiteNumber2(permission.time.created) !== void 0 ? { created: finiteNumber2(permission.time.created) } : void 0 : void 0;
5634
+ const time = isRecord5(permission.time) ? finiteNumber2(permission.time.created) !== void 0 ? { created: finiteNumber2(permission.time.created) } : void 0 : void 0;
5376
5635
  return {
5377
5636
  id: permission.id,
5378
5637
  type,
5379
5638
  sessionID: permission.sessionID,
5380
- metadata: isRecord3(permission.metadata) ? permission.metadata : {},
5639
+ metadata: isRecord5(permission.metadata) ? permission.metadata : {},
5381
5640
  raw: permission,
5382
5641
  ...pattern !== void 0 ? { pattern } : {},
5383
5642
  ...typeof permission.messageID === "string" ? { messageID: permission.messageID } : {},
@@ -5387,12 +5646,12 @@ function adaptV2Permission(value) {
5387
5646
  };
5388
5647
  }
5389
5648
  function adaptV2PermissionList(value) {
5390
- if (!isRecord3(value) || !Array.isArray(value.data)) return null;
5649
+ if (!isRecord5(value) || !Array.isArray(value.data)) return null;
5391
5650
  return value.data.map(adaptV2Permission).filter((permission) => permission !== null);
5392
5651
  }
5393
5652
  function adaptV2Session(value) {
5394
- if (!isRecord3(value) || typeof value.id !== "string" || value.id.length === 0) return null;
5395
- const time = isRecord3(value.time) ? {
5653
+ if (!isRecord5(value) || typeof value.id !== "string" || value.id.length === 0) return null;
5654
+ const time = isRecord5(value.time) ? {
5396
5655
  ...finiteNumber2(value.time.created) !== void 0 ? { created: finiteNumber2(value.time.created) } : {},
5397
5656
  ...finiteNumber2(value.time.updated) !== void 0 ? { updated: finiteNumber2(value.time.updated) } : {}
5398
5657
  } : void 0;
@@ -5404,7 +5663,7 @@ function adaptV2Session(value) {
5404
5663
  };
5405
5664
  }
5406
5665
  function adaptV2SessionList(value) {
5407
- if (!isRecord3(value) || !Array.isArray(value.data) || !isRecord3(value.cursor)) return null;
5666
+ if (!isRecord5(value) || !Array.isArray(value.data) || !isRecord5(value.cursor)) return null;
5408
5667
  return {
5409
5668
  data: value.data.map(adaptV2Session).filter((session) => session !== null),
5410
5669
  cursor: value.cursor
@@ -5413,18 +5672,18 @@ function adaptV2SessionList(value) {
5413
5672
  function adaptV2Location(value) {
5414
5673
  const candidates = [
5415
5674
  value,
5416
- isRecord3(value) ? value.data : void 0,
5417
- isRecord3(value) ? value.location : void 0
5675
+ isRecord5(value) ? value.data : void 0,
5676
+ isRecord5(value) ? value.location : void 0
5418
5677
  ];
5419
5678
  for (const candidate of candidates) {
5420
- if (!isRecord3(candidate) || typeof candidate.directory !== "string") continue;
5679
+ if (!isRecord5(candidate) || typeof candidate.directory !== "string") continue;
5421
5680
  const directory = candidate.directory.trim();
5422
5681
  if (directory) return directory;
5423
5682
  }
5424
5683
  return null;
5425
5684
  }
5426
5685
  function adaptV2Messages(value) {
5427
- if (!isRecord3(value) || !Array.isArray(value.data)) return [];
5686
+ if (!isRecord5(value) || !Array.isArray(value.data)) return [];
5428
5687
  return value.data.slice().reverse().map(adaptV2Message).filter((message) => message !== null);
5429
5688
  }
5430
5689
  async function readJson(response) {
@@ -5439,7 +5698,7 @@ async function readJson(response) {
5439
5698
  async function readData(client, path, init) {
5440
5699
  const response = await client.request(path, init);
5441
5700
  const body = await readJson(response);
5442
- if (!isRecord3(body) || !("data" in body)) {
5701
+ if (!isRecord5(body) || !("data" in body)) {
5443
5702
  throw new Error(`OpenCode V2 response for ${path} was missing its data envelope`);
5444
5703
  }
5445
5704
  return body.data;
@@ -5474,7 +5733,7 @@ async function createV2Session(client, directory) {
5474
5733
  headers: { "Content-Type": "application/json" },
5475
5734
  body: JSON.stringify({ location: { directory } })
5476
5735
  });
5477
- if (!isRecord3(data) || typeof data.id !== "string" || data.id.length === 0) {
5736
+ if (!isRecord5(data) || typeof data.id !== "string" || data.id.length === 0) {
5478
5737
  throw new Error("OpenCode V2 create session response was missing data.id");
5479
5738
  }
5480
5739
  return data.id;
@@ -5619,8 +5878,8 @@ async function sendV2Prompt(client, sessionId, text, options, onInvalidModelSele
5619
5878
  `OpenCode V2 prompt response could not be read: ${error2 instanceof Error ? error2.message : String(error2)}`
5620
5879
  );
5621
5880
  }
5622
- const data = isRecord3(body) && "data" in body ? body.data : void 0;
5623
- if (!isRecord3(data) || typeof data.id !== "string" || data.id.length === 0) {
5881
+ const data = isRecord5(body) && "data" in body ? body.data : void 0;
5882
+ if (!isRecord5(data) || typeof data.id !== "string" || data.id.length === 0) {
5624
5883
  throw new OpenCodeV2PromptAckError("OpenCode V2 prompt response was missing data.id");
5625
5884
  }
5626
5885
  return data.id;
@@ -5629,7 +5888,7 @@ async function getV2SessionMessages(client, sessionId) {
5629
5888
  const path = `/api/session/${encodeURIComponent(sessionId)}/message?order=desc&limit=200`;
5630
5889
  try {
5631
5890
  const body = await readJson(await client.request(path));
5632
- if (!isRecord3(body) || !Array.isArray(body.data) || !isRecord3(body.cursor)) return null;
5891
+ if (!isRecord5(body) || !Array.isArray(body.data) || !isRecord5(body.cursor)) return null;
5633
5892
  return adaptV2Messages(body);
5634
5893
  } catch (error2) {
5635
5894
  console.error(
@@ -5668,7 +5927,7 @@ async function interruptV2Session(client, sessionId) {
5668
5927
  async function getV2ActiveSessions(client) {
5669
5928
  try {
5670
5929
  const body = await readJson(await client.request("/api/session/active"));
5671
- if (!isRecord3(body) || !isRecord3(body.data)) return null;
5930
+ if (!isRecord5(body) || !isRecord5(body.data)) return null;
5672
5931
  return body.data;
5673
5932
  } catch (error2) {
5674
5933
  console.error(
@@ -5684,8 +5943,8 @@ async function isV2SessionOngoing(client, sessionId) {
5684
5943
  }
5685
5944
  function sessionErrorReason2(value) {
5686
5945
  if (typeof value === "string" && value.trim()) return value.trim().slice(0, 500);
5687
- if (isRecord3(value)) {
5688
- const data = isRecord3(value.data) ? value.data : void 0;
5946
+ if (isRecord5(value)) {
5947
+ const data = isRecord5(value.data) ? value.data : void 0;
5689
5948
  const reason = typeof data?.message === "string" && data.message || typeof value.message === "string" && value.message || typeof value.name === "string" && value.name;
5690
5949
  if (reason) return reason.replace(/\s+/g, " ").trim().slice(0, 500);
5691
5950
  }
@@ -5701,17 +5960,17 @@ function adaptV2SessionErrorEvent(value) {
5701
5960
  return null;
5702
5961
  }
5703
5962
  }
5704
- if (!isRecord3(parsed)) return null;
5963
+ if (!isRecord5(parsed)) return null;
5705
5964
  try {
5706
5965
  const establishedShape = parseSessionErrorFrame(JSON.stringify(parsed));
5707
5966
  if (establishedShape) return establishedShape;
5708
5967
  } catch (error2) {
5709
5968
  void error2;
5710
5969
  }
5711
- const candidates = [parsed, parsed.payload, parsed.data].filter(isRecord3);
5970
+ const candidates = [parsed, parsed.payload, parsed.data].filter(isRecord5);
5712
5971
  for (const event of candidates) {
5713
5972
  if (event.type !== "session.error") continue;
5714
- const properties = [event.properties, event.data, event].find(isRecord3);
5973
+ const properties = [event.properties, event.data, event].find(isRecord5);
5715
5974
  if (!properties) continue;
5716
5975
  const sessionId = typeof properties.sessionID === "string" && properties.sessionID || typeof properties.sessionId === "string" && properties.sessionId;
5717
5976
  if (!sessionId) continue;
@@ -5847,7 +6106,7 @@ function parseCodexUsageHeaders(headers) {
5847
6106
  function normalizeProbeModel(model) {
5848
6107
  return model.endsWith("-fast") ? model.slice(0, -"-fast".length) : model;
5849
6108
  }
5850
- function isRecord4(value) {
6109
+ function isRecord6(value) {
5851
6110
  return typeof value === "object" && value !== null && !Array.isArray(value);
5852
6111
  }
5853
6112
  function unsupportedProbeModels(reason, port) {
@@ -5858,14 +6117,14 @@ async function resolveV1ProbeModels(client, port) {
5858
6117
  try {
5859
6118
  const response = await client.request("/config/providers");
5860
6119
  const body = await response.json();
5861
- if (!isRecord4(body) || !Array.isArray(body.providers)) {
6120
+ if (!isRecord6(body) || !Array.isArray(body.providers)) {
5862
6121
  return unsupportedProbeModels("V1 provider response did not contain a providers array", port);
5863
6122
  }
5864
6123
  const provider = body.providers.find(
5865
- (candidate) => isRecord4(candidate) && candidate.id === "openai"
6124
+ (candidate) => isRecord6(candidate) && candidate.id === "openai"
5866
6125
  );
5867
- if (!provider || !isRecord4(provider.models)) return { status: "supported", models: [] };
5868
- const defaults2 = isRecord4(body.default) ? body.default : void 0;
6126
+ if (!provider || !isRecord6(provider.models)) return { status: "supported", models: [] };
6127
+ const defaults2 = isRecord6(body.default) ? body.default : void 0;
5869
6128
  const candidates = [
5870
6129
  ...typeof defaults2?.openai === "string" ? [defaults2.openai] : [],
5871
6130
  ...Object.keys(provider.models)
@@ -5887,14 +6146,14 @@ async function resolveV2ProbeModels(client, port) {
5887
6146
  try {
5888
6147
  const response = await client.request(path);
5889
6148
  const body = await response.json();
5890
- if (!isRecord4(body) || !Array.isArray(body.data)) {
6149
+ if (!isRecord6(body) || !Array.isArray(body.data)) {
5891
6150
  return unsupportedProbeModels(`V2 GET ${path} did not contain a provider data array`, port);
5892
6151
  }
5893
6152
  const provider = body.data.find(
5894
- (candidate) => isRecord4(candidate) && candidate.id === "openai"
6153
+ (candidate) => isRecord6(candidate) && candidate.id === "openai"
5895
6154
  );
5896
6155
  if (!provider) return { status: "supported", models: [] };
5897
- if (!isRecord4(provider.models)) {
6156
+ if (!isRecord6(provider.models)) {
5898
6157
  return unsupportedProbeModels(
5899
6158
  "V2 provider response has no safe OpenAI model catalogue",
5900
6159
  port
@@ -6004,7 +6263,7 @@ var CONNECT_KEY_MAX_ATTEMPTS = 3;
6004
6263
  var CONNECT_KEY_RETRY_DELAY_MS = 250;
6005
6264
  var INTEGRATION_LIST_MAX_READS = 10;
6006
6265
  var INTEGRATION_LIST_RETRY_DELAY_MS = 1e3;
6007
- function isRecord5(value) {
6266
+ function isRecord7(value) {
6008
6267
  return typeof value === "object" && value !== null && !Array.isArray(value);
6009
6268
  }
6010
6269
  var sleep2 = (milliseconds) => new Promise((resolve5) => setTimeout(resolve5, milliseconds));
@@ -6020,7 +6279,7 @@ function readOpenCodeCredentials(homeDir, log3) {
6020
6279
  } catch (error2) {
6021
6280
  return null;
6022
6281
  }
6023
- return isRecord5(parsed) ? parsed : null;
6282
+ return isRecord7(parsed) ? parsed : null;
6024
6283
  } catch (error2) {
6025
6284
  const code = error2.code;
6026
6285
  if (code !== "ENOENT" && code !== "ENOTDIR") {
@@ -6033,7 +6292,7 @@ function classifyOpenCodeCredentials(credentials2) {
6033
6292
  const importable = [];
6034
6293
  const unsupported = [];
6035
6294
  for (const [integrationId, value] of Object.entries(credentials2)) {
6036
- if (!isRecord5(value) || typeof value.type !== "string") {
6295
+ if (!isRecord7(value) || typeof value.type !== "string") {
6037
6296
  unsupported.push({ integrationId, reason: "unrecognised credential entry" });
6038
6297
  continue;
6039
6298
  }
@@ -6048,7 +6307,7 @@ function classifyOpenCodeCredentials(credentials2) {
6048
6307
  unsupported.push({ integrationId, reason: "unrecognised credential entry" });
6049
6308
  continue;
6050
6309
  }
6051
- if (value.metadata === void 0 || isRecord5(value.metadata) && Object.keys(value.metadata).length === 0) {
6310
+ if (value.metadata === void 0 || isRecord7(value.metadata) && Object.keys(value.metadata).length === 0) {
6052
6311
  importable.push({ integrationId, key: value.key });
6053
6312
  } else {
6054
6313
  unsupported.push({ integrationId, reason: "needs the interactive connect flow" });
@@ -6125,10 +6384,10 @@ async function importOpenCodeCredentials({
6125
6384
  return result;
6126
6385
  }
6127
6386
  function parseIntegrationList(value) {
6128
- if (!isRecord5(value) || !Array.isArray(value.data)) return null;
6387
+ if (!isRecord7(value) || !Array.isArray(value.data)) return null;
6129
6388
  const integrationIds = /* @__PURE__ */ new Set();
6130
6389
  for (const integration of value.data) {
6131
- if (!isRecord5(integration) || typeof integration.id !== "string" || !Array.isArray(integration.connections)) {
6390
+ if (!isRecord7(integration) || typeof integration.id !== "string" || !Array.isArray(integration.connections)) {
6132
6391
  return null;
6133
6392
  }
6134
6393
  if (integration.connections.length > 0) integrationIds.add(integration.id);
@@ -17004,11 +17263,11 @@ function logSessionArchiveSummary(state, phase, summary) {
17004
17263
  const everyUploadFailed = summary.uploadAttempts > 0 && summary.uploadFailures === summary.uploadAttempts;
17005
17264
  const staleSince = everyUploadFailed && summary.staleSince !== null ? `; archive stale since ${summary.staleSince}` : "";
17006
17265
  const neverWatermarked = summary.neverWatermarkedFailures > 0 ? `; ${summary.neverWatermarkedFailures} failed session(s) had no prior successful upload` : "";
17007
- const warning2 = summary.failed > 0 || summary.failures.length > 0 || everyUploadFailed;
17266
+ const warning2 = summary.failed > 0 || summary.failures.length > 0 || everyUploadFailed || !summary.reconciliationComplete;
17008
17267
  logSessionArchiveActivity(state, phase, {
17009
17268
  type: "info",
17010
17269
  level: warning2 ? "warn" : "info",
17011
- message: `Session archive: inspected ${summary.inspected}, uploaded ${summary.uploaded}, unchanged ${summary.unchanged}, failed ${summary.failed}, skipped budget ${summary.skippedBudget}, unmapped ${summary.unmappedIds.length}` + staleSince + neverWatermarked,
17270
+ message: `Session archive: inspected ${summary.inspected}, uploaded ${summary.uploaded}, unchanged ${summary.unchanged}, failed ${summary.failed}, skipped budget ${summary.skippedBudget}, unmapped ${summary.unmappedIds.length}, conflicts ${summary.conflicts}, reconciliation incomplete ${summary.reconciliationIncomplete}; bindings ledger ${summary.bindingSourceCounts.ledger}, watermark ${summary.bindingSourceCounts.watermark}, current ${summary.bindingSourceCounts.current}, invocation ${summary.bindingSourceCounts.invocation}, history ${summary.bindingSourceCounts.history}, parent ${summary.bindingSourceCounts.parent}` + staleSince + neverWatermarked,
17012
17271
  metadata: {
17013
17272
  outcome: "archive-run-complete",
17014
17273
  sessions_inspected: summary.inspected,
@@ -17019,22 +17278,41 @@ function logSessionArchiveSummary(state, phase, summary) {
17019
17278
  uploads_failed: summary.uploadFailures,
17020
17279
  sessions_skipped_budget: summary.skippedBudget,
17021
17280
  sessions_unmapped: summary.unmappedIds.length,
17281
+ sessions_unresolved: summary.unresolved,
17282
+ sessions_conflicted: summary.conflicts,
17283
+ sessions_reconciliation_incomplete: summary.reconciliationIncomplete,
17284
+ binding_reconciliation_complete: summary.reconciliationComplete,
17285
+ bindings_from_ledger: summary.bindingSourceCounts.ledger,
17286
+ bindings_from_watermark: summary.bindingSourceCounts.watermark,
17287
+ bindings_from_current: summary.bindingSourceCounts.current,
17288
+ bindings_from_invocation: summary.bindingSourceCounts.invocation,
17289
+ bindings_from_history: summary.bindingSourceCounts.history,
17290
+ bindings_from_parent: summary.bindingSourceCounts.parent,
17022
17291
  sessions_without_prior_watermark: summary.neverWatermarkedFailures,
17023
17292
  ...everyUploadFailed && summary.staleSince !== null ? { stale_since: summary.staleSince } : {}
17024
17293
  }
17025
17294
  });
17026
17295
  for (const failure of summary.failures) {
17027
17296
  const permanentSizeFailure = failure.reason === "too-large";
17028
- const safeMessage = failure.message ? `: ${redactDiagnosticText(failure.message).slice(0, 200)}` : "";
17297
+ const hasPayloadDetails = failure.bytes !== void 0 && failure.byteLimit !== void 0 && failure.messageCount !== void 0 && failure.partCount !== void 0;
17298
+ const payloadDetails = hasPayloadDetails ? `; payload bytes=${failure.bytes} limit=${failure.byteLimit} messages=${failure.messageCount} parts=${failure.partCount}` : "";
17299
+ const safeMessage = !hasPayloadDetails && failure.message ? `: ${redactDiagnosticText(failure.message).slice(0, 200)}` : "";
17029
17300
  logSessionArchiveActivity(state, phase, {
17030
17301
  type: "info",
17031
17302
  level: "warn",
17032
- message: `Session archive: session ${failure.sessionId} ` + (permanentSizeFailure ? "failed permanently (too-large payload)" : `failed (${failure.outcome}`) + (permanentSizeFailure ? "" : failure.reason ? `, reason=${failure.reason})` : ")") + safeMessage,
17303
+ message: `Session archive: session ${failure.sessionId} ` + (permanentSizeFailure ? "failed permanently (too-large payload)" : `failed (${failure.outcome}`) + (permanentSizeFailure ? "" : failure.reason ? `, reason=${failure.reason})` : ")") + payloadDetails + (failure.exitCode !== void 0 ? `; child exit code=${failure.exitCode}` : "") + safeMessage,
17033
17304
  metadata: {
17034
17305
  outcome: failure.outcome,
17035
17306
  session_id: failure.sessionId,
17036
17307
  archived: failure.archived,
17037
- ...failure.reason !== void 0 ? { reason: failure.reason } : {}
17308
+ ...failure.reason !== void 0 ? { reason: failure.reason } : {},
17309
+ ...failure.exitCode !== void 0 ? { child_exit_code: failure.exitCode } : {},
17310
+ ...hasPayloadDetails ? {
17311
+ payload_bytes: failure.bytes,
17312
+ payload_limit_bytes: failure.byteLimit,
17313
+ message_count: failure.messageCount,
17314
+ part_count: failure.partCount
17315
+ } : {}
17038
17316
  }
17039
17317
  });
17040
17318
  }
@@ -17049,6 +17327,17 @@ function logSessionArchiveSummary(state, phase, summary) {
17049
17327
  }
17050
17328
  });
17051
17329
  }
17330
+ if (summary.conflictedIds.length > 0) {
17331
+ logSessionArchiveActivity(state, phase, {
17332
+ type: "info",
17333
+ level: "warn",
17334
+ message: `Session archive: ${summary.conflictedIds.length} session(s) have conflicting conversation identities: ${summary.conflictedIds.join(", ")}`,
17335
+ metadata: {
17336
+ outcome: "conflicted-session-bindings",
17337
+ sessions_conflicted: summary.conflicts
17338
+ }
17339
+ });
17340
+ }
17052
17341
  }
17053
17342
  async function drainSessionDeletionReports(state) {
17054
17343
  const path = sessionDeletionReportPath(homedir8(), process.env);
@@ -17507,21 +17796,48 @@ async function runSessionArchiveBatch(state, phase, sessions, budgetMs, signal)
17507
17796
  });
17508
17797
  return null;
17509
17798
  }
17799
+ const bindings = readSessionBindings(
17800
+ sessionBindingLedgerPath(homedir8(), process.env),
17801
+ (message) => logSessionArchiveActivity(state, phase, {
17802
+ type: "info",
17803
+ level: "warn",
17804
+ message: redactDiagnosticText(message),
17805
+ metadata: { outcome: "binding-ledger-read-failed" }
17806
+ })
17807
+ );
17808
+ const reconciliation = await fetchSessionBindingProjection({
17809
+ apiUrl: getApiUrlConfig(),
17810
+ runnerId: state.agentId,
17811
+ authHeader: state.authHeader,
17812
+ sessions,
17813
+ signal
17814
+ });
17815
+ if (!reconciliation.complete) {
17816
+ const failure = reconciliation.failures[0];
17817
+ const detail = failure.kind === "http" ? `HTTP ${failure.status} for chunk ${failure.chunk}` : failure.kind === "network" ? `network failure for chunk ${failure.chunk}: ${redactDiagnosticText(failure.message ?? "unknown error").slice(0, 200)}` : `invalid response for chunk ${failure.chunk}`;
17818
+ logSessionArchiveActivity(state, phase, {
17819
+ type: "info",
17820
+ level: "warn",
17821
+ message: `Session archive: binding reconciliation incomplete for runner ${state.agentId}; ${reconciliation.failures.length} chunk(s) failed (${detail}), unresolved sessions will not replace existing archives`,
17822
+ metadata: {
17823
+ outcome: "binding-reconciliation-incomplete",
17824
+ runner_id: state.agentId,
17825
+ failed_chunks: reconciliation.failures.length,
17826
+ first_failed_chunk: failure.chunk,
17827
+ first_failure_kind: failure.kind,
17828
+ ...failure.status !== void 0 ? { http_status: failure.status } : {}
17829
+ }
17830
+ });
17831
+ }
17510
17832
  const summary = await runSessionArchive({
17511
17833
  sessions,
17512
17834
  deps: {
17513
17835
  client,
17514
17836
  runnerId: state.agentId,
17515
17837
  keyPrefix: config.keyPrefix,
17516
- bindings: readSessionBindings(
17517
- sessionBindingLedgerPath(homedir8(), process.env),
17518
- (message) => logSessionArchiveActivity(state, phase, {
17519
- type: "info",
17520
- level: "warn",
17521
- message: redactDiagnosticText(message),
17522
- metadata: { outcome: "binding-ledger-read-failed" }
17523
- })
17524
- ),
17838
+ bindings,
17839
+ projection: reconciliation.projection,
17840
+ reconciliationComplete: reconciliation.complete,
17525
17841
  watermarks: state.sessionArchiveWatermarks,
17526
17842
  watermarkPath,
17527
17843
  signal,