@cuylabs/channel-slack-agent-core 0.11.0 → 0.12.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/README.md CHANGED
@@ -5,11 +5,15 @@ Slack adapter for `@cuylabs/agent-core`, built on
5
5
 
6
6
  Use this package when an Agent Core application needs Agent Core-specific Slack
7
7
  bindings: event mapping, source adaptation, context fragments, history helpers,
8
- MCP token bridging, or approval/human-input controller binding.
8
+ or MCP token bridging.
9
9
 
10
10
  Generic Slack mechanics, mounts, Assistant rendering, classic message routing,
11
- feedback, artifacts, interactive stores/builders, response sinks, and
12
- transports live in `@cuylabs/channel-slack`.
11
+ feedback, artifacts, interactive request rendering/storage and controller
12
+ wiring, response sinks, and transports live in `@cuylabs/channel-slack`.
13
+
14
+ For Slack-native approvals and human input, import the controller from
15
+ `@cuylabs/channel-slack/interactive`; this package does not provide an
16
+ `interactive` subpath.
13
17
 
14
18
  ## Install
15
19
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  export { D as DEFAULT_SLACK_CONTEXT_FRAGMENT_KEY, S as SlackContextFragmentMiddlewareOptions, a as SlackContextFragmentPayload, b as SlackContextFragmentResolver, c as SlackContextFragmentResolverContext, d as createSlackContextFragmentMiddleware } from './context-fragments-CQEDcjYR.js';
2
- export { SlackEventBridgeOptions, UnsupportedSlackInteractiveRequestError, adaptAgentTurnSourceToSlackTurnSource, bridgeAgentEventsToSlack, mapAgentEventToSlackTurnEvent, mapAgentSlackEventBridgeOptionsToRuntimeOptions, resolveSlackEventBridgeOptions } from './shared/index.js';
3
- export { S as SlackApprovalRequest, a as SlackEventInteractiveRequestHandler, b as SlackHumanInputRequest, c as SlackInteractiveRequest, d as SlackInteractiveRequestBaseContext, e as SlackInteractiveRequestContext, f as SlackInteractiveRequestHandler } from './interactive-BigrPKnu.js';
2
+ export { SlackApprovalRequest, SlackEventBridgeOptions, SlackEventInteractiveRequestHandler, SlackHumanInputRequest, SlackInteractiveRequest, SlackInteractiveRequestBaseContext, SlackInteractiveRequestContext, SlackInteractiveRequestHandler, UnsupportedSlackInteractiveRequestError, adaptAgentTurnSourceToSlackTurnSource, bridgeAgentEventsToSlack, mapAgentEventToSlackTurnEvent, mapAgentSlackEventBridgeOptionsToRuntimeOptions, resolveSlackEventBridgeOptions } from './shared/index.js';
4
3
  export { LoadSlackAgentTurnHistoryContextOptions, SlackAgentTurnHistoryContextResult, SlackAgentTurnHistoryOptions, SlackAgentTurnHistoryProfileResolver, emptySlackAgentTurnHistoryContextResult, loadSlackAgentTurnHistoryContext } from './history/index.js';
5
- export { SlackInteractiveApprovalRequest, SlackInteractiveController, SlackInteractiveControllerOptions, SlackInteractiveHumanInputRequest, SlackInteractivePendingWaiter, SlackInteractivePostedMessage, SlackInteractiveRequestWaitOptions, SlackInteractiveResolution, createSlackInteractiveController } from './interactive/index.js';
6
4
  export { InspectSlackTurnStatusVisibilityOptions, ResolvedSlackTurnStatusVisibilityOptions, RouteSlackAgentEventOptions, SlackActiveToolCall, SlackAgentEventQueue, SlackAgentEventQueueState, SlackSubagentCompletionMessage, SlackSubagentCompletionMessageFormatterOptions, SlackSubagentCompletionNotifierOptions, SlackSubagentCompletionPoster, SlackSubagentCompletionRun, SlackSubagentCompletionSlackContext, SlackSubagentCompletionTurnContext, SlackTurnActivityState, SlackTurnPhase, SlackTurnStatusVisibilityOptions, SlackTurnStatusVisibilityState, SlackTurnStatusVisibilityWarning, SlackTypedApprovalAction, coalesceSlackAgentEvents, createSlackSubagentCompletionNotifier, formatDefaultSlackSubagentCompletionMessage, immediateSlackTextResponse, inspectSlackTurnStatusVisibility, isAbortLikeError, isRunningAgentTurnError, isSlackCancelMessage, isSlackSubagentTerminalEvent, isSlackTerminalTurnPhase, isSlackWaitingForHumanTurnPhase, recordSlackTurnActivity, resolveSlackTurnStatusVisibilityOptions, resolveSlackTypedApprovalAction, routeSlackAgentEvent, shouldInspectSlackTurnStatusVisibility, shouldQueueSlackAgentEvent } from './source/index.js';
7
5
  export { CreateSlackMcpServerConfigOptions, SLACK_MCP_URL, createSlackMcpServerConfig } from './mcp.js';
8
6
  import '@cuylabs/agent-core';
@@ -12,6 +10,5 @@ import '@cuylabs/channel-slack/runtime';
12
10
  import '@cuylabs/channel-slack/interactive';
13
11
  import '@cuylabs/channel-slack/history';
14
12
  import '@slack/web-api';
15
- import '@slack/bolt';
16
13
  import '@cuylabs/agent-core/dispatch';
17
14
  import '@cuylabs/agent-core/mcp';
