deepline 0.1.146 → 0.1.148

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
@@ -418,10 +418,11 @@ var SDK_RELEASE = {
418
418
  // 0.1.108 ships explicit dataset column/tool recompute policy and removes
419
419
  // the SDK enrich generator's one-second stale policy.
420
420
  // 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
421
- version: "0.1.146",
422
- apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
421
+ // 0.1.111 ships dataset-native tool list getters and result row datasets.
422
+ version: "0.1.148",
423
+ apiContract: "2026-06-dataset-handle-results-hard-cutover",
423
424
  supportPolicy: {
424
- latest: "0.1.146",
425
+ latest: "0.1.148",
425
426
  minimumSupported: "0.1.53",
426
427
  deprecatedBelow: "0.1.53",
427
428
  commandMinimumSupported: [
@@ -432,52 +433,79 @@ var SDK_RELEASE = {
432
433
  },
433
434
  {
434
435
  command: "plays",
435
- minimumSupported: "0.1.110",
436
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
436
+ minimumSupported: "0.1.111",
437
+ reason: "Play file commands now use dataset-native list getters and result row datasets."
437
438
  },
438
439
  {
439
440
  command: "plays run",
440
- minimumSupported: "0.1.110",
441
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
441
+ minimumSupported: "0.1.111",
442
+ reason: "Play run results now promote row-shaped outputs into dataset handles for safe export."
442
443
  },
443
444
  {
444
445
  command: "run",
445
446
  displayCommand: "plays run",
446
- minimumSupported: "0.1.110",
447
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
447
+ minimumSupported: "0.1.111",
448
+ reason: "Play run results now promote row-shaped outputs into dataset handles for safe export."
448
449
  },
449
450
  {
450
451
  command: "plays check",
451
- minimumSupported: "0.1.110",
452
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
452
+ minimumSupported: "0.1.111",
453
+ reason: "Play file checks now validate dataset-native list getter authoring."
453
454
  },
454
455
  {
455
456
  command: "check",
456
457
  displayCommand: "plays check",
457
- minimumSupported: "0.1.110",
458
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
458
+ minimumSupported: "0.1.111",
459
+ reason: "Play file checks now validate dataset-native list getter authoring."
459
460
  },
460
461
  {
461
462
  command: "plays publish",
462
- minimumSupported: "0.1.110",
463
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
463
+ minimumSupported: "0.1.111",
464
+ reason: "Published play artifacts now target dataset-native list getters and result row datasets."
464
465
  },
465
466
  {
466
467
  command: "publish",
467
468
  displayCommand: "plays publish",
468
- minimumSupported: "0.1.110",
469
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
469
+ minimumSupported: "0.1.111",
470
+ reason: "Published play artifacts now target dataset-native list getters and result row datasets."
470
471
  },
471
472
  {
472
473
  command: "plays set-live",
473
- minimumSupported: "0.1.110",
474
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
474
+ minimumSupported: "0.1.111",
475
+ reason: "Published play artifacts now target dataset-native list getters and result row datasets."
475
476
  },
476
477
  {
477
478
  command: "set-live",
478
479
  displayCommand: "plays set-live",
479
- minimumSupported: "0.1.110",
480
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
480
+ minimumSupported: "0.1.111",
481
+ reason: "Published play artifacts now target dataset-native list getters and result row datasets."
482
+ },
483
+ {
484
+ command: "runs",
485
+ minimumSupported: "0.1.111",
486
+ reason: "Run result rows now render as dataset handles with explicit export commands."
487
+ },
488
+ {
489
+ command: "runs get",
490
+ minimumSupported: "0.1.111",
491
+ reason: "Run result rows now render as dataset handles with explicit export commands."
492
+ },
493
+ {
494
+ command: "get",
495
+ displayCommand: "runs get",
496
+ minimumSupported: "0.1.111",
497
+ reason: "Run result rows now render as dataset handles with explicit export commands."
498
+ },
499
+ {
500
+ command: "runs export",
501
+ minimumSupported: "0.1.111",
502
+ reason: "Run result row datasets now use the dataset-handle export contract."
503
+ },
504
+ {
505
+ command: "export",
506
+ displayCommand: "runs export",
507
+ minimumSupported: "0.1.111",
508
+ reason: "Run result row datasets now use the dataset-handle export contract."
481
509
  }
482
510
  ],
483
511
  autoUpdatePatchLag: 2
@@ -3973,6 +4001,420 @@ function isDeeplineExtractorTarget(value) {
3973
4001
  return value in DEEPLINE_EXTRACTOR_TARGET_DEFINITIONS;
3974
4002
  }
3975
4003
 
4004
+ // ../shared_libs/plays/dataset.ts
4005
+ var PLAY_DATASET_BRAND = /* @__PURE__ */ Symbol.for("deepline.play.dataset");
4006
+ var NODE_INSPECT_CUSTOM = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
4007
+ var DEFAULT_MATERIALIZE_LIMIT = 1e4;
4008
+ function resolveMaterializeLimitCap() {
4009
+ const raw = process.env.DEEPLINE_PLAY_DATASET_MATERIALIZE_LIMIT;
4010
+ const parsed = raw ? Number(raw) : NaN;
4011
+ if (Number.isFinite(parsed) && parsed > 0) {
4012
+ return Math.floor(parsed);
4013
+ }
4014
+ return DEFAULT_MATERIALIZE_LIMIT;
4015
+ }
4016
+ function inferPreviewColumns(rows) {
4017
+ const columns = /* @__PURE__ */ new Set();
4018
+ for (const row of rows) {
4019
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
4020
+ continue;
4021
+ }
4022
+ for (const key of Object.keys(row)) {
4023
+ columns.add(key);
4024
+ }
4025
+ }
4026
+ return columns.size > 0 ? [...columns] : void 0;
4027
+ }
4028
+ var DeferredPlayDataset = class {
4029
+ [PLAY_DATASET_BRAND] = true;
4030
+ datasetKind;
4031
+ datasetId;
4032
+ backing;
4033
+ sourceLabel;
4034
+ tableNamespace;
4035
+ previewRows;
4036
+ previewColumns;
4037
+ workProgress;
4038
+ cachedCount;
4039
+ resolvers;
4040
+ constructor(input) {
4041
+ this.datasetKind = input.datasetKind;
4042
+ this.datasetId = input.datasetId;
4043
+ this.cachedCount = input.count;
4044
+ this.backing = input.backing;
4045
+ this.previewRows = input.previewRows;
4046
+ this.previewColumns = inferPreviewColumns(this.previewRows);
4047
+ this.sourceLabel = input.sourceLabel ?? null;
4048
+ this.tableNamespace = input.tableNamespace ?? null;
4049
+ this.workProgress = input.workProgress;
4050
+ this.resolvers = input.resolvers;
4051
+ }
4052
+ async count() {
4053
+ this.cachedCount = await this.resolvers.count();
4054
+ return this.cachedCount;
4055
+ }
4056
+ async peek(limit = 10) {
4057
+ if (limit <= this.previewRows.length) {
4058
+ return this.previewRows.slice(0, Math.max(0, limit));
4059
+ }
4060
+ return await this.resolvers.peek(limit);
4061
+ }
4062
+ map(mapper, options) {
4063
+ return createTransformedPlayDataset(this, { kind: "map", mapper }, options);
4064
+ }
4065
+ filter(predicate, options) {
4066
+ return createTransformedPlayDataset(
4067
+ this,
4068
+ { kind: "filter", predicate },
4069
+ options
4070
+ );
4071
+ }
4072
+ slice(start, end, options) {
4073
+ return createTransformedPlayDataset(
4074
+ this,
4075
+ { kind: "slice", start, end },
4076
+ options
4077
+ );
4078
+ }
4079
+ take(limit, options) {
4080
+ return this.slice(0, limit, options);
4081
+ }
4082
+ async materialize(limit) {
4083
+ const requestedLimit = limit !== void 0 ? Math.max(0, Math.floor(limit)) : void 0;
4084
+ const cap = resolveMaterializeLimitCap();
4085
+ if (requestedLimit !== void 0) {
4086
+ if (requestedLimit > cap) {
4087
+ throw new Error(
4088
+ `PlayDataset.materialize(${requestedLimit}) exceeds the hard limit of ${cap} rows. Return the dataset handle instead, or request a smaller bounded slice.`
4089
+ );
4090
+ }
4091
+ return await this.resolvers.materialize(requestedLimit);
4092
+ }
4093
+ const count = await this.count();
4094
+ if (count > cap) {
4095
+ throw new Error(
4096
+ `PlayDataset.materialize() refuses to load ${count} rows into memory. The hard limit is ${cap}. Return the dataset handle instead or call materialize(limit).`
4097
+ );
4098
+ }
4099
+ return await this.resolvers.materialize();
4100
+ }
4101
+ async *[Symbol.asyncIterator]() {
4102
+ for await (const row of this.resolvers.iterate()) {
4103
+ yield row;
4104
+ }
4105
+ }
4106
+ toJSON() {
4107
+ return {
4108
+ kind: "dataset",
4109
+ datasetKind: this.datasetKind,
4110
+ datasetId: this.datasetId,
4111
+ count: this.cachedCount,
4112
+ ...this.backing ? { backing: this.backing } : {},
4113
+ ...this.sourceLabel ? { sourceLabel: this.sourceLabel } : {},
4114
+ ...this.tableNamespace ? { tableNamespace: this.tableNamespace } : {},
4115
+ ...this.previewColumns ? { columns: this.previewColumns } : {},
4116
+ ...this.workProgress ? { _metadata: { workProgress: this.workProgress } } : {},
4117
+ preview: [...this.previewRows]
4118
+ };
4119
+ }
4120
+ [NODE_INSPECT_CUSTOM]() {
4121
+ return this.toJSON();
4122
+ }
4123
+ };
4124
+ function normalizeSliceBounds(input) {
4125
+ const count = Math.max(0, Math.floor(input.count));
4126
+ const rawStart = input.start ?? 0;
4127
+ const rawEnd = input.end ?? count;
4128
+ const startInteger = Number.isFinite(rawStart) ? Math.trunc(rawStart) : 0;
4129
+ const endInteger = Number.isFinite(rawEnd) ? Math.trunc(rawEnd) : count;
4130
+ const start = startInteger < 0 ? Math.max(count + startInteger, 0) : Math.min(startInteger, count);
4131
+ const end = endInteger < 0 ? Math.max(count + endInteger, 0) : Math.min(endInteger, count);
4132
+ return { start, end: Math.max(start, end) };
4133
+ }
4134
+ function transformDatasetId(input) {
4135
+ const key = input.key?.trim();
4136
+ return key ? `${input.source.datasetId}:${input.kind}:${key}` : `${input.source.datasetId}:${input.kind}`;
4137
+ }
4138
+ function createTransformedPlayDataset(source, transform, options) {
4139
+ const sourceLabel = options?.sourceLabel ?? `${source.sourceLabel ?? source.tableNamespace ?? source.datasetId}.${transform.kind}`;
4140
+ const iterate = async function* () {
4141
+ if (transform.kind === "slice") {
4142
+ const bounds = normalizeSliceBounds({
4143
+ start: transform.start,
4144
+ end: transform.end,
4145
+ count: await source.count()
4146
+ });
4147
+ let index = 0;
4148
+ for await (const row of source) {
4149
+ if (index >= bounds.end) break;
4150
+ if (index >= bounds.start) {
4151
+ yield row;
4152
+ }
4153
+ index += 1;
4154
+ }
4155
+ return;
4156
+ }
4157
+ let inputIndex = 0;
4158
+ let outputIndex = 0;
4159
+ for await (const row of source) {
4160
+ if (transform.kind === "filter") {
4161
+ if (await transform.predicate(row, inputIndex)) {
4162
+ yield row;
4163
+ outputIndex += 1;
4164
+ }
4165
+ } else {
4166
+ yield await transform.mapper(row, outputIndex);
4167
+ outputIndex += 1;
4168
+ }
4169
+ inputIndex += 1;
4170
+ }
4171
+ };
4172
+ const collect = async (limit) => {
4173
+ const rows = [];
4174
+ const boundedLimit = limit === void 0 ? void 0 : Math.max(0, Math.floor(limit));
4175
+ if (boundedLimit === 0) return rows;
4176
+ for await (const row of iterate()) {
4177
+ rows.push(row);
4178
+ if (boundedLimit !== void 0 && rows.length >= boundedLimit) break;
4179
+ }
4180
+ return rows;
4181
+ };
4182
+ const count = async () => {
4183
+ if (transform.kind === "map") return await source.count();
4184
+ if (transform.kind === "slice") {
4185
+ const bounds = normalizeSliceBounds({
4186
+ start: transform.start,
4187
+ end: transform.end,
4188
+ count: await source.count()
4189
+ });
4190
+ return Math.max(0, bounds.end - bounds.start);
4191
+ }
4192
+ let total = 0;
4193
+ for await (const _row of iterate()) {
4194
+ void _row;
4195
+ total += 1;
4196
+ }
4197
+ return total;
4198
+ };
4199
+ return createDeferredPlayDataset({
4200
+ datasetKind: source.datasetKind,
4201
+ datasetId: transformDatasetId({
4202
+ source,
4203
+ kind: transform.kind,
4204
+ key: options?.key
4205
+ }),
4206
+ count: 0,
4207
+ backing: source.backing,
4208
+ sourceLabel,
4209
+ tableNamespace: options?.key ?? null,
4210
+ resolvers: {
4211
+ count,
4212
+ peek: async (limit) => collect(limit),
4213
+ materialize: async (limit) => collect(limit),
4214
+ iterate: () => ({
4215
+ async *[Symbol.asyncIterator]() {
4216
+ yield* iterate();
4217
+ }
4218
+ })
4219
+ }
4220
+ });
4221
+ }
4222
+ function createDeferredPlayDataset(input) {
4223
+ return new DeferredPlayDataset({
4224
+ ...input,
4225
+ previewRows: input.previewRows ?? []
4226
+ });
4227
+ }
4228
+ function createPlayDataset(rows, metadata) {
4229
+ const materializedRows = [...rows];
4230
+ return createDeferredPlayDataset({
4231
+ datasetKind: metadata?.kind ?? "map",
4232
+ datasetId: metadata?.datasetId ?? `${metadata?.kind ?? "map"}:${metadata?.tableNamespace ?? metadata?.sourceLabel ?? "inline"}`,
4233
+ count: materializedRows.length,
4234
+ previewRows: materializedRows.slice(0, 5),
4235
+ sourceLabel: metadata?.sourceLabel ?? null,
4236
+ tableNamespace: metadata?.tableNamespace ?? null,
4237
+ resolvers: {
4238
+ count: async () => materializedRows.length,
4239
+ peek: async (limit) => materializedRows.slice(0, Math.max(0, limit)),
4240
+ materialize: async (limit) => limit === void 0 ? [...materializedRows] : materializedRows.slice(0, Math.max(0, limit)),
4241
+ iterate: () => ({
4242
+ async *[Symbol.asyncIterator]() {
4243
+ for (const row of materializedRows) {
4244
+ yield row;
4245
+ }
4246
+ }
4247
+ })
4248
+ }
4249
+ });
4250
+ }
4251
+
4252
+ // ../shared_libs/plays/row-identity.ts
4253
+ var POSTGRES_IDENTIFIER_MAX_LENGTH = 63;
4254
+ var MAP_KEY_NAMESPACE_MAX_LENGTH = POSTGRES_IDENTIFIER_MAX_LENGTH;
4255
+ var SHA256_INITIAL_HASH = [
4256
+ 1779033703,
4257
+ 3144134277,
4258
+ 1013904242,
4259
+ 2773480762,
4260
+ 1359893119,
4261
+ 2600822924,
4262
+ 528734635,
4263
+ 1541459225
4264
+ ];
4265
+ var SHA256_ROUND_CONSTANTS = [
4266
+ 1116352408,
4267
+ 1899447441,
4268
+ 3049323471,
4269
+ 3921009573,
4270
+ 961987163,
4271
+ 1508970993,
4272
+ 2453635748,
4273
+ 2870763221,
4274
+ 3624381080,
4275
+ 310598401,
4276
+ 607225278,
4277
+ 1426881987,
4278
+ 1925078388,
4279
+ 2162078206,
4280
+ 2614888103,
4281
+ 3248222580,
4282
+ 3835390401,
4283
+ 4022224774,
4284
+ 264347078,
4285
+ 604807628,
4286
+ 770255983,
4287
+ 1249150122,
4288
+ 1555081692,
4289
+ 1996064986,
4290
+ 2554220882,
4291
+ 2821834349,
4292
+ 2952996808,
4293
+ 3210313671,
4294
+ 3336571891,
4295
+ 3584528711,
4296
+ 113926993,
4297
+ 338241895,
4298
+ 666307205,
4299
+ 773529912,
4300
+ 1294757372,
4301
+ 1396182291,
4302
+ 1695183700,
4303
+ 1986661051,
4304
+ 2177026350,
4305
+ 2456956037,
4306
+ 2730485921,
4307
+ 2820302411,
4308
+ 3259730800,
4309
+ 3345764771,
4310
+ 3516065817,
4311
+ 3600352804,
4312
+ 4094571909,
4313
+ 275423344,
4314
+ 430227734,
4315
+ 506948616,
4316
+ 659060556,
4317
+ 883997877,
4318
+ 958139571,
4319
+ 1322822218,
4320
+ 1537002063,
4321
+ 1747873779,
4322
+ 1955562222,
4323
+ 2024104815,
4324
+ 2227730452,
4325
+ 2361852424,
4326
+ 2428436474,
4327
+ 2756734187,
4328
+ 3204031479,
4329
+ 3329325298
4330
+ ];
4331
+ function rightRotate32(value, bits) {
4332
+ return value >>> bits | value << 32 - bits;
4333
+ }
4334
+ function sha256Hex(input) {
4335
+ const bytes = Array.from(new TextEncoder().encode(input));
4336
+ const bitLength = bytes.length * 8;
4337
+ bytes.push(128);
4338
+ while (bytes.length % 64 !== 56) {
4339
+ bytes.push(0);
4340
+ }
4341
+ const highBits = Math.floor(bitLength / 4294967296);
4342
+ const lowBits = bitLength >>> 0;
4343
+ bytes.push(
4344
+ highBits >>> 24 & 255,
4345
+ highBits >>> 16 & 255,
4346
+ highBits >>> 8 & 255,
4347
+ highBits & 255,
4348
+ lowBits >>> 24 & 255,
4349
+ lowBits >>> 16 & 255,
4350
+ lowBits >>> 8 & 255,
4351
+ lowBits & 255
4352
+ );
4353
+ const hash = [...SHA256_INITIAL_HASH];
4354
+ const words = new Array(64).fill(0);
4355
+ for (let offset = 0; offset < bytes.length; offset += 64) {
4356
+ for (let index = 0; index < 16; index += 1) {
4357
+ const wordOffset = offset + index * 4;
4358
+ words[index] = (bytes[wordOffset] ?? 0) << 24 | (bytes[wordOffset + 1] ?? 0) << 16 | (bytes[wordOffset + 2] ?? 0) << 8 | (bytes[wordOffset + 3] ?? 0);
4359
+ }
4360
+ for (let index = 16; index < 64; index += 1) {
4361
+ const s0 = rightRotate32(words[index - 15], 7) ^ rightRotate32(words[index - 15], 18) ^ words[index - 15] >>> 3;
4362
+ const s1 = rightRotate32(words[index - 2], 17) ^ rightRotate32(words[index - 2], 19) ^ words[index - 2] >>> 10;
4363
+ words[index] = words[index - 16] + s0 + words[index - 7] + s1 >>> 0;
4364
+ }
4365
+ let [a, b, c, d, e, f, g, h] = hash;
4366
+ for (let index = 0; index < 64; index += 1) {
4367
+ const s1 = rightRotate32(e, 6) ^ rightRotate32(e, 11) ^ rightRotate32(e, 25);
4368
+ const ch = e & f ^ ~e & g;
4369
+ const temp1 = h + s1 + ch + SHA256_ROUND_CONSTANTS[index] + words[index] >>> 0;
4370
+ const s0 = rightRotate32(a, 2) ^ rightRotate32(a, 13) ^ rightRotate32(a, 22);
4371
+ const maj = a & b ^ a & c ^ b & c;
4372
+ const temp2 = s0 + maj >>> 0;
4373
+ h = g;
4374
+ g = f;
4375
+ f = e;
4376
+ e = d + temp1 >>> 0;
4377
+ d = c;
4378
+ c = b;
4379
+ b = a;
4380
+ a = temp1 + temp2 >>> 0;
4381
+ }
4382
+ hash[0] = hash[0] + a >>> 0;
4383
+ hash[1] = hash[1] + b >>> 0;
4384
+ hash[2] = hash[2] + c >>> 0;
4385
+ hash[3] = hash[3] + d >>> 0;
4386
+ hash[4] = hash[4] + e >>> 0;
4387
+ hash[5] = hash[5] + f >>> 0;
4388
+ hash[6] = hash[6] + g >>> 0;
4389
+ hash[7] = hash[7] + h >>> 0;
4390
+ }
4391
+ return hash.map((word) => word.toString(16).padStart(8, "0")).join("");
4392
+ }
4393
+ function sanitizeIdentifierPart(value) {
4394
+ return value.trim().replace(/[^a-z0-9]+/gi, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
4395
+ }
4396
+ function validateIdentifierPart(rawValue, label, maxLength) {
4397
+ const sanitized = sanitizeIdentifierPart(rawValue);
4398
+ if (!sanitized) {
4399
+ throw new Error(
4400
+ `${label} must contain at least one letter or number after normalization. Use only letters, numbers, underscores, or hyphens.`
4401
+ );
4402
+ }
4403
+ if (sanitized.length > maxLength) {
4404
+ throw new Error(
4405
+ `${label} is too long after normalization (${sanitized.length}/${maxLength}). Shorten it to ${maxLength} characters or fewer. Normalized value: "${sanitized}".`
4406
+ );
4407
+ }
4408
+ return sanitized;
4409
+ }
4410
+ function normalizeTableNamespace(value) {
4411
+ return validateIdentifierPart(
4412
+ value,
4413
+ "ctx.dataset() key",
4414
+ MAP_KEY_NAMESPACE_MAX_LENGTH
4415
+ );
4416
+ }
4417
+
3976
4418
  // ../shared_libs/play-runtime/tool-result.ts
3977
4419
  var TARGET_FALLBACK_KEYS = {
3978
4420
  email: [/^email$/i, /^address$/i, /email/i],
@@ -4320,17 +4762,31 @@ function resolveListRows(result, listExtractorPaths) {
4320
4762
  );
4321
4763
  let resolvedPath = null;
4322
4764
  let rows = null;
4765
+ let emptyMatch = null;
4323
4766
  for (const candidate of candidates) {
4324
4767
  rows = normalizeRows(getAtPath(result, candidate));
4325
- if (rows) {
4768
+ if (!rows) {
4769
+ continue;
4770
+ }
4771
+ if (rows.length > 0) {
4326
4772
  resolvedPath = candidate;
4327
4773
  break;
4328
4774
  }
4775
+ emptyMatch ??= { path: candidate, rows };
4776
+ }
4777
+ if (!rows && emptyMatch) {
4778
+ resolvedPath = emptyMatch.path;
4779
+ rows = emptyMatch.rows;
4329
4780
  }
4330
4781
  if (!rows) continue;
4331
4782
  const storedPath = resolvedPath ?? path;
4332
4783
  const name = storedPath.split(".").filter(Boolean).at(-1)?.replace(/\[\d+\]$/, "");
4333
- lists[name || storedPath] = { path: storedPath, rows };
4784
+ const listName = name || storedPath;
4785
+ const existing = lists[listName];
4786
+ if (existing?.rows.length && rows.length === 0) {
4787
+ continue;
4788
+ }
4789
+ lists[listName] = { path: storedPath, rows };
4334
4790
  }
4335
4791
  return lists;
4336
4792
  }
@@ -4478,10 +4934,24 @@ function buildExtractedAccessors(targets) {
4478
4934
  })
4479
4935
  );
