@objectstack/connector-slack 7.9.0 → 8.0.1

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/connector-slack@7.9.0 build /home/runner/work/framework/framework/packages/connectors/connector-slack
2
+ > @objectstack/connector-slack@8.0.1 build /home/runner/work/framework/framework/packages/connectors/connector-slack
3
3
  > tsup --config ../../../tsup.config.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- ESM dist/index.mjs 5.34 KB
14
- ESM dist/index.mjs.map 15.18 KB
15
- ESM ⚡️ Build success in 99ms
16
- CJS dist/index.js 6.42 KB
17
- CJS dist/index.js.map 16.33 KB
18
- CJS ⚡️ Build success in 99ms
13
+ CJS dist/index.js 6.48 KB
14
+ CJS dist/index.js.map 16.39 KB
15
+ CJS ⚡️ Build success in 109ms
16
+ ESM dist/index.mjs 5.40 KB
17
+ ESM dist/index.mjs.map 15.25 KB
18
+ ESM ⚡️ Build success in 109ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 12627ms
20
+ DTS ⚡️ Build success in 12137ms
21
21
  DTS dist/index.d.mts 4.81 KB
22
22
  DTS dist/index.d.ts 4.81 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # @objectstack/connector-slack
2
2
 
3
+ ## 8.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @objectstack/spec@8.0.1
8
+ - @objectstack/core@8.0.1
9
+
10
+ ## 8.0.0
11
+
12
+ ### Patch Changes
13
+
14
+ - d5a8161: feat(spec): resilientFetch — timeout + backoff for outbound HTTP (P1-1)
15
+
16
+ Outbound calls in the connectors/embedder were naked `fetch` with no timeout or
17
+ retry, so a slow or rate-limited external API could hang an agent turn with no
18
+ recovery.
19
+
20
+ New shared `resilientFetch` (`@objectstack/spec/shared`):
21
+
22
+ - per-attempt timeout via `AbortController` (default 30s);
23
+ - exponential backoff with jitter, up to 3 attempts, on network errors / 429 / 5xx;
24
+ - honours a `Retry-After` header on 429;
25
+ - never retries a caller-initiated abort (intentional cancellation).
26
+
27
+ Wired into `connector-rest`, `connector-slack`, and `embedder-openai`.
28
+ `connector-mcp` talks through the MCP SDK transport, so it gets a 30s per-request
29
+ `timeout` on `callTool` / `listTools` instead.
30
+
31
+ A stateful per-host **circuit breaker** is deliberately left as a follow-up:
32
+ timeout + backoff already removes the hang/no-recovery risk.
33
+
34
+ - Updated dependencies [a46c017]
35
+ - Updated dependencies [b990b89]
36
+ - Updated dependencies [99111ec]
37
+ - Updated dependencies [d5a8161]
38
+ - Updated dependencies [5cf1f1b]
39
+ - Updated dependencies [9ef89d4]
40
+ - Updated dependencies [3306d2f]
41
+ - Updated dependencies [c262301]
42
+ - Updated dependencies [bc44195]
43
+ - Updated dependencies [9e2e229]
44
+ - @objectstack/spec@8.0.0
45
+ - @objectstack/core@8.0.0
46
+
3
47
  ## 7.9.0
4
48
 
5
49
  ### Patch Changes
