deepline 0.3.14 → 0.3.16

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.
@@ -98,6 +98,7 @@ import {
98
98
  TOOL_EXECUTION_ERROR_SCHEMA_HEADER,
99
99
  TOOL_EXECUTION_ERROR_SCHEMA_VERSION,
100
100
  } from '../../shared_libs/tool-execution-error.js';
101
+
101
102
  import { decodePlayRunPublicStatus } from '../../shared_libs/play-runtime/run-lifecycle-policy.js';
102
103
  import {
103
104
  legacyRawFromToolResponseRawV2,
@@ -105,6 +106,20 @@ import {
105
106
  RAW_V2_TOOL_RESPONSE_CONTRACT,
106
107
  } from '../../shared_libs/play-runtime/tool-response-contract.js';
107
108
 
109
+ export type SlackNotificationTarget =
110
+ | { channel: string; memberId?: never }
111
+ | { channel?: never; memberId: string };
112
+
113
+ export type CreateNotificationInput = {
114
+ name: string;
115
+ provider: 'slack';
116
+ eventTypes: string[];
117
+ } & SlackNotificationTarget;
118
+
119
+ export type UpdateNotificationInput =
120
+ | { enabled: boolean }
121
+ | ({ name: string; eventTypes: string[] } & SlackNotificationTarget);
122
+
108
123
  const TERMINAL_PLAY_STATUSES = new Set(['completed', 'failed', 'cancelled']);
109
124
  const INCLUDE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
110
125
  const EXECUTE_RESPONSE_CONTRACT_HEADER = 'x-deepline-execute-response-contract';
@@ -2835,7 +2850,8 @@ export class DeeplineClient {
2835
2850
  }
2836
2851
 
2837
2852
  type DirectStageResult =
2838
- { ref: PlayStagedFileRef } | { fallbackFile: (typeof files)[number] };
2853
+ | { ref: PlayStagedFileRef }
2854
+ | { fallbackFile: (typeof files)[number] };
2839
2855
  const directResults: DirectStageResult[] = await Promise.all(
2840
2856
  files.map(async (file) => {
2841
2857
  const upload = uploadByIdentity.get(
@@ -3930,9 +3946,12 @@ export class DeeplineClient {
3930
3946
  return this.http.get(`/api/v2/settings/notifications/channels${suffix}`);
3931
3947
  }
3932
3948
 
3933
- /** Select the Slack channel used for product notifications. */
3934
- async setNotificationSlack(channel: string) {
3935
- return this.http.put('/api/v2/settings/notifications', { channel });
3949
+ /** Select a Slack channel or direct member used for product notifications. */
3950
+ async setNotificationSlack(destination: string | { memberId: string }) {
3951
+ return this.http.put(
3952
+ '/api/v2/settings/notifications',
3953
+ typeof destination === 'string' ? { channel: destination } : destination,
3954
+ );
3936
3955
  }
3937
3956
 
3938
3957
  /** Send one synchronous test ping and return Slack's delivery result. */
@@ -3998,21 +4017,14 @@ export class DeeplineClient {
3998
4017
  }
3999
4018
 
4000
4019
  /** Create a named notification routed through an existing provider integration. */
4001
- async createNotification(input: {
4002
- name: string;
4003
- provider: 'slack';
4004
- channel: string;
4005
- eventTypes: string[];
4006
- }) {
4020
+ async createNotification(input: CreateNotificationInput) {
4007
4021
  return this.http.post('/api/v2/notifications', input);
4008
4022
  }
4009
4023
 
4010
4024
  /** Update a notification's target, event selection, or enabled state. */
4011
4025
  async updateNotification(
4012
4026
  notificationId: string,
4013
- input:
4014
- | { enabled: boolean }
4015
- | { name: string; channel: string; eventTypes: string[] },
4027
+ input: UpdateNotificationInput,
4016
4028
  ) {
4017
4029
  return this.http.patch(
4018
4030
  `/api/v2/notifications/${encodeURIComponent(notificationId)}`,
@@ -192,7 +192,7 @@ export const SDK_RELEASE = {
192
192
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
193
193
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
194
194
  // getters keep their established compatibility behavior.
195
- version: '0.3.14',
195
+ version: '0.3.16',
196
196
  updateSummary:
197
197
  'New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.',
198
198
  contracts: {
@@ -1136,7 +1136,7 @@ export interface ProductNotification {
1136
1136
  id: string;
1137
1137
  name: string;
1138
1138
  provider: string;
1139
- target: { id: string; name: string };
1139
+ target: { id: string; name: string; kind?: 'channel' | 'member' };
1140
1140
  enabled: boolean;
1141
1141
  status: string;
1142
1142
  eventTypes: string[];
@@ -75,14 +75,90 @@ export function defaultProductNotificationEventTypes(): ProductNotificationEvent
75
75
  }
76
76
 
77
77
  export const PRODUCT_NOTIFICATION_DESTINATION_KINDS = ['slack'] as const;
78
+ /**
79
+ * The legacy settings API predates named notification rules. Its shortcuts
80
+ * operate on this deterministic compatibility rule when more than one Slack
81
+ * notification exists.
82
+ */
83
+ export const LEGACY_SLACK_NOTIFICATION_NAME = 'Slack notifications';
84
+ export const PRODUCT_NOTIFICATION_LEGACY_SELECTION_ERROR_CODE =
85
+ 'PRODUCT_NOTIFICATION_LEGACY_SELECTION_AMBIGUOUS';
86
+
87
+ export type LegacySlackNotificationDestination = {
88
+ kind: string;
89
+ name: string;
90
+ archivedAt?: number;
91
+ };
92
+
93
+ export class ProductNotificationLegacySelectionError extends Error {
94
+ constructor(message: string) {
95
+ super(message);
96
+ this.name = 'ProductNotificationLegacySelectionError';
97
+ }
98
+ }
99
+
100
+ export function isProductNotificationLegacySelectionError(
101
+ error: unknown,
102
+ ): boolean {
103
+ if (!error || typeof error !== 'object' || !('data' in error)) return false;
104
+ const data = (error as { data?: unknown }).data;
105
+ return (
106
+ typeof data === 'object' &&
107
+ data !== null &&
108
+ (data as { code?: unknown }).code ===
109
+ PRODUCT_NOTIFICATION_LEGACY_SELECTION_ERROR_CODE
110
+ );
111
+ }
112
+
113
+ /**
114
+ * Resolve the singleton target for the legacy settings API without ever
115
+ * silently choosing one of several named notification rules.
116
+ */
117
+ export function resolveLegacySlackNotification<
118
+ T extends LegacySlackNotificationDestination,
119
+ >(destinations: readonly T[]): T | null {
120
+ const slackDestinations = destinations.filter(
121
+ (destination) =>
122
+ destination.kind === 'slack' && destination.archivedAt === undefined,
123
+ );
124
+ const canonical = slackDestinations.filter(
125
+ (destination) => destination.name === LEGACY_SLACK_NOTIFICATION_NAME,
126
+ );
127
+ if (canonical.length === 1) return canonical[0]!;
128
+ if (canonical.length > 1) {
129
+ throw new ProductNotificationLegacySelectionError(
130
+ `More than one \"${LEGACY_SLACK_NOTIFICATION_NAME}\" Slack notification exists. Use the named notification API to choose one.`,
131
+ );
132
+ }
133
+ if (slackDestinations.length <= 1) return slackDestinations[0] ?? null;
134
+ throw new ProductNotificationLegacySelectionError(
135
+ 'More than one Slack notification is configured. Use the named notification API to choose one.',
136
+ );
137
+ }
138
+
78
139
  export const PRODUCT_NOTIFICATION_SLACK_OAUTH_SCOPES = [
79
140
  'channels:read',
80
141
  'chat:write',
81
142
  'groups:read',
143
+ 'im:write',
82
144
  ] as const;
83
145
  export type ProductNotificationDestinationKind =
84
146
  (typeof PRODUCT_NOTIFICATION_DESTINATION_KINDS)[number];
85
147
 
148
+ export const PRODUCT_NOTIFICATION_SLACK_TARGET_KINDS = [
149
+ 'channel',
150
+ 'member',
151
+ ] as const;
152
+ export type ProductNotificationSlackTargetKind =
153
+ (typeof PRODUCT_NOTIFICATION_SLACK_TARGET_KINDS)[number];
154
+
155
+ /** Slack member IDs begin with U (or W for Enterprise Grid workspaces). */
156
+ export function isProductNotificationSlackMemberId(
157
+ value: unknown,
158
+ ): value is string {
159
+ return typeof value === 'string' && /^[UW][A-Z0-9]{8,}$/.test(value.trim());
160
+ }
161
+
86
162
  export const PRODUCT_NOTIFICATION_DELIVERY_STATES = [
87
163
  'pending',
88
164
  'delivering',
package/dist/cli/index.js CHANGED
@@ -1047,7 +1047,7 @@ var SDK_RELEASE = {
1047
1047
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
1048
1048
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
1049
1049
  // getters keep their established compatibility behavior.
1050
- version: "0.3.14",
1050
+ version: "0.3.16",
1051
1051
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
1052
1052
  contracts: {
1053
1053
  api: {
@@ -1904,10 +1904,36 @@ var PRODUCT_NOTIFICATION_EVENT_CATALOG = [
1904
1904
  var PRODUCT_NOTIFICATION_EVENT_TYPE_SET = new Set(
1905
1905
  PRODUCT_NOTIFICATION_EVENT_CATALOG.map((event) => event.id)
1906
1906
  );
1907
+ var LEGACY_SLACK_NOTIFICATION_NAME = "Slack notifications";
1908
+ var ProductNotificationLegacySelectionError = class extends Error {
1909
+ constructor(message) {
1910
+ super(message);
1911
+ this.name = "ProductNotificationLegacySelectionError";
1912
+ }
1913
+ };
1914
+ function resolveLegacySlackNotification(destinations) {
1915
+ const slackDestinations = destinations.filter(
1916
+ (destination) => destination.kind === "slack" && destination.archivedAt === void 0
1917
+ );
1918
+ const canonical = slackDestinations.filter(
1919
+ (destination) => destination.name === LEGACY_SLACK_NOTIFICATION_NAME
1920
+ );
1921
+ if (canonical.length === 1) return canonical[0];
1922
+ if (canonical.length > 1) {
1923
+ throw new ProductNotificationLegacySelectionError(
1924
+ `More than one "${LEGACY_SLACK_NOTIFICATION_NAME}" Slack notification exists. Use the named notification API to choose one.`
1925
+ );
1926
+ }
1927
+ if (slackDestinations.length <= 1) return slackDestinations[0] ?? null;
1928
+ throw new ProductNotificationLegacySelectionError(
1929
+ "More than one Slack notification is configured. Use the named notification API to choose one."
1930
+ );
1931
+ }
1907
1932
  var PRODUCT_NOTIFICATION_SLACK_OAUTH_SCOPES = [
1908
1933
  "channels:read",
1909
1934
  "chat:write",
1910
- "groups:read"
1935
+ "groups:read",
1936
+ "im:write"
1911
1937
  ];
1912
1938
  var PRODUCT_NOTIFICATION_RETRY_DELAYS_MS = [
1913
1939
  0,
@@ -5987,9 +6013,12 @@ var DeeplineClient = class {
5987
6013
  const suffix = query ? `?query=${encodeURIComponent(query)}` : "";
5988
6014
  return this.http.get(`/api/v2/settings/notifications/channels${suffix}`);
5989
6015
  }
5990
- /** Select the Slack channel used for product notifications. */
5991
- async setNotificationSlack(channel) {
5992
- return this.http.put("/api/v2/settings/notifications", { channel });
6016
+ /** Select a Slack channel or direct member used for product notifications. */
6017
+ async setNotificationSlack(destination) {
6018
+ return this.http.put(
6019
+ "/api/v2/settings/notifications",
6020
+ typeof destination === "string" ? { channel: destination } : destination
6021
+ );
5993
6022
  }
5994
6023
  /** Send one synchronous test ping and return Slack's delivery result. */
5995
6024
  async testNotificationSlack() {
@@ -36089,11 +36118,23 @@ var eventHelp = PRODUCT_NOTIFICATION_EVENT_CATALOG.map(
36089
36118
  (event) => ` ${event.id.padEnd(28)} ${event.description}`
36090
36119
  ).join("\n");
36091
36120
  function slackDestination(settings) {
36092
- return settings.destinations.find((entry) => entry.kind === "slack");
36121
+ const destinations = settings.destinations.map(
36122
+ (entry) => ({
36123
+ ...entry,
36124
+ kind: typeof entry.kind === "string" ? entry.kind : "",
36125
+ name: typeof entry.name === "string" ? entry.name : "",
36126
+ archivedAt: typeof entry.archivedAt === "number" ? entry.archivedAt : void 0
36127
+ })
36128
+ );
36129
+ return resolveLegacySlackNotification(
36130
+ destinations
36131
+ );
36093
36132
  }
36094
- function eventLines(settings) {
36133
+ function eventLines(settings, destinationId) {
36095
36134
  const enabled = new Map(
36096
- settings.subscriptions.map((entry) => [entry.eventType, entry.enabled])
36135
+ settings.subscriptions.filter(
36136
+ (entry) => !destinationId || entry.destinationId === destinationId
36137
+ ).map((entry) => [entry.eventType, entry.enabled])
36097
36138
  );
36098
36139
  return settings.catalog.map(
36099
36140
  (event) => `${event.id}: ${enabled.get(event.id) === true ? "enabled" : "disabled"} \u2014 ${event.description}`
@@ -36111,6 +36152,7 @@ function requireCatalogEvent(settings, eventType) {
36111
36152
  async function printSettings(options) {
36112
36153
  const settings = await new DeeplineClient().getNotificationSettings();
36113
36154
  const slack = slackDestination(settings);
36155
+ const destinationId = typeof slack?._id === "string" ? slack._id : void 0;
36114
36156
  printCommandEnvelope(
36115
36157
  {
36116
36158
  settings,
@@ -36120,11 +36162,14 @@ async function printSettings(options) {
36120
36162
  title: "Slack destination",
36121
36163
  lines: slack ? [
36122
36164
  `status: ${String(slack.status ?? "unknown")}`,
36123
- `channel: #${String(slack.channelName ?? slack.channelId ?? "unknown")}`,
36165
+ `${slack.targetKind === "member" ? "member" : "channel"}: ${slack.targetKind === "member" ? "@" : "#"}${String(slack.channelName ?? slack.channelId ?? "unknown")}`,
36124
36166
  ...slack.lastErrorMessage ? [`issue: ${String(slack.lastErrorMessage)}`] : []
36125
36167
  ] : ["Not configured."]
36126
36168
  },
36127
- { title: "subscriptions", lines: eventLines(settings) },
36169
+ {
36170
+ title: "subscriptions",
36171
+ lines: eventLines(settings, destinationId)
36172
+ },
36128
36173
  {
36129
36174
  title: "dead letter queue",
36130
36175
  lines: [
@@ -36224,27 +36269,32 @@ notifications; there is no general integrations CLI.
36224
36269
  { json: options.json }
36225
36270
  );
36226
36271
  });
36227
- notifications.command("set").description("Set a product-notification destination.").argument("<destination>", "Destination kind (currently slack)").requiredOption("--channel <channel>", "Slack channel name or ID").option("--dry-run", "Validate intent without changing settings").option("--json", "Emit JSON output").action(
36272
+ notifications.command("set").description("Set a product-notification destination.").argument("<destination>", "Destination kind (currently slack)").option("--channel <channel>", "Slack channel name or ID").option("--member-id <id>", "Slack member ID, for example U0123456789").option("--dry-run", "Validate intent without changing settings").option("--json", "Emit JSON output").action(
36228
36273
  async (destination, options) => {
36229
36274
  if (destination !== "slack")
36230
36275
  throw new Error("Only slack is supported.");
36276
+ if (Boolean(options.channel) === Boolean(options.memberId)) {
36277
+ throw new Error("Provide exactly one of --channel or --member-id.");
36278
+ }
36279
+ const target = options.memberId ? { memberId: options.memberId } : options.channel;
36231
36280
  if (options.dryRun) {
36232
36281
  printCommandEnvelope(
36233
- { dryRun: true, destination, channel: options.channel },
36282
+ { dryRun: true, destination, target },
36234
36283
  { json: options.json }
36235
36284
  );
36236
36285
  return;
36237
36286
  }
36238
- const result = await new DeeplineClient().setNotificationSlack(
36239
- options.channel
36240
- );
36287
+ const result = await new DeeplineClient().setNotificationSlack(target);
36241
36288
  printCommandEnvelope(
36242
36289
  {
36243
36290
  ok: true,
36244
36291
  result,
36245
36292
  render: {
36246
36293
  sections: [
36247
- { title: "Slack destination saved", lines: [options.channel] }
36294
+ {
36295
+ title: "Slack destination saved",
36296
+ lines: [options.memberId ?? options.channel]
36297
+ }
36248
36298
  ],
36249
36299
  actions: [
36250
36300
  {
@@ -36308,12 +36358,19 @@ ${eventHelp}
36308
36358
  );
36309
36359
  subscriptions.command("list").description("List every supported event and its current state.").option("--json", "Emit JSON output").action(async (options) => {
36310
36360
  const current = await new DeeplineClient().getNotificationSettings();
36361
+ const destination = slackDestination(current);
36362
+ const destinationId = typeof destination?._id === "string" ? destination._id : void 0;
36311
36363
  printCommandEnvelope(
36312
36364
  {
36313
36365
  catalog: current.catalog,
36314
36366
  subscriptions: current.subscriptions,
36315
36367
  render: {
36316
- sections: [{ title: "subscriptions", lines: eventLines(current) }]
36368
+ sections: [
36369
+ {
36370
+ title: "subscriptions",
36371
+ lines: eventLines(current, destinationId)
36372
+ }
36373
+ ]
36317
36374
  }
36318
36375
  },
36319
36376
  { json: options.json }
@@ -36322,8 +36379,10 @@ ${eventHelp}
36322
36379
  subscriptions.command("describe").description("Describe one event from the shared product catalog.").argument("<event>", "Event ID; run subscriptions list to enumerate").option("--json", "Emit JSON output").action(async (eventType, options) => {
36323
36380
  const current = await new DeeplineClient().getNotificationSettings();
36324
36381
  const event = requireCatalogEvent(current, eventType);
36382
+ const destination = slackDestination(current);
36383
+ const destinationId = typeof destination?._id === "string" ? destination._id : void 0;
36325
36384
  const subscription = current.subscriptions.find(
36326
- (entry) => entry.eventType === event.id
36385
+ (entry) => entry.eventType === event.id && (!destinationId || entry.destinationId === destinationId)
36327
36386
  );
36328
36387
  printCommandEnvelope(
36329
36388
  {
@@ -36446,9 +36505,22 @@ function notificationByName(settings, reference) {
36446
36505
  function parseSlackTarget(value) {
36447
36506
  const match = /^slack:(.+)$/i.exec(value.trim());
36448
36507
  if (!match?.[1]) {
36449
- throw new Error("Use --to slack:#channel.");
36508
+ throw new Error(
36509
+ "Use --to slack:#channel or --to slack:member:U0123456789."
36510
+ );
36511
+ }
36512
+ const target = match[1].trim();
36513
+ const member = /^member:([UW][A-Z0-9]{8,})$/i.exec(target);
36514
+ if (member?.[1]) return { memberId: member[1].toUpperCase() };
36515
+ if (target.toLowerCase().startsWith("member:")) {
36516
+ throw new Error(
36517
+ "Slack member IDs must look like slack:member:U0123456789."
36518
+ );
36450
36519
  }
36451
- return match[1];
36520
+ return { channel: target };
36521
+ }
36522
+ function displaySlackTarget(target) {
36523
+ return `${target.kind === "member" ? "@" : "#"}${target.name}`;
36452
36524
  }
36453
36525
  function collectEvent(value, previous = []) {
36454
36526
  return [...previous, value];
@@ -36461,10 +36533,11 @@ Examples:
36461
36533
  deepline notifications events
36462
36534
  deepline notifications slack channels --search pipeline
36463
36535
  deepline notifications add pipeline-watchdog --to slack:#pipeline-alerts --for play.cron.failed
36536
+ deepline notifications add owner-alerts --to slack:member:U0123456789 --for play.cron.failed
36464
36537
  deepline notifications test pipeline-watchdog
36465
36538
 
36466
36539
  Slack connections are managed in Dashboard \u2192 Integrations. This command only
36467
- chooses the connected Slack channel and the events it receives.
36540
+ chooses the connected Slack channel or member and the events it receives.
36468
36541
  `
36469
36542
  );
36470
36543
  notifications.command("events").description("List the Play events available to a notification.").option("--json", "Emit JSON output").action(async (options) => {
@@ -36495,7 +36568,7 @@ chooses the connected Slack channel and the events it receives.
36495
36568
  id: rule.id,
36496
36569
  name: rule.name,
36497
36570
  enabled: rule.enabled,
36498
- target: `#${rule.target.name}`,
36571
+ target: displaySlackTarget(rule.target),
36499
36572
  eventTypes: rule.eventTypes
36500
36573
  })) : rules,
36501
36574
  count: rules.length,
@@ -36504,7 +36577,7 @@ chooses the connected Slack channel and the events it receives.
36504
36577
  {
36505
36578
  title: "notifications",
36506
36579
  lines: rules.length ? rules.map(
36507
- (rule) => `${rule.name}: ${rule.enabled ? "on" : "paused"} \u2192 #${rule.target.name} (${rule.eventTypes.join(", ") || "no events"})`
36580
+ (rule) => `${rule.name}: ${rule.enabled ? "on" : "paused"} \u2192 ${displaySlackTarget(rule.target)} (${rule.eventTypes.join(", ") || "no events"})`
36508
36581
  ) : [
36509
36582
  "None yet. Add one with deepline notifications add <name> --to slack:#channel --for play.cron.failed."
36510
36583
  ]
@@ -36524,7 +36597,7 @@ chooses the connected Slack channel and the events it receives.
36524
36597
  });
36525
36598
  notifications.command("add").argument("<name>", "Short stable name, for example pipeline-watchdog").requiredOption(
36526
36599
  "--to <target>",
36527
- "Target, for example slack:#pipeline-alerts"
36600
+ "Target, for example slack:#pipeline-alerts or slack:member:U0123456789"
36528
36601
  ).option(
36529
36602
  "--for <event>",
36530
36603
  "Event ID; repeat for more events. Run notifications events to list them",
@@ -36535,7 +36608,7 @@ chooses the connected Slack channel and the events it receives.
36535
36608
  if (!options.for.length) {
36536
36609
  throw new Error("Choose at least one event with --for <event>.");
36537
36610
  }
36538
- const channel = parseSlackTarget(options.to);
36611
+ const target = parseSlackTarget(options.to);
36539
36612
  if (options.dryRun) {
36540
36613
  printCommandEnvelope(
36541
36614
  { dryRun: true, name, target: options.to, eventTypes: options.for },
@@ -36546,7 +36619,7 @@ chooses the connected Slack channel and the events it receives.
36546
36619
  const result = await new DeeplineClient().createNotification({
36547
36620
  name,
36548
36621
  provider: "slack",
36549
- channel,
36622
+ ...target,
36550
36623
  eventTypes: options.for
36551
36624
  });
36552
36625
  printCommandEnvelope(
@@ -36566,7 +36639,10 @@ chooses the connected Slack channel and the events it receives.
36566
36639
  );
36567
36640
  }
36568
36641
  );
36569
- notifications.command("edit").argument("<name>", "Notification name or ID").option("--to <target>", "New target, for example slack:#pipeline-alerts").option(
36642
+ notifications.command("edit").argument("<name>", "Notification name or ID").option(
36643
+ "--to <target>",
36644
+ "New target, for example slack:#pipeline-alerts or slack:member:U0123456789"
36645
+ ).option(
36570
36646
  "--for <event>",
36571
36647
  "Replace selected events; repeat for more events. Run notifications events to list them",
36572
36648
  collectEvent,
@@ -36575,18 +36651,18 @@ chooses the connected Slack channel and the events it receives.
36575
36651
  async (name, options) => {
36576
36652
  const client2 = new DeeplineClient();
36577
36653
  const rule = notificationByName(await client2.getNotifications(), name);
36578
- const channel = options.to ? parseSlackTarget(options.to) : rule.target.name;
36654
+ const target = options.to ? parseSlackTarget(options.to) : rule.target.kind === "member" ? { memberId: rule.target.id } : { channel: rule.target.name };
36579
36655
  const eventTypes = options.for.length ? options.for : rule.eventTypes;
36580
36656
  if (options.dryRun) {
36581
36657
  printCommandEnvelope(
36582
- { dryRun: true, notification: rule.id, channel, eventTypes },
36658
+ { dryRun: true, notification: rule.id, target, eventTypes },
36583
36659
  { json: options.json }
36584
36660
  );
36585
36661
  return;
36586
36662
  }
36587
36663
  const result = await client2.updateNotification(rule.id, {
36588
36664
  name: rule.name,
36589
- channel,
36665
+ ...target,
36590
36666
  eventTypes
36591
36667
  });
36592
36668
  printCommandEnvelope({ notification: result }, { json: options.json });
@@ -1033,7 +1033,7 @@ var SDK_RELEASE = {
1033
1033
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
1034
1034
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
1035
1035
  // getters keep their established compatibility behavior.
1036
- version: "0.3.14",
1036
+ version: "0.3.16",
1037
1037
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
1038
1038
  contracts: {
1039
1039
  api: {
@@ -1890,10 +1890,36 @@ var PRODUCT_NOTIFICATION_EVENT_CATALOG = [
1890
1890
  var PRODUCT_NOTIFICATION_EVENT_TYPE_SET = new Set(
1891
1891
  PRODUCT_NOTIFICATION_EVENT_CATALOG.map((event) => event.id)
1892
1892
  );
1893
+ var LEGACY_SLACK_NOTIFICATION_NAME = "Slack notifications";
1894
+ var ProductNotificationLegacySelectionError = class extends Error {
1895
+ constructor(message) {
1896
+ super(message);
1897
+ this.name = "ProductNotificationLegacySelectionError";
1898
+ }
1899
+ };
1900
+ function resolveLegacySlackNotification(destinations) {
1901
+ const slackDestinations = destinations.filter(
1902
+ (destination) => destination.kind === "slack" && destination.archivedAt === void 0
1903
+ );
1904
+ const canonical = slackDestinations.filter(
1905
+ (destination) => destination.name === LEGACY_SLACK_NOTIFICATION_NAME
1906
+ );
1907
+ if (canonical.length === 1) return canonical[0];
1908
+ if (canonical.length > 1) {
1909
+ throw new ProductNotificationLegacySelectionError(
1910
+ `More than one "${LEGACY_SLACK_NOTIFICATION_NAME}" Slack notification exists. Use the named notification API to choose one.`
1911
+ );
1912
+ }
1913
+ if (slackDestinations.length <= 1) return slackDestinations[0] ?? null;
1914
+ throw new ProductNotificationLegacySelectionError(
1915
+ "More than one Slack notification is configured. Use the named notification API to choose one."
1916
+ );
1917
+ }
1893
1918
  var PRODUCT_NOTIFICATION_SLACK_OAUTH_SCOPES = [
1894
1919
  "channels:read",
1895
1920
  "chat:write",
1896
- "groups:read"
1921
+ "groups:read",
1922
+ "im:write"
1897
1923
  ];
1898
1924
  var PRODUCT_NOTIFICATION_RETRY_DELAYS_MS = [
1899
1925
  0,
@@ -5973,9 +5999,12 @@ var DeeplineClient = class {
5973
5999
  const suffix = query ? `?query=${encodeURIComponent(query)}` : "";
5974
6000
  return this.http.get(`/api/v2/settings/notifications/channels${suffix}`);
5975
6001
  }
5976
- /** Select the Slack channel used for product notifications. */
5977
- async setNotificationSlack(channel) {
5978
- return this.http.put("/api/v2/settings/notifications", { channel });
6002
+ /** Select a Slack channel or direct member used for product notifications. */
6003
+ async setNotificationSlack(destination) {
6004
+ return this.http.put(
6005
+ "/api/v2/settings/notifications",
6006
+ typeof destination === "string" ? { channel: destination } : destination
6007
+ );
5979
6008
  }
5980
6009
  /** Send one synchronous test ping and return Slack's delivery result. */
5981
6010
  async testNotificationSlack() {
@@ -36167,11 +36196,23 @@ var eventHelp = PRODUCT_NOTIFICATION_EVENT_CATALOG.map(
36167
36196
  (event) => ` ${event.id.padEnd(28)} ${event.description}`
36168
36197
  ).join("\n");
36169
36198
  function slackDestination(settings) {
36170
- return settings.destinations.find((entry) => entry.kind === "slack");
36199
+ const destinations = settings.destinations.map(
36200
+ (entry) => ({
36201
+ ...entry,
36202
+ kind: typeof entry.kind === "string" ? entry.kind : "",
36203
+ name: typeof entry.name === "string" ? entry.name : "",
36204
+ archivedAt: typeof entry.archivedAt === "number" ? entry.archivedAt : void 0
36205
+ })
36206
+ );
36207
+ return resolveLegacySlackNotification(
36208
+ destinations
36209
+ );
36171
36210
  }
36172
- function eventLines(settings) {
36211
+ function eventLines(settings, destinationId) {
36173
36212
  const enabled = new Map(
36174
- settings.subscriptions.map((entry) => [entry.eventType, entry.enabled])
36213
+ settings.subscriptions.filter(
36214
+ (entry) => !destinationId || entry.destinationId === destinationId
36215
+ ).map((entry) => [entry.eventType, entry.enabled])
36175
36216
  );
36176
36217
  return settings.catalog.map(
36177
36218
  (event) => `${event.id}: ${enabled.get(event.id) === true ? "enabled" : "disabled"} \u2014 ${event.description}`
@@ -36189,6 +36230,7 @@ function requireCatalogEvent(settings, eventType) {
36189
36230
  async function printSettings(options) {
36190
36231
  const settings = await new DeeplineClient().getNotificationSettings();
36191
36232
  const slack = slackDestination(settings);
36233
+ const destinationId = typeof slack?._id === "string" ? slack._id : void 0;
36192
36234
  printCommandEnvelope(
36193
36235
  {
36194
36236
  settings,
@@ -36198,11 +36240,14 @@ async function printSettings(options) {
36198
36240
  title: "Slack destination",
36199
36241
  lines: slack ? [
36200
36242
  `status: ${String(slack.status ?? "unknown")}`,
36201
- `channel: #${String(slack.channelName ?? slack.channelId ?? "unknown")}`,
36243
+ `${slack.targetKind === "member" ? "member" : "channel"}: ${slack.targetKind === "member" ? "@" : "#"}${String(slack.channelName ?? slack.channelId ?? "unknown")}`,
36202
36244
  ...slack.lastErrorMessage ? [`issue: ${String(slack.lastErrorMessage)}`] : []
36203
36245
  ] : ["Not configured."]
36204
36246
  },
36205
- { title: "subscriptions", lines: eventLines(settings) },
36247
+ {
36248
+ title: "subscriptions",
36249
+ lines: eventLines(settings, destinationId)
36250
+ },
36206
36251
  {
36207
36252
  title: "dead letter queue",
36208
36253
  lines: [
@@ -36302,27 +36347,32 @@ notifications; there is no general integrations CLI.
36302
36347
  { json: options.json }
36303
36348
  );
36304
36349
  });
36305
- notifications.command("set").description("Set a product-notification destination.").argument("<destination>", "Destination kind (currently slack)").requiredOption("--channel <channel>", "Slack channel name or ID").option("--dry-run", "Validate intent without changing settings").option("--json", "Emit JSON output").action(
36350
+ notifications.command("set").description("Set a product-notification destination.").argument("<destination>", "Destination kind (currently slack)").option("--channel <channel>", "Slack channel name or ID").option("--member-id <id>", "Slack member ID, for example U0123456789").option("--dry-run", "Validate intent without changing settings").option("--json", "Emit JSON output").action(
36306
36351
  async (destination, options) => {
36307
36352
  if (destination !== "slack")
36308
36353
  throw new Error("Only slack is supported.");
36354
+ if (Boolean(options.channel) === Boolean(options.memberId)) {
36355
+ throw new Error("Provide exactly one of --channel or --member-id.");
36356
+ }
36357
+ const target = options.memberId ? { memberId: options.memberId } : options.channel;
36309
36358
  if (options.dryRun) {
36310
36359
  printCommandEnvelope(
36311
- { dryRun: true, destination, channel: options.channel },
36360
+ { dryRun: true, destination, target },
36312
36361
  { json: options.json }
36313
36362
  );
36314
36363
  return;
36315
36364
  }
36316
- const result = await new DeeplineClient().setNotificationSlack(
36317
- options.channel
36318
- );
36365
+ const result = await new DeeplineClient().setNotificationSlack(target);
36319
36366
  printCommandEnvelope(
36320
36367
  {
36321
36368
  ok: true,
36322
36369
  result,
36323
36370
  render: {
36324
36371
  sections: [
36325
- { title: "Slack destination saved", lines: [options.channel] }
36372
+ {
36373
+ title: "Slack destination saved",
36374
+ lines: [options.memberId ?? options.channel]
36375
+ }
36326
36376
  ],
36327
36377
  actions: [
36328
36378
  {
@@ -36386,12 +36436,19 @@ ${eventHelp}
36386
36436
  );
36387
36437
  subscriptions.command("list").description("List every supported event and its current state.").option("--json", "Emit JSON output").action(async (options) => {
36388
36438
  const current = await new DeeplineClient().getNotificationSettings();
36439
+ const destination = slackDestination(current);
36440
+ const destinationId = typeof destination?._id === "string" ? destination._id : void 0;
36389
36441
  printCommandEnvelope(
36390
36442
  {
36391
36443
  catalog: current.catalog,
36392
36444
  subscriptions: current.subscriptions,
36393
36445
  render: {
36394
- sections: [{ title: "subscriptions", lines: eventLines(current) }]
36446
+ sections: [
36447
+ {
36448
+ title: "subscriptions",
36449
+ lines: eventLines(current, destinationId)
36450
+ }
36451
+ ]
36395
36452
  }
36396
36453
  },
36397
36454
  { json: options.json }
@@ -36400,8 +36457,10 @@ ${eventHelp}
36400
36457
  subscriptions.command("describe").description("Describe one event from the shared product catalog.").argument("<event>", "Event ID; run subscriptions list to enumerate").option("--json", "Emit JSON output").action(async (eventType, options) => {
36401
36458
  const current = await new DeeplineClient().getNotificationSettings();
36402
36459
  const event = requireCatalogEvent(current, eventType);
36460
+ const destination = slackDestination(current);
36461
+ const destinationId = typeof destination?._id === "string" ? destination._id : void 0;
36403
36462
  const subscription = current.subscriptions.find(
36404
- (entry) => entry.eventType === event.id
36463
+ (entry) => entry.eventType === event.id && (!destinationId || entry.destinationId === destinationId)
36405
36464
  );
36406
36465
  printCommandEnvelope(
36407
36466
  {
@@ -36524,9 +36583,22 @@ function notificationByName(settings, reference) {
36524
36583
  function parseSlackTarget(value) {
36525
36584
  const match = /^slack:(.+)$/i.exec(value.trim());
36526
36585
  if (!match?.[1]) {
36527
- throw new Error("Use --to slack:#channel.");
36586
+ throw new Error(
36587
+ "Use --to slack:#channel or --to slack:member:U0123456789."
36588
+ );
36589
+ }
36590
+ const target = match[1].trim();
36591
+ const member = /^member:([UW][A-Z0-9]{8,})$/i.exec(target);
36592
+ if (member?.[1]) return { memberId: member[1].toUpperCase() };
36593
+ if (target.toLowerCase().startsWith("member:")) {
36594
+ throw new Error(
36595
+ "Slack member IDs must look like slack:member:U0123456789."
36596
+ );
36528
36597
  }
36529
- return match[1];
36598
+ return { channel: target };
36599
+ }
36600
+ function displaySlackTarget(target) {
36601
+ return `${target.kind === "member" ? "@" : "#"}${target.name}`;
36530
36602
  }
36531
36603
  function collectEvent(value, previous = []) {
36532
36604
  return [...previous, value];
@@ -36539,10 +36611,11 @@ Examples:
36539
36611
  deepline notifications events
36540
36612
  deepline notifications slack channels --search pipeline
36541
36613
  deepline notifications add pipeline-watchdog --to slack:#pipeline-alerts --for play.cron.failed
36614
+ deepline notifications add owner-alerts --to slack:member:U0123456789 --for play.cron.failed
36542
36615
  deepline notifications test pipeline-watchdog
36543
36616
 
36544
36617
  Slack connections are managed in Dashboard \u2192 Integrations. This command only
36545
- chooses the connected Slack channel and the events it receives.
36618
+ chooses the connected Slack channel or member and the events it receives.
36546
36619
  `
36547
36620
  );
36548
36621
  notifications.command("events").description("List the Play events available to a notification.").option("--json", "Emit JSON output").action(async (options) => {
@@ -36573,7 +36646,7 @@ chooses the connected Slack channel and the events it receives.
36573
36646
  id: rule.id,
36574
36647
  name: rule.name,
36575
36648
  enabled: rule.enabled,
36576
- target: `#${rule.target.name}`,
36649
+ target: displaySlackTarget(rule.target),
36577
36650
  eventTypes: rule.eventTypes
36578
36651
  })) : rules,
36579
36652
  count: rules.length,
@@ -36582,7 +36655,7 @@ chooses the connected Slack channel and the events it receives.
36582
36655
  {
36583
36656
  title: "notifications",
36584
36657
  lines: rules.length ? rules.map(
36585
- (rule) => `${rule.name}: ${rule.enabled ? "on" : "paused"} \u2192 #${rule.target.name} (${rule.eventTypes.join(", ") || "no events"})`
36658
+ (rule) => `${rule.name}: ${rule.enabled ? "on" : "paused"} \u2192 ${displaySlackTarget(rule.target)} (${rule.eventTypes.join(", ") || "no events"})`
36586
36659
  ) : [
36587
36660
  "None yet. Add one with deepline notifications add <name> --to slack:#channel --for play.cron.failed."
36588
36661
  ]
@@ -36602,7 +36675,7 @@ chooses the connected Slack channel and the events it receives.
36602
36675
  });
36603
36676
  notifications.command("add").argument("<name>", "Short stable name, for example pipeline-watchdog").requiredOption(
36604
36677
  "--to <target>",
36605
- "Target, for example slack:#pipeline-alerts"
36678
+ "Target, for example slack:#pipeline-alerts or slack:member:U0123456789"
36606
36679
  ).option(
36607
36680
  "--for <event>",
36608
36681
  "Event ID; repeat for more events. Run notifications events to list them",
@@ -36613,7 +36686,7 @@ chooses the connected Slack channel and the events it receives.
36613
36686
  if (!options.for.length) {
36614
36687
  throw new Error("Choose at least one event with --for <event>.");
36615
36688
  }
36616
- const channel = parseSlackTarget(options.to);
36689
+ const target = parseSlackTarget(options.to);
36617
36690
  if (options.dryRun) {
36618
36691
  printCommandEnvelope(
36619
36692
  { dryRun: true, name, target: options.to, eventTypes: options.for },
@@ -36624,7 +36697,7 @@ chooses the connected Slack channel and the events it receives.
36624
36697
  const result = await new DeeplineClient().createNotification({
36625
36698
  name,
36626
36699
  provider: "slack",
36627
- channel,
36700
+ ...target,
36628
36701
  eventTypes: options.for
36629
36702
  });
36630
36703
  printCommandEnvelope(
@@ -36644,7 +36717,10 @@ chooses the connected Slack channel and the events it receives.
36644
36717
  );
36645
36718
  }
36646
36719
  );
36647
- notifications.command("edit").argument("<name>", "Notification name or ID").option("--to <target>", "New target, for example slack:#pipeline-alerts").option(
36720
+ notifications.command("edit").argument("<name>", "Notification name or ID").option(
36721
+ "--to <target>",
36722
+ "New target, for example slack:#pipeline-alerts or slack:member:U0123456789"
36723
+ ).option(
36648
36724
  "--for <event>",
36649
36725
  "Replace selected events; repeat for more events. Run notifications events to list them",
36650
36726
  collectEvent,
@@ -36653,18 +36729,18 @@ chooses the connected Slack channel and the events it receives.
36653
36729
  async (name, options) => {
36654
36730
  const client2 = new DeeplineClient();
36655
36731
  const rule = notificationByName(await client2.getNotifications(), name);
36656
- const channel = options.to ? parseSlackTarget(options.to) : rule.target.name;
36732
+ const target = options.to ? parseSlackTarget(options.to) : rule.target.kind === "member" ? { memberId: rule.target.id } : { channel: rule.target.name };
36657
36733
  const eventTypes = options.for.length ? options.for : rule.eventTypes;
36658
36734
  if (options.dryRun) {
36659
36735
  printCommandEnvelope(
36660
- { dryRun: true, notification: rule.id, channel, eventTypes },
36736
+ { dryRun: true, notification: rule.id, target, eventTypes },
36661
36737
  { json: options.json }
36662
36738
  );
36663
36739
  return;
36664
36740
  }
36665
36741
  const result = await client2.updateNotification(rule.id, {
36666
36742
  name: rule.name,
36667
- channel,
36743
+ ...target,
36668
36744
  eventTypes
36669
36745
  });
36670
36746
  printCommandEnvelope({ notification: result }, { json: options.json });
package/dist/index.d.mts CHANGED
@@ -1243,6 +1243,7 @@ interface ProductNotification {
1243
1243
  target: {
1244
1244
  id: string;
1245
1245
  name: string;
1246
+ kind?: 'channel' | 'member';
1246
1247
  };
1247
1248
  enabled: boolean;
1248
1249
  status: string;
@@ -1894,6 +1895,24 @@ type EnrichCompiledConfig = {
1894
1895
  };
1895
1896
  };
1896
1897
 
1898
+ type SlackNotificationTarget = {
1899
+ channel: string;
1900
+ memberId?: never;
1901
+ } | {
1902
+ channel?: never;
1903
+ memberId: string;
1904
+ };
1905
+ type CreateNotificationInput = {
1906
+ name: string;
1907
+ provider: 'slack';
1908
+ eventTypes: string[];
1909
+ } & SlackNotificationTarget;
1910
+ type UpdateNotificationInput = {
1911
+ enabled: boolean;
1912
+ } | ({
1913
+ name: string;
1914
+ eventTypes: string[];
1915
+ } & SlackNotificationTarget);
1897
1916
  type IngestionStorageRepairResult = {
1898
1917
  status?: 'repaired';
1899
1918
  connection_grants: {
@@ -3364,8 +3383,10 @@ declare class DeeplineClient {
3364
3383
  isPrivate: boolean;
3365
3384
  }>;
3366
3385
  }>;
3367
- /** Select the Slack channel used for product notifications. */
3368
- setNotificationSlack(channel: string): Promise<unknown>;
3386
+ /** Select a Slack channel or direct member used for product notifications. */
3387
+ setNotificationSlack(destination: string | {
3388
+ memberId: string;
3389
+ }): Promise<unknown>;
3369
3390
  /** Send one synchronous test ping and return Slack's delivery result. */
3370
3391
  testNotificationSlack(): Promise<{
3371
3392
  ok: boolean;
@@ -3398,20 +3419,9 @@ declare class DeeplineClient {
3398
3419
  }>;
3399
3420
  }>;
3400
3421
  /** Create a named notification routed through an existing provider integration. */
3401
- createNotification(input: {
3402
- name: string;
3403
- provider: 'slack';
3404
- channel: string;
3405
- eventTypes: string[];
3406
- }): Promise<unknown>;
3422
+ createNotification(input: CreateNotificationInput): Promise<unknown>;
3407
3423
  /** Update a notification's target, event selection, or enabled state. */
3408
- updateNotification(notificationId: string, input: {
3409
- enabled: boolean;
3410
- } | {
3411
- name: string;
3412
- channel: string;
3413
- eventTypes: string[];
3414
- }): Promise<unknown>;
3424
+ updateNotification(notificationId: string, input: UpdateNotificationInput): Promise<unknown>;
3415
3425
  /** Send a validation ping to one notification. */
3416
3426
  testNotification(notificationId: string): Promise<{
3417
3427
  ok: boolean;
package/dist/index.d.ts CHANGED
@@ -1243,6 +1243,7 @@ interface ProductNotification {
1243
1243
  target: {
1244
1244
  id: string;
1245
1245
  name: string;
1246
+ kind?: 'channel' | 'member';
1246
1247
  };
1247
1248
  enabled: boolean;
1248
1249
  status: string;
@@ -1894,6 +1895,24 @@ type EnrichCompiledConfig = {
1894
1895
  };
1895
1896
  };
1896
1897
 
1898
+ type SlackNotificationTarget = {
1899
+ channel: string;
1900
+ memberId?: never;
1901
+ } | {
1902
+ channel?: never;
1903
+ memberId: string;
1904
+ };
1905
+ type CreateNotificationInput = {
1906
+ name: string;
1907
+ provider: 'slack';
1908
+ eventTypes: string[];
1909
+ } & SlackNotificationTarget;
1910
+ type UpdateNotificationInput = {
1911
+ enabled: boolean;
1912
+ } | ({
1913
+ name: string;
1914
+ eventTypes: string[];
1915
+ } & SlackNotificationTarget);
1897
1916
  type IngestionStorageRepairResult = {
1898
1917
  status?: 'repaired';
1899
1918
  connection_grants: {
@@ -3364,8 +3383,10 @@ declare class DeeplineClient {
3364
3383
  isPrivate: boolean;
3365
3384
  }>;
3366
3385
  }>;
3367
- /** Select the Slack channel used for product notifications. */
3368
- setNotificationSlack(channel: string): Promise<unknown>;
3386
+ /** Select a Slack channel or direct member used for product notifications. */
3387
+ setNotificationSlack(destination: string | {
3388
+ memberId: string;
3389
+ }): Promise<unknown>;
3369
3390
  /** Send one synchronous test ping and return Slack's delivery result. */
3370
3391
  testNotificationSlack(): Promise<{
3371
3392
  ok: boolean;
@@ -3398,20 +3419,9 @@ declare class DeeplineClient {
3398
3419
  }>;
3399
3420
  }>;
3400
3421
  /** Create a named notification routed through an existing provider integration. */
3401
- createNotification(input: {
3402
- name: string;
3403
- provider: 'slack';
3404
- channel: string;
3405
- eventTypes: string[];
3406
- }): Promise<unknown>;
3422
+ createNotification(input: CreateNotificationInput): Promise<unknown>;
3407
3423
  /** Update a notification's target, event selection, or enabled state. */
3408
- updateNotification(notificationId: string, input: {
3409
- enabled: boolean;
3410
- } | {
3411
- name: string;
3412
- channel: string;
3413
- eventTypes: string[];
3414
- }): Promise<unknown>;
3424
+ updateNotification(notificationId: string, input: UpdateNotificationInput): Promise<unknown>;
3415
3425
  /** Send a validation ping to one notification. */
3416
3426
  testNotification(notificationId: string): Promise<{
3417
3427
  ok: boolean;
package/dist/index.js CHANGED
@@ -783,7 +783,7 @@ var SDK_RELEASE = {
783
783
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
784
784
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
785
785
  // getters keep their established compatibility behavior.
786
- version: "0.3.14",
786
+ version: "0.3.16",
787
787
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
788
788
  contracts: {
789
789
  api: {
@@ -1643,7 +1643,8 @@ var PRODUCT_NOTIFICATION_EVENT_TYPE_SET = new Set(
1643
1643
  var PRODUCT_NOTIFICATION_SLACK_OAUTH_SCOPES = [
1644
1644
  "channels:read",
1645
1645
  "chat:write",
1646
- "groups:read"
1646
+ "groups:read",
1647
+ "im:write"
1647
1648
  ];
1648
1649
  var PRODUCT_NOTIFICATION_RETRY_DELAYS_MS = [
1649
1650
  0,
@@ -5723,9 +5724,12 @@ var DeeplineClient = class {
5723
5724
  const suffix = query ? `?query=${encodeURIComponent(query)}` : "";
5724
5725
  return this.http.get(`/api/v2/settings/notifications/channels${suffix}`);
5725
5726
  }
5726
- /** Select the Slack channel used for product notifications. */
5727
- async setNotificationSlack(channel) {
5728
- return this.http.put("/api/v2/settings/notifications", { channel });
5727
+ /** Select a Slack channel or direct member used for product notifications. */
5728
+ async setNotificationSlack(destination) {
5729
+ return this.http.put(
5730
+ "/api/v2/settings/notifications",
5731
+ typeof destination === "string" ? { channel: destination } : destination
5732
+ );
5729
5733
  }
5730
5734
  /** Send one synchronous test ping and return Slack's delivery result. */
5731
5735
  async testNotificationSlack() {
package/dist/index.mjs CHANGED
@@ -706,7 +706,7 @@ var SDK_RELEASE = {
706
706
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
707
707
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
708
708
  // getters keep their established compatibility behavior.
709
- version: "0.3.14",
709
+ version: "0.3.16",
710
710
  updateSummary: "New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.",
711
711
  contracts: {
712
712
  api: {
@@ -1566,7 +1566,8 @@ var PRODUCT_NOTIFICATION_EVENT_TYPE_SET = new Set(
1566
1566
  var PRODUCT_NOTIFICATION_SLACK_OAUTH_SCOPES = [
1567
1567
  "channels:read",
1568
1568
  "chat:write",
1569
- "groups:read"
1569
+ "groups:read",
1570
+ "im:write"
1570
1571
  ];
1571
1572
  var PRODUCT_NOTIFICATION_RETRY_DELAYS_MS = [
1572
1573
  0,
@@ -5646,9 +5647,12 @@ var DeeplineClient = class {
5646
5647
  const suffix = query ? `?query=${encodeURIComponent(query)}` : "";
5647
5648
  return this.http.get(`/api/v2/settings/notifications/channels${suffix}`);
5648
5649
  }
5649
- /** Select the Slack channel used for product notifications. */
5650
- async setNotificationSlack(channel) {
5651
- return this.http.put("/api/v2/settings/notifications", { channel });
5650
+ /** Select a Slack channel or direct member used for product notifications. */
5651
+ async setNotificationSlack(destination) {
5652
+ return this.http.put(
5653
+ "/api/v2/settings/notifications",
5654
+ typeof destination === "string" ? { channel: destination } : destination
5655
+ );
5652
5656
  }
5653
5657
  /** Send one synchronous test ping and return Slack's delivery result. */
5654
5658
  async testNotificationSlack() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.3.14",
3
+ "version": "0.3.16",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",