4480
4936
  }
4481
- function buildListAccessors(resolved, lists) {
4937
+ function buildListAccessors(resolved, lists, toolId, executionDiscriminator) {
4482
4938
  return Object.fromEntries(
4483
4939
  Object.entries(lists).map(([name, metadata]) => {
4484
4940
  const rows = resolved[name]?.rows ?? [];
4941
+ const datasetDiscriminator = `${executionDiscriminator}:${listRowsFingerprint(rows)}`;
4942
+ const dataset = createPlayDataset(rows, {
4943
+ kind: "csv",
4944
+ sourceLabel: metadata.path,
4945
+ tableNamespace: listTableNamespace(
4946
+ toolId,
4947
+ name,
4948
+ metadata.path,
4949
+ datasetDiscriminator
4950
+ ),
4951
+ datasetId: `tool-list:${sha256Hex(
4952
+ `${toolId}:${metadata.path}:${datasetDiscriminator}`
4953
+ )}`
4954
+ });
4485
4955
  const accessor = {
4486
4956
  path: metadata.path,
4487
4957
  count: metadata.count,
@@ -4489,7 +4959,7 @@ function buildListAccessors(resolved, lists) {
4489
4959
  };
4490
4960
  Object.defineProperty(accessor, "get", {
4491
4961
  value() {
4492
- return rows;
4962
+ return dataset;
4493
4963
  },
4494
4964
  enumerable: false
4495
4965
  });
@@ -4497,6 +4967,37 @@ function buildListAccessors(resolved, lists) {
4497
4967
  })
4498
4968
  );
4499
4969
  }
4970
+ function listRowsFingerprint(rows) {
4971
+ try {
4972
+ return sha256Hex(
4973
+ JSON.stringify(
4974
+ {
4975
+ count: rows.length,
4976
+ rows
4977
+ },
4978
+ (_key, value) => typeof value === "bigint" ? value.toString() : value
4979
+ )
4980
+ ).slice(0, 12);
4981
+ } catch {
4982
+ return sha256Hex(String(rows.length)).slice(0, 12);
4983
+ }
4984
+ }
4985
+ function listTableNamespace(toolId, name, path, discriminator) {
4986
+ const raw = `${toolId}_${name || path || "rows"}_${sha256Hex(discriminator).slice(0, 10)}`;
4987
+ try {
4988
+ return normalizeTableNamespace(raw);
4989
+ } catch {
4990
+ const hash = sha256Hex(raw).slice(0, 10);
4991
+ const leaf = name || path.split(".").filter(Boolean).at(-1) || "rows";
4992
+ let prefix = "rows";
4993
+ try {
4994
+ prefix = normalizeTableNamespace(leaf).slice(0, 52) || "rows";
4995
+ } catch {
4996
+ prefix = "rows";
4997
+ }
4998
+ return normalizeTableNamespace(`${prefix}_${hash}`);
4999
+ }
5000
+ }
4500
5001
  function createToolExecuteResult(input) {
4501
5002
  const result = toResultEnvelope(input.result);
4502
5003
  const resultRoot = {
@@ -4527,7 +5028,12 @@ function createToolExecuteResult(input) {
4527
5028
  ...result.meta ? { meta: result.meta } : {}
4528
5029
  };
4529
5030
  const extractedValues = buildExtractedAccessors(targets);
4530
- const extractedLists = buildListAccessors(resolvedLists, lists);
5031
+ const extractedLists = buildListAccessors(
5032
+ resolvedLists,
5033
+ lists,
5034
+ input.metadata.toolId,
5035
+ input.jobId ?? input.execution.cacheKey ?? "inline"
5036
+ );
4531
5037
  const wrapper = {
4532
5038
  status: input.status,
4533
5039
  ...input.jobId ? { job_id: input.jobId } : {},
@@ -5138,16 +5644,24 @@ function tryConvertToList(payload, options) {
5138
5644
  (entry) => typeof entry === "string" && entry.trim().length > 0
5139
5645
  ) : [];
5140
5646
  if (listExtractorPaths.length > 0) {
5647
+ let emptyMatch = null;
5141
5648
  for (const root of candidateRoots(payload)) {
5142
5649
  for (const extractorPath of listExtractorPaths) {
5143
5650
  const resolved = getByDottedPath(root.value, extractorPath);
5144
5651
  const rows = normalizeRows2(resolved);
5145
- if (rows && rows.length > 0) {
5146
- const sourcePath = root.path ? `${root.path}.${extractorPath}` : extractorPath;
5652
+ if (!rows) {
5653
+ continue;
5654
+ }
5655
+ const sourcePath = root.path ? `${root.path}.${extractorPath}` : extractorPath;
5656
+ if (rows.length > 0) {
5147
5657
  return { rows, strategy: "configured_paths", sourcePath };
5148
5658
  }
5659
+ emptyMatch ??= { rows, strategy: "configured_paths", sourcePath };
5149
5660
  }
5150
5661
  }
5662
+ if (emptyMatch) {
5663
+ return emptyMatch;
5664
+ }
5151
5665
  }
5152
5666
  for (const root of candidateRoots(payload)) {
5153
5667
  const candidate = findBestArrayCandidate(root.value, root.path ?? "");
@@ -5171,9 +5685,9 @@ function writeJsonOutputFile(payload, stem) {
5171
5685
  (0, import_node_fs3.writeFileSync)(outputPath, JSON.stringify(payload, null, 2), "utf-8");
5172
5686
  return outputPath;
5173
5687
  }
5174
- function writeCsvOutputFile(rows, stem) {
5175
- const outputDir = ensureOutputDir();
5176
- const outputPath = (0, import_node_path3.join)(outputDir, `${stem}_${Date.now()}.csv`);
5688
+ function writeCsvOutputFile(rows, stem, options) {
5689
+ const outputPath = options?.outPath ? options.outPath : (0, import_node_path3.join)(ensureOutputDir(), `${stem}_${Date.now()}.csv`);
5690
+ (0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(outputPath), { recursive: true });
5177
5691
  const seen = /* @__PURE__ */ new Set();
5178
5692
  const columns = [];
5179
5693
  for (const row of rows) {