@amykit/plugin-slack 0.5.0 → 0.7.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
@@ -6,11 +6,23 @@ A Slack thread per piece of work, over the Web API, as the conversation port.
6
6
 
7
7
  A plugin for [amy](https://github.com/nicolasmelo1/amy). It provides the `conversation` port.
8
8
 
9
+ Use it when you want to follow and answer amy from Slack. It mounts the conversation port: each piece of work gets a thread in one channel, and only the operator's replies count as answers. It can also hold amy's chat. It needs a bot token, read from the environment or a file.
10
+
11
+ ## What it extends
12
+
13
+ | | |
14
+ | :-- | :-- |
15
+ | Ports | `conversation` |
16
+ | Actions | _none_ |
17
+ | Operations | _none_ |
18
+ | Commands | _none_ |
19
+ | Events | _none_ |
20
+ | Environment | _none_ |
21
+
9
22
  ## Install
10
23
 
11
24
  ```sh
12
- # Add @amykit/plugin-slack to amy.yaml, then install configured plugins:
13
- amy init --install
25
+ amy add @amykit/plugin-slack
14
26
  ```
15
27
 
16
28
  Then name it in `~/.amy/config.yaml`:
@@ -28,8 +40,10 @@ workflows:
28
40
  plugins:
29
41
  "@amykit/plugin-slack":
30
42
  channel: <string>
43
+ decisionReaction: "white_check_mark"
31
44
  defaultRetryAfterSeconds: 60
32
45
  directory: "slack"
46
+ discussionReaction: "speech_balloon"
33
47
  maxRateLimitRetries: 5
34
48
  operator: <string>
35
49
  retentionDays: 30
@@ -39,8 +53,10 @@ plugins:
39
53
  | Setting | Type | Required | Default | What it is |
40
54
  | :-- | :-- | :-- | :-- | :-- |
41
55
  | `channel` | `string` | **yes** | | the channel id every work thread opens in, such as C0XXXXXXX — an id, not a name |
56
+ | `decisionReaction` | `string` | no | `white_check_mark` | the reaction, by emoji name, the operator puts on their own reply to mark it as the decision that moves the work. A reaction Slack lists only in part is read again in full with reactions.get, which needs reactions:read |
42
57
  | `defaultRetryAfterSeconds` | `number` | no | `60` | how long to wait after a 429 that carries no Retry-After; one names its own wait, and that wins |
43
58
  | `directory` | `string` | no | `slack` | where each work item's thread is remembered, relative to the state directory |
59
+ | `discussionReaction` | `string` | no | `speech_balloon` | the reaction, by emoji name, the operator puts on their own reply to mark it as a turn in a discussion, answered in the thread while the work waits |
44
60
  | `maxRateLimitRetries` | `number` | no | `5` | how many times a call Slack rate limits (HTTP 429) is waited out and tried again before it fails |
45
61
  | `operator` | `string` | **yes** | | the user id, such as U0XXXXXXX, whose replies are answers; everybody else's are ignored |
46
62
  | `retentionDays` | `number` | no | `30` | days a downloaded file or a thread's memory stays unused before it is pruned, 0 to keep everything. A pruned file is fetched again when its reply is read; a pruned thread means the next question opens a new one |
@@ -52,7 +68,7 @@ silently never applied.
52
68
 
53
69
  ## Where the reasoning is
54
70
 
55
- - [Writing a plugin](https://github.com/nicolasmelo1/amy/tree/main/docs/build/write-a-plugin.md)
71
+ - [Writing a plugin](https://github.com/nicolasmelo1/amy/tree/main/docs/guides/write-a-plugin.md)
56
72
  - [Reference: plugins](https://github.com/nicolasmelo1/amy/tree/main/docs/reference/plugins.md)
57
73
  - [Ports and the registry](https://github.com/nicolasmelo1/amy/tree/main/docs/concepts/ports.md)
58
74
 
@@ -1,4 +1,4 @@
1
- import { Conversation, Reply, ThreadRef } from "@amykit/core";
1
+ import { Ask, ChatMessage, Conversation, Reply, ThreadRef } from "@amykit/core";
2
2
  import { SlackApi } from "./SlackApi.js";
3
3
  export interface SlackConversationOptions {
4
4
  /** A channel id, not a name. */
@@ -18,6 +18,17 @@ export interface SlackConversationOptions {
18
18
  * pruned; `0` keeps everything.
19
19
  */
20
20
  retentionDays?: number;
21
+ /**
22
+ * The reactions the operator puts on their own reply to mark what it is,
23
+ * by emoji name. A reply without one carries no kind, and the workflow's
24
+ * policy reads it.
25
+ */
26
+ marks?: {
27
+ decision: string;
28
+ discussion: string;
29
+ };
30
+ /** Inspection reads existing state but never creates or changes it. */
31
+ readOnly?: boolean;
21
32
  now?: () => Date;
22
33
  }
23
34
  /** What `amy doctor` prints for this plugin, one line per question. */
@@ -28,10 +39,20 @@ export interface SlackCheck {
28
39
  }
29
40
  /** Long enough for an operator's holiday, short enough that screenshots do not pile up. */
30
41
  export declare const DEFAULT_RETENTION_DAYS = 30;
42
+ /** ✅ settles it, 💬 asks something first. */
43
+ export declare const DEFAULT_MARKS: {
44
+ readonly decision: "white_check_mark";
45
+ readonly discussion: "speech_balloon";
46
+ };
31
47
  export declare class SlackConversation implements Conversation {
32
48
  private readonly api;
33
49
  private readonly options;
34
- private readonly threads;
50
+ readonly asks: true;
51
+ readonly threads: true;
52
+ readonly edit: true;
53
+ /** The channel: its conversations and claims are its own. */
54
+ get source(): string;
55
+ private readonly threadMap;
35
56
  private readonly filesDirectory;
36
57
  private readonly opening;
37
58
  private botUser?;
@@ -41,6 +62,7 @@ export declare class SlackConversation implements Conversation {
41
62
  post(thread: ThreadRef, message: {
42
63
  text: string;
43
64
  files?: string[];
65
+ ask?: Ask;
44
66
  }): Promise<string>;
45
67
  replies(thread: ThreadRef, since: string): Promise<Reply[]>;
46
68
  /**
@@ -66,6 +88,41 @@ export declare class SlackConversation implements Conversation {
66
88
  threads: number;
67
89
  files: number;
68
90
  };
91
+ /**
92
+ * The messages in the channel that reached no work, oldest first, until
93
+ * they are settled: a root message the operator posts, and the operator's
94
+ * replies in that root's thread, which is one conversation (S14). The
95
+ * channel is read for them first, so it is read all the time (S20).
96
+ */
97
+ chat(): Promise<ChatMessage[]>;
98
+ settle(id: string): Promise<void>;
99
+ /** Says something in a conversation's thread. */
100
+ say(place: string, text: string): Promise<string>;
101
+ revise(_place: string, id: string, text: string): Promise<void>;
102
+ /**
103
+ * Files the operator's new root messages, and their replies in the
104
+ * threads of conversations still going, each once. The first read starts
105
+ * from now: what was said in the channel before amy listened is not asked
106
+ * of it. A thread goes quiet a day after the operator last wrote in it.
107
+ */
108
+ private fileChat;
109
+ /** The chat's own state, by channel: a channel changed in the config starts afresh and never answers the old one's messages. */
110
+ private chatDirectory;
111
+ /**
112
+ * One conversation's replies per minute, whichever has waited longest:
113
+ * Slack may allow conversations.replies once a minute for the whole app, not
114
+ * once per thread, so the chat shares that one call rather than queueing.
115
+ */
116
+ private fileDueReplies;
117
+ /** The operator's root messages since the cursor, each a conversation from now on; answers the new cursor. */
118
+ private fileRoots;
119
+ /** The operator's replies in one conversation's thread, at most once a minute, until it goes quiet. */
120
+ private fileReplies;
121
+ /** One channel-wide conversations.replies allowance, shared by chat and work threads. */
122
+ private repliesQuota;
123
+ private fileChatMessage;
124
+ /** The operator, writing as themselves: no bot, no other person, no channel event. */
125
+ private isOperatorMessage;
69
126
  private clock;
70
127
  /** Where one thread's downloads live, so forgetting it takes them too. */
71
128
  private threadFiles;
@@ -79,6 +136,31 @@ export declare class SlackConversation implements Conversation {
79
136
  private dropFiles;
80
137
  /** Throttled, and never the reason a call fails: pruning is housekeeping. */
81
138
  private pruneNowAndThen;
139
+ private postText;
140
+ private postAskFallback;
141
+ /** Reconstruct answers from Slack every time; no accepted-answer cache survives a process. */
142
+ private askedReplies;
143
+ /** The answer this ask holds after this look, if any: the remembered one, or the first complete one seen now. */
144
+ private acceptedAnswer;
145
+ /** The first complete answer is immutable once seen, even if its reaction changes later. */
146
+ protected rememberedAnswer(threadTs: string, askId: string): {
147
+ chosen: string[];
148
+ messageTs: string;
149
+ acceptedAt?: string;
150
+ } | undefined;
151
+ /** Creates the decision once; a competing reader adopts the value written first. */
152
+ private rememberAnswer;
153
+ /** The first ask amy posted in this thread, as microseconds, if it kept one. */
154
+ private askedFrom;
155
+ /** Keeps the earliest ask's ts; a later ask leaves it where it is. */
156
+ private rememberAskedFrom;
157
+ private answerFile;
158
+ private answerDirectory;
159
+ private hasChangedAnswer;
160
+ private answerOf;
161
+ private completeReactions;
162
+ private explainAmbiguity;
163
+ private acknowledgeTaken;
82
164
  private channelChecks;
83
165
  /**
84
166
  * Exactly one process posts a root per attempt; an attempt that follows
@@ -92,9 +174,26 @@ export declare class SlackConversation implements Conversation {
92
174
  * text: two items can share a title, and a title can change.
93
175
  */
94
176
  private findRoot;
95
- /** Every message in the thread after `afterMicros`, across pages. */
177
+ /** Every message in the thread after `afterMicros`, or from it on when `inclusive`: Slack leaves the bound out by default. */
96
178
  private threadMessages;
97
179
  private pages;
180
+ /**
181
+ * What the operator marked their reply as, by their own reaction on it and
182
+ * nobody else's — never by its text. Both marks read as the decision, the
183
+ * one that moves the work, because a person who put both meant to be done.
184
+ * Re-read on every look, so marking a reply after it was answered counts.
185
+ *
186
+ * Slack may list only some of a reaction's users while `count` holds them
187
+ * all. Only a mark whose answer could still change the kind is asked again
188
+ * in full, and a reply the operator may have marked is never read as
189
+ * unmarked: if the full list still cannot say, the read fails and says why.
190
+ */
191
+ private kindOf;
192
+ /** The kind these reactions say, or UNKNOWN when a list too short to tell could change it. */
193
+ private kindFrom;
194
+ /** Whether the operator put this reaction on, or UNKNOWN when Slack's list is short and leaves them out. */
195
+ private marked;
196
+ private allReactions;
98
197
  /** By author id, never by a marker in the text. */
99
198
  private isOperatorAnswer;
100
199
  /** Cached once known; a failure is not, so the next poll asks again. */
@@ -1 +1 @@
1
- {"version":3,"file":"SlackConversation.d.ts","sourceRoot":"","sources":["../src/SlackConversation.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAY,MAAM,eAAe,CAAC;AAGnD,MAAM,WAAW,wBAAwB;IACvC,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAgCD,2FAA2F;AAC3F,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAUzC,qBAAa,iBAAkB,YAAW,YAAY;IAQlD,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAR1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAY;IACpC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;IACjE,OAAO,CAAC,OAAO,CAAC,CAAkB;IAClC,OAAO,CAAC,QAAQ,CAA4B;gBAGzB,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,wBAAwB;IAapD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAoBjD,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBrF,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAyBjE;;;;;;;OAOG;IACG,MAAM,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAWrC;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK5B;;;;OAIG;IACH,KAAK,CAAC,GAAG,GAAE,IAAmB,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAanE,OAAO,CAAC,KAAK;IAIb,0EAA0E;IAC1E,OAAO,CAAC,WAAW;IAInB,2EAA2E;IAC3E,OAAO,CAAC,eAAe;IAIvB;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAMjB,6EAA6E;IAC7E,OAAO,CAAC,eAAe;YAWT,aAAa;IAgC3B;;;OAGG;YACW,WAAW;IAmBzB;;;;;OAKG;YACW,QAAQ;IAetB,qEAAqE;IACrE,OAAO,CAAC,cAAc;YAIR,KAAK;IAYnB,mDAAmD;IACnD,OAAO,CAAC,gBAAgB;IAOxB,wEAAwE;IACxE,OAAO,CAAC,SAAS;IAWjB;;;;OAIG;YACW,WAAW;IAwBzB,uEAAuE;YACzD,QAAQ;CAqBvB"}
1
+ {"version":3,"file":"SlackConversation.d.ts","sourceRoot":"","sources":["../src/SlackConversation.ts"],"names":[],"mappings":"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"}