@objectstack/connector-slack 17.0.0 → 17.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,174 @@
1
1
  # @objectstack/connector-slack
2
2
 
3
+ ## 17.2.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 6d5c4fa: Release these plugins' resources from `destroy()`, the teardown hook the kernel
8
+ actually calls (#10371). `Plugin` declares `init()`, `start?(ctx)` and
9
+ `destroy?()` — and no `stop()` — so `ObjectKernel.performShutdown()` and
10
+ `LiteKernel.destroy()`, which walk the plugins in reverse calling
11
+ `plugin.destroy()`, walked straight past every plugin whose teardown was spelled
12
+ `stop()`. `await kernel.shutdown()` resolved with the reports dispatcher still
13
+ armed, the REST/OpenAPI/Slack connectors still registered on the automation
14
+ engine, the approvals SLA escalation job still scheduled, and the knowledge
15
+ event-sync subscription still open.
16
+
17
+ Each teardown body now lives in `destroy()`. `stop()` is retained as a
18
+ delegating alias with its parameter made optional, so an embedder that learned
19
+ to call it directly — precisely because the kernel never did — keeps working
20
+ unchanged. No export is removed and the `Plugin` interface is untouched.
21
+
22
+ Same defect as #9371 in `@objectstack/service-messaging`, which surfaced as
23
+ fully green test runs exiting 1 on `EnvironmentTeardownError` and being evicted
24
+ from the merge queue.
25
+ - Updated dependencies [6936d07]
26
+ - Updated dependencies [59eb04d]
27
+ - Updated dependencies [9f05b7d]
28
+ - Updated dependencies [3b2af5e]
29
+ - Updated dependencies [7d2d112]
30
+ - Updated dependencies [5fa0d72]
31
+ - Updated dependencies [02b3b07]
32
+ - Updated dependencies [914c413]
33
+ - Updated dependencies [55809a0]
34
+ - Updated dependencies [ee2ff45]
35
+ - Updated dependencies [47cd3ec]
36
+ - Updated dependencies [52db1d1]
37
+ - Updated dependencies [5649efb]
38
+ - Updated dependencies [9d7d2de]
39
+ - Updated dependencies [c815c50]
40
+ - Updated dependencies [795ea05]
41
+ - Updated dependencies [2306a76]
42
+ - Updated dependencies [e5ea701]
43
+ - Updated dependencies [a40dcc1]
44
+ - Updated dependencies [def0d3e]
45
+ - Updated dependencies [8d0bb79]
46
+ - Updated dependencies [5acb58d]
47
+ - Updated dependencies [2e3cf95]
48
+ - Updated dependencies [4c93387]
49
+ - Updated dependencies [504c8d5]
50
+ - Updated dependencies [a037f7c]
51
+ - Updated dependencies [3ee8ddf]
52
+ - Updated dependencies [16cef97]
53
+ - Updated dependencies [a79bd35]
54
+ - Updated dependencies [6ceaa4b]
55
+ - Updated dependencies [15ea214]
56
+ - Updated dependencies [de19489]
57
+ - Updated dependencies [c684d00]
58
+ - Updated dependencies [923c424]
59
+ - Updated dependencies [1ec36b7]
60
+ - Updated dependencies [5f2e54c]
61
+ - Updated dependencies [189373b]
62
+ - Updated dependencies [35ad101]
63
+ - Updated dependencies [ceb33a9]
64
+ - Updated dependencies [73d9795]
65
+ - Updated dependencies [8012960]
66
+ - Updated dependencies [f34f56b]
67
+ - Updated dependencies [f399618]
68
+ - Updated dependencies [75e9301]
69
+ - Updated dependencies [2810695]
70
+ - @objectstack/spec@17.2.0
71
+ - @objectstack/core@17.2.0
72
+
73
+ ## 17.1.0
74
+
75
+ ### Patch Changes
76
+
77
+ - Updated dependencies [56656aa]
78
+ - Updated dependencies [07e630e]
79
+ - Updated dependencies [2f65b1b]
80
+ - Updated dependencies [720ee95]
81
+ - Updated dependencies [f287435]
82
+ - Updated dependencies [2782805]
83
+ - Updated dependencies [e43d63a]
84
+ - Updated dependencies [9aa8890]
85
+ - Updated dependencies [7c9c1dd]
86
+ - Updated dependencies [75b7c24]
87
+ - Updated dependencies [d5552ca]
88
+ - Updated dependencies [d9813a9]
89
+ - Updated dependencies [8640fb2]
90
+ - Updated dependencies [2420641]
91
+ - Updated dependencies [2ad91c3]
92
+ - Updated dependencies [f57fb38]
93
+ - Updated dependencies [00777a0]
94
+ - Updated dependencies [d491625]
95
+ - Updated dependencies [420804d]
96
+ - Updated dependencies [716ac9b]
97
+ - Updated dependencies [a38408a]
98
+ - Updated dependencies [62b1427]
99
+ - Updated dependencies [7ea1372]
100
+ - Updated dependencies [23abe27]
101
+ - Updated dependencies [985a9cd]
102
+ - Updated dependencies [5f5e234]
103
+ - Updated dependencies [a8189ae]
104
+ - Updated dependencies [26e70fb]
105
+ - Updated dependencies [42b05af]
106
+ - Updated dependencies [2b292ce]
107
+ - Updated dependencies [abcf853]
108
+ - Updated dependencies [8b9eba5]
109
+ - Updated dependencies [d575779]
110
+ - Updated dependencies [94f7ef8]
111
+ - Updated dependencies [c5ac5e4]
112
+ - Updated dependencies [a777944]
113
+ - Updated dependencies [dd88e1c]
114
+ - Updated dependencies [856527c]
115
+ - Updated dependencies [870f710]
116
+ - Updated dependencies [79c46da]
117
+ - Updated dependencies [7ff3975]
118
+ - Updated dependencies [29d055b]
119
+ - Updated dependencies [65589d6]
120
+ - Updated dependencies [2c86fe3]
121
+ - Updated dependencies [e196c6a]
122
+ - Updated dependencies [24173e9]
123
+ - Updated dependencies [4ab7523]
124
+ - Updated dependencies [19539b4]
125
+ - Updated dependencies [f8eb736]
126
+ - Updated dependencies [11b779e]
127
+ - Updated dependencies [739fe5b]
128
+ - Updated dependencies [4bfe1a5]
129
+ - Updated dependencies [2065e31]
130
+ - Updated dependencies [b69d0f5]
131
+ - Updated dependencies [4d47afe]
132
+ - Updated dependencies [e4e5c6e]
133
+ - Updated dependencies [9a56784]
134
+ - Updated dependencies [d00d2f6]
135
+ - Updated dependencies [df0c12d]
136
+ - Updated dependencies [d31785f]
137
+ - Updated dependencies [c308a4f]
138
+ - Updated dependencies [e2899f6]
139
+ - Updated dependencies [3851f87]
140
+ - Updated dependencies [2a29caa]
141
+ - Updated dependencies [09a6eee]
142
+ - Updated dependencies [1a7f907]
143
+ - Updated dependencies [cd455c8]
144
+ - Updated dependencies [e1bb0ca]
145
+ - Updated dependencies [30d3752]
146
+ - Updated dependencies [c80e7ae]
147
+ - Updated dependencies [09a9a8a]
148
+ - Updated dependencies [07026cf]
149
+ - Updated dependencies [5d4f3d5]
150
+ - Updated dependencies [4d80e8b]
151
+ - Updated dependencies [30b1c63]
152
+ - Updated dependencies [079b457]
153
+ - Updated dependencies [e43b211]
154
+ - Updated dependencies [890b38f]
155
+ - Updated dependencies [8bee54b]
156
+ - Updated dependencies [7a537ce]
157
+ - Updated dependencies [593c4bf]
158
+ - Updated dependencies [ff08691]
159
+ - Updated dependencies [60e0f90]
160
+ - Updated dependencies [90c5285]
161
+ - Updated dependencies [402c125]
162
+ - Updated dependencies [7901b2d]
163
+ - Updated dependencies [56bca91]
164
+ - Updated dependencies [79394d7]
165
+ - Updated dependencies [730fd9a]
166
+ - Updated dependencies [44bc51d]
167
+ - Updated dependencies [73cfddf]
168
+ - Updated dependencies [d634e66]
169
+ - @objectstack/spec@17.1.0
170
+ - @objectstack/core@17.1.0
171
+
3
172
  ## 17.0.0
4
173
 
5
174
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -99,7 +99,30 @@ declare class ConnectorSlackPlugin implements Plugin {
99
99
  constructor(options: ConnectorSlackPluginOptions);
100
100
  init(_ctx: PluginContext): Promise<void>;
101
101
  start(ctx: PluginContext): Promise<void>;
102
- stop(_ctx: PluginContext): Promise<void>;
102
+ /**
103
+ * The kernel's teardown hook (`Plugin.destroy?()`, core `types.ts`) — the
104
+ * ONLY teardown entry point `ObjectKernel.performShutdown()` and
105
+ * `LiteKernel.destroy()` invoke.
106
+ *
107
+ * [#10371] IT USED TO BE `stop()`, WHICH NOTHING CALLED. `Plugin` declares
108
+ * `init()`, `start?()` and `destroy?()` and no `stop()`, so the kernel
109
+ * walked past this plugin at shutdown and the Slack connector stayed registered in the automation
110
+ * engine for the lifetime of the process. `start()` IS on the
111
+ * interface, so the pair read as symmetric in review — that asymmetry is
112
+ * what let the same shape survive in six packages at once.
113
+ *
114
+ * No timers here, so this instance never cost a merge-queue eviction the
115
+ * way the `plugin-reports` / `service-messaging` members did (#9371). The
116
+ * class is the same one either way: a teardown the kernel does not reach.
117
+ */
118
+ destroy(): Promise<void>;
119
+ /**
120
+ * Retained alias for {@link destroy}. Kept because it is public API of an
121
+ * exported class, and removing it would break an embedder who learned to
122
+ * call it directly precisely BECAUSE the kernel never did. Prefer kernel
123
+ * shutdown; direct callers keep working unchanged.
124
+ */
125
+ stop(_ctx?: PluginContext): Promise<void>;
103
126
  }
104
127
 
105
128
  export { type ConnectorRegistrySurface, ConnectorSlackPlugin, type ConnectorSlackPluginOptions, type SlackConnectorBundle, type SlackConnectorOptions, type SlackPostMessageInput, type SlackResult, createSlackConnector };
package/dist/index.d.ts CHANGED
@@ -99,7 +99,30 @@ declare class ConnectorSlackPlugin implements Plugin {
99
99
  constructor(options: ConnectorSlackPluginOptions);
100
100
  init(_ctx: PluginContext): Promise<void>;
101
101
  start(ctx: PluginContext): Promise<void>;
102
- stop(_ctx: PluginContext): Promise<void>;
102
+ /**
103
+ * The kernel's teardown hook (`Plugin.destroy?()`, core `types.ts`) — the
104
+ * ONLY teardown entry point `ObjectKernel.performShutdown()` and
105
+ * `LiteKernel.destroy()` invoke.
106
+ *
107
+ * [#10371] IT USED TO BE `stop()`, WHICH NOTHING CALLED. `Plugin` declares
108
+ * `init()`, `start?()` and `destroy?()` and no `stop()`, so the kernel
109
+ * walked past this plugin at shutdown and the Slack connector stayed registered in the automation
110
+ * engine for the lifetime of the process. `start()` IS on the
111
+ * interface, so the pair read as symmetric in review — that asymmetry is
112
+ * what let the same shape survive in six packages at once.
113
+ *
114
+ * No timers here, so this instance never cost a merge-queue eviction the
115
+ * way the `plugin-reports` / `service-messaging` members did (#9371). The
116
+ * class is the same one either way: a teardown the kernel does not reach.
117
+ */
118
+ destroy(): Promise<void>;
119
+ /**
120
+ * Retained alias for {@link destroy}. Kept because it is public API of an
121
+ * exported class, and removing it would break an embedder who learned to
122
+ * call it directly precisely BECAUSE the kernel never did. Prefer kernel
123
+ * shutdown; direct callers keep working unchanged.
124
+ */
125
+ stop(_ctx?: PluginContext): Promise<void>;
103
126
  }
104
127
 
105
128
  export { type ConnectorRegistrySurface, ConnectorSlackPlugin, type ConnectorSlackPluginOptions, type SlackConnectorBundle, type SlackConnectorOptions, type SlackPostMessageInput, type SlackResult, createSlackConnector };
package/dist/index.js CHANGED
@@ -177,13 +177,40 @@ var ConnectorSlackPlugin = class {
177
177
  this.connectorName = def.name;
178
178
  ctx.logger.info(`ConnectorSlackPlugin: Slack connector '${def.name}' registered`);
179
179
  }
180
- async stop(_ctx) {
180
+ /**
181
+ * The kernel's teardown hook (`Plugin.destroy?()`, core `types.ts`) — the
182
+ * ONLY teardown entry point `ObjectKernel.performShutdown()` and
183
+ * `LiteKernel.destroy()` invoke.
184
+ *
185
+ * [#10371] IT USED TO BE `stop()`, WHICH NOTHING CALLED. `Plugin` declares
186
+ * `init()`, `start?()` and `destroy?()` and no `stop()`, so the kernel
187
+ * walked past this plugin at shutdown and the Slack connector stayed registered in the automation
188
+ * engine for the lifetime of the process. `start()` IS on the
189
+ * interface, so the pair read as symmetric in review — that asymmetry is
190
+ * what let the same shape survive in six packages at once.
191
+ *
192
+ * No timers here, so this instance never cost a merge-queue eviction the
193
+ * way the `plugin-reports` / `service-messaging` members did (#9371). The
194
+ * class is the same one either way: a teardown the kernel does not reach.
195
+ */
196
+ async destroy() {
181
197
  if (this.automation && this.connectorName) {
182
198
  try {
183
199
  this.automation.unregisterConnector(this.connectorName);
184
200
  } catch {
185
201
  }
186
202
  }
203
+ this.automation = void 0;
204
+ this.connectorName = void 0;
205
+ }
206
+ /**
207
+ * Retained alias for {@link destroy}. Kept because it is public API of an
208
+ * exported class, and removing it would break an embedder who learned to
209
+ * call it directly precisely BECAUSE the kernel never did. Prefer kernel
210
+ * shutdown; direct callers keep working unchanged.
211
+ */
212
+ async stop(_ctx) {
213
+ await this.destroy();
187
214
  }
188
215
  };
189
216
  // Annotate the CommonJS export names for ESM import in node:
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';\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":[]}
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 /**\n * The kernel's teardown hook (`Plugin.destroy?()`, core `types.ts`) — the\n * ONLY teardown entry point `ObjectKernel.performShutdown()` and\n * `LiteKernel.destroy()` invoke.\n *\n * [#10371] IT USED TO BE `stop()`, WHICH NOTHING CALLED. `Plugin` declares\n * `init()`, `start?()` and `destroy?()` and no `stop()`, so the kernel\n * walked past this plugin at shutdown and the Slack connector stayed registered in the automation\n * engine for the lifetime of the process. `start()` IS on the\n * interface, so the pair read as symmetric in review — that asymmetry is\n * what let the same shape survive in six packages at once.\n *\n * No timers here, so this instance never cost a merge-queue eviction the\n * way the `plugin-reports` / `service-messaging` members did (#9371). The\n * class is the same one either way: a teardown the kernel does not reach.\n */\n async destroy(): Promise<void> {\n if (this.automation && this.connectorName) {\n try { this.automation.unregisterConnector(this.connectorName); } catch { /* ignore */ }\n }\n this.automation = undefined;\n this.connectorName = undefined;\n }\n\n /**\n * Retained alias for {@link destroy}. Kept because it is public API of an\n * exported class, and removing it would break an embedder who learned to\n * call it directly precisely BECAUSE the kernel never did. Prefer kernel\n * shutdown; direct callers keep working unchanged.\n */\n async stop(_ctx?: PluginContext): Promise<void> {\n await this.destroy();\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -150,13 +150,40 @@ var ConnectorSlackPlugin = class {
150
150
  this.connectorName = def.name;
151
151
  ctx.logger.info(`ConnectorSlackPlugin: Slack connector '${def.name}' registered`);
152
152
  }
153
- async stop(_ctx) {
153
+ /**
154
+ * The kernel's teardown hook (`Plugin.destroy?()`, core `types.ts`) — the
155
+ * ONLY teardown entry point `ObjectKernel.performShutdown()` and
156
+ * `LiteKernel.destroy()` invoke.
157
+ *
158
+ * [#10371] IT USED TO BE `stop()`, WHICH NOTHING CALLED. `Plugin` declares
159
+ * `init()`, `start?()` and `destroy?()` and no `stop()`, so the kernel
160
+ * walked past this plugin at shutdown and the Slack connector stayed registered in the automation
161
+ * engine for the lifetime of the process. `start()` IS on the
162
+ * interface, so the pair read as symmetric in review — that asymmetry is
163
+ * what let the same shape survive in six packages at once.
164
+ *
165
+ * No timers here, so this instance never cost a merge-queue eviction the
166
+ * way the `plugin-reports` / `service-messaging` members did (#9371). The
167
+ * class is the same one either way: a teardown the kernel does not reach.
168
+ */
169
+ async destroy() {
154
170
  if (this.automation && this.connectorName) {
155
171
  try {
156
172
  this.automation.unregisterConnector(this.connectorName);
157
173
  } catch {
158
174
  }
159
175
  }
176
+ this.automation = void 0;
177
+ this.connectorName = void 0;
178
+ }
179
+ /**
180
+ * Retained alias for {@link destroy}. Kept because it is public API of an
181
+ * exported class, and removing it would break an embedder who learned to
182
+ * call it directly precisely BECAUSE the kernel never did. Prefer kernel
183
+ * shutdown; direct callers keep working unchanged.
184
+ */
185
+ async stop(_ctx) {
186
+ await this.destroy();
160
187
  }
161
188
  };
162
189
  export {
@@ -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';\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":[]}
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 /**\n * The kernel's teardown hook (`Plugin.destroy?()`, core `types.ts`) — the\n * ONLY teardown entry point `ObjectKernel.performShutdown()` and\n * `LiteKernel.destroy()` invoke.\n *\n * [#10371] IT USED TO BE `stop()`, WHICH NOTHING CALLED. `Plugin` declares\n * `init()`, `start?()` and `destroy?()` and no `stop()`, so the kernel\n * walked past this plugin at shutdown and the Slack connector stayed registered in the automation\n * engine for the lifetime of the process. `start()` IS on the\n * interface, so the pair read as symmetric in review — that asymmetry is\n * what let the same shape survive in six packages at once.\n *\n * No timers here, so this instance never cost a merge-queue eviction the\n * way the `plugin-reports` / `service-messaging` members did (#9371). The\n * class is the same one either way: a teardown the kernel does not reach.\n */\n async destroy(): Promise<void> {\n if (this.automation && this.connectorName) {\n try { this.automation.unregisterConnector(this.connectorName); } catch { /* ignore */ }\n }\n this.automation = undefined;\n this.connectorName = undefined;\n }\n\n /**\n * Retained alias for {@link destroy}. Kept because it is public API of an\n * exported class, and removing it would break an embedder who learned to\n * call it directly precisely BECAUSE the kernel never did. Prefer kernel\n * shutdown; direct callers keep working unchanged.\n */\n async stop(_ctx?: PluginContext): Promise<void> {\n await this.destroy();\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/connector-slack",
3
- "version": "17.0.0",
3
+ "version": "17.2.0",
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": "17.0.0",
17
- "@objectstack/spec": "17.0.0"
16
+ "@objectstack/core": "17.2.0",
17
+ "@objectstack/spec": "17.2.0"
18
18
  },
19
19
  "devDependencies": {
20
- "@types/node": "^26.1.2",
20
+ "@types/node": "^26.2.0",
21
21
  "typescript": "^6.0.3",
22
22
  "vitest": "^4.1.10",
23
- "@objectstack/service-automation": "17.0.0"
23
+ "@objectstack/service-automation": "17.2.0"
24
24
  },
25
25
  "keywords": [
26
26
  "objectstack",