deepline 0.1.271 → 0.1.272

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.
@@ -77,6 +77,7 @@ import type {
77
77
  CustomerDbQueryResult,
78
78
  DeeplineAgentModelDescription,
79
79
  } from './types.js';
80
+ import type { MonitorDefinition } from './monitors.js';
80
81
  import type { PlayStagedFileRef } from './plays/local-file-discovery.js';
81
82
  import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-manifest.js';
82
83
  import type { EnrichCompiledConfig } from './cli/enrich-play-compiler.js';
@@ -609,6 +610,151 @@ export type DbNamespace = {
609
610
  }) => Promise<CustomerDbQueryResult>;
610
611
  };
611
612
 
613
+ /**
614
+ * Whether the current workspace can use Deepline Monitors, from
615
+ * `GET /api/v2/monitors/access`. Reachable without monitor access (requires
616
+ * only an authenticated session/API key); a denial is a normal 200 body, not a
617
+ * 403.
618
+ */
619
+ export type MonitorsAccessStatus = {
620
+ has_access: boolean;
621
+ reason?: string;
622
+ };
623
+
624
+ /**
625
+ * The monitor tools catalog (deployable monitor TYPES), from
626
+ * `GET /api/v2/monitors/tools`. Server-owned shape: describing one tool returns
627
+ * the full payload/stream contract, while list mode returns the compact
628
+ * inventory. Kept as an open record so the SDK does not have to bump when the
629
+ * server enriches the catalog.
630
+ */
631
+ export type MonitorsAvailableResult = {
632
+ tools?: Array<Record<string, unknown>>;
633
+ total?: number;
634
+ returned?: number;
635
+ is_truncated?: boolean;
636
+ [key: string]: unknown;
637
+ };
638
+
639
+ /** Options for `client.monitors.available(...)` (list or describe mode). */
640
+ export type MonitorsAvailableOptions = {
641
+ provider?: string;
642
+ search?: string;
643
+ limit?: number | string;
644
+ /**
645
+ * Request the full catalog contract in LIST mode (payload schemas + streams).
646
+ * List mode is compact by default; ignored when describing a single tool.
647
+ */
648
+ full?: boolean;
649
+ /** Ask the server for high-signal fields only. */
650
+ compact?: boolean;
651
+ };
652
+
653
+ /** One deployed monitor row returned by `client.monitors.list(...)`. */
654
+ export type MonitorListEntry = {
655
+ key?: string;
656
+ monitor_key?: string;
657
+ status?: string;
658
+ tool?: string;
659
+ name?: string;
660
+ [key: string]: unknown;
661
+ };
662
+
663
+ /**
664
+ * Deployed-monitor registry page from `GET /api/v2/monitors/deployed`. `total`
665
+ * is the TRUE registry count for the status filter, not this page's size; page
666
+ * past a truncated result with `next_cursor`.
667
+ */
668
+ export type MonitorsListResult = {
669
+ monitors?: MonitorListEntry[];
670
+ total?: number;
671
+ returned?: number;
672
+ is_truncated?: boolean;
673
+ next_cursor?: string | null;
674
+ status_filter_applied?: string;
675
+ [key: string]: unknown;
676
+ };
677
+
678
+ /** Options for `client.monitors.list(...)`. */
679
+ export type MonitorsListOptions = {
680
+ /** active (default), disabled, or all. */
681
+ status?: string;
682
+ limit?: number | string;
683
+ /** Page past a truncated result using a prior response's `next_cursor`. */
684
+ cursor?: string;
685
+ compact?: boolean;
686
+ };
687
+
688
+ /**
689
+ * Server-owned monitor payload shapes returned by the deploy/check/get/mutation
690
+ * endpoints. Kept as open records because the render layer and the endpoints
691
+ * own the exact field set (output contracts, pricing, reuse candidates, delete
692
+ * plans, dependents) and it must not require an SDK bump to evolve.
693
+ */
694
+ export type MonitorCheckResult = Record<string, unknown>;
695
+ export type MonitorDeployResult = Record<string, unknown>;
696
+ export type MonitorDetail = Record<string, unknown>;
697
+ export type MonitorDependents = Record<string, unknown>;
698
+ export type MonitorUpdateResult = Record<string, unknown>;
699
+ export type MonitorDeleteResult = Record<string, unknown>;
700
+ export type MonitorReactivateResult = Record<string, unknown>;
701
+
702
+ /**
703
+ * Public monitors namespace exposed as `client.monitors`.
704
+ *
705
+ * Mirrors the /api/v2/monitors resource family so the monitors CLI and
706
+ * programmatic callers share one product surface — every `deepline monitors`
707
+ * verb maps to a method here. Monitors are fully expressible as SDK code: author
708
+ * a definition with {@link defineMonitor}, then check/deploy/list/get/update/
709
+ * delete/reactivate through this namespace.
710
+ *
711
+ * @sdkReference client 040 client.monitors
712
+ */
713
+ export type MonitorsNamespace = {
714
+ /** Whether the current workspace can use monitors (`{ has_access, reason }`). */
715
+ status: () => Promise<MonitorsAccessStatus>;
716
+ /**
717
+ * The deployable monitor tools catalog. Call with no tool id for the list, or
718
+ * with a tool id (positional or `{ tool }`) to describe one tool's full
719
+ * payload/stream contract.
720
+ */
721
+ available: (
722
+ toolIdOrOptions?: string | (MonitorsAvailableOptions & { tool?: string }),
723
+ options?: MonitorsAvailableOptions,
724
+ ) => Promise<MonitorsAvailableResult>;
725
+ /** Validate a monitor definition without deploying it (no spend). */
726
+ check: (definition: MonitorDefinition) => Promise<MonitorCheckResult>;
727
+ /** Deploy a monitor from a definition. May spend Deepline credits. */
728
+ deploy: (
729
+ definition: MonitorDefinition,
730
+ options?: { dryRun?: boolean },
731
+ ) => Promise<MonitorDeployResult>;
732
+ /** List deployed monitors (active by default). */
733
+ list: (options?: MonitorsListOptions) => Promise<MonitorsListResult>;
734
+ /** Fetch one deployed monitor by public key (without dependents). */
735
+ get: (key: string) => Promise<MonitorDetail>;
736
+ /** List the published plays depending on one monitor's output streams. */
737
+ dependents: (key: string) => Promise<MonitorDependents>;
738
+ /** Update a deployed monitor by public key. */
739
+ update: (
740
+ key: string,
741
+ patch: Record<string, unknown>,
742
+ ) => Promise<MonitorUpdateResult>;
743
+ /**
744
+ * Delete a deployed monitor by public key. Deprovisions the upstream provider
745
+ * resource unless `localOnly` is set. `dryRun` returns the delete plan.
746
+ */
747
+ delete: (
748
+ key: string,
749
+ options?: { localOnly?: boolean; dryRun?: boolean },
750
+ ) => Promise<MonitorDeleteResult>;
751
+ /** Reactivate a disabled monitor. `dryRun` returns the reactivation cost. */
752
+ reactivate: (
753
+ key: string,
754
+ options?: { dryRun?: boolean },
755
+ ) => Promise<MonitorReactivateResult>;
756
+ };
757
+
612
758
  /** One credit grant pool reported by the billing subscription status endpoint. */
