dataiku-sdk 3.9.0 → 4.1.0

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 (74) hide show
  1. package/README.md +4 -3
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/packages/types/src/index.d.ts +6 -0
  4. package/dist/packages/types/src/index.js +6 -0
  5. package/dist/src/cli/coerce.d.ts +6 -0
  6. package/dist/src/cli/coerce.js +9 -0
  7. package/dist/src/cli/command-syntax.json +216 -0
  8. package/dist/src/cli/commands/app.js +2 -2
  9. package/dist/src/cli/commands/code-env.js +10 -9
  10. package/dist/src/cli/commands/code.js +16 -2
  11. package/dist/src/cli/commands/dashboard.js +1 -2
  12. package/dist/src/cli/commands/dataset.js +6 -4
  13. package/dist/src/cli/commands/flow-zone.js +1 -1
  14. package/dist/src/cli/commands/job.js +70 -16
  15. package/dist/src/cli/commands/plugin.js +7 -7
  16. package/dist/src/cli/commands/project-library.js +2 -2
  17. package/dist/src/cli/commands/project.js +21 -4
  18. package/dist/src/cli/commands/recipe.js +34 -21
  19. package/dist/src/cli/commands/scenario.js +78 -12
  20. package/dist/src/cli/contract.js +30 -7
  21. package/dist/src/cli/env.d.ts +6 -0
  22. package/dist/src/cli/env.js +10 -3
  23. package/dist/src/cli/helpers/app-permissions.d.ts +1 -1
  24. package/dist/src/cli/helpers/app-permissions.js +2 -2
  25. package/dist/src/cli/helpers/cleanup.d.ts +2 -0
  26. package/dist/src/cli/helpers/cleanup.js +122 -8
  27. package/dist/src/cli/helpers/flow-zone.d.ts +1 -1
  28. package/dist/src/cli/helpers/flow-zone.js +3 -4
  29. package/dist/src/cli/list-output.d.ts +5 -0
  30. package/dist/src/cli/list-output.js +11 -2
  31. package/dist/src/cli/list-shapes.d.ts +6 -0
  32. package/dist/src/cli/list-shapes.js +7 -1
  33. package/dist/src/cli/output-schemas.d.ts +1 -1
  34. package/dist/src/cli/output-schemas.js +1 -1
  35. package/dist/src/cli/output.d.ts +2 -0
  36. package/dist/src/cli/output.js +31 -4
  37. package/dist/src/cli/read-shapes.js +90 -0
  38. package/dist/src/cli/result-shapes.d.ts +19 -1
  39. package/dist/src/cli/result-shapes.js +227 -4
  40. package/dist/src/cli/selector-suggestions.d.ts +14 -0
  41. package/dist/src/cli/selector-suggestions.js +56 -0
  42. package/dist/src/cli/usage.d.ts +1 -1
  43. package/dist/src/cli/usage.js +29 -4
  44. package/dist/src/cli/version.d.ts +2 -2
  45. package/dist/src/cli/version.js +4 -5
  46. package/dist/src/cli.js +128 -30
  47. package/dist/src/client.d.ts +15 -2
  48. package/dist/src/client.js +36 -11
  49. package/dist/src/errors.d.ts +2 -0
  50. package/dist/src/errors.js +12 -2
  51. package/dist/src/generated/action-output-schemas.json +232 -0
  52. package/dist/src/index.d.ts +1 -1
  53. package/dist/src/resources/datasets.d.ts +11 -1
  54. package/dist/src/resources/datasets.js +91 -30
  55. package/dist/src/resources/jobs.d.ts +35 -5
  56. package/dist/src/resources/jobs.js +68 -41
  57. package/dist/src/resources/projects.js +2 -2
  58. package/dist/src/resources/recipe-create.d.ts +4 -1
  59. package/dist/src/resources/recipe-create.js +11 -2
  60. package/dist/src/resources/recipes.d.ts +12 -1
  61. package/dist/src/resources/recipes.js +39 -12
  62. package/dist/src/resources/scenarios.d.ts +8 -1
  63. package/dist/src/resources/scenarios.js +51 -12
  64. package/dist/src/resources/sql.js +3 -3
  65. package/dist/src/resources/wiki.js +3 -7
  66. package/dist/src/skill.js +1 -2
  67. package/dist/src/utils/project-archive.d.ts +25 -0
  68. package/dist/src/utils/project-archive.js +216 -5
  69. package/dist/src/utils/response-file.js +15 -8
  70. package/package.json +12 -12
  71. package/plugin.json +1 -1
  72. package/skills/dataiku-dss/SKILL.md +15 -17
  73. package/skills/dataiku-dss/references/discovery.md +2 -2
  74. package/skills/dataiku-dss/references/troubleshooting.md +1 -0