package/dist/index.js CHANGED
@@ -6,9 +6,6 @@ import {
6
6
  emptySlackAgentTurnHistoryContextResult,
7
7
  loadSlackAgentTurnHistoryContext
8
8
  } from "./chunk-P7PFQ3SQ.js";
9
- import {
10
- createSlackInteractiveController
11
- } from "./chunk-OP27SSZU.js";
12
9
  import {
13
10
  DEFAULT_SLACK_CONTEXT_FRAGMENT_KEY,
14
11
  UnsupportedSlackInteractiveRequestError,
@@ -46,7 +43,6 @@ export {
46
43
  bridgeAgentEventsToSlack,
47
44
  coalesceSlackAgentEvents,
48
45
  createSlackContextFragmentMiddleware,
49
- createSlackInteractiveController,
50
46
  createSlackMcpServerConfig,
51
47
  createSlackSubagentCompletionNotifier,
52
48
  emptySlackAgentTurnHistoryContextResult,
@@ -2,10 +2,33 @@ export { D as DEFAULT_SLACK_CONTEXT_FRAGMENT_KEY, S as SlackContextFragmentMiddl
2
2
  import { AgentEvent, ApprovalEvent, AgentTurnSource } from '@cuylabs/agent-core';
3
3
  import { SlackResponseSink } from '@cuylabs/channel-slack/responses';
4
4
  import { SlackFinalResponseArtifactPublisher, SlackFinalResponseArtifactDeliveryMode, SlackFinalResponseArtifactContext, SlackFinalResponseArtifactResult, SlackTurnEvent, SlackEventBridgeOptions as SlackEventBridgeOptions$1, SlackTurnSource } from '@cuylabs/channel-slack/runtime';
5
- import { a as SlackEventInteractiveRequestHandler } from '../interactive-BigrPKnu.js';
6
- export { S as SlackApprovalRequest, b as SlackHumanInputRequest, c as SlackInteractiveRequest, d as SlackInteractiveRequestBaseContext, e as SlackInteractiveRequestContext, f as SlackInteractiveRequestHandler } from '../interactive-BigrPKnu.js';
7
- import '@cuylabs/channel-slack/core';
8
- import '@cuylabs/channel-slack/interactive';
5
+ import { SlackInteractiveRequestBaseContext as SlackInteractiveRequestBaseContext$1, SlackInteractiveRequestContext as SlackInteractiveRequestContext$1 } from '@cuylabs/channel-slack/interactive';
6
+ import { SlackActivityInfo, SlackUserIdentity } from '@cuylabs/channel-slack/core';
7
+
8
+ /**
9
+ * Agent Core interactive request contracts for the Slack event bridge.
10
+ *
11
+ * Generic Slack message/responder primitives live in `@cuylabs/channel-slack`.
12
+ * This module narrows the request payloads to Agent Core events.
13
+ */
14
+
15
+ type SlackApprovalRequest = Extract<AgentEvent, {
16
+ type: "approval-request";
17
+ }>["request"];
18
+ type SlackHumanInputRequest = Extract<AgentEvent, {
19
+ type: "human-input-request";
20
+ }>["request"];
21
+ type SlackInteractiveRequest = SlackApprovalRequest | SlackHumanInputRequest;
22
+ interface SlackInteractiveRequestBaseContext extends Omit<SlackInteractiveRequestBaseContext$1, "request"> {
23
+ request: SlackInteractiveRequest;
24
+ }
25
+ interface SlackInteractiveRequestContext extends Omit<SlackInteractiveRequestContext$1, "request"> {
26
+ slackActivity: SlackActivityInfo;
27
+ user: SlackUserIdentity;
28
+ request: SlackInteractiveRequest;
29
+ }
30
+ type SlackInteractiveRequestHandler = (context: SlackInteractiveRequestContext) => boolean | void | Promise<boolean | void>;
31
+ type SlackEventInteractiveRequestHandler = (context: SlackInteractiveRequestBaseContext) => boolean | void | Promise<boolean | void>;
9
32
 
10
33
  /**
11
34
  * Event-bridge configuration. The bridge is mode-aware (progressive,
@@ -170,4 +193,4 @@ declare class UnsupportedSlackInteractiveRequestError extends Error {
170
193
 
171
194
  declare function adaptAgentTurnSourceToSlackTurnSource(source: AgentTurnSource): SlackTurnSource;
172
195
 
173
- export { type SlackEventBridgeOptions, SlackEventInteractiveRequestHandler, UnsupportedSlackInteractiveRequestError, adaptAgentTurnSourceToSlackTurnSource, bridgeAgentEventsToSlack, mapAgentEventToSlackTurnEvent, mapAgentSlackEventBridgeOptionsToRuntimeOptions, resolveSlackEventBridgeOptions };
196
+ export { type SlackApprovalRequest, type SlackEventBridgeOptions, type SlackEventInteractiveRequestHandler, type SlackHumanInputRequest, type SlackInteractiveRequest, type SlackInteractiveRequestBaseContext, type SlackInteractiveRequestContext, type SlackInteractiveRequestHandler, UnsupportedSlackInteractiveRequestError, adaptAgentTurnSourceToSlackTurnSource, bridgeAgentEventsToSlack, mapAgentEventToSlackTurnEvent, mapAgentSlackEventBridgeOptionsToRuntimeOptions, resolveSlackEventBridgeOptions };
package/docs/README.md CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  `@cuylabs/channel-slack-agent-core` is the `@cuylabs/agent-core` runtime binding
4
4
  for direct Slack traffic. It composes `@cuylabs/channel-slack` primitives with
5
- Agent Core turn sources, event streams, scopes, context fragments, approvals,
6
- and human-input requests.
5
+ Agent Core turn sources, event streams, scopes, and context fragments.
7
6
 
8
7
  ## Reference
9
8
 
@@ -12,6 +11,6 @@ and human-input requests.
12
11
 
13
12
  ## Concepts
14
13
 
15
- - [Final response artifacts](concepts/final-response-artifacts.md)
16
14
  - [Interactive requests](concepts/interactive-requests.md)
15
+ - [Final response artifacts](concepts/final-response-artifacts.md)
17
16
  - [Tool task rendering](concepts/tool-task-rendering.md)
@@ -1,43 +1,48 @@
1
1
  # Interactive Requests
2
2
 
3
- Slack interactive requests render Agent Core approval and human-input requests
4
- as Slack buttons and modals.
3
+ Agent Core approval and human-input events are mapped into the runtime-neutral
4
+ Slack event stream by `adaptAgentTurnSourceToSlackTurnSource`. The Slack-native
5
+ controller lives in `@cuylabs/channel-slack/interactive`; do not import it from
6
+ `@cuylabs/channel-slack-agent-core/interactive`.
5
7
 
6
8
  ```typescript
7
- import { createSlackInteractiveController } from "@cuylabs/channel-slack-agent-core/interactive";
8
- import { createPostgresSlackInteractiveRequestStore } from "@cuylabs/channel-slack/interactive";
9
-
10
- const interactiveStore = createPostgresSlackInteractiveRequestStore({
11
- connectionString: process.env.DATABASE_URL,
12
- schema: "agent",
13
- });
9
+ import {
10
+ createPostgresSlackInteractiveRequestStore,
11
+ createSlackInteractiveController,
12
+ } from "@cuylabs/channel-slack/interactive";
13
+ import { mountSlackAppSocket } from "@cuylabs/channel-slack/socket";
14
+ import { adaptAgentTurnSourceToSlackTurnSource } from "@cuylabs/channel-slack-agent-core";
14
15
 
15
16
  const interactive = createSlackInteractiveController({
16
- store: interactiveStore,
17
+ store: createPostgresSlackInteractiveRequestStore({
18
+ connectionString: process.env.DATABASE_URL,
19
+ schema: "agent",
20
+ }),
17
21
  namespace: "my_agent_slack",
18
22
  requestTimeoutMs: 5 * 60 * 1000,
19
23
  });
20
24
 
21
- // Wire these into the Agent Core runtime:
25
+ // Wire these into the Agent Core runtime that owns the pause/resume decision:
22
26
  // approval: { onRequest: interactive.approval.onRequest }
23
27
  // humanInput: { onRequest: interactive.humanInput.onRequest }
24
28
 
25
- // Wire the controller into the Slack app surface:
26
- // installSlackAgentAppSurface(boltApp, { source, interactive, ... })
29
+ await mountSlackAppSocket({
30
+ source: adaptAgentTurnSourceToSlackTurnSource(agent),
31
+ interactive,
32
+ appToken: process.env.SLACK_APP_TOKEN,
33
+ botToken: process.env.SLACK_BOT_TOKEN,
34
+ });
27
35
  ```
28
36
 
29
- ## Store Choices
30
-
31
- - `createInMemorySlackInteractiveRequestStore` from
32
- `@cuylabs/channel-slack/interactive` is useful for tests and local
33
- single-process apps.
34
- - `createPostgresSlackInteractiveRequestStore` from
35
- `@cuylabs/channel-slack/interactive` persists pending requests across restarts
36
- and lets multiple Slack workers resolve the same request safely.
37
+ `mountSlackApp` and `mountSlackAppSocket` install the controller on the Bolt app
38
+ and route interactive request events from the Slack renderer to
39
+ `interactive.handleInteractiveRequest`.
37
40
 
38
- The Postgres store uses one table keyed by request ID. `resolve(...)` is
39
- idempotent: the first resolution wins, and later duplicate button clicks return
40
- the original resolution without overwriting it.
41
+ The controller persists pending requests before rendering Slack buttons. It uses
42
+ first-wins resolution semantics, so duplicate Slack clicks return the stored
43
+ resolution instead of overwriting it. Request IDs must be unique across pending
44
+ approval and human-input requests.
41
45
 
42
- Call `close()` during shutdown when the store owns its `pg` pool. Call
43
- `prune()` manually when you disable the background prune timer.
46
+ By default, only the Slack user who triggered the original turn can resolve the
47
+ request. Pass `authorize(record, actor)` to the controller to allow delegated
48
+ approvers or workspace policy.
@@ -7,17 +7,19 @@ This package owns only the `@cuylabs/agent-core` Slack binding.
7
7
  - Mapping Slack turns to `AgentTurnSource.chat(...)`.
8
8
  - Creating Agent Core scopes and context fragments.
9
9
  - Converting `AgentEvent` streams through Slack response sink contracts.
10
- - Mounting Agent Core sources on Express Events API and Socket Mode surfaces.
10
+ - Adapting Agent Core sources for the runtime-neutral Express Events API and
11
+ Socket Mode mounts in `@cuylabs/channel-slack`.
11
12
  - Binding Slack Assistant lifecycle handlers to Agent Core turns.
12
- - Binding Agent Core approval and human-input requests to Slack interactive
13
- request primitives.
13
+ - Mapping Agent Core approval and human-input events into the runtime-neutral
14
+ Slack event stream.
14
15
  - Agent Core helpers for Slack history context fragments and source events.
15
16
 
16
17
  ## Outside This Package
17
18
 
18
19
  - Generic Slack activity parsing, formatting, setup, auth, policy, history,
19
20
  targets, users, entrypoints, artifacts, response sink contracts, interactive
20
- request builders/stores, and transport factories.
21
+ request builders/stores/controllers, and transport factories.
22
+ - Slack-native rendering and resolution of approvals or human input.
21
23
  - Product prompts, tools, audit policy, privacy rules, and deployment policy.
22
24
  - Slack app configuration outside the reusable setup helpers.
23
25
 
@@ -3,14 +3,18 @@
3
3
  Use feature subpaths when you only need one Agent Core binding. Generic Slack
4
4
  mounts and primitives must be imported from `@cuylabs/channel-slack` directly.
5
5
 
6
- | Export | Depends on | Notes |
7
- | ------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------- |
8
- | `@cuylabs/channel-slack-agent-core` | `@cuylabs/agent-core` | Agent Core event bridge, context fragments, history, interactive, source, MCP |
9
- | `@cuylabs/channel-slack-agent-core/history` | `@slack/web-api`, `@cuylabs/agent-core` types | Agent Core turn history context fragment assembly |
10
- | `@cuylabs/channel-slack-agent-core/interactive` | `@slack/bolt`, `@cuylabs/channel-slack/interactive` | Agent Core approval and human-input controller binding for Slack |
11
- | `@cuylabs/channel-slack-agent-core/mcp` | `@cuylabs/agent-core/mcp` types | Slack OAuth token bridge for Agent Core MCP setup |
12
- | `@cuylabs/channel-slack-agent-core/shared` | `@cuylabs/agent-core` types | Context fragments, Agent Core event mapping, and source adaptation |
13
- | `@cuylabs/channel-slack-agent-core/source` | `@cuylabs/agent-core` types | Agent Core event queue, response helpers, and status visibility helpers |
6
+ | Export | Depends on | Notes |
7
+ | ------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------- |
8
+ | `@cuylabs/channel-slack-agent-core` | `@cuylabs/agent-core` | Agent Core event bridge, context fragments, history, source, and MCP |
9
+ | `@cuylabs/channel-slack-agent-core/history` | `@slack/web-api`, `@cuylabs/agent-core` types | Agent Core turn history context fragment assembly |
10
+ | `@cuylabs/channel-slack-agent-core/mcp` | `@cuylabs/agent-core/mcp` types | Slack OAuth token bridge for Agent Core MCP setup |
11
+ | `@cuylabs/channel-slack-agent-core/shared` | `@cuylabs/agent-core` types | Context fragments, Agent Core event mapping, and source adaptation |
12
+ | `@cuylabs/channel-slack-agent-core/source` | `@cuylabs/agent-core` types | Agent Core event queue, response helpers, and status visibility helpers |
13
+
14
+ There is intentionally no
15
+ `@cuylabs/channel-slack-agent-core/interactive` export. Use
16
+ `@cuylabs/channel-slack/interactive` for Slack-native approval and human-input
17
+ controllers.
14
18
 
15
19
  For generic Slack package exports, see
16
20
  `@cuylabs/channel-slack/docs/reference/exports.md`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuylabs/channel-slack-agent-core",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Slack adapter for @cuylabs/agent-core built on @cuylabs/channel-slack",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,11 +16,6 @@
16
16
  "import": "./dist/history/index.js",
17
17
  "default": "./dist/history/index.js"
18
18
  },
19
- "./interactive": {
20
- "types": "./dist/interactive/index.d.ts",
21
- "import": "./dist/interactive/index.js",
22
- "default": "./dist/interactive/index.js"
23
- },
24
19
  "./mcp": {
25
20
  "types": "./dist/mcp.d.ts",
26
21
  "import": "./dist/mcp.js",
@@ -44,7 +39,7 @@
44
39
  ],
45
40
  "dependencies": {
46
41
  "@cuylabs/agent-core": "^7.2.0",
47
- "@cuylabs/channel-slack": "^0.11.0"
42
+ "@cuylabs/channel-slack": "^0.12.0"
48
43
  },
49
44
  "peerDependencies": {
50
45
  "@slack/bolt": ">=4.7.3",
@@ -94,9 +89,9 @@
94
89
  "node": ">=20"
95
90
  },
96
91
  "scripts": {
97
- "build": "tsup src/index.ts src/shared/index.ts src/history/index.ts src/interactive/index.ts src/mcp.ts src/source/index.ts --format esm --dts --clean",
92
+ "build": "tsup src/index.ts src/shared/index.ts src/history/index.ts src/mcp.ts src/source/index.ts --format esm --dts --clean",
98
93
  "clean": "rm -rf dist",
99
- "dev": "tsup src/index.ts src/shared/index.ts src/history/index.ts src/interactive/index.ts src/mcp.ts src/source/index.ts --format esm --dts --watch",
94
+ "dev": "tsup src/index.ts src/shared/index.ts src/history/index.ts src/mcp.ts src/source/index.ts --format esm --dts --watch",
100
95
  "lint": "eslint \"src/**/*.{ts,tsx}\" \"tests/**/*.{ts,tsx}\" --max-warnings=0",
101
96
  "test": "vitest run",
102
97
  "test:watch": "vitest",
@@ -1,409 +0,0 @@
1
- // src/interactive/controller.ts
2
- import {
3
- buildApprovalRequestMessage,
4
- buildHumanInputModal,
5
- buildHumanInputRequestMessage,
6
- buildResolvedMessage,
7
- createInMemorySlackInteractiveRequestStore,
8
- decodeActionValue,
9
- nowIso
10
- } from "@cuylabs/channel-slack/interactive";
11
- import { openSlackModal } from "@cuylabs/channel-slack/views";
12
- var DEFAULT_NAMESPACE = "agent_slack";
13
- var DEFAULT_REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
14
- var installedActionIds = /* @__PURE__ */ new WeakMap();
15
- function createSlackInteractiveController(options = {}) {
16
- const store = options.store ?? createInMemorySlackInteractiveRequestStore();
17
- const actionIds = resolveActionIds(
18
- options.namespace ?? DEFAULT_NAMESPACE,
19
- options.actionIds
20
- );
21
- const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
22
- const waiters = /* @__PURE__ */ new Map();
23
- const pendingIds = /* @__PURE__ */ new Set();
24
- async function ensurePending(kind, request) {
25
- const existing = await store.get(request.id);
26
- if (existing) {
27
- pendingIds.add(request.id);
28
- return existing;
29
- }
30
- const createdAt = nowIso();
31
- const record = await store.upsert({
32
- id: request.id,
33
- kind,
34
- request,
35
- status: "pending",
36
- createdAt,
37
- updatedAt: createdAt
38
- });
39
- pendingIds.add(request.id);
40
- return record;
41
- }
42
- async function waitForResolution(kind, request, waitOptions = {}) {
43
- const existing = await ensurePending(kind, request);
44
- if (existing.status === "resolved" && existing.resolution) {
45
- return toAgentCoreResolution(existing.resolution);
46
- }
47
- if (waiters.has(request.id)) {
48
- throw new Error(
49
- `Slack interactive request is already waiting: ${request.id}. Resolve or cancel the in-flight request before requesting again.`
50
- );
51
- }
52
- return await new Promise((resolve, reject) => {
53
- const cleanupCallbacks = [];
54
- const timeoutMs = waitOptions.timeoutMs ?? requestTimeoutMs;
55
- if (timeoutMs > 0) {
56
- const timeoutId = setTimeout(() => {
57
- void cancel(request.id, "Slack interactive request timed out.");
58
- }, timeoutMs);
59
- cleanupCallbacks.push(() => clearTimeout(timeoutId));
60
- }
61
- let abortImmediately = false;
62
- if (waitOptions.signal) {
63
- const onAbort = () => {
64
- void cancel(request.id, "Slack interactive request aborted.");
65
- };
66
- if (waitOptions.signal.aborted) {
67
- abortImmediately = true;
68
- } else {
69
- waitOptions.signal.addEventListener("abort", onAbort, {
70
- once: true
71
- });
72
- cleanupCallbacks.push(
73
- () => waitOptions.signal?.removeEventListener("abort", onAbort)
74
- );
75
- }
76
- }
77
- waiters.set(request.id, {
78
- resolve,
79
- reject,
80
- cleanup: () => {
81
- for (const cleanup of cleanupCallbacks.splice(0)) {
82
- cleanup();
83
- }
84
- }
85
- });
86
- if (abortImmediately) {
87
- void cancel(request.id, "Slack interactive request aborted.");
88
- }
89
- });
90
- }
91
- async function resolveRequest(requestId, resolution) {
92
- const record = await store.resolve(requestId, resolution);
93
- const waiter = waiters.get(requestId);
94
- if (waiter) {
95
- waiters.delete(requestId);
96
- waiter.cleanup();
97
- waiter.resolve(resolution);
98
- }
99
- if (record) {
100
- pendingIds.delete(requestId);
101
- await options.onResolve?.(requestId, resolution);
102
- }
103
- return record;
104
- }
105
- async function cancel(requestId, reason = "Cancelled") {
106
- const waiter = waiters.get(requestId);
107
- if (waiter) {
108
- waiters.delete(requestId);
109
- waiter.cleanup();
110
- waiter.reject(new Error(reason));
111
- }
112
- const existing = await store.get(requestId);
113
- if (existing?.status === "pending") {
114
- await store.delete(requestId);
115
- pendingIds.delete(requestId);
116
- return true;
117
- }
118
- pendingIds.delete(requestId);
119
- return Boolean(waiter);
120
- }
121
- async function cancelAll(reason = "Cancelled") {
122
- await Promise.all(
123
- [...pendingIds].map((requestId) => cancel(requestId, reason))
124
- );
125
- }
126
- async function handleInteractiveRequest(context) {
127
- const request = context.request;
128
- const record = await ensurePending(context.kind, request);
129
- if (record.target) {
130
- return true;
131
- }
132
- const message = context.kind === "approval" ? buildApprovalRequestMessage(
133
- request,
134
- actionIds
135
- ) : buildHumanInputRequestMessage(
136
- request,
137
- actionIds
138
- );
139
- const ref = await context.responder.postMessage(message);
140
- await store.attachTarget(request.id, {
141
- channel: ref.channel,
142
- ts: ref.ts,
143
- userId: context.user.userId,
144
- teamId: context.user.teamId,
145
- ...context.slackActivity.threadTs ? { threadTs: context.slackActivity.threadTs } : {}
146
- });
147
- return true;
148
- }
149
- function install(app) {
150
- assertActionIdsCanInstall(app, actionIds);
151
- app.action(actionIds.approvalAllow, async (args) => {
152
- await handleAction(args, {
153
- kind: "approval",
154
- action: "allow"
155
- });
156
- });
157
- app.action(actionIds.approvalDeny, async (args) => {
158
- await handleAction(args, {
159
- kind: "approval",
160
- action: "deny"
161
- });
162
- });
163
- app.action(actionIds.approvalRemember, async (args) => {
164
- const value = firstActionValue(args);
165
- const rememberScope = typeof value.rememberScope === "string" ? value.rememberScope : void 0;
166
- await handleAction(args, {
167
- kind: "approval",
168
- action: "remember",
169
- ...rememberScope ? { rememberScope } : {}
170
- });
171
- });
172
- app.action(actionIds.humanConfirm, async (args) => {
173
- await handleAction(args, {
174
- kind: "human-input",
175
- response: { kind: "confirm", confirmed: true, text: "Confirmed" }
176
- });
177
- });
178
- app.action(actionIds.humanDeny, async (args) => {
179
- await handleAction(args, {
180
- kind: "human-input",
181
- response: { kind: "confirm", confirmed: false, text: "Cancelled" }
182
- });
183
- });
184
- app.action(actionIds.humanOpen, async (args) => {
185
- await openHumanInputModal(args);
186
- });
187
- app.view(actionIds.humanSubmit, async (args) => {
188
- await submitHumanInputModal(args);
189
- });
190
- }
191
- async function handleAction(args, resolutionInput) {
192
- const actionArgs = args;
193
- await actionArgs.ack();
194
- const requestId = extractRequestId(firstActionValue(args));
195
- if (!requestId) return;
196
- const record = await store.get(requestId);
197
- if (!record || record.status === "resolved") {
198
- return;
199
- }
200
- const actor = extractActor(actionArgs.body);
201
- if (!await isAuthorized(record, actor)) {
202
- await postEphemeral(
203
- actionArgs,
204
- "Only the original requester can resolve this request."
205
- );
206
- return;
207
- }
208
- const resolution = resolutionInput;
209
- const resolved = await resolveRequest(requestId, resolution);
210
- if (resolved?.target) {
211
- await updateOriginalMessage(actionArgs, resolved, resolution);
212
- }
213
- }
214
- async function openHumanInputModal(args) {
215
- const actionArgs = args;
216
- await actionArgs.ack();
217
- const requestId = extractRequestId(firstActionValue(args));
218
- if (!requestId || !actionArgs.body.trigger_id) return;
219
- const record = await store.get(requestId);
220
- if (!record || record.kind !== "human-input") return;
221
- const actor = extractActor(actionArgs.body);
222
- if (!await isAuthorized(record, actor)) {
223
- await postEphemeral(
224
- actionArgs,
225
- "Only the original requester can answer this request."
226
- );
227
- return;
228
- }
229
- await openSlackModal({
230
- client: actionArgs.client,
231
- triggerId: actionArgs.body.trigger_id,
232
- view: buildHumanInputModal(
233
- record.request,
234
- actionIds
235
- )
236
- });
237
- }
238
- async function submitHumanInputModal(args) {
239
- const viewArgs = args;
240
- await viewArgs.ack();
241
- const requestId = extractRequestIdFromView(viewArgs.view);
242
- if (!requestId) return;
243
- const record = await store.get(requestId);
244
- if (!record || record.kind !== "human-input" || record.status === "resolved") {
245
- return;
246
- }
247
- const actor = extractActor(viewArgs.body);
248
- if (!await isAuthorized(record, actor)) {
249
- return;
250
- }
251
- const response = responseFromView(
252
- record.request,
253
- viewArgs.view
254
- );
255
- const resolution = {
256
- kind: "human-input",
257
- response
258
- };
259
- const resolved = await resolveRequest(requestId, resolution);
260
- if (resolved?.target) {
261
- await viewArgs.client.chat.update({
262
- channel: resolved.target.channel,
263
- ts: resolved.target.ts,
264
- ...buildResolvedMessage("Slack response received.", resolution)
265
- });
266
- }
267
- }
268
- async function isAuthorized(record, actor) {
269
- if (options.authorize) {
270
- return await options.authorize(record, actor);
271
- }
272
- return record.target?.userId === actor.userId;
273
- }
274
- async function updateOriginalMessage(args, record, resolution) {
275
- const target = record.target;
276
- if (!target) return;
277
- const label = resolution.kind === "approval" ? `${resolution.action} selected.` : resolution.response.text;
278
- await args.client.chat.update({
279
- channel: target.channel,
280
- ts: target.ts,
281
- ...buildResolvedMessage(label, resolution)
282
- });
283
- }
284
- return {
285
- actionIds,
286
- store,
287
- approval: {
288
- async onRequest(request, options2) {
289
- const resolution = await waitForResolution(
290
- "approval",
291
- request,
292
- options2
293
- );
294
- if (resolution.kind !== "approval") {
295
- throw new Error(
296
- `Unexpected human-input resolution for ${request.id}.`
297
- );
298
- }
299
- return {
300
- action: resolution.action,
301
- ...resolution.feedback ? { feedback: resolution.feedback } : {},
302
- ...resolution.rememberScope ? { rememberScope: resolution.rememberScope } : {}
303
- };
304
- }
305
- },
306
- humanInput: {
307
- async onRequest(request, options2) {
308
- const resolution = await waitForResolution(
309
- "human-input",
310
- request,
311
- options2
312
- );
313
- if (resolution.kind !== "human-input") {
314
- throw new Error(`Unexpected approval resolution for ${request.id}.`);
315
- }
316
- return resolution.response;
317
- }
318
- },
319
- cancel,
320
- cancelAll,
321
- handleInteractiveRequest,
322
- install
323
- };
324
- }
325
- function toAgentCoreResolution(resolution) {
326
- return resolution;
327
- }
328
- function resolveActionIds(namespace, overrides) {
329
- const prefix = normalizeActionIdNamespace(namespace);
330
- return {
331
- approvalAllow: `${prefix}_approval_allow`,
332
- approvalDeny: `${prefix}_approval_deny`,
333
- approvalRemember: `${prefix}_approval_remember`,
334
- humanConfirm: `${prefix}_human_confirm`,
335
- humanDeny: `${prefix}_human_deny`,
336
- humanOpen: `${prefix}_human_open`,
337
- humanSubmit: `${prefix}_human_submit`,
338
- ...overrides ?? {}
339
- };
340
- }
341
- function normalizeActionIdNamespace(namespace) {
342
- const trimmed = namespace.trim();
343
- if (!trimmed) {
344
- throw new Error("Slack interactive action namespace cannot be empty.");
345
- }
346
- return trimmed;
347
- }
348
- function assertActionIdsCanInstall(app, actionIds) {
349
- const ids = Object.values(actionIds);
350
- const duplicateWithinController = ids.find(
351
- (id, index) => ids.indexOf(id) !== index
352
- );
353
- if (duplicateWithinController) {
354
- throw new Error(
355
- `Duplicate Slack interactive action id configured: ${duplicateWithinController}`
356
- );
357
- }
358
- const appKey = app;
359
- const installed = installedActionIds.get(appKey) ?? /* @__PURE__ */ new Set();
360
- const duplicate = ids.find((id) => installed.has(id));
361
- if (duplicate) {
362
- throw new Error(
363
- `Slack interactive action id '${duplicate}' is already installed on this Bolt app. Provide a unique createSlackInteractiveController({ namespace }) or actionIds config.`
364
- );
365
- }
366
- for (const id of ids) {
367
- installed.add(id);
368
- }
369
- installedActionIds.set(appKey, installed);
370
- }
371
- function firstActionValue(args) {
372
- const body = args.body;
373
- return decodeActionValue(body.actions?.[0]?.value);
374
- }
375
- function extractRequestId(value) {
376
- return typeof value.requestId === "string" && value.requestId.length > 0 ? value.requestId : void 0;
377
- }
378
- function extractRequestIdFromView(view) {
379
- return extractRequestId(decodeActionValue(view.private_metadata));
380
- }
381
- function extractActor(body) {
382
- return {
383
- userId: body.user?.id ?? "unknown",
384
- ...body.user?.team_id ?? body.team?.id ? { teamId: body.user?.team_id ?? body.team?.id } : {}
385
- };
386
- }
387
- async function postEphemeral(args, text) {
388
- const channel = args.body.channel?.id;
389
- const user = args.body.user?.id;
390
- if (!channel || !user || !args.client.chat.postEphemeral) return;
391
- await args.client.chat.postEphemeral({ channel, user, text });
392
- }
393
- function responseFromView(request, view) {
394
- const input = view.state?.values?.input?.value;
395
- if (request.kind === "choice") {
396
- const selected = input?.selected_options?.map((option) => option.value ?? "").filter(Boolean) ?? (input?.selected_option?.value ? [input.selected_option.value] : []);
397
- return {
398
- kind: "choice",
399
- selected,
400
- text: selected.join(", ")
401
- };
402
- }
403
- const text = input?.value ?? "";
404
- return { kind: "text", text };
405
- }
406
-
407
- export {
408
- createSlackInteractiveController
409
- };
@@ -1,90 +0,0 @@
1
- import { ApprovalRequest, ApprovalResolution, HumanInputRequest, HumanInputResponse, ApprovalAction, ApprovalRememberScope } from '@cuylabs/agent-core';
2
- import { SlackInteractiveActionIds, SlackInteractiveRequestStore, SlackInteractiveRequestRecord, SlackInteractiveActor, SlackInteractiveHumanInputRequest as SlackInteractiveHumanInputRequest$1, SlackInteractiveMessageRef } from '@cuylabs/channel-slack/interactive';
3
- import { App } from '@slack/bolt';
4
- import { e as SlackInteractiveRequestContext } from '../interactive-BigrPKnu.js';
5
- import '@cuylabs/channel-slack/core';
6
-
7
- type SlackInteractiveApprovalRequest = ApprovalRequest;
8
- type SlackInteractiveHumanInputRequest = SlackInteractiveHumanInputRequest$1 & Partial<Pick<HumanInputRequest, "sessionId" | "timestamp" | "toolCallId">>;
9
- type SlackInteractiveResolution = {
10
- kind: "approval";
11
- action: ApprovalAction;
12
- feedback?: string;
13
- rememberScope?: ApprovalRememberScope;
14
- } | {
15
- kind: "human-input";
16
- response: HumanInputResponse;
17
- };
18
- interface SlackInteractiveRequestWaitOptions {
19
- /**
20
- * Abort waiting for this request. The controller removes its local waiter and
21
- * deletes the pending store record when the signal fires.
22
- */
23
- signal?: AbortSignal;
24
- /**
25
- * Override the controller-level request timeout for this request. Use `0` to
26
- * disable timeout cleanup for this request.
27
- */
28
- timeoutMs?: number;
29
- }
30
- interface SlackInteractiveControllerOptions {
31
- store?: SlackInteractiveRequestStore;
32
- /**
33
- * Stable namespace for default Slack action IDs. Use this when installing
34
- * multiple interactive controllers in the same Slack app.
35
- *
36
- * @default "agent_slack"
37
- */
38
- namespace?: string;
39
- actionIds?: Partial<SlackInteractiveActionIds>;
40
- /**
41
- * Default timeout for local waiters and pending store records. This should
42
- * usually match the agent-core approval/human-input timeout.
43
- *
44
- * @default 300000
45
- */
46
- requestTimeoutMs?: number;
47
- /**
48
- * Called on every successful Slack resolution after the local waiter, if
49
- * present, is resolved. Use this to fan out decisions to agent-server or
50
- * another runtime that owns the turn.
51
- */
52
- onResolve?: (requestId: string, resolution: SlackInteractiveResolution) => void | Promise<void>;
53
- /**
54
- * Authorization hook for approving/responding to pending requests.
55
- *
56
- * Defaults to the original Slack requester only. Return `true` for delegated
57
- * approvers, channel owners, or admin policy checks.
58
- */
59
- authorize?: (record: SlackInteractiveRequestRecord, actor: SlackInteractiveActor) => boolean | Promise<boolean>;
60
- }
61
- interface SlackInteractiveController {
62
- readonly actionIds: SlackInteractiveActionIds;
63
- readonly store: SlackInteractiveRequestStore;
64
- approval: {
65
- onRequest(request: ApprovalRequest, options?: SlackInteractiveRequestWaitOptions): Promise<ApprovalResolution>;
66
- };
67
- humanInput: {
68
- onRequest(request: HumanInputRequest, options?: SlackInteractiveRequestWaitOptions): Promise<HumanInputResponse>;
69
- };
70
- /** Reject one pending in-process waiter and delete its pending store record. */
71
- cancel(requestId: string, reason?: string): Promise<boolean>;
72
- /** Shutdown helper: cancel every pending request created by this controller. */
73
- cancelAll(reason?: string): Promise<void>;
74
- handleInteractiveRequest(context: SlackInteractiveRequestContext): Promise<boolean>;
75
- install(app: App): void;
76
- }
77
- type SlackInteractivePendingWaiter = {
78
- resolve: (resolution: SlackInteractiveResolution) => void;
79
- reject: (error: Error) => void;
80
- cleanup: () => void;
81
- };
82
- interface SlackInteractivePostedMessage {
83
- text: string;
84
- blocks: unknown[];
85
- ref: SlackInteractiveMessageRef;
86
- }
87
-
88
- declare function createSlackInteractiveController(options?: SlackInteractiveControllerOptions): SlackInteractiveController;
89
-
90
- export { type SlackInteractiveApprovalRequest, type SlackInteractiveController, type SlackInteractiveControllerOptions, type SlackInteractiveHumanInputRequest, type SlackInteractivePendingWaiter, type SlackInteractivePostedMessage, type SlackInteractiveRequestWaitOptions, type SlackInteractiveResolution, createSlackInteractiveController };
@@ -1,6 +0,0 @@
1
- import {
2
- createSlackInteractiveController
3
- } from "../chunk-OP27SSZU.js";
4
- export {
5
- createSlackInteractiveController
6
- };
@@ -1,30 +0,0 @@
1
- import { AgentEvent } from '@cuylabs/agent-core';
2
- import { SlackInteractiveRequestBaseContext as SlackInteractiveRequestBaseContext$1, SlackInteractiveRequestContext as SlackInteractiveRequestContext$1 } from '@cuylabs/channel-slack/interactive';
3
- import { SlackActivityInfo, SlackUserIdentity } from '@cuylabs/channel-slack/core';
4
-
5
- /**
6
- * Agent Core interactive request contracts for the Slack event bridge.
7
- *
8
- * Generic Slack message/responder primitives live in `@cuylabs/channel-slack`.
9
- * This module narrows the request payloads to Agent Core events.
10
- */
11
-
12
- type SlackApprovalRequest = Extract<AgentEvent, {
13
- type: "approval-request";
14
- }>["request"];
15
- type SlackHumanInputRequest = Extract<AgentEvent, {
16
- type: "human-input-request";
17
- }>["request"];
18
- type SlackInteractiveRequest = SlackApprovalRequest | SlackHumanInputRequest;
19
- interface SlackInteractiveRequestBaseContext extends Omit<SlackInteractiveRequestBaseContext$1, "request"> {
20
- request: SlackInteractiveRequest;
21
- }
22
- interface SlackInteractiveRequestContext extends Omit<SlackInteractiveRequestContext$1, "request"> {
23
- slackActivity: SlackActivityInfo;
24
- user: SlackUserIdentity;
25
- request: SlackInteractiveRequest;
26
- }
27
- type SlackInteractiveRequestHandler = (context: SlackInteractiveRequestContext) => boolean | void | Promise<boolean | void>;
28
- type SlackEventInteractiveRequestHandler = (context: SlackInteractiveRequestBaseContext) => boolean | void | Promise<boolean | void>;
29
-
30
- export type { SlackApprovalRequest as S, SlackEventInteractiveRequestHandler as a, SlackHumanInputRequest as b, SlackInteractiveRequest as c, SlackInteractiveRequestBaseContext as d, SlackInteractiveRequestContext as e, SlackInteractiveRequestHandler as f };