@moxxy/plugin-channel-slack 0.27.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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/dist/channel/slack-client.d.ts +53 -0
  3. package/dist/channel/slack-client.d.ts.map +1 -0
  4. package/dist/channel/slack-client.js +82 -0
  5. package/dist/channel/slack-client.js.map +1 -0
  6. package/dist/channel/turn-runner.d.ts +39 -0
  7. package/dist/channel/turn-runner.d.ts.map +1 -0
  8. package/dist/channel/turn-runner.js +81 -0
  9. package/dist/channel/turn-runner.js.map +1 -0
  10. package/dist/channel.d.ts +100 -0
  11. package/dist/channel.d.ts.map +1 -0
  12. package/dist/channel.js +286 -0
  13. package/dist/channel.js.map +1 -0
  14. package/dist/index.d.ts +31 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +207 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/keys.d.ts +50 -0
  19. package/dist/keys.d.ts.map +1 -0
  20. package/dist/keys.js +78 -0
  21. package/dist/keys.js.map +1 -0
  22. package/dist/pair-flow.d.ts +18 -0
  23. package/dist/pair-flow.d.ts.map +1 -0
  24. package/dist/pair-flow.js +106 -0
  25. package/dist/pair-flow.js.map +1 -0
  26. package/dist/permission.d.ts +27 -0
  27. package/dist/permission.d.ts.map +1 -0
  28. package/dist/permission.js +33 -0
  29. package/dist/permission.js.map +1 -0
  30. package/dist/server/dedupe.d.ts +8 -0
  31. package/dist/server/dedupe.d.ts.map +1 -0
  32. package/dist/server/dedupe.js +8 -0
  33. package/dist/server/dedupe.js.map +1 -0
  34. package/dist/server/ingest-server.d.ts +80 -0
  35. package/dist/server/ingest-server.d.ts.map +1 -0
  36. package/dist/server/ingest-server.js +142 -0
  37. package/dist/server/ingest-server.js.map +1 -0
  38. package/dist/server/schema.d.ts +257 -0
  39. package/dist/server/schema.d.ts.map +1 -0
  40. package/dist/server/schema.js +69 -0
  41. package/dist/server/schema.js.map +1 -0
  42. package/dist/server/verify.d.ts +39 -0
  43. package/dist/server/verify.d.ts.map +1 -0
  44. package/dist/server/verify.js +73 -0
  45. package/dist/server/verify.js.map +1 -0
  46. package/dist/setup-wizard.d.ts +17 -0
  47. package/dist/setup-wizard.d.ts.map +1 -0
  48. package/dist/setup-wizard.js +92 -0
  49. package/dist/setup-wizard.js.map +1 -0
  50. package/package.json +97 -0
  51. package/src/channel/slack-client.ts +123 -0
  52. package/src/channel/turn-runner.test.ts +143 -0
  53. package/src/channel/turn-runner.ts +107 -0
  54. package/src/channel.ts +378 -0
  55. package/src/index.ts +254 -0
  56. package/src/keys.ts +96 -0
  57. package/src/pair-flow.ts +119 -0
  58. package/src/permission.test.ts +65 -0
  59. package/src/permission.ts +42 -0
  60. package/src/server/dedupe.ts +7 -0
  61. package/src/server/ingest-server.test.ts +280 -0
  62. package/src/server/ingest-server.ts +205 -0
  63. package/src/server/schema.test.ts +67 -0
  64. package/src/server/schema.ts +77 -0
  65. package/src/server/verify.test.ts +132 -0
  66. package/src/server/verify.ts +88 -0
  67. package/src/setup-wizard.ts +121 -0
  68. package/src/subcommands.test.ts +189 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moxxy (moxxy.ai)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Minimal hand-rolled Slack Web API client. The repo norm is hand-rolled