@@ -1,6 +1,5 @@
1
1
  import { readFileSync, } from "node:fs";
2
- import { dirname, resolve, } from "node:path";
3
- import { fileURLToPath, } from "node:url";
2
+ import { resolve, } from "node:path";
4
3
  const projectEnvVars = new Set();
5
4
  const installEnvVars = new Set();
6
5
  /**
@@ -24,6 +23,14 @@ export function dataikuEnvironmentEnabled() {
24
23
  export function ambientProjectKey() {
25
24
  return dataikuEnvironmentEnabled() ? process.env.DATAIKU_PROJECT_KEY : undefined;
26
25
  }
26
+ /**
27
+ * `DSS_PRETTY=1` indents stdout JSON. Agent shells that cut long lines (omp at
28
+ * 768 bytes) truncate a one-line result; indented output keeps each line short.
29
+ * Read from the process environment only, never from a `.env` file.
30
+ */
31
+ export function prettyOutputEnabled() {
32
+ return process.env.DSS_PRETTY === "1";
33
+ }
27
34
  const allowedEnvKeys = new Set(["NODE_EXTRA_CA_CERTS", "NODE_TLS_REJECT_UNAUTHORIZED",]);
28
35
  /**
29
36
  * `.env` files are untrusted input: only the CLI's own `DATAIKU_*` variables and
@@ -42,7 +49,7 @@ export function loadEnvFile() {
42
49
  // CLI. First writer wins below, so cwd must be listed first.
43
50
  const dirs = [
44
51
  process.cwd(),
45
- resolve(dirname(fileURLToPath(import.meta.url)), "..", ".."),
52
+ resolve(import.meta.dir, "..", ".."),
46
53
  ];
47
54
  for (const [index, dir,] of dirs.entries()) {
48
55
  try {
@@ -43,7 +43,7 @@ export declare function buildAppPermissionsSnapshot(projectKey: string, dssUrl:
43
43
  export declare function writeAppPermissionsSnapshot(filePath: string, snapshot: AppPermissionsSnapshot): void;
44
44
  /** Validate envelope shape, version, and hash integrity. Never echoes permission payloads. */
45
45
  export declare function parseAppPermissionsSnapshot(text: string, source: string): AppPermissionsSnapshot;
46
- export declare function readAppPermissionsSnapshot(filePath: string): AppPermissionsSnapshot;
46
+ export declare function readAppPermissionsSnapshot(filePath: string): Promise<AppPermissionsSnapshot>;
47
47
  /**
48
48
  * Refuse to use a snapshot outside the exact project *and* DSS server it was captured
49
49
  * from. Access-control rules are server-scoped: the same project key, users, and groups
@@ -153,10 +153,10 @@ export function parseAppPermissionsSnapshot(text, source) {
153
153
  permissions,
154
154
  };
155
155
  }
156
- export function readAppPermissionsSnapshot(filePath) {
156
+ export async function readAppPermissionsSnapshot(filePath) {
157
157
  let text;
158
158
  try {
159
- text = fs.readFileSync(filePath, "utf-8");
159
+ text = await fs.promises.readFile(filePath, "utf-8");
160
160
  }
161
161
  catch (error) {
162
162
  const message = error instanceof Error ? error.message : String(error);
@@ -1,3 +1,5 @@
1
1
  import type { CleanupLedgerEntry } from "../../utils/cleanup-ledger.js";
2
2
  export declare function projectArg(projectKey: string | undefined): string[];
3
+ /** Input field that identifies the created object, when the result does not. */
4
+ export declare function cleanupInputIdentifierField(resource: string, action: string): string | undefined;
3
5
  export declare function cleanupLedgerEntry(resource: string, action: string, args: string[], flags: Record<string, string | boolean>, result: unknown, projectKey: string | undefined): CleanupLedgerEntry | undefined;
