deepline 0.3.290 → 0.3.292

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.
Files changed (46) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +8 -6
  2. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  3. package/dist/bundling-sources/sdk/src/runs/overview.ts +16 -6
  4. package/dist/bundling-sources/sdk/src/types.ts +21 -2
  5. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +11 -1
  6. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +209 -51
  7. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +10 -2
  8. package/dist/bundling-sources/shared_libs/play-runtime/log-context.ts +36 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/log-provenance.ts +2 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +5 -1
  11. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger-event-transport.ts +54 -41
  12. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +50 -3
  13. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +9 -2
  14. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +35 -7
  15. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +55 -9
  16. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +16 -4
  17. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +140 -4
  18. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +559 -313
  19. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +1 -1
  20. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +116 -24
  21. package/dist/bundling-sources/shared_libs/play-runtime/step-progress.ts +6 -1
  22. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +22 -5
  23. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +35 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +68 -8
  25. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +2 -0
  26. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +2 -0
  27. package/dist/cli/index.js +209 -588
  28. package/dist/cli/index.mjs +209 -588
  29. package/dist/{client-FR_u0dxT.d.ts → client-Ct4jmvAg.d.ts} +29 -8
  30. package/dist/{client-phK1Ixcl.d.mts → client-Ct9GKrwu.d.mts} +29 -8
  31. package/dist/{compiler-manifest-9SQtUuKb.d.mts → compiler-manifest-qtTNYvBl.d.mts} +2 -0
  32. package/dist/{compiler-manifest-9SQtUuKb.d.ts → compiler-manifest-qtTNYvBl.d.ts} +2 -0
  33. package/dist/index.d.mts +4 -4
  34. package/dist/index.d.ts +4 -4
  35. package/dist/index.js +12 -5
  36. package/dist/index.mjs +12 -5
  37. package/dist/install-integrity.json +4 -4
  38. package/dist/plays/bundle-play-file.d.mts +2 -2
  39. package/dist/plays/bundle-play-file.d.ts +2 -2
  40. package/dist/plays/run-request.d.mts +2 -2
  41. package/dist/plays/run-request.d.ts +2 -2
  42. package/dist/release.d.mts +1 -1
  43. package/dist/release.d.ts +1 -1
  44. package/dist/release.js +1 -1
  45. package/dist/release.mjs +1 -1
  46. package/package.json +1 -1
