@geminixiang/mikan 1.0.0-beta.40 → 1.0.0-beta.41
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 +26 -0
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +7 -6
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/components.d.ts +33 -0
- package/dist/adapters/discord/components.d.ts.map +1 -0
- package/dist/adapters/discord/components.js +32 -0
- package/dist/adapters/discord/components.js.map +1 -0
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +16 -6
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/discord/format.d.ts +3 -0
- package/dist/adapters/discord/format.d.ts.map +1 -0
- package/dist/adapters/discord/format.js +86 -0
- package/dist/adapters/discord/format.js.map +1 -0
- package/dist/adapters/markdown-tables.d.ts +37 -0
- package/dist/adapters/markdown-tables.d.ts.map +1 -0
- package/dist/adapters/markdown-tables.js +164 -0
- package/dist/adapters/markdown-tables.js.map +1 -0
- package/dist/adapters/progressive-renderer.d.ts.map +1 -1
- package/dist/adapters/progressive-renderer.js +69 -12
- package/dist/adapters/progressive-renderer.js.map +1 -1
- package/dist/adapters/slack/assistant.d.ts +101 -0
- package/dist/adapters/slack/assistant.d.ts.map +1 -0
- package/dist/adapters/slack/assistant.js +204 -0
- package/dist/adapters/slack/assistant.js.map +1 -0
- package/dist/adapters/slack/blocks.d.ts.map +1 -1
- package/dist/adapters/slack/blocks.js +4 -82
- package/dist/adapters/slack/blocks.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +52 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +161 -5
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
- package/dist/adapters/slack/response-lifecycle.js +86 -15
- package/dist/adapters/slack/response-lifecycle.js.map +1 -1
- package/dist/adapters/slack/stream-limits.d.ts +35 -0
- package/dist/adapters/slack/stream-limits.d.ts.map +1 -0
- package/dist/adapters/slack/stream-limits.js +56 -0
- package/dist/adapters/slack/stream-limits.js.map +1 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +26 -47
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +7 -19
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/types.d.ts +24 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +2 -1
- package/dist/agent.js.map +1 -1
- package/dist/commands/extensions.d.ts +18 -6
- package/dist/commands/extensions.d.ts.map +1 -1
- package/dist/commands/extensions.js +55 -8
- package/dist/commands/extensions.js.map +1 -1
- package/dist/env-manifest.d.ts.map +1 -1
- package/dist/env-manifest.js +4 -0
- package/dist/env-manifest.js.map +1 -1
- package/dist/harness/subagent-runner.d.ts +6 -0
- package/dist/harness/subagent-runner.d.ts.map +1 -1
- package/dist/harness/subagent-runner.js +63 -0
- package/dist/harness/subagent-runner.js.map +1 -1
- package/dist/subagent-progress.d.ts +3 -7
- package/dist/subagent-progress.d.ts.map +1 -1
- package/dist/subagent-progress.js +15 -10
- package/dist/subagent-progress.js.map +1 -1
- package/dist/tools/subagent.d.ts +8 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +24 -1
- package/dist/tools/subagent.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/web/admin/portal.js +5 -1
- package/dist/web/admin/portal.js.map +1 -1
- package/package.json +2 -1
- package/dist/adapters/telegram/html.d.ts +0 -3
- package/dist/adapters/telegram/html.d.ts.map +0 -1
- package/dist/adapters/telegram/html.js +0 -101
- package/dist/adapters/telegram/html.js.map +0 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** The `assistant_thread` payload on the `assistant_view` lifecycle events. */
|
|
2
|
+
export interface AssistantThreadPayload {
|
|
3
|
+
user_id?: string;
|
|
4
|
+
channel_id?: string;
|
|
5
|
+
thread_ts?: string;
|
|
6
|
+
context?: AgentContext;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Which channel the person is viewing beside the pane. Slack spells this
|
|
10
|
+
* `context` on `app_home_opened` and `app_context_changed`, and `app_context`
|
|
11
|
+
* on `message.im` — the asymmetry is Slack's, normalized here.
|
|
12
|
+
*/
|
|
13
|
+
export interface AgentContext {
|
|
14
|
+
channel_id?: string;
|
|
15
|
+
team_id?: string;
|
|
16
|
+
enterprise_id?: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface SuggestedPrompt {
|
|
19
|
+
title: string;
|
|
20
|
+
message: string;
|
|
21
|
+
}
|
|
22
|
+
/** What the adapter needs from the bot to serve this surface. */
|
|
23
|
+
export interface AssistantSurfaceOps {
|
|
24
|
+
postInThread(channel: string, threadTs: string, text: string): Promise<string>;
|
|
25
|
+
/** `threadTs` omitted pins the prompts to the DM instead of one thread. */
|
|
26
|
+
setSuggestedPrompts(channel: string, threadTs: string | undefined, prompts: SuggestedPrompt[]): Promise<void>;
|
|
27
|
+
setTitle(channel: string, threadTs: string, title: string): Promise<void>;
|
|
28
|
+
/** Human-readable channel name for context, when the bot knows it. */
|
|
29
|
+
channelName(channelId: string): string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Which conversations belong to this surface, and the context Slack last
|
|
33
|
+
* reported for each.
|
|
34
|
+
*
|
|
35
|
+
* Two levels, because the two manifest keys register at different grains: an
|
|
36
|
+
* `agent_view` DM is known before any thread exists, while an
|
|
37
|
+
* `assistant_view` thread is known individually.
|
|
38
|
+
*
|
|
39
|
+
* In memory on purpose: transient UI state that Slack re-sends on open and on
|
|
40
|
+
* every change, so a restart self-heals on the next interaction. Persisting it
|
|
41
|
+
* would add a store to keep consistent for nothing that survives the trip.
|
|
42
|
+
*/
|
|
43
|
+
export declare class AssistantThreadRegistry {
|
|
44
|
+
private threads;
|
|
45
|
+
private channels;
|
|
46
|
+
private titled;
|
|
47
|
+
private key;
|
|
48
|
+
/** An `assistant_view` thread. */
|
|
49
|
+
remember(channelId: string, threadTs: string, context?: AgentContext): void;
|
|
50
|
+
/** An `agent_view` DM, which has no thread yet. */
|
|
51
|
+
rememberChannel(channelId: string, context?: AgentContext): void;
|
|
52
|
+
/** Thread context, falling back to the channel's. */
|
|
53
|
+
contextFor(channelId: string, threadTs: string): AgentContext | undefined;
|
|
54
|
+
channelContext(channelId: string): AgentContext | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Whether this thread is part of the pane — either registered individually,
|
|
57
|
+
* or living in a DM known to be the agent surface. The second case is what
|
|
58
|
+
* makes titles work under `agent_view`, where Slack opens threads itself and
|
|
59
|
+
* never announces them.
|
|
60
|
+
*/
|
|
61
|
+
isAgentSurface(channelId: string, threadTs: string): boolean;
|
|
62
|
+
/** True the first time a thread is seen, so a title is set exactly once. */
|
|
63
|
+
claimTitle(channelId: string, threadTs: string): boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* `assistant_view`: a new conversation opened. Greet, and offer prompts for
|
|
67
|
+
* that thread.
|
|
68
|
+
*
|
|
69
|
+
* Failures are logged, never thrown. A greeting that did not post is a worse
|
|
70
|
+
* pane, not a broken conversation, and taking the socket handler down over it
|
|
71
|
+
* would break the surface entirely.
|
|
72
|
+
*/
|
|
73
|
+
export declare function handleAssistantThreadStarted(ops: AssistantSurfaceOps, registry: AssistantThreadRegistry, thread: AssistantThreadPayload): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* `agent_view`: the person opened the app's DM. Refresh the prompts pinned to
|
|
76
|
+
* it — and deliberately do not greet, because Slack sends this on *every*
|
|
77
|
+
* open, so a greeting here would arrive again and again.
|
|
78
|
+
*/
|
|
79
|
+
export declare function handleAgentDmOpened(ops: AssistantSurfaceOps, registry: AssistantThreadRegistry, channelId: string, context?: AgentContext): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* The person navigated elsewhere. Recorded at both grains so the next turn —
|
|
82
|
+
* and the next prompt refresh — knows where they are, whichever manifest key
|
|
83
|
+
* this app uses.
|
|
84
|
+
*/
|
|
85
|
+
export declare function handleAgentContextChanged(registry: AssistantThreadRegistry, payload: {
|
|
86
|
+
channel_id?: string;
|
|
87
|
+
thread_ts?: string;
|
|
88
|
+
context?: AgentContext;
|
|
89
|
+
}): void;
|
|
90
|
+
/**
|
|
91
|
+
* Title a conversation from the first thing the person said, so the pane's
|
|
92
|
+
* sidebar is navigable. Without it every past conversation looks the same.
|
|
93
|
+
*/
|
|
94
|
+
export declare function titleAssistantThread(ops: AssistantSurfaceOps, registry: AssistantThreadRegistry, channelId: string, threadTs: string, firstMessage: string): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* First line, trimmed to something a sidebar can show. Deliberately not a
|
|
97
|
+
* model call: a title is worth having immediately and cheaply, and the
|
|
98
|
+
* person's own words label the conversation better than a paraphrase.
|
|
99
|
+
*/
|
|
100
|
+
export declare function summarizeTitle(message: string): string;
|
|
101
|
+
//# sourceMappingURL=assistant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/assistant.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack's agent / assistant surface — the app's own pane, separate from
|
|
3
|
+
* channels and ordinary DMs.
|
|
4
|
+
*
|
|
5
|
+
* There are two of these, not one, and they differ in which events signal
|
|
6
|
+
* "someone opened a conversation":
|
|
7
|
+
*
|
|
8
|
+
* | Manifest key | Opened signal | Prompts |
|
|
9
|
+
* | ----------------- | ------------------------------------ | ------------- |
|
|
10
|
+
* | `agent_view` | `app_home_opened` with tab=`messages` | pinned to the DM |
|
|
11
|
+
* | `assistant_view` | `assistant_thread_started` | per thread |
|
|
12
|
+
*
|
|
13
|
+
* Slack's own words for the newer one: "rely on the `app_home_opened` event to
|
|
14
|
+
* know when a user has actively opened a DM with your app", and
|
|
15
|
+
* "the `assistant_thread_started` event no longer indicates this". What the
|
|
16
|
+
* docs do *not* say is whether `assistant_thread_started` still fires for an
|
|
17
|
+
* `agent_view` app — so both paths are handled rather than guessed at, which
|
|
18
|
+
* also means an app on either manifest key works without a migration.
|
|
19
|
+
*
|
|
20
|
+
* The two signals need different behaviour, and conflating them would be a
|
|
21
|
+
* bug: `assistant_thread_started` fires once per new conversation, so greeting
|
|
22
|
+
* is right; `app_home_opened` fires **every time the pane is opened**, so
|
|
23
|
+
* greeting there would nag. The agent path therefore only refreshes the
|
|
24
|
+
* pinned prompts, which is idempotent.
|
|
25
|
+
*
|
|
26
|
+
* Session semantics are deliberately unchanged. Each conversation is a thread
|
|
27
|
+
* and mikan already scopes a thread to its own session — the right shape,
|
|
28
|
+
* because the pane's sidebar is a list of separate conversations rather than
|
|
29
|
+
* one transcript. Continuity across them comes from the office's memory.
|
|
30
|
+
*
|
|
31
|
+
* Suggested prompts are deterministic, varying only with the channel Slack
|
|
32
|
+
* says the person is viewing. Enough to feel aware without spending a model
|
|
33
|
+
* call before they have typed anything.
|
|
34
|
+
*/
|
|
35
|
+
import * as log from "../../log.js";
|
|
36
|
+
const GREETING = "有什麼我可以幫忙的?";
|
|
37
|
+
const GREETING_WITH_CHANNEL = (channel) => `有什麼我可以幫忙的?我看得到你正在 #${channel}。`;
|
|
38
|
+
/** Prompts offered with no channel context — general-purpose starting points. */
|
|
39
|
+
const DEFAULT_PROMPTS = [
|
|
40
|
+
{ title: "看看工作區", message: "這個工作區裡有什麼?先給我一個概覽。" },
|
|
41
|
+
{ title: "最近的變更", message: "最近的程式碼變更有哪些?挑重要的說。" },
|
|
42
|
+
{ title: "排一個提醒", message: "幫我排一個每天早上的提醒。" },
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Prompts when Slack tells us which channel the person is looking at. Naming
|
|
46
|
+
* the channel is the cheap half of "context-aware": it costs nothing and it is
|
|
47
|
+
* what makes the suggestions feel addressed to the moment.
|
|
48
|
+
*/
|
|
49
|
+
function promptsFor(channel) {
|
|
50
|
+
if (!channel)
|
|
51
|
+
return DEFAULT_PROMPTS;
|
|
52
|
+
return [
|
|
53
|
+
{ title: `#${channel} 在討論什麼`, message: `幫我摘要 #${channel} 最近的討論。` },
|
|
54
|
+
{ title: "有什麼要我接手", message: `#${channel} 裡有沒有需要我處理或追蹤的事?` },
|
|
55
|
+
{ title: "看看工作區", message: "這個工作區裡有什麼?先給我一個概覽。" },
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Which conversations belong to this surface, and the context Slack last
|
|
60
|
+
* reported for each.
|
|
61
|
+
*
|
|
62
|
+
* Two levels, because the two manifest keys register at different grains: an
|
|
63
|
+
* `agent_view` DM is known before any thread exists, while an
|
|
64
|
+
* `assistant_view` thread is known individually.
|
|
65
|
+
*
|
|
66
|
+
* In memory on purpose: transient UI state that Slack re-sends on open and on
|
|
67
|
+
* every change, so a restart self-heals on the next interaction. Persisting it
|
|
68
|
+
* would add a store to keep consistent for nothing that survives the trip.
|
|
69
|
+
*/
|
|
70
|
+
export class AssistantThreadRegistry {
|
|
71
|
+
constructor() {
|
|
72
|
+
this.threads = new Map();
|
|
73
|
+
this.channels = new Map();
|
|
74
|
+
this.titled = new Set();
|
|
75
|
+
}
|
|
76
|
+
key(channelId, threadTs) {
|
|
77
|
+
return `${channelId}\n${threadTs}`;
|
|
78
|
+
}
|
|
79
|
+
/** An `assistant_view` thread. */
|
|
80
|
+
remember(channelId, threadTs, context) {
|
|
81
|
+
this.threads.set(this.key(channelId, threadTs), context ?? {});
|
|
82
|
+
}
|
|
83
|
+
/** An `agent_view` DM, which has no thread yet. */
|
|
84
|
+
rememberChannel(channelId, context) {
|
|
85
|
+
this.channels.set(channelId, context ?? {});
|
|
86
|
+
}
|
|
87
|
+
/** Thread context, falling back to the channel's. */
|
|
88
|
+
contextFor(channelId, threadTs) {
|
|
89
|
+
return this.threads.get(this.key(channelId, threadTs)) ?? this.channels.get(channelId);
|
|
90
|
+
}
|
|
91
|
+
channelContext(channelId) {
|
|
92
|
+
return this.channels.get(channelId);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Whether this thread is part of the pane — either registered individually,
|
|
96
|
+
* or living in a DM known to be the agent surface. The second case is what
|
|
97
|
+
* makes titles work under `agent_view`, where Slack opens threads itself and
|
|
98
|
+
* never announces them.
|
|
99
|
+
*/
|
|
100
|
+
isAgentSurface(channelId, threadTs) {
|
|
101
|
+
return this.threads.has(this.key(channelId, threadTs)) || this.channels.has(channelId);
|
|
102
|
+
}
|
|
103
|
+
/** True the first time a thread is seen, so a title is set exactly once. */
|
|
104
|
+
claimTitle(channelId, threadTs) {
|
|
105
|
+
const key = this.key(channelId, threadTs);
|
|
106
|
+
if (this.titled.has(key))
|
|
107
|
+
return false;
|
|
108
|
+
this.titled.add(key);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* `assistant_view`: a new conversation opened. Greet, and offer prompts for
|
|
114
|
+
* that thread.
|
|
115
|
+
*
|
|
116
|
+
* Failures are logged, never thrown. A greeting that did not post is a worse
|
|
117
|
+
* pane, not a broken conversation, and taking the socket handler down over it
|
|
118
|
+
* would break the surface entirely.
|
|
119
|
+
*/
|
|
120
|
+
export async function handleAssistantThreadStarted(ops, registry, thread) {
|
|
121
|
+
const channelId = thread.channel_id;
|
|
122
|
+
const threadTs = thread.thread_ts;
|
|
123
|
+
if (!channelId || !threadTs)
|
|
124
|
+
return;
|
|
125
|
+
registry.remember(channelId, threadTs, thread.context);
|
|
126
|
+
const channel = thread.context?.channel_id
|
|
127
|
+
? ops.channelName(thread.context.channel_id)
|
|
128
|
+
: undefined;
|
|
129
|
+
await swallow("greeting", () => ops.postInThread(channelId, threadTs, channel ? GREETING_WITH_CHANNEL(channel) : GREETING));
|
|
130
|
+
await swallow("setSuggestedPrompts", () => ops.setSuggestedPrompts(channelId, threadTs, promptsFor(channel)));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* `agent_view`: the person opened the app's DM. Refresh the prompts pinned to
|
|
134
|
+
* it — and deliberately do not greet, because Slack sends this on *every*
|
|
135
|
+
* open, so a greeting here would arrive again and again.
|
|
136
|
+
*/
|
|
137
|
+
export async function handleAgentDmOpened(ops, registry, channelId, context) {
|
|
138
|
+
if (!channelId)
|
|
139
|
+
return;
|
|
140
|
+
registry.rememberChannel(channelId, context);
|
|
141
|
+
const channel = context?.channel_id ? ops.channelName(context.channel_id) : undefined;
|
|
142
|
+
// The agent surface is otherwise silent, and its failure mode — no prompts,
|
|
143
|
+
// untitled conversations — looks identical from the outside to Slack simply
|
|
144
|
+
// not sending the event. This line is the difference between the two, and
|
|
145
|
+
// is the first thing to look for when the pane seems inert.
|
|
146
|
+
log.logInfo(`[${channelId}] Slack agent DM opened${channel ? ` (viewing #${channel})` : ""}; refreshing suggested prompts`);
|
|
147
|
+
await swallow("setSuggestedPrompts", () => ops.setSuggestedPrompts(channelId, undefined, promptsFor(channel)));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The person navigated elsewhere. Recorded at both grains so the next turn —
|
|
151
|
+
* and the next prompt refresh — knows where they are, whichever manifest key
|
|
152
|
+
* this app uses.
|
|
153
|
+
*/
|
|
154
|
+
export function handleAgentContextChanged(registry, payload) {
|
|
155
|
+
if (!payload.channel_id)
|
|
156
|
+
return;
|
|
157
|
+
// Logged for the same reason as the open event: these two are the whole
|
|
158
|
+
// agent surface, and whether either is subscribed is otherwise invisible
|
|
159
|
+
// from inside mikan.
|
|
160
|
+
log.logInfo(`[${payload.channel_id}] Slack agent context changed${payload.context?.channel_id ? ` (viewing ${payload.context.channel_id})` : ""}`);
|
|
161
|
+
if (payload.thread_ts) {
|
|
162
|
+
registry.remember(payload.channel_id, payload.thread_ts, payload.context);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
registry.rememberChannel(payload.channel_id, payload.context);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Title a conversation from the first thing the person said, so the pane's
|
|
169
|
+
* sidebar is navigable. Without it every past conversation looks the same.
|
|
170
|
+
*/
|
|
171
|
+
export async function titleAssistantThread(ops, registry, channelId, threadTs, firstMessage) {
|
|
172
|
+
if (!registry.isAgentSurface(channelId, threadTs))
|
|
173
|
+
return;
|
|
174
|
+
if (!registry.claimTitle(channelId, threadTs))
|
|
175
|
+
return;
|
|
176
|
+
const title = summarizeTitle(firstMessage);
|
|
177
|
+
if (!title)
|
|
178
|
+
return;
|
|
179
|
+
await swallow("setTitle", () => ops.setTitle(channelId, threadTs, title));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* First line, trimmed to something a sidebar can show. Deliberately not a
|
|
183
|
+
* model call: a title is worth having immediately and cheaply, and the
|
|
184
|
+
* person's own words label the conversation better than a paraphrase.
|
|
185
|
+
*/
|
|
186
|
+
export function summarizeTitle(message) {
|
|
187
|
+
const firstLine = message
|
|
188
|
+
.replace(/<@[^>]+>/g, "")
|
|
189
|
+
.split("\n")
|
|
190
|
+
.map((line) => line.trim())
|
|
191
|
+
.find((line) => line.length > 0);
|
|
192
|
+
if (!firstLine)
|
|
193
|
+
return "";
|
|
194
|
+
return firstLine.length <= 50 ? firstLine : `${firstLine.slice(0, 49)}…`;
|
|
195
|
+
}
|
|
196
|
+
async function swallow(label, run) {
|
|
197
|
+
try {
|
|
198
|
+
await run();
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
log.logWarning(`Slack agent surface ${label} failed`, err instanceof Error ? err.message : String(err));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=assistant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant.js","sourceRoot":"","sources":["../../../src/adapters/slack/assistant.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Slack's agent / assistant surface — the app's own pane, separate from\n * channels and ordinary DMs.\n *\n * There are two of these, not one, and they differ in which events signal\n * \"someone opened a conversation\":\n *\n * | Manifest key | Opened signal | Prompts |\n * | ----------------- | ------------------------------------ | ------------- |\n * | `agent_view` | `app_home_opened` with tab=`messages` | pinned to the DM |\n * | `assistant_view` | `assistant_thread_started` | per thread |\n *\n * Slack's own words for the newer one: \"rely on the `app_home_opened` event to\n * know when a user has actively opened a DM with your app\", and\n * \"the `assistant_thread_started` event no longer indicates this\". What the\n * docs do *not* say is whether `assistant_thread_started` still fires for an\n * `agent_view` app — so both paths are handled rather than guessed at, which\n * also means an app on either manifest key works without a migration.\n *\n * The two signals need different behaviour, and conflating them would be a\n * bug: `assistant_thread_started` fires once per new conversation, so greeting\n * is right; `app_home_opened` fires **every time the pane is opened**, so\n * greeting there would nag. The agent path therefore only refreshes the\n * pinned prompts, which is idempotent.\n *\n * Session semantics are deliberately unchanged. Each conversation is a thread\n * and mikan already scopes a thread to its own session — the right shape,\n * because the pane's sidebar is a list of separate conversations rather than\n * one transcript. Continuity across them comes from the office's memory.\n *\n * Suggested prompts are deterministic, varying only with the channel Slack\n * says the person is viewing. Enough to feel aware without spending a model\n * call before they have typed anything.\n */\nimport * as log from \"../../log.js\";\n\n/** The `assistant_thread` payload on the `assistant_view` lifecycle events. */\nexport interface AssistantThreadPayload {\n user_id?: string;\n channel_id?: string;\n thread_ts?: string;\n context?: AgentContext;\n}\n\n/**\n * Which channel the person is viewing beside the pane. Slack spells this\n * `context` on `app_home_opened` and `app_context_changed`, and `app_context`\n * on `message.im` — the asymmetry is Slack's, normalized here.\n */\nexport interface AgentContext {\n channel_id?: string;\n team_id?: string;\n enterprise_id?: string | null;\n}\n\nexport interface SuggestedPrompt {\n title: string;\n message: string;\n}\n\n/** What the adapter needs from the bot to serve this surface. */\nexport interface AssistantSurfaceOps {\n postInThread(channel: string, threadTs: string, text: string): Promise<string>;\n /** `threadTs` omitted pins the prompts to the DM instead of one thread. */\n setSuggestedPrompts(\n channel: string,\n threadTs: string | undefined,\n prompts: SuggestedPrompt[],\n ): Promise<void>;\n setTitle(channel: string, threadTs: string, title: string): Promise<void>;\n /** Human-readable channel name for context, when the bot knows it. */\n channelName(channelId: string): string | undefined;\n}\n\nconst GREETING = \"有什麼我可以幫忙的?\";\nconst GREETING_WITH_CHANNEL = (channel: string) =>\n `有什麼我可以幫忙的?我看得到你正在 #${channel}。`;\n\n/** Prompts offered with no channel context — general-purpose starting points. */\nconst DEFAULT_PROMPTS: SuggestedPrompt[] = [\n { title: \"看看工作區\", message: \"這個工作區裡有什麼?先給我一個概覽。\" },\n { title: \"最近的變更\", message: \"最近的程式碼變更有哪些?挑重要的說。\" },\n { title: \"排一個提醒\", message: \"幫我排一個每天早上的提醒。\" },\n];\n\n/**\n * Prompts when Slack tells us which channel the person is looking at. Naming\n * the channel is the cheap half of \"context-aware\": it costs nothing and it is\n * what makes the suggestions feel addressed to the moment.\n */\nfunction promptsFor(channel: string | undefined): SuggestedPrompt[] {\n if (!channel) return DEFAULT_PROMPTS;\n return [\n { title: `#${channel} 在討論什麼`, message: `幫我摘要 #${channel} 最近的討論。` },\n { title: \"有什麼要我接手\", message: `#${channel} 裡有沒有需要我處理或追蹤的事?` },\n { title: \"看看工作區\", message: \"這個工作區裡有什麼?先給我一個概覽。\" },\n ];\n}\n\n/**\n * Which conversations belong to this surface, and the context Slack last\n * reported for each.\n *\n * Two levels, because the two manifest keys register at different grains: an\n * `agent_view` DM is known before any thread exists, while an\n * `assistant_view` thread is known individually.\n *\n * In memory on purpose: transient UI state that Slack re-sends on open and on\n * every change, so a restart self-heals on the next interaction. Persisting it\n * would add a store to keep consistent for nothing that survives the trip.\n */\nexport class AssistantThreadRegistry {\n private threads = new Map<string, AgentContext>();\n private channels = new Map<string, AgentContext>();\n private titled = new Set<string>();\n\n private key(channelId: string, threadTs: string): string {\n return `${channelId}\\n${threadTs}`;\n }\n\n /** An `assistant_view` thread. */\n remember(channelId: string, threadTs: string, context?: AgentContext): void {\n this.threads.set(this.key(channelId, threadTs), context ?? {});\n }\n\n /** An `agent_view` DM, which has no thread yet. */\n rememberChannel(channelId: string, context?: AgentContext): void {\n this.channels.set(channelId, context ?? {});\n }\n\n /** Thread context, falling back to the channel's. */\n contextFor(channelId: string, threadTs: string): AgentContext | undefined {\n return this.threads.get(this.key(channelId, threadTs)) ?? this.channels.get(channelId);\n }\n\n channelContext(channelId: string): AgentContext | undefined {\n return this.channels.get(channelId);\n }\n\n /**\n * Whether this thread is part of the pane — either registered individually,\n * or living in a DM known to be the agent surface. The second case is what\n * makes titles work under `agent_view`, where Slack opens threads itself and\n * never announces them.\n */\n isAgentSurface(channelId: string, threadTs: string): boolean {\n return this.threads.has(this.key(channelId, threadTs)) || this.channels.has(channelId);\n }\n\n /** True the first time a thread is seen, so a title is set exactly once. */\n claimTitle(channelId: string, threadTs: string): boolean {\n const key = this.key(channelId, threadTs);\n if (this.titled.has(key)) return false;\n this.titled.add(key);\n return true;\n }\n}\n\n/**\n * `assistant_view`: a new conversation opened. Greet, and offer prompts for\n * that thread.\n *\n * Failures are logged, never thrown. A greeting that did not post is a worse\n * pane, not a broken conversation, and taking the socket handler down over it\n * would break the surface entirely.\n */\nexport async function handleAssistantThreadStarted(\n ops: AssistantSurfaceOps,\n registry: AssistantThreadRegistry,\n thread: AssistantThreadPayload,\n): Promise<void> {\n const channelId = thread.channel_id;\n const threadTs = thread.thread_ts;\n if (!channelId || !threadTs) return;\n\n registry.remember(channelId, threadTs, thread.context);\n const channel = thread.context?.channel_id\n ? ops.channelName(thread.context.channel_id)\n : undefined;\n\n await swallow(\"greeting\", () =>\n ops.postInThread(channelId, threadTs, channel ? GREETING_WITH_CHANNEL(channel) : GREETING),\n );\n await swallow(\"setSuggestedPrompts\", () =>\n ops.setSuggestedPrompts(channelId, threadTs, promptsFor(channel)),\n );\n}\n\n/**\n * `agent_view`: the person opened the app's DM. Refresh the prompts pinned to\n * it — and deliberately do not greet, because Slack sends this on *every*\n * open, so a greeting here would arrive again and again.\n */\nexport async function handleAgentDmOpened(\n ops: AssistantSurfaceOps,\n registry: AssistantThreadRegistry,\n channelId: string,\n context?: AgentContext,\n): Promise<void> {\n if (!channelId) return;\n registry.rememberChannel(channelId, context);\n const channel = context?.channel_id ? ops.channelName(context.channel_id) : undefined;\n // The agent surface is otherwise silent, and its failure mode — no prompts,\n // untitled conversations — looks identical from the outside to Slack simply\n // not sending the event. This line is the difference between the two, and\n // is the first thing to look for when the pane seems inert.\n log.logInfo(\n `[${channelId}] Slack agent DM opened${channel ? ` (viewing #${channel})` : \"\"}; refreshing suggested prompts`,\n );\n await swallow(\"setSuggestedPrompts\", () =>\n ops.setSuggestedPrompts(channelId, undefined, promptsFor(channel)),\n );\n}\n\n/**\n * The person navigated elsewhere. Recorded at both grains so the next turn —\n * and the next prompt refresh — knows where they are, whichever manifest key\n * this app uses.\n */\nexport function handleAgentContextChanged(\n registry: AssistantThreadRegistry,\n payload: { channel_id?: string; thread_ts?: string; context?: AgentContext },\n): void {\n if (!payload.channel_id) return;\n // Logged for the same reason as the open event: these two are the whole\n // agent surface, and whether either is subscribed is otherwise invisible\n // from inside mikan.\n log.logInfo(\n `[${payload.channel_id}] Slack agent context changed${payload.context?.channel_id ? ` (viewing ${payload.context.channel_id})` : \"\"}`,\n );\n if (payload.thread_ts) {\n registry.remember(payload.channel_id, payload.thread_ts, payload.context);\n return;\n }\n registry.rememberChannel(payload.channel_id, payload.context);\n}\n\n/**\n * Title a conversation from the first thing the person said, so the pane's\n * sidebar is navigable. Without it every past conversation looks the same.\n */\nexport async function titleAssistantThread(\n ops: AssistantSurfaceOps,\n registry: AssistantThreadRegistry,\n channelId: string,\n threadTs: string,\n firstMessage: string,\n): Promise<void> {\n if (!registry.isAgentSurface(channelId, threadTs)) return;\n if (!registry.claimTitle(channelId, threadTs)) return;\n const title = summarizeTitle(firstMessage);\n if (!title) return;\n await swallow(\"setTitle\", () => ops.setTitle(channelId, threadTs, title));\n}\n\n/**\n * First line, trimmed to something a sidebar can show. Deliberately not a\n * model call: a title is worth having immediately and cheaply, and the\n * person's own words label the conversation better than a paraphrase.\n */\nexport function summarizeTitle(message: string): string {\n const firstLine = message\n .replace(/<@[^>]+>/g, \"\")\n .split(\"\\n\")\n .map((line) => line.trim())\n .find((line) => line.length > 0);\n if (!firstLine) return \"\";\n return firstLine.length <= 50 ? firstLine : `${firstLine.slice(0, 49)}…`;\n}\n\nasync function swallow(label: string, run: () => Promise<unknown>): Promise<void> {\n try {\n await run();\n } catch (err) {\n log.logWarning(\n `Slack agent surface ${label} failed`,\n err instanceof Error ? err.message : String(err),\n );\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/blocks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/blocks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AA8B/C;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,GACrE,MAAM,CAiBR;AAuDD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,UAAU,EAAE,CAAA;CAAE,CA6BxF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import MarkdownIt from "markdown-it";
|
|
2
|
+
import { extractMarkdownTables, inlinePlainText, normalizeMarkdownTables, } from "../markdown-tables.js";
|
|
2
3
|
// Parser is used only to locate tables and derive plain-text fallback.
|
|
3
4
|
// Prose is never re-serialized from tokens: it is sliced verbatim from the
|
|
4
5
|
// source and handed to Slack as a native `markdown` block, so Slack owns
|
|
@@ -48,33 +49,6 @@ export function resolveSlackMentions(source, users) {
|
|
|
48
49
|
return id ? `<@${id}>` : mention;
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
|
-
function normalizeMarkdownTables(source) {
|
|
52
|
-
return source
|
|
53
|
-
.split("\n")
|
|
54
|
-
.map((line) => {
|
|
55
|
-
const trimmed = line.trim();
|
|
56
|
-
if (!trimmed.startsWith("|") || !trimmed.endsWith("|"))
|
|
57
|
-
return line;
|
|
58
|
-
if (!/^[|+\-:\s]+$/.test(trimmed) || !trimmed.includes("+"))
|
|
59
|
-
return line;
|
|
60
|
-
return line.replaceAll("+", "|");
|
|
61
|
-
})
|
|
62
|
-
.join("\n");
|
|
63
|
-
}
|
|
64
|
-
function inlinePlainText(tokens) {
|
|
65
|
-
if (!tokens)
|
|
66
|
-
return "";
|
|
67
|
-
let text = "";
|
|
68
|
-
for (const token of tokens) {
|
|
69
|
-
if (token.type === "text" || token.type === "code_inline")
|
|
70
|
-
text += token.content;
|
|
71
|
-
else if (token.type === "softbreak" || token.type === "hardbreak")
|
|
72
|
-
text += "\n";
|
|
73
|
-
else if (token.children)
|
|
74
|
-
text += inlinePlainText(token.children);
|
|
75
|
-
}
|
|
76
|
-
return text;
|
|
77
|
-
}
|
|
78
52
|
function buildTableBlock(headers, rows) {
|
|
79
53
|
const hasIndex = headers[0] === "#";
|
|
80
54
|
const tableHeaders = hasIndex ? headers : ["#", ...headers];
|
|
@@ -87,50 +61,6 @@ function buildTableBlock(headers, rows) {
|
|
|
87
61
|
column_settings: tableHeaders.map(() => ({ is_wrapped: true })),
|
|
88
62
|
};
|
|
89
63
|
}
|
|
90
|
-
function parseTable(tokens, index) {
|
|
91
|
-
const open = tokens[index];
|
|
92
|
-
if (open?.type !== "table_open" || !open.map)
|
|
93
|
-
return null;
|
|
94
|
-
const headers = [];
|
|
95
|
-
const rows = [];
|
|
96
|
-
let currentRow = null;
|
|
97
|
-
let inHead = false;
|
|
98
|
-
let i = index + 1;
|
|
99
|
-
for (; i < tokens.length; i++) {
|
|
100
|
-
const token = tokens[i];
|
|
101
|
-
if (token === undefined || token.type === "table_close")
|
|
102
|
-
break;
|
|
103
|
-
if (token.type === "thead_open")
|
|
104
|
-
inHead = true;
|
|
105
|
-
else if (token.type === "thead_close")
|
|
106
|
-
inHead = false;
|
|
107
|
-
else if (token.type === "tr_open")
|
|
108
|
-
currentRow = [];
|
|
109
|
-
else if (token.type === "tr_close" && currentRow) {
|
|
110
|
-
if (inHead)
|
|
111
|
-
headers.push(...currentRow);
|
|
112
|
-
else
|
|
113
|
-
rows.push(currentRow);
|
|
114
|
-
currentRow = null;
|
|
115
|
-
}
|
|
116
|
-
else if (token.type === "inline" && currentRow) {
|
|
117
|
-
currentRow.push(inlinePlainText(token.children) || token.content);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (!headers.length || !rows.length)
|
|
121
|
-
return null;
|
|
122
|
-
const fallback = [headers.join(" | "), ...rows.map((row) => row.join(" | "))].join("\n");
|
|
123
|
-
const endLine = tokens[i]?.map?.[1] ?? open.map[1];
|
|
124
|
-
return {
|
|
125
|
-
table: {
|
|
126
|
-
block: buildTableBlock(headers, rows),
|
|
127
|
-
fallback,
|
|
128
|
-
startLine: open.map[0],
|
|
129
|
-
endLine,
|
|
130
|
-
},
|
|
131
|
-
next: i,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
64
|
/** Split prose into chunks under the markdown block limit, preferring
|
|
135
65
|
* paragraph boundaries, then line boundaries, then a hard slice. */
|
|
136
66
|
function splitProse(text) {
|
|
@@ -176,16 +106,8 @@ function plainTextFallback(tokens) {
|
|
|
176
106
|
}
|
|
177
107
|
export function renderSlackBlocks(source) {
|
|
178
108
|
const normalized = normalizeMarkdownTables(convertLegacyMrkdwnLinks(source));
|
|
179
|
-
const tokens = markdown.parse(normalized, {});
|
|
180
109
|
const lines = normalized.split("\n");
|
|
181
|
-
const tables =
|
|
182
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
183
|
-
const parsed = parseTable(tokens, i);
|
|
184
|
-
if (parsed) {
|
|
185
|
-
tables.push(parsed.table);
|
|
186
|
-
i = parsed.next;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
110
|
+
const tables = extractMarkdownTables(normalized);
|
|
189
111
|
const blocks = [];
|
|
190
112
|
const fallback = [];
|
|
191
113
|
let cursor = 0;
|
|
@@ -195,8 +117,8 @@ export function renderSlackBlocks(source) {
|
|
|
195
117
|
if (prose.trim())
|
|
196
118
|
fallback.push(plainTextFallback(markdown.parse(prose, {})));
|
|
197
119
|
if (blocks.length < MAX_BLOCKS) {
|
|
198
|
-
blocks.push(table.
|
|
199
|
-
fallback.push(table.
|
|
120
|
+
blocks.push(buildTableBlock(table.headers, table.rows));
|
|
121
|
+
fallback.push([table.headers.join(" | "), ...table.rows.map((row) => row.join(" | "))].join("\n"));
|
|
200
122
|
}
|
|
201
123
|
cursor = table.endLine;
|
|
202
124
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocks.js","sourceRoot":"","sources":["../../../src/adapters/slack/blocks.ts"],"names":[],"mappings":"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","sourcesContent":["import MarkdownIt from \"markdown-it\";\nimport type Token from \"markdown-it/lib/token.mjs\";\nimport type { KnownBlock } from \"@slack/types\";\n\n// Parser is used only to locate tables and derive plain-text fallback.\n// Prose is never re-serialized from tokens: it is sliced verbatim from the\n// source and handed to Slack as a native `markdown` block, so Slack owns\n// all prose rendering (same renderer as the markdown_text streaming API).\nconst markdown = new MarkdownIt({ html: false });\n\nconst MAX_BLOCKS = 50;\n// Slack rejects messages whose markdown block text exceeds 12k (msg_too_long).\nconst MARKDOWN_TEXT_LIMIT = 12000;\nconst FIELD_TEXT_LIMIT = 2000;\n\n// Legacy Slack-style links (<url|label>) predate the standard-GFM response\n// source contract; system producers may still emit them.\nconst LEGACY_MRKDWN_LINK_PATTERN = /<(https?:\\/\\/[^<>|\\s]+)\\|([^<>\\n]+)>/g;\n\nfunction convertLegacyMrkdwnLinks(source: string): string {\n return source.replace(LEGACY_MRKDWN_LINK_PATTERN, \"[$2]($1)\");\n}\n\n// Slack user/bot-user ids: U/W prefix. Already-native mentions pass through.\nconst SLACK_USER_ID_PATTERN = /^[UW][A-Z0-9]{2,}$/;\nconst MENTION_PATTERN = /<@([^<>\\n]+)>/g;\n\n/**\n * Resolve response-source mentions to Slack's native form.\n *\n * The response source is platform-neutral: the model writes `<@userName>`\n * from the prompt's Users table, and the adapter owns the conversion to\n * `<@U…>` — Slack only links and notifies on the raw user id. Lookup covers\n * userName and displayName case-insensitively; native-id mentions pass\n * through, and unknown names stay verbatim rather than guessing.\n */\nexport function resolveSlackMentions(\n source: string,\n users: Iterable<{ id: string; userName: string; displayName: string }>,\n): string {\n if (!source.includes(\"<@\")) return source;\n const list = [...users];\n const byName = new Map<string, string>();\n for (const user of list) {\n if (user.userName) byName.set(user.userName.toLowerCase(), user.id);\n }\n // Display names never shadow a userName held by someone else.\n for (const user of list) {\n const display = user.displayName?.toLowerCase();\n if (display && !byName.has(display)) byName.set(display, user.id);\n }\n return source.replace(MENTION_PATTERN, (mention, name: string) => {\n if (SLACK_USER_ID_PATTERN.test(name)) return mention;\n const id = byName.get(name.trim().replace(/^@/, \"\").toLowerCase());\n return id ? `<@${id}>` : mention;\n });\n}\n\nfunction normalizeMarkdownTables(source: string): string {\n return source\n .split(\"\\n\")\n .map((line) => {\n const trimmed = line.trim();\n if (!trimmed.startsWith(\"|\") || !trimmed.endsWith(\"|\")) return line;\n if (!/^[|+\\-:\\s]+$/.test(trimmed) || !trimmed.includes(\"+\")) return line;\n return line.replaceAll(\"+\", \"|\");\n })\n .join(\"\\n\");\n}\n\nfunction inlinePlainText(tokens: Token[] | null | undefined): string {\n if (!tokens) return \"\";\n let text = \"\";\n for (const token of tokens) {\n if (token.type === \"text\" || token.type === \"code_inline\") text += token.content;\n else if (token.type === \"softbreak\" || token.type === \"hardbreak\") text += \"\\n\";\n else if (token.children) text += inlinePlainText(token.children);\n }\n return text;\n}\n\nfunction buildTableBlock(headers: string[], rows: string[][]): KnownBlock {\n const hasIndex = headers[0] === \"#\";\n const tableHeaders = hasIndex ? headers : [\"#\", ...headers];\n const tableRows = hasIndex\n ? rows\n : rows.map((row, rowNumber) => [String(rowNumber + 1)].concat(row));\n return {\n type: \"table\",\n rows: [tableHeaders, ...tableRows].map((row) =>\n row.map((cell) => ({ type: \"raw_text\", text: (cell || \" \").slice(0, FIELD_TEXT_LIMIT) })),\n ),\n column_settings: tableHeaders.map(() => ({ is_wrapped: true })),\n } as KnownBlock;\n}\n\ninterface ParsedTable {\n block: KnownBlock;\n fallback: string;\n startLine: number;\n endLine: number;\n}\n\nfunction parseTable(tokens: Token[], index: number): { table: ParsedTable; next: number } | null {\n const open = tokens[index];\n if (open?.type !== \"table_open\" || !open.map) return null;\n\n const headers: string[] = [];\n const rows: string[][] = [];\n let currentRow: string[] | null = null;\n let inHead = false;\n let i = index + 1;\n\n for (; i < tokens.length; i++) {\n const token = tokens[i];\n if (token === undefined || token.type === \"table_close\") break;\n if (token.type === \"thead_open\") inHead = true;\n else if (token.type === \"thead_close\") inHead = false;\n else if (token.type === \"tr_open\") currentRow = [];\n else if (token.type === \"tr_close\" && currentRow) {\n if (inHead) headers.push(...currentRow);\n else rows.push(currentRow);\n currentRow = null;\n } else if (token.type === \"inline\" && currentRow) {\n currentRow.push(inlinePlainText(token.children) || token.content);\n }\n }\n\n if (!headers.length || !rows.length) return null;\n\n const fallback = [headers.join(\" | \"), ...rows.map((row) => row.join(\" | \"))].join(\"\\n\");\n const endLine = tokens[i]?.map?.[1] ?? open.map[1];\n return {\n table: {\n block: buildTableBlock(headers, rows),\n fallback,\n startLine: open.map[0],\n endLine,\n },\n next: i,\n };\n}\n\n/** Split prose into chunks under the markdown block limit, preferring\n * paragraph boundaries, then line boundaries, then a hard slice. */\nfunction splitProse(text: string): string[] {\n if (text.length <= MARKDOWN_TEXT_LIMIT) return [text];\n const chunks: string[] = [];\n let rest = text;\n while (rest.length > MARKDOWN_TEXT_LIMIT) {\n const window = rest.slice(0, MARKDOWN_TEXT_LIMIT);\n let cut = window.lastIndexOf(\"\\n\\n\");\n if (cut <= 0) cut = window.lastIndexOf(\"\\n\");\n if (cut <= 0) cut = MARKDOWN_TEXT_LIMIT;\n chunks.push(rest.slice(0, cut));\n rest = rest.slice(cut);\n }\n if (rest.trim()) chunks.push(rest);\n return chunks;\n}\n\nfunction pushMarkdown(blocks: KnownBlock[], prose: string): void {\n const trimmed = prose.trim();\n if (!trimmed) return;\n for (const chunk of splitProse(trimmed)) {\n if (blocks.length >= MAX_BLOCKS) return;\n const text = chunk.trim();\n if (text) blocks.push({ type: \"markdown\", text } as KnownBlock);\n }\n}\n\nfunction plainTextFallback(tokens: Token[]): string {\n const parts: string[] = [];\n for (const token of tokens) {\n if (token.type === \"inline\") parts.push(inlinePlainText(token.children) || token.content);\n else if (token.type === \"fence\" || token.type === \"code_block\")\n parts.push(token.content.trimEnd());\n }\n return parts.filter(Boolean).join(\"\\n\");\n}\n\nexport function renderSlackBlocks(source: string): { text: string; blocks: KnownBlock[] } {\n const normalized = normalizeMarkdownTables(convertLegacyMrkdwnLinks(source));\n const tokens = markdown.parse(normalized, {});\n const lines = normalized.split(\"\\n\");\n\n const tables: ParsedTable[] = [];\n for (let i = 0; i < tokens.length; i++) {\n const parsed = parseTable(tokens, i);\n if (parsed) {\n tables.push(parsed.table);\n i = parsed.next;\n }\n }\n\n const blocks: KnownBlock[] = [];\n const fallback: string[] = [];\n let cursor = 0;\n for (const table of tables) {\n const prose = lines.slice(cursor, table.startLine).join(\"\\n\");\n pushMarkdown(blocks, prose);\n if (prose.trim()) fallback.push(plainTextFallback(markdown.parse(prose, {})));\n if (blocks.length < MAX_BLOCKS) {\n blocks.push(table.block);\n fallback.push(table.fallback);\n }\n cursor = table.endLine;\n }\n const trailingProse = lines.slice(cursor).join(\"\\n\");\n pushMarkdown(blocks, trailingProse);\n if (trailingProse.trim()) fallback.push(plainTextFallback(markdown.parse(trailingProse, {})));\n\n return {\n text: fallback.filter(Boolean).join(\"\\n\\n\") || normalized,\n blocks,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"blocks.js","sourceRoot":"","sources":["../../../src/adapters/slack/blocks.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,aAAa,CAAC;AAGrC,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAE/B,uEAAuE;AACvE,2EAA2E;AAC3E,yEAAyE;AACzE,0EAA0E;AAC1E,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAEjD,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,+EAA+E;AAC/E,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAClC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,2EAA2E;AAC3E,yDAAyD;AACzD,MAAM,0BAA0B,GAAG,uCAAuC,CAAC;AAE3E,SAAS,wBAAwB,CAAC,MAAc;IAC9C,OAAO,MAAM,CAAC,OAAO,CAAC,0BAA0B,EAAE,UAAU,CAAC,CAAC;AAChE,CAAC;AAED,6EAA6E;AAC7E,MAAM,qBAAqB,GAAG,oBAAoB,CAAC;AACnD,MAAM,eAAe,GAAG,gBAAgB,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAc,EACd,KAAsE;IAEtE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IAC1C,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IACxB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,IAAI,IAAI,CAAC,QAAQ;YAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,8DAA8D;IAC9D,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC;QAChD,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,OAAO,EAAE,IAAY,EAAE,EAAE;QAC/D,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,OAAO,CAAC;QACrD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QACnE,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,OAAiB,EAAE,IAAgB;IAC1D,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;IACpC,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAG,QAAQ;QACxB,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,CAAC,YAAY,EAAE,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAC7C,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAC1F;QACD,eAAe,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;KAClD,CAAC;AAClB,CAAC;AAED;qEACqE;AACrE,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,IAAI,CAAC,MAAM,IAAI,mBAAmB;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,OAAO,IAAI,CAAC,MAAM,GAAG,mBAAmB,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC;QAClD,IAAI,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,GAAG,IAAI,CAAC;YAAE,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,GAAG,IAAI,CAAC;YAAE,GAAG,GAAG,mBAAmB,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAChC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,EAAE;QAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,YAAY,CAAC,MAAoB,EAAE,KAAa;IACvD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACxC,IAAI,MAAM,CAAC,MAAM,IAAI,UAAU;YAAE,OAAO;QACxC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1B,IAAI,IAAI;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAgB,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAe;IACxC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;aACrF,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY;YAC5D,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,MAAM,UAAU,GAAG,uBAAuB,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7E,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAErC,MAAM,MAAM,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAEjD,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,EAAE;YAAE,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9E,IAAI,MAAM,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YACxD,QAAQ,CAAC,IAAI,CACX,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACpF,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC;IACzB,CAAC;IACD,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrD,YAAY,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACpC,IAAI,aAAa,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAE9F,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,UAAU;QACzD,MAAM;KACP,CAAC;AACJ,CAAC","sourcesContent":["import MarkdownIt from \"markdown-it\";\nimport type Token from \"markdown-it/lib/token.mjs\";\nimport type { KnownBlock } from \"@slack/types\";\nimport {\n extractMarkdownTables,\n inlinePlainText,\n normalizeMarkdownTables,\n} from \"../markdown-tables.js\";\n\n// Parser is used only to locate tables and derive plain-text fallback.\n// Prose is never re-serialized from tokens: it is sliced verbatim from the\n// source and handed to Slack as a native `markdown` block, so Slack owns\n// all prose rendering (same renderer as the markdown_text streaming API).\nconst markdown = new MarkdownIt({ html: false });\n\nconst MAX_BLOCKS = 50;\n// Slack rejects messages whose markdown block text exceeds 12k (msg_too_long).\nconst MARKDOWN_TEXT_LIMIT = 12000;\nconst FIELD_TEXT_LIMIT = 2000;\n\n// Legacy Slack-style links (<url|label>) predate the standard-GFM response\n// source contract; system producers may still emit them.\nconst LEGACY_MRKDWN_LINK_PATTERN = /<(https?:\\/\\/[^<>|\\s]+)\\|([^<>\\n]+)>/g;\n\nfunction convertLegacyMrkdwnLinks(source: string): string {\n return source.replace(LEGACY_MRKDWN_LINK_PATTERN, \"[$2]($1)\");\n}\n\n// Slack user/bot-user ids: U/W prefix. Already-native mentions pass through.\nconst SLACK_USER_ID_PATTERN = /^[UW][A-Z0-9]{2,}$/;\nconst MENTION_PATTERN = /<@([^<>\\n]+)>/g;\n\n/**\n * Resolve response-source mentions to Slack's native form.\n *\n * The response source is platform-neutral: the model writes `<@userName>`\n * from the prompt's Users table, and the adapter owns the conversion to\n * `<@U…>` — Slack only links and notifies on the raw user id. Lookup covers\n * userName and displayName case-insensitively; native-id mentions pass\n * through, and unknown names stay verbatim rather than guessing.\n */\nexport function resolveSlackMentions(\n source: string,\n users: Iterable<{ id: string; userName: string; displayName: string }>,\n): string {\n if (!source.includes(\"<@\")) return source;\n const list = [...users];\n const byName = new Map<string, string>();\n for (const user of list) {\n if (user.userName) byName.set(user.userName.toLowerCase(), user.id);\n }\n // Display names never shadow a userName held by someone else.\n for (const user of list) {\n const display = user.displayName?.toLowerCase();\n if (display && !byName.has(display)) byName.set(display, user.id);\n }\n return source.replace(MENTION_PATTERN, (mention, name: string) => {\n if (SLACK_USER_ID_PATTERN.test(name)) return mention;\n const id = byName.get(name.trim().replace(/^@/, \"\").toLowerCase());\n return id ? `<@${id}>` : mention;\n });\n}\n\nfunction buildTableBlock(headers: string[], rows: string[][]): KnownBlock {\n const hasIndex = headers[0] === \"#\";\n const tableHeaders = hasIndex ? headers : [\"#\", ...headers];\n const tableRows = hasIndex\n ? rows\n : rows.map((row, rowNumber) => [String(rowNumber + 1)].concat(row));\n return {\n type: \"table\",\n rows: [tableHeaders, ...tableRows].map((row) =>\n row.map((cell) => ({ type: \"raw_text\", text: (cell || \" \").slice(0, FIELD_TEXT_LIMIT) })),\n ),\n column_settings: tableHeaders.map(() => ({ is_wrapped: true })),\n } as KnownBlock;\n}\n\n/** Split prose into chunks under the markdown block limit, preferring\n * paragraph boundaries, then line boundaries, then a hard slice. */\nfunction splitProse(text: string): string[] {\n if (text.length <= MARKDOWN_TEXT_LIMIT) return [text];\n const chunks: string[] = [];\n let rest = text;\n while (rest.length > MARKDOWN_TEXT_LIMIT) {\n const window = rest.slice(0, MARKDOWN_TEXT_LIMIT);\n let cut = window.lastIndexOf(\"\\n\\n\");\n if (cut <= 0) cut = window.lastIndexOf(\"\\n\");\n if (cut <= 0) cut = MARKDOWN_TEXT_LIMIT;\n chunks.push(rest.slice(0, cut));\n rest = rest.slice(cut);\n }\n if (rest.trim()) chunks.push(rest);\n return chunks;\n}\n\nfunction pushMarkdown(blocks: KnownBlock[], prose: string): void {\n const trimmed = prose.trim();\n if (!trimmed) return;\n for (const chunk of splitProse(trimmed)) {\n if (blocks.length >= MAX_BLOCKS) return;\n const text = chunk.trim();\n if (text) blocks.push({ type: \"markdown\", text } as KnownBlock);\n }\n}\n\nfunction plainTextFallback(tokens: Token[]): string {\n const parts: string[] = [];\n for (const token of tokens) {\n if (token.type === \"inline\") parts.push(inlinePlainText(token.children) || token.content);\n else if (token.type === \"fence\" || token.type === \"code_block\")\n parts.push(token.content.trimEnd());\n }\n return parts.filter(Boolean).join(\"\\n\");\n}\n\nexport function renderSlackBlocks(source: string): { text: string; blocks: KnownBlock[] } {\n const normalized = normalizeMarkdownTables(convertLegacyMrkdwnLinks(source));\n const lines = normalized.split(\"\\n\");\n\n const tables = extractMarkdownTables(normalized);\n\n const blocks: KnownBlock[] = [];\n const fallback: string[] = [];\n let cursor = 0;\n for (const table of tables) {\n const prose = lines.slice(cursor, table.startLine).join(\"\\n\");\n pushMarkdown(blocks, prose);\n if (prose.trim()) fallback.push(plainTextFallback(markdown.parse(prose, {})));\n if (blocks.length < MAX_BLOCKS) {\n blocks.push(buildTableBlock(table.headers, table.rows));\n fallback.push(\n [table.headers.join(\" | \"), ...table.rows.map((row) => row.join(\" | \"))].join(\"\\n\"),\n );\n }\n cursor = table.endLine;\n }\n const trailingProse = lines.slice(cursor).join(\"\\n\");\n pushMarkdown(blocks, trailingProse);\n if (trailingProse.trim()) fallback.push(plainTextFallback(markdown.parse(trailingProse, {})));\n\n return {\n text: fallback.filter(Boolean).join(\"\\n\\n\") || normalized,\n blocks,\n };\n}\n"]}
|
|
@@ -2,6 +2,9 @@ import { type MessagingBot, type ConversationEvent, type MessagingEventHandler,
|
|
|
2
2
|
import { type Workspace } from "../../office/index.js";
|
|
3
3
|
import type { EventsWatcher } from "../../events.js";
|
|
4
4
|
import type { SlackChannel, SlackUser } from "./types.js";
|
|
5
|
+
import { type SuggestedPrompt } from "./assistant.js";
|
|
6
|
+
/** Slices `text` into pieces that each fit one streaming call. */
|
|
7
|
+
export declare function chunkStreamText(text: string, limit?: number): string[];
|
|
5
8
|
/**
|
|
6
9
|
* Build a Slack context block whose text is capped at the mrkdwn limit.
|
|
7
10
|
* Used for muted diagnostics and ephemeral command responses.
|
|
@@ -21,6 +24,15 @@ export declare class SlackMessagingBot implements MessagingBot {
|
|
|
21
24
|
private startupTs;
|
|
22
25
|
private users;
|
|
23
26
|
private channels;
|
|
27
|
+
/** Which threads belong to the assistant pane, and their last known context. */
|
|
28
|
+
private assistantThreads;
|
|
29
|
+
/**
|
|
30
|
+
* Native response streams opened recently. Per bot rather than per module:
|
|
31
|
+
* the rate tier applies per workspace, and this bot is the connection to
|
|
32
|
+
* one — which also means a test constructing a bot gets a fresh budget
|
|
33
|
+
* instead of inheriting whatever ran before it.
|
|
34
|
+
*/
|
|
35
|
+
private streamStarts;
|
|
24
36
|
private queues;
|
|
25
37
|
private eventsWatcher;
|
|
26
38
|
/** Host office dir for a Slack conversation. */
|
|
@@ -67,12 +79,52 @@ export declare class SlackMessagingBot implements MessagingBot {
|
|
|
67
79
|
/** Refresh and list the workspace's active users (`users.list`). */
|
|
68
80
|
listUsers(): Promise<PlatformUserInfo[]>;
|
|
69
81
|
updateMessage(channel: string, ts: string, text: string): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Claim one native stream, or decline. Declining is not an error: the caller
|
|
84
|
+
* falls back to edit-based updates, which is a working answer rather than a
|
|
85
|
+
* degraded one, and costs no request against the tighter start tier.
|
|
86
|
+
*/
|
|
87
|
+
tryReserveStreamStart(): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Open a stream, carrying text of any length.
|
|
90
|
+
*
|
|
91
|
+
* Slack caps `markdown_text` at 12,000 characters per call, and a long first
|
|
92
|
+
* render exceeds that easily — mikan allows 35,000 in a message. Rather than
|
|
93
|
+
* truncating (losing the answer) or failing (falling back to edit mode for
|
|
94
|
+
* the whole response), the opening call takes the first slice and the rest
|
|
95
|
+
* follows as appends before this resolves. The caller sees one operation
|
|
96
|
+
* that sent everything, so the renderer's idea of what has been streamed
|
|
97
|
+
* stays true.
|
|
98
|
+
*
|
|
99
|
+
* Splitting mid-word or mid-fence is harmless here: a stream concatenates,
|
|
100
|
+
* so Slack renders the reassembled text, not the pieces.
|
|
101
|
+
*/
|
|
70
102
|
startMessageStream(channel: string, text: string, threadTs?: string, recipientUserId?: string): Promise<string>;
|
|
103
|
+
/** Append a delta of any length, split to the per-call cap. */
|
|
71
104
|
appendMessageStream(channel: string, ts: string, text: string): Promise<void>;
|
|
105
|
+
private appendStreamChunk;
|
|
72
106
|
stopMessageStream(channel: string, ts: string): Promise<void>;
|
|
73
107
|
deleteMessage(channel: string, ts: string): Promise<void>;
|
|
74
108
|
/** Set the status for an assistant thread (shows "thinking" state) */
|
|
75
109
|
setAssistantStatus(channel: string, threadTs: string, status: string): Promise<void>;
|
|
110
|
+
/** Offer starting points in a new assistant thread. */
|
|
111
|
+
setAssistantSuggestedPrompts(channel: string, threadTs: string | undefined, prompts: SuggestedPrompt[]): Promise<void>;
|
|
112
|
+
/** Name an assistant thread, which is what the sidebar conversation list shows. */
|
|
113
|
+
setAssistantTitle(channel: string, threadTs: string, title: string): Promise<void>;
|
|
114
|
+
/** The assistant surface's view of this bot, handed to the lifecycle handlers. */
|
|
115
|
+
private assistantOps;
|
|
116
|
+
/**
|
|
117
|
+
* A new conversation opened in the assistant pane. Slack waits for the app
|
|
118
|
+
* to speak first here; staying silent is what makes the pane look broken.
|
|
119
|
+
*/
|
|
120
|
+
private handleAssistantThreadStarted;
|
|
121
|
+
/**
|
|
122
|
+
* The person navigated to another channel while the pane is open. Slack
|
|
123
|
+
* sends this as `assistant_thread_context_changed` under `assistant_view`
|
|
124
|
+
* and `app_context_changed` under `agent_view`; the payload differs enough
|
|
125
|
+
* to normalize here rather than in the handler.
|
|
126
|
+
*/
|
|
127
|
+
private handleAgentContextChangedEvent;
|
|
76
128
|
postInThread(channel: string, threadTs: string, text: string): Promise<string>;
|
|
77
129
|
postInThreadBlocks(channel: string, threadTs: string, text: string, blocks: object[]): Promise<string>;
|
|
78
130
|
uploadFile(channel: string, filePath: string, title?: string, threadTs?: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/bot.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/bot.ts"],"names":[],"mappings":"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"}
|