@@ -35,6 +35,30 @@ function createInputFromFlags(flags) {
35
35
  return undefined;
36
36
  }
37
37
  }
38
+ /**
39
+ * Creates whose DSS response carries no identifier: the cleanup ledger pins the
40
+ * created object from this field of the `--data`/`--data-file` input instead.
41
+ * `--stdin` cannot be replayed, so the CLI rejects it with `--record-cleanup`
42
+ * before mutating.
43
+ */
44
+ const CLEANUP_INPUT_IDENTIFIER_FIELDS = {
45
+ "workspace.create": "workspaceKey",
46
+ "connection.create": "name",
47
+ "user.create": "login",
48
+ "group.create": "name",
49
+ };
50
+ /** Input field that identifies the created object, when the result does not. */
51
+ export function cleanupInputIdentifierField(resource, action) {
52
+ return CLEANUP_INPUT_IDENTIFIER_FIELDS[`${resource}.${action}`];
53
+ }
54
+ function inputIdentifier(resource, action, flags) {
55
+ const field = cleanupInputIdentifierField(resource, action);
56
+ if (!field)
57
+ return undefined;
58
+ const input = resultRecord(createInputFromFlags(flags));
59
+ const identifier = stringField(input, [field,]);
60
+ return identifier ? { identifier, input, } : undefined;
61
+ }
38
62
  export function cleanupLedgerEntry(resource, action, args, flags, result, projectKey) {
39
63
  const record = resultRecord(result);
40
64
  // A notebook save is ledger-eligible only when it created the notebook:
@@ -77,6 +101,22 @@ export function cleanupLedgerEntry(resource, action, args, flags, result, projec
77
101
  cleanup: { argv: ["dataset", "delete", name, "--if-exists", ...withProject,], },
78
102
  };
79
103
  }
104
+ case "dataset.create-managed": {
105
+ // The handler returns `created` as the dataset name and the project
106
+ // DSS created it in, which wins over the requested project key.
107
+ const name = stringField(record, ["created", "name",]) ?? flags["name"];
108
+ if (!name)
109
+ return undefined;
110
+ const landedProject = stringField(record, ["projectKey",]) ?? project;
111
+ return {
112
+ ...base,
113
+ ...(landedProject ? { projectKey: landedProject, } : {}),
114
+ name,
115
+ cleanup: {
116
+ argv: ["dataset", "delete", name, "--if-exists", ...projectArg(landedProject),],
117
+ },
118
+ };
119
+ }
80
120
  case "recipe.create": {
81
121
  const name = stringField(record, ["created", "recipeName", "name",])
82
122
  ?? flags["name"];
@@ -199,6 +239,78 @@ export function cleanupLedgerEntry(resource, action, args, flags, result, projec
199
239
  cleanup: { argv: ["ml-task", "delete", analysisId, taskId, ...withProject,], },
200
240
  };
201
241
  }
