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.mjs CHANGED
@@ -348,10 +348,11 @@ var SDK_RELEASE = {
348
348
  // 0.1.108 ships explicit dataset column/tool recompute policy and removes
349
349
  // the SDK enrich generator's one-second stale policy.
350
350
  // 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
351
- version: "0.1.146",
352
- apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
351
+ // 0.1.111 ships dataset-native tool list getters and result row datasets.
352
+ version: "0.1.148",
353
+ apiContract: "2026-06-dataset-handle-results-hard-cutover",
353
354
  supportPolicy: {
354
- latest: "0.1.146",
355
+ latest: "0.1.148",
355
356
  minimumSupported: "0.1.53",
356
357
  deprecatedBelow: "0.1.53",
357
358
  commandMinimumSupported: [
@@ -362,52 +363,79 @@ var SDK_RELEASE = {
362
363
  },
363
364
  {
364
365
  command: "plays",
365
- minimumSupported: "0.1.110",
366
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
366
+ minimumSupported: "0.1.111",
367
+ reason: "Play file commands now use dataset-native list getters and result row datasets."
367
368
  },
368
369
  {
369
370
  command: "plays run",
370
- minimumSupported: "0.1.110",
371
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
371
+ minimumSupported: "0.1.111",
372
+ reason: "Play run results now promote row-shaped outputs into dataset handles for safe export."
372
373
  },
373
374
  {
374
375
  command: "run",
375
376
  displayCommand: "plays run",
376
- minimumSupported: "0.1.110",
377
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
377
+ minimumSupported: "0.1.111",
378
+ reason: "Play run results now promote row-shaped outputs into dataset handles for safe export."
378
379
  },
379
380
  {
380
381
  command: "plays check",
381
- minimumSupported: "0.1.110",
382
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
382
+ minimumSupported: "0.1.111",
383
+ reason: "Play file checks now validate dataset-native list getter authoring."
383
384
  },
384
385
  {
385
386
  command: "check",
386
387
  displayCommand: "plays check",
387
- minimumSupported: "0.1.110",
388
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
388
+ minimumSupported: "0.1.111",
389
+ reason: "Play file checks now validate dataset-native list getter authoring."
389
390
  },
390
391
  {
391
392
  command: "plays publish",
392
- minimumSupported: "0.1.110",
393
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
393
+ minimumSupported: "0.1.111",
394
+ reason: "Published play artifacts now target dataset-native list getters and result row datasets."
394
395
  },
395
396
  {
396
397
  command: "publish",
397
398
  displayCommand: "plays publish",
398
- minimumSupported: "0.1.110",
399
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
399
+ minimumSupported: "0.1.111",
400
+ reason: "Published play artifacts now target dataset-native list getters and result row datasets."
400
401
  },
401
402
  {
402
403
  command: "plays set-live",
403
- minimumSupported: "0.1.110",
404
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
404
+ minimumSupported: "0.1.111",
405
+ reason: "Published play artifacts now target dataset-native list getters and result row datasets."
405
406
  },
406
407
  {
407
408
  command: "set-live",
408
409
  displayCommand: "plays set-live",
409
- minimumSupported: "0.1.110",
410
- reason: "Play file commands now require top-level definePlay descriptions so agents and play surfaces can explain local plays."
410
+ minimumSupported: "0.1.111",
411
+ reason: "Published play artifacts now target dataset-native list getters and result row datasets."
412
+ },
413
+ {
414
+ command: "runs",
415
+ minimumSupported: "0.1.111",
416
+ reason: "Run result rows now render as dataset handles with explicit export commands."
417
+ },
418
+ {
419
+ command: "runs get",
420
+ minimumSupported: "0.1.111",
421
+ reason: "Run result rows now render as dataset handles with explicit export commands."
422
+ },
423
+ {
424
+ command: "get",
425
+ displayCommand: "runs get",
426
+ minimumSupported: "0.1.111",
427
+ reason: "Run result rows now render as dataset handles with explicit export commands."
428
+ },
429
+ {
430
+ command: "runs export",
431
+ minimumSupported: "0.1.111",
432
+ reason: "Run result row datasets now use the dataset-handle export contract."
433
+ },
434
+ {
435
+ command: "export",
436
+ displayCommand: "runs export",
437
+ minimumSupported: "0.1.111",
438
+ reason: "Run result row datasets now use the dataset-handle export contract."
411
439
  }
412
440
  ],
413
441
  autoUpdatePatchLag: 2
@@ -3903,6 +3931,420 @@ function isDeeplineExtractorTarget(value) {
3903
3931
  return value in DEEPLINE_EXTRACTOR_TARGET_DEFINITIONS;
3904
3932
  }
3905
3933
 
3934
+ // ../shared_libs/plays/dataset.ts
3935
+ var PLAY_DATASET_BRAND = /* @__PURE__ */ Symbol.for("deepline.play.dataset");
3936
+ var NODE_INSPECT_CUSTOM = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
3937
+ var DEFAULT_MATERIALIZE_LIMIT = 1e4;
3938
+ function resolveMaterializeLimitCap() {
3939
+ const raw = process.env.DEEPLINE_PLAY_DATASET_MATERIALIZE_LIMIT;
3940
+ const parsed = raw ? Number(raw) : NaN;
3941
+ if (Number.isFinite(parsed) && parsed > 0) {
3942
+ return Math.floor(parsed);
3943
+ }
3944
+ return DEFAULT_MATERIALIZE_LIMIT;
3945
+ }
3946
+ function inferPreviewColumns(rows) {
3947
+ const columns = /* @__PURE__ */ new Set();
3948
+ for (const row of rows) {
3949
+ if (!row || typeof row !== "object" || Array.isArray(row)) {
3950
+ continue;
3951
+ }
3952
+ for (const key of Object.keys(row)) {
3953
+ columns.add(key);
3954
+ }
3955
+ }
3956
+ return columns.size > 0 ? [...columns] : void 0;
3957
+ }
3958
+ var DeferredPlayDataset = class {
3959
+ [PLAY_DATASET_BRAND] = true;
3960
+ datasetKind;
3961
+ datasetId;
3962
+ backing;
3963
+ sourceLabel;
3964
+ tableNamespace;
3965
+ previewRows;
3966
+ previewColumns;
3967
+ workProgress;
3968
+ cachedCount;
3969
+ resolvers;
3970
+ constructor(input) {
3971
+ this.datasetKind = input.datasetKind;
3972
+ this.datasetId = input.datasetId;
3973
+ this.cachedCount = input.count;
3974
+ this.backing = input.backing;
3975
+ this.previewRows = input.previewRows;
3976
+ this.previewColumns = inferPreviewColumns(this.previewRows);
3977
+ this.sourceLabel = input.sourceLabel ?? null;
3978
+ this.tableNamespace = input.tableNamespace ?? null;
3979
+ this.workProgress = input.workProgress;
3980
+ this.resolvers = input.resolvers;
3981
+ }
3982
+ async count() {
3983
+ this.cachedCount = await this.resolvers.count();
3984
+ return this.cachedCount;
3985
+ }
3986
+ async peek(limit = 10) {
3987
+ if (limit <= this.previewRows.length) {
3988
+ return this.previewRows.slice(0, Math.max(0, limit));
3989
+ }
3990
+ return await this.resolvers.peek(limit);
3991
+ }
3992
+ map(mapper, options) {
3993
+ return createTransformedPlayDataset(this, { kind: "map", mapper }, options);
3994
+ }
3995
+ filter(predicate, options) {
3996
+ return createTransformedPlayDataset(
3997
+ this,
3998
+ { kind: "filter", predicate },
3999
+ options
4000
+ );
4001
+ }
4002
+ slice(start, end, options) {
4003
+ return createTransformedPlayDataset(
4004
+ this,
4005
+ { kind: "slice", start, end },
4006
+ options
4007
+ );
4008
+ }
4009
+ take(limit, options) {
4010
+ return this.slice(0, limit, options);
4011
+ }
4012
+ async materialize(limit) {
4013
+ const requestedLimit = limit !== void 0 ? Math.max(0, Math.floor(limit)) : void 0;
4014
+ const cap = resolveMaterializeLimitCap();
4015
+ if (requestedLimit !== void 0) {
4016
+ if (requestedLimit > cap) {
4017
+ throw new Error(
4018
+ `PlayDataset.materialize(${requestedLimit}) exceeds the hard limit of ${cap} rows. Return the dataset handle instead, or request a smaller bounded slice.`
4019
+ );
4020
+ }
4021
+ return await this.resolvers.materialize(requestedLimit);
4022
+ }
4023
+ const count = await this.count();
4024
+ if (count > cap) {
4025
+ throw new Error(
4026
+ `PlayDataset.materialize() refuses to load ${count} rows into memory. The hard limit is ${cap}. Return the dataset handle instead or call materialize(limit).`
4027
+ );
4028
+ }
4029
+ return await this.resolvers.materialize();
4030
+ }
4031
+ async *[Symbol.asyncIterator]() {
4032
+ for await (const row of this.resolvers.iterate()) {
4033
+ yield row;
4034
+ }
4035
+ }
4036
+ toJSON() {
4037
+ return {
4038
+ kind: "dataset",
4039
+ datasetKind: this.datasetKind,
4040
+ datasetId: this.datasetId,
4041
+ count: this.cachedCount,
4042
+ ...this.backing ? { backing: this.backing } : {},
4043
+ ...this.sourceLabel ? { sourceLabel: this.sourceLabel } : {},
4044
+ ...this.tableNamespace ? { tableNamespace: this.tableNamespace } : {},
4045
+ ...this.previewColumns ? { columns: this.previewColumns } : {},
4046
+ ...this.workProgress ? { _metadata: { workProgress: this.workProgress } } : {},
4047
+ preview: [...this.previewRows]
4048
+ };
4049
+ }
4050
+ [NODE_INSPECT_CUSTOM]() {
4051
+ return this.toJSON();
4052
+ }
4053
+ };
4054
+ function normalizeSliceBounds(input) {
4055
+ const count = Math.max(0, Math.floor(input.count));
4056
+ const rawStart = input.start ?? 0;
4057
+ const rawEnd = input.end ?? count;
4058
+ const startInteger = Number.isFinite(rawStart) ? Math.trunc(rawStart) : 0;
4059
+ const endInteger = Number.isFinite(rawEnd) ? Math.trunc(rawEnd) : count;
4060
+ const start = startInteger < 0 ? Math.max(count + startInteger, 0) : Math.min(startInteger, count);
4061
+ const end = endInteger < 0 ? Math.max(count + endInteger, 0) : Math.min(endInteger, count);
4062
+ return { start, end: Math.max(start, end) };
4063
+ }
4064
+ function transformDatasetId(input) {
4065
+ const key = input.key?.trim();
4066
+ return key ? `${input.source.datasetId}:${input.kind}:${key}` : `${input.source.datasetId}:${input.kind}`;
4067
+ }
4068
+ function createTransformedPlayDataset(source, transform, options) {
4069
+ const sourceLabel = options?.sourceLabel ?? `${source.sourceLabel ?? source.tableNamespace ?? source.datasetId}.${transform.kind}`;
4070
+ const iterate = async function* () {
4071
+ if (transform.kind === "slice") {
4072
+ const bounds = normalizeSliceBounds({
4073
+ start: transform.start,
4074
+ end: transform.end,
4075
+ count: await source.count()
4076
+ });
4077
+ let index = 0;
4078
+ for await (const row of source) {
4079
+ if (index >= bounds.end) break;
4080
+ if (index >= bounds.start) {
4081
+ yield row;
4082
+ }
4083
+ index += 1;
4084
+ }
4085
+ return;
4086
+ }
4087
+ let inputIndex = 0;
4088
+ let outputIndex = 0;
4089
+ for await (const row of source) {
4090
+ if (transform.kind === "filter") {
4091
+ if (await transform.predicate(row, inputIndex)) {
4092
+ yield row;
4093
+ outputIndex += 1;
4094
+ }
4095
+ } else {
4096
+ yield await transform.mapper(row, outputIndex);
4097
+ outputIndex += 1;
4098
+ }
4099
+ inputIndex += 1;
4100
+ }
4101
+ };
4102
+ const collect = async (limit) => {
4103
+ const rows = [];
4104
+ const boundedLimit = limit === void 0 ? void 0 : Math.max(0, Math.floor(limit));
4105
+ if (boundedLimit === 0) return rows;
4106
+ for await (const row of iterate()) {
4107
+ rows.push(row);
4108
+ if (boundedLimit !== void 0 && rows.length >= boundedLimit) break;
4109
+ }
4110
+ return rows;
4111
+ };
4112
+ const count = async () => {
4113
+ if (transform.kind === "map") return await source.count();
4114
+ if (transform.kind === "slice") {
4115
+ const bounds = normalizeSliceBounds({
4116
+ start: transform.start,
4117
+ end: transform.end,
4118
+ count: await source.count()
4119
+ });
4120
+ return Math.max(0, bounds.end - bounds.start);
4121
+ }
4122
+ let total = 0;
4123
+ for await (const _row of iterate()) {
4124
+ void _row;
4125
+ total += 1;
4126
+ }
4127
+ return total;
4128
+ };
4129
+ return createDeferredPlayDataset({
4130
+ datasetKind: source.datasetKind,
4131
+ datasetId: transformDatasetId({
4132
+ source,
4133
+ kind: transform.kind,
4134
+ key: options?.key
4135
+ }),
4136
+ count: 0,
4137
+ backing: source.backing,
4138
+ sourceLabel,
4139
+ tableNamespace: options?.key ?? null,
4140
+ resolvers: {
4141
+ count,
4142
+ peek: async (limit) => collect(limit),
4143
+ materialize: async (limit) => collect(limit),
4144
+ iterate: () => ({
4145
+ async *[Symbol.asyncIterator]() {
4146
+ yield* iterate();
4147
+ }
4148
+ })
4149
+ }
4150
+ });
4151
+ }
4152
+ function createDeferredPlayDataset(input) {
4153
+ return new DeferredPlayDataset({
4154
+ ...input,
4155
+ previewRows: input.previewRows ?? []
4156
+ });
4157
+ }
4158
+ function createPlayDataset(rows, metadata) {
4159
+ const materializedRows = [...rows];
4160
+ return createDeferredPlayDataset({
4161
+ datasetKind: metadata?.kind ?? "map",
4162
+ datasetId: metadata?.datasetId ?? `${metadata?.kind ?? "map"}:${metadata?.tableNamespace ?? metadata?.sourceLabel ?? "inline"}`,
4163
+ count: materializedRows.length,
4164
+ previewRows: materializedRows.slice(0, 5),
4165
+ sourceLabel: metadata?.sourceLabel ?? null,
4166
+ tableNamespace: metadata?.tableNamespace ?? null,
4167
+ resolvers: {
4168
+ count: async () => materializedRows.length,
4169
+ peek: async (limit) => materializedRows.slice(0, Math.max(0, limit)),
4170
+ materialize: async (limit) => limit === void 0 ? [...materializedRows] : materializedRows.slice(0, Math.max(0, limit)),
4171
+ iterate: () => ({
4172
+ async *[Symbol.asyncIterator]() {
4173
+ for (const row of materializedRows) {
4174
+ yield row;
4175
+ }
4176
+ }
4177
+ })
4178
+ }
4179
+ });
4180
+ }
4181
+
4182
+ // ../shared_libs/plays/row-identity.ts
4183
+ var POSTGRES_IDENTIFIER_MAX_LENGTH = 63;
4184
+ var MAP_KEY_NAMESPACE_MAX_LENGTH = POSTGRES_IDENTIFIER_MAX_LENGTH;
4185
+ var SHA256_INITIAL_HASH = [
4186
+ 1779033703,
4187
+ 3144134277,
4188
+ 1013904242,
4189
+ 2773480762,
4190
+ 1359893119,
4191
+ 2600822924,
4192
+ 528734635,
4193
+ 1541459225
4194
+ ];
4195
+ var SHA256_ROUND_CONSTANTS = [
4196
+ 1116352408,
4197
+ 1899447441,
4198
+ 3049323471,
4199
+ 3921009573,
4200
+ 961987163,
4201
+ 1508970993,
4202
+ 2453635748,
4203
+ 2870763221,
4204
+ 3624381080,
4205
+ 310598401,
4206
+ 607225278,
4207
+ 1426881987,
4208
+ 1925078388,
4209
+ 2162078206,
4210
+ 2614888103,
4211
+ 3248222580,
4212
+ 3835390401,
4213
+ 4022224774,
4214
+ 264347078,
4215
+ 604807628,
4216
+ 770255983,
4217
+ 1249150122,
4218
+ 1555081692,
4219
+ 1996064986,
4220
+ 2554220882,
4221
+ 2821834349,
4222
+ 2952996808,
4223
+ 3210313671,
4224
+ 3336571891,
4225
+ 3584528711,
4226
+ 113926993,
4227
+ 338241895,
4228
+ 666307205,
4229
+ 773529912,
4230
+ 1294757372,
4231
+ 1396182291,
4232
+ 1695183700,
4233
+ 1986661051,
4234
+ 2177026350,
4235
+ 2456956037,
4236
+ 2730485921,
4237
+ 2820302411,
4238
+ 3259730800,
4239
+ 3345764771,
4240
+ 3516065817,
4241
+ 3600352804,
4242
+ 4094571909,
4243
+ 275423344,
4244
+ 430227734,
4245
+ 506948616,
4246
+ 659060556,
4247
+ 883997877,
4248
+ 958139571,
4249
+ 1322822218,
4250
+ 1537002063,
4251
+ 1747873779,
4252
+ 1955562222,
4253
+ 2024104815,
4254
+ 2227730452,
4255
+ 2361852424,
4256
+ 2428436474,
4257
+ 2756734187,
4258
+ 3204031479,
4259
+ 3329325298
4260
+ ];
4261
+ function rightRotate32(value, bits) {
4262
+ return value >>> bits | value << 32 - bits;
4263
+ }
4264
+ function sha256Hex(input) {
4265
+ const bytes = Array.from(new TextEncoder().encode(input));
4266
+ const bitLength = bytes.length * 8;
4267
+ bytes.push(128);
4268
+ while (bytes.length % 64 !== 56) {
4269
+ bytes.push(0);
4270
+ }
4271
+ const highBits = Math.floor(bitLength / 4294967296);
4272
+ const lowBits = bitLength >>> 0;
4273
+ bytes.push(
4274
+ highBits >>> 24 & 255,
4275
+ highBits >>> 16 & 255,
4276
+ highBits >>> 8 & 255,
4277
+ highBits & 255,
4278
+ lowBits >>> 24 & 255,
4279
+ lowBits >>> 16 & 255,
4280
+ lowBits >>> 8 & 255,
4281
+ lowBits & 255
4282
+ );
4283
+ const hash = [...SHA256_INITIAL_HASH];
4284
+ const words = new Array(64).fill(0);
4285
+ for (let offset = 0; offset < bytes.length; offset += 64) {
4286
+ for (let index = 0; index < 16; index += 1) {
4287
+ const wordOffset = offset + index * 4;
4288
+ words[index] = (bytes[wordOffset] ?? 0) << 24 | (bytes[wordOffset + 1] ?? 0) << 16 | (bytes[wordOffset + 2] ?? 0) << 8 | (bytes[wordOffset + 3] ?? 0);
4289
+ }
4290
+ for (let index = 16; index < 64; index += 1) {
4291
+ const s0 = rightRotate32(words[index - 15], 7) ^ rightRotate32(words[index - 15], 18) ^ words[index - 15] >>> 3;
4292
+ const s1 = rightRotate32(words[index - 2], 17) ^ rightRotate32(words[index - 2], 19) ^ words[index - 2] >>> 10;
4293
+ words[index] = words[index - 16] + s0 + words[index - 7] + s1 >>> 0;
4294
+ }
4295
+ let [a, b, c, d, e, f, g, h] = hash;
4296
+ for (let index = 0; index < 64; index += 1) {
4297
+ const s1 = rightRotate32(e, 6) ^ rightRotate32(e, 11) ^ rightRotate32(e, 25);
4298
+ const ch = e & f ^ ~e & g;
4299
+ const temp1 = h + s1 + ch + SHA256_ROUND_CONSTANTS[index] + words[index] >>> 0;
4300
+ const s0 = rightRotate32(a, 2) ^ rightRotate32(a, 13) ^ rightRotate32(a, 22);
4301
+ const maj = a & b ^ a & c ^ b & c;
4302
+ const temp2 = s0 + maj >>> 0;
4303
+ h = g;
4304
+ g = f;
4305
+ f = e;
4306
+ e = d + temp1 >>> 0;
4307
+ d = c;
4308
+ c = b;
4309
+ b = a;
4310
+ a = temp1 + temp2 >>> 0;
4311
+ }
4312
+ hash[0] = hash[0] + a >>> 0;
4313
+ hash[1] = hash[1] + b >>> 0;
4314
+ hash[2] = hash[2] + c >>> 0;
4315
+ hash[3] = hash[3] + d >>> 0;
4316
+ hash[4] = hash[4] + e >>> 0;
4317
+ hash[5] = hash[5] + f >>> 0;
4318
+ hash[6] = hash[6] + g >>> 0;
4319
+ hash[7] = hash[7] + h >>> 0;
4320
+ }
4321
+ return hash.map((word) => word.toString(16).padStart(8, "0")).join("");
4322
+ }
4323
+ function sanitizeIdentifierPart(value) {
4324
+ return value.trim().replace(/[^a-z0-9]+/gi, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
4325
+ }
4326
+ function validateIdentifierPart(rawValue, label, maxLength) {
4327
+ const sanitized = sanitizeIdentifierPart(rawValue);
4328
+ if (!sanitized) {
4329
+ throw new Error(
4330
+ `${label} must contain at least one letter or number after normalization. Use only letters, numbers, underscores, or hyphens.`
4331
+ );
4332
+ }
4333
+ if (sanitized.length > maxLength) {
4334
+ throw new Error(
4335
+ `${label} is too long after normalization (${sanitized.length}/${maxLength}). Shorten it to ${maxLength} characters or fewer. Normalized value: "${sanitized}".`
4336
+ );
4337
+ }
4338
+ return sanitized;
4339
+ }
4340
+ function normalizeTableNamespace(value) {
4341
+ return validateIdentifierPart(
4342
+ value,
4343
+ "ctx.dataset() key",
4344
+ MAP_KEY_NAMESPACE_MAX_LENGTH
4345
+ );
4346
+ }
4347
+
3906
4348
  // ../shared_libs/play-runtime/tool-result.ts
3907
4349
  var TARGET_FALLBACK_KEYS = {
3908
4350
  email: [/^email$/i, /^address$/i, /email/i],
@@ -4250,17 +4692,31 @@ function resolveListRows(result, listExtractorPaths) {
4250
4692
  );
4251
4693
  let resolvedPath = null;
4252
4694
  let rows = null;
4695
+ let emptyMatch = null;
4253
4696
  for (const candidate of candidates) {
4254
4697
  rows = normalizeRows(getAtPath(result, candidate));
4255
- if (rows) {
4698
+ if (!rows) {
4699
+ continue;
4700
+ }
4701
+ if (rows.length > 0) {
4256
4702
  resolvedPath = candidate;
4257
4703
  break;
4258
4704
  }
4705
+ emptyMatch ??= { path: candidate, rows };
4706
+ }
4707
+ if (!rows && emptyMatch) {
4708
+ resolvedPath = emptyMatch.path;
4709
+ rows = emptyMatch.rows;
4259
4710
  }
4260
4711
  if (!rows) continue;
4261
4712
  const storedPath = resolvedPath ?? path;
4262
4713
  const name = storedPath.split(".").filter(Boolean).at(-1)?.replace(/\[\d+\]$/, "");
4263
- lists[name || storedPath] = { path: storedPath, rows };
4714
+ const listName = name || storedPath;
4715
+ const existing = lists[listName];
4716
+ if (existing?.rows.length && rows.length === 0) {
4717
+ continue;
4718
+ }
4719
+ lists[listName] = { path: storedPath, rows };
4264
4720
  }
4265
4721
  return lists;
4266
4722
  }
@@ -4408,10 +4864,24 @@ function buildExtractedAccessors(targets) {
4408
4864
  })
4409
4865
  );
4410
4866
  }
4411
- function buildListAccessors(resolved, lists) {
4867
+ function buildListAccessors(resolved, lists, toolId, executionDiscriminator) {
4412
4868
  return Object.fromEntries(
4413
4869
  Object.entries(lists).map(([name, metadata]) => {
4414
4870
  const rows = resolved[name]?.rows ?? [];
4871
+ const datasetDiscriminator = `${executionDiscriminator}:${listRowsFingerprint(rows)}`;
4872
+ const dataset = createPlayDataset(rows, {
4873
+ kind: "csv",
4874
+ sourceLabel: metadata.path,
4875
+ tableNamespace: listTableNamespace(
4876
+ toolId,
4877
+ name,
4878
+ metadata.path,
4879
+ datasetDiscriminator
4880
+ ),
4881
+ datasetId: `tool-list:${sha256Hex(
4882
+ `${toolId}:${metadata.path}:${datasetDiscriminator}`
4883
+ )}`
4884
+ });
4415
4885
  const accessor = {
4416
4886
  path: metadata.path,
4417
4887
  count: metadata.count,
@@ -4419,7 +4889,7 @@ function buildListAccessors(resolved, lists) {
4419
4889
  };
4420
4890
  Object.defineProperty(accessor, "get", {
4421
4891
  value() {
4422
- return rows;
4892
+ return dataset;
4423
4893
  },
4424
4894
  enumerable: false
4425
4895
  });
@@ -4427,6 +4897,37 @@ function buildListAccessors(resolved, lists) {
4427
4897
  })
4428
4898
  );
4429
4899
  }
4900
+ function listRowsFingerprint(rows) {
4901
+ try {
4902
+ return sha256Hex(
4903
+ JSON.stringify(
4904
+ {
4905
+ count: rows.length,
4906
+ rows
4907
+ },
4908
+ (_key, value) => typeof value === "bigint" ? value.toString() : value
4909
+ )
4910
+ ).slice(0, 12);
4911
+ } catch {
4912
+ return sha256Hex(String(rows.length)).slice(0, 12);
4913
+ }
4914
+ }
4915
+ function listTableNamespace(toolId, name, path, discriminator) {
4916
+ const raw = `${toolId}_${name || path || "rows"}_${sha256Hex(discriminator).slice(0, 10)}`;
4917
+ try {
4918
+ return normalizeTableNamespace(raw);
4919
+ } catch {
4920
+ const hash = sha256Hex(raw).slice(0, 10);
4921
+ const leaf = name || path.split(".").filter(Boolean).at(-1) || "rows";
4922
+ let prefix = "rows";
4923
+ try {
4924
+ prefix = normalizeTableNamespace(leaf).slice(0, 52) || "rows";
4925
+ } catch {
4926
+ prefix = "rows";
4927
+ }
4928
+ return normalizeTableNamespace(`${prefix}_${hash}`);
4929
+ }
4930
+ }
4430
4931
  function createToolExecuteResult(input) {
4431
4932
  const result = toResultEnvelope(input.result);
4432
4933
  const resultRoot = {
@@ -4457,7 +4958,12 @@ function createToolExecuteResult(input) {
4457
4958
  ...result.meta ? { meta: result.meta } : {}
4458
4959
  };
4459
4960
  const extractedValues = buildExtractedAccessors(targets);
4460
- const extractedLists = buildListAccessors(resolvedLists, lists);
4961
+ const extractedLists = buildListAccessors(
4962
+ resolvedLists,
4963
+ lists,
4964
+ input.metadata.toolId,
4965
+ input.jobId ?? input.execution.cacheKey ?? "inline"
4966
+ );
4461
4967
  const wrapper = {
4462
4968
  status: input.status,
4463
4969
  ...input.jobId ? { job_id: input.jobId } : {},
@@ -4995,7 +5501,7 @@ function getDefinedPlayMetadata(value) {
4995
5501
  // src/tool-output.ts
4996
5502
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
4997
5503
  import { homedir as homedir4 } from "os";
4998
- import { join as join3 } from "path";
5504
+ import { dirname as dirname2, join as join3 } from "path";
4999
5505
  function isPlainObject(value) {
5000
5506
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5001
5507
  }
@@ -5068,16 +5574,24 @@ function tryConvertToList(payload, options) {
5068
5574
  (entry) => typeof entry === "string" && entry.trim().length > 0
5069
5575
  ) : [];
5070
5576
  if (listExtractorPaths.length > 0) {
5577
+ let emptyMatch = null;
5071
5578
  for (const root of candidateRoots(payload)) {
5072
5579
  for (const extractorPath of listExtractorPaths) {
5073
5580
  const resolved = getByDottedPath(root.value, extractorPath);
5074
5581
  const rows = normalizeRows2(resolved);
5075
- if (rows && rows.length > 0) {
5076
- const sourcePath = root.path ? `${root.path}.${extractorPath}` : extractorPath;
5582
+ if (!rows) {
5583
+ continue;
5584
+ }
5585
+ const sourcePath = root.path ? `${root.path}.${extractorPath}` : extractorPath;
5586
+ if (rows.length > 0) {
5077
5587
  return { rows, strategy: "configured_paths", sourcePath };
5078
5588
  }
5589
+ emptyMatch ??= { rows, strategy: "configured_paths", sourcePath };
5079
5590
  }
5080
5591
  }
5592
+ if (emptyMatch) {
5593
+ return emptyMatch;
5594
+ }
5081
5595
  }
5082
5596
  for (const root of candidateRoots(payload)) {
5083
5597
  const candidate = findBestArrayCandidate(root.value, root.path ?? "");
@@ -5101,9 +5615,9 @@ function writeJsonOutputFile(payload, stem) {
5101
5615
  writeFileSync2(outputPath, JSON.stringify(payload, null, 2), "utf-8");
5102
5616
  return outputPath;
5103
5617
  }
5104
- function writeCsvOutputFile(rows, stem) {
5105
- const outputDir = ensureOutputDir();
5106
- const outputPath = join3(outputDir, `${stem}_${Date.now()}.csv`);
5618
+ function writeCsvOutputFile(rows, stem, options) {
5619
+ const outputPath = options?.outPath ? options.outPath : join3(ensureOutputDir(), `${stem}_${Date.now()}.csv`);
5620
+ mkdirSync2(dirname2(outputPath), { recursive: true });
5107
5621
  const seen = /* @__PURE__ */ new Set();
5108
5622
  const columns = [];
5109
5623
  for (const row of rows) {