613
759
  export type BillingCreditPool = {
614
760
  pool: string;
@@ -1220,6 +1366,8 @@ export class DeeplineClient {
1220
1366
  readonly db: DbNamespace;
1221
1367
  /** Billing namespace: subscription status/cancel and invoice history. */
1222
1368
  readonly billing: BillingNamespace;
1369
+ /** Monitors namespace: access, catalog, deploy/check, and lifecycle. */
1370
+ readonly monitors: MonitorsNamespace;
1223
1371
 
1224
1372
  /**
1225
1373
  * Create a low-level SDK client.
@@ -1256,6 +1404,19 @@ export class DeeplineClient {
1256
1404
  list: (options) => this.listBillingInvoices(options),
1257
1405
  },
1258
1406
  };
1407
+ this.monitors = {
1408
+ status: () => this.getMonitorsAccess(),
1409
+ available: (toolIdOrOptions, options) =>
1410
+ this.getMonitorsAvailable(toolIdOrOptions, options),
1411
+ check: (definition) => this.checkMonitor(definition),
1412
+ deploy: (definition, options) => this.deployMonitor(definition, options),
1413
+ list: (options) => this.listMonitors(options),
1414
+ get: (key) => this.getMonitor(key),
1415
+ dependents: (key) => this.getMonitorDependents(key),
1416
+ update: (key, patch) => this.updateMonitor(key, patch),
1417
+ delete: (key, options) => this.deleteMonitor(key, options),
1418
+ reactivate: (key, options) => this.reactivateMonitor(key, options),
1419
+ };
1259
1420
  }
1260
1421
 
1261
1422
  /** The resolved base URL this client is targeting (e.g. `"http://localhost:3000"`). */
@@ -3710,6 +3871,192 @@ export class DeeplineClient {
3710
3871
  );
3711
3872
  }
3712
3873
 
3874
+ // ——————————————————————————————————————————————————————————
3875
+ // Monitors
3876
+ // ——————————————————————————————————————————————————————————
3877
+
3878
+ /**
3879
+ * Whether the current workspace can use Deepline Monitors. Reachable without
3880
+ * monitor access; a denial is a normal 200 body, not a 403. Prefer
3881
+ * `client.monitors.status()`.
3882
+ */
3883
+ async getMonitorsAccess(): Promise<MonitorsAccessStatus> {
3884
+ // No forbiddenAsApiError: the endpoint answers 200 in both the granted and
3885
+ // denied cases, so a 403 would be a real auth failure, not a denial body.
3886
+ const payload = await this.http.request<MonitorsAccessStatus>(
3887
+ '/api/v2/monitors/access',
3888
+ { method: 'GET' },
3889
+ );
3890
+ return {
3891
+ has_access: payload.has_access === true,
3892
+ ...(typeof payload.reason === 'string' && payload.reason.trim()
3893
+ ? { reason: payload.reason.trim() }
3894
+ : {}),
3895
+ };
3896
+ }
3897
+
3898
+ /**
3899
+ * The deployable monitor tools catalog. Pass a tool id (positional or
3900
+ * `{ tool }`) to describe one tool's full payload/stream contract, or no tool
3901
+ * id for the compact inventory. Prefer `client.monitors.available(...)`.
3902
+ */
3903
+ async getMonitorsAvailable(
3904
+ toolIdOrOptions?:
3905
+ | string
3906
+ | (MonitorsAvailableOptions & { tool?: string }),
3907
+ maybeOptions?: MonitorsAvailableOptions,
3908
+ ): Promise<MonitorsAvailableResult> {
3909
+ const positionalTool =
3910
+ typeof toolIdOrOptions === 'string' ? toolIdOrOptions : undefined;
3911
+ // The first argument is either the tool id (string) or an options object.
3912
+ // When it is a string (or omitted), `maybeOptions` carries the options;
3913
+ // when it is an options object, use it directly.
3914
+ const options =
3915
+ toolIdOrOptions && typeof toolIdOrOptions === 'object'
3916
+ ? toolIdOrOptions
3917
+ : (maybeOptions ?? {});
3918
+ const optionTool =
3919
+ toolIdOrOptions && typeof toolIdOrOptions === 'object'
3920
+ ? toolIdOrOptions.tool
3921
+ : undefined;
3922
+ const tool = positionalTool ?? optionTool;
3923
+ const params = new URLSearchParams();
3924
+ if (options.provider) params.set('provider', options.provider);
3925
+ if (tool) params.set('tool', tool);
3926
+ if (options.search) params.set('search', options.search);
3927
+ if (options.limit !== undefined) params.set('limit', String(options.limit));
3928
+ // List mode is compact by default (id + name + deployed_count). `full`
3929
+ // restores the heavy catalog; `compact` stays an explicit alias. Describing
3930
+ // a single tool always returns the full contract, so this is skipped there.
3931
+ const compactList = !tool && options.full !== true;
3932
+ if (compactList || options.compact) params.set('compact', 'true');
3933
+ const query = params.toString();
3934
+ // Precompute the query suffix: a nested template literal in the request
3935
+ // path (`...tools${query ? `?...` : ''}`) breaks the SDK/API contract
3936
+ // checker's backtick path extraction. Keep the path a single interpolation.
3937
+ const suffix = query ? `?${query}` : '';
3938
+ return this.http.request<MonitorsAvailableResult>(
3939
+ `/api/v2/monitors/tools${suffix}`,
3940
+ { method: 'GET', forbiddenAsApiError: true },
3941
+ );
3942
+ }
3943
+
3944
+ /** Validate a monitor definition without deploying it. Prefer `client.monitors.check(...)`. */
3945
+ async checkMonitor(
3946
+ definition: MonitorDefinition,
3947
+ ): Promise<MonitorCheckResult> {
3948
+ return this.http.request<MonitorCheckResult>('/api/v2/monitors/check', {
3949
+ method: 'POST',
3950
+ body: definition,
3951
+ forbiddenAsApiError: true,
3952
+ });
3953
+ }
3954
+
3955
+ /**
3956
+ * Deploy a monitor from a definition. `dryRun` validates via the check
3957
+ * endpoint and returns the plan without deploying. Prefer
3958
+ * `client.monitors.deploy(...)`.
3959
+ */
3960
+ async deployMonitor(
3961
+ definition: MonitorDefinition,
3962
+ options?: { dryRun?: boolean },
3963
+ ): Promise<MonitorDeployResult> {
3964
+ if (options?.dryRun) {
3965
+ // The deploy plan is served by the CHECK endpoint. No deploy call is ever
3966
+ // made in dry-run mode.
3967
+ return this.http.request<MonitorDeployResult>('/api/v2/monitors/check', {
3968
+ method: 'POST',
3969
+ body: definition,
3970
+ forbiddenAsApiError: true,
3971
+ });
3972
+ }
3973
+ return this.http.request<MonitorDeployResult>('/api/v2/monitors/deploy', {
3974
+ method: 'POST',
3975
+ body: definition,
3976
+ forbiddenAsApiError: true,
3977
+ });
3978
+ }
3979
+
3980
+ /** List deployed monitors. Prefer `client.monitors.list(...)`. */
3981
+ async listMonitors(
3982
+ options?: MonitorsListOptions,
3983
+ ): Promise<MonitorsListResult> {
3984
+ const params = new URLSearchParams();
3985
+ if (options?.status) params.set('status', options.status);
3986
+ if (options?.limit !== undefined) params.set('limit', String(options.limit));
3987
+ if (options?.cursor) params.set('cursor', options.cursor);
3988
+ if (options?.compact) params.set('compact', 'true');
3989
+ const query = params.toString();
3990
+ // Single interpolation only — see availableMonitors: a nested template in
3991
+ // the path confuses the SDK/API contract path extractor.
3992
+ const suffix = query ? `?${query}` : '';
3993
+ return this.http.request<MonitorsListResult>(
3994
+ `/api/v2/monitors/deployed${suffix}`,
3995
+ { method: 'GET', forbiddenAsApiError: true },
3996
+ );
3997
+ }
3998
+
3999
+ /** Fetch one deployed monitor by public key. Prefer `client.monitors.get(...)`. */
4000
+ async getMonitor(key: string): Promise<MonitorDetail> {
4001
+ return this.http.request<MonitorDetail>(
4002
+ `/api/v2/monitors/deployed/${encodeURIComponent(key)}`,
4003
+ { method: 'GET', forbiddenAsApiError: true },
4004
+ );
4005
+ }
4006
+
4007
+ /** Published plays depending on one monitor. Prefer `client.monitors.dependents(...)`. */
4008
+ async getMonitorDependents(key: string): Promise<MonitorDependents> {
4009
+ return this.http.request<MonitorDependents>(
4010
+ `/api/v2/monitors/deployed/${encodeURIComponent(key)}/dependents`,
4011
+ { method: 'GET', forbiddenAsApiError: true },
4012
+ );
4013
+ }
4014
+
4015
+ /** Update a deployed monitor by public key. Prefer `client.monitors.update(...)`. */
4016
+ async updateMonitor(
4017
+ key: string,
4018
+ patch: Record<string, unknown>,
4019
+ ): Promise<MonitorUpdateResult> {
4020
+ return this.http.request<MonitorUpdateResult>(
4021
+ `/api/v2/monitors/deployed/${encodeURIComponent(key)}`,
4022
+ { method: 'PATCH', body: patch, forbiddenAsApiError: true },
4023
+ );
4024
+ }
4025
+
4026
+ /**
4027
+ * Delete a deployed monitor by public key. Deprovisions the upstream provider
4028
+ * resource unless `localOnly`; `dryRun` returns the delete plan. Prefer
4029
+ * `client.monitors.delete(...)`.
4030
+ */
4031
+ async deleteMonitor(
4032
+ key: string,
4033
+ options?: { localOnly?: boolean; dryRun?: boolean },
4034
+ ): Promise<MonitorDeleteResult> {
4035
+ const params = new URLSearchParams();
4036
+ if (options?.localOnly) params.set('local_only', 'true');
4037
+ if (options?.dryRun) params.set('dry_run', 'true');
4038
+ const query = params.toString();
4039
+ return this.http.request<MonitorDeleteResult>(
4040
+ `/api/v2/monitors/deployed/${encodeURIComponent(key)}${query ? `?${query}` : ''}`,
4041
+ { method: 'DELETE', forbiddenAsApiError: true },
4042
+ );
4043
+ }
4044
+
4045
+ /**
4046
+ * Reactivate a disabled monitor. `dryRun` returns the reactivation cost.
4047
+ * Prefer `client.monitors.reactivate(...)`.
4048
+ */
4049
+ async reactivateMonitor(
4050
+ key: string,
4051
+ options?: { dryRun?: boolean },
4052
+ ): Promise<MonitorReactivateResult> {
4053
+ const query = options?.dryRun ? '?dry_run=true' : '';
4054
+ return this.http.request<MonitorReactivateResult>(
4055
+ `/api/v2/monitors/deployed/${encodeURIComponent(key)}/reactivate${query}`,
4056
+ { method: 'POST', body: {}, forbiddenAsApiError: true },
4057
+ );
4058
+ }
4059
+
3713
4060
  /**
3714
4061
  * Check API connectivity and server health.
3715
4062
  *
@@ -67,6 +67,20 @@ export type {
67
67
  BillingSubscriptionCancelResult,
68
68
  BillingSubscriptionStatus,
69
69
  BillingTopUpResult,
70
+ MonitorsNamespace,
71
+ MonitorsAccessStatus,
72
+ MonitorsAvailableOptions,
73
+ MonitorsAvailableResult,
74
+ MonitorsListOptions,
75
+ MonitorsListResult,
76
+ MonitorListEntry,
77
+ MonitorCheckResult,
78
+ MonitorDeployResult,
79
+ MonitorDetail,
80
+ MonitorDependents,
81
+ MonitorUpdateResult,
82
+ MonitorDeleteResult,
83
+ MonitorReactivateResult,
70
84
  IngestionStorageRepairResult,
71
85
  PlayStatus,
72
86
  PlaySheetRow,
@@ -78,6 +92,14 @@ export type {
78
92
  RunsTailOptions,
79
93
  ToolExecution,
80
94
  } from './client.js';
95
+
96
+ // ——— Monitors framework ———
97
+ export { defineMonitor } from './monitors.js';
98
+ export type {
99
+ MonitorDefinition,
100
+ MonitorPayload,
101
+ MonitorControls,
102
+ } from './monitors.js';
81
103
  export { SDK_API_CONTRACT, SDK_VERSION } from './version.js';
82
104
  export {
83
105
  DEEPLINE_TOOL_CATEGORIES,
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Monitor authoring and typing for the Deepline SDK.
3
+ *
4
+ * A **monitor** is a Deepline-native signal feed. A deployed monitor writes
5
+ * events into a Customer DB table (one row per finding). Plays react to those
6
+ * rows through `sqlListeners` bindings (see {@link definePlay}). This module is
7
+ * the code-first authoring surface for monitors — the same product model the
8
+ * `deepline monitors` CLI drives, expressed as typed SDK code.
9
+ *
10
+ * Use {@link defineMonitor} to author a typed monitor definition, then deploy or
11
+ * validate it with the monitors namespace:
12
+ *
13
+ * ```typescript
14
+ * import { DeeplineClient, defineMonitor } from 'deepline';
15
+ *
16
+ * const monitor = defineMonitor({
17
+ * key: 'stripe-job-openings',
18
+ * tool: 'deepline_native.company_radar',
19
+ * name: 'Stripe job openings',
20
+ * payload: { domain: 'stripe.com', radar_type: 'company_job_openings' },
21
+ * });
22
+ *
23
+ * const client = new DeeplineClient();
24
+ * const plan = await client.monitors.check(monitor); // validate, no spend
25
+ * await client.monitors.deploy(monitor); // deploy for real
26
+ * ```
27
+ *
28
+ * @module
29
+ */
30
+
31
+ /**
32
+ * A monitor definition: the exact object accepted by
33
+ * `client.monitors.check(...)` and `client.monitors.deploy(...)` and by the
34
+ * `/api/v2/monitors/{check,deploy}` endpoints the CLI uses.
35
+ *
36
+ * @typeParam TPayload - Provider-specific monitor payload shape (defaults to a
37
+ * loose record). Pass a concrete shape to `defineMonitor<TPayload>(...)` for
38
+ * compile-time checking of the payload fields.
39
+ */
40
+ export type MonitorDefinition<
41
+ TPayload extends MonitorPayload = MonitorPayload,
42
+ > = {
43
+ /** Stable public key for this monitor, unique within the workspace. */
44
+ key: string;
45
+ /** Monitor tool id, e.g. `"deepline_native.company_radar"`. */
46
+ tool: string;
47
+ /** Optional human-readable name shown in listings and detail views. */
48
+ name?: string;
49
+ /** Provider-specific monitor payload (e.g. domain + radar_type). */
50
+ payload: TPayload;
51
+ /** Optional Deepline lifecycle metadata (deploy/reuse controls). */
52
+ controls?: MonitorControls;
53
+ };
54
+
55
+ /** Provider-specific monitor payload. Keys and value types depend on the tool. */
56
+ export type MonitorPayload = Record<string, unknown>;
57
+
58
+ /**
59
+ * Deepline lifecycle metadata attached to a monitor definition. These are
60
+ * Deepline-side deploy/reuse controls, not provider payload fields. The set is
61
+ * intentionally open (server-owned) so newer controls do not require an SDK
62
+ * bump; known controls are typed for discoverability.
63
+ */
64
+ export type MonitorControls = {
65
+ [key: string]: unknown;
66
+ };
67
+
68
+ /**
69
+ * Define a typed monitor definition.
70
+ *
71
+ * Mirrors {@link definePlay} as the code-first authoring entrypoint: it gives
72
+ * compile-time type safety on the definition object and returns it verbatim for
73
+ * passing to `client.monitors.check(...)` / `client.monitors.deploy(...)`. It
74
+ * performs the same lightweight local invariants the server enforces (non-empty
75
+ * `key` and `tool`, object `payload`) so authoring mistakes fail before a
76
+ * network round-trip.
77
+ *
78
+ * @typeParam TPayload - Provider payload shape.
79
+ * @param definition - The monitor definition.
80
+ * @returns The validated definition object.
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const monitor = defineMonitor({
85
+ * key: 'job-openings',
86
+ * tool: 'deepline_native.company_radar',
87
+ * payload: { domain: 'stripe.com', radar_type: 'company_job_openings' },
88
+ * });
89
+ * ```
90
+ */
91
+ export function defineMonitor<TPayload extends MonitorPayload = MonitorPayload>(
92
+ definition: MonitorDefinition<TPayload>,
93
+ ): MonitorDefinition<TPayload> {
94
+ if (!definition || typeof definition !== 'object') {
95
+ throw new Error('defineMonitor(definition) requires a definition object.');
96
+ }
97
+ const key = typeof definition.key === 'string' ? definition.key.trim() : '';
98
+ if (!key) {
99
+ throw new Error('defineMonitor(definition) requires a non-empty "key".');
100
+ }
101
+ const tool =
102
+ typeof definition.tool === 'string' ? definition.tool.trim() : '';
103
+ if (!tool) {
104
+ throw new Error(
105
+ 'defineMonitor(definition) requires a non-empty monitor tool id in "tool" ' +
106
+ '(e.g. "deepline_native.company_radar").',
107
+ );
108
+ }
109
+ if (
110
+ !definition.payload ||
111
+ typeof definition.payload !== 'object' ||
112
+ Array.isArray(definition.payload)
113
+ ) {
114
+ throw new Error(
115
+ 'defineMonitor(definition) requires "payload" to be a JSON object.',
116
+ );
117
+ }
118
+ if (
119
+ definition.name !== undefined &&
120
+ typeof definition.name !== 'string'
121
+ ) {
122
+ throw new Error('defineMonitor(definition) "name" must be a string.');
123
+ }
124
+ if (
125
+ definition.controls !== undefined &&
126
+ (typeof definition.controls !== 'object' ||
127
+ Array.isArray(definition.controls))
128
+ ) {
129
+ throw new Error(
130
+ 'defineMonitor(definition) "controls" must be a JSON object.',
131
+ );
132
+ }
133
+ return definition;
134
+ }
@@ -155,7 +155,7 @@ export const SDK_RELEASE = {
155
155
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
156
156
  // 0.1.254 removes the internal operations tree from the published SDK CLI.
157
157
  // Operators use the checkout-local deepline-admin binary instead.
158
- version: '0.1.271',
158
+ version: '0.1.272',
159
159
  contracts: {
160
160
  api: {
161
161
  name: 'sdk-http-api',
@@ -44,9 +44,23 @@ export function ctxRunPlayInlineOnlyMessage(
44
44
  explicit_timeout: 'it sets an explicit timeout',
45
45
  explicit_child_workflow: 'it requests execution: "child-workflow"',
46
46
  };
47
+ const nextStep: Record<CtxRunPlayInlineOnlyReason, string> = {
48
+ dataset_child:
49
+ 'Move the per-record enrichment into a scalar core play, call that core with ctx.runPlay(), and keep ctx.dataset()/ctx.csv() in the router. If this child must process a batch, trigger it as a top-level play and let it own the follow-up actions.',
50
+ event_wait_child:
51
+ 'Trigger it as a top-level play. Event waits need their own run lifecycle and cannot be awaited through ctx.runPlay().',
52
+ suspending_child:
53
+ 'Trigger it as a top-level play. Work that can suspend needs its own run lifecycle and cannot be awaited through ctx.runPlay().',
54
+ missing_static_contract:
55
+ 'Use a published play with a resolvable static contract, or move lifecycle-owning work into a top-level play.',
56
+ explicit_timeout:
57
+ 'Remove the child timeout if the child is a scalar lookup, or trigger the child as a top-level play when it needs independent timing.',
58
+ explicit_child_workflow:
59
+ 'Remove execution: "child-workflow" for a scalar lookup, or trigger the child as a top-level play when it needs its own lifecycle.',
60
+ };
47
61
  return (
48
- `${CTX_RUN_PLAY_INLINE_ONLY}: ctx.runPlay("${childPlayName}") only composes a resolved scalar child; ` +
49
- `${detail[reason]}. Move that work to a top-level play and compose only the scalar result.`
62
+ `${CTX_RUN_PLAY_INLINE_ONLY}: ctx.runPlay("${childPlayName}") can only call a scalar, per-record play; ` +
63
+ `${detail[reason]}. ${nextStep[reason]}`
50
64
  );
51
65
  }
52
66