242
+ case "ml-task.create-for-dataset": {
243
+ // This create opens a new visual analysis around the task, so cleanup
244
+ // removes the whole analysis rather than leaving an empty one behind.
245
+ const analysisId = stringField(record, ["analysisId",]);
246
+ if (!analysisId)
247
+ return undefined;
248
+ return {
249
+ ...base,
250
+ id: analysisId,
251
+ cleanup: { argv: ["analysis", "delete", analysisId, "--if-exists", ...withProject,], },
252
+ };
253
+ }
254
+ case "saved-model.create-external": {
255
+ const id = stringField(record, ["id",]);
256
+ if (!id)
257
+ return undefined;
258
+ const landedProject = stringField(record, ["projectKey",]) ?? project;
259
+ return {
260
+ ...base,
261
+ ...(landedProject ? { projectKey: landedProject, } : {}),
262
+ id,
263
+ name: args[0],
264
+ cleanup: {
265
+ argv: ["saved-model", "delete", id, "--if-exists", ...projectArg(landedProject),],
266
+ },
267
+ };
268
+ }
269
+ case "project-folder.create-child": {
270
+ // `created` falls back to the requested name when DSS omits the id,
271
+ // and a name cannot address a folder delete: only `id` pins it.
272
+ const id = stringField(record, ["id",]);
273
+ if (!id)
274
+ return undefined;
275
+ return {
276
+ ...base,
277
+ id,
278
+ name: flags["name"],
279
+ cleanup: { argv: ["project-folder", "delete", id, "--if-exists",], },
280
+ };
281
+ }
282
+ case "data-collection.create": {
283
+ const id = stringField(record, ["id",]);
284
+ if (!id)
285
+ return undefined;
286
+ return {
287
+ ...base,
288
+ id,
289
+ cleanup: { argv: ["data-collection", "delete", id,], },
290
+ };
291
+ }
292
+ case "plugin.create-dev": {
293
+ const id = stringField(record, ["created",]) ?? args[0];
294
+ if (!id)
295
+ return undefined;
296
+ return {
297
+ ...base,
298
+ id,
299
+ cleanup: { argv: ["plugin", "delete", id,], },
300
+ };
301
+ }
302
+ case "connection.create":
303
+ case "user.create":
304
+ case "group.create": {
305
+ const pinned = inputIdentifier(resource, action, flags);
306
+ if (!pinned)
307
+ return undefined;
308
+ return {
309
+ ...base,
310
+ name: pinned.identifier,
311
+ cleanup: { argv: [resource, "delete", pinned.identifier, "--if-exists",], },
312
+ };
313
+ }
202
314
  case "data-quality.create-rule": {
203
315
  const ruleId = stringField(record, ["id", "created",]);
204
316
  if (!ruleId)
@@ -222,8 +334,11 @@ export function cleanupLedgerEntry(resource, action, args, flags, result, projec
222
334
  cleanup: { argv: ["code-env", "delete", lang, name, "--if-exists",], },
223
335
  };
224
336
  }
225
- case "streaming-endpoint.create": {
226
- const id = args[0];
337
+ case "streaming-endpoint.create":
338
+ case "streaming-endpoint.create-managed": {
339
+ const id = stringField(record, ["created",]) ?? args[0];
340
+ if (!id)
341
+ return undefined;
227
342
  return {
228
343
  ...base,
229
344
  id,
@@ -240,15 +355,14 @@ export function cleanupLedgerEntry(resource, action, args, flags, result, projec
240
355
  };
241
356
  }
242
357
  case "workspace.create": {
243
- const input = resultRecord(createInputFromFlags(flags));
244
- const workspaceKey = stringField(input, ["workspaceKey",]);
245
- if (!workspaceKey)
358
+ const pinned = inputIdentifier(resource, action, flags);
359
+ if (!pinned)
246
360
  return undefined;
247
361
  return {
248
362
  ...base,
249
- id: workspaceKey,
250
- name: stringField(input, ["displayName",]),
251
- cleanup: { argv: ["workspace", "delete", workspaceKey,], },
363
+ id: pinned.identifier,
364
+ name: stringField(pinned.input, ["displayName",]),
365
+ cleanup: { argv: ["workspace", "delete", pinned.identifier,], },
252
366
  };
253
367
  }
254
368
  case "project-library.create-file":
@@ -56,7 +56,7 @@ export declare function dedupeFlowZonePlanItems(items: FlowZoneItemInput[]): Flo
56
56
  export declare function flowZonePlanItemKeys(plan: FlowZoneOrganizePlan): Set<string>;
57
57
  export declare function validateUniqueFlowZoneAssignments(plan: FlowZoneOrganizePlan): void;
58
58
  export declare function parseFlowZoneOrganizePlan(input: Record<string, unknown>): FlowZoneOrganizePlan;
59
- export declare function readFlowZoneOrganizePlan(flags: Record<string, string | boolean>, usage: string): FlowZoneOrganizePlan;
59
+ export declare function readFlowZoneOrganizePlan(flags: Record<string, string | boolean>, usage: string): Promise<FlowZoneOrganizePlan>;
60
60
  export declare function findFlowZoneForPlan(zones: FlowZone[], plan: FlowZoneOrganizeZonePlan): FlowZone | undefined;
61
61
  export declare function ensureFlowZonePlanTarget(plan: FlowZoneOrganizeZonePlan, existing: FlowZone | undefined): void;
62
62
  export declare function flowZoneExplicitItems(zone: FlowZone): FlowZoneItemInput[];
@@ -1,8 +1,7 @@
1
- import { readFileSync, } from "node:fs";
2
1
  import { ClientValidationError, } from "../../errors.js";
3
2
  import { asRecord, } from "../../utils/records.js";
4
3
  import { compareStrings, } from "../../utils/stable-hash.js";
5
- import { finiteNumberField, jsonInput, optionalStringField, parseJsonObject, requiredStringArray, splitCsvFlag, } from "../coerce.js";
4
+ import { finiteNumberField, jsonInput, optionalStringField, parseJsonObject, readTextFile, requiredStringArray, splitCsvFlag, } from "../coerce.js";
6
5
  import { commandUsage, } from "../syntax.js";
7
6
  import { UsageError, } from "../usage.js";
8
7
  export function flowZoneId(value) {
@@ -257,9 +256,9 @@ export function parseFlowZoneOrganizePlan(input) {
257
256
  validateUniqueFlowZoneAssignments(plan);
258
257
  return plan;
259
258
  }
260
- export function readFlowZoneOrganizePlan(flags, usage) {
259
+ export async function readFlowZoneOrganizePlan(flags, usage) {
261
260
  const data = typeof flags["file"] === "string"
262
- ? parseJsonObject(readFileSync(flags["file"], "utf-8"), flags["file"])
261
+ ? parseJsonObject(await readTextFile(flags["file"]), flags["file"])
263
262
  : jsonInput(flags);
264
263
  if (!data) {
265
264
  throw new UsageError(`--data, --data-file, --file, or --stdin is required. Usage: ${usage}`, "missing_required_flag");
@@ -1,3 +1,7 @@
1
+ import { type CompactListField } from "./list-shapes.js";
2
+ type Item = Record<string, unknown>;
3
+ /** One compact field of a DSS list item, as `RESOURCE.list` prints it. */
4
+ export declare function compactValue(item: Item, spec: CompactListField): unknown;
1
5
  /**
2
6
  * Shape a `*.list` result for agents: `--contains` filters on identifier
3
7
  * fields, `--limit` caps the count (a `list_truncated` warning reports the
@@ -5,3 +9,4 @@
5
9
  * `--full` or `--fields` asks for the DSS objects.
6
10
  */
7
11
  export declare function shapeListResult(resource: string, result: unknown, flags: Record<string, string | boolean>): unknown;
12
+ export {};
@@ -4,10 +4,19 @@ import { enqueueCliWarning, } from "./output.js";
4
4
  function record(value) {
5
5
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6
6
  }
7
- function compactValue(item, spec) {
7
+ function valueAt(item, path) {
8
8
  let value = item;
9
- for (const key of spec.path.split("."))
9
+ for (const key of path.split("."))
10
10
  value = record(value)[key];
11
+ return value;
12
+ }
13
+ /** One compact field of a DSS list item, as `RESOURCE.list` prints it. */
14
+ export function compactValue(item, spec) {
15
+ const value = valueAt(item, spec.path);
16
+ if (value === undefined && spec.fallback
17
+ && valueAt(item, spec.fallback.when) === spec.fallback.equals) {
18
+ return spec.fallback.value;
19
+ }
11
20
  if (spec.roleRefs !== true)
12
21
  return value;
13
22
  // Dataset refs across every role of recipe inputs/outputs (`{main:{items:[{ref}]}}`).
@@ -9,6 +9,12 @@ export interface CompactListField {
9
9
  path: string;
10
10
  /** Flatten recipe roles (`{main:{items:[{ref}]}}`) to the dataset refs. */
11
11
  roleRefs?: true;
12
+ /** Value when `path` is absent and the item's `when` path equals `equals`. */
13
+ fallback?: {
14
+ when: string;
15
+ equals: unknown;
16
+ value: unknown;
17
+ };
12
18
  schema: Record<string, unknown>;
13
19
  }
14
20
  /**
@@ -51,7 +51,12 @@ export const COMPACT_LIST_FIELDS = {
51
51
  id: field("def.id", STRING),
52
52
  name: field("def.name", STRING),
53
53
  type: field("def.type", STRING),
54
- state: field("state", STRING),
54
+ // DSS lists in-progress jobs with `stableState: false` and no `state`
55
+ // (COMPUTING_DEPS or RUNNING); `job get` reports the exact phase.
56
+ state: {
57
+ ...field("state", STRING),
58
+ fallback: { when: "stableState", equals: false, value: "RUNNING", },
59
+ },
55
60
  startTime: field("startTime", NUMBER),
56
61
  endTime: field("endTime", NUMBER),
57
62
  },
@@ -83,6 +88,7 @@ export const COMPACT_LIST_FIELDS = {
83
88
  id: field("id", STRING),
84
89
  version: field("version", STRING),
85
90
  label: field("meta.label", STRING),
91
+ isDev: field("isDev", BOOLEAN),
86
92
  },
87
93
  macro: {
88
94
  runnableType: field("runnableType", STRING),
@@ -3,7 +3,7 @@
3
3
  * the single source of truth for `src/generated/action-output-schemas.json`,
4
4
  * which discovery reads as plain data so no TypeBox schema graph loads on the
5
5
  * discovery path. Regenerate with
6
- * `bun scripts/generate-action-output-schemas.mjs`; `bun run check` fails when
6
+ * `bun scripts/generate-action-output-schemas.mjs`; `bun run verify` fails when
7
7
  * the generated file is stale. SDK validation keeps using the TypeBox schema
8
8
  * objects from `packages/types` directly and never depends on the generated file.
9
9
  */
@@ -72,7 +72,7 @@ let commandOutputSchemas;
72
72
  * the single source of truth for `src/generated/action-output-schemas.json`,
73
73
  * which discovery reads as plain data so no TypeBox schema graph loads on the
74
74
  * discovery path. Regenerate with
75
- * `bun scripts/generate-action-output-schemas.mjs`; `bun run check` fails when
75
+ * `bun scripts/generate-action-output-schemas.mjs`; `bun run verify` fails when
76
76
  * the generated file is stale. SDK validation keeps using the TypeBox schema
77
77
  * objects from `packages/types` directly and never depends on the generated file.
78
78
  */
@@ -11,6 +11,8 @@ export declare function pickResultFields(item: unknown, fields: string[]): unkno
11
11
  * that are absent become null so every row keeps a stable, predictable shape.
12
12
  */
13
13
  export declare function projectResultFields(result: unknown, fields: string[], availableFieldNames?: () => string[]): unknown;
14
+ /** One JSON value plus newline for stdout; indented when `DSS_PRETTY=1`. */
15
+ export declare function stdoutJson(value: unknown): string;
14
16
  export declare function writeCommandResult(result: unknown): void;
15
17
  export declare function addTransientTargetContext(error: unknown, target: string, elapsedMs: number): never;
16
18
  export declare function isFailedWaitResult(result: unknown): boolean;
@@ -1,4 +1,5 @@
1
1
  import { DataikuError, } from "../errors.js";
2
+ import { prettyOutputEnabled, } from "./env.js";
2
3
  let outputFieldProjection;
3
4
  export function setOutputFieldProjection(fields) {
4
5
  outputFieldProjection = fields;
@@ -98,12 +99,16 @@ export function projectResultFields(result, fields, availableFieldNames) {
98
99
  return result.map((item) => pickResultFields(item, fields));
99
100
  return pickResultFields(result, fields);
100
101
  }
102
+ /** One JSON value plus newline for stdout; indented when `DSS_PRETTY=1`. */
103
+ export function stdoutJson(value) {
104
+ return `${JSON.stringify(value, null, prettyOutputEnabled() ? 2 : undefined)}\n`;
105
+ }
101
106
  export function writeCommandResult(result) {
102
107
  const projected = outputFieldProjection
103
108
  ? projectResultFields(result, outputFieldProjection)
104
109
  : result;
105
110
  flushCliWarnings();
106
- process.stdout.write(`${JSON.stringify(projected ?? { ok: true, })}\n`);
111
+ process.stdout.write(stdoutJson(projected ?? { ok: true, }));
107
112
  }
108
113
  export function addTransientTargetContext(error, target, elapsedMs) {
109
114
  if (error instanceof DataikuError && error.category === "transient") {
@@ -193,13 +198,35 @@ export function commandFailureMessage(result) {
193
198
  if (typeof message === "string" && message.length > 0)
194
199
  return message;
195
200
  }
201
+ if (record.timedOut === true && typeof record.jobId === "string"
202
+ && typeof record.progress === "object" && typeof record.state === "string") {
203
+ // A job wait result: the timeout only stopped polling, the DSS job is
204
+ // still running (and was not cancelled).
205
+ const resume = typeof record.resume === "string" ? ` Resume with: ${record.resume}` : "";
206
+ return `Timed out waiting for job ${record.jobId} after ${record.elapsedMs}ms (last known state: ${record.state}); the job is still running in DSS and was not cancelled.${resume}`;
207
+ }
208
+ if (record.timedOut === true) {
209
+ // ML training, scenario runs, futures, code run: the client stopped waiting. Only a
210
+ // result that says `cancelled: false` proves nothing was stopped; `code run` cleans up
211
+ // its throwaway scenario, so its `cleanup` status is what tells.
212
+ const known = typeof record.state === "string"
213
+ ? record.state
214
+ : typeof record.outcome === "string"
215
+ ? record.outcome
216
+ : undefined;
217
+ const elapsed = typeof record.elapsedMs === "number" ? ` after ${record.elapsedMs}ms` : "";
218
+ const after = record.cancelled === false
219
+ ? "the operation was not cancelled and may still be running in DSS."
220
+ : record.cleanup !== undefined
221
+ ? "the client stopped waiting; see `cleanup` for what happened to the run."
222
+ : "the client stopped waiting; the operation may still be running in DSS.";
223
+ return `Timed out waiting${elapsed}${known ? ` (last known state: ${known})` : ""}; ${after}`;
224
+ }
196
225
  const state = typeof record.state === "string"
197
226
  ? record.state
198
227
  : typeof record.outcome === "string"
199
228
  ? record.outcome
200
- : typeof record.timedOut === "boolean"
201
- ? "timed out"
202
- : undefined;
229
+ : undefined;
203
230
  return `Command completed with failed long-running result${state ? `: ${state}` : ""}.`;
204
231
  }
205
232
  if (isAssertionFailureResult(result)) {
@@ -32,6 +32,28 @@ export function compactReadOutputSchemas() {
32
32
  additionalProperties: false,
33
33
  properties: { type: STRING, message: STRING, },
34
34
  };
35
+ const triggerSchema = {
36
+ type: "object",
37
+ additionalProperties: false,
38
+ properties: { type: STRING, name: STRING, },
39
+ };
40
+ const failureSchema = {
41
+ type: "object",
42
+ additionalProperties: false,
43
+ properties: {
44
+ stepId: STRING,
45
+ jobId: STRING,
46
+ dataset: STRING,
47
+ recipe: STRING,
48
+ inputs: { type: "array", items: { type: "string", }, },
49
+ message: STRING,
50
+ },
51
+ };
52
+ const nextSchema = {
53
+ type: "array",
54
+ description: "Next commands to run, each as an argv array.",
55
+ items: { type: "array", items: STRING, },
56
+ };
35
57
  return {
36
58
  "job.get": {
37
59
  type: "object",
@@ -110,5 +132,73 @@ export function compactReadOutputSchemas() {
110
132
  lastModifiedBy: STRING,
111
133
  },
112
134
  },
135
+ "scenario.get-run": {
136
+ type: "object",
137
+ additionalProperties: false,
138
+ required: ["runId", "steps",],
139
+ properties: {
140
+ runId: STRING,
141
+ scenarioId: STRING,
142
+ projectKey: STRING,
143
+ outcome: STRING,
144
+ start: NUMBER,
145
+ end: NUMBER,
146
+ durationMs: NUMBER,
147
+ trigger: triggerSchema,
148
+ steps: {
149
+ type: "array",
150
+ items: {
151
+ type: "object",
152
+ additionalProperties: false,
153
+ properties: {
154
+ id: STRING,
155
+ name: STRING,
156
+ type: STRING,
157
+ outcome: STRING,
158
+ message: STRING,
159
+ datasets: {
160
+ type: "array",
161
+ items: {
162
+ type: "object",
163
+ additionalProperties: false,
164
+ properties: { dataset: STRING, projectKey: STRING, partition: STRING, outcome: STRING, },
165
+ },
166
+ },
167
+ jobs: {
168
+ type: "array",
169
+ items: {
170
+ type: "object",
171
+ additionalProperties: false,
172
+ properties: { jobId: STRING, outcome: STRING, message: STRING, },
173
+ },
174
+ },
175
+ },
176
+ },
177
+ },
178
+ failure: failureSchema,
179
+ next: nextSchema,
180
+ hint: {
181
+ type: "string",
182
+ description: "Edit-and-rerun commands when the failed recipe is known.",
183
+ },
184
+ },
185
+ },
186
+ "scenario.last-runs": {
187
+ type: "array",
188
+ items: {
189
+ type: "object",
190
+ additionalProperties: false,
191
+ required: ["runId",],
192
+ properties: {
193
+ runId: STRING,
194
+ outcome: STRING,
195
+ start: NUMBER,
196
+ end: NUMBER,
197
+ trigger: triggerSchema,
198
+ failure: { type: "object", additionalProperties: false, properties: { message: STRING, }, },
199
+ next: nextSchema,
200
+ },
201
+ },
202
+ },
113
203
  };
114
204
  }
@@ -1,6 +1,24 @@
1
+ type Item = Record<string, unknown>;
2
+ /** The failed activity of a job: the recipe that failed and the datasets it read. */
3
+ export interface FailedActivity {
4
+ recipe: string;
5
+ inputs: string[];
6
+ }
7
+ /**
8
+ * Failure diagnosis of a raw `scenario get-run` report: the first failure
9
+ * (`{ stepId, jobId, dataset, message }`) and the next command(s) to run as
10
+ * argv arrays. With the failed recipe known, `hint` names the edit-and-rerun
11
+ * loop, so the agent does not look up the write commands. Empty when the run did not fail.
12
+ */
13
+ export declare function scenarioRunDiagnosis(details: unknown, activity?: FailedActivity): {
14
+ failure?: Item;
15
+ next?: string[][];
16
+ hint?: string;
17
+ };
1
18
  /**
2
19
  * Compact a command result for agents: `*.list` items (filter, cap, compact
3
- * fields) and the four large read commands. `--full` and `--fields` bypass
20
+ * fields) and the large read commands. `--full` and `--fields` bypass
4
21
  * the read shapers so the DSS object (or a projection of it) is returned.
5
22
  */
6
23
  export declare function shapeCommandResult(resource: string, action: string, result: unknown, flags: Record<string, string | boolean>): unknown;
24
+ export {};