deepline 0.2.46 → 0.2.48

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.
@@ -160,7 +160,7 @@ export const SDK_RELEASE = {
160
160
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
161
161
  // exposed storage-dependent synchronous access. This deliberate minor
162
162
  // release keeps lazy paging semantics independent of row residency.
163
- version: '0.2.46',
163
+ version: '0.2.48',
164
164
  contracts: {
165
165
  api: {
166
166
  name: 'sdk-http-api',
@@ -1102,6 +1102,7 @@ function durableCtxKey(input: {
1102
1102
  id: string;
1103
1103
  semanticKey?: string | null;
1104
1104
  staleAfterSeconds?: number | null;
1105
+ cacheEpochMs?: number;
1105
1106
  }): string {
1106
1107
  if (input.operation === 'tool') {
1107
1108
  throw new Error('Tool calls use tool receipt keys.');
@@ -1113,6 +1114,7 @@ function durableCtxKey(input: {
1113
1114
  id: input.id,
1114
1115
  semanticKey: input.semanticKey,
1115
1116
  staleAfterSeconds: input.staleAfterSeconds,
1117
+ cacheEpochMs: input.cacheEpochMs,
1116
1118
  });
1117
1119
  }
1118
1120
 
@@ -1391,6 +1393,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
1391
1393
  private fixtureProviderPacingBypassLogged = false;
1392
1394
  private fixtureProviderPacingEnforcementLogged = false;
1393
1395
  private checkpoint: PlayCheckpoint;
1396
+ private readonly durableCallCacheEpochMs: number;
1394
1397
  /**
1395
1398
  * Durable tool receipts are the replay/cache authority for the execution
1396
1399
  * paths the host supports. Keeping the same completed result in this
@@ -1664,6 +1667,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
1664
1667
  };
1665
1668
  this.#options = options;
1666
1669
  this.checkpoint = options.checkpoint ?? emptyCheckpoint();
1670
+ const checkpointCacheEpochMs = this.checkpoint.durableCallCacheEpochMs;
1671
+ this.durableCallCacheEpochMs =
1672
+ typeof checkpointCacheEpochMs === 'number' &&
1673
+ Number.isFinite(checkpointCacheEpochMs) &&
1674
+ checkpointCacheEpochMs >= 0
1675
+ ? checkpointCacheEpochMs
1676
+ : Date.now();
1677
+ this.checkpoint.durableCallCacheEpochMs = this.durableCallCacheEpochMs;
1667
1678
  this.durableMappedToolResultsBackedByReceipts = Boolean(
1668
1679
  (options.claimRuntimeStepReceipt || options.claimRuntimeStepReceipts) &&
1669
1680
  (options.getRuntimeStepReceipt || options.getRuntimeStepReceipts) &&
@@ -3104,6 +3115,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
3104
3115
  }),
3105
3116
  providerActionVersion,
3106
3117
  staleAfterSeconds: input.staleAfterSeconds,
3118
+ cacheEpochMs: this.durableCallCacheEpochMs,
3107
3119
  });
3108
3120
  }
3109
3121
 
@@ -3286,6 +3298,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
3286
3298
  id,
3287
3299
  semanticKey: opts.semanticKey,
3288
3300
  staleAfterSeconds: stalePolicy.staleAfterSeconds,
3301
+ cacheEpochMs: this.durableCallCacheEpochMs,
3289
3302
  });
3290
3303
  return await executeWithDurableRuntimeReceipt<T>({
3291
3304
  operation,
@@ -779,6 +779,11 @@ export interface ContextOptions {
779
779
  }
780
780
 
781
781
  export interface PlayCheckpoint {
782
+ /**
783
+ * Clock captured when this logical run first creates its context. All
784
+ * staleAfterSeconds receipt buckets use this value across durable resumes.
785
+ */
786
+ durableCallCacheEpochMs?: number;
782
787
  /** Waterfall batches that have completed: key = `${toolName}:${provider}`, value = results array. */
783
788
  completedBatches: Record<string, BatchResult[]>;
784
789
  /** Tool call batches that have completed: key = toolId, value = sparse row-cache-key -> result map. */
@@ -42,6 +42,8 @@ export function buildDurableToolCallCacheKey(input: {
42
42
  providerActionVersion?: string | null;
43
43
  cachePolicyVersion?: string | null;
44
44
  staleAfterSeconds?: number | null;
45
+ /** Run-stable clock used to choose the stale bucket. */
46
+ cacheEpochMs?: number;
45
47
  playLocalScope?: string | null;
46
48
  }): string {
47
49
  const orgId = input.orgId?.trim() || 'org';
@@ -70,6 +72,7 @@ export function buildDurableToolCallCacheKey(input: {
70
72
  input.cachePolicyVersion ?? DURABLE_CALL_CACHE_POLICY_VERSION,
71
73
  staleBucket: durableCacheStaleBucket({
72
74
  staleAfterSeconds: input.staleAfterSeconds,
75
+ nowMs: input.cacheEpochMs,
73
76
  }),
74
77
  }),
75
78
  );
@@ -84,6 +87,8 @@ export function buildDurableCtxCallCacheKey(input: {
84
87
  semanticKey?: string | null;
85
88
  cachePolicyVersion?: string | null;
86
89
  staleAfterSeconds?: number | null;
90
+ /** Run-stable clock used to choose the stale bucket. */
91
+ cacheEpochMs?: number;
87
92
  }): string {
88
93
  const orgId = input.orgId?.trim() || 'org';
89
94
  const playId = input.playId?.trim() || 'play';
@@ -104,6 +109,7 @@ export function buildDurableCtxCallCacheKey(input: {
104
109
  input.cachePolicyVersion ?? DURABLE_CALL_CACHE_POLICY_VERSION,
105
110
  staleBucket: durableCacheStaleBucket({
106
111
  staleAfterSeconds: input.staleAfterSeconds,
112
+ nowMs: input.cacheEpochMs,
107
113
  }),
108
114
  }),
109
115
  );
@@ -11,8 +11,6 @@ import {
11
11
  isPlayExecutionSuspendedError,
12
12
  isPlayRowExecutionSuspendedError,
13
13
  } from './suspension';
14
- import { getToolHttpErrorReceiptFailureKind } from './tool-http-errors';
15
- import { isLegacyRepairableWorkReceiptError } from './work-receipt-state-machine';
16
14
  import type { WorkReceiptFailureKind } from './work-receipts';
17
15
  import {
18
16
  PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
@@ -68,37 +66,13 @@ function isInFlightRuntimeReceipt(
68
66
  }
69
67
 
70
68
  export function runtimeReceiptFailureKindForError(
71
- error: unknown,
69
+ _error: unknown,
72
70
  ): WorkReceiptFailureKind {
73
- // A failed ownership check means the runner cannot prove whether the
74
- // invocation gateway or a newer attempt owns/completed the side effect. It
75
- // must never stamp that receipt terminal: a later fenced Play Run attempt
76
- // first reconciles the invocation receipt, then replays or reclaims safely.
77
- if (error instanceof RuntimeReceiptLeaseLostError) return 'repairable';
78
-
79
- const toolFailureKind = getToolHttpErrorReceiptFailureKind(error);
80
- if (toolFailureKind) return toolFailureKind;
81
-
82
- if (
83
- isPlayExecutionSuspendedError(error) ||
84
- isPlayRowExecutionSuspendedError(error)
85
- ) {
86
- return 'repairable';
87
- }
88
-
89
- // Transport/timeout failures never reach a `ToolHttpError` (there was no HTTP
90
- // response to classify), so they would otherwise default to `terminal` and
91
- // poison the durable receipt: the next run would replay the cached transport
92
- // failure instead of re-executing. These are transient infra failures, so
93
- // mark them repairable. `isLegacyRepairableWorkReceiptError` already encodes
94
- // the exact transient-string contract (5xx, "transport failed calling",
95
- // "runtime api call timed out") while excluding hard billing caps, and the
96
- // read-side SQL predicate uses the same rules, so write and read stay aligned.
97
- const message =
98
- error instanceof Error ? error.message : error != null ? String(error) : '';
99
- return isLegacyRepairableWorkReceiptError(message)
100
- ? 'repairable'
101
- : 'terminal';
71
+ // Current-run retry and cross-run repair are separate contracts. The tool
72
+ // policy still decides whether this call retries inside the owning run. Once
73
+ // that run stores a failure, however, a later explicit run may always try the
74
+ // semantic call again. The claim state machine keeps the owning run blocked.
75
+ return 'repairable';
102
76
  }
103
77
 
104
78
  export function resolveRuntimeToolReceiptWaitTimeoutMs(
@@ -1,45 +1,4 @@
1
1
  import { RECEIPT_STATUS_CODE } from './receipt-status';
2
- import { workReceiptFailureKindCode } from './work-receipts';
3
-
4
- // This predicate MUST stay behaviorally identical to
5
- // `isLegacyRepairableWorkReceiptError` in ./work-receipt-state-machine.ts.
6
- // Write-side classification and this read-side reclaim gate share one contract:
7
- // a receipt is repairable if its failure_kind is repairable, OR it is a legacy
8
- // terminal receipt whose error text proves a later-run-repairable failure (5xx,
9
- // transport, timeout, a transient billing-plane outage, or the structured
10
- // insufficient-balance snapshot). These narrow legacy clauses are checked
11
- // BEFORE the hard-billing exclusions so poisoned receipts re-execute on the
12
- // next run instead of replaying stale mutable state.
13
- export function workReceiptRepairableFailurePredicateSql(
14
- receiptTable: string,
15
- ): string {
16
- return `(
17
- ${receiptTable}.failure_kind = ${workReceiptFailureKindCode('repairable')}::smallint
18
- OR (
19
- ${receiptTable}.failure_kind = ${workReceiptFailureKindCode('terminal')}::smallint
20
- AND ${receiptTable}.error IS NOT NULL
21
- AND (
22
- lower(${receiptTable}.error) LIKE '%billing_unavailable%'
23
- OR lower(${receiptTable}.error) LIKE '%billing is temporarily unavailable%'
24
- OR lower(${receiptTable}.error) LIKE '%billing temporarily unavailable%'
25
- OR (
26
- lower(${receiptTable}.error) LIKE '%workspace balance %'
27
- AND lower(${receiptTable}.error) LIKE '% < required %'
28
- )
29
- OR (
30
- lower(${receiptTable}.error) NOT LIKE '%billing cap%'
31
- AND lower(${receiptTable}.error) NOT LIKE '%insufficient credits%'
32
- AND lower(${receiptTable}.error) NOT LIKE '%monthly billing limit%'
33
- AND (
34
- ${receiptTable}.error ~* '(^|[^0-9])5[0-9]{2}([^0-9]|$)'
35
- OR lower(${receiptTable}.error) LIKE '%transport failed calling%'
36
- OR lower(${receiptTable}.error) LIKE '%runtime api call timed out%'
37
- )
38
- )
39
- )
40
- )
41
- )`;
42
- }
43
2
 
44
3
  export function workReceiptClaimableStatusCodes(input: {
45
4
  forceRefresh?: boolean;
@@ -96,11 +55,8 @@ export function workReceiptClaimConflictPredicateSql(input: {
96
55
  )
97
56
  OR ${table}.status <> ${RECEIPT_STATUS_CODE.failed}::smallint
98
57
  OR (
99
- ${workReceiptRepairableFailurePredicateSql(table)}
100
- AND (
101
- ${table}.run_id IS DISTINCT FROM ${input.claimantRunIdSql}
102
- OR COALESCE(${table}.lease_owner_attempt, 0) < ${claimantRunAttemptSql}::integer
103
- )
58
+ ${table}.status = ${RECEIPT_STATUS_CODE.failed}::smallint
59
+ AND ${table}.run_id IS DISTINCT FROM ${input.claimantRunIdSql}
104
60
  )
105
61
  )
106
62
  AND (
@@ -32,6 +32,7 @@ export type PlayRunnerRuntimeResource = {
32
32
 
33
33
  export type RuntimeResourceTerminalReason =
34
34
  | 'completed'
35
+ | 'suspended'
35
36
  | 'runner_failed'
36
37
  | 'sandbox_missing'
37
38
  | 'sandbox_killed'
@@ -55,7 +55,7 @@ import {
55
55
  isReusableWorkReceipt,
56
56
  type WorkReceipt,
57
57
  type WorkReceiptClaim,
58
- workReceiptFailureKindCode,
58
+ workReceiptFailureKindCodeForWrite,
59
59
  workReceiptFailureKindFromCode,
60
60
  type WorkReceiptFailureKind,
61
61
  } from './work-receipts';
@@ -249,7 +249,7 @@ async function forceFailRuntimeWorkReceiptForRuntimeTestFault(
249
249
  RECEIPT_STATUS_FAILED,
250
250
  input.error,
251
251
  input.runId,
252
- workReceiptFailureKindCode(input.failureKind),
252
+ workReceiptFailureKindCodeForWrite(input.failureKind),
253
253
  RECEIPT_STATUS_COMPLETED,
254
254
  runAttempt,
255
255
  ],
@@ -5620,7 +5620,7 @@ export async function failRuntimeWorkReceipt(
5620
5620
  input.error,
5621
5621
  input.runId,
5622
5622
  leaseId,
5623
- workReceiptFailureKindCode(input.failureKind),
5623
+ workReceiptFailureKindCodeForWrite(input.failureKind),
5624
5624
  runAttempt,
5625
5625
  input.errorPayload ? JSON.stringify(input.errorPayload) : null,
5626
5626
  ],
@@ -5855,7 +5855,7 @@ export async function failRuntimeWorkReceipts(
5855
5855
  ),
5856
5856
  receipts.map((receipt) => receipt.error),
5857
5857
  receipts.map((receipt) =>
5858
- workReceiptFailureKindCode(receipt.failureKind),
5858
+ workReceiptFailureKindCodeForWrite(receipt.failureKind),
5859
5859
  ),
5860
5860
  receipts.map((receipt) =>
5861
5861
  receipt.errorPayload ? JSON.stringify(receipt.errorPayload) : null,
@@ -17,50 +17,6 @@ export type WorkReceiptStateInput = {
17
17
  leaseExpiresAt?: string | null;
18
18
  };
19
19
 
20
- export function isLegacyRepairableWorkReceiptError(
21
- error: string | null | undefined,
22
- ): boolean {
23
- const message = error?.trim().toLowerCase() ?? '';
24
- if (!message) return false;
25
- // Transient billing-PLANE outages (503 BILLING_UNAVAILABLE) were historically
26
- // formatted with the hard "billing cap exceeded" template (see
27
- // formatHardBillingFailureMessage), so the receipts written during a
28
- // billing-plane incident carry a "billing cap" string even though they are
29
- // retryable infra failures that charged nothing. Recognize them BEFORE the
30
- // hard-billing exclusion below so the poisoned backlog re-executes on the next
31
- // run instead of replaying the cached failure. Real hard denials never carry a
32
- // BILLING_UNAVAILABLE code or "temporarily unavailable" text.
33
- if (
34
- message.includes('billing_unavailable') ||
35
- message.includes('billing is temporarily unavailable') ||
36
- message.includes('billing temporarily unavailable')
37
- ) {
38
- return true;
39
- }
40
- // Before insufficient-credit failures were written as repairable, the
41
- // structured billing formatter persisted the balance snapshot in this
42
- // stable shape. A later run must re-check the now-mutable balance while the
43
- // same run attempt remains fenced by the claim decision below.
44
- if (
45
- message.includes('workspace balance ') &&
46
- message.includes(' < required ')
47
- ) {
48
- return true;
49
- }
50
- if (
51
- message.includes('billing cap') ||
52
- message.includes('insufficient credits') ||
53
- message.includes('monthly billing limit')
54
- ) {
55
- return false;
56
- }
57
- return (
58
- /(?:^|\D)5\d\d(?:\D|$)/.test(message) ||
59
- message.includes('transport failed calling') ||
60
- message.includes('runtime api call timed out')
61
- );
62
- }
63
-
64
20
  export type WorkReceiptLeaseState =
65
21
  | { kind: 'none' }
66
22
  | {
@@ -312,15 +268,7 @@ export function decideWorkReceiptClaim(input: {
312
268
  }
313
269
  const claimantRunId = normalize(input.claimantRunId);
314
270
  const ownerRunId = normalize(input.receipt.runId);
315
- const claimantRunAttempt = normalizeAttempt(input.claimantRunAttempt);
316
- const ownerAttempt = normalizeAttempt(input.receipt.leaseOwnerAttempt);
317
- if (
318
- (input.receipt.failureKind === 'repairable' ||
319
- isLegacyRepairableWorkReceiptError(input.receipt.error)) &&
320
- claimantRunId !== null &&
321
- ownerRunId !== null &&
322
- (claimantRunId !== ownerRunId || ownerAttempt < claimantRunAttempt)
323
- ) {
271
+ if (claimantRunId !== null && claimantRunId !== ownerRunId) {
324
272
  return { kind: 'claim' };
325
273
  }
326
274
  return { kind: 'blocked_failed' };
@@ -25,6 +25,20 @@ export function workReceiptFailureKindCode(
25
25
  return WORK_RECEIPT_FAILURE_KIND_CODE[value ?? 'terminal'];
26
26
  }
27
27
 
28
+ /**
29
+ * Failure writes are always reclaimable by a later explicit Play Run.
30
+ *
31
+ * The input remains accepted at compatibility boundaries because older
32
+ * runners still send `terminal`, but new storage state must never make a
33
+ * failed semantic call permanent. Claim fencing, not this bit, prevents the
34
+ * owning run from automatically repeating the failure.
35
+ */
36
+ export function workReceiptFailureKindCodeForWrite(
37
+ _value?: WorkReceiptFailureKind | null,
38
+ ): number {
39
+ return WORK_RECEIPT_FAILURE_KIND_CODE.repairable;
40
+ }
41
+
28
42
  export type WorkReceiptStatus =
29
43
  | 'queued'
30
44
  | 'pending'
package/dist/cli/index.js CHANGED
@@ -1044,7 +1044,7 @@ var SDK_RELEASE = {
1044
1044
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
1045
1045
  // exposed storage-dependent synchronous access. This deliberate minor
1046
1046
  // release keeps lazy paging semantics independent of row residency.
1047
- version: "0.2.46",
1047
+ version: "0.2.48",
1048
1048
  contracts: {
1049
1049
  api: {
1050
1050
  name: "sdk-http-api",
@@ -1030,7 +1030,7 @@ var SDK_RELEASE = {
1030
1030
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
1031
1031
  // exposed storage-dependent synchronous access. This deliberate minor
1032
1032
  // release keeps lazy paging semantics independent of row residency.
1033
- version: "0.2.46",
1033
+ version: "0.2.48",
1034
1034
  contracts: {
1035
1035
  api: {
1036
1036
  name: "sdk-http-api",
package/dist/index.js CHANGED
@@ -763,7 +763,7 @@ var SDK_RELEASE = {
763
763
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
764
764
  // exposed storage-dependent synchronous access. This deliberate minor
765
765
  // release keeps lazy paging semantics independent of row residency.
766
- version: "0.2.46",
766
+ version: "0.2.48",
767
767
  contracts: {
768
768
  api: {
769
769
  name: "sdk-http-api",
package/dist/index.mjs CHANGED
@@ -689,7 +689,7 @@ var SDK_RELEASE = {
689
689
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
690
690
  // exposed storage-dependent synchronous access. This deliberate minor
691
691
  // release keeps lazy paging semantics independent of row residency.
692
- version: "0.2.46",
692
+ version: "0.2.48",
693
693
  contracts: {
694
694
  api: {
695
695
  name: "sdk-http-api",
@@ -881,6 +881,21 @@ body {
881
881
  .expectation-result.pass { color: var(--green); }
882
882
  .expectation-result.fail { color: var(--red); }
883
883
  .expectation-result.unknown { color: var(--yellow); }
884
+ .infra-signals {
885
+ margin-top: 10px;
886
+ border: 1px solid color-mix(in srgb, var(--red) 45%, var(--viewer-border));
887
+ border-radius: var(--viewer-radius-md);
888
+ background: color-mix(in srgb, var(--red) 5%, var(--bg));
889
+ color: var(--text-dim);
890
+ font-size: 10px;
891
+ }
892
+ .infra-signals-heading { padding: 7px 9px; border-bottom: 1px solid var(--viewer-border); color: var(--red); font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
893
+ .infra-signals ul { margin: 0; padding: 0; list-style: none; }
894
+ .infra-signals li { padding: 7px 9px; }
895
+ .infra-signals li + li { border-top: 1px solid var(--viewer-border); }
896
+ .infra-signals strong { color: var(--text); font-family: var(--viewer-font-mono); }
897
+ .infra-signals span { margin-left: 7px; color: var(--text-dimmer); font-family: var(--viewer-font-mono); }
898
+ .infra-signals p { margin: 4px 0 0; overflow-wrap: anywhere; }
884
899
  .lane-prompt, .lane-result { border-bottom: 1px solid var(--viewer-border); }
885
900
  .lane-prompt summary, .lane-result summary { cursor: pointer; padding: 8px 13px; color: var(--text-dim); font-size: 11px; font-weight: 650; }
886
901
  .lane-prompt .prompt-text, .lane-result .md-rendered { padding: 0 13px 12px; font-size: 12px; }
@@ -1354,16 +1354,38 @@ function renderExpectationMiniTable(session) {
1354
1354
  </div>`;
1355
1355
  }
1356
1356
 
1357
+ function normalizedInfraSignals(metric) {
1358
+ if (!metric || !Array.isArray(metric.infra_signals)) return [];
1359
+ return metric.infra_signals.filter(signal => signal && typeof signal === 'object').map(signal => ({
1360
+ code: String(signal.code || 'unknown_infrastructure_error'),
1361
+ source: String(signal.source || 'unknown'),
1362
+ message: String(signal.message || 'No detail was recorded.'),
1363
+ }));
1364
+ }
1365
+
1366
+ function renderInfraSignals(metric) {
1367
+ const signals = normalizedInfraSignals(metric);
1368
+ if (!signals.length) return '';
1369
+ const rows = signals.map(signal => `<li><strong>${esc(signal.code)}</strong><span>${esc(signal.source)}</span><p>${esc(signal.message)}</p></li>`).join('');
1370
+ return `<div class="infra-signals"><div class="infra-signals-heading">Infrastructure signals</div><ul>${rows}</ul></div>`;
1371
+ }
1372
+
1357
1373
  function renderLaneSession(session) {
1358
1374
  const i = session.sourceIndex;
1359
1375
  const d = session.dimensions;
1360
1376
  const prefix = 'lane-' + i;
1361
1377
  const errorRate = session.stats.tool_calls > 0 ? Math.round(session.stats.tool_errors / session.stats.tool_calls * 100) : 0;
1378
+ const metric = session.evalMetric || {};
1379
+ const infraAffected = metric.sample_validity === 'infra_affected';
1380
+ const infraSignals = normalizedInfraSignals(metric);
1381
+ const infraCodes = [...new Set(infraSignals.map(signal => signal.code))].join(', ');
1382
+ const embeddedFailures = Number(metric.embedded_failure_count || 0);
1362
1383
  let html = `<article class="lane-session">
1363
1384
  <header class="lane-session-header">
1364
1385
  <div class="lane-title-row"><div><span class="run-kicker">Run ${esc(d.run)}</span><h3>${esc(d.agent)} / ${esc(d.model)}</h3></div><button class="download-btn" onclick="downloadJsonl(${i})">JSONL</button></div>
1365
- <div class="dimension-chips"><span>${esc(d.skill)}</span><span>${formatDuration(session.stats.duration_s)}</span><span>${session.stats.tool_calls} calls</span><span class="${session.stats.tool_errors ? 'chip-error' : ''}">${session.stats.tool_errors} errors${errorRate ? ' · ' + errorRate + '%' : ''}</span></div>
1386
+ <div class="dimension-chips"><span>${esc(d.skill)}</span><span>${formatDuration(session.stats.duration_s)}</span><span>${session.stats.tool_calls} calls</span><span class="${session.stats.tool_errors ? 'chip-error' : ''}">${session.stats.tool_errors} errors${errorRate ? ' · ' + errorRate + '%' : ''}</span>${embeddedFailures ? `<span class="chip-error">${embeddedFailures} embedded failures</span>` : ''}${infraAffected ? `<span class="chip-error">infra affected${infraCodes ? ' · ' + esc(infraCodes) : ''}</span>` : ''}</div>
1366
1387
  ${renderExpectationMiniTable(session)}
1388
+ ${renderInfraSignals(metric)}
1367
1389
  </header>`;
1368
1390
  if (!session.transcript_available) {
1369
1391
  html += '<div class="transcript-unavailable">No transcript was retained for this completed eval. Its scored expectations remain included in this comparison.</div>';
@@ -1431,6 +1453,7 @@ function downloadJsonl(i) {
1431
1453
 
1432
1454
  function renderSession(i) {
1433
1455
  const s = SESSIONS[i];
1456
+ const metric = s.evalMetric || {};
1434
1457
  const main = document.getElementById('main-content');
1435
1458
  main.classList.add('full-width');
1436
1459
 
@@ -1458,11 +1481,22 @@ function renderSession(i) {
1458
1481
  html += item('Turns', s.stats.num_turns || '\u2014');
1459
1482
  html += item('Tool Calls', s.stats.tool_calls);
1460
1483
  html += item('Errors', s.stats.tool_errors + (s.stats.tool_calls > 0 ? ' (' + Math.round(s.stats.tool_errors / s.stats.tool_calls * 100) + '%)' : ''));
1484
+ if (Number(metric.embedded_failure_count || 0) > 0) {
1485
+ html += item('Embedded Failures', Number(metric.embedded_failure_count));
1486
+ }
1487
+ if (metric.sample_validity === 'infra_affected') {
1488
+ html += item('Sample Validity', 'infra affected');
1489
+ }
1490
+ const infraSignals = normalizedInfraSignals(metric);
1491
+ if (infraSignals.length) {
1492
+ html += item('Infra Signal Codes', [...new Set(infraSignals.map(signal => signal.code))].join(', '));
1493
+ }
1461
1494
  if (s.stats.cost_usd != null) html += item('Cost', '$' + s.stats.cost_usd.toFixed(2));
1462
1495
  if (s.stats.loop_groups > 0) html += item('Retry Loops', s.stats.loop_groups);
1463
1496
  if (s.stats.error_streak_groups > 0) html += item('Error Streaks', s.stats.error_streak_groups + ' (max ' + s.stats.max_error_streak + ')');
1464
1497
  html += '</div>';
1465
1498
  html += renderExpectationMiniTable(s);
1499
+ html += renderInfraSignals(metric);
1466
1500
  if (!s.transcript_available) {
1467
1501
  html += '<div class="transcript-unavailable">No transcript was retained for this completed eval. Its scored expectations remain included in this comparison.</div>';
1468
1502
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.2.46",
3
+ "version": "0.2.48",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {