deepline 0.3.87 → 0.3.89

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 (69) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +532 -2
  2. package/dist/bundling-sources/sdk/src/config.ts +77 -0
  3. package/dist/bundling-sources/sdk/src/http.ts +76 -30
  4. package/dist/bundling-sources/sdk/src/index.ts +6 -0
  5. package/dist/bundling-sources/sdk/src/play.ts +15 -1
  6. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
  7. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  8. package/dist/bundling-sources/sdk/src/types.ts +83 -4
  9. package/dist/bundling-sources/shared_libs/monitors/deactivation-reasons.ts +16 -0
  10. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +263 -0
  11. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +9 -1
  12. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-state.ts +122 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/activity-observation.ts +6 -11
  14. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +90 -12
  15. package/dist/bundling-sources/shared_libs/play-runtime/async-play-invocation.ts +78 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/checkpoint-boundaries.ts +70 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +407 -53
  18. package/dist/bundling-sources/shared_libs/play-runtime/coordinator-headers.ts +1 -32
  19. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +57 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/default-batch-strategies.ts +20 -1
  21. package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +6 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/execution-protocol.ts +7 -1
  23. package/dist/bundling-sources/shared_libs/play-runtime/lane-retirement-gate.ts +189 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +28 -0
  25. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +2 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +23 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +1 -1
  28. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +18 -4
  29. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-start-admission.ts +68 -0
  30. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +2 -0
  31. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +1 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +10 -8
  33. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +20 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +6 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +24 -0
  36. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +30 -1
  37. package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +6 -3
  38. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +152 -17
  39. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +19 -4
  40. package/dist/bundling-sources/shared_libs/plays/contracts.ts +6 -14
  41. package/dist/bundling-sources/shared_libs/plays/core.ts +11 -6
  42. package/dist/bundling-sources/shared_libs/plays/docflow-binding-owner.ts +16 -11
  43. package/dist/bundling-sources/shared_libs/plays/resolve-static-pipeline.ts +1 -1
  44. package/dist/bundling-sources/shared_libs/plays/slug.ts +115 -0
  45. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +44 -14
  46. package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +877 -0
  47. package/dist/bundling-sources/shared_libs/product-notifications/configuration-draft.ts +456 -0
  48. package/dist/bundling-sources/shared_libs/product-notifications/configuration.ts +560 -0
  49. package/dist/bundling-sources/shared_libs/product-notifications/content.ts +28 -0
  50. package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +59 -0
  51. package/dist/bundling-sources/shared_libs/product-notifications/email.ts +246 -0
  52. package/dist/bundling-sources/shared_libs/product-notifications/evaluation.ts +280 -0
  53. package/dist/bundling-sources/shared_libs/product-notifications/events.ts +433 -0
  54. package/dist/bundling-sources/shared_libs/product-notifications/legacy-slack.ts +80 -0
  55. package/dist/bundling-sources/shared_libs/product-notifications/workspace.ts +38 -0
  56. package/dist/bundling-sources/shared_libs/security/internal-actor.ts +50 -0
  57. package/dist/cli/index.js +1065 -190
  58. package/dist/cli/index.mjs +1052 -177
  59. package/dist/{compiler-manifest-Dbov16F7.d.mts → compiler-manifest-DsLbQaew.d.mts} +132 -8
  60. package/dist/{compiler-manifest-Dbov16F7.d.ts → compiler-manifest-DsLbQaew.d.ts} +132 -8
  61. package/dist/index.d.mts +833 -11
  62. package/dist/index.d.ts +833 -11
  63. package/dist/index.js +467 -38
  64. package/dist/index.mjs +467 -38
  65. package/dist/install-integrity.json +20 -2
  66. package/dist/plays/bundle-play-file.d.mts +8 -3
  67. package/dist/plays/bundle-play-file.d.ts +8 -3
  68. package/dist/plays/bundle-play-file.mjs +100 -20
  69. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -587,6 +587,7 @@ var HOST_URL_ENV = "DEEPLINE_HOST_URL";
587
587
  var API_KEY_ENV = "DEEPLINE_API_KEY";
588
588
  var ACTIVE_ORG_ID_ENV = "DEEPLINE_ACTIVE_ORG_ID";
589
589
  var ACTIVE_ORG_NAME_ENV = "DEEPLINE_ACTIVE_ORG_NAME";
590
+ var FEATURES_ENV = "DEEPLINE_FEATURES";
590
591
  var PROD_URL = "https://code.deepline.com";
591
592
  var DEFAULT_TIMEOUT = 6e4;
592
593
  var DEFAULT_MAX_RETRIES = 3;
@@ -611,8 +612,40 @@ var CLI_ENV_ALLOWED_KEYS = /* @__PURE__ */ new Set([
611
612
  HOST_URL_ENV,
612
613
  API_KEY_ENV,
613
614
  ACTIVE_ORG_ID_ENV,
614
- ACTIVE_ORG_NAME_ENV
615
+ ACTIVE_ORG_NAME_ENV,
616
+ FEATURES_ENV
615
617
  ]);
618
+ function featureEnvValues(data) {
619
+ if (!Array.isArray(data.features)) return {};
620
+ return {
621
+ [FEATURES_ENV]: data.features.filter(
622
+ (key) => typeof key === "string" && key.trim() !== ""
623
+ ).map((key) => key.trim()).join(",")
624
+ };
625
+ }
626
+ function storedHostApiKey(baseUrl) {
627
+ return loadCliEnv(normalizeBaseUrl(baseUrl) || baseUrl)[API_KEY_ENV] ?? "";
628
+ }
629
+ function storedHostFeatures(baseUrl) {
630
+ return loadCliEnv(normalizeBaseUrl(baseUrl) || baseUrl)[FEATURES_ENV];
631
+ }
632
+ function envHasFeature(env, feature) {
633
+ return (env[FEATURES_ENV] ?? "").split(",").map((key) => key.trim()).includes(feature);
634
+ }
635
+ function cliFeatureEnabled(feature) {
636
+ const processEnv = process.env;
637
+ if (envHasFeature(processEnv, feature)) return true;
638
+ if (processEnv[API_KEY_ENV]?.trim()) return false;
639
+ try {
640
+ const baseUrl = resolveBaseUrlOnly();
641
+ const projectEnv = loadProjectEnvCandidates().find(
642
+ ({ env }) => normalizeBaseUrl(env[HOST_URL_ENV] ?? "") === baseUrl && Boolean(env[API_KEY_ENV])
643
+ );
644
+ return envHasFeature(projectEnv?.env ?? loadCliEnv(baseUrl), feature);
645
+ } catch {
646
+ return false;
647
+ }
648
+ }
616
649
  function baseUrlSlug(baseUrl) {
617
650
  let url;
618
651
  try {
@@ -812,6 +845,10 @@ function mergePersistedEnvValues(existing, values) {
812
845
  if (nextApiKey !== void 0 && nextApiKey !== existing[API_KEY_ENV] && values[ACTIVE_ORG_ID_ENV] === void 0) {
813
846
  delete merged[ACTIVE_ORG_ID_ENV];
814
847
  delete merged[ACTIVE_ORG_NAME_ENV];
848
+ delete merged[FEATURES_ENV];
849
+ }
850
+ if (values[FEATURES_ENV] === "") {
851
+ delete merged[FEATURES_ENV];
815
852
  }
816
853
  return merged;
817
854
  }
@@ -935,7 +972,10 @@ function resolveConfig(options) {
935
972
  apiKey,
936
973
  baseUrl,
937
974
  timeout: options?.timeout ?? DEFAULT_TIMEOUT,
938
- maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES
975
+ maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES,
976
+ // Pass through untouched: the transport owns case-insensitive merging so
977
+ // there is exactly one place that decides which header wins.
978
+ ...options?.headers ? { headers: { ...options.headers } } : {}
939
979
  };
940
980
  }
941
981
  function mergeProjectEnvFile(filePath, values) {
@@ -1225,7 +1265,7 @@ var SDK_RELEASE = {
1225
1265
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
1226
1266
  // getters keep their established compatibility behavior.
1227
1267
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
1228
- version: "0.3.87",
1268
+ version: "0.3.89",
1229
1269
  updateSummary: "Automatic CLI updates are now enabled by default. To opt out, run `deepline settings autoupdate off`; use `deepline settings autoupdate on` to re-enable updates or `deepline settings autoupdate pin <version>` to hold an exact release. This release also adds raw-v2 tool responses at toolResponse.rawV2 while preserving existing toolResponse.raw and declared getters.",
1230
1270
  packageCapabilities: {
1231
1271
  updatePreferences: 1
@@ -1779,11 +1819,33 @@ function normalizeRequestAbortError(error, input2) {
1779
1819
  }
1780
1820
  return normalized;
1781
1821
  }
1822
+ function applyExplicitHeaders(headers, explicit) {
1823
+ if (!explicit) return headers;
1824
+ for (const [rawName, value] of Object.entries(explicit)) {
1825
+ const name = rawName.trim().toLowerCase();
1826
+ if (!name) continue;
1827
+ for (const existing of Object.keys(headers)) {
1828
+ if (existing.toLowerCase() === name) delete headers[existing];
1829
+ }
1830
+ headers[name] = value;
1831
+ }
1832
+ return headers;
1833
+ }
1782
1834
  function isAbortLikeError(error) {
1783
1835
  const name = error.name.toLowerCase();
1784
1836
  const message = error.message.toLowerCase();
1785
1837
  return name === "aborterror" || message.includes("operation was aborted") || message === "aborted";
1786
1838
  }
1839
+ function withErrorCause(error, cause) {
1840
+ if (cause !== null && cause !== void 0) {
1841
+ Object.defineProperty(error, "cause", {
1842
+ value: cause,
1843
+ configurable: true,
1844
+ writable: true
1845
+ });
1846
+ }
1847
+ return error;
1848
+ }
1787
1849
  function mapNetworkError(baseUrl, error, targetLabel2) {
1788
1850
  const tagged = error;
1789
1851
  const timeoutMs = tagged?.[REQUEST_TIMEOUT_MARKER];
@@ -1900,7 +1962,7 @@ var HttpClient = class {
1900
1962
  if (coordinatorInternalToken?.trim() && (coordinatorUrl?.trim() || workerCallbackUrl?.trim() || runtimeTestFault?.trim() || absurdReleaseOverride?.trim() || preferredReceiptGatewayMachineId?.trim())) {
1901
1963
  headers[COORDINATOR_INTERNAL_TOKEN_HEADER] = coordinatorInternalToken.trim();
1902
1964
  }
1903
- return headers;
1965
+ return applyExplicitHeaders(headers, this.config.headers);
1904
1966
  }
1905
1967
  /**
1906
1968
  * Send an HTTP request with automatic retries and error handling.
@@ -2054,32 +2116,38 @@ var HttpClient = class {
2054
2116
  );
2055
2117
  if (options?.toolId) {
2056
2118
  const code = mappedNetworkError.code;
2057
- throw new ToolExecutionError(
2058
- withCoworkNetworkHint(mappedNetworkError.message),
2059
- {
2060
- toolId: options.toolId,
2061
- provider: providerLabelFromToolId(options.toolId),
2062
- operation: options.toolId,
2063
- code,
2064
- origin: "deepline",
2065
- category: "network",
2066
- // A direct client timeout has ambiguous delivery. Fail closed unless
2067
- // the server returns the authoritative action delivery contract.
2068
- retryable: false,
2069
- statusCode: null,
2070
- requestId: null,
2071
- retryAfterMs: null,
2072
- networkKind: code === "NETWORK_TIMEOUT" ? "timeout" : code === "NETWORK_ABORTED" ? "unknown" : "unavailable",
2073
- networkScope: "client_to_deepline",
2074
- details: mappedNetworkError.details
2075
- }
2119
+ throw withErrorCause(
2120
+ new ToolExecutionError(
2121
+ withCoworkNetworkHint(mappedNetworkError.message),
2122
+ {
2123
+ toolId: options.toolId,
2124
+ provider: providerLabelFromToolId(options.toolId),
2125
+ operation: options.toolId,
2126
+ code,
2127
+ origin: "deepline",
2128
+ category: "network",
2129
+ // A direct client timeout has ambiguous delivery. Fail closed unless
2130
+ // the server returns the authoritative action delivery contract.
2131
+ retryable: false,
2132
+ statusCode: null,
2133
+ requestId: null,
2134
+ retryAfterMs: null,
2135
+ networkKind: code === "NETWORK_TIMEOUT" ? "timeout" : code === "NETWORK_ABORTED" ? "unknown" : "unavailable",
2136
+ networkScope: "client_to_deepline",
2137
+ details: mappedNetworkError.details
2138
+ }
2139
+ ),
2140
+ lastError
2076
2141
  );
2077
2142
  }
2078
- throw new DeeplineError(
2079
- withCoworkNetworkHint(mappedNetworkError.message),
2080
- void 0,
2081
- mappedNetworkError.code,
2082
- mappedNetworkError.details
2143
+ throw withErrorCause(
2144
+ new DeeplineError(
2145
+ withCoworkNetworkHint(mappedNetworkError.message),
2146
+ void 0,
2147
+ mappedNetworkError.code,
2148
+ mappedNetworkError.details
2149
+ ),
2150
+ lastError
2083
2151
  );
2084
2152
  }
2085
2153
  /**
@@ -2453,6 +2521,13 @@ var PRODUCT_NOTIFICATION_DELIVERY_LEASE_MS = 2 * 6e4;
2453
2521
  var PRODUCT_NOTIFICATION_SUCCESS_TTL_MS = 15 * 6e4;
2454
2522
  var PRODUCT_NOTIFICATION_FAILURE_TTL_MS = 24 * 60 * 6e4;
2455
2523
 
2524
+ // ../product-notifications/configuration.ts
2525
+ var PRODUCT_NOTIFICATION_EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2526
+ function normalizeProductNotificationEmail(value) {
2527
+ const email = typeof value === "string" ? value.trim().toLowerCase() : "";
2528
+ return email && email.length <= 320 && PRODUCT_NOTIFICATION_EMAIL_PATTERN.test(email) ? email : null;
2529
+ }
2530
+
2456
2531
  // src/stream-reconnect.ts
2457
2532
  var STREAM_RECONNECT_BASE_DELAY_MS = 500;
2458
2533
  var STREAM_RECONNECT_MAX_DELAY_MS = 15e3;
@@ -2981,7 +3056,7 @@ function isRecord4(value) {
2981
3056
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
2982
3057
  }
2983
3058
  function isPlayActivityObservation(value) {
2984
- if (!isRecord4(value) || value.schemaVersion !== 1 || typeof value.activityId !== "string" || !value.activityId.trim() || typeof value.observedAt !== "number" || !Number.isFinite(value.observedAt) || !isRecord4(value.target) || !isRecord4(value.state)) {
3059
+ if (!isRecord4(value) || value.schemaVersion !== void 0 && value.schemaVersion !== 1 || typeof value.activityId !== "string" || !value.activityId.trim() || typeof value.observedAt !== "number" || !Number.isFinite(value.observedAt) || !isRecord4(value.target) || !isRecord4(value.state)) {
2985
3060
  return false;
2986
3061
  }
2987
3062
  const targetKinds = /* @__PURE__ */ new Set([
@@ -3088,7 +3163,6 @@ function projectPlayRunActivity(input2) {
3088
3163
  if (input2.waitKind === "detached_runner") {
3089
3164
  return projection(
3090
3165
  {
3091
- schemaVersion: 1,
3092
3166
  activityId: "runner:managed",
3093
3167
  target: {
3094
3168
  kind: "runner",
@@ -3104,7 +3178,6 @@ function projectPlayRunActivity(input2) {
3104
3178
  if (input2.waitKind === "sleep") {
3105
3179
  return projection(
3106
3180
  {
3107
- schemaVersion: 1,
3108
3181
  activityId: "run:sleep",
3109
3182
  target: {
3110
3183
  kind: "step",
@@ -3124,7 +3197,6 @@ function projectPlayRunActivity(input2) {
3124
3197
  if (input2.waitKind === "integration_event" || input2.waitKind === "integration_event_batch") {
3125
3198
  return projection(
3126
3199
  {
3127
- schemaVersion: 1,
3128
3200
  activityId: `event:${input2.activeNodeId ?? "run"}`,
3129
3201
  stepId: input2.activeNodeId ?? void 0,
3130
3202
  target: {
@@ -3148,7 +3220,6 @@ function projectPlayRunActivity(input2) {
3148
3220
  if (normalizedStatus === "waiting") {
3149
3221
  return projection(
3150
3222
  {
3151
- schemaVersion: 1,
3152
3223
  activityId: "run:legacy-wait",
3153
3224
  target: {
3154
3225
  kind: "step",
@@ -3175,7 +3246,6 @@ function projectPlayRunActivity(input2) {
3175
3246
  const datasetProgress = datasetStep?.progress ?? null;
3176
3247
  return projection(
3177
3248
  {
3178
- schemaVersion: 1,
3179
3249
  activityId: `dataset:${pendingDataset.datasetId ?? pendingDataset.tableNamespace}`,
3180
3250
  target: {
3181
3251
  kind: "dataset",
@@ -3203,7 +3273,6 @@ function projectPlayRunActivity(input2) {
3203
3273
  const provider = toolId?.split(/[._]/)[0]?.trim() || null;
3204
3274
  return projection(
3205
3275
  {
3206
- schemaVersion: 1,
3207
3276
  activityId: `step:${activeStep.nodeId}`,
3208
3277
  stepId: activeStep.nodeId,
3209
3278
  target: toolId && provider ? {
@@ -3228,7 +3297,6 @@ function projectPlayRunActivity(input2) {
3228
3297
  if (normalizedStatus === "queued") {
3229
3298
  return projection(
3230
3299
  {
3231
- schemaVersion: 1,
3232
3300
  activityId: "capacity:worker",
3233
3301
  target: { kind: "capacity", pool: "worker" },
3234
3302
  state: { kind: "queued", reason: "capacity" },
@@ -3238,7 +3306,6 @@ function projectPlayRunActivity(input2) {
3238
3306
  );
3239
3307
  }
3240
3308
  const fallback = {
3241
- schemaVersion: 1,
3242
3309
  activityId: "run:legacy",
3243
3310
  target: {
3244
3311
  kind: "step",
@@ -4498,6 +4565,42 @@ function providerMetaFromToolResponseRawV2(rawV2, view) {
4498
4565
  }
4499
4566
 
4500
4567
  // src/client.ts
4568
+ var MAX_NOTIFICATION_GROUP_CREATE_TARGETS = 50;
4569
+ function parseNotificationGroupTarget(value) {
4570
+ const normalized = value.trim();
4571
+ if (normalized.toLowerCase().startsWith("email:")) {
4572
+ const email = normalizeProductNotificationEmail(normalized.slice(6));
4573
+ if (!email) {
4574
+ throw new Error(
4575
+ `Invalid email notification target "${value}". Use --to email:name@example.com.`
4576
+ );
4577
+ }
4578
+ return { kind: "email", email };
4579
+ }
4580
+ const slack = /^slack:(.+)$/i.exec(normalized)?.[1]?.trim();
4581
+ if (!slack) {
4582
+ const provider = normalized.split(":", 1)[0] || normalized;
4583
+ throw new Error(
4584
+ `Unsupported notification destination "${provider}". Use email: or slack:.`
4585
+ );
4586
+ }
4587
+ const member = /^member:([UW][A-Z0-9]{8,})$/i.exec(slack);
4588
+ if (member?.[1]) {
4589
+ return { kind: "slack", memberId: member[1].toUpperCase() };
4590
+ }
4591
+ if (slack.toLowerCase().startsWith("member:")) {
4592
+ throw new Error(
4593
+ "Slack member IDs must look like slack:member:U0123456789."
4594
+ );
4595
+ }
4596
+ if (!slack.replace(/^#/, "").trim()) {
4597
+ throw new Error("Use --to slack:#channel.");
4598
+ }
4599
+ return { kind: "slack", channel: slack };
4600
+ }
4601
+ function notificationObjectKey(kind) {
4602
+ return `${kind}.${globalThis.crypto.randomUUID()}`;
4603
+ }
4501
4604
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
4502
4605
  var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
4503
4606
  var EXECUTE_RESPONSE_CONTRACT_HEADER = "x-deepline-execute-response-contract";
@@ -5067,6 +5170,10 @@ var DeeplineClient = class {
5067
5170
  audit: (options2) => this.auditMonitors(options2),
5068
5171
  repair: (options2) => this.repairMonitors(options2),
5069
5172
  health: (options2) => this.readMonitorsHealth(options2),
5173
+ batch: {
5174
+ submit: (options2) => this.submitMonitorBatch(options2),
5175
+ get: (operationId, options2) => this.getMonitorBatch(operationId, options2)
5176
+ },
5070
5177
  fleets: {
5071
5178
  sync: (definitionOrId, options2) => this.syncMonitorFleet(definitionOrId, options2),
5072
5179
  get: (fleetId, options2) => fleetId === void 0 ? this.listMonitorFleets() : this.getMonitorFleet(fleetId, options2),
@@ -5199,6 +5306,16 @@ var DeeplineClient = class {
5199
5306
  (secret) => secret.name === normalized && secret.status === "active" && secret.hasValue
5200
5307
  ) ?? null;
5201
5308
  }
5309
+ /** Check effective scope and active-version health without reading the value.
5310
+ * Without playName, checks the organization-scoped secret only. With a Play
5311
+ * name, an active Play secret takes precedence; a deleted/disabled Play
5312
+ * secret falls back to the organization-scoped secret.
5313
+ */
5314
+ async inspectSecret(name, options = {}) {
5315
+ const params = new URLSearchParams({ name: name.trim().toUpperCase() });
5316
+ if (options.playName !== void 0) params.set("play", options.playName);
5317
+ return this.http.get(`/api/v2/secrets?${params.toString()}`);
5318
+ }
5202
5319
  // ——————————————————————————————————————————————————————————
5203
5320
  // Tools
5204
5321
  // ——————————————————————————————————————————————————————————
@@ -5290,6 +5407,10 @@ var DeeplineClient = class {
5290
5407
  `/api/v2/integrations/${encodeURIComponent(toolId)}/get`,
5291
5408
  {
5292
5409
  method: "GET",
5410
+ // Tool metadata is a read-only catalog lookup. Keep the metadata
5411
+ // header while opting into the same transient edge-response retries as
5412
+ // other GET catalog methods such as searchTools().
5413
+ retryApiErrors: true,
5293
5414
  headers: {
5294
5415
  "x-deepline-tool-meta-only": "1"
5295
5416
  }
@@ -6745,6 +6866,197 @@ var DeeplineClient = class {
6745
6866
  async getNotifications() {
6746
6867
  return this.http.get("/api/v2/notifications");
6747
6868
  }
6869
+ /** Read the group-based product-notification configuration and rule catalog. */
6870
+ async getNotificationConfiguration() {
6871
+ return this.http.get("/api/v2/notifications/configuration");
6872
+ }
6873
+ /** Atomically apply one retry-safe desired configuration change. */
6874
+ async changeNotificationConfiguration(expectedWorkspaceVersion, change) {
6875
+ return this.http.put("/api/v2/notifications/configuration", {
6876
+ expectedWorkspaceVersion,
6877
+ change
6878
+ });
6879
+ }
6880
+ /** Create one notification group and its destinations as a retry-safe unit. */
6881
+ async createNotificationGroup(input2) {
6882
+ const name = input2.name.trim().replace(/\s+/g, " ");
6883
+ if (!name) throw new Error("Notification group name is required.");
6884
+ if (input2.targets.length < 1 || input2.targets.length > MAX_NOTIFICATION_GROUP_CREATE_TARGETS) {
6885
+ throw new Error(
6886
+ `Choose between 1 and ${MAX_NOTIFICATION_GROUP_CREATE_TARGETS} notification targets.`
6887
+ );
6888
+ }
6889
+ const targets = input2.targets.map(parseNotificationGroupTarget);
6890
+ const legacyEvents = input2.legacyEvents ?? [];
6891
+ const legacyTarget = targets.length === 1 && targets[0]?.kind === "slack" && legacyEvents.length > 0 ? targets[0] : null;
6892
+ if (legacyEvents.length > 0 && !legacyTarget) {
6893
+ throw new Error(
6894
+ "--for is supported only by the legacy one-Slack-target form. Create the group first, then add rules in notification configuration."
6895
+ );
6896
+ }
6897
+ if (!legacyTarget && name.toLowerCase() === "default") {
6898
+ throw new Error(
6899
+ "Default is reserved for the workspace default notification group."
6900
+ );
6901
+ }
6902
+ const normalizedTargets = targets.map(
6903
+ (target) => target.kind === "email" ? `email:${target.email}` : "memberId" in target ? `slack:member:${target.memberId}` : `slack:${target.channel}`
6904
+ );
6905
+ if (input2.dryRun) {
6906
+ return {
6907
+ mode: "dry_run",
6908
+ compatibilityMode: legacyTarget !== null,
6909
+ name: legacyTarget ? input2.name : name,
6910
+ targets: normalizedTargets,
6911
+ legacyEvents
6912
+ };
6913
+ }
6914
+ if (legacyTarget) {
6915
+ const notification = await this.createNotification({
6916
+ name: input2.name,
6917
+ provider: "slack",
6918
+ ..."channel" in legacyTarget ? { channel: legacyTarget.channel } : { memberId: legacyTarget.memberId },
6919
+ eventTypes: legacyEvents
6920
+ });
6921
+ return { mode: "legacy", notification };
6922
+ }
6923
+ const current = await this.getNotificationConfiguration();
6924
+ const existingGroup = current.configuration.groups.find(
6925
+ (candidate) => candidate.name.toLowerCase() === name.toLowerCase()
6926
+ );
6927
+ const groupKey = existingGroup?.groupKey ?? notificationObjectKey("group");
6928
+ const group = existingGroup ?? {
6929
+ groupKey,
6930
+ name,
6931
+ enabled: true,
6932
+ deliveryTiming: { mode: "immediate" },
6933
+ source: "workspace",
6934
+ configMode: "custom",
6935
+ version: 0
6936
+ };
6937
+ const destinations = current.configuration.destinations.filter(
6938
+ (destination) => destination.groupKey === groupKey
6939
+ );
6940
+ const emails = [
6941
+ ...new Set(
6942
+ targets.flatMap(
6943
+ (target) => target.kind === "email" ? [target.email] : []
6944
+ )
6945
+ )
6946
+ ].sort();
6947
+ const existingEmails = new Set(
6948
+ destinations.flatMap(
6949
+ (destination) => destination.settings.kind === "email_addresses" ? destination.settings.to : []
6950
+ )
6951
+ );
6952
+ const newEmails = emails.filter((email) => !existingEmails.has(email));
6953
+ if (newEmails.length > 0) {
6954
+ destinations.push({
6955
+ destinationKey: notificationObjectKey("destination"),
6956
+ groupKey,
6957
+ name: "Email recipients",
6958
+ enabled: true,
6959
+ source: "workspace",
6960
+ configMode: "custom",
6961
+ version: 0,
6962
+ status: "active",
6963
+ settings: { kind: "email_addresses", to: newEmails }
6964
+ });
6965
+ }
6966
+ const slackTargets = /* @__PURE__ */ new Map();
6967
+ for (const target of targets) {
6968
+ if (target.kind !== "slack") continue;
6969
+ const targetKey = "memberId" in target ? `member:${target.memberId}` : `channel:${target.channel.replace(/^#/, "").toLowerCase()}`;
6970
+ slackTargets.set(targetKey, target);
6971
+ }
6972
+ const connectionId = current.slackConnection.connectionId;
6973
+ if (slackTargets.size > 0 && !connectionId) {
6974
+ throw new Error(
6975
+ "Slack is not connected to this workspace. Connect it in Dashboard \u2192 Integrations, then retry."
6976
+ );
6977
+ }
6978
+ const resolvedSlackTargets = await Promise.all(
6979
+ [...slackTargets.values()].map(async (target) => {
6980
+ if ("memberId" in target) {
6981
+ return {
6982
+ targetKind: "member",
6983
+ targetId: target.memberId,
6984
+ targetName: target.memberId
6985
+ };
6986
+ }
6987
+ const channel = target.channel;
6988
+ const requested = channel.replace(/^#/, "");
6989
+ const response = await this.listNotificationChannels(channel);
6990
+ const matches = response.channels.filter(
6991
+ (candidate) => candidate.id === requested || candidate.name.toLowerCase() === requested.toLowerCase()
6992
+ );
6993
+ if (matches.length !== 1) {
6994
+ throw new Error(
6995
+ matches.length === 0 ? `Slack channel ${channel} was not found or is not visible to the Deepline app.` : `Slack channel ${channel} is ambiguous. Use its channel ID.`
6996
+ );
6997
+ }
6998
+ return {
6999
+ targetKind: "channel",
7000
+ targetId: matches[0].id,
7001
+ targetName: matches[0].name
7002
+ };
7003
+ })
7004
+ );
7005
+ const uniqueSlackTargets = new Map(
7006
+ resolvedSlackTargets.map((target) => [
7007
+ `${target.targetKind}:${target.targetId}`,
7008
+ target
7009
+ ])
7010
+ );
7011
+ for (const target of uniqueSlackTargets.values()) {
7012
+ if (destinations.some(
7013
+ (destination) => destination.settings.kind === "slack" && destination.settings.targetKind === target.targetKind && destination.settings.targetId === target.targetId
7014
+ )) {
7015
+ continue;
7016
+ }
7017
+ destinations.push({
7018
+ destinationKey: notificationObjectKey("destination"),
7019
+ groupKey,
7020
+ name: `${target.targetKind === "channel" ? "#" : "@"}${target.targetName}`,
7021
+ enabled: true,
7022
+ source: "workspace",
7023
+ configMode: "custom",
7024
+ version: 0,
7025
+ status: "pending_verification",
7026
+ settings: {
7027
+ kind: "slack",
7028
+ ...target,
7029
+ oauthConnectionId: connectionId
7030
+ }
7031
+ });
7032
+ }
7033
+ const draft = {
7034
+ group,
7035
+ destinations,
7036
+ rules: current.configuration.rules.filter(
7037
+ (rule) => rule.groupKey === groupKey
7038
+ )
7039
+ };
7040
+ const result = await this.changeNotificationConfiguration(
7041
+ current.configuration.workspaceVersion,
7042
+ { kind: "save_group", draft }
7043
+ );
7044
+ const storedGroup = result.configuration.groups.find(
7045
+ (candidate) => candidate.groupKey === groupKey
7046
+ );
7047
+ if (!storedGroup) {
7048
+ throw new Error("Notification group creation returned no saved group.");
7049
+ }
7050
+ return {
7051
+ mode: "native",
7052
+ groupKey,
7053
+ targets: normalizedTargets,
7054
+ group: storedGroup,
7055
+ destinations: result.configuration.destinations.filter(
7056
+ (destination) => destination.groupKey === groupKey
7057
+ )
7058
+ };
7059
+ }
6748
7060
  /** List Slack channels available to an already-connected Slack integration. */
6749
7061
  async listNotificationChannels(query) {
6750
7062
  const suffix = query ? `?search=${encodeURIComponent(query)}` : "";
@@ -7289,6 +7601,78 @@ var DeeplineClient = class {
7289
7601
  { method: "GET" }
7290
7602
  );
7291
7603
  }
7604
+ /** Submit durable monitor work; transport retries use the same idempotency key. */
7605
+ async submitMonitorBatch(options) {
7606
+ const idempotencyKey = options.idempotencyKey.trim();
7607
+ if (!idempotencyKey) {
7608
+ throw new DeeplineError(
7609
+ "Monitor batch idempotencyKey is required.",
7610
+ void 0,
7611
+ "INVALID_MONITOR_BATCH_IDEMPOTENCY_KEY"
7612
+ );
7613
+ }
7614
+ if (options.kind === "deploy") {
7615
+ if (!options.manifestNdjson.trim()) {
7616
+ throw new DeeplineError(
7617
+ "Monitor batch deploy requires a non-empty NDJSON manifest.",
7618
+ void 0,
7619
+ "INVALID_MONITOR_BATCH_MANIFEST"
7620
+ );
7621
+ }
7622
+ return this.http.request(
7623
+ "/api/v2/monitors/batch",
7624
+ {
7625
+ method: "POST",
7626
+ body: {
7627
+ kind: "deploy",
7628
+ manifest_ndjson: options.manifestNdjson,
7629
+ dry_run: options.dryRun === true,
7630
+ idempotency_key: idempotencyKey
7631
+ },
7632
+ headers: { "Idempotency-Key": idempotencyKey },
7633
+ maxRetries: 0,
7634
+ exactUrlOnly: true
7635
+ }
7636
+ );
7637
+ }
7638
+ const scope = options.scope.kind === "keys" ? { kind: "keys", monitor_keys: options.scope.monitorKeys } : options.scope.kind === "fleet" ? { kind: "fleet", fleet_id: options.scope.fleetId } : { kind: "target", target: options.scope.target };
7639
+ return this.http.request(
7640
+ "/api/v2/monitors/batch",
7641
+ {
7642
+ method: "POST",
7643
+ body: {
7644
+ kind: options.kind,
7645
+ scope,
7646
+ dry_run: options.dryRun === true,
7647
+ idempotency_key: idempotencyKey,
7648
+ ...options.deactivationReason === void 0 ? {} : { deactivation_reason: options.deactivationReason }
7649
+ },
7650
+ headers: { "Idempotency-Key": idempotencyKey },
7651
+ // Durable submission is itself idempotent, but an ambiguous local retry
7652
+ // must retain the exact key and never turn a CLI attempt into two runs.
7653
+ maxRetries: 0,
7654
+ exactUrlOnly: true
7655
+ }
7656
+ );
7657
+ }
7658
+ /** Get durable batch progress and a bounded retained evidence page. */
7659
+ async getMonitorBatch(operationId, options) {
7660
+ const runId = operationId.trim();
7661
+ if (!runId) {
7662
+ throw new DeeplineError(
7663
+ "Monitor batch operation id is required.",
7664
+ void 0,
7665
+ "INVALID_MONITOR_BATCH_OPERATION_ID"
7666
+ );
7667
+ }
7668
+ const params = new URLSearchParams();
7669
+ if (options?.cursor) params.set("cursor", options.cursor);
7670
+ const suffix = params.size > 0 ? `?${params}` : "";
7671
+ return this.http.request(
7672
+ `/api/v2/monitors/batch/${encodeURIComponent(runId)}${suffix}`,
7673
+ { method: "GET" }
7674
+ );
7675
+ }
7292
7676
  /**
7293
7677
  * The deployable monitor tools catalog. Pass a tool id (positional or
7294
7678
  * `{ tool }`) to describe one tool's full payload/stream contract, or no tool
@@ -8594,7 +8978,9 @@ function saveEnvValues(values, baseUrl, scope) {
8594
8978
  ...values[HOST_URL_ENV] ? { [HOST_URL_ENV]: values[HOST_URL_ENV] } : {},
8595
8979
  ...values[API_KEY_ENV] ? { [API_KEY_ENV]: values[API_KEY_ENV] } : {},
8596
8980
  ...values[ACTIVE_ORG_ID_ENV] ? { [ACTIVE_ORG_ID_ENV]: values[ACTIVE_ORG_ID_ENV] } : {},
8597
- ...Object.prototype.hasOwnProperty.call(values, ACTIVE_ORG_NAME_ENV) ? { [ACTIVE_ORG_NAME_ENV]: values[ACTIVE_ORG_NAME_ENV] } : {}
8981
+ ...Object.prototype.hasOwnProperty.call(values, ACTIVE_ORG_NAME_ENV) ? { [ACTIVE_ORG_NAME_ENV]: values[ACTIVE_ORG_NAME_ENV] } : {},
8982
+ // An explicit empty list must reach the store: it clears a stale line.
8983
+ ...Object.prototype.hasOwnProperty.call(values, FEATURES_ENV) ? { [FEATURES_ENV]: values[FEATURES_ENV] } : {}
8598
8984
  };
8599
8985
  if (scope === "folder") {
8600
8986
  saveProjectDeeplineEnvValues(filtered);
@@ -8608,7 +8994,8 @@ function activeOrgEnvValues(data) {
8608
8994
  const orgName = typeof data.org_name === "string" ? data.org_name.trim() : "";
8609
8995
  return {
8610
8996
  [ACTIVE_ORG_ID_ENV]: orgId,
8611
- [ACTIVE_ORG_NAME_ENV]: orgName
8997
+ [ACTIVE_ORG_NAME_ENV]: orgName,
8998
+ ...featureEnvValues(data)
8612
8999
  };
8613
9000
  }
8614
9001
  async function httpJson(method, url, apiKey, body, timeoutMs = AUTH_HTTP_TIMEOUT_MS) {
@@ -9133,6 +9520,8 @@ async function handleStatus(args) {
9133
9520
  savedApiKeyPath = envFilePath(baseUrl);
9134
9521
  }
9135
9522
  }
9523
+ } else if (resolvedAuthScope === "folder" || storedHostApiKey(baseUrl) === apiKey) {
9524
+ saveEnvValues(featureEnvValues(data), baseUrl, resolvedAuthScope);
9136
9525
  }
9137
9526
  printCommandEnvelope(
9138
9527
  {
@@ -10337,12 +10726,12 @@ async function handleBuyCredits(creditsRaw, options) {
10337
10726
  );
10338
10727
  }
10339
10728
  async function handleTargetPlan(planSku, options) {
10340
- if (planSku !== "payg-v1" && planSku !== "builder-v1" && planSku !== "team-v1") {
10729
+ if (planSku !== "payg-v1" && planSku !== "builder-v1" && planSku !== "growth-v1" && planSku !== "growth-canary-v1" && planSku !== "team-v1") {
10341
10730
  reportBillingFailure(
10342
10731
  {
10343
10732
  exitCode: 2,
10344
10733
  code: "INVALID_PLAN",
10345
- message: "Plan must be payg-v1, builder-v1, or team-v1."
10734
+ message: "Plan must be payg-v1, builder-v1, growth-v1, growth-canary-v1, or team-v1."
10346
10735
  },
10347
10736
  options
10348
10737
  );
@@ -10603,7 +10992,7 @@ Examples:
10603
10992
  "Disable automatic recharge without clearing saved amounts."
10604
10993
  ).option("--idempotency-key <key>", "Stable retry key").option("--dry-run", "Print the planned update without applying it").option("--json", "Emit JSON output").action(handleAutoRechargeOff)
10605
10994
  );
10606
- billing.command("change-plan").description("Start or change the target billing plan.").argument("<plan_sku>", "payg-v1, builder-v1, or team-v1").option("--idempotency-key <key>", "Stable retry key").option("--json", "Emit JSON output").action(handleTargetPlan);
10995
+ billing.command("change-plan").description("Start or change the target billing plan.").argument("<plan_sku>", "payg-v1, builder-v1, growth-v1, growth-canary-v1, or team-v1").option("--idempotency-key <key>", "Stable retry key").option("--json", "Emit JSON output").action(handleTargetPlan);
10607
10996
  billing.command("cancel-plan").description("Cancel a target subscription at period end, or undo it.").option("--undo", "Undo a pending period-end cancellation").option("--idempotency-key <key>", "Stable retry key").option("--json", "Emit JSON output").action(handleTargetPlanCancellation);
10608
10997
  billing.command("portal").description("Open the Stripe-hosted billing recovery portal.").option("--no-open", "Print the URL without opening a browser").option("--json", "Emit JSON output").action(handleTargetPortal);
10609
10998
  billing.command("plans").description("Show published plans and the plan you are on.").addHelpText(
@@ -10889,6 +11278,12 @@ function mergeCanonicalRowsInfo(left, right) {
10889
11278
  const exportUnavailableReason = left.exportUnavailableReason === "shared_table_namespace" || right.exportUnavailableReason === "shared_table_namespace" ? "shared_table_namespace" : left.exportUnavailableReason ?? right.exportUnavailableReason;
10890
11279
  const unavailableSource = left.exportUnavailableReason === exportUnavailableReason ? left : right.exportUnavailableReason === exportUnavailableReason ? right : null;
10891
11280
  const columns = preferred.columns.length > 0 ? preferred.columns : fallback.columns;
11281
+ const recoveredReturnPaths2 = [
11282
+ .../* @__PURE__ */ new Set([
11283
+ ...left.recoveredReturnPaths ?? [],
11284
+ ...right.recoveredReturnPaths ?? []
11285
+ ])
11286
+ ];
10892
11287
  return {
10893
11288
  ...fallback,
10894
11289
  ...preferred,
@@ -10901,6 +11296,7 @@ function mergeCanonicalRowsInfo(left, right) {
10901
11296
  datasetId: preferred.datasetId ?? fallback.datasetId,
10902
11297
  tableNamespace: preferred.tableNamespace ?? fallback.tableNamespace,
10903
11298
  ...left.recovered || right.recovered ? { recovered: true } : {},
11299
+ ...recoveredReturnPaths2.length ? { recoveredReturnPaths: recoveredReturnPaths2 } : {},
10904
11300
  ...exportUnavailableReason ? {
10905
11301
  exportUnavailableReason,
10906
11302
  ...unavailableSource?.exportUnavailableMessage ? {
@@ -10961,6 +11357,16 @@ function isSerializedDataset(value) {
10961
11357
  function isPackagedDatasetOutput(value) {
10962
11358
  return isRecord8(value) && value.kind === "dataset" && isRecord8(value.preview) && Array.isArray(value.preview.rows);
10963
11359
  }
11360
+ function recoveredReturnPaths(value) {
11361
+ if (!Array.isArray(value)) return [];
11362
+ return [
11363
+ ...new Set(
11364
+ value.filter(
11365
+ (path) => typeof path === "string" && /^result\.[A-Za-z_$][A-Za-z0-9_$]*$/.test(path)
11366
+ ).slice(0, 200)
11367
+ )
11368
+ ];
11369
+ }
10964
11370
  function pathParts(path) {
10965
11371
  return path.split(".").map((part) => part.trim()).filter(Boolean);
10966
11372
  }
@@ -11057,6 +11463,11 @@ function canonicalRowsInfoFromCandidate(input2) {
11057
11463
  datasetId: typeof candidate.value.datasetId === "string" ? candidate.value.datasetId : null,
11058
11464
  tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null,
11059
11465
  ...candidate.value.recovered === true ? { recovered: true } : {},
11466
+ ...recoveredReturnPaths(candidate.value.recoveredReturnPaths).length ? {
11467
+ recoveredReturnPaths: recoveredReturnPaths(
11468
+ candidate.value.recoveredReturnPaths
11469
+ )
11470
+ } : {},
11060
11471
  ...isRecord8(candidate.value.exportUnavailable) && (candidate.value.exportUnavailable.reason === "empty_dataset" || candidate.value.exportUnavailable.reason === "shared_table_namespace") ? {
11061
11472
  exportUnavailableReason: candidate.value.exportUnavailable.reason,
11062
11473
  ...typeof candidate.value.exportUnavailable.message === "string" ? {
@@ -15162,6 +15573,82 @@ var PLAY_AUTHORING_FIELD_REGISTRY = {
15162
15573
  description: "Unsupported legacy child-workflow timeout.",
15163
15574
  errorMessage: "ctx.runPlay timeoutMs is unsupported because child Plays execute inline."
15164
15575
  },
15576
+ "ctx.runPlayAsync.key": {
15577
+ schema: import_typebox.Type.String({ minLength: 1 }),
15578
+ fixtures: {
15579
+ valid: "request-approval",
15580
+ invalid: "",
15581
+ absent: void 0,
15582
+ unresolved: { expression: "callKey" },
15583
+ edition1: "request-approval"
15584
+ },
15585
+ referenceType: "string",
15586
+ required: true,
15587
+ resolution: "runtime-dynamic",
15588
+ issueCode: "play_authoring_run_play_option_invalid",
15589
+ description: "Stable identity for one independent child Play Run.",
15590
+ errorMessage: "ctx.runPlayAsync key must be a non-empty string."
15591
+ },
15592
+ "ctx.runPlayAsync.playRef": {
15593
+ schema: import_typebox.Type.Union([
15594
+ import_typebox.Type.String({ minLength: 1 }),
15595
+ import_typebox.Type.Union([
15596
+ import_typebox.Type.Object(
15597
+ { playName: import_typebox.Type.String({ minLength: 1 }) },
15598
+ { additionalProperties: true }
15599
+ ),
15600
+ import_typebox.Type.Object(
15601
+ { name: import_typebox.Type.String({ minLength: 1 }) },
15602
+ { additionalProperties: true }
15603
+ )
15604
+ ])
15605
+ ]),
15606
+ fixtures: {
15607
+ valid: "request-approval",
15608
+ invalid: {},
15609
+ absent: void 0,
15610
+ unresolved: { expression: "playRef" },
15611
+ edition1: "request-approval"
15612
+ },
15613
+ referenceType: "string | PlayReferenceLike",
15614
+ required: true,
15615
+ resolution: "runtime-dynamic",
15616
+ issueCode: "play_authoring_run_play_option_invalid",
15617
+ description: "Independently published scalar child Play name or strongly typed definition handle.",
15618
+ errorMessage: "ctx.runPlayAsync playRef must be a non-empty Play name or definition handle."
15619
+ },
15620
+ "ctx.runPlayAsync.input": {
15621
+ schema: import_typebox.Type.Record(import_typebox.Type.String(), import_typebox.Type.Unknown()),
15622
+ fixtures: {
15623
+ valid: { draftId: "draft-1" },
15624
+ invalid: "draft-1",
15625
+ absent: void 0,
15626
+ unresolved: "childInput",
15627
+ edition1: {}
15628
+ },
15629
+ referenceType: "Record<string, unknown>",
15630
+ required: true,
15631
+ resolution: "runtime-dynamic",
15632
+ issueCode: "play_authoring_run_play_option_invalid",
15633
+ description: "Scalar input object submitted to the independent child Run.",
15634
+ errorMessage: "ctx.runPlayAsync input must be an object."
15635
+ },
15636
+ "ctx.runPlayAsync.options.description": {
15637
+ schema: import_typebox.Type.String({ minLength: 1 }),
15638
+ fixtures: {
15639
+ valid: "Request approval.",
15640
+ invalid: "",
15641
+ absent: void 0,
15642
+ unresolved: { expression: "description" },
15643
+ edition1: "Request approval."
15644
+ },
15645
+ referenceType: "string",
15646
+ required: true,
15647
+ resolution: "runtime-dynamic",
15648
+ issueCode: "play_authoring_run_play_option_invalid",
15649
+ description: "Non-empty purpose for one independent child Play Run.",
15650
+ errorMessage: "ctx.runPlayAsync options.description must be non-empty."
15651
+ },
15165
15652
  "runtime.timeout": {
15166
15653
  schema: import_typebox.Type.String({ pattern: "^\\d+\\s*[mh]$" }),
15167
15654
  fixtures: {
@@ -15449,6 +15936,10 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
15449
15936
  `export type CsvOptions = { description?: ${cloudReferenceType("ctx.csv.options.description")}; columns?: ${cloudReferenceType("ctx.csv.options.columns")}; rename?: ${cloudReferenceType("ctx.csv.options.rename")}; required?: ${cloudReferenceType("ctx.csv.options.required")} };`,
15450
15937
  "export type PlayCallExecution = 'inline';",
15451
15938
  `export type PlayCallOptions = { description: ${cloudReferenceType("ctx.runPlay.options.description")}; execution?: ${cloudReferenceType("ctx.runPlay.options.execution")}; timeoutMs?: ${cloudReferenceType("ctx.runPlay.options.timeoutMs")} };`,
15939
+ `export type PlayAsyncCallOptions = { description: ${cloudReferenceType("ctx.runPlayAsync.options.description")} };`,
15940
+ "declare const PLAY_RUN_ID_BRAND: unique symbol;",
15941
+ "export type PlayRunId = string & { readonly [PLAY_RUN_ID_BRAND]: 'PlayRunId' };",
15942
+ "export type AsyncPlayRunRef = Readonly<{ runId: PlayRunId }>;",
15452
15943
  `export type RuntimeStepOptions = { semanticKey?: ${cloudReferenceType("ctx.step.semanticKey")}; staleAfterSeconds?: ${cloudReferenceType("ctx.step.staleAfterSeconds")} };`,
15453
15944
  `export type FetchOptions = { staleAfterSeconds?: ${cloudReferenceType("ctx.fetch.staleAfterSeconds")}; transient?: boolean };`,
15454
15945
  "export type PlayFetchResponse = { ok: boolean; status: number; statusText: string; url: string; headers: Record<string, string>; bodyText: string; json: unknown | null };",
@@ -15474,11 +15965,11 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
15474
15965
  " withColumn<Name extends string, Value>(name: Name, definition: DatasetColumnDefinition<OutputRow, Value> & { readonly runIf: (row: OutputRow, index: number) => boolean | Promise<boolean> }): DatasetBuilder<InputRow, OutputRow & Record<Name, Value | null>>;",
15475
15966
  " withColumn<Name extends string, Value>(name: Name, definition: DatasetColumnDefinition<OutputRow, Value>): DatasetBuilder<InputRow, OutputRow & Record<Name, Value>>;",
15476
15967
  " withColumn<Name extends string, Value>(name: Name, resolver: StepResolver<OutputRow, Value> | RunnableColumnStepProgram<Value>, options: StepOptions<OutputRow, Value>): DatasetBuilder<InputRow, OutputRow & Record<Name, Value | null>>;",
15477
- " withColumns<Program extends StepProgram<OutputRow, object, unknown>>(program: Program): DatasetBuilder<InputRow, StepProgramOutput<Program>>;",
15968
+ " withColumns<ProgramOutput extends OutputRow, Return>(program: StepProgram<OutputRow, ProgramOutput, Return>): DatasetBuilder<InputRow, ProgramOutput>;",
15478
15969
  " step<Name extends string, Value>(name: Name, resolver: ColumnResolver<OutputRow, Value>): never;",
15479
15970
  " run(options?: DatasetRunOptions<InputRow>): Promise<PlayDataset<OutputRow>>;",
15480
15971
  "};",
15481
- "export type PlayReferenceLike = { readonly playName: string; readonly name?: string } | { readonly name: string; readonly playName?: string };",
15972
+ "export type PlayReferenceLike<TInput extends Record<string, unknown> = Record<string, unknown>> = ({ readonly playName: string; readonly name?: string } | { readonly name: string; readonly playName?: string }) & { readonly __inputType?: TInput };",
15482
15973
  "export interface DeeplinePlayRuntimeContext {",
15483
15974
  " csv<T = Record<string, unknown>>(path: string | CsvInput<T & object>, options?: CsvOptions): Promise<PlayDataset<T>>;",
15484
15975
  " dataset<TSource extends PlayDatasetInput<object>>(key: string, items: TSource): DatasetBuilder<PlayDatasetRow<TSource> & object, PlayDatasetRow<TSource> & object>;",
@@ -15492,6 +15983,8 @@ var PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
15492
15983
  " fetch(key: string, url: string | URL, init?: RequestInit & { headers?: HeadersInit & { 'Idempotency-Key'?: string; 'X-Idempotency-Key'?: string }; auth?: SecretAuthInput }, options?: FetchOptions): Promise<PlayFetchResponse>;",
15493
15984
  " secrets: { get(name: string): SecretPromise; bearer(secret: string | SecretPromise | SecretHandle): SecretAuth; header(header: string, secret: string | SecretPromise | SecretHandle): SecretAuth };",
15494
15985
  ` runPlay<TOutput = unknown>(key: string, playRef: ${cloudReferenceType("ctx.runPlay.playRef")}, input: ${cloudReferenceType("ctx.runPlay.input")}, options: PlayCallOptions): Promise<TOutput>;`,
15986
+ " runPlayAsync<TInput extends Record<string, unknown>>(key: string, playRef: PlayReferenceLike<TInput>, input: NoInfer<TInput>, options: PlayAsyncCallOptions): Promise<AsyncPlayRunRef>;",
15987
+ " runPlayAsync(key: string, playRef: string, input: Record<string, unknown>, options: PlayAsyncCallOptions): Promise<AsyncPlayRunRef>;",
15495
15988
  " log(message: string, options?: { level?: 'debug' | 'info' | 'warn' | 'error' }): void;",
15496
15989
  ` sleep(ms: ${cloudReferenceType("ctx.sleep.ms")}): Promise<void>;`,
15497
15990
  "}",
@@ -15766,7 +16259,7 @@ function collectInlineSecretFindings(sourceCode) {
15766
16259
  var MAX_PLAY_BUNDLE_BYTES = 30 * 1024 * 1024;
15767
16260
 
15768
16261
  // ../plays/bundling/index.ts
15769
- var PLAY_BUNDLE_CACHE_VERSION = 36;
16262
+ var PLAY_BUNDLE_CACHE_VERSION = 37;
15770
16263
  var PLAY_ARTIFACT_CACHE_DIR = (0, import_node_path11.join)(
15771
16264
  (0, import_node_os7.tmpdir)(),
15772
16265
  `deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION}`
@@ -16498,7 +16991,7 @@ var RunDataset = class _RunDataset {
16498
16991
  return this.#info.exportUnavailableMessage;
16499
16992
  }
16500
16993
  matches(path) {
16501
- return this.source === path || this.datasetId === path || this.tableNamespace === path;
16994
+ return this.source === path || this.datasetId === path || this.tableNamespace === path || this.#info.recoveredReturnPaths?.includes(path) === true;
16502
16995
  }
16503
16996
  /** Return a new Handle when its preview has been replaced by fetched rows. */
16504
16997
  withRows(info) {
@@ -17343,6 +17836,39 @@ var PLAY_RUN_RESERVED_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
17343
17836
  "--open",
17344
17837
  "--debug-map-latency"
17345
17838
  ]);
17839
+ var PLAY_RUN_HEADER_FLAG_USAGE = `--header requires "<name>: <value>", for example: --header 'x-deepline-absurd-release: 2026-09-07.1'`;
17840
+ var HTTP_HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9a-z]+$/;
17841
+ function parsePlayRunHeaderFlag(raw) {
17842
+ const separator = raw.indexOf(":");
17843
+ if (separator < 0) {
17844
+ throw new Error(
17845
+ `${PLAY_RUN_HEADER_FLAG_USAGE}. Got ${JSON.stringify(raw)} with no ":" separator.`
17846
+ );
17847
+ }
17848
+ const name = raw.slice(0, separator).trim().toLowerCase();
17849
+ const value = raw.slice(separator + 1).trim();
17850
+ if (!name) {
17851
+ throw new Error(
17852
+ `${PLAY_RUN_HEADER_FLAG_USAGE}. Got ${JSON.stringify(raw)} with an empty header name.`
17853
+ );
17854
+ }
17855
+ if (!HTTP_HEADER_NAME_PATTERN.test(name)) {
17856
+ throw new Error(
17857
+ `${PLAY_RUN_HEADER_FLAG_USAGE}. ${JSON.stringify(name)} is not a valid HTTP header name.`
17858
+ );
17859
+ }
17860
+ if (!value) {
17861
+ throw new Error(
17862
+ `${PLAY_RUN_HEADER_FLAG_USAGE}. Header ${JSON.stringify(name)} was given an empty value.`
17863
+ );
17864
+ }
17865
+ if (/[\u0000-\u001f\u007f]/.test(value)) {
17866
+ throw new Error(
17867
+ `${PLAY_RUN_HEADER_FLAG_USAGE}. Header ${JSON.stringify(name)} contains a control character.`
17868
+ );
17869
+ }
17870
+ return { name, value };
17871
+ }
17346
17872
  function isBarePlayRunDebugFlag(args, index) {
17347
17873
  return args[index] === "--debug" && (args[index + 1] === void 0 || args[index + 1].startsWith("--"));
17348
17874
  }
@@ -18956,6 +19482,9 @@ function assertPlayWaitNotTimedOut(input2) {
18956
19482
  }
18957
19483
  }
18958
19484
  var PLAY_DURABLE_STATUS_PROBE_INTERVAL_MS = 15e3;
19485
+ function isAdmittedRunVisibilityDelay(error) {
19486
+ return error instanceof DeeplineError && error.statusCode === 404 && error.message.trim() === "Workflow run not found.";
19487
+ }
18959
19488
  async function waitForPlayCompletionByStream(input2) {
18960
19489
  let lastPhase = null;
18961
19490
  let reconnectAttempt = 0;
@@ -18969,7 +19498,7 @@ async function waitForPlayCompletionByStream(input2) {
18969
19498
  billing: false
18970
19499
  });
18971
19500
  } catch (error) {
18972
- if (isTransientPlayStreamError(error)) {
19501
+ if (isTransientPlayStreamError(error) || isAdmittedRunVisibilityDelay(error)) {
18973
19502
  return null;
18974
19503
  }
18975
19504
  throw error;
@@ -19080,7 +19609,7 @@ async function waitForPlayCompletionByStream(input2) {
19080
19609
  if (timedOut) {
19081
19610
  return { kind: "timed_out", sawEvent };
19082
19611
  }
19083
- if (!isTransientPlayStreamError(error)) {
19612
+ if (!isTransientPlayStreamError(error) && !isAdmittedRunVisibilityDelay(error)) {
19084
19613
  throw error;
19085
19614
  }
19086
19615
  return {
@@ -21316,7 +21845,7 @@ function writeStartedPlayRun(input2) {
21316
21845
  );
21317
21846
  }
21318
21847
  function parsePlayRunOptions(args) {
21319
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--json] [--full] [--<input> value]\n Runs use the Absurd CJS runtime.\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotent call caching and ctx.dataset guidance.";
21848
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--run-id-file <path>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--open] [--json] [--full] [--<input> value]\n Runs use the Absurd CJS runtime.\n --header '<name>: <value>' may repeat; each is sent on every request for this run.\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotent call caching and ctx.dataset guidance.";
21320
21849
  let filePath = null;
21321
21850
  let playName = null;
21322
21851
  let input2 = null;
@@ -21337,8 +21866,20 @@ function parsePlayRunOptions(args) {
21337
21866
  let profile = null;
21338
21867
  let fixtureBehavior = null;
21339
21868
  let runIdFile = null;
21869
+ let headers = null;
21340
21870
  for (let index = 0; index < args.length; index += 1) {
21341
21871
  const arg = args[index];
21872
+ if (arg === "--header" || arg.startsWith("--header=")) {
21873
+ const raw = arg === "--header" ? args[index + 1] : arg.slice("--header=".length);
21874
+ if (raw === void 0 || arg === "--header" && raw.startsWith("--")) {
21875
+ throw new Error(`${PLAY_RUN_HEADER_FLAG_USAGE}. No value was given.`);
21876
+ }
21877
+ const header = parsePlayRunHeaderFlag(raw);
21878
+ headers ??= {};
21879
+ headers[header.name] = header.value;
21880
+ if (arg === "--header") index += 1;
21881
+ continue;
21882
+ }
21342
21883
  if (arg === "--file" && args[index + 1]) {
21343
21884
  filePath = args[++index];
21344
21885
  continue;
@@ -21577,7 +22118,8 @@ function parsePlayRunOptions(args) {
21577
22118
  profile,
21578
22119
  debugMapLatency,
21579
22120
  fixtureBehavior,
21580
- runIdFile
22121
+ runIdFile,
22122
+ headers
21581
22123
  };
21582
22124
  }
21583
22125
  function parsePlayCheckOptions(args) {
@@ -22333,11 +22875,14 @@ function printPlayCheckOutcomes(outcomes, target) {
22333
22875
  printPlayCheckOutcome(outcome, target, `[${outcome.exportName}] `);
22334
22876
  }
22335
22877
  }
22878
+ function playRunClientOptions(options) {
22879
+ return options.headers ? { headers: options.headers } : void 0;
22880
+ }
22336
22881
  async function handleFileBackedRun(options, hooks) {
22337
22882
  if (options.target.kind !== "file") {
22338
22883
  throw new Error("Expected a file-backed play run target.");
22339
22884
  }
22340
- const client2 = new DeeplineClient();
22885
+ const client2 = new DeeplineClient(playRunClientOptions(options));
22341
22886
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
22342
22887
  const absolutePlayPath = (0, import_node_path14.resolve)(options.target.path);
22343
22888
  progress.phase("compiling play");
@@ -22545,7 +23090,7 @@ async function handleNamedRun(options, hooks) {
22545
23090
  if (options.target.kind !== "name") {
22546
23091
  throw new Error("Expected a named play run target.");
22547
23092
  }
22548
- const client2 = new DeeplineClient();
23093
+ const client2 = new DeeplineClient(playRunClientOptions(options));
22549
23094
  const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
22550
23095
  const playName = options.target.name;
22551
23096
  const runtimeInput = options.input ? { ...options.input } : {};
@@ -32633,7 +33178,7 @@ function renderDeployedListText(payload, requestedStatus) {
32633
33178
  }
32634
33179
  const applied = asString(payload.status_filter_applied) ?? requestedStatus ?? "active";
32635
33180
  lines.push(
32636
- `Status filter: ${applied}${requestedStatus === void 0 && applied === "active" ? " (default)" : ""} \u2014 use --status all to include disabled monitors.`
33181
+ `Status filter: ${applied}${requestedStatus === void 0 && applied === "active" ? " (default)" : ""} \u2014 use --status all to include inactive monitors.`
32637
33182
  );
32638
33183
  const summary = Array.isArray(payload.status_summary) ? payload.status_summary.flatMap((raw) => {
32639
33184
  const item = asRecord2(raw);
@@ -32641,7 +33186,7 @@ function renderDeployedListText(payload, requestedStatus) {
32641
33186
  const count = item ? asFiniteNumber(item.count) : void 0;
32642
33187
  return status && count !== void 0 ? [`${status}: ${count}`] : [];
32643
33188
  }).join(" ") : "";
32644
- if (summary) lines.push(`All lifecycle states: ${summary}`);
33189
+ if (summary) lines.push(`All monitor states: ${summary}`);
32645
33190
  if (payload.has_more === true || payload.is_truncated === true) {
32646
33191
  const nextCursor = asString(payload.next_cursor);
32647
33192
  lines.push(
@@ -33730,6 +34275,9 @@ async function handleMonitorsReactivate(key, options) {
33730
34275
  }
33731
34276
  var MONITOR_VERB_POLL_INTERVAL_MS = 2e3;
33732
34277
  function monitorHealthLine(health) {
34278
+ if (health.refresh?.state === "running") {
34279
+ return `refreshing whole scope \xB7 scanned ${health.scanned} so far`;
34280
+ }
33733
34281
  return [
33734
34282
  `converged ${health.converged}/${health.scanned}`,
33735
34283
  `needs_repair ${health.needs_repair}`,
@@ -33768,7 +34316,9 @@ async function watchMonitorHealth(input2) {
33768
34316
  let last = await input2.client.monitors.health(scope);
33769
34317
  input2.onProgress(last);
33770
34318
  while (Date.now() < deadlineAt) {
33771
- if (last.needs_repair === 0 && last.observed.unknown === 0) return last;
34319
+ if (last.refresh?.state !== "running" && last.needs_repair === 0 && last.observed.unknown === 0) {
34320
+ return last;
34321
+ }
33772
34322
  await new Promise(
33773
34323
  (resolve21) => setTimeout(resolve21, MONITOR_VERB_POLL_INTERVAL_MS)
33774
34324
  );
@@ -33778,11 +34328,21 @@ async function watchMonitorHealth(input2) {
33778
34328
  return last;
33779
34329
  }
33780
34330
  function monitorHealthText(health) {
33781
- const partial = health.truncated ? " (partial \u2014 scope larger than one read)" : "";
34331
+ const partial = health.refresh?.state === "running" ? " (refreshing \u2014 counts below are not final)" : health.truncated ? " (partial \u2014 scope larger than one read)" : "";
33782
34332
  const lines = [
33783
34333
  `scope: ${health.scope.fleet_id ?? "workspace"}${partial}`,
33784
34334
  monitorHealthLine(health)
33785
34335
  ];
34336
+ if ((health.batch_operations?.length ?? 0) > 0) {
34337
+ lines.push("");
34338
+ lines.push("batch work in progress:");
34339
+ for (const operation of health.batch_operations ?? []) {
34340
+ const retry = operation.next_attempt_at === void 0 ? "" : ` \xB7 next ${new Date(operation.next_attempt_at).toISOString()}`;
34341
+ lines.push(
34342
+ ` ${operation.kind} ${operation.run_id} \xB7 ${operation.state}${retry}`
34343
+ );
34344
+ }
34345
+ }
33786
34346
  if (health.deactivation_reasons.length > 0) {
33787
34347
  lines.push("");
33788
34348
  lines.push("deactivated by Deepline:");
@@ -33855,78 +34415,143 @@ async function handleMonitorsAudit(options) {
33855
34415
  );
33856
34416
  }
33857
34417
  async function handleMonitorsRepair(options) {
33858
- const client2 = new DeeplineClient();
34418
+ return await handleMonitorBatch("repair", {
34419
+ ...options,
34420
+ ...options.fleet?.trim() ? {} : { target: "needs-repair" }
34421
+ });
34422
+ }
34423
+ async function handleMonitorsHealth(options) {
33859
34424
  const fleetId = options.fleet?.trim() || void 0;
33860
- const scope = fleetId ? { fleetId } : {};
33861
- const result = await client2.monitors.repair({
33862
- ...scope,
33863
- ...options.dryRun ? { dryRun: true } : {}
34425
+ const health = await new DeeplineClient().monitors.health(
34426
+ fleetId ? { fleetId } : {}
34427
+ );
34428
+ printCommandEnvelope(health, {
34429
+ json: options.json,
34430
+ text: monitorHealthText(health)
33864
34431
  });
33865
- if (options.dryRun) {
33866
- assertMonitorDryRunAcknowledged(
33867
- result,
33868
- { command: "monitors repair", mutation: "repair" }
34432
+ }
34433
+ function monitorBatchScope(options, kind) {
34434
+ const selectors = [
34435
+ options.target ? "target" : null,
34436
+ options.fleet?.trim() ? "fleet" : null,
34437
+ options.keys?.trim() ? "keys" : null
34438
+ ].filter((value) => value !== null);
34439
+ if (selectors.length === 0 && kind === "audit") {
34440
+ return { kind: "target", target: "all" };
34441
+ }
34442
+ if (selectors.length !== 1) {
34443
+ throw new MonitorsUsageError(
34444
+ `monitors batch ${kind} needs exactly one selector: --target, --fleet, or --keys.`
33869
34445
  );
33870
34446
  }
33871
- let health = result.health;
33872
- if (options.watch && !options.dryRun) {
33873
- const progress = monitorVerbProgressReporter();
33874
- try {
33875
- health = await watchMonitorHealth({
33876
- client: client2,
33877
- ...scope,
33878
- timeoutMs: fleetTimeoutMs(options.timeout),
33879
- onProgress: progress.report
33880
- });
33881
- } finally {
33882
- progress.finish();
33883
- }
34447
+ if (options.target) return { kind: "target", target: options.target };
34448
+ if (options.fleet?.trim())
34449
+ return { kind: "fleet", fleetId: options.fleet.trim() };
34450
+ const monitorKeys = (options.keys ?? "").split(",").map((key) => key.trim()).filter(Boolean);
34451
+ if (monitorKeys.length === 0) {
34452
+ throw new MonitorsUsageError(
34453
+ "--keys needs one or more comma-separated monitor keys."
34454
+ );
33884
34455
  }
33885
- const verb = options.dryRun ? "would queue" : "queued";
33886
- const lines = [
33887
- `${verb} ${result.repair.queued} of ${result.repair.scanned} monitors`
33888
- ];
33889
- for (const item of result.repair.plan.slice(0, REPAIR_PLAN_PREVIEW)) {
33890
- const why = item.deactivation_reason ? ` \xB7 ${item.deactivation_reason}` : "";
33891
- lines.push(
33892
- ` ${item.action.padEnd(6)} ${item.monitor_key} desired ${item.desired} \xB7 observed ${item.observed}${why}`
34456
+ return { kind: "keys", monitorKeys };
34457
+ }
34458
+ function monitorBatchIdempotencyKey(kind, value) {
34459
+ return value?.trim() || `monitor-batch-${kind}-${(0, import_node_crypto8.randomUUID)()}`;
34460
+ }
34461
+ async function handleMonitorBatchDeploy(options) {
34462
+ if (!options.file?.trim()) {
34463
+ throw new MonitorsUsageError(
34464
+ "monitors batch deploy requires --file <monitors.ndjson>."
33893
34465
  );
33894
34466
  }
33895
- if (result.repair.plan.length > REPAIR_PLAN_PREVIEW) {
33896
- lines.push(` \u2026 ${result.repair.plan.length - REPAIR_PLAN_PREVIEW} more`);
34467
+ if (options.dryRun !== true && options.yes !== true) {
34468
+ throw new MonitorsUsageError(
34469
+ "monitors batch deploy changes provider monitors asynchronously. Preview with --dry-run, then confirm with --yes."
34470
+ );
33897
34471
  }
33898
- if (result.repair.unaudited.length > 0) {
33899
- lines.push(
33900
- `${result.repair.unaudited.length} monitors have never been observed \u2014 run 'deepline monitors audit' first.`
34472
+ let manifestNdjson;
34473
+ try {
34474
+ manifestNdjson = (0, import_node_fs15.readFileSync)((0, import_node_path17.resolve)(options.file), "utf8");
34475
+ } catch (error) {
34476
+ throw new MonitorsUsageError(
34477
+ `Could not read monitor batch manifest '${options.file}': ${error instanceof Error ? error.message : String(error)}`
33901
34478
  );
33902
34479
  }
33903
- if (result.repair.unowned.length > 0) {
33904
- lines.push(
33905
- `${result.repair.unowned.length} monitors disagree but no fleet owns them; repair those with 'deepline monitors deploy' or 'deepline monitors delete'.`
34480
+ if (!manifestNdjson.trim()) {
34481
+ throw new MonitorsUsageError("Monitor batch manifest is empty.");
34482
+ }
34483
+ const idempotencyKey = monitorBatchIdempotencyKey(
34484
+ "deploy",
34485
+ options.idempotencyKey
34486
+ );
34487
+ const result = await new DeeplineClient().monitors.batch.submit({
34488
+ kind: "deploy",
34489
+ manifestNdjson,
34490
+ dryRun: options.dryRun === true,
34491
+ idempotencyKey
34492
+ });
34493
+ const operation = result.operation;
34494
+ printCommandEnvelope(
34495
+ { ...result, idempotency_key: idempotencyKey },
34496
+ {
34497
+ json: options.json,
34498
+ text: [
34499
+ `${result.rejoined ? "rejoined" : "submitted"} deploy batch ${operation.runId}`,
34500
+ `state ${operation.state} \xB7 retry key ${idempotencyKey}`,
34501
+ `inspect: deepline monitors batch get ${operation.runId} --json`
34502
+ ].join("\n") + "\n"
34503
+ }
34504
+ );
34505
+ }
34506
+ async function handleMonitorBatch(kind, options) {
34507
+ if ((kind === "deactivate" || kind === "reactivate" || kind === "repair") && options.dryRun !== true && options.yes !== true) {
34508
+ throw new MonitorsUsageError(
34509
+ `monitors batch ${kind} changes provider monitors asynchronously. Preview with --dry-run, then confirm with --yes.`
33906
34510
  );
33907
34511
  }
33908
- if (result.repair.fenced.length > 0) {
33909
- lines.push(
33910
- `${result.repair.fenced.length} monitors have a provider operation in flight; Deepline is finishing those and repair left them alone.`
34512
+ if (kind === "deactivate" && !options.reason?.trim()) {
34513
+ throw new MonitorsUsageError(
34514
+ "monitors batch deactivate requires --reason (for example insufficient_balance or billing_failure)."
33911
34515
  );
33912
34516
  }
33913
- lines.push("");
33914
- lines.push(monitorHealthText(health));
34517
+ const idempotencyKey = monitorBatchIdempotencyKey(
34518
+ kind,
34519
+ options.idempotencyKey
34520
+ );
34521
+ const result = await new DeeplineClient().monitors.batch.submit({
34522
+ kind,
34523
+ scope: monitorBatchScope(options, kind),
34524
+ dryRun: options.dryRun === true,
34525
+ ...options.reason?.trim() ? { deactivationReason: options.reason.trim() } : {},
34526
+ idempotencyKey
34527
+ });
34528
+ const operation = result.operation;
34529
+ const text = [
34530
+ `${result.rejoined ? "rejoined" : "submitted"} ${operation.kind} batch ${operation.runId}`,
34531
+ `state ${operation.state} \xB7 retry key ${idempotencyKey}`,
34532
+ `inspect: deepline monitors batch get ${operation.runId} --json`
34533
+ ].join("\n");
33915
34534
  printCommandEnvelope(
33916
- { ...result, health },
33917
- { json: options.json, text: lines.join("\n") }
34535
+ { ...result, idempotency_key: idempotencyKey },
34536
+ { json: options.json, text: `${text}
34537
+ ` }
33918
34538
  );
33919
34539
  }
33920
- var REPAIR_PLAN_PREVIEW = 20;
33921
- async function handleMonitorsHealth(options) {
33922
- const fleetId = options.fleet?.trim() || void 0;
33923
- const health = await new DeeplineClient().monitors.health(
33924
- fleetId ? { fleetId } : {}
33925
- );
33926
- printCommandEnvelope(health, {
33927
- json: options.json,
33928
- text: monitorHealthText(health)
34540
+ async function handleMonitorBatchGet(operationId, options) {
34541
+ const result = await new DeeplineClient().monitors.batch.get(operationId, {
34542
+ ...options.cursor ? { cursor: options.cursor } : {}
33929
34543
  });
34544
+ const summary = result.operation.summary ?? {};
34545
+ const text = [
34546
+ `${result.operation.kind} batch ${result.operation.runId}`,
34547
+ `state ${result.operation.state}`,
34548
+ `progress ${JSON.stringify(summary)}`,
34549
+ ...result.results ? [
34550
+ `results ${result.results.page.length}${result.results.isDone ? "" : " (more; pass --cursor)"}`
34551
+ ] : []
34552
+ ].join("\n");
34553
+ printCommandEnvelope(result, { json: options.json, text: `${text}
34554
+ ` });
33930
34555
  }
33931
34556
  function registerMonitorsCommands(program) {
33932
34557
  const monitors = program.command("monitors").description("Discover, deploy, and manage Deepline monitors.").showSuggestionAfterError(true).addHelpText(
@@ -33991,25 +34616,29 @@ Examples:
33991
34616
  withJsonOption(
33992
34617
  monitors.command("repair").description(
33993
34618
  "Converge the monitors whose desired and observed states disagree."
33994
- ).option("--fleet <fleet-id>", "Repair one fleet instead of every monitor").option("--dry-run", "Show what would be queued without queueing it").option("--watch", "Keep polling health until the scope settles").option(
33995
- "--timeout <duration>",
33996
- "How long --watch waits, e.g. 90s, 5m, 1h (default 10m)"
34619
+ ).option("--fleet <fleet-id>", "Repair one fleet instead of every monitor").option(
34620
+ "--dry-run",
34621
+ "Record the bounded repair plan without provider changes"
34622
+ ).option("--yes", "Confirm the asynchronous provider repair").option(
34623
+ "--idempotency-key <key>",
34624
+ "Stable retry key (generated when omitted)"
33997
34625
  ).addHelpText(
33998
34626
  "after",
33999
34627
  `
34000
34628
  Notes:
34001
- Queues the monitors that disagree with the provider onto the same convergence
34002
- loop a fleet sync uses; the provider work happens on Deepline's own schedule.
34003
- Idempotent \u2014 the queue IS the disagreement, so a repeat run queues only what
34004
- is still wrong.
34629
+ This is the short spelling of 'monitors batch repair --target needs-repair'.
34630
+ It handles both direct monitors and Fleet members through one durable
34631
+ service-account worker. An active historical monitor without an upstream id
34632
+ is selected for repair; current Convex writes cannot create that state.
34005
34633
 
34006
- Monitors nobody has observed are never repaired: run 'deepline monitors audit'
34007
- first. Monitors no fleet owns are reported rather than queued; deploy or delete
34008
- those directly.
34634
+ It returns a run id immediately. Inspect retained results with
34635
+ 'deepline monitors batch get <run-id> --json'. Reuse --idempotency-key after
34636
+ a lost response to rejoin the exact run without another provider request.
34009
34637
 
34010
34638
  Examples:
34011
34639
  deepline monitors repair --dry-run
34012
- deepline monitors repair --fleet accounts-eu --watch
34640
+ deepline monitors repair --yes
34641
+ deepline monitors repair --fleet accounts-eu --yes --idempotency-key repair-eu-2026-09-09
34013
34642
  `
34014
34643
  )
34015
34644
  ).action(monitorsAction(handleMonitorsRepair));
@@ -34032,6 +34661,75 @@ Examples:
34032
34661
  `
34033
34662
  )
34034
34663
  ).action(monitorsAction(handleMonitorsHealth));
34664
+ const batch = monitors.command("batch").description("Submit or inspect durable bulk monitor operations.");
34665
+ const batchSelectorOptions = (command) => command.option(
34666
+ "--target <target>",
34667
+ "One of: all, all-active, billing-suspended, needs-repair"
34668
+ ).option("--fleet <fleet-id>", "Select one Fleet").option("--keys <keys>", "Up to 100 comma-separated monitor keys").option(
34669
+ "--dry-run",
34670
+ "Record the same bounded plan without provider changes"
34671
+ ).option(
34672
+ "--idempotency-key <key>",
34673
+ "Stable retry key (generated when omitted)"
34674
+ );
34675
+ withJsonOption(
34676
+ batch.command("deploy").description(
34677
+ "Durably create complete NDJSON monitor definitions through provider bulk create."
34678
+ ).requiredOption(
34679
+ "--file <path>",
34680
+ "NDJSON file of complete monitor definitions"
34681
+ ).option("--dry-run", "Validate and record the durable deploy plan").option("--yes", "Confirm asynchronous provider creation").option(
34682
+ "--idempotency-key <key>",
34683
+ "Stable retry key (generated when omitted)"
34684
+ )
34685
+ ).action(
34686
+ monitorsAction(
34687
+ (options) => handleMonitorBatchDeploy(options)
34688
+ )
34689
+ );
34690
+ withJsonOption(
34691
+ batchSelectorOptions(
34692
+ batch.command("audit").description("Durably audit a selection; defaults to --target all.")
34693
+ )
34694
+ ).action(
34695
+ monitorsAction(
34696
+ (options) => handleMonitorBatch("audit", options)
34697
+ )
34698
+ );
34699
+ withJsonOption(
34700
+ batchSelectorOptions(
34701
+ batch.command("repair").description(
34702
+ "Durably re-audit and converge a selection through the existing lifecycle fences."
34703
+ )
34704
+ ).option("--yes", "Confirm the asynchronous provider repair")
34705
+ ).action(
34706
+ monitorsAction(
34707
+ (options) => handleMonitorBatch("repair", options)
34708
+ )
34709
+ );
34710
+ withJsonOption(
34711
+ batchSelectorOptions(
34712
+ batch.command("deactivate").description(
34713
+ "Durably deactivate a selection through provider batch deletes."
34714
+ )
34715
+ ).requiredOption("--reason <reason>", "Declared deactivation reason").option("--yes", "Confirm the asynchronous provider deactivation")
34716
+ ).action(
34717
+ monitorsAction(
34718
+ (options) => handleMonitorBatch("deactivate", options)
34719
+ )
34720
+ );
34721
+ withJsonOption(
34722
+ batchSelectorOptions(
34723
+ batch.command("reactivate").description("Durably reactivate a selection after a suspension.")
34724
+ ).option("--yes", "Confirm provider reactivation and possible billing")
34725
+ ).action(
34726
+ monitorsAction(
34727
+ (options) => handleMonitorBatch("reactivate", options)
34728
+ )
34729
+ );
34730
+ withJsonOption(
34731
+ batch.command("get <operation-id>").description("Read batch progress and one bounded evidence page.").option("--cursor <cursor>", "Read the next retained evidence page")
34732
+ ).action(monitorsAction(handleMonitorBatchGet));
34035
34733
  withJsonOption(
34036
34734
  monitors.command("status").description("Check whether you have access to Deepline monitors.").addHelpText(
34037
34735
  "after",
@@ -34083,8 +34781,9 @@ Examples:
34083
34781
  "after",
34084
34782
  `
34085
34783
  Notes:
34086
- Read-only. --status filters by monitor status: active (default), disabled, or
34087
- all. The output echoes the status filter that was applied. The response reports
34784
+ Read-only. A monitor state is active or inactive; provider disagreement is
34785
+ reported separately by audit/health. The output echoes the state filter that
34786
+ was applied. The response reports
34088
34787
  total (the true registry count, not the page size), returned, has_more, and
34089
34788
  next_cursor. When has_more is true, page with --cursor <next_cursor> until it
34090
34789
  is false \u2014 a "no matching
@@ -34102,7 +34801,7 @@ Examples:
34102
34801
  `
34103
34802
  ).option(
34104
34803
  "--status <status>",
34105
- "Filter by monitor status: active (default), disabled, or all"
34804
+ "Filter by monitor state: active (default), inactive, or all"
34106
34805
  ).option("--limit <n>", "Limit the number of deployed monitors returned").option(
34107
34806
  "--cursor <cursor>",
34108
34807
  "Page past a truncated result using the next_cursor from a prior list response"
@@ -34495,7 +35194,7 @@ Example:
34495
35194
  const deployed = withJsonOption(
34496
35195
  monitors.command("deployed", { hidden: true }).description("Alias of `monitors list` (plus get/update/delete aliases).").option(
34497
35196
  "--status <status>",
34498
- "Filter by monitor status: active (default), disabled, or all"
35197
+ "Filter by monitor state: active (default), inactive, or all"
34499
35198
  ).option("--limit <n>", "Limit the number of deployed monitors returned").option(
34500
35199
  "--cursor <cursor>",
34501
35200
  "Page past a truncated result using the next_cursor from a prior list response"
@@ -34637,7 +35336,8 @@ function organizationAuthValues(input2) {
34637
35336
  [HOST_URL_ENV]: input2.baseUrl,
34638
35337
  [API_KEY_ENV]: input2.apiKey,
34639
35338
  [ACTIVE_ORG_ID_ENV]: input2.orgId,
34640
- [ACTIVE_ORG_NAME_ENV]: input2.orgName
35339
+ [ACTIVE_ORG_NAME_ENV]: input2.orgName,
35340
+ ...featureEnvValues(input2)
34641
35341
  };
34642
35342
  }
34643
35343
  function organizationCommandContext(input2) {
@@ -34853,6 +35553,7 @@ async function handleOrgSwitch(selection, options) {
34853
35553
  if (!selection && !options.orgId && authScope === "folder" && provenance.scope === "global" && provenance.project.state === "project") {
34854
35554
  const commandContext = resolveCliCommandContext(config);
34855
35555
  const activeOrganization = commandContext.active_organization;
35556
+ const pinnedFeatures = storedHostFeatures(config.baseUrl);
34856
35557
  const project_env_paths2 = saveProjectDeeplineEnvValues(
34857
35558
  {
34858
35559
  [HOST_URL_ENV]: config.baseUrl,
@@ -34860,7 +35561,8 @@ async function handleOrgSwitch(selection, options) {
34860
35561
  ...activeOrganization ? {
34861
35562
  [ACTIVE_ORG_ID_ENV]: activeOrganization.org_id,
34862
35563
  [ACTIVE_ORG_NAME_ENV]: activeOrganization.org_name ?? ""
34863
- } : {}
35564
+ } : {},
35565
+ ...pinnedFeatures !== void 0 ? { [FEATURES_ENV]: pinnedFeatures } : {}
34864
35566
  },
34865
35567
  provenance.project.dir
34866
35568
  );
@@ -34940,11 +35642,15 @@ async function handleOrgSwitch(selection, options) {
34940
35642
  const authTarget = resolveOrgSwitchAuthTarget(authScope, config);
34941
35643
  if (target.is_current) {
34942
35644
  let project_env_paths2 = [];
35645
+ const features = await http.post("/api/v2/auth/cli/status", {
35646
+ api_key: config.apiKey
35647
+ }).then((status) => status.features).catch(() => void 0);
34943
35648
  const authValues2 = organizationAuthValues({
34944
35649
  baseUrl: config.baseUrl,
34945
35650
  apiKey: config.apiKey,
34946
35651
  orgId: target.org_id,
34947
- orgName: target.name
35652
+ orgName: target.name,
35653
+ features
34948
35654
  });
34949
35655
  if (authTarget.kind === "folder") {
34950
35656
  project_env_paths2 = saveProjectDeeplineEnvValues(authValues2);
@@ -35011,7 +35717,8 @@ async function handleOrgSwitch(selection, options) {
35011
35717
  baseUrl: config.baseUrl,
35012
35718
  apiKey: switched.api_key,
35013
35719
  orgId: switched.org_id,
35014
- orgName: switched.org_name
35720
+ orgName: switched.org_name,
35721
+ features: switched.features
35015
35722
  });
35016
35723
  if (authTarget.kind === "folder") {
35017
35724
  project_env_paths = saveProjectDeeplineEnvValues(authValues);
@@ -35083,14 +35790,20 @@ async function handleOrgCreate(name, options) {
35083
35790
  const identity = await fetchWorkspaceCreationIdentity(http, config.apiKey);
35084
35791
  let created;
35085
35792
  let workspaceApiKey;
35793
+ let workspaceFeatures;
35086
35794
  let pendingIntentPath = null;
35087
35795
  if (!identity.orgId) {
35088
35796
  const firstWorkspace = await http.post("/api/v2/auth/cli/org-create", {
35089
35797
  api_key: config.apiKey,
35090
35798
  name: normalizedName
35091
35799
  });
35092
- const { api_key: apiKey, ...publicFirstWorkspace } = firstWorkspace;
35800
+ const {
35801
+ api_key: apiKey,
35802
+ features,
35803
+ ...publicFirstWorkspace
35804
+ } = firstWorkspace;
35093
35805
  workspaceApiKey = apiKey;
35806
+ workspaceFeatures = features;
35094
35807
  created = publicFirstWorkspace;
35095
35808
  } else {
35096
35809
  const pending = loadOrCreatePendingOrgCreate({
@@ -35111,6 +35824,7 @@ async function handleOrgCreate(name, options) {
35111
35824
  org_id: workspace.org_id
35112
35825
  });
35113
35826
  workspaceApiKey = switched.api_key;
35827
+ workspaceFeatures = switched.features;
35114
35828
  created = { ...workspace };
35115
35829
  pendingIntentPath = pending.path;
35116
35830
  }
@@ -35118,7 +35832,8 @@ async function handleOrgCreate(name, options) {
35118
35832
  baseUrl: config.baseUrl,
35119
35833
  apiKey: workspaceApiKey,
35120
35834
  orgId: created.org_id,
35121
- orgName: created.org_name
35835
+ orgName: created.org_name,
35836
+ features: workspaceFeatures
35122
35837
  });
35123
35838
  saveHostEnvValues(config.baseUrl, authValues);
35124
35839
  if (pendingIntentPath) (0, import_node_fs16.unlinkSync)(pendingIntentPath);
@@ -35315,6 +36030,7 @@ Examples:
35315
36030
  }
35316
36031
 
35317
36032
  // src/cli/commands/git.ts
36033
+ var GIT_DEPLOY_FEATURE = "github-deploy";
35318
36034
  function renderProject(project) {
35319
36035
  const root = project.rootDirectory ? ` (${project.rootDirectory})` : "";
35320
36036
  const flags = [
@@ -35567,7 +36283,7 @@ async function handleDeploymentGet(id, options) {
35567
36283
  );
35568
36284
  }
35569
36285
  function registerGitCommands(program) {
35570
- const git = program.command("git").alias("projects").description(
36286
+ const git = program.command("git", { hidden: !cliFeatureEnabled(GIT_DEPLOY_FEATURE) }).alias("projects").description(
35571
36287
  "Git deploys: link repositories to this workspace and deploy plays on push."
35572
36288
  ).addHelpText(
35573
36289
  "after",
@@ -35888,18 +36604,25 @@ async function handleList2(options) {
35888
36604
  async function handleCheck(nameInput, options) {
35889
36605
  const name = normalizeSecretName(nameInput);
35890
36606
  const client2 = new DeeplineClient();
35891
- const secret = await client2.checkSecret(name);
36607
+ const check = options.play !== void 0 ? await client2.inspectSecret(name, { playName: options.play }) : null;
36608
+ const secret = check ? check.secret : await client2.checkSecret(name);
36609
+ const ok = check ? check.ok : Boolean(secret);
36610
+ const quotedPlay = `'${(options.play ?? "PLAY").replace(/'/g, `'"'"'`)}'`;
36611
+ const scopeHint = check ? secret?.scope === "play" ? check.reason === "disabled" ? `The Play-scoped secret is disabled and no active org fallback is available. Restore it with: deepline secrets set ${name} --scope play --play ${quotedPlay}` : `The active Play-scoped secret takes precedence over the org secret.${ok ? "" : ` Restore it with: deepline secrets set ${name} --scope play --play ${quotedPlay}`}` : `No Play override.${ok ? " The org secret is available." : ` Set or rotate the org secret with: deepline secrets set ${name}`}` : `Metadata only. Verify effective scope and active version with: deepline secrets check ${name} --play <play-name>`;
35892
36612
  printCommandEnvelope(
35893
36613
  {
35894
- ok: Boolean(secret),
36614
+ ok,
35895
36615
  name,
35896
36616
  secret: secret ?? null,
36617
+ check: check ? { playName: check.playName, reason: check.reason } : null,
36618
+ scopeHint,
35897
36619
  render: {
35898
36620
  sections: [
35899
36621
  {
35900
36622
  title: "secret check",
35901
36623
  lines: [
35902
- secret ? `${name}: active \u2014 ${renderSecret(secret)}` : `${name}: missing, disabled, or empty`
36624
+ check ? `${name}: ${check.reason} for ${check.playName}${secret ? ` (${secret.scope})` : ""}` : secret ? `${name}: active metadata \u2014 ${renderSecret(secret)}` : `${name}: missing, disabled, or empty`,
36625
+ scopeHint
35903
36626
  ]
35904
36627
  }
35905
36628
  ]
@@ -35907,7 +36630,7 @@ async function handleCheck(nameInput, options) {
35907
36630
  },
35908
36631
  { json: options.json }
35909
36632
  );
35910
- if (!secret) process.exitCode = 4;
36633
+ if (!ok) process.exitCode = 4;
35911
36634
  }
35912
36635
  async function handleSet2(nameInput, forbidden, options) {
35913
36636
  preventShellHistoryLeak(forbidden);
@@ -35961,6 +36684,7 @@ Notes:
35961
36684
  Examples:
35962
36685
  deepline secrets list
35963
36686
  deepline secrets check HUBSPOT_TOKEN
36687
+ deepline secrets check HUBSPOT_TOKEN --play hubspot-sync
35964
36688
  deepline secrets set HUBSPOT_TOKEN
35965
36689
  printf %s "$HUBSPOT_TOKEN" | deepline secrets set HUBSPOT_TOKEN --value-stdin
35966
36690
  deepline secrets set HUBSPOT_TOKEN --from-env HUBSPOT_TOKEN
@@ -35970,7 +36694,12 @@ Examples:
35970
36694
  secrets.command("list").description("List secret metadata only.").option("--json", "Emit JSON output").action(async (options) => {
35971
36695
  await handleList2(options);
35972
36696
  });
35973
- secrets.command("check").description("Check whether a secret exists and is active.").argument("<name>", "Secret name").option("--json", "Emit JSON output").action(async (name, options) => {
36697
+ secrets.command("check").description(
36698
+ "Check secret metadata, or effective availability with --play."
36699
+ ).argument("<name>", "Secret name").option(
36700
+ "--play <name>",
36701
+ "Validate the effective secret for a bare Play name and active version"
36702
+ ).option("--json", "Emit JSON output").action(async (name, options) => {
35974
36703
  await handleCheck(name, options);
35975
36704
  });
35976
36705
  secrets.command("set").description("Set or rotate a secret without exposing its value in argv.").argument("<name>", "Secret name").argument("[forbidden...]", "Do not pass secret values here").option("--json", "Emit JSON output").option("--scope <scope>", "Secret scope: org or play", "org").option("--play <name>", "Play name for play-scoped secrets").option("--value-stdin", "Read the value from piped stdin").option("--from-env <name>", "Read the value from an environment variable").option("--from-file <path>", "Read the value from a file").action(
@@ -36344,6 +37073,20 @@ function classifyNetworkFailure(error) {
36344
37073
  }
36345
37074
  return "network_error";
36346
37075
  }
37076
+ function classifyNetworkCauseCategory(error) {
37077
+ switch (classifyNetworkFailure(error)) {
37078
+ case "network_dns_resolution_failed":
37079
+ return "dns";
37080
+ case "network_ssl_error":
37081
+ return "tls";
37082
+ case "network_connection_reset":
37083
+ return "reset";
37084
+ case "network_timeout":
37085
+ return "connect_timeout";
37086
+ default:
37087
+ return "unknown";
37088
+ }
37089
+ }
36347
37090
  function isNetworkFailure(error) {
36348
37091
  if (!(error instanceof Error)) return false;
36349
37092
  if (error instanceof DeeplineError && error.statusCode) return false;
@@ -36513,6 +37256,7 @@ function buildFailureReport(input2) {
36513
37256
  });
36514
37257
  const code = failureCode({ exitCode: input2.exitCode, error: input2.error });
36515
37258
  const errorBody = input2.error === void 0 ? `SDK CLI command exited ${input2.exitCode ?? "unknown"}` : errorMessage3(input2.error);
37259
+ const networkCauseCategory = input2.error !== void 0 && isNetworkFailure(input2.error) ? classifyNetworkCauseCategory(input2.error) : null;
36516
37260
  return {
36517
37261
  command: sanitizeCommand(input2.argv),
36518
37262
  subcommand: subcommandFromArgv(input2.argv),
@@ -36534,7 +37278,8 @@ function buildFailureReport(input2) {
36534
37278
  failure_code: code,
36535
37279
  failure_stage: input2.error === void 0 ? "command_exit" : "cli_main",
36536
37280
  duration_ms: durationMs,
36537
- ...input2.exitCode !== null ? { exit_code: input2.exitCode } : {}
37281
+ ...input2.exitCode !== null ? { exit_code: input2.exitCode } : {},
37282
+ ...networkCauseCategory ? { network_cause_category: networkCauseCategory } : {}
36538
37283
  }
36539
37284
  };
36540
37285
  }
@@ -38239,6 +38984,9 @@ Examples:
38239
38984
  });
38240
38985
  }
38241
38986
 
38987
+ // src/cli/commands/settings.ts
38988
+ var import_node_fs24 = require("fs");
38989
+
38242
38990
  // src/cli/update-preferences.ts
38243
38991
  var import_node_fs21 = require("fs");
38244
38992
  var import_node_os14 = require("os");
@@ -39930,21 +40678,104 @@ function displaySlackTarget(target) {
39930
40678
  function collectEvent(value, previous = []) {
39931
40679
  return [...previous, value];
39932
40680
  }
40681
+ function collectNotificationTarget(value, previous = []) {
40682
+ return [...previous, value];
40683
+ }
40684
+ async function readNotificationConfigurationChange(source, read = (path, encoding) => (0, import_node_fs24.readFileSync)(path, encoding)) {
40685
+ const label = source === "-" ? "stdin" : source;
40686
+ let text;
40687
+ try {
40688
+ text = await read(source === "-" ? 0 : source, "utf8");
40689
+ } catch (error) {
40690
+ throw new Error(
40691
+ `Could not read notification configuration from ${label}: ${error instanceof Error ? error.message : String(error)}`
40692
+ );
40693
+ }
40694
+ let parsed;
40695
+ try {
40696
+ parsed = JSON.parse(text);
40697
+ } catch (error) {
40698
+ throw new Error(
40699
+ `Notification configuration from ${label} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
40700
+ );
40701
+ }
40702
+ if (typeof parsed.expectedWorkspaceVersion !== "number" || !Number.isInteger(parsed.expectedWorkspaceVersion) || !parsed.change || typeof parsed.change !== "object") {
40703
+ throw new Error(
40704
+ `Notification configuration from ${label} must contain an integer expectedWorkspaceVersion and a change object.`
40705
+ );
40706
+ }
40707
+ return {
40708
+ expectedWorkspaceVersion: parsed.expectedWorkspaceVersion,
40709
+ change: parsed.change
40710
+ };
40711
+ }
39933
40712
  function registerNotificationCommands(program) {
39934
- const notifications = program.command("notifications").description("Choose which Play outcomes notify which people and channels.").addHelpText(
40713
+ const notifications = program.command("notifications").description("Configure notification groups, destinations, and rules.").addHelpText(
39935
40714
  "after",
39936
40715
  `
39937
40716
  Examples:
40717
+ deepline notifications add oncall --to email:oncall@example.com --to slack:#oncall
39938
40718
  deepline notifications events
39939
40719
  deepline notifications slack channels --search pipeline
39940
40720
  deepline notifications add pipeline-watchdog --to slack:#pipeline-alerts --for play.cron.failed
39941
40721
  deepline notifications add owner-alerts --to slack:member:U0123456789 --for play.cron.failed
39942
40722
  deepline notifications test pipeline-watchdog
39943
40723
 
39944
- Slack connections are managed in Dashboard \u2192 Integrations. This command only
39945
- chooses the connected Slack channel or member and the events it receives.
40724
+ Each named notification is a group. Repeat --to to give it email and Slack
40725
+ destinations. Add rules from the dashboard or notification configuration. The
40726
+ existing one-Slack-target form with --for remains supported and creates the
40727
+ equivalent compatibility group and rules. Slack connections are managed in
40728
+ Dashboard \u2192 Integrations.
39946
40729
  `
39947
40730
  );
40731
+ const configuration = notifications.command("configuration").description("Read or atomically update notification groups and rules.").addHelpText(
40732
+ "after",
40733
+ `
40734
+ Examples:
40735
+ deepline notifications configuration get --json
40736
+ deepline notifications configuration apply change.json --dry-run --json
40737
+ jq '{expectedWorkspaceVersion: .configuration.workspaceVersion, change: {kind: "delete_group", groupKey: "oncall"}}' current.json | deepline notifications configuration apply - --json
40738
+ `
40739
+ );
40740
+ configuration.command("get").description("Show the effective group-based notification configuration.").option("--json", "Emit JSON output").action(async (options) => {
40741
+ const result = await new DeeplineClient().getNotificationConfiguration();
40742
+ printCommandEnvelope(
40743
+ {
40744
+ ...result,
40745
+ render: {
40746
+ sections: [
40747
+ {
40748
+ title: "notification groups",
40749
+ lines: result.configuration.groups.length ? result.configuration.groups.map(
40750
+ (group) => `${group.name}: ${group.enabled ? "active" : "paused"}`
40751
+ ) : ["No notification groups are configured."]
40752
+ }
40753
+ ]
40754
+ }
40755
+ },
40756
+ { json: options.json }
40757
+ );
40758
+ });
40759
+ configuration.command("apply").argument(
40760
+ "<file>",
40761
+ "JSON file, or - for stdin, containing expectedWorkspaceVersion and one desired change"
40762
+ ).description(
40763
+ "Atomically apply a configuration draft exported by an agent or UI."
40764
+ ).option("--dry-run", "Validate and display the file without saving it").option("--json", "Emit JSON output").action(async (file, options) => {
40765
+ const input2 = await readNotificationConfigurationChange(file);
40766
+ if (options.dryRun) {
40767
+ printCommandEnvelope(
40768
+ { dryRun: true, ...input2 },
40769
+ { json: options.json }
40770
+ );
40771
+ return;
40772
+ }
40773
+ const result = await new DeeplineClient().changeNotificationConfiguration(
40774
+ input2.expectedWorkspaceVersion,
40775
+ input2.change
40776
+ );
40777
+ printCommandEnvelope(result, { json: options.json });
40778
+ });
39948
40779
  notifications.command("events").description("List the Play events available to a notification.").option("--json", "Emit JSON output").action(async (options) => {
39949
40780
  const settings = await new DeeplineClient().getNotifications();
39950
40781
  printCommandEnvelope(
@@ -40000,42 +40831,82 @@ chooses the connected Slack channel or member and the events it receives.
40000
40831
  );
40001
40832
  printCommandEnvelope({ notification: rule }, { json: options.json });
40002
40833
  });
40003
- notifications.command("add").argument("<name>", "Short stable name, for example pipeline-watchdog").requiredOption(
40834
+ notifications.command("add").argument("<name>", "Group name, for example oncall").requiredOption(
40004
40835
  "--to <target>",
40005
- "Target, for example slack:#pipeline-alerts or slack:member:U0123456789"
40836
+ "Target; repeat email:name@example.com, slack:#channel, or slack:member:U0123456789",
40837
+ collectNotificationTarget,
40838
+ []
40006
40839
  ).option(
40007
40840
  "--for <event>",
40008
- "Event ID; repeat for more events. Run notifications events to list them",
40841
+ "Legacy one-Slack-target event ID; repeat for more events",
40009
40842
  collectEvent,
40010
40843
  []
40011
40844
  ).option("--dry-run", "Describe the notification without saving it").option("--json", "Emit JSON output").action(
40012
40845
  async (name, options) => {
40013
- if (!options.for.length) {
40014
- throw new Error("Choose at least one event with --for <event>.");
40846
+ const result = await new DeeplineClient().createNotificationGroup({
40847
+ name,
40848
+ targets: options.to,
40849
+ legacyEvents: options.for,
40850
+ dryRun: options.dryRun
40851
+ });
40852
+ if (result.mode === "dry_run") {
40853
+ printCommandEnvelope(
40854
+ result.compatibilityMode ? {
40855
+ dryRun: true,
40856
+ name: result.name,
40857
+ target: options.to[0],
40858
+ eventTypes: result.legacyEvents
40859
+ } : {
40860
+ dryRun: true,
40861
+ group: result.name,
40862
+ targets: result.targets,
40863
+ compatibilityMode: false
40864
+ },
40865
+ { json: options.json }
40866
+ );
40867
+ return;
40015
40868
  }
40016
- const target = parseSlackTarget(options.to);
40017
- if (options.dryRun) {
40869
+ if (result.mode === "legacy") {
40018
40870
  printCommandEnvelope(
40019
- { dryRun: true, name, target: options.to, eventTypes: options.for },
40871
+ {
40872
+ notification: result.notification,
40873
+ render: {
40874
+ sections: [
40875
+ {
40876
+ title: "notification saved",
40877
+ lines: [name]
40878
+ }
40879
+ ],
40880
+ actions: [
40881
+ {
40882
+ label: "Send a test",
40883
+ command: `deepline notifications test ${name}`
40884
+ }
40885
+ ]
40886
+ }
40887
+ },
40020
40888
  { json: options.json }
40021
40889
  );
40022
40890
  return;
40023
40891
  }
40024
- const result = await new DeeplineClient().createNotification({
40025
- name,
40026
- provider: "slack",
40027
- ...target,
40028
- eventTypes: options.for
40029
- });
40030
40892
  printCommandEnvelope(
40031
40893
  {
40032
- notification: result,
40894
+ ...result,
40033
40895
  render: {
40034
- sections: [{ title: "notification saved", lines: [name] }],
40896
+ sections: [
40897
+ {
40898
+ title: "notification group saved",
40899
+ lines: [
40900
+ result.group.name,
40901
+ `${result.destinations.length} destination${result.destinations.length === 1 ? "" : "s"}`,
40902
+ "0 rules"
40903
+ ]
40904
+ }
40905
+ ],
40035
40906
  actions: [
40036
40907
  {
40037
- label: "Send a test",
40038
- command: `deepline notifications test ${name}`
40908
+ label: "View configuration",
40909
+ command: "deepline notifications configuration get"
40039
40910
  }
40040
40911
  ]
40041
40912
  }
@@ -40149,12 +41020,12 @@ chooses the connected Slack channel or member and the events it receives.
40149
41020
 
40150
41021
  // src/cli/commands/tools.ts
40151
41022
  var import_commander4 = require("commander");
40152
- var import_node_fs25 = require("fs");
41023
+ var import_node_fs26 = require("fs");
40153
41024
  var import_node_os17 = require("os");
40154
41025
  var import_node_path27 = require("path");
40155
41026
 
40156
41027
  // src/tool-output.ts
40157
- var import_node_fs24 = require("fs");
41028
+ var import_node_fs25 = require("fs");
40158
41029
  var import_node_os16 = require("os");
40159
41030
  var import_node_path26 = require("path");
40160
41031
  function isPlainObject(value) {
@@ -40284,18 +41155,18 @@ function projectRowOutput(conversion) {
40284
41155
  }
40285
41156
  function ensureOutputDir() {
40286
41157
  const outputDir = (0, import_node_path26.join)((0, import_node_os16.homedir)(), ".local", "share", "deepline", "data");
40287
- (0, import_node_fs24.mkdirSync)(outputDir, { recursive: true });
41158
+ (0, import_node_fs25.mkdirSync)(outputDir, { recursive: true });
40288
41159
  return outputDir;
40289
41160
  }
40290
41161
  function writeJsonOutputFile(payload, stem) {
40291
41162
  const outputDir = ensureOutputDir();
40292
41163
  const outputPath = (0, import_node_path26.join)(outputDir, `${stem}_${Date.now()}.json`);
40293
- (0, import_node_fs24.writeFileSync)(outputPath, JSON.stringify(payload, null, 2), "utf-8");
41164
+ (0, import_node_fs25.writeFileSync)(outputPath, JSON.stringify(payload, null, 2), "utf-8");
40294
41165
  return outputPath;
40295
41166
  }
40296
41167
  function writeCsvOutputFile(rows, stem, options) {
40297
41168
  const outputPath = options?.outPath ? options.outPath : (0, import_node_path26.join)(ensureOutputDir(), `${stem}_${Date.now()}.csv`);
40298
- (0, import_node_fs24.mkdirSync)((0, import_node_path26.dirname)(outputPath), { recursive: true });
41169
+ (0, import_node_fs25.mkdirSync)((0, import_node_path26.dirname)(outputPath), { recursive: true });
40299
41170
  const columns = columnsForRows(rows);
40300
41171
  const escapeCell = (value) => {
40301
41172
  const normalized = value == null ? "" : typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : JSON.stringify(value);
@@ -40304,19 +41175,19 @@ function writeCsvOutputFile(rows, stem, options) {
40304
41175
  }
40305
41176
  return normalized;
40306
41177
  };
40307
- const fd = (0, import_node_fs24.openSync)(outputPath, "w");
41178
+ const fd = (0, import_node_fs25.openSync)(outputPath, "w");
40308
41179
  try {
40309
- (0, import_node_fs24.writeSync)(fd, `${columns.map(escapeCell).join(",")}
41180
+ (0, import_node_fs25.writeSync)(fd, `${columns.map(escapeCell).join(",")}
40310
41181
  `);
40311
41182
  for (const row of rows) {
40312
- (0, import_node_fs24.writeSync)(
41183
+ (0, import_node_fs25.writeSync)(
40313
41184
  fd,
40314
41185
  `${columns.map((column) => escapeCell(row[column])).join(",")}
40315
41186
  `
40316
41187
  );
40317
41188
  }
40318
41189
  } finally {
40319
- (0, import_node_fs24.closeSync)(fd);
41190
+ (0, import_node_fs25.closeSync)(fd);
40320
41191
  }
40321
41192
  const previewRows = rows.slice(0, 5);
40322
41193
  const previewColumns = columns.slice(0, 5);
@@ -41010,6 +41881,9 @@ async function findPlayForToolId(client2, toolId) {
41010
41881
  const plays = await client2.searchPlays({ query: requested, compact: true });
41011
41882
  return plays.find((play) => playIdentifiers(play).includes(requested)) ?? null;
41012
41883
  }
41884
+ function isMissingToolMetadataError(error) {
41885
+ return error instanceof DeeplineError && error.statusCode === 404;
41886
+ }
41013
41887
  function playAliasToolErrorMessage(toolId, play) {
41014
41888
  const playName = play.reference ?? play.name;
41015
41889
  return `${toolId} is a play, not a tool.
@@ -41291,10 +42165,12 @@ async function getTool(toolId, options = {}) {
41291
42165
  try {
41292
42166
  tool = await client2.getTool(toolId);
41293
42167
  } catch (error) {
41294
- const play = await findPlayForToolId(client2, toolId);
41295
- if (play) {
41296
- printPlayAliasToolError(toolId, play);
41297
- return 2;
42168
+ if (isMissingToolMetadataError(error)) {
42169
+ const play = await findPlayForToolId(client2, toolId);
42170
+ if (play) {
42171
+ printPlayAliasToolError(toolId, play);
42172
+ return 2;
42173
+ }
41298
42174
  }
41299
42175
  throw error;
41300
42176
  }
@@ -42268,10 +43144,10 @@ function normalizeOutputFormat(raw) {
42268
43144
  function resolveAtFilePath(rawPath) {
42269
43145
  const trimmed = rawPath.trim();
42270
43146
  const resolved = (0, import_node_path27.resolve)(trimmed);
42271
- if ((0, import_node_fs25.existsSync)(resolved)) return resolved;
43147
+ if ((0, import_node_fs26.existsSync)(resolved)) return resolved;
42272
43148
  if (process.platform !== "win32" && trimmed.includes("\\")) {
42273
43149
  const normalized = (0, import_node_path27.resolve)(trimmed.replace(/\\/g, "/"));
42274
- if ((0, import_node_fs25.existsSync)(normalized)) return normalized;
43150
+ if ((0, import_node_fs26.existsSync)(normalized)) return normalized;
42275
43151
  }
42276
43152
  return resolved;
42277
43153
  }
@@ -42282,7 +43158,7 @@ function readJsonArgument(raw, flagName) {
42282
43158
  throw new Error(`Invalid ${flagName} value: empty @file path.`);
42283
43159
  }
42284
43160
  try {
42285
- return (0, import_node_fs25.readFileSync)(resolveAtFilePath(filePath), "utf8").replace(
43161
+ return (0, import_node_fs26.readFileSync)(resolveAtFilePath(filePath), "utf8").replace(
42286
43162
  /^\uFEFF/,
42287
43163
  ""
42288
43164
  );
@@ -42389,8 +43265,8 @@ function starterScriptJson(script) {
42389
43265
  function seedToolListScript(input2) {
42390
43266
  const stem = safeFileStem(input2.toolId);
42391
43267
  const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
42392
- const scriptDir = (0, import_node_fs25.mkdtempSync)((0, import_node_path27.join)((0, import_node_os17.tmpdir)(), "deepline-workflow-seed-"));
42393
- (0, import_node_fs25.chmodSync)(scriptDir, 448);
43268
+ const scriptDir = (0, import_node_fs26.mkdtempSync)((0, import_node_path27.join)((0, import_node_os17.tmpdir)(), "deepline-workflow-seed-"));
43269
+ (0, import_node_fs26.chmodSync)(scriptDir, 448);
42394
43270
  const scriptPath = (0, import_node_path27.join)(scriptDir, fileName);
42395
43271
  const projectDir = `deepline/projects/${stem}-workflow`;
42396
43272
  const playName = `${stem}-workflow`;
@@ -42434,7 +43310,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
42434
43310
  description: ${JSON.stringify(`Seed ${input2.toolId} rows into a Deepline workflow-ready dataset.`)},
42435
43311
  });
42436
43312
  `;
42437
- (0, import_node_fs25.writeFileSync)(scriptPath, script, { encoding: "utf-8", mode: 384 });
43313
+ (0, import_node_fs26.writeFileSync)(scriptPath, script, { encoding: "utf-8", mode: 384 });
42438
43314
  return {
42439
43315
  path: scriptPath,
42440
43316
  sourceCode: script,
@@ -42633,19 +43509,18 @@ async function executeTool(args) {
42633
43509
  try {
42634
43510
  metadata = await client2.getTool(parsed.toolId);
42635
43511
  } catch (error) {
42636
- const play = await findPlayForToolId(client2, parsed.toolId);
42637
- if (play) {
42638
- if (argsWantJson(args)) {
42639
- printJsonError(
42640
- new Error(playAliasToolErrorMessage(parsed.toolId, play))
42641
- );
42642
- } else {
42643
- printPlayAliasToolError(parsed.toolId, play);
43512
+ if (isMissingToolMetadataError(error)) {
43513
+ const play = await findPlayForToolId(client2, parsed.toolId);
43514
+ if (play) {
43515
+ if (argsWantJson(args)) {
43516
+ printJsonError(
43517
+ new Error(playAliasToolErrorMessage(parsed.toolId, play))
43518
+ );
43519
+ } else {
43520
+ printPlayAliasToolError(parsed.toolId, play);
43521
+ }
43522
+ return 2;
42644
43523
  }
42645
- return 2;
42646
- }
42647
- if (error instanceof DeeplineError) {
42648
- throw error;
42649
43524
  }
42650
43525
  throw error;
42651
43526
  }