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.
- package/dist/bundling-sources/sdk/src/client.ts +532 -2
- package/dist/bundling-sources/sdk/src/config.ts +77 -0
- package/dist/bundling-sources/sdk/src/http.ts +76 -30
- package/dist/bundling-sources/sdk/src/index.ts +6 -0
- package/dist/bundling-sources/sdk/src/play.ts +15 -1
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/types.ts +83 -4
- package/dist/bundling-sources/shared_libs/monitors/deactivation-reasons.ts +16 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +263 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +9 -1
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-state.ts +122 -0
- package/dist/bundling-sources/shared_libs/play-runtime/activity-observation.ts +6 -11
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +90 -12
- package/dist/bundling-sources/shared_libs/play-runtime/async-play-invocation.ts +78 -0
- package/dist/bundling-sources/shared_libs/play-runtime/checkpoint-boundaries.ts +70 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +407 -53
- package/dist/bundling-sources/shared_libs/play-runtime/coordinator-headers.ts +1 -32
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +57 -0
- package/dist/bundling-sources/shared_libs/play-runtime/default-batch-strategies.ts +20 -1
- package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-protocol.ts +7 -1
- package/dist/bundling-sources/shared_libs/play-runtime/lane-retirement-gate.ts +189 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +28 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +23 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +18 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-start-admission.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +10 -8
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +20 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +24 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +30 -1
- package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +6 -3
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +152 -17
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +19 -4
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +6 -14
- package/dist/bundling-sources/shared_libs/plays/core.ts +11 -6
- package/dist/bundling-sources/shared_libs/plays/docflow-binding-owner.ts +16 -11
- package/dist/bundling-sources/shared_libs/plays/resolve-static-pipeline.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/slug.ts +115 -0
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +44 -14
- package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +877 -0
- package/dist/bundling-sources/shared_libs/product-notifications/configuration-draft.ts +456 -0
- package/dist/bundling-sources/shared_libs/product-notifications/configuration.ts +560 -0
- package/dist/bundling-sources/shared_libs/product-notifications/content.ts +28 -0
- package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +59 -0
- package/dist/bundling-sources/shared_libs/product-notifications/email.ts +246 -0
- package/dist/bundling-sources/shared_libs/product-notifications/evaluation.ts +280 -0
- package/dist/bundling-sources/shared_libs/product-notifications/events.ts +433 -0
- package/dist/bundling-sources/shared_libs/product-notifications/legacy-slack.ts +80 -0
- package/dist/bundling-sources/shared_libs/product-notifications/workspace.ts +38 -0
- package/dist/bundling-sources/shared_libs/security/internal-actor.ts +50 -0
- package/dist/cli/index.js +1065 -190
- package/dist/cli/index.mjs +1052 -177
- package/dist/{compiler-manifest-Dbov16F7.d.mts → compiler-manifest-DsLbQaew.d.mts} +132 -8
- package/dist/{compiler-manifest-Dbov16F7.d.ts → compiler-manifest-DsLbQaew.d.ts} +132 -8
- package/dist/index.d.mts +833 -11
- package/dist/index.d.ts +833 -11
- package/dist/index.js +467 -38
- package/dist/index.mjs +467 -38
- package/dist/install-integrity.json +20 -2
- package/dist/plays/bundle-play-file.d.mts +8 -3
- package/dist/plays/bundle-play-file.d.ts +8 -3
- package/dist/plays/bundle-play-file.mjs +100 -20
- package/package.json +1 -1
|
@@ -37,6 +37,13 @@ import { resolveConfig } from './config.js';
|
|
|
37
37
|
import { DeeplineError } from './errors.js';
|
|
38
38
|
import { HttpClient } from './http.js';
|
|
39
39
|
import { PRODUCT_NOTIFICATION_SLACK_OAUTH_SCOPES } from '../../shared_libs/product-notifications/contract.js';
|
|
40
|
+
import { normalizeProductNotificationEmail } from '../../shared_libs/product-notifications/configuration.js';
|
|
41
|
+
import {
|
|
42
|
+
type ProductNotificationConfigurationChange,
|
|
43
|
+
type EffectiveProductNotificationDestination,
|
|
44
|
+
type EffectiveProductNotificationGroup,
|
|
45
|
+
type ProductNotificationGroupDraft,
|
|
46
|
+
} from '../../shared_libs/product-notifications/configuration.js';
|
|
40
47
|
import {
|
|
41
48
|
STREAM_HEALTHY_CONNECTION_MS,
|
|
42
49
|
isTransientPlayStreamError,
|
|
@@ -60,6 +67,7 @@ import type {
|
|
|
60
67
|
PlayLiveEvent,
|
|
61
68
|
PlayListItem,
|
|
62
69
|
ProductNotificationSettings,
|
|
70
|
+
ProductNotificationConfigurationResponse,
|
|
63
71
|
PlayDescription,
|
|
64
72
|
StopPlayRunResult,
|
|
65
73
|
StopAllPlayRunsResult,
|
|
@@ -124,6 +132,76 @@ export type UpdateNotificationInput =
|
|
|
124
132
|
| { enabled: boolean }
|
|
125
133
|
| ({ name: string; eventTypes: string[] } & SlackNotificationTarget);
|
|
126
134
|
|
|
135
|
+
export type CreateNotificationGroupInput = {
|
|
136
|
+
name: string;
|
|
137
|
+
targets: string[];
|
|
138
|
+
legacyEvents?: string[];
|
|
139
|
+
dryRun?: boolean;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export type CreateNotificationGroupResult =
|
|
143
|
+
| {
|
|
144
|
+
mode: 'dry_run';
|
|
145
|
+
compatibilityMode: boolean;
|
|
146
|
+
name: string;
|
|
147
|
+
targets: string[];
|
|
148
|
+
legacyEvents: string[];
|
|
149
|
+
}
|
|
150
|
+
| { mode: 'legacy'; notification: unknown }
|
|
151
|
+
| {
|
|
152
|
+
mode: 'native';
|
|
153
|
+
groupKey: string;
|
|
154
|
+
targets: string[];
|
|
155
|
+
group: EffectiveProductNotificationGroup;
|
|
156
|
+
destinations: EffectiveProductNotificationDestination[];
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
type ParsedNotificationGroupTarget =
|
|
160
|
+
| { kind: 'email'; email: string }
|
|
161
|
+
| { kind: 'slack'; channel: string }
|
|
162
|
+
| { kind: 'slack'; memberId: string };
|
|
163
|
+
|
|
164
|
+
const MAX_NOTIFICATION_GROUP_CREATE_TARGETS = 50;
|
|
165
|
+
|
|
166
|
+
function parseNotificationGroupTarget(
|
|
167
|
+
value: string,
|
|
168
|
+
): ParsedNotificationGroupTarget {
|
|
169
|
+
const normalized = value.trim();
|
|
170
|
+
if (normalized.toLowerCase().startsWith('email:')) {
|
|
171
|
+
const email = normalizeProductNotificationEmail(normalized.slice(6));
|
|
172
|
+
if (!email) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Invalid email notification target "${value}". Use --to email:name@example.com.`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return { kind: 'email', email };
|
|
178
|
+
}
|
|
179
|
+
const slack = /^slack:(.+)$/i.exec(normalized)?.[1]?.trim();
|
|
180
|
+
if (!slack) {
|
|
181
|
+
const provider = normalized.split(':', 1)[0] || normalized;
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Unsupported notification destination "${provider}". Use email: or slack:.`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const member = /^member:([UW][A-Z0-9]{8,})$/i.exec(slack);
|
|
187
|
+
if (member?.[1]) {
|
|
188
|
+
return { kind: 'slack', memberId: member[1].toUpperCase() };
|
|
189
|
+
}
|
|
190
|
+
if (slack.toLowerCase().startsWith('member:')) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
'Slack member IDs must look like slack:member:U0123456789.',
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (!slack.replace(/^#/, '').trim()) {
|
|
196
|
+
throw new Error('Use --to slack:#channel.');
|
|
197
|
+
}
|
|
198
|
+
return { kind: 'slack', channel: slack };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function notificationObjectKey(kind: 'group' | 'destination') {
|
|
202
|
+
return `${kind}.${globalThis.crypto.randomUUID()}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
127
205
|
const TERMINAL_PLAY_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
128
206
|
const INCLUDE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
|
|
129
207
|
const EXECUTE_RESPONSE_CONTRACT_HEADER = 'x-deepline-execute-response-contract';
|
|
@@ -645,6 +723,14 @@ export type PlaySecretMetadata = {
|
|
|
645
723
|
lastUsedAt?: number;
|
|
646
724
|
};
|
|
647
725
|
|
|
726
|
+
export type PlaySecretCheck = {
|
|
727
|
+
ok: boolean;
|
|
728
|
+
name: string;
|
|
729
|
+
playName: string | null;
|
|
730
|
+
reason: 'active' | 'missing' | 'disabled' | 'empty' | 'invalid_version';
|
|
731
|
+
secret: PlaySecretMetadata | null;
|
|
732
|
+
};
|
|
733
|
+
|
|
648
734
|
/**
|
|
649
735
|
* Public runs namespace exposed as `client.runs`.
|
|
650
736
|
*
|
|
@@ -930,6 +1016,62 @@ export type MonitorFleetWaitOptions = {
|
|
|
930
1016
|
until?: 'converged' | 'deactivated';
|
|
931
1017
|
};
|
|
932
1018
|
|
|
1019
|
+
/** A durable monitor batch can name a small key set, a query target, or one Fleet. */
|
|
1020
|
+
export type MonitorBatchScope =
|
|
1021
|
+
| { kind: 'keys'; monitorKeys: string[] }
|
|
1022
|
+
| {
|
|
1023
|
+
kind: 'target';
|
|
1024
|
+
target: 'all' | 'all-active' | 'billing-suspended' | 'needs-repair';
|
|
1025
|
+
}
|
|
1026
|
+
| { kind: 'fleet'; fleetId: string };
|
|
1027
|
+
|
|
1028
|
+
export type MonitorBatchExistingSubmitOptions = {
|
|
1029
|
+
kind: 'audit' | 'deactivate' | 'reactivate' | 'repair';
|
|
1030
|
+
scope: MonitorBatchScope;
|
|
1031
|
+
dryRun?: boolean;
|
|
1032
|
+
/** Required for deactivate; it is the declared reason shown by health/UI. */
|
|
1033
|
+
deactivationReason?: string;
|
|
1034
|
+
/** Stable retry key. Reusing it joins the original durable operation. */
|
|
1035
|
+
idempotencyKey: string;
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
/** Complete definitions, one JSON object per line; uploaded as bounded shards. */
|
|
1039
|
+
export type MonitorBatchDeploySubmitOptions = {
|
|
1040
|
+
kind: 'deploy';
|
|
1041
|
+
manifestNdjson: string;
|
|
1042
|
+
dryRun?: boolean;
|
|
1043
|
+
/** Stable retry key. Reusing it joins the same content-addressed manifest. */
|
|
1044
|
+
idempotencyKey: string;
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
export type MonitorBatchSubmitOptions =
|
|
1048
|
+
| MonitorBatchExistingSubmitOptions
|
|
1049
|
+
| MonitorBatchDeploySubmitOptions;
|
|
1050
|
+
|
|
1051
|
+
export type MonitorBatchOperationResult = {
|
|
1052
|
+
ok?: boolean;
|
|
1053
|
+
rejoined?: boolean;
|
|
1054
|
+
operation: {
|
|
1055
|
+
runId: string;
|
|
1056
|
+
kind: 'audit' | 'deactivate' | 'reactivate' | 'deploy' | 'repair';
|
|
1057
|
+
state: 'running' | 'retry_scheduled' | 'completed' | 'failed';
|
|
1058
|
+
dryRun: boolean;
|
|
1059
|
+
scope: unknown;
|
|
1060
|
+
submittedAt: number;
|
|
1061
|
+
updatedAt: number;
|
|
1062
|
+
deactivationReason?: string;
|
|
1063
|
+
nextAttemptAt?: number;
|
|
1064
|
+
completedAt?: number;
|
|
1065
|
+
summary?: Record<string, unknown>;
|
|
1066
|
+
lastError?: string;
|
|
1067
|
+
};
|
|
1068
|
+
results?: {
|
|
1069
|
+
page: Array<Record<string, unknown>>;
|
|
1070
|
+
isDone: boolean;
|
|
1071
|
+
continueCursor: string;
|
|
1072
|
+
};
|
|
1073
|
+
};
|
|
1074
|
+
|
|
933
1075
|
export type MonitorFleetsNamespace = {
|
|
934
1076
|
/**
|
|
935
1077
|
* Create, update, or re-plan a fleet. Pass a definition to admit new source
|
|
@@ -963,6 +1105,16 @@ export type MonitorFleetsNamespace = {
|
|
|
963
1105
|
) => Promise<MonitorFleetResult>;
|
|
964
1106
|
};
|
|
965
1107
|
|
|
1108
|
+
export type MonitorBatchNamespace = {
|
|
1109
|
+
submit: (
|
|
1110
|
+
options: MonitorBatchSubmitOptions,
|
|
1111
|
+
) => Promise<MonitorBatchOperationResult>;
|
|
1112
|
+
get: (
|
|
1113
|
+
operationId: string,
|
|
1114
|
+
options?: { cursor?: string | null },
|
|
1115
|
+
) => Promise<MonitorBatchOperationResult>;
|
|
1116
|
+
};
|
|
1117
|
+
|
|
966
1118
|
/**
|
|
967
1119
|
* Delivery and convergence health for a scope, from
|
|
968
1120
|
* `GET /api/v2/monitors/health`.
|
|
@@ -977,6 +1129,16 @@ export type MonitorsHealth = {
|
|
|
977
1129
|
scanned: number;
|
|
978
1130
|
/** True when the scope was larger than one bounded read; counts are partial. */
|
|
979
1131
|
truncated: boolean;
|
|
1132
|
+
/**
|
|
1133
|
+
* A durable whole-scope snapshot is rebuilding. Counts remain intentionally
|
|
1134
|
+
* partial until it completes; older servers omit this additive field.
|
|
1135
|
+
*/
|
|
1136
|
+
refresh?: {
|
|
1137
|
+
state: 'running' | 'completed';
|
|
1138
|
+
started_at: number;
|
|
1139
|
+
updated_at: number;
|
|
1140
|
+
completed_at: number | null;
|
|
1141
|
+
};
|
|
980
1142
|
desired: { active: number; inactive: number; unset: number };
|
|
981
1143
|
observed: {
|
|
982
1144
|
active: number;
|
|
@@ -1003,6 +1165,15 @@ export type MonitorsHealth = {
|
|
|
1003
1165
|
observed_at: number | null;
|
|
1004
1166
|
provider_resource_id: string | null;
|
|
1005
1167
|
}>;
|
|
1168
|
+
/** Bounded current durable monitor work, if the server supports it. */
|
|
1169
|
+
batch_operations?: Array<{
|
|
1170
|
+
run_id: string;
|
|
1171
|
+
kind: 'audit' | 'deactivate' | 'reactivate' | 'repair' | 'deploy';
|
|
1172
|
+
state: 'running' | 'retry_scheduled';
|
|
1173
|
+
updated_at: number;
|
|
1174
|
+
next_attempt_at?: number;
|
|
1175
|
+
summary?: Record<string, unknown>;
|
|
1176
|
+
}>;
|
|
1006
1177
|
};
|
|
1007
1178
|
|
|
1008
1179
|
/** One bounded audit pass, from `POST /api/v2/monitors/audit`. */
|
|
@@ -1137,6 +1308,8 @@ export type MonitorsNamespace = {
|
|
|
1137
1308
|
}) => Promise<MonitorsRepairResult>;
|
|
1138
1309
|
/** Delivery and convergence health for the workspace or one fleet. */
|
|
1139
1310
|
health: (options?: { fleetId?: string }) => Promise<MonitorsHealth>;
|
|
1311
|
+
/** Durable bulk audit/deactivation/reactivation/repair with retained progress. */
|
|
1312
|
+
batch: MonitorBatchNamespace;
|
|
1140
1313
|
/** Define, reconcile, and control table-backed monitor fleets. */
|
|
1141
1314
|
fleets: MonitorFleetsNamespace;
|
|
1142
1315
|
};
|
|
@@ -1293,7 +1466,12 @@ export type TargetBillingMutationResult = {
|
|
|
1293
1466
|
export type TargetBillingPlanTransitionOptions =
|
|
1294
1467
|
| {
|
|
1295
1468
|
action: 'start_or_change';
|
|
1296
|
-
targetPlanSku:
|
|
1469
|
+
targetPlanSku:
|
|
1470
|
+
| 'payg-v1'
|
|
1471
|
+
| 'builder-v1'
|
|
1472
|
+
| 'growth-v1'
|
|
1473
|
+
| 'growth-canary-v1'
|
|
1474
|
+
| 'team-v1';
|
|
1297
1475
|
idempotencyKey: string;
|
|
1298
1476
|
}
|
|
1299
1477
|
| {
|
|
@@ -1379,7 +1557,12 @@ export type WorkspaceCreateResult = {
|
|
|
1379
1557
|
org_name: string;
|
|
1380
1558
|
org_slug: string;
|
|
1381
1559
|
plan_sku: 'payg-v1';
|
|
1382
|
-
|
|
1560
|
+
/**
|
|
1561
|
+
* The PAYG rate policy the workspace was provisioned onto, taken from the
|
|
1562
|
+
* catalog revision its operation pinned: v4 bound payg-rates-v2, v5 binds
|
|
1563
|
+
* the flat payg-rates-v3.
|
|
1564
|
+
*/
|
|
1565
|
+
rate_policy_ref: 'payg-rates-v2' | 'payg-rates-v3';
|
|
1383
1566
|
operation: {
|
|
1384
1567
|
id: string;
|
|
1385
1568
|
state: 'completed';
|
|
@@ -2059,6 +2242,11 @@ export class DeeplineClient {
|
|
|
2059
2242
|
audit: (options) => this.auditMonitors(options),
|
|
2060
2243
|
repair: (options) => this.repairMonitors(options),
|
|
2061
2244
|
health: (options) => this.readMonitorsHealth(options),
|
|
2245
|
+
batch: {
|
|
2246
|
+
submit: (options) => this.submitMonitorBatch(options),
|
|
2247
|
+
get: (operationId, options) =>
|
|
2248
|
+
this.getMonitorBatch(operationId, options),
|
|
2249
|
+
},
|
|
2062
2250
|
fleets: {
|
|
2063
2251
|
sync: (definitionOrId, options) =>
|
|
2064
2252
|
this.syncMonitorFleet(definitionOrId, options),
|
|
@@ -2271,6 +2459,20 @@ export class DeeplineClient {
|
|
|
2271
2459
|
);
|
|
2272
2460
|
}
|
|
2273
2461
|
|
|
2462
|
+
/** Check effective scope and active-version health without reading the value.
|
|
2463
|
+
* Without playName, checks the organization-scoped secret only. With a Play
|
|
2464
|
+
* name, an active Play secret takes precedence; a deleted/disabled Play
|
|
2465
|
+
* secret falls back to the organization-scoped secret.
|
|
2466
|
+
*/
|
|
2467
|
+
async inspectSecret(
|
|
2468
|
+
name: string,
|
|
2469
|
+
options: { playName?: string } = {},
|
|
2470
|
+
): Promise<PlaySecretCheck> {
|
|
2471
|
+
const params = new URLSearchParams({ name: name.trim().toUpperCase() });
|
|
2472
|
+
if (options.playName !== undefined) params.set('play', options.playName);
|
|
2473
|
+
return this.http.get(`/api/v2/secrets?${params.toString()}`);
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2274
2476
|
// ——————————————————————————————————————————————————————————
|
|
2275
2477
|
// Tools
|
|
2276
2478
|
// ——————————————————————————————————————————————————————————
|
|
@@ -2376,6 +2578,10 @@ export class DeeplineClient {
|
|
|
2376
2578
|
`/api/v2/integrations/${encodeURIComponent(toolId)}/get`,
|
|
2377
2579
|
{
|
|
2378
2580
|
method: 'GET',
|
|
2581
|
+
// Tool metadata is a read-only catalog lookup. Keep the metadata
|
|
2582
|
+
// header while opting into the same transient edge-response retries as
|
|
2583
|
+
// other GET catalog methods such as searchTools().
|
|
2584
|
+
retryApiErrors: true,
|
|
2379
2585
|
headers: {
|
|
2380
2586
|
'x-deepline-tool-meta-only': '1',
|
|
2381
2587
|
},
|
|
@@ -4342,6 +4548,244 @@ export class DeeplineClient {
|
|
|
4342
4548
|
return this.http.get('/api/v2/notifications');
|
|
4343
4549
|
}
|
|
4344
4550
|
|
|
4551
|
+
/** Read the group-based product-notification configuration and rule catalog. */
|
|
4552
|
+
async getNotificationConfiguration(): Promise<ProductNotificationConfigurationResponse> {
|
|
4553
|
+
return this.http.get('/api/v2/notifications/configuration');
|
|
4554
|
+
}
|
|
4555
|
+
|
|
4556
|
+
/** Atomically apply one retry-safe desired configuration change. */
|
|
4557
|
+
async changeNotificationConfiguration(
|
|
4558
|
+
expectedWorkspaceVersion: number,
|
|
4559
|
+
change: ProductNotificationConfigurationChange,
|
|
4560
|
+
): Promise<Pick<ProductNotificationConfigurationResponse, 'configuration'>> {
|
|
4561
|
+
return this.http.put('/api/v2/notifications/configuration', {
|
|
4562
|
+
expectedWorkspaceVersion,
|
|
4563
|
+
change,
|
|
4564
|
+
});
|
|
4565
|
+
}
|
|
4566
|
+
|
|
4567
|
+
/** Create one notification group and its destinations as a retry-safe unit. */
|
|
4568
|
+
async createNotificationGroup(
|
|
4569
|
+
input: CreateNotificationGroupInput,
|
|
4570
|
+
): Promise<CreateNotificationGroupResult> {
|
|
4571
|
+
const name = input.name.trim().replace(/\s+/g, ' ');
|
|
4572
|
+
if (!name) throw new Error('Notification group name is required.');
|
|
4573
|
+
if (
|
|
4574
|
+
input.targets.length < 1 ||
|
|
4575
|
+
input.targets.length > MAX_NOTIFICATION_GROUP_CREATE_TARGETS
|
|
4576
|
+
) {
|
|
4577
|
+
throw new Error(
|
|
4578
|
+
`Choose between 1 and ${MAX_NOTIFICATION_GROUP_CREATE_TARGETS} notification targets.`,
|
|
4579
|
+
);
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
const targets = input.targets.map(parseNotificationGroupTarget);
|
|
4583
|
+
const legacyEvents = input.legacyEvents ?? [];
|
|
4584
|
+
const legacyTarget =
|
|
4585
|
+
targets.length === 1 &&
|
|
4586
|
+
targets[0]?.kind === 'slack' &&
|
|
4587
|
+
legacyEvents.length > 0
|
|
4588
|
+
? targets[0]
|
|
4589
|
+
: null;
|
|
4590
|
+
if (legacyEvents.length > 0 && !legacyTarget) {
|
|
4591
|
+
throw new Error(
|
|
4592
|
+
'--for is supported only by the legacy one-Slack-target form. Create the group first, then add rules in notification configuration.',
|
|
4593
|
+
);
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
if (!legacyTarget && name.toLowerCase() === 'default') {
|
|
4597
|
+
throw new Error(
|
|
4598
|
+
'Default is reserved for the workspace default notification group.',
|
|
4599
|
+
);
|
|
4600
|
+
}
|
|
4601
|
+
|
|
4602
|
+
const normalizedTargets = targets.map((target) =>
|
|
4603
|
+
target.kind === 'email'
|
|
4604
|
+
? `email:${target.email}`
|
|
4605
|
+
: 'memberId' in target
|
|
4606
|
+
? `slack:member:${target.memberId}`
|
|
4607
|
+
: `slack:${target.channel}`,
|
|
4608
|
+
);
|
|
4609
|
+
if (input.dryRun) {
|
|
4610
|
+
return {
|
|
4611
|
+
mode: 'dry_run' as const,
|
|
4612
|
+
compatibilityMode: legacyTarget !== null,
|
|
4613
|
+
name: legacyTarget ? input.name : name,
|
|
4614
|
+
targets: normalizedTargets,
|
|
4615
|
+
legacyEvents,
|
|
4616
|
+
};
|
|
4617
|
+
}
|
|
4618
|
+
|
|
4619
|
+
if (legacyTarget) {
|
|
4620
|
+
const notification = await this.createNotification({
|
|
4621
|
+
name: input.name,
|
|
4622
|
+
provider: 'slack',
|
|
4623
|
+
...('channel' in legacyTarget
|
|
4624
|
+
? { channel: legacyTarget.channel }
|
|
4625
|
+
: { memberId: legacyTarget.memberId }),
|
|
4626
|
+
eventTypes: legacyEvents,
|
|
4627
|
+
});
|
|
4628
|
+
return { mode: 'legacy' as const, notification };
|
|
4629
|
+
}
|
|
4630
|
+
|
|
4631
|
+
const current = await this.getNotificationConfiguration();
|
|
4632
|
+
const existingGroup = current.configuration.groups.find(
|
|
4633
|
+
(candidate) => candidate.name.toLowerCase() === name.toLowerCase(),
|
|
4634
|
+
);
|
|
4635
|
+
const groupKey = existingGroup?.groupKey ?? notificationObjectKey('group');
|
|
4636
|
+
const group: EffectiveProductNotificationGroup = existingGroup ?? {
|
|
4637
|
+
groupKey,
|
|
4638
|
+
name,
|
|
4639
|
+
enabled: true,
|
|
4640
|
+
deliveryTiming: { mode: 'immediate' },
|
|
4641
|
+
source: 'workspace',
|
|
4642
|
+
configMode: 'custom',
|
|
4643
|
+
version: 0,
|
|
4644
|
+
};
|
|
4645
|
+
const destinations = current.configuration.destinations.filter(
|
|
4646
|
+
(destination) => destination.groupKey === groupKey,
|
|
4647
|
+
);
|
|
4648
|
+
|
|
4649
|
+
const emails = [
|
|
4650
|
+
...new Set(
|
|
4651
|
+
targets.flatMap((target) =>
|
|
4652
|
+
target.kind === 'email' ? [target.email] : [],
|
|
4653
|
+
),
|
|
4654
|
+
),
|
|
4655
|
+
].sort();
|
|
4656
|
+
const existingEmails = new Set(
|
|
4657
|
+
destinations.flatMap((destination) =>
|
|
4658
|
+
destination.settings.kind === 'email_addresses'
|
|
4659
|
+
? destination.settings.to
|
|
4660
|
+
: [],
|
|
4661
|
+
),
|
|
4662
|
+
);
|
|
4663
|
+
const newEmails = emails.filter((email) => !existingEmails.has(email));
|
|
4664
|
+
if (newEmails.length > 0) {
|
|
4665
|
+
destinations.push({
|
|
4666
|
+
destinationKey: notificationObjectKey('destination'),
|
|
4667
|
+
groupKey,
|
|
4668
|
+
name: 'Email recipients',
|
|
4669
|
+
enabled: true,
|
|
4670
|
+
source: 'workspace',
|
|
4671
|
+
configMode: 'custom',
|
|
4672
|
+
version: 0,
|
|
4673
|
+
status: 'active',
|
|
4674
|
+
settings: { kind: 'email_addresses', to: newEmails },
|
|
4675
|
+
});
|
|
4676
|
+
}
|
|
4677
|
+
|
|
4678
|
+
const slackTargets = new Map<
|
|
4679
|
+
string,
|
|
4680
|
+
Extract<ParsedNotificationGroupTarget, { kind: 'slack' }>
|
|
4681
|
+
>();
|
|
4682
|
+
for (const target of targets) {
|
|
4683
|
+
if (target.kind !== 'slack') continue;
|
|
4684
|
+
const targetKey =
|
|
4685
|
+
'memberId' in target
|
|
4686
|
+
? `member:${target.memberId}`
|
|
4687
|
+
: `channel:${target.channel.replace(/^#/, '').toLowerCase()}`;
|
|
4688
|
+
slackTargets.set(targetKey, target);
|
|
4689
|
+
}
|
|
4690
|
+
const connectionId = current.slackConnection.connectionId;
|
|
4691
|
+
if (slackTargets.size > 0 && !connectionId) {
|
|
4692
|
+
throw new Error(
|
|
4693
|
+
'Slack is not connected to this workspace. Connect it in Dashboard → Integrations, then retry.',
|
|
4694
|
+
);
|
|
4695
|
+
}
|
|
4696
|
+
const resolvedSlackTargets = await Promise.all(
|
|
4697
|
+
[...slackTargets.values()].map(async (target) => {
|
|
4698
|
+
if ('memberId' in target) {
|
|
4699
|
+
return {
|
|
4700
|
+
targetKind: 'member' as const,
|
|
4701
|
+
targetId: target.memberId,
|
|
4702
|
+
targetName: target.memberId,
|
|
4703
|
+
};
|
|
4704
|
+
}
|
|
4705
|
+
const channel = target.channel;
|
|
4706
|
+
const requested = channel.replace(/^#/, '');
|
|
4707
|
+
const response = await this.listNotificationChannels(channel);
|
|
4708
|
+
const matches = response.channels.filter(
|
|
4709
|
+
(candidate) =>
|
|
4710
|
+
candidate.id === requested ||
|
|
4711
|
+
candidate.name.toLowerCase() === requested.toLowerCase(),
|
|
4712
|
+
);
|
|
4713
|
+
if (matches.length !== 1) {
|
|
4714
|
+
throw new Error(
|
|
4715
|
+
matches.length === 0
|
|
4716
|
+
? `Slack channel ${channel} was not found or is not visible to the Deepline app.`
|
|
4717
|
+
: `Slack channel ${channel} is ambiguous. Use its channel ID.`,
|
|
4718
|
+
);
|
|
4719
|
+
}
|
|
4720
|
+
return {
|
|
4721
|
+
targetKind: 'channel' as const,
|
|
4722
|
+
targetId: matches[0]!.id,
|
|
4723
|
+
targetName: matches[0]!.name,
|
|
4724
|
+
};
|
|
4725
|
+
}),
|
|
4726
|
+
);
|
|
4727
|
+
const uniqueSlackTargets = new Map(
|
|
4728
|
+
resolvedSlackTargets.map((target) => [
|
|
4729
|
+
`${target.targetKind}:${target.targetId}`,
|
|
4730
|
+
target,
|
|
4731
|
+
]),
|
|
4732
|
+
);
|
|
4733
|
+
for (const target of uniqueSlackTargets.values()) {
|
|
4734
|
+
if (
|
|
4735
|
+
destinations.some(
|
|
4736
|
+
(destination) =>
|
|
4737
|
+
destination.settings.kind === 'slack' &&
|
|
4738
|
+
destination.settings.targetKind === target.targetKind &&
|
|
4739
|
+
destination.settings.targetId === target.targetId,
|
|
4740
|
+
)
|
|
4741
|
+
) {
|
|
4742
|
+
continue;
|
|
4743
|
+
}
|
|
4744
|
+
destinations.push({
|
|
4745
|
+
destinationKey: notificationObjectKey('destination'),
|
|
4746
|
+
groupKey,
|
|
4747
|
+
name: `${target.targetKind === 'channel' ? '#' : '@'}${target.targetName}`,
|
|
4748
|
+
enabled: true,
|
|
4749
|
+
source: 'workspace',
|
|
4750
|
+
configMode: 'custom',
|
|
4751
|
+
version: 0,
|
|
4752
|
+
status: 'pending_verification',
|
|
4753
|
+
settings: {
|
|
4754
|
+
kind: 'slack',
|
|
4755
|
+
...target,
|
|
4756
|
+
oauthConnectionId: connectionId!,
|
|
4757
|
+
},
|
|
4758
|
+
});
|
|
4759
|
+
}
|
|
4760
|
+
|
|
4761
|
+
const draft: ProductNotificationGroupDraft = {
|
|
4762
|
+
group,
|
|
4763
|
+
destinations,
|
|
4764
|
+
rules: current.configuration.rules.filter(
|
|
4765
|
+
(rule) => rule.groupKey === groupKey,
|
|
4766
|
+
),
|
|
4767
|
+
};
|
|
4768
|
+
const result = await this.changeNotificationConfiguration(
|
|
4769
|
+
current.configuration.workspaceVersion,
|
|
4770
|
+
{ kind: 'save_group', draft },
|
|
4771
|
+
);
|
|
4772
|
+
const storedGroup = result.configuration.groups.find(
|
|
4773
|
+
(candidate) => candidate.groupKey === groupKey,
|
|
4774
|
+
);
|
|
4775
|
+
if (!storedGroup) {
|
|
4776
|
+
throw new Error('Notification group creation returned no saved group.');
|
|
4777
|
+
}
|
|
4778
|
+
return {
|
|
4779
|
+
mode: 'native' as const,
|
|
4780
|
+
groupKey,
|
|
4781
|
+
targets: normalizedTargets,
|
|
4782
|
+
group: storedGroup,
|
|
4783
|
+
destinations: result.configuration.destinations.filter(
|
|
4784
|
+
(destination) => destination.groupKey === groupKey,
|
|
4785
|
+
),
|
|
4786
|
+
};
|
|
4787
|
+
}
|
|
4788
|
+
|
|
4345
4789
|
/** List Slack channels available to an already-connected Slack integration. */
|
|
4346
4790
|
async listNotificationChannels(query?: string): Promise<{
|
|
4347
4791
|
identity: { teamId: string; teamName?: string };
|
|
@@ -5049,6 +5493,92 @@ export class DeeplineClient {
|
|
|
5049
5493
|
);
|
|
5050
5494
|
}
|
|
5051
5495
|
|
|
5496
|
+
/** Submit durable monitor work; transport retries use the same idempotency key. */
|
|
5497
|
+
async submitMonitorBatch(
|
|
5498
|
+
options: MonitorBatchSubmitOptions,
|
|
5499
|
+
): Promise<MonitorBatchOperationResult> {
|
|
5500
|
+
const idempotencyKey = options.idempotencyKey.trim();
|
|
5501
|
+
if (!idempotencyKey) {
|
|
5502
|
+
throw new DeeplineError(
|
|
5503
|
+
'Monitor batch idempotencyKey is required.',
|
|
5504
|
+
undefined,
|
|
5505
|
+
'INVALID_MONITOR_BATCH_IDEMPOTENCY_KEY',
|
|
5506
|
+
);
|
|
5507
|
+
}
|
|
5508
|
+
if (options.kind === 'deploy') {
|
|
5509
|
+
if (!options.manifestNdjson.trim()) {
|
|
5510
|
+
throw new DeeplineError(
|
|
5511
|
+
'Monitor batch deploy requires a non-empty NDJSON manifest.',
|
|
5512
|
+
undefined,
|
|
5513
|
+
'INVALID_MONITOR_BATCH_MANIFEST',
|
|
5514
|
+
);
|
|
5515
|
+
}
|
|
5516
|
+
return this.http.request<MonitorBatchOperationResult>(
|
|
5517
|
+
'/api/v2/monitors/batch',
|
|
5518
|
+
{
|
|
5519
|
+
method: 'POST',
|
|
5520
|
+
body: {
|
|
5521
|
+
kind: 'deploy',
|
|
5522
|
+
manifest_ndjson: options.manifestNdjson,
|
|
5523
|
+
dry_run: options.dryRun === true,
|
|
5524
|
+
idempotency_key: idempotencyKey,
|
|
5525
|
+
},
|
|
5526
|
+
headers: { 'Idempotency-Key': idempotencyKey },
|
|
5527
|
+
maxRetries: 0,
|
|
5528
|
+
exactUrlOnly: true,
|
|
5529
|
+
},
|
|
5530
|
+
);
|
|
5531
|
+
}
|
|
5532
|
+
const scope =
|
|
5533
|
+
options.scope.kind === 'keys'
|
|
5534
|
+
? { kind: 'keys', monitor_keys: options.scope.monitorKeys }
|
|
5535
|
+
: options.scope.kind === 'fleet'
|
|
5536
|
+
? { kind: 'fleet', fleet_id: options.scope.fleetId }
|
|
5537
|
+
: { kind: 'target', target: options.scope.target };
|
|
5538
|
+
return this.http.request<MonitorBatchOperationResult>(
|
|
5539
|
+
'/api/v2/monitors/batch',
|
|
5540
|
+
{
|
|
5541
|
+
method: 'POST',
|
|
5542
|
+
body: {
|
|
5543
|
+
kind: options.kind,
|
|
5544
|
+
scope,
|
|
5545
|
+
dry_run: options.dryRun === true,
|
|
5546
|
+
idempotency_key: idempotencyKey,
|
|
5547
|
+
...(options.deactivationReason === undefined
|
|
5548
|
+
? {}
|
|
5549
|
+
: { deactivation_reason: options.deactivationReason }),
|
|
5550
|
+
},
|
|
5551
|
+
headers: { 'Idempotency-Key': idempotencyKey },
|
|
5552
|
+
// Durable submission is itself idempotent, but an ambiguous local retry
|
|
5553
|
+
// must retain the exact key and never turn a CLI attempt into two runs.
|
|
5554
|
+
maxRetries: 0,
|
|
5555
|
+
exactUrlOnly: true,
|
|
5556
|
+
},
|
|
5557
|
+
);
|
|
5558
|
+
}
|
|
5559
|
+
|
|
5560
|
+
/** Get durable batch progress and a bounded retained evidence page. */
|
|
5561
|
+
async getMonitorBatch(
|
|
5562
|
+
operationId: string,
|
|
5563
|
+
options?: { cursor?: string | null },
|
|
5564
|
+
): Promise<MonitorBatchOperationResult> {
|
|
5565
|
+
const runId = operationId.trim();
|
|
5566
|
+
if (!runId) {
|
|
5567
|
+
throw new DeeplineError(
|
|
5568
|
+
'Monitor batch operation id is required.',
|
|
5569
|
+
undefined,
|
|
5570
|
+
'INVALID_MONITOR_BATCH_OPERATION_ID',
|
|
5571
|
+
);
|
|
5572
|
+
}
|
|
5573
|
+
const params = new URLSearchParams();
|
|
5574
|
+
if (options?.cursor) params.set('cursor', options.cursor);
|
|
5575
|
+
const suffix = params.size > 0 ? `?${params}` : '';
|
|
5576
|
+
return this.http.request<MonitorBatchOperationResult>(
|
|
5577
|
+
`/api/v2/monitors/batch/${encodeURIComponent(runId)}${suffix}`,
|
|
5578
|
+
{ method: 'GET' },
|
|
5579
|
+
);
|
|
5580
|
+
}
|
|
5581
|
+
|
|
5052
5582
|
/**
|
|
5053
5583
|
* The deployable monitor tools catalog. Pass a tool id (positional or
|
|
5054
5584
|
* `{ tool }`) to describe one tool's full payload/stream contract, or no tool
|