@@ -879,7 +879,7 @@ export type RunsEventSearchOptions = {
879
879
  playName?: string;
880
880
  /** Exact dotted event kind, such as `step.failed`. The catalog is discoverable with `runs logs --help`; future stored kinds are accepted too. */
881
881
  kind?: string;
882
- /** JSONPath equality joined with `&&`, at most eight predicates and 512 bytes. See `runs logs --help` for fields and examples. Missing fields do not match; status is not inferred from kind. HTTP `jsonpath`, CLI `--where`. */
882
+ /** JSONPath equality joined with `&&`, at most eight predicates and 512 bytes. `eventId` selects an exact returned event; add `payloads` to retrieve its saved details. See `runs logs --help` for fields and examples. Missing fields do not match; status is not inferred from kind. HTTP `jsonpath`, CLI `--where`. */
883
883
  jsonpath?: string;
884
884
  /** Include available `eventPayload` details and receipt `payload` results; maximum ten events and 16 MiB. HTTP `payloads=true`, CLI `--payloads`. */
885
885
  includePayloads?: boolean;
@@ -905,7 +905,7 @@ export type RunsEventSearchEntry = {
905
905
  playName: string;
906
906
  /** Exact dotted event kind, such as `step.failed`. */
907
907
  kind: string;
908
- /** Recorded log severity, or null; not a JSONPath filter field. */
908
+ /** Recorded log severity, or null on older events. Filter with `$.level == "error"`. */
909
909
  level: string | null;
910
910
  /** Log message preview of at most 2,048 characters, or null. Use text logs/export for the full retained line. */
911
911
  message: string | null;
@@ -933,7 +933,7 @@ export type RunsEventSearchResult = {
933
933
  export type RunsEventPayload = {
934
934
  /** Recorded terminal work outcome. */
935
935
  status: 'completed' | 'failed' | 'skipped';
936
- /** Stored result data, or null when no output was retained. */
936
+ /** Saved result, including available sanitized call input/output details; failed-call records use kind: tool_call_evidence. Null when no output was retained. */
937
937
  output: unknown | null;
938
938
  /** Stored failure message, or null when absent. */
939
939
  error: string | null;
@@ -3320,8 +3320,10 @@ export class DeeplineClient {
3320
3320
  /**
3321
3321
  * Execute a tool and return the standard execution envelope.
3322
3322
  *
3323
- * The `toolResponse.rawV2` field contains the complete parsed and scrubbed tool response.
3324
- * `toolResponse.meta` contains tool/provider metadata.
3323
+ * The `toolResponse.rawV2` field contains the complete tool response,
3324
+ * preserving its original nesting. Inspect `getTool().examples` for a full
3325
+ * Deepline response example. Optional `toolResponse.responseMeta` contains
3326
+ * response metadata.
3325
3327
  * Top-level fields such as `status`, `job_id`, and `billing` describe the
3326
3328
  * Deepline execution envelope.
3327
3329
  */
@@ -4346,7 +4348,7 @@ export class DeeplineClient {
4346
4348
  });
4347
4349
  } catch (error) {
4348
4350
  failedLogsLoaded = false;
4349
- const retryCommand = `deepline runs get ${runId} --log-failed --json`;
4351
+ const retryCommand = `deepline runs logs ${runId} --failed --json`;
4350
4352
  const reason =
4351
4353
  error instanceof Error && error.message.trim()
4352
4354
  ? ` (${error.message.trim().slice(0, 500)})`
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.290',
203
+ version: '0.3.292',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -35,7 +35,7 @@ function lifecycleActions(status: PlayStatus, id: string, state?: string) {
35
35
  : {}),
36
36
  ...(state === 'failed'
37
37
  ? {
38
- getFailedLogsCmd: `deepline runs get ${id} --log-failed --json`,
38
+ getFailedLogsCmd: `deepline runs logs ${id} --failed --json`,
39
39
  ...(status.rerunCommand ? { rerunCmd: status.rerunCommand } : {}),
40
40
  }
41
41
  : {}),
@@ -149,7 +149,10 @@ function datasetSummary(value: unknown, conflictingPopulation: boolean) {
149
149
  }
150
150
 
151
151
  /** Action descriptions are affordances, never evidence that an action ran. */
152
- function actions(value: unknown, names: Record<string, string>) {
152
+ function actions<const Names extends Record<string, string>>(
153
+ value: unknown,
154
+ names: Names,
155
+ ): Partial<Record<Names[keyof Names], string>> {
153
156
  return Object.fromEntries(
154
157
  Object.entries(record(value) ?? {}).flatMap(([name, value]) => {
155
158
  const action = record(value);
@@ -167,7 +170,7 @@ function actions(value: unknown, names: Record<string, string>) {
167
170
  ],
168
171
  ];
169
172
  }),
170
- );
173
+ ) as Partial<Record<Names[keyof Names], string>>;
171
174
  }
172
175
 
173
176
  /**
@@ -175,7 +178,10 @@ function actions(value: unknown, names: Record<string, string>) {
175
178
  * from a server that predates it, the same derivation over the step's
176
179
  * reported status and map progress.
177
180
  */
178
- function stepExecution(step: RecordValue, runStatus: string): NodeExecutionState {
181
+ function stepExecution(
182
+ step: RecordValue,
183
+ runStatus: string,
184
+ ): NodeExecutionState {
179
185
  const decoded = decodeNodeExecutionState(step.execution);
180
186
  if (decoded) return decoded;
181
187
  const progress = record(step.progress);
@@ -323,7 +329,11 @@ export function buildRunOverview(status: PlayStatus, fullStatus?: PlayStatus) {
323
329
  );
324
330
  return {
325
331
  datasetId: dataset.datasetId,
326
- name: dataset.tableNamespace,
332
+ name:
333
+ dataset.tableNamespace ??
334
+ (typeof stepOutput?.tableNamespace === 'string'
335
+ ? stepOutput.tableNamespace
336
+ : dataset.path),
327
337
  stepId: step?.id ?? named[0]?.[1].stepId,
328
338
  rowCount: dataset.rowCount ?? null,
329
339
  ...(dataset.lifecycle ? { lifecycle: dataset.lifecycle } : {}),
@@ -456,7 +466,7 @@ export function buildRunOverview(status: PlayStatus, fullStatus?: PlayStatus) {
456
466
  function receiptAction(id: string) {
457
467
  const quotedId = `'${id.replace(/'/g, "'\\''")}'`;
458
468
  return {
459
- getReceiptCmd: `deepline runs receipt ${quotedId} --key '<receipt-key>' --json`,
469
+ getReceiptCmd: `deepline runs logs ${quotedId} --kind receipt.completed --payloads --json`,
460
470
  };
461
471
  }
462
472
 
@@ -226,6 +226,24 @@ export interface ToolDefinition extends ToolExecutionMetadataFields {
226
226
  inputSchema?: Record<string, unknown>;
227
227
  /** JSON Schema describing the tool's output shape. */
228
228
  outputSchema?: Record<string, unknown>;
229
+ /** JSON Schema for the Deepline API execution response, including toolResponse.rawV2. */
230
+ responseSchema?: Record<string, unknown>;
231
+ /** Illustrative input and full API response examples, separate from schemas. */
232
+ examples?: Array<{
233
+ input: Record<string, unknown>;
234
+ inputSource: 'sample' | 'schema';
235
+ responseSource: 'sample' | 'schema' | 'no_result';
236
+ description: string;
237
+ response: {
238
+ job_id: string;
239
+ status: string;
240
+ toolResponse: {
241
+ rawV2: unknown;
242
+ view: 'data' | 'rawV2';
243
+ responseMeta?: Record<string, unknown>;
244
+ };
245
+ };
246
+ }>;
229
247
  /** User-facing pricing summary. Internal provider/settlement costs are intentionally omitted. */
230
248
  pricing?: ToolPricingSummary | null;
231
249
  /** How to use the tool well: the provider's summary and the tool's own notes. */
@@ -600,7 +618,8 @@ export interface ToolSearchResult {
600
618
  * const meta = await client.getTool('dropleads_search_people');
601
619
  * console.log(meta.displayName); // "DropLeads People Search"
602
620
  * console.log(meta.estimatedCreditsRange); // "1-5"
603
- * console.log(meta.samples); // { input: {...}, output: {...} }
621
+ * console.log(meta.examples?.[0].input); // example input payload
622
+ * console.log(meta.examples?.[0].response); // full Deepline execution response
604
623
  * ```
605
624
  */
606
625
  export interface ToolMetadata extends ToolDefinition {
@@ -632,7 +651,7 @@ export interface ToolMetadata extends ToolDefinition {
632
651
  estimateBasedOnTools?: string[];
633
652
  /** Individual step cost contributions (for composite tools). */
634
653
  stepContributions?: unknown[];
635
- /** Example input/output pairs for documentation and testing. */
654
+ /** Tool body samples. Full execution response examples are available in examples. */
636
655
  samples?: Record<string, unknown>;
637
656
  }
638
657
 
@@ -13,7 +13,10 @@ import type { AsyncPlayRunRef } from '../plays/authoring-contract';
13
13
  import type { PlayRowUpdate } from './/ctx-types';
14
14
  import type { PlayRunTimelineEntry } from './/live-events';
15
15
  import type { PlayDatasetBornFrom } from './/cell-provenance';
16
- import type { PlayLogContext } from './/log-context';
16
+ import type {
17
+ PlayLogContext,
18
+ PlayLogMetadata,
19
+ } from './/log-context';
17
20
  import {
18
21
  routePlayActivityObservation,
19
22
  type PlayActivityObservation,
@@ -111,6 +114,7 @@ export type RuntimeStatusUpdate = {
111
114
  liveLogs?: string[];
112
115
  /** Search-only context aligned to `liveLogs`; stripped before Convex. */
113
116
  liveLogContexts?: Array<PlayLogContext | null>;
117
+ liveLogMetadata?: Array<PlayLogMetadata | null>;
114
118
  liveLogIds?: Array<string | null>;
115
119
  /**
116
120
  * Monotonic count of log lines the producer ever emitted on this run's
@@ -160,6 +164,7 @@ export type RuntimeStatusUpdate = {
160
164
  terminalLogReplay?: {
161
165
  lines: string[];
162
166
  lineContexts?: Array<PlayLogContext | null>;
167
+ lineMetadata?: Array<PlayLogMetadata | null>;
163
168
  lineIds?: Array<string | null>;
164
169
  totalCount?: number;
165
170
  dedupeMode?: 'semantic' | 'exact';
@@ -2219,6 +2224,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
2219
2224
  ? slicePositionalLogLines({
2220
2225
  bufferLines: update.liveLogs,
2221
2226
  bufferContexts: update.liveLogContexts,
2227
+ bufferMetadata: update.liveLogMetadata,
2222
2228
  bufferIds: update.liveLogIds,
2223
2229
  bufferTotalCount: update.liveLogTotalCount ?? update.liveLogs.length,
2224
2230
  sentCount,
@@ -2231,6 +2237,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
2231
2237
  ? {
2232
2238
  lines: update.liveLogs,
2233
2239
  lineContexts: update.liveLogContexts,
2240
+ lineMetadata: update.liveLogMetadata,
2234
2241
  lineIds: update.liveLogIds,
2235
2242
  totalCount: update.liveLogTotalCount,
2236
2243
  producerAttempt: update.liveLogProducerAttempt,
@@ -2256,6 +2263,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
2256
2263
  ...logSlice,
2257
2264
  lines: prefix,
2258
2265
  lineContexts: logSlice.lineContexts?.slice(0, prefix.length),
2266
+ lineMetadata: logSlice.lineMetadata?.slice(0, prefix.length),
2259
2267
  lineIds: logSlice.lineIds?.slice(0, prefix.length),
2260
2268
  };
2261
2269
  }
@@ -2268,6 +2276,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
2268
2276
  lastCheckpointAt: update.lastCheckpointAt ?? null,
2269
2277
  liveLogs: statusPatchLogSlice?.lines ?? null,
2270
2278
  liveLogContexts: statusPatchLogSlice?.lineContexts ?? null,
2279
+ liveLogMetadata: statusPatchLogSlice?.lineMetadata ?? null,
2271
2280
  liveLogIds: statusPatchLogSlice?.lineIds ?? null,
2272
2281
  liveLogsChannelOffset: statusPatchLogSlice?.channelOffset ?? null,
2273
2282
  liveLogsProducerAttempt: update.liveLogProducerAttempt ?? null,
@@ -2354,6 +2363,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
2354
2363
  occurredAt: update.lastCheckpointAt ?? Date.now(),
2355
2364
  lines: terminalLogReplay.lines,
2356
2365
  lineContexts: terminalLogReplay.lineContexts,
2366
+ lineMetadata: terminalLogReplay.lineMetadata,
2357
2367
  lineIds: terminalLogReplay.lineIds,
2358
2368
  liveLogTotalCount: terminalLogReplay.totalCount,
2359
2369
  dedupeMode: terminalLogReplay.dedupeMode,
@@ -216,7 +216,11 @@ import {
216
216
  ToolExecuteAuthScopeChangedError,
217
217
  type ToolExecuteHttpFailureOutcome,
218
218
  } from './tool-execute-retry-policy';
219
- import { createToolHttpError } from './tool-http-errors';
219
+ import {
220
+ createToolHttpError,
221
+ attachToolCallFailureEvidenceFromResponse,
222
+ readToolCallFailureEvidence,
223
+ } from './tool-http-errors';
220
224
  import {
221
225
  describeTransportError,
222
226
  transportGatewayOriginForDiagnostic,
@@ -255,6 +259,7 @@ import {
255
259
  } from './play-artifact-runtime-contract';
256
260
  import {
257
261
  LOG_LEVELS,
262
+ classifyLogLine,
258
263
  formatCtxFetchHttpFailureDiagnostic,
259
264
  tagLogEntry,
260
265
  tagLogLevel,
@@ -446,7 +451,11 @@ import {
446
451
  RunLogBuffer,
447
452
  RUNNER_LOG_CONTEXT_MAX_BYTES,
448
453
  } from './run-log-buffer';
449
- import { normalizePlayLogContext, type PlayLogContext } from './log-context';
454
+ import {
455
+ normalizePlayLogContext,
456
+ type PlayLogContext,
457
+ type PlayLogMetadata,
458
+ } from './log-context';
450
459
  import type { AsyncPlayRunRef } from '../plays/authoring-contract';
451
460
  import { admitAsyncPlayLineage } from './async-play-invocation';
452
461
  import {
@@ -536,6 +545,11 @@ type InlineCompositionStore = {
536
545
  };
537
546
  const inlineCompositionContext =
538
547
  new AsyncLocalStorage<InlineCompositionStore>();
548
+ // Authored scope is async-local: concurrent steps and rows cannot steal attribution.
549
+ const authoredStepContext = new AsyncLocalStorage<{
550
+ context: PlayContextImpl;
551
+ stepId: string;
552
+ }>();
539
553
  const toolExecutionOverrides = new AsyncLocalStorage<{ force: boolean }>();
540
554
  const PROGRESS_HEARTBEAT_INTERVAL_MS = 1_000;
541
555
  const PURE_JS_HEARTBEAT_ROW_INTERVAL = 250;
@@ -6387,11 +6401,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
6387
6401
  ? {
6388
6402
  relayReceiptOutput: {
6389
6403
  receiptKey: input.resultReceiptKey,
6390
- execution: toolExecutionMetadataForOutcome({
6391
- kind: 'live',
6392
- cacheKey: input.request.cacheKey,
6393
- receiptKey: input.request.receiptKey,
6394
- }),
6404
+ execution: {
6405
+ ...toolExecutionMetadataForOutcome({
6406
+ kind: 'live',
6407
+ cacheKey: input.request.cacheKey,
6408
+ receiptKey: input.request.receiptKey,
6409
+ }),
6410
+ resultReceiptKey: input.resultReceiptKey,
6411
+ },
6395
6412
  metadata: await this.resolveToolResultMetadata(
6396
6413
  input.dispatchToolId,
6397
6414
  ),
@@ -6431,11 +6448,16 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
6431
6448
  meta: execution.meta,
6432
6449
  billing: execution.billing,
6433
6450
  requestInput: input.dispatchPayload,
6434
- execution: toolExecutionMetadataForOutcome({
6435
- kind: 'live',
6436
- cacheKey: input.request.cacheKey,
6437
- receiptKey: input.request.receiptKey,
6438
- }),
6451
+ execution: {
6452
+ ...toolExecutionMetadataForOutcome({
6453
+ kind: 'live',
6454
+ cacheKey: input.request.cacheKey,
6455
+ receiptKey: input.request.receiptKey,
6456
+ }),
6457
+ ...(!alwaysFresh && input.resultReceiptKey
6458
+ ? { resultReceiptKey: input.resultReceiptKey }
6459
+ : {}),
6460
+ },
6439
6461
  });
6440
6462
  // First sub-phase of `resultDelivery`: the body was parsed above, and
6441
6463
  // this is the instant the row's result actually exists. Everything
@@ -7052,7 +7074,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
7052
7074
 
7053
7075
  private async serializeCellValue(value: unknown): Promise<unknown> {
7054
7076
  const serialized = isToolExecuteResult(value)
7055
- ? serializeToolExecuteResult(value)
7077
+ ? serializeToolExecuteResult(value, { includeCallEvidence: false })
7056
7078
  : await this.serializeDatasetCells(value);
7057
7079
  try {
7058
7080
  stringifyPostgresJson(serialized);
@@ -7073,7 +7095,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
7073
7095
  const visiting = new WeakSet<object>();
7074
7096
  const visit = async (current: unknown): Promise<unknown> => {
7075
7097
  if (isToolExecuteResult(current)) {
7076
- return serializeToolExecuteResult(current);
7098
+ return serializeToolExecuteResult(current, {
7099
+ includeCallEvidence: false,
7100
+ });
7077
7101
  }
7078
7102
  if (isPlayDataset(current)) {
7079
7103
  return await serializePlayDatasetCell(current, visit);
@@ -7379,7 +7403,20 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
7379
7403
  input.metadata ??
7380
7404
  (await this.resolveToolResultMetadata(input.toolId)),
7381
7405
  execution: input.execution,
7382
- meta: input.meta,
7406
+ meta: {
7407
+ ...input.meta,
7408
+ ...(input.execution.source === 'live' &&
7409
+ input.requestInput &&
7410
+ !input.meta?.callEvidence
7411
+ ? {
7412
+ callEvidence: {
7413
+ input: this.secretRedactor.redact(input.requestInput),
7414
+ providerRequestsUnavailable:
7415
+ 'This response did not retain provider HTTP evidence.',
7416
+ },
7417
+ }
7418
+ : {}),
7419
+ },
7383
7420
  billing: input.billing,
7384
7421
  }),
7385
7422
  );
@@ -12582,6 +12619,54 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12582
12619
  };
12583
12620
  }
12584
12621
 
12622
+ private async saveToolFailureEvidence(input: {
12623
+ toolId: string;
12624
+ callKey: string;
12625
+ input: Record<string, unknown>;
12626
+ error: unknown;
12627
+ context: PlayLogContext;
12628
+ }): Promise<string | undefined> {
12629
+ if (!this.#options.completeRuntimeStepReceipt) return;
12630
+ try {
12631
+ // An immutable diagnostic record in the SAME receipt store, discovered
12632
+ // through runtime_observability.events. It is never an executable result
12633
+ // cache and cannot change provider admission or replay semantics.
12634
+ const receipt = await this.completeRuntimeStepReceipt(
12635
+ `${buildDurableToolReceiptPrefix({
12636
+ orgId: this.#options.orgId ?? 'unknown-org',
12637
+ toolId: 'call-evidence',
12638
+ })}${stableDigest(`${this.currentRunId}:${crypto.randomUUID()}`)}`,
12639
+ this.currentRunId,
12640
+ this.secretRedactor.redact({
12641
+ kind: 'tool_call_evidence',
12642
+ toolId: input.toolId,
12643
+ callKey: input.callKey,
12644
+ status: 'failed',
12645
+ context: input.context,
12646
+ callEvidence: readToolCallFailureEvidence(input.error) ?? {
12647
+ input: input.input,
12648
+ providerRequestsUnavailable:
12649
+ 'This failure did not retain provider HTTP evidence.',
12650
+ },
12651
+ error:
12652
+ serializeToolExecutionFailure(input.error) ??
12653
+ this.formatRuntimeError(input.error),
12654
+ }),
12655
+ COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID,
12656
+ );
12657
+ if (!receipt)
12658
+ throw new Error('Saved failure evidence was not acknowledged.');
12659
+ return receipt.key;
12660
+ } catch (error) {
12661
+ // Keep the original failure and never repeat a paid operation just to
12662
+ // repair observability. A missing record is explicit in retained logs.
12663
+ this.runtimeLog(
12664
+ `Saved call details unavailable: ${this.formatRuntimeError(error)}`,
12665
+ { level: 'warn' },
12666
+ );
12667
+ }
12668
+ }
12669
+
12585
12670
  private async executeTool(
12586
12671
  key: string,
12587
12672
  toolId: string,
@@ -12610,13 +12695,31 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12610
12695
  !isPlayRowExecutionSuspendedError(error) &&
12611
12696
  !(error instanceof PlayRowProviderBackpressureError)
12612
12697
  ) {
12698
+ const scope = authoredStepContext.getStore();
12699
+ const savedKey = await this.saveToolFailureEvidence({
12700
+ toolId,
12701
+ callKey: context.call_key,
12702
+ context: {
12703
+ callKey: context.call_key,
12704
+ toolId,
12705
+ callStatus: 'failed',
12706
+ ...(scope?.context === this ? { stepId: scope.stepId } : {}),
12707
+ ...(context.dataset ? { dataset: context.dataset } : {}),
12708
+ ...(context.column ? { column: context.column } : {}),
12709
+ ...(context.row_key ? { rowKey: context.row_key } : {}),
12710
+ },
12711
+ input,
12712
+ error,
12713
+ });
12613
12714
  this.runtimeLog(
12614
12715
  JSON.stringify({
12615
12716
  event: 'tool_call_failed',
12616
12717
  ...context,
12718
+ ...(savedKey ? { receipt_key: savedKey } : {}),
12617
12719
  status: 'failed',
12618
12720
  duration_ms: Math.max(0, Date.now() - startedAt),
12619
12721
  }),
12722
+ { level: 'error' },
12620
12723
  );
12621
12724
  }
12622
12725
  throw error;
@@ -12627,7 +12730,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12627
12730
  JSON.stringify({
12628
12731
  event: 'tool_call_completed',
12629
12732
  ...context,
12630
- ...(execution?.receiptKey ? { receipt_key: execution.receiptKey } : {}),
12733
+ ...(execution?.resultReceiptKey || execution?.receiptKey
12734
+ ? { receipt_key: execution.resultReceiptKey ?? execution.receiptKey }
12735
+ : {}),
12631
12736
  ...(execution?.source ? { execution_source: execution.source } : {}),
12632
12737
  status: toolResult?.status ?? 'completed',
12633
12738
  // Caller wall time includes queueing/reuse and result delivery. It is
@@ -14287,7 +14392,21 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14287
14392
  ): void {
14288
14393
  const boundedLine = boundRunLogLine(line);
14289
14394
  const omittedBefore = this.logBuffer.snapshot().omittedLines;
14290
- for (const retained of this.logBuffer.append(line, terminal, context)) {
14395
+ const scope = authoredStepContext.getStore();
14396
+ const classified = classifyLogLine(line);
14397
+ const timestamp = classified.line.match(/^\[([^\]]+)\]/)?.[1];
14398
+ const parsedAt = timestamp ? Date.parse(timestamp) : NaN;
14399
+ const metadata: PlayLogMetadata = {
14400
+ level: classified.level,
14401
+ occurredAt: Number.isFinite(parsedAt) ? parsedAt : Date.now(),
14402
+ ...(scope?.context === this ? { stepId: scope.stepId } : {}),
14403
+ };
14404
+ for (const retained of this.logBuffer.append(
14405
+ line,
14406
+ terminal,
14407
+ context,
14408
+ metadata,
14409
+ )) {
14291
14410
  const originalLineWasRetained =
14292
14411
  retained === boundedLine &&
14293
14412
  this.logBuffer.snapshot().omittedLines === omittedBefore;
@@ -14297,6 +14416,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14297
14416
  ? (this.logBuffer.contexts.at(-1) ?? undefined)
14298
14417
  : undefined,
14299
14418
  this.logBuffer.ids.at(-1),
14419
+ originalLineWasRetained
14420
+ ? (this.logBuffer.metadata.at(-1) ?? undefined)
14421
+ : undefined,
14300
14422
  );
14301
14423
  if (this.#options.verbose) console.log(retained);
14302
14424
  }
@@ -14960,42 +15082,76 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14960
15082
  return output;
14961
15083
  };
14962
15084
 
14963
- return this.executeWithRuntimeReceipt<T>(
14964
- 'step',
14965
- normalizedKey,
14966
- this.currentRunId,
14967
- {
14968
- semanticKey: rowStore
14969
- ? stableDigest(
14970
- stableStringify({
14971
- scope: 'row',
14972
- tableNamespace: rowStore.tableNamespace ?? null,
14973
- rowKey: rowStore.rowKey ?? null,
14974
- rowId: rowStore.rowKey ? null : rowStore.rowId,
14975
- fieldName: rowStore.fieldName ?? null,
14976
- callIndex,
14977
- // Row scope prevents unrelated map cells from sharing a
14978
- // checkpoint. A supplied semantic key identifies the actual
14979
- // work (for example, ctx.runSteps hashes its current input),
14980
- // so it must refine row scope rather than being discarded.
14981
- semanticKey: options?.semanticKey ?? null,
14982
- }),
14983
- )
14984
- : options?.semanticKey,
14985
- staleAfterSeconds: options?.staleAfterSeconds,
14986
- force: this.#options.cachePolicy?.forceStepRefresh === true,
14987
- markSkipped: (output) => {
14988
- assertJsonSerializableStepOutput(normalizedKey, output);
14989
- },
14990
- execute: executeStep,
14991
- },
14992
- );
15085
+ const observe = !rowStore;
15086
+ const startedAt = Date.now();
15087
+ if (observe)
15088
+ this.emitExecutionEvent({
15089
+ type: 'step.started',
15090
+ stepId: normalizedKey,
15091
+ at: startedAt,
15092
+ });
15093
+ try {
15094
+ const output = await authoredStepContext.run(
15095
+ { context: this, stepId: normalizedKey },
15096
+ () =>
15097
+ this.executeWithRuntimeReceipt<T>(
15098
+ 'step',
15099
+ normalizedKey,
15100
+ this.currentRunId,
15101
+ {
15102
+ semanticKey: rowStore
15103
+ ? stableDigest(
15104
+ stableStringify({
15105
+ scope: 'row',
15106
+ tableNamespace: rowStore.tableNamespace ?? null,
15107
+ rowKey: rowStore.rowKey ?? null,
15108
+ rowId: rowStore.rowKey ? null : rowStore.rowId,
15109
+ fieldName: rowStore.fieldName ?? null,
15110
+ callIndex,
15111
+ // Row scope prevents unrelated map cells from sharing a
15112
+ // checkpoint. A supplied semantic key identifies the actual
15113
+ // work (for example, ctx.runSteps hashes its current input),
15114
+ // so it must refine row scope rather than being discarded.
15115
+ semanticKey: options?.semanticKey ?? null,
15116
+ }),
15117
+ )
15118
+ : options?.semanticKey,
15119
+ staleAfterSeconds: options?.staleAfterSeconds,
15120
+ force: this.#options.cachePolicy?.forceStepRefresh === true,
15121
+ markSkipped: (output) => {
15122
+ assertJsonSerializableStepOutput(normalizedKey, output);
15123
+ },
15124
+ execute: executeStep,
15125
+ },
15126
+ ),
15127
+ );
15128
+ if (observe)
15129
+ this.emitExecutionEvent({
15130
+ type: 'step.completed',
15131
+ stepId: normalizedKey,
15132
+ at: Date.now(),
15133
+ });
15134
+ return output;
15135
+ } catch (error) {
15136
+ if (observe && !isControlledPlaySuspension(error))
15137
+ this.emitExecutionEvent({
15138
+ type: 'step.failed',
15139
+ stepId: normalizedKey,
15140
+ at: Date.now(),
15141
+ error: buildPlayDocflowNodeErrorPreview(error, this.secretRedactor),
15142
+ });
15143
+ throw error;
15144
+ }
14993
15145
  }
14994
15146
 
14995
15147
  getLogs(): string[] {
14996
15148
  return this.logBuffer.lines;
14997
15149
  }
14998
15150
 
15151
+ getLogMetadata(): Array<PlayLogMetadata | null> {
15152
+ return this.logBuffer.metadata.slice();
15153
+ }
15154
+
14999
15155
  getLogIds(): string[] {
15000
15156
  return this.logBuffer.ids.slice();
15001
15157
  }
@@ -15582,15 +15738,16 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
15582
15738
  source: 'cache' | 'owner',
15583
15739
  ): Promise<void> => {
15584
15740
  const receiptKey = request.receiptKey!;
15585
- const finalResult = cloneToolExecuteResultWithExecution(
15586
- result,
15587
- toolExecutionMetadataForOutcome({
15741
+ const finalResult = cloneToolExecuteResultWithExecution(result, {
15742
+ ...toolExecutionMetadataForOutcome({
15588
15743
  kind: source === 'owner' ? 'live' : source,
15589
15744
  cacheKey: request.cacheKey,
15590
15745
  receiptKey,
15591
15746
  ...(source === 'owner' ? {} : { attachedToReceiptKey: receiptKey }),
15592
15747
  }),
15593
- );
15748
+ resultReceiptKey:
15749
+ this.mappedToolResultReceiptPolicy(request).resultReceiptKey,
15750
+ });
15594
15751
  this.cacheToolResult(toolId, request.cacheKey, finalResult);
15595
15752
  const resolver = this.toolCallResolvers.get(request.callId);
15596
15753
  if (resolver) {
@@ -19305,6 +19462,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19305
19462
  `gateway=${transportGatewayOriginForDiagnostic(url)} attempt=${httpFailureAttempt})`,
19306
19463
  { level: 'error' },
19307
19464
  );
19465
+ attachToolCallFailureEvidenceFromResponse(surfacedError, text);
19308
19466
  throw surfacedError;
19309
19467
  }
19310
19468
 
@@ -11,7 +11,7 @@ import type { PlayRunContractSnapshot } from '../plays/contracts';
11
11
  import type { PlayDatasetColumnTypes } from '../play-data-plane/contracts';
12
12
  import type { PlayStructuredDefinition } from '../plays/definition';
13
13
  import type { PlayStaticPipeline } from '../plays/static-pipeline';
14
- import type { PlayLogContext } from './log-context';
14
+ import type { PlayLogContext, PlayLogMetadata } from './log-context';
15
15
  import type {
16
16
  AsyncPlayRunRef,
17
17
  DurableCallStaleAfterSeconds,
@@ -514,6 +514,9 @@ export interface MapExecutionScope {
514
514
  }
515
515
 
516
516
  export type PlayExecutionEvent =
517
+ | { type: 'step.started'; stepId: string; at: number }
518
+ | { type: 'step.completed'; stepId: string; at: number }
519
+ | { type: 'step.failed'; stepId: string; error: string; at: number }
517
520
  | PlayActivityEvent
518
521
  | {
519
522
  type: 'docflow.node.hit';
@@ -851,7 +854,12 @@ export interface ContextOptions {
851
854
  /** Called after each durable batch completes for scheduler checkpointing. */
852
855
  onBatchComplete?: (checkpoint: PlayCheckpoint) => void;
853
856
  /** Called when the runtime emits a new execution log line. */
854
- onLog?: (line: string, context?: PlayLogContext, entryId?: string) => void;
857
+ onLog?: (
858
+ line: string,
859
+ context?: PlayLogContext,
860
+ entryId?: string,
861
+ metadata?: PlayLogMetadata,
862
+ ) => void;
855
863
  /**
856
864
  * Runtime-provided HTTP transport for ctx.fetch. Node runtimes should provide
857
865
  * a transport that validates resolved IP addresses at connect time.