3
+ * `fetch` over a vendor SDK (only Telegram pulls in grammy), so there is NO
4
+ * `@slack/web-api` dependency: each method POSTs JSON to
5
+ * `https://slack.com/api/<method>` with `Authorization: Bearer <token>` and
6
+ * throws on a non-`ok` Slack response.
7
+ */
8
+ export interface SlackClientOptions {
9
+ readonly token: string;
10
+ /** Override the API base (tests). */
11
+ readonly baseUrl?: string;
12
+ /** Injectable fetch (tests). Defaults to the global `fetch`. */
13
+ readonly fetchImpl?: typeof fetch;
14
+ }
15
+ export interface AuthTestResult {
16
+ /** The bot's own user id — used to drop the bot's own messages. */
17
+ readonly botUserId: string;
18
+ readonly teamId?: string;
19
+ readonly team?: string;
20
+ readonly url?: string;
21
+ }
22
+ export interface PostMessageResult {
23
+ readonly channel: string;
24
+ /** The new message ts — used as the edit target for streaming. */
25
+ readonly ts: string;
26
+ }
27
+ export declare class SlackClient {
28
+ private readonly token;
29
+ private readonly baseUrl;
30
+ private readonly fetchImpl;
31
+ constructor(opts: SlackClientOptions);
32
+ private call;
33
+ /** Validate the token and capture the bot's own user id. */
34
+ authTest(): Promise<AuthTestResult>;
35
+ /** Post a message into a channel/thread. Returns the channel + new ts. */
36
+ postMessage(args: {
37
+ channel: string;
38
+ text: string;
39
+ threadTs?: string;
40
+ }): Promise<PostMessageResult>;
41
+ /** Edit an existing message (the streaming-update path). */
42
+ updateMessage(args: {
43
+ channel: string;
44
+ ts: string;
45
+ text: string;
46
+ }): Promise<void>;
47
+ /** Optional: read a thread's replies (unused by v1 streaming, exposed for tools). */
48
+ conversationsReplies(args: {
49
+ channel: string;
50
+ ts: string;
51
+ }): Promise<ReadonlyArray<Record<string, unknown>>>;
52
+ }
53
+ //# sourceMappingURL=slack-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-client.d.ts","sourceRoot":"","sources":["../../src/channel/slack-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AASD,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAE7B,IAAI,EAAE,kBAAkB;YAMtB,IAAI;IAqBlB,4DAA4D;IACtD,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAYzC,0EAA0E;IACpE,WAAW,CAAC,IAAI,EAAE;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAY9B,4DAA4D;IACtD,aAAa,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAQvF,qFAAqF;IAC/E,oBAAoB,CAAC,IAAI,EAAE;QAC/B,OAAO,EAAE,MAAM,CAAC;QAChB,EAAE,EAAE,MAAM,CAAC;KACZ,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAQpD"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Minimal hand-rolled Slack Web API client. The repo norm is hand-rolled
3
+ * `fetch` over a vendor SDK (only Telegram pulls in grammy), so there is NO
4
+ * `@slack/web-api` dependency: each method POSTs JSON to
5
+ * `https://slack.com/api/<method>` with `Authorization: Bearer <token>` and
6
+ * throws on a non-`ok` Slack response.
7
+ */
8
+ const SLACK_API_BASE = 'https://slack.com/api';
9
+ export class SlackClient {
10
+ token;
11
+ baseUrl;
12
+ fetchImpl;
13
+ constructor(opts) {
14
+ this.token = opts.token;
15
+ this.baseUrl = (opts.baseUrl ?? SLACK_API_BASE).replace(/\/+$/, '');
16
+ this.fetchImpl = opts.fetchImpl ?? fetch;
17
+ }
18
+ async call(method, body) {
19
+ const res = await this.fetchImpl(`${this.baseUrl}/${method}`, {
20
+ method: 'POST',
21
+ headers: {
22
+ 'content-type': 'application/json; charset=utf-8',
23
+ authorization: `Bearer ${this.token}`,
24
+ },
25
+ body: JSON.stringify(body),
26
+ });
27
+ // Slack always returns 200 with `{ ok: false, error }` for app-level errors;
28
+ // a non-2xx is a transport/auth problem worth surfacing distinctly.
29
+ if (!res.ok) {
30
+ throw new Error(`Slack ${method} HTTP ${res.status}`);
31
+ }
32
+ const json = (await res.json());
33
+ if (!json.ok) {
34
+ throw new Error(`Slack ${method} failed: ${json.error ?? 'unknown_error'}`);
35
+ }
36
+ return json;
37
+ }
38
+ /** Validate the token and capture the bot's own user id. */
39
+ async authTest() {
40
+ const json = await this.call('auth.test', {});
41
+ const botUserId = typeof json['user_id'] === 'string' ? json['user_id'] : '';
42
+ if (!botUserId)
43
+ throw new Error('Slack auth.test returned no user_id');
44
+ return {
45
+ botUserId,
46
+ ...(typeof json['team_id'] === 'string' ? { teamId: json['team_id'] } : {}),
47
+ ...(typeof json['team'] === 'string' ? { team: json['team'] } : {}),
48
+ ...(typeof json['url'] === 'string' ? { url: json['url'] } : {}),
49
+ };
50
+ }
51
+ /** Post a message into a channel/thread. Returns the channel + new ts. */
52
+ async postMessage(args) {
53
+ const json = await this.call('chat.postMessage', {
54
+ channel: args.channel,
55
+ text: args.text,
56
+ ...(args.threadTs ? { thread_ts: args.threadTs } : {}),
57
+ });
58
+ const channel = typeof json['channel'] === 'string' ? json['channel'] : args.channel;
59
+ const ts = typeof json['ts'] === 'string' ? json['ts'] : '';
60
+ if (!ts)
61
+ throw new Error('Slack chat.postMessage returned no ts');
62
+ return { channel, ts };
63
+ }
64
+ /** Edit an existing message (the streaming-update path). */
65
+ async updateMessage(args) {
66
+ await this.call('chat.update', {
67
+ channel: args.channel,
68
+ ts: args.ts,
69
+ text: args.text,
70
+ });
71
+ }
72
+ /** Optional: read a thread's replies (unused by v1 streaming, exposed for tools). */
73
+ async conversationsReplies(args) {
74
+ const json = await this.call('conversations.replies', {
75
+ channel: args.channel,
76
+ ts: args.ts,
77
+ });
78
+ const messages = json['messages'];
79
+ return Array.isArray(messages) ? messages : [];
80
+ }
81
+ }
82
+ //# sourceMappingURL=slack-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-client.js","sourceRoot":"","sources":["../../src/channel/slack-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,cAAc,GAAG,uBAAuB,CAAC;AA+B/C,MAAM,OAAO,WAAW;IACL,KAAK,CAAS;IACd,OAAO,CAAS;IAChB,SAAS,CAAe;IAEzC,YAAY,IAAwB;QAClC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,cAAc,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACpE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IAC3C,CAAC;IAEO,KAAK,CAAC,IAAI,CAAC,MAAc,EAAE,IAA6B;QAC9D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,OAAO,IAAI,MAAM,EAAE,EAAE;YAC5D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,iCAAiC;gBACjD,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;aACtC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAC;QACH,6EAA6E;QAC7E,oEAAoE;QACpE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,SAAS,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAqB,CAAC;QACpD,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,SAAS,MAAM,YAAY,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC;QAC9E,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,4DAA4D;IAC5D,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7E,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACvE,OAAO;YACL,SAAS;YACT,GAAG,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3E,GAAG,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,GAAG,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACjE,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,KAAK,CAAC,WAAW,CAAC,IAIjB;QACC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE;YAC/C,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvD,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;QACrF,MAAM,EAAE,GAAG,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAClE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,4DAA4D;IAC5D,KAAK,CAAC,aAAa,CAAC,IAAmD;QACrE,MAAM,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE;YAC7B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB,CAAC,CAAC;IACL,CAAC;IAED,qFAAqF;IACrF,KAAK,CAAC,oBAAoB,CAAC,IAG1B;QACC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,uBAAuB,EAAE;YACpD,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,EAAE,EAAE,IAAI,CAAC,EAAE;SACZ,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;QAClC,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAE,QAA2C,CAAC,CAAC,CAAC,EAAE,CAAC;IACrF,CAAC;CACF"}
@@ -0,0 +1,39 @@
1
+ import type { newTurnId } from '@moxxy/core';
2
+ import type { ClientSession as Session } from '@moxxy/sdk';
3
+ import type { SlackClient } from './slack-client.js';
4
+ export interface TurnRunnerLogger {
5
+ warn?(msg: string, meta?: Record<string, unknown>): void;
6
+ }
7
+ export interface RunSlackTurnDeps {
8
+ readonly session: Session;
9
+ readonly client: SlackClient;
10
+ readonly editFrameMs: number;
11
+ readonly logger?: TurnRunnerLogger;
12
+ }
13
+ export interface RunSlackTurnOptions {
14
+ /** Channel the triggering event arrived in. */
15
+ readonly channel: string;
16
+ /** Thread root for the reply: `event.thread_ts ?? event.ts`. */
17
+ readonly threadTs: string;
18
+ readonly text: string;
19
+ readonly model?: string;
20
+ readonly controller: AbortController;
21
+ /** Pre-minted turn id; the channel records it as an own-turn id. */
22
+ readonly turnId: ReturnType<typeof newTurnId>;
23
+ }
24
+ /**
25
+ * Drive a single Slack turn end-to-end: subscribe the frame pump to THIS turn's
26
+ * events (filtered by turnId — `session.log` fans out to every listener, so a
27
+ * concurrent turn on the same Session would otherwise stream into this thread,
28
+ * AGENTS.md invariant #8), run the turn through `runTurn`, flush the final
29
+ * frame, and unwind in `finally`.
30
+ *
31
+ * The streaming loop is `@moxxy/channel-kit`'s {@link FramePump} ("post once
32
+ * via `chat.postMessage`, then edit THAT message via `chat.update`, throttled
33
+ * to `editFrameMs`") over a {@link PlainTurnRenderer} snapshot; only the Slack
34
+ * Web-API calls live here. The turnId is minted by the caller so the channel
35
+ * can also record it as an own-turn id (it filters foreign-turn mirroring on
36
+ * those).
37
+ */
38
+ export declare function runSlackTurn(deps: RunSlackTurnDeps, opts: RunSlackTurnOptions): Promise<void>;
39
+ //# sourceMappingURL=turn-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-runner.d.ts","sourceRoot":"","sources":["../../src/channel/turn-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1D;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;CAC/C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,IAAI,CAAC,CA6Df"}
@@ -0,0 +1,81 @@
1
+ import { FramePump, PlainTurnRenderer, driveTurn, subscribeTurn } from '@moxxy/channel-kit';
2
+ /**
3
+ * Drive a single Slack turn end-to-end: subscribe the frame pump to THIS turn's
4
+ * events (filtered by turnId — `session.log` fans out to every listener, so a
5
+ * concurrent turn on the same Session would otherwise stream into this thread,
6
+ * AGENTS.md invariant #8), run the turn through `runTurn`, flush the final
7
+ * frame, and unwind in `finally`.
8
+ *
9
+ * The streaming loop is `@moxxy/channel-kit`'s {@link FramePump} ("post once
10
+ * via `chat.postMessage`, then edit THAT message via `chat.update`, throttled
11
+ * to `editFrameMs`") over a {@link PlainTurnRenderer} snapshot; only the Slack
12
+ * Web-API calls live here. The turnId is minted by the caller so the channel
13
+ * can also record it as an own-turn id (it filters foreign-turn mirroring on
14
+ * those).
15
+ */
16
+ export async function runSlackTurn(deps, opts) {
17
+ const { session, client, editFrameMs, logger } = deps;
18
+ const { channel, threadTs, text, model, controller, turnId } = opts;
19
+ const renderer = new PlainTurnRenderer();
20
+ const pump = new FramePump({
21
+ editFrameMs,
22
+ frame: () => renderer.snapshot(),
23
+ // Guarantee at least one message even when the turn produced no text.
24
+ emptyFinalText: '_(no output)_',
25
+ sink: {
26
+ send: async (t) => {
27
+ try {
28
+ const res = await client.postMessage({ channel, text: t, threadTs });
29
+ return res.ts;
30
+ }
31
+ catch (err) {
32
+ logger?.warn?.('slack chat.postMessage failed', { err: String(err) });
33
+ return null;
34
+ }
35
+ },
36
+ edit: async (ts, t) => {
37
+ try {
38
+ await client.updateMessage({ channel, ts, text: t });
39
+ }
40
+ catch (err) {
41
+ logger?.warn?.('slack chat.update failed', { err: String(err) });
42
+ }
43
+ },
44
+ },
45
+ });
46
+ const unsubscribe = subscribeTurn(session, turnId, (event) => {
47
+ if (renderer.accept(event))
48
+ pump.scheduleEdit();
49
+ });
50
+ try {
51
+ await driveTurn(session, {
52
+ turnId,
53
+ prompt: text,
54
+ ...(model ? { model } : {}),
55
+ signal: controller.signal,
56
+ });
57
+ await pump.flush(true);
58
+ }
59
+ catch (err) {
60
+ logger?.warn?.('slack turn failed', {
61
+ err: err instanceof Error ? err.message : String(err),
62
+ });
63
+ // Surface the failure into the thread rather than leaving a dangling
64
+ // placeholder. Errors from this send are swallowed (best-effort).
65
+ try {
66
+ await client.postMessage({
67
+ channel,
68
+ threadTs,
69
+ text: `Turn failed: ${err instanceof Error ? err.message : String(err)}`,
70
+ });
71
+ }
72
+ catch {
73
+ /* ignore */
74
+ }
75
+ }
76
+ finally {
77
+ unsubscribe();
78
+ pump.dispose();
79
+ }
80
+ }
81
+ //# sourceMappingURL=turn-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-runner.js","sourceRoot":"","sources":["../../src/channel/turn-runner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AA0B5F;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAsB,EACtB,IAAyB;IAEzB,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACtD,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEpE,MAAM,QAAQ,GAAG,IAAI,iBAAiB,EAAE,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,SAAS,CAAS;QACjC,WAAW;QACX,KAAK,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE;QAChC,sEAAsE;QACtE,cAAc,EAAE,eAAe;QAC/B,IAAI,EAAE;YACJ,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;gBAChB,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;oBACrE,OAAO,GAAG,CAAC,EAAE,CAAC;gBAChB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,MAAM,EAAE,IAAI,EAAE,CAAC,+BAA+B,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBACtE,OAAO,IAAI,CAAC;gBACd,CAAC;YACH,CAAC;YACD,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE;gBACpB,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;gBACvD,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,MAAM,EAAE,IAAI,EAAE,CAAC,0BAA0B,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACnE,CAAC;YACH,CAAC;SACF;KACF,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;QAC3D,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC;YAAE,IAAI,CAAC,YAAY,EAAE,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,OAAO,EAAE;YACvB,MAAM;YACN,MAAM,EAAE,IAAI;YACZ,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,EAAE,IAAI,EAAE,CAAC,mBAAmB,EAAE;YAClC,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACtD,CAAC,CAAC;QACH,qEAAqE;QACrE,kEAAkE;QAClE,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,WAAW,CAAC;gBACvB,OAAO;gBACP,QAAQ;gBACR,IAAI,EAAE,gBAAgB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;aACzE,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;IACH,CAAC;YAAS,CAAC;QACT,WAAW,EAAE,CAAC;QACd,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;AACH,CAAC"}
@@ -0,0 +1,100 @@
1
+ import type { ClientSession as Session } from '@moxxy/sdk';
2
+ import type { Channel, ChannelHandle, ChannelStartOptsBase, PermissionResolver, TunnelProviderDef } from '@moxxy/sdk';
3
+ import type { VaultStore } from '@moxxy/plugin-vault';
4
+ import { SlackClient } from './channel/slack-client.js';
5
+ export interface SlackChannelLogger {
6
+ debug?(msg: string, meta?: Record<string, unknown>): void;
7
+ info?(msg: string, meta?: Record<string, unknown>): void;
8
+ warn?(msg: string, meta?: Record<string, unknown>): void;
9
+ error?(msg: string, meta?: Record<string, unknown>): void;
10
+ }
11
+ export interface SlackChannelOptions {
12
+ readonly vault: VaultStore;
13
+ /**
14
+ * Tunnel provider used to expose the local ingest server publicly. Defaults to
15
+ * the self-hosted `proxyTunnel` (imported directly, like the webhooks tunnel);
16
+ * tests pass a fake provider so they never hit the network.
17
+ */
18
+ readonly tunnelProvider?: TunnelProviderDef;
19
+ /** Tools the model may call autonomously. `['*']` allows every registered tool. */
20
+ readonly allowedTools?: ReadonlyArray<string>;
21
+ /** Debounce window for streaming `chat.update` edits (ms). Default 1000. */
22
+ readonly editFrameMs?: number;
23
+ /** Bind host for the local ingest server. Default 127.0.0.1. */
24
+ readonly host?: string;
25
+ readonly logger?: SlackChannelLogger;
26
+ /** Injectable Slack client factory (tests). */
27
+ readonly makeClient?: (token: string) => SlackClient;
28
+ }
29
+ export interface SlackStartOpts extends ChannelStartOptsBase {
30
+ readonly session: Session;
31
+ /**
32
+ * If true, arm a TOFU pairing window: the first verified inbound event from a
33
+ * team/channel is captured and (via `onPairConfirm`) persisted to the vault.
34
+ */
35
+ readonly pair?: boolean;
36
+ /** Override allowedTools at start (the CLI forwards channel config / flags). */
37
+ readonly allowedTools?: ReadonlyArray<string>;
38
+ }
39
+ /** Fires when a verified event lands during a pairing window. */
40
+ export interface PairCandidate {
41
+ readonly teamId: string;
42
+ readonly channelId: string;
43
+ }
44
+ export declare class SlackChannel implements Channel<SlackStartOpts> {
45
+ readonly name = "slack";
46
+ /**
47
+ * Installed on the session by the CLI dispatcher. Replaced in `start()` once
48
+ * we can expand a `['*']` allow-list against the live tool registry; until
49
+ * then it denies everything (safe default before start).
50
+ */
51
+ permissionResolver: PermissionResolver;
52
+ private readonly opts;
53
+ private session;
54
+ private client;
55
+ private ingest;
56
+ private tunnel;
57
+ private handle;
58
+ private resolveRunning;
59
+ private botUserId;
60
+ private authorization;
61
+ private model;
62
+ private readonly turns;
63
+ private currentChannelId;
64
+ private currentThreadTs;
65
+ private lastChannelId;
66
+ private lastThreadTs;
67
+ private logUnsub;
68
+ private readonly tofu;
69
+ private publicUrl;
70
+ constructor(opts: SlackChannelOptions);
71
+ /** The public Request URL to paste into Slack's Event Subscriptions. */
72
+ get requestUrl(): string | null;
73
+ /** Subscribe to pairing candidates (the setup/pair flows use this). */
74
+ onPairCandidate(listener: (c: PairCandidate) => void): () => void;
75
+ /** Persist a team/channel as authorized (called by the pair flow on confirm). */
76
+ confirmPairing(candidate: PairCandidate): Promise<void>;
77
+ start(startOpts: SlackStartOpts): Promise<ChannelHandle>;
78
+ private openTunnel;
79
+ /**
80
+ * TOFU pairing hook: while a pairing window is armed, the first verified event
81
+ * captures the team/channel and notifies listeners. It's "consumed" (no turn)
82
+ * so the very first message just establishes trust. Returns true when consumed.
83
+ */
84
+ private handlePairingCandidate;
85
+ /**
86
+ * Fire-and-forget a turn for an authorized event. Single-flight (v1): if a
87
+ * turn is already running we drop the new event with a thread reply rather
88
+ * than corrupting the per-turn state. The ingest handler has already ACKed.
89
+ */
90
+ private dispatchTurn;
91
+ private runTurn;
92
+ /**
93
+ * Post the assistant's prose for a turn this channel did not initiate. Gated by
94
+ * `!busy` (our own turns stream via the frame pump) and by having served a
95
+ * thread at least once. Skipped for our own turnIds (robust to async ordering /
96
+ * replay, invariant #8).
97
+ */
98
+ private mirrorForeignTurn;
99
+ }
100
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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"}