package/dist/index.js CHANGED
@@ -26,10 +26,10 @@ __export(index_exports, {
26
26
  module.exports = __toCommonJS(index_exports);
27
27
 
28
28
  // src/slack-connector.ts
29
+ var import_shared = require("@objectstack/spec/shared");
29
30
  function createSlackConnector(opts) {
30
31
  const name = opts.name ?? "slack";
31
32
  const baseUrl = (opts.baseUrl ?? "https://slack.com/api").replace(/\/+$/, "");
32
- const doFetch = opts.fetchImpl ?? fetch;
33
33
  const def = {
34
34
  name,
35
35
  label: opts.label ?? "Slack",
@@ -98,11 +98,11 @@ function createSlackConnector(opts) {
98
98
  ...opts.defaultHeaders,
99
99
  Authorization: `Bearer ${opts.token}`
100
100
  };
101
- const response = await doFetch(`${baseUrl}/${method}`, {
101
+ const response = await (0, import_shared.resilientFetch)(`${baseUrl}/${method}`, {
102
102
  method: "POST",
103
103
  headers,
104
104
  body: JSON.stringify(params)
105
- });
105
+ }, { fetchImpl: opts.fetchImpl });
106
106
  const body = await response.json();
107
107
  const ok = body.ok === true;
108
108
  return {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/slack-connector.ts","../src/connector-slack-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/connector-slack\n *\n * Slack Web API connector — a concrete connector (ADR-0018 §Addendum) and the\n * second reference implementation after `@objectstack/connector-rest`. The\n * baseline automation engine ships the `connector_action` dispatch node + an\n * empty connector registry; this plugin populates it with a `slack` connector\n * exposing `chat.postMessage`, `chat.update`, and a generic `call` action.\n *\n * This is the integration mechanism (ADR-0022 \"raw API call\" path), not the\n * human-notification layer. Static bot-token auth only; OAuth2 install/refresh,\n * credential vaulting, and multi-tenant lifecycle are the enterprise tier.\n */\n\nexport {\n createSlackConnector,\n type SlackConnectorOptions,\n type SlackConnectorBundle,\n type SlackPostMessageInput,\n type SlackResult,\n} from './slack-connector.js';\nexport {\n ConnectorSlackPlugin,\n type ConnectorSlackPluginOptions,\n type ConnectorRegistrySurface,\n} from './connector-slack-plugin.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Connector } from '@objectstack/spec/integration';\n\n/**\n * Slack connector — a *concrete* connector (ADR-0018 §Addendum) and the second\n * reference implementation after `@objectstack/connector-rest`. It produces a\n * {@link Connector} definition plus handlers for a small set of Slack Web API\n * actions, which the baseline `connector_action` node dispatches to.\n *\n * Scope (ADR-0022 \"raw API call\" path): this is the *integration mechanism*\n * for talking to Slack's API — \"post this exact text to this channel\". It is\n * deliberately **not** the human-notification layer: there is no preference\n * matrix, inbox, outbox, or thread/session semantics here. Those belong to a\n * `MessagingChannel` (ADR-0012/0013), which may itself delegate its transport\n * to this connector.\n *\n * Open-source scope: **static** auth only — a Slack **bot token** (`xoxb-…`),\n * supplied by the caller and sent as a bearer credential. OAuth2 install/refresh,\n * credential vaulting, and multi-tenant connection lifecycle are the enterprise\n * tier (see `../cloud/docs/design/connector-tiering.md`) and are out of scope.\n *\n * Slack quirk: the Web API returns HTTP `200` even on logical failure, with the\n * real outcome in the JSON body's `ok` field (and `error` on failure). Handlers\n * therefore surface `ok` from the payload, not from the HTTP status, and never\n * throw on a logical failure — the flow author branches on `${node.ok}`.\n */\n\nexport interface SlackConnectorOptions {\n /** Connector machine name (snake_case). Defaults to `slack`. */\n name?: string;\n /** Human-readable label. Defaults to `Slack`. */\n label?: string;\n /** Slack bot token (`xoxb-…`), sent as `Authorization: Bearer <token>`. */\n token: string;\n /** Web API base URL. Defaults to `https://slack.com/api`. */\n baseUrl?: string;\n /** Headers merged into every request (request-level headers win). */\n defaultHeaders?: Record<string, string>;\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\n/** Input accepted by `chat.postMessage`. */\nexport interface SlackPostMessageInput {\n /** Channel id (`C…`), user id (`U…`), or channel name (`#general`). */\n channel: string;\n /** Message text (fallback when `blocks` are present). */\n text?: string;\n /** Thread root `ts` to reply into an existing thread. */\n thread_ts?: string;\n /** Block Kit blocks. */\n blocks?: unknown[];\n [key: string]: unknown;\n}\n\n/** Generic Slack Web API result envelope. */\nexport interface SlackResult {\n /** Slack's logical success flag (from the JSON body, not the HTTP status). */\n ok: boolean;\n /** HTTP status (almost always 200 for the Slack Web API). */\n status: number;\n /** Full parsed Slack payload. */\n body: Record<string, unknown>;\n /** Slack error code when `ok` is false (e.g. `channel_not_found`). */\n error?: string;\n}\n\n/** A connector definition paired with its action handlers, ready for registerConnector(). */\nexport interface SlackConnectorBundle {\n def: Connector;\n handlers: Record<\n string,\n (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>\n >;\n}\n\nexport function createSlackConnector(opts: SlackConnectorOptions): SlackConnectorBundle {\n const name = opts.name ?? 'slack';\n const baseUrl = (opts.baseUrl ?? 'https://slack.com/api').replace(/\\/+$/, '');\n const doFetch = opts.fetchImpl ?? fetch;\n\n const def: Connector = {\n name,\n label: opts.label ?? 'Slack',\n type: 'api',\n description: 'Slack Web API connector (static bot-token auth). Post and update messages, or call any Web API method.',\n icon: 'slack',\n authentication: { type: 'bearer', token: opts.token },\n // Defaulted by ConnectorSchema; set explicitly so the literal satisfies\n // the (post-parse) Connector output type.\n status: 'active',\n enabled: true,\n connectionTimeoutMs: 30000,\n requestTimeoutMs: 30000,\n actions: [\n {\n key: 'chat.postMessage',\n label: 'Post Message',\n description: 'Post a message to a channel, DM, or thread (Slack chat.postMessage).',\n inputSchema: {\n type: 'object',\n required: ['channel'],\n properties: {\n channel: { type: 'string', description: 'Channel id, user id, or #name' },\n text: { type: 'string', description: 'Message text' },\n thread_ts: { type: 'string', description: 'Thread root ts to reply into' },\n blocks: { type: 'array', description: 'Block Kit blocks' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n {\n key: 'chat.update',\n label: 'Update Message',\n description: 'Edit an existing message (Slack chat.update).',\n inputSchema: {\n type: 'object',\n required: ['channel', 'ts'],\n properties: {\n channel: { type: 'string', description: 'Channel id' },\n ts: { type: 'string', description: 'Timestamp of the message to update' },\n text: { type: 'string', description: 'New message text' },\n blocks: { type: 'array', description: 'New Block Kit blocks' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n {\n key: 'call',\n label: 'Call Web API Method',\n description: 'Escape hatch — call any Slack Web API method with arbitrary params.',\n inputSchema: {\n type: 'object',\n required: ['method'],\n properties: {\n method: { type: 'string', description: 'Web API method, e.g. conversations.list' },\n params: { type: 'object', description: 'Method parameters (JSON body)' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n ],\n };\n\n /** POST a JSON body to a Slack Web API method and normalise the result. */\n async function callSlack(method: string, params: Record<string, unknown>): Promise<SlackResult> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json; charset=utf-8',\n ...opts.defaultHeaders,\n Authorization: `Bearer ${opts.token}`,\n };\n\n const response = await doFetch(`${baseUrl}/${method}`, {\n method: 'POST',\n headers,\n body: JSON.stringify(params),\n });\n\n // The Slack Web API always answers with JSON; `ok` is the real outcome.\n const body = (await response.json()) as Record<string, unknown>;\n const ok = body.ok === true;\n return {\n ok,\n status: response.status,\n body,\n error: ok ? undefined : (body.error as string | undefined),\n };\n }\n\n async function postMessage(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n return toRecord(await callSlack('chat.postMessage', input));\n }\n\n async function update(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n return toRecord(await callSlack('chat.update', input));\n }\n\n async function call(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n const method = String(input.method ?? '');\n if (!method) throw new Error(\"slack 'call' action: 'method' is required\");\n const params = (input.params as Record<string, unknown>) ?? {};\n return toRecord(await callSlack(method, params));\n }\n\n return {\n def,\n handlers: {\n 'chat.postMessage': postMessage,\n 'chat.update': update,\n call,\n },\n };\n}\n\nfunction toRecord(r: SlackResult): Record<string, unknown> {\n return { ok: r.ok, status: r.status, body: r.body, error: r.error };\n}\n\nfunction slackOutputSchema(): Record<string, unknown> {\n return {\n type: 'object',\n properties: {\n ok: { type: 'boolean', description: \"Slack's logical success flag\" },\n status: { type: 'number', description: 'HTTP status' },\n body: { type: 'object', description: 'Full Slack response payload' },\n error: { type: 'string', description: 'Slack error code when ok is false' },\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { Connector } from '@objectstack/spec/integration';\nimport { createSlackConnector, type SlackConnectorOptions } from './slack-connector.js';\n\n/**\n * Minimal surface of the automation engine this plugin depends on — the\n * connector registry from ADR-0018 §Addendum. Kept structural so the plugin\n * needs no runtime dependency on `@objectstack/service-automation`.\n */\nexport interface ConnectorRegistrySurface {\n registerConnector(\n def: Connector,\n handlers: Record<\n string,\n (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>\n >,\n ): void;\n unregisterConnector(name: string): void;\n}\n\nexport interface ConnectorSlackPluginOptions extends SlackConnectorOptions {}\n\n/**\n * ConnectorSlackPlugin — registers a Slack Web API connector on the automation\n * engine. The second reference concrete connector (ADR-0018 §Addendum); it\n * enables the ADR-0022 \"raw API call\" path — a flow's `connector_action` step\n * can dispatch to `slack.chat.postMessage` without the messaging stack.\n *\n * If no automation engine is present the plugin logs and skips — the connector\n * has nowhere to register, which is not an error.\n */\nexport class ConnectorSlackPlugin implements Plugin {\n name = 'com.objectstack.connector.slack';\n version = '1.0.0';\n type = 'standard' as const;\n // Ensure the automation engine (and its connector registry) is started first.\n dependencies = ['com.objectstack.service-automation'];\n\n private readonly options: ConnectorSlackPluginOptions;\n private connectorName?: string;\n private automation?: ConnectorRegistrySurface;\n\n constructor(options: ConnectorSlackPluginOptions) {\n this.options = options;\n }\n\n async init(_ctx: PluginContext): Promise<void> {\n // No services to register; the connector is registered in start() once\n // the automation engine is available.\n }\n\n async start(ctx: PluginContext): Promise<void> {\n let automation: ConnectorRegistrySurface | undefined;\n try {\n automation = ctx.getService<ConnectorRegistrySurface>('automation');\n } catch {\n automation = undefined;\n }\n\n if (!automation || typeof automation.registerConnector !== 'function') {\n ctx.logger.info('ConnectorSlackPlugin: no automation engine — Slack connector not registered');\n return;\n }\n\n const { def, handlers } = createSlackConnector(this.options);\n automation.registerConnector(def, handlers);\n this.automation = automation;\n this.connectorName = def.name;\n ctx.logger.info(`ConnectorSlackPlugin: Slack connector '${def.name}' registered`);\n }\n\n async stop(_ctx: PluginContext): Promise<void> {\n if (this.automation && this.connectorName) {\n try { this.automation.unregisterConnector(this.connectorName); } catch { /* ignore */ }\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/slack-connector.ts","../src/connector-slack-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/connector-slack\n *\n * Slack Web API connector — a concrete connector (ADR-0018 §Addendum) and the\n * second reference implementation after `@objectstack/connector-rest`. The\n * baseline automation engine ships the `connector_action` dispatch node + an\n * empty connector registry; this plugin populates it with a `slack` connector\n * exposing `chat.postMessage`, `chat.update`, and a generic `call` action.\n *\n * This is the integration mechanism (ADR-0022 \"raw API call\" path), not the\n * human-notification layer. Static bot-token auth only; OAuth2 install/refresh,\n * credential vaulting, and multi-tenant lifecycle are the enterprise tier.\n */\n\nexport {\n createSlackConnector,\n type SlackConnectorOptions,\n type SlackConnectorBundle,\n type SlackPostMessageInput,\n type SlackResult,\n} from './slack-connector.js';\nexport {\n ConnectorSlackPlugin,\n type ConnectorSlackPluginOptions,\n type ConnectorRegistrySurface,\n} from './connector-slack-plugin.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Connector } from '@objectstack/spec/integration';\nimport { resilientFetch } from '@objectstack/spec/shared';\n\n/**\n * Slack connector — a *concrete* connector (ADR-0018 §Addendum) and the second\n * reference implementation after `@objectstack/connector-rest`. It produces a\n * {@link Connector} definition plus handlers for a small set of Slack Web API\n * actions, which the baseline `connector_action` node dispatches to.\n *\n * Scope (ADR-0022 \"raw API call\" path): this is the *integration mechanism*\n * for talking to Slack's API — \"post this exact text to this channel\". It is\n * deliberately **not** the human-notification layer: there is no preference\n * matrix, inbox, outbox, or thread/session semantics here. Those belong to a\n * `MessagingChannel` (ADR-0012/0013), which may itself delegate its transport\n * to this connector.\n *\n * Open-source scope: **static** auth only — a Slack **bot token** (`xoxb-…`),\n * supplied by the caller and sent as a bearer credential. OAuth2 install/refresh,\n * credential vaulting, and multi-tenant connection lifecycle are the enterprise\n * tier (see `../cloud/docs/design/connector-tiering.md`) and are out of scope.\n *\n * Slack quirk: the Web API returns HTTP `200` even on logical failure, with the\n * real outcome in the JSON body's `ok` field (and `error` on failure). Handlers\n * therefore surface `ok` from the payload, not from the HTTP status, and never\n * throw on a logical failure — the flow author branches on `${node.ok}`.\n */\n\nexport interface SlackConnectorOptions {\n /** Connector machine name (snake_case). Defaults to `slack`. */\n name?: string;\n /** Human-readable label. Defaults to `Slack`. */\n label?: string;\n /** Slack bot token (`xoxb-…`), sent as `Authorization: Bearer <token>`. */\n token: string;\n /** Web API base URL. Defaults to `https://slack.com/api`. */\n baseUrl?: string;\n /** Headers merged into every request (request-level headers win). */\n defaultHeaders?: Record<string, string>;\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\n/** Input accepted by `chat.postMessage`. */\nexport interface SlackPostMessageInput {\n /** Channel id (`C…`), user id (`U…`), or channel name (`#general`). */\n channel: string;\n /** Message text (fallback when `blocks` are present). */\n text?: string;\n /** Thread root `ts` to reply into an existing thread. */\n thread_ts?: string;\n /** Block Kit blocks. */\n blocks?: unknown[];\n [key: string]: unknown;\n}\n\n/** Generic Slack Web API result envelope. */\nexport interface SlackResult {\n /** Slack's logical success flag (from the JSON body, not the HTTP status). */\n ok: boolean;\n /** HTTP status (almost always 200 for the Slack Web API). */\n status: number;\n /** Full parsed Slack payload. */\n body: Record<string, unknown>;\n /** Slack error code when `ok` is false (e.g. `channel_not_found`). */\n error?: string;\n}\n\n/** A connector definition paired with its action handlers, ready for registerConnector(). */\nexport interface SlackConnectorBundle {\n def: Connector;\n handlers: Record<\n string,\n (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>\n >;\n}\n\nexport function createSlackConnector(opts: SlackConnectorOptions): SlackConnectorBundle {\n const name = opts.name ?? 'slack';\n const baseUrl = (opts.baseUrl ?? 'https://slack.com/api').replace(/\\/+$/, '');\n\n const def: Connector = {\n name,\n label: opts.label ?? 'Slack',\n type: 'api',\n description: 'Slack Web API connector (static bot-token auth). Post and update messages, or call any Web API method.',\n icon: 'slack',\n authentication: { type: 'bearer', token: opts.token },\n // Defaulted by ConnectorSchema; set explicitly so the literal satisfies\n // the (post-parse) Connector output type.\n status: 'active',\n enabled: true,\n connectionTimeoutMs: 30000,\n requestTimeoutMs: 30000,\n actions: [\n {\n key: 'chat.postMessage',\n label: 'Post Message',\n description: 'Post a message to a channel, DM, or thread (Slack chat.postMessage).',\n inputSchema: {\n type: 'object',\n required: ['channel'],\n properties: {\n channel: { type: 'string', description: 'Channel id, user id, or #name' },\n text: { type: 'string', description: 'Message text' },\n thread_ts: { type: 'string', description: 'Thread root ts to reply into' },\n blocks: { type: 'array', description: 'Block Kit blocks' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n {\n key: 'chat.update',\n label: 'Update Message',\n description: 'Edit an existing message (Slack chat.update).',\n inputSchema: {\n type: 'object',\n required: ['channel', 'ts'],\n properties: {\n channel: { type: 'string', description: 'Channel id' },\n ts: { type: 'string', description: 'Timestamp of the message to update' },\n text: { type: 'string', description: 'New message text' },\n blocks: { type: 'array', description: 'New Block Kit blocks' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n {\n key: 'call',\n label: 'Call Web API Method',\n description: 'Escape hatch — call any Slack Web API method with arbitrary params.',\n inputSchema: {\n type: 'object',\n required: ['method'],\n properties: {\n method: { type: 'string', description: 'Web API method, e.g. conversations.list' },\n params: { type: 'object', description: 'Method parameters (JSON body)' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n ],\n };\n\n /** POST a JSON body to a Slack Web API method and normalise the result. */\n async function callSlack(method: string, params: Record<string, unknown>): Promise<SlackResult> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json; charset=utf-8',\n ...opts.defaultHeaders,\n Authorization: `Bearer ${opts.token}`,\n };\n\n const response = await resilientFetch(`${baseUrl}/${method}`, {\n method: 'POST',\n headers,\n body: JSON.stringify(params),\n }, { fetchImpl: opts.fetchImpl });\n\n // The Slack Web API always answers with JSON; `ok` is the real outcome.\n const body = (await response.json()) as Record<string, unknown>;\n const ok = body.ok === true;\n return {\n ok,\n status: response.status,\n body,\n error: ok ? undefined : (body.error as string | undefined),\n };\n }\n\n async function postMessage(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n return toRecord(await callSlack('chat.postMessage', input));\n }\n\n async function update(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n return toRecord(await callSlack('chat.update', input));\n }\n\n async function call(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n const method = String(input.method ?? '');\n if (!method) throw new Error(\"slack 'call' action: 'method' is required\");\n const params = (input.params as Record<string, unknown>) ?? {};\n return toRecord(await callSlack(method, params));\n }\n\n return {\n def,\n handlers: {\n 'chat.postMessage': postMessage,\n 'chat.update': update,\n call,\n },\n };\n}\n\nfunction toRecord(r: SlackResult): Record<string, unknown> {\n return { ok: r.ok, status: r.status, body: r.body, error: r.error };\n}\n\nfunction slackOutputSchema(): Record<string, unknown> {\n return {\n type: 'object',\n properties: {\n ok: { type: 'boolean', description: \"Slack's logical success flag\" },\n status: { type: 'number', description: 'HTTP status' },\n body: { type: 'object', description: 'Full Slack response payload' },\n error: { type: 'string', description: 'Slack error code when ok is false' },\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { Connector } from '@objectstack/spec/integration';\nimport { createSlackConnector, type SlackConnectorOptions } from './slack-connector.js';\n\n/**\n * Minimal surface of the automation engine this plugin depends on — the\n * connector registry from ADR-0018 §Addendum. Kept structural so the plugin\n * needs no runtime dependency on `@objectstack/service-automation`.\n */\nexport interface ConnectorRegistrySurface {\n registerConnector(\n def: Connector,\n handlers: Record<\n string,\n (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>\n >,\n ): void;\n unregisterConnector(name: string): void;\n}\n\nexport interface ConnectorSlackPluginOptions extends SlackConnectorOptions {}\n\n/**\n * ConnectorSlackPlugin — registers a Slack Web API connector on the automation\n * engine. The second reference concrete connector (ADR-0018 §Addendum); it\n * enables the ADR-0022 \"raw API call\" path — a flow's `connector_action` step\n * can dispatch to `slack.chat.postMessage` without the messaging stack.\n *\n * If no automation engine is present the plugin logs and skips — the connector\n * has nowhere to register, which is not an error.\n */\nexport class ConnectorSlackPlugin implements Plugin {\n name = 'com.objectstack.connector.slack';\n version = '1.0.0';\n type = 'standard' as const;\n // Ensure the automation engine (and its connector registry) is started first.\n dependencies = ['com.objectstack.service-automation'];\n\n private readonly options: ConnectorSlackPluginOptions;\n private connectorName?: string;\n private automation?: ConnectorRegistrySurface;\n\n constructor(options: ConnectorSlackPluginOptions) {\n this.options = options;\n }\n\n async init(_ctx: PluginContext): Promise<void> {\n // No services to register; the connector is registered in start() once\n // the automation engine is available.\n }\n\n async start(ctx: PluginContext): Promise<void> {\n let automation: ConnectorRegistrySurface | undefined;\n try {\n automation = ctx.getService<ConnectorRegistrySurface>('automation');\n } catch {\n automation = undefined;\n }\n\n if (!automation || typeof automation.registerConnector !== 'function') {\n ctx.logger.info('ConnectorSlackPlugin: no automation engine — Slack connector not registered');\n return;\n }\n\n const { def, handlers } = createSlackConnector(this.options);\n automation.registerConnector(def, handlers);\n this.automation = automation;\n this.connectorName = def.name;\n ctx.logger.info(`ConnectorSlackPlugin: Slack connector '${def.name}' registered`);\n }\n\n async stop(_ctx: PluginContext): Promise<void> {\n if (this.automation && this.connectorName) {\n try { this.automation.unregisterConnector(this.connectorName); } catch { /* ignore */ }\n }\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  // src/slack-connector.ts
2
+ import { resilientFetch } from "@objectstack/spec/shared";
2
3
  function createSlackConnector(opts) {
3
4
  const name = opts.name ?? "slack";
4
5
  const baseUrl = (opts.baseUrl ?? "https://slack.com/api").replace(/\/+$/, "");
5
- const doFetch = opts.fetchImpl ?? fetch;
6
6
  const def = {
7
7
  name,
8
8
  label: opts.label ?? "Slack",
@@ -71,11 +71,11 @@ function createSlackConnector(opts) {
71
71
  ...opts.defaultHeaders,
72
72
  Authorization: `Bearer ${opts.token}`
73
73
  };
74
- const response = await doFetch(`${baseUrl}/${method}`, {
74
+ const response = await resilientFetch(`${baseUrl}/${method}`, {
75
75
  method: "POST",
76
76
  headers,
77
77
  body: JSON.stringify(params)
78
- });
78
+ }, { fetchImpl: opts.fetchImpl });
79
79
  const body = await response.json();
80
80
  const ok = body.ok === true;
81
81
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/slack-connector.ts","../src/connector-slack-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Connector } from '@objectstack/spec/integration';\n\n/**\n * Slack connector — a *concrete* connector (ADR-0018 §Addendum) and the second\n * reference implementation after `@objectstack/connector-rest`. It produces a\n * {@link Connector} definition plus handlers for a small set of Slack Web API\n * actions, which the baseline `connector_action` node dispatches to.\n *\n * Scope (ADR-0022 \"raw API call\" path): this is the *integration mechanism*\n * for talking to Slack's API — \"post this exact text to this channel\". It is\n * deliberately **not** the human-notification layer: there is no preference\n * matrix, inbox, outbox, or thread/session semantics here. Those belong to a\n * `MessagingChannel` (ADR-0012/0013), which may itself delegate its transport\n * to this connector.\n *\n * Open-source scope: **static** auth only — a Slack **bot token** (`xoxb-…`),\n * supplied by the caller and sent as a bearer credential. OAuth2 install/refresh,\n * credential vaulting, and multi-tenant connection lifecycle are the enterprise\n * tier (see `../cloud/docs/design/connector-tiering.md`) and are out of scope.\n *\n * Slack quirk: the Web API returns HTTP `200` even on logical failure, with the\n * real outcome in the JSON body's `ok` field (and `error` on failure). Handlers\n * therefore surface `ok` from the payload, not from the HTTP status, and never\n * throw on a logical failure — the flow author branches on `${node.ok}`.\n */\n\nexport interface SlackConnectorOptions {\n /** Connector machine name (snake_case). Defaults to `slack`. */\n name?: string;\n /** Human-readable label. Defaults to `Slack`. */\n label?: string;\n /** Slack bot token (`xoxb-…`), sent as `Authorization: Bearer <token>`. */\n token: string;\n /** Web API base URL. Defaults to `https://slack.com/api`. */\n baseUrl?: string;\n /** Headers merged into every request (request-level headers win). */\n defaultHeaders?: Record<string, string>;\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\n/** Input accepted by `chat.postMessage`. */\nexport interface SlackPostMessageInput {\n /** Channel id (`C…`), user id (`U…`), or channel name (`#general`). */\n channel: string;\n /** Message text (fallback when `blocks` are present). */\n text?: string;\n /** Thread root `ts` to reply into an existing thread. */\n thread_ts?: string;\n /** Block Kit blocks. */\n blocks?: unknown[];\n [key: string]: unknown;\n}\n\n/** Generic Slack Web API result envelope. */\nexport interface SlackResult {\n /** Slack's logical success flag (from the JSON body, not the HTTP status). */\n ok: boolean;\n /** HTTP status (almost always 200 for the Slack Web API). */\n status: number;\n /** Full parsed Slack payload. */\n body: Record<string, unknown>;\n /** Slack error code when `ok` is false (e.g. `channel_not_found`). */\n error?: string;\n}\n\n/** A connector definition paired with its action handlers, ready for registerConnector(). */\nexport interface SlackConnectorBundle {\n def: Connector;\n handlers: Record<\n string,\n (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>\n >;\n}\n\nexport function createSlackConnector(opts: SlackConnectorOptions): SlackConnectorBundle {\n const name = opts.name ?? 'slack';\n const baseUrl = (opts.baseUrl ?? 'https://slack.com/api').replace(/\\/+$/, '');\n const doFetch = opts.fetchImpl ?? fetch;\n\n const def: Connector = {\n name,\n label: opts.label ?? 'Slack',\n type: 'api',\n description: 'Slack Web API connector (static bot-token auth). Post and update messages, or call any Web API method.',\n icon: 'slack',\n authentication: { type: 'bearer', token: opts.token },\n // Defaulted by ConnectorSchema; set explicitly so the literal satisfies\n // the (post-parse) Connector output type.\n status: 'active',\n enabled: true,\n connectionTimeoutMs: 30000,\n requestTimeoutMs: 30000,\n actions: [\n {\n key: 'chat.postMessage',\n label: 'Post Message',\n description: 'Post a message to a channel, DM, or thread (Slack chat.postMessage).',\n inputSchema: {\n type: 'object',\n required: ['channel'],\n properties: {\n channel: { type: 'string', description: 'Channel id, user id, or #name' },\n text: { type: 'string', description: 'Message text' },\n thread_ts: { type: 'string', description: 'Thread root ts to reply into' },\n blocks: { type: 'array', description: 'Block Kit blocks' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n {\n key: 'chat.update',\n label: 'Update Message',\n description: 'Edit an existing message (Slack chat.update).',\n inputSchema: {\n type: 'object',\n required: ['channel', 'ts'],\n properties: {\n channel: { type: 'string', description: 'Channel id' },\n ts: { type: 'string', description: 'Timestamp of the message to update' },\n text: { type: 'string', description: 'New message text' },\n blocks: { type: 'array', description: 'New Block Kit blocks' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n {\n key: 'call',\n label: 'Call Web API Method',\n description: 'Escape hatch — call any Slack Web API method with arbitrary params.',\n inputSchema: {\n type: 'object',\n required: ['method'],\n properties: {\n method: { type: 'string', description: 'Web API method, e.g. conversations.list' },\n params: { type: 'object', description: 'Method parameters (JSON body)' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n ],\n };\n\n /** POST a JSON body to a Slack Web API method and normalise the result. */\n async function callSlack(method: string, params: Record<string, unknown>): Promise<SlackResult> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json; charset=utf-8',\n ...opts.defaultHeaders,\n Authorization: `Bearer ${opts.token}`,\n };\n\n const response = await doFetch(`${baseUrl}/${method}`, {\n method: 'POST',\n headers,\n body: JSON.stringify(params),\n });\n\n // The Slack Web API always answers with JSON; `ok` is the real outcome.\n const body = (await response.json()) as Record<string, unknown>;\n const ok = body.ok === true;\n return {\n ok,\n status: response.status,\n body,\n error: ok ? undefined : (body.error as string | undefined),\n };\n }\n\n async function postMessage(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n return toRecord(await callSlack('chat.postMessage', input));\n }\n\n async function update(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n return toRecord(await callSlack('chat.update', input));\n }\n\n async function call(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n const method = String(input.method ?? '');\n if (!method) throw new Error(\"slack 'call' action: 'method' is required\");\n const params = (input.params as Record<string, unknown>) ?? {};\n return toRecord(await callSlack(method, params));\n }\n\n return {\n def,\n handlers: {\n 'chat.postMessage': postMessage,\n 'chat.update': update,\n call,\n },\n };\n}\n\nfunction toRecord(r: SlackResult): Record<string, unknown> {\n return { ok: r.ok, status: r.status, body: r.body, error: r.error };\n}\n\nfunction slackOutputSchema(): Record<string, unknown> {\n return {\n type: 'object',\n properties: {\n ok: { type: 'boolean', description: \"Slack's logical success flag\" },\n status: { type: 'number', description: 'HTTP status' },\n body: { type: 'object', description: 'Full Slack response payload' },\n error: { type: 'string', description: 'Slack error code when ok is false' },\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { Connector } from '@objectstack/spec/integration';\nimport { createSlackConnector, type SlackConnectorOptions } from './slack-connector.js';\n\n/**\n * Minimal surface of the automation engine this plugin depends on — the\n * connector registry from ADR-0018 §Addendum. Kept structural so the plugin\n * needs no runtime dependency on `@objectstack/service-automation`.\n */\nexport interface ConnectorRegistrySurface {\n registerConnector(\n def: Connector,\n handlers: Record<\n string,\n (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>\n >,\n ): void;\n unregisterConnector(name: string): void;\n}\n\nexport interface ConnectorSlackPluginOptions extends SlackConnectorOptions {}\n\n/**\n * ConnectorSlackPlugin — registers a Slack Web API connector on the automation\n * engine. The second reference concrete connector (ADR-0018 §Addendum); it\n * enables the ADR-0022 \"raw API call\" path — a flow's `connector_action` step\n * can dispatch to `slack.chat.postMessage` without the messaging stack.\n *\n * If no automation engine is present the plugin logs and skips — the connector\n * has nowhere to register, which is not an error.\n */\nexport class ConnectorSlackPlugin implements Plugin {\n name = 'com.objectstack.connector.slack';\n version = '1.0.0';\n type = 'standard' as const;\n // Ensure the automation engine (and its connector registry) is started first.\n dependencies = ['com.objectstack.service-automation'];\n\n private readonly options: ConnectorSlackPluginOptions;\n private connectorName?: string;\n private automation?: ConnectorRegistrySurface;\n\n constructor(options: ConnectorSlackPluginOptions) {\n this.options = options;\n }\n\n async init(_ctx: PluginContext): Promise<void> {\n // No services to register; the connector is registered in start() once\n // the automation engine is available.\n }\n\n async start(ctx: PluginContext): Promise<void> {\n let automation: ConnectorRegistrySurface | undefined;\n try {\n automation = ctx.getService<ConnectorRegistrySurface>('automation');\n } catch {\n automation = undefined;\n }\n\n if (!automation || typeof automation.registerConnector !== 'function') {\n ctx.logger.info('ConnectorSlackPlugin: no automation engine — Slack connector not registered');\n return;\n }\n\n const { def, handlers } = createSlackConnector(this.options);\n automation.registerConnector(def, handlers);\n this.automation = automation;\n this.connectorName = def.name;\n ctx.logger.info(`ConnectorSlackPlugin: Slack connector '${def.name}' registered`);\n }\n\n async stop(_ctx: PluginContext): Promise<void> {\n if (this.automation && this.connectorName) {\n try { this.automation.unregisterConnector(this.connectorName); } catch { /* ignore */ }\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/slack-connector.ts","../src/connector-slack-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Connector } from '@objectstack/spec/integration';\nimport { resilientFetch } from '@objectstack/spec/shared';\n\n/**\n * Slack connector — a *concrete* connector (ADR-0018 §Addendum) and the second\n * reference implementation after `@objectstack/connector-rest`. It produces a\n * {@link Connector} definition plus handlers for a small set of Slack Web API\n * actions, which the baseline `connector_action` node dispatches to.\n *\n * Scope (ADR-0022 \"raw API call\" path): this is the *integration mechanism*\n * for talking to Slack's API — \"post this exact text to this channel\". It is\n * deliberately **not** the human-notification layer: there is no preference\n * matrix, inbox, outbox, or thread/session semantics here. Those belong to a\n * `MessagingChannel` (ADR-0012/0013), which may itself delegate its transport\n * to this connector.\n *\n * Open-source scope: **static** auth only — a Slack **bot token** (`xoxb-…`),\n * supplied by the caller and sent as a bearer credential. OAuth2 install/refresh,\n * credential vaulting, and multi-tenant connection lifecycle are the enterprise\n * tier (see `../cloud/docs/design/connector-tiering.md`) and are out of scope.\n *\n * Slack quirk: the Web API returns HTTP `200` even on logical failure, with the\n * real outcome in the JSON body's `ok` field (and `error` on failure). Handlers\n * therefore surface `ok` from the payload, not from the HTTP status, and never\n * throw on a logical failure — the flow author branches on `${node.ok}`.\n */\n\nexport interface SlackConnectorOptions {\n /** Connector machine name (snake_case). Defaults to `slack`. */\n name?: string;\n /** Human-readable label. Defaults to `Slack`. */\n label?: string;\n /** Slack bot token (`xoxb-…`), sent as `Authorization: Bearer <token>`. */\n token: string;\n /** Web API base URL. Defaults to `https://slack.com/api`. */\n baseUrl?: string;\n /** Headers merged into every request (request-level headers win). */\n defaultHeaders?: Record<string, string>;\n /** Injected for tests; defaults to the global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\n/** Input accepted by `chat.postMessage`. */\nexport interface SlackPostMessageInput {\n /** Channel id (`C…`), user id (`U…`), or channel name (`#general`). */\n channel: string;\n /** Message text (fallback when `blocks` are present). */\n text?: string;\n /** Thread root `ts` to reply into an existing thread. */\n thread_ts?: string;\n /** Block Kit blocks. */\n blocks?: unknown[];\n [key: string]: unknown;\n}\n\n/** Generic Slack Web API result envelope. */\nexport interface SlackResult {\n /** Slack's logical success flag (from the JSON body, not the HTTP status). */\n ok: boolean;\n /** HTTP status (almost always 200 for the Slack Web API). */\n status: number;\n /** Full parsed Slack payload. */\n body: Record<string, unknown>;\n /** Slack error code when `ok` is false (e.g. `channel_not_found`). */\n error?: string;\n}\n\n/** A connector definition paired with its action handlers, ready for registerConnector(). */\nexport interface SlackConnectorBundle {\n def: Connector;\n handlers: Record<\n string,\n (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>\n >;\n}\n\nexport function createSlackConnector(opts: SlackConnectorOptions): SlackConnectorBundle {\n const name = opts.name ?? 'slack';\n const baseUrl = (opts.baseUrl ?? 'https://slack.com/api').replace(/\\/+$/, '');\n\n const def: Connector = {\n name,\n label: opts.label ?? 'Slack',\n type: 'api',\n description: 'Slack Web API connector (static bot-token auth). Post and update messages, or call any Web API method.',\n icon: 'slack',\n authentication: { type: 'bearer', token: opts.token },\n // Defaulted by ConnectorSchema; set explicitly so the literal satisfies\n // the (post-parse) Connector output type.\n status: 'active',\n enabled: true,\n connectionTimeoutMs: 30000,\n requestTimeoutMs: 30000,\n actions: [\n {\n key: 'chat.postMessage',\n label: 'Post Message',\n description: 'Post a message to a channel, DM, or thread (Slack chat.postMessage).',\n inputSchema: {\n type: 'object',\n required: ['channel'],\n properties: {\n channel: { type: 'string', description: 'Channel id, user id, or #name' },\n text: { type: 'string', description: 'Message text' },\n thread_ts: { type: 'string', description: 'Thread root ts to reply into' },\n blocks: { type: 'array', description: 'Block Kit blocks' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n {\n key: 'chat.update',\n label: 'Update Message',\n description: 'Edit an existing message (Slack chat.update).',\n inputSchema: {\n type: 'object',\n required: ['channel', 'ts'],\n properties: {\n channel: { type: 'string', description: 'Channel id' },\n ts: { type: 'string', description: 'Timestamp of the message to update' },\n text: { type: 'string', description: 'New message text' },\n blocks: { type: 'array', description: 'New Block Kit blocks' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n {\n key: 'call',\n label: 'Call Web API Method',\n description: 'Escape hatch — call any Slack Web API method with arbitrary params.',\n inputSchema: {\n type: 'object',\n required: ['method'],\n properties: {\n method: { type: 'string', description: 'Web API method, e.g. conversations.list' },\n params: { type: 'object', description: 'Method parameters (JSON body)' },\n },\n },\n outputSchema: slackOutputSchema(),\n },\n ],\n };\n\n /** POST a JSON body to a Slack Web API method and normalise the result. */\n async function callSlack(method: string, params: Record<string, unknown>): Promise<SlackResult> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json; charset=utf-8',\n ...opts.defaultHeaders,\n Authorization: `Bearer ${opts.token}`,\n };\n\n const response = await resilientFetch(`${baseUrl}/${method}`, {\n method: 'POST',\n headers,\n body: JSON.stringify(params),\n }, { fetchImpl: opts.fetchImpl });\n\n // The Slack Web API always answers with JSON; `ok` is the real outcome.\n const body = (await response.json()) as Record<string, unknown>;\n const ok = body.ok === true;\n return {\n ok,\n status: response.status,\n body,\n error: ok ? undefined : (body.error as string | undefined),\n };\n }\n\n async function postMessage(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n return toRecord(await callSlack('chat.postMessage', input));\n }\n\n async function update(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n return toRecord(await callSlack('chat.update', input));\n }\n\n async function call(input: Record<string, unknown>): Promise<Record<string, unknown>> {\n const method = String(input.method ?? '');\n if (!method) throw new Error(\"slack 'call' action: 'method' is required\");\n const params = (input.params as Record<string, unknown>) ?? {};\n return toRecord(await callSlack(method, params));\n }\n\n return {\n def,\n handlers: {\n 'chat.postMessage': postMessage,\n 'chat.update': update,\n call,\n },\n };\n}\n\nfunction toRecord(r: SlackResult): Record<string, unknown> {\n return { ok: r.ok, status: r.status, body: r.body, error: r.error };\n}\n\nfunction slackOutputSchema(): Record<string, unknown> {\n return {\n type: 'object',\n properties: {\n ok: { type: 'boolean', description: \"Slack's logical success flag\" },\n status: { type: 'number', description: 'HTTP status' },\n body: { type: 'object', description: 'Full Slack response payload' },\n error: { type: 'string', description: 'Slack error code when ok is false' },\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { Connector } from '@objectstack/spec/integration';\nimport { createSlackConnector, type SlackConnectorOptions } from './slack-connector.js';\n\n/**\n * Minimal surface of the automation engine this plugin depends on — the\n * connector registry from ADR-0018 §Addendum. Kept structural so the plugin\n * needs no runtime dependency on `@objectstack/service-automation`.\n */\nexport interface ConnectorRegistrySurface {\n registerConnector(\n def: Connector,\n handlers: Record<\n string,\n (input: Record<string, unknown>, ctx: unknown) => Promise<Record<string, unknown>>\n >,\n ): void;\n unregisterConnector(name: string): void;\n}\n\nexport interface ConnectorSlackPluginOptions extends SlackConnectorOptions {}\n\n/**\n * ConnectorSlackPlugin — registers a Slack Web API connector on the automation\n * engine. The second reference concrete connector (ADR-0018 §Addendum); it\n * enables the ADR-0022 \"raw API call\" path — a flow's `connector_action` step\n * can dispatch to `slack.chat.postMessage` without the messaging stack.\n *\n * If no automation engine is present the plugin logs and skips — the connector\n * has nowhere to register, which is not an error.\n */\nexport class ConnectorSlackPlugin implements Plugin {\n name = 'com.objectstack.connector.slack';\n version = '1.0.0';\n type = 'standard' as const;\n // Ensure the automation engine (and its connector registry) is started first.\n dependencies = ['com.objectstack.service-automation'];\n\n private readonly options: ConnectorSlackPluginOptions;\n private connectorName?: string;\n private automation?: ConnectorRegistrySurface;\n\n constructor(options: ConnectorSlackPluginOptions) {\n this.options = options;\n }\n\n async init(_ctx: PluginContext): Promise<void> {\n // No services to register; the connector is registered in start() once\n // the automation engine is available.\n }\n\n async start(ctx: PluginContext): Promise<void> {\n let automation: ConnectorRegistrySurface | undefined;\n try {\n automation = ctx.getService<ConnectorRegistrySurface>('automation');\n } catch {\n automation = undefined;\n }\n\n if (!automation || typeof automation.registerConnector !== 'function') {\n ctx.logger.info('ConnectorSlackPlugin: no automation engine — Slack connector not registered');\n return;\n }\n\n const { def, handlers } = createSlackConnector(this.options);\n automation.registerConnector(def, handlers);\n this.automation = automation;\n this.connectorName = def.name;\n ctx.logger.info(`ConnectorSlackPlugin: Slack connector '${def.name}' registered`);\n }\n\n async stop(_ctx: PluginContext): Promise<void> {\n if (this.automation && this.connectorName) {\n try { this.automation.unregisterConnector(this.connectorName); } catch { /* ignore */ }\n }\n }\n}\n"],"mappings":";AAGA,SAAS,sBAAsB;AA2ExB,SAAS,qBAAqB,MAAmD;AACpF,QAAM,OAAO,KAAK,QAAQ;AAC1B,QAAM,WAAW,KAAK,WAAW,yBAAyB,QAAQ,QAAQ,EAAE;AAE5E,QAAM,MAAiB;AAAA,IACnB;AAAA,IACA,OAAO,KAAK,SAAS;AAAA,IACrB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,IACN,gBAAgB,EAAE,MAAM,UAAU,OAAO,KAAK,MAAM;AAAA;AAAA;AAAA,IAGpD,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,qBAAqB;AAAA,IACrB,kBAAkB;AAAA,IAClB,SAAS;AAAA,MACL;AAAA,QACI,KAAK;AAAA,QACL,OAAO;AAAA,QACP,aAAa;AAAA,QACb,aAAa;AAAA,UACT,MAAM;AAAA,UACN,UAAU,CAAC,SAAS;AAAA,UACpB,YAAY;AAAA,YACR,SAAS,EAAE,MAAM,UAAU,aAAa,gCAAgC;AAAA,YACxE,MAAM,EAAE,MAAM,UAAU,aAAa,eAAe;AAAA,YACpD,WAAW,EAAE,MAAM,UAAU,aAAa,+BAA+B;AAAA,YACzE,QAAQ,EAAE,MAAM,SAAS,aAAa,mBAAmB;AAAA,UAC7D;AAAA,QACJ;AAAA,QACA,cAAc,kBAAkB;AAAA,MACpC;AAAA,MACA;AAAA,QACI,KAAK;AAAA,QACL,OAAO;AAAA,QACP,aAAa;AAAA,QACb,aAAa;AAAA,UACT,MAAM;AAAA,UACN,UAAU,CAAC,WAAW,IAAI;AAAA,UAC1B,YAAY;AAAA,YACR,SAAS,EAAE,MAAM,UAAU,aAAa,aAAa;AAAA,YACrD,IAAI,EAAE,MAAM,UAAU,aAAa,qCAAqC;AAAA,YACxE,MAAM,EAAE,MAAM,UAAU,aAAa,mBAAmB;AAAA,YACxD,QAAQ,EAAE,MAAM,SAAS,aAAa,uBAAuB;AAAA,UACjE;AAAA,QACJ;AAAA,QACA,cAAc,kBAAkB;AAAA,MACpC;AAAA,MACA;AAAA,QACI,KAAK;AAAA,QACL,OAAO;AAAA,QACP,aAAa;AAAA,QACb,aAAa;AAAA,UACT,MAAM;AAAA,UACN,UAAU,CAAC,QAAQ;AAAA,UACnB,YAAY;AAAA,YACR,QAAQ,EAAE,MAAM,UAAU,aAAa,0CAA0C;AAAA,YACjF,QAAQ,EAAE,MAAM,UAAU,aAAa,gCAAgC;AAAA,UAC3E;AAAA,QACJ;AAAA,QACA,cAAc,kBAAkB;AAAA,MACpC;AAAA,IACJ;AAAA,EACJ;AAGA,iBAAe,UAAU,QAAgB,QAAuD;AAC5F,UAAM,UAAkC;AAAA,MACpC,gBAAgB;AAAA,MAChB,GAAG,KAAK;AAAA,MACR,eAAe,UAAU,KAAK,KAAK;AAAA,IACvC;AAEA,UAAM,WAAW,MAAM,eAAe,GAAG,OAAO,IAAI,MAAM,IAAI;AAAA,MAC1D,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,MAAM;AAAA,IAC/B,GAAG,EAAE,WAAW,KAAK,UAAU,CAAC;AAGhC,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,UAAM,KAAK,KAAK,OAAO;AACvB,WAAO;AAAA,MACH;AAAA,MACA,QAAQ,SAAS;AAAA,MACjB;AAAA,MACA,OAAO,KAAK,SAAa,KAAK;AAAA,IAClC;AAAA,EACJ;AAEA,iBAAe,YAAY,OAAkE;AACzF,WAAO,SAAS,MAAM,UAAU,oBAAoB,KAAK,CAAC;AAAA,EAC9D;AAEA,iBAAe,OAAO,OAAkE;AACpF,WAAO,SAAS,MAAM,UAAU,eAAe,KAAK,CAAC;AAAA,EACzD;AAEA,iBAAe,KAAK,OAAkE;AAClF,UAAM,SAAS,OAAO,MAAM,UAAU,EAAE;AACxC,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,2CAA2C;AACxE,UAAM,SAAU,MAAM,UAAsC,CAAC;AAC7D,WAAO,SAAS,MAAM,UAAU,QAAQ,MAAM,CAAC;AAAA,EACnD;AAEA,SAAO;AAAA,IACH;AAAA,IACA,UAAU;AAAA,MACN,oBAAoB;AAAA,MACpB,eAAe;AAAA,MACf;AAAA,IACJ;AAAA,EACJ;AACJ;AAEA,SAAS,SAAS,GAAyC;AACvD,SAAO,EAAE,IAAI,EAAE,IAAI,QAAQ,EAAE,QAAQ,MAAM,EAAE,MAAM,OAAO,EAAE,MAAM;AACtE;AAEA,SAAS,oBAA6C;AAClD,SAAO;AAAA,IACH,MAAM;AAAA,IACN,YAAY;AAAA,MACR,IAAI,EAAE,MAAM,WAAW,aAAa,+BAA+B;AAAA,MACnE,QAAQ,EAAE,MAAM,UAAU,aAAa,cAAc;AAAA,MACrD,MAAM,EAAE,MAAM,UAAU,aAAa,8BAA8B;AAAA,MACnE,OAAO,EAAE,MAAM,UAAU,aAAa,oCAAoC;AAAA,IAC9E;AAAA,EACJ;AACJ;;;AChLO,IAAM,uBAAN,MAA6C;AAAA,EAWhD,YAAY,SAAsC;AAVlD,gBAAO;AACP,mBAAU;AACV,gBAAO;AAEP;AAAA,wBAAe,CAAC,oCAAoC;AAOhD,SAAK,UAAU;AAAA,EACnB;AAAA,EAEA,MAAM,KAAK,MAAoC;AAAA,EAG/C;AAAA,EAEA,MAAM,MAAM,KAAmC;AAC3C,QAAI;AACJ,QAAI;AACA,mBAAa,IAAI,WAAqC,YAAY;AAAA,IACtE,QAAQ;AACJ,mBAAa;AAAA,IACjB;AAEA,QAAI,CAAC,cAAc,OAAO,WAAW,sBAAsB,YAAY;AACnE,UAAI,OAAO,KAAK,kFAA6E;AAC7F;AAAA,IACJ;AAEA,UAAM,EAAE,KAAK,SAAS,IAAI,qBAAqB,KAAK,OAAO;AAC3D,eAAW,kBAAkB,KAAK,QAAQ;AAC1C,SAAK,aAAa;AAClB,SAAK,gBAAgB,IAAI;AACzB,QAAI,OAAO,KAAK,0CAA0C,IAAI,IAAI,cAAc;AAAA,EACpF;AAAA,EAEA,MAAM,KAAK,MAAoC;AAC3C,QAAI,KAAK,cAAc,KAAK,eAAe;AACvC,UAAI;AAAE,aAAK,WAAW,oBAAoB,KAAK,aAAa;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IAC1F;AAAA,EACJ;AACJ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/connector-slack",
3
- "version": "7.9.0",
3
+ "version": "8.0.1",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Slack Web API connector for ObjectStack — registers `chat.postMessage` / `chat.update` / `call` actions on the automation engine's connector registry (ADR-0018 §Addendum, ADR-0022).",
6
6
  "main": "dist/index.js",
@@ -13,14 +13,14 @@
13
13
  }
14
14
  },
15
15
  "dependencies": {
16
- "@objectstack/core": "7.9.0",
17
- "@objectstack/spec": "7.9.0"
16
+ "@objectstack/core": "8.0.1",
17
+ "@objectstack/spec": "8.0.1"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@types/node": "^25.9.1",
21
21
  "typescript": "^6.0.3",
22
22
  "vitest": "^4.1.8",
23
- "@objectstack/service-automation": "7.9.0"
23
+ "@objectstack/service-automation": "8.0.1"
24
24
  },
25
25
  "keywords": [
26
26
  "objectstack",
@@ -1,6 +1,7 @@
1
1
  // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
2
 
3
3
  import type { Connector } from '@objectstack/spec/integration';
4
+ import { resilientFetch } from '@objectstack/spec/shared';
4
5
 
5
6
  /**
6
7
  * Slack connector — a *concrete* connector (ADR-0018 §Addendum) and the second
@@ -78,7 +79,6 @@ export interface SlackConnectorBundle {
78
79
  export function createSlackConnector(opts: SlackConnectorOptions): SlackConnectorBundle {
79
80
  const name = opts.name ?? 'slack';
80
81
  const baseUrl = (opts.baseUrl ?? 'https://slack.com/api').replace(/\/+$/, '');
81
- const doFetch = opts.fetchImpl ?? fetch;
82
82
 
83
83
  const def: Connector = {
84
84
  name,
@@ -151,11 +151,11 @@ export function createSlackConnector(opts: SlackConnectorOptions): SlackConnecto
151
151
  Authorization: `Bearer ${opts.token}`,
152
152
  };
153
153
 
154
- const response = await doFetch(`${baseUrl}/${method}`, {
154
+ const response = await resilientFetch(`${baseUrl}/${method}`, {
155
155
  method: 'POST',
156
156
  headers,
157
157
  body: JSON.stringify(params),
158
- });
158
+ }, { fetchImpl: opts.fetchImpl });
159
159
 
160
160
  // The Slack Web API always answers with JSON; `ok` is the real outcome.
161
161
  const body = (await response.json()) as Record<string, unknown>;