toolroll 0.9.52 → 0.9.54
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 +16 -0
- package/README.md +55 -80
- package/dist/agentconfig.d.ts +11 -36
- package/dist/agentconfig.js +3 -62
- package/dist/approval-policy.d.ts +1 -1
- package/dist/approval-policy.js +1 -1
- package/dist/board.d.ts +3 -19
- package/dist/board.js +4 -42
- package/dist/browser/workspace.js +18 -18
- package/dist/browser-workspace.d.ts +2 -1
- package/dist/builder.d.ts +9 -40
- package/dist/builder.js +50 -257
- package/dist/chat-actions.js +3 -14
- package/dist/chat-ask.js +8 -8
- package/dist/chat-controls.d.ts +0 -4
- package/dist/chat-controls.js +0 -1
- package/dist/chat-core.d.ts +171 -0
- package/dist/chat-core.js +288 -0
- package/dist/chat-decide.d.ts +10 -8
- package/dist/chat-decide.js +27 -24
- package/dist/chat-delivery-state.d.ts +51 -75
- package/dist/chat-delivery-state.js +140 -170
- package/dist/chat-delivery.d.ts +21 -4
- package/dist/chat-delivery.js +313 -195
- package/dist/chat-flow.d.ts +29 -1
- package/dist/chat-flow.js +24 -50
- package/dist/chat-messages.d.ts +319 -0
- package/dist/chat-messages.js +841 -0
- package/dist/chat-migration.d.ts +30 -0
- package/dist/chat-migration.js +802 -0
- package/dist/chat-quiet.d.ts +3 -0
- package/dist/chat-quiet.js +7 -2
- package/dist/chat-task-actions.d.ts +1 -1
- package/dist/chat-task-actions.js +0 -2
- package/dist/claim.d.ts +79 -244
- package/dist/claim.js +688 -1023
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +10 -2
- package/dist/contracts/chat-content.d.ts +71 -2
- package/dist/contracts/chat-content.js +22 -3
- package/dist/contracts/chat-tables.d.ts +11 -4
- package/dist/contracts/chat-tables.js +165 -87
- package/dist/contracts/cli.d.ts +0 -13
- package/dist/contracts/cli.js +0 -2
- package/dist/contracts/console-api.d.ts +5 -10
- package/dist/contracts/console-api.js +4 -10
- package/dist/contracts/discord-callback.js +2 -1
- package/dist/contracts/flow.d.ts +50 -0
- package/dist/contracts/flow.js +26 -1
- package/dist/contracts/lead-tools.d.ts +0 -13
- package/dist/contracts/lead-tools.js +3 -3
- package/dist/contracts/slack-callback.js +3 -2
- package/dist/contracts/store-json.d.ts +1 -29
- package/dist/contracts/store-json.js +4 -17
- package/dist/contracts/teams-callback.js +2 -1
- package/dist/converse.d.ts +1 -11
- package/dist/converse.js +6 -38
- package/dist/coordinator.d.ts +0 -1
- package/dist/coordinator.js +3 -5
- package/dist/demo.js +16 -26
- package/dist/desktop-update-gate.js +0 -1
- package/dist/desktop-update.js +1 -3
- package/dist/discord-chat.js +19 -38
- package/dist/discord-settings.js +4 -13
- package/dist/discord.js +4 -14
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/dispose.d.ts +2 -3
- package/dist/dispose.js +17 -92
- package/dist/envelope.js +0 -3
- package/dist/exec.d.ts +0 -64
- package/dist/exec.js +3 -356
- package/dist/exhaustion.d.ts +22 -76
- package/dist/exhaustion.js +14 -140
- package/dist/export.js +8 -8
- package/dist/flow-engine.js +1 -1
- package/dist/flow-schedule.d.ts +154 -0
- package/dist/flow-schedule.js +321 -0
- package/dist/flow-share.js +1 -1
- package/dist/flow-triggers.d.ts +6 -1
- package/dist/flow-triggers.js +65 -10
- package/dist/flows-cli.js +8 -4
- package/dist/flows-ui.js +2 -0
- package/dist/flows.js +1 -1
- package/dist/guides.js +9 -29
- package/dist/invoke.d.ts +1 -24
- package/dist/invoke.js +17 -259
- package/dist/lead-status.js +2 -4
- package/dist/lead-voice.js +1 -1
- package/dist/mate-doors.d.ts +1 -2
- package/dist/mate-doors.js +12 -25
- package/dist/mate-tools.d.ts +2 -2
- package/dist/mate-tools.js +13 -24
- package/dist/modes.d.ts +19 -8
- package/dist/modes.js +24 -14
- package/dist/notification-rows.d.ts +30 -0
- package/dist/notification-rows.js +102 -0
- package/dist/operate-remote-arguments.js +2 -6
- package/dist/operate-remote.js +6 -7
- package/dist/operate.d.ts +0 -6
- package/dist/operate.js +226 -2248
- package/dist/phase-routing.d.ts +13 -23
- package/dist/phase-routing.js +13 -35
- package/dist/plan-auto.js +2 -2
- package/dist/plane-review.js +5 -11
- package/dist/planner-source.js +2 -1
- package/dist/policy.d.ts +1 -4
- package/dist/policy.js +0 -8
- package/dist/process-recovery-eligibility.js +2 -3
- package/dist/project-delete.js +24 -41
- package/dist/proposal.d.ts +4 -24
- package/dist/proposal.js +3 -88
- package/dist/provider-auth.d.ts +3 -3
- package/dist/provider-auth.js +4 -6
- package/dist/provider.d.ts +11 -25
- package/dist/provider.js +4 -36
- package/dist/push.js +1 -1
- package/dist/recipe-ui.js +6 -6
- package/dist/recipes.d.ts +3 -3
- package/dist/recipes.js +27 -23
- package/dist/result-shots.js +2 -2
- package/dist/retention.js +1 -1
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +1 -4
- package/dist/scope.d.ts +11 -65
- package/dist/scope.js +17 -82
- package/dist/serve.js +15 -24
- package/dist/server/chat.js +7 -30
- package/dist/server/flows.js +11 -10
- package/dist/server/guards.js +0 -1
- package/dist/server/pages-assets.js +1 -1
- package/dist/server/pages.js +0 -2
- package/dist/server/people-tokens.js +0 -4
- package/dist/server/route-table.d.ts +1 -1
- package/dist/server/route-table.js +2 -17
- package/dist/server/settings.js +6 -12
- package/dist/server/shared.d.ts +28 -212
- package/dist/server/shared.js +71 -780
- package/dist/server/task-views.js +9 -76
- package/dist/server/tasks.d.ts +1 -50
- package/dist/server/tasks.js +36 -946
- package/dist/setup-guide.d.ts +2 -2
- package/dist/slack-api.d.ts +5 -1
- package/dist/slack-api.js +2 -2
- package/dist/slack-chat.d.ts +9 -6
- package/dist/slack-chat.js +29 -53
- package/dist/slack-settings.js +6 -15
- package/dist/slack.js +6 -16
- package/dist/store.d.ts +200 -1702
- package/dist/store.js +811 -5439
- package/dist/surface.js +10 -24
- package/dist/task-composer.d.ts +1 -1
- package/dist/task-composer.js +1 -1
- package/dist/task-control.d.ts +3 -10
- package/dist/task-control.js +2 -7
- package/dist/teammate-desk.js +1 -1
- package/dist/teammate-question.js +12 -12
- package/dist/teammates-ui.js +1 -1
- package/dist/teams-chat.js +12 -9
- package/dist/teams-settings.js +2 -2
- package/dist/teams.js +4 -5
- package/dist/telegram-flow.js +5 -5
- package/dist/telegram-mate.d.ts +0 -3
- package/dist/telegram-mate.js +0 -1
- package/dist/telegram.js +4 -4
- package/dist/templates.d.ts +11 -7
- package/dist/templates.js +12 -9
- package/dist/toolroll-update.d.ts +20 -0
- package/dist/toolroll-update.js +104 -5
- package/dist/work-index.js +2 -6
- package/dist/workspace-revision.js +3 -1
- package/dist/workspace-ui.js +1 -1
- package/package.json +1 -1
- package/scripts/recovery-canary.mjs +3 -3
- package/dist/contest.d.ts +0 -318
- package/dist/contest.js +0 -753
- package/dist/held.d.ts +0 -150
- package/dist/held.js +0 -799
- package/dist/liveness.d.ts +0 -30
- package/dist/liveness.js +0 -42
- package/dist/pricing.d.ts +0 -45
- package/dist/pricing.js +0 -77
- package/dist/routine.d.ts +0 -228
- package/dist/routine.js +0 -764
- package/dist/slack-state.d.ts +0 -13
- package/dist/slack-state.js +0 -10
- package/dist/supervisor.mjs +0 -297
package/dist/setup-guide.d.ts
CHANGED
|
@@ -15,10 +15,10 @@ export declare function detectPreparation(repo: string): {
|
|
|
15
15
|
} | null;
|
|
16
16
|
/** Browser installation may write only this known file inside its project. */
|
|
17
17
|
export declare function previewProjectInstructions(repo: string): {
|
|
18
|
-
content?: never;
|
|
19
18
|
ok: false;
|
|
20
19
|
message: string;
|
|
21
20
|
plan?: never;
|
|
21
|
+
content?: never;
|
|
22
22
|
fingerprint?: never;
|
|
23
23
|
installed?: never;
|
|
24
24
|
} | {
|
|
@@ -30,10 +30,10 @@ export declare function previewProjectInstructions(repo: string): {
|
|
|
30
30
|
installed: boolean;
|
|
31
31
|
};
|
|
32
32
|
export declare function addProjectInstructions(repo: string, fingerprint: string): {
|
|
33
|
-
content?: never;
|
|
34
33
|
ok: false;
|
|
35
34
|
message: string;
|
|
36
35
|
plan?: never;
|
|
36
|
+
content?: never;
|
|
37
37
|
fingerprint?: never;
|
|
38
38
|
installed?: never;
|
|
39
39
|
} | import("./skills.js").InstallResult;
|
package/dist/slack-api.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ChatDeliveryError } from "./chat-delivery-state.js";
|
|
2
|
-
import { type
|
|
2
|
+
import { type ChatIdentity } from "./chat-delivery-state.js";
|
|
3
|
+
/** A Slack app installation: the chat identity, with the workspace (team) it belongs to. */
|
|
4
|
+
export type SlackIdentity = ChatIdentity & {
|
|
5
|
+
team: string;
|
|
6
|
+
};
|
|
3
7
|
export type SlackCredentials = SlackIdentity & {
|
|
4
8
|
appToken: string;
|
|
5
9
|
botToken: string;
|
package/dist/slack-api.js
CHANGED
|
@@ -2,7 +2,7 @@ import { ChatDeliveryError } from "./chat-delivery-state.js";
|
|
|
2
2
|
import { chmodSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
|
-
import {
|
|
5
|
+
import { chatHash } from "./chat-delivery-state.js";
|
|
6
6
|
export class SlackError extends ChatDeliveryError {
|
|
7
7
|
constructor(code, retryMs = 5_000, uncertain = false) {
|
|
8
8
|
super(code, retryMs, uncertain);
|
|
@@ -128,7 +128,7 @@ export async function checkSlackCredentials(appToken, botToken, fetcher = fetch)
|
|
|
128
128
|
app: String(detail.app_id),
|
|
129
129
|
bot: String(auth.user_id),
|
|
130
130
|
workspace: typeof auth.team === "string" ? auth.team : "Slack",
|
|
131
|
-
installation:
|
|
131
|
+
installation: chatHash(`${auth.team_id}:${detail.app_id}:${auth.user_id}`),
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
134
|
export function object(value) {
|
package/dist/slack-chat.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import {
|
|
3
|
-
import { uploadSlackBytes, type SlackApi } from "./slack-api.js";
|
|
1
|
+
import { ChatState, type ChatBinding, type ChatContent, type ChatEvent } from "./chat-delivery-state.js";
|
|
2
|
+
import { uploadSlackBytes, type SlackApi, type SlackIdentity } from "./slack-api.js";
|
|
4
3
|
import { type Store } from "./store.js";
|
|
5
4
|
import type { SubscriptionMateRunner } from "./subscription-chat.js";
|
|
6
5
|
export type SlackChatOptions = {
|
|
@@ -13,15 +12,19 @@ export type SlackChatOptions = {
|
|
|
13
12
|
current: () => boolean;
|
|
14
13
|
origin: () => string | null;
|
|
15
14
|
subscriptionRunner?: SubscriptionMateRunner;
|
|
16
|
-
held?: DoorOptions["held"];
|
|
17
15
|
canNotify?: () => boolean;
|
|
18
16
|
clock?: () => Date;
|
|
19
17
|
upload?: typeof uploadSlackBytes;
|
|
20
18
|
};
|
|
21
19
|
/** Synchronous receipt before ACK. Never stores response_url, raw tokens, or a pairing code. */
|
|
22
|
-
export declare function receiveSlack(state:
|
|
20
|
+
export declare function receiveSlack(state: ChatState, identity: SlackIdentity, type: string, raw: unknown, now: Date): boolean;
|
|
23
21
|
export declare const processSlackEvent: (options: SlackChatOptions) => Promise<boolean>;
|
|
24
|
-
export declare const applySlackAction: (options: SlackChatOptions, event:
|
|
22
|
+
export declare const applySlackAction: (options: SlackChatOptions, event: ChatEvent, binding: ChatBinding, repos: readonly string[]) => {
|
|
23
|
+
merge: import("./chat-decide.js").ChatMerge;
|
|
24
|
+
part: number;
|
|
25
|
+
shown: string;
|
|
26
|
+
content: ChatContent;
|
|
27
|
+
} | null;
|
|
25
28
|
export declare const planSlackNotifications: (options: SlackChatOptions) => Promise<void>;
|
|
26
29
|
export declare const planSlackRooms: (options: SlackChatOptions) => Promise<void>;
|
|
27
30
|
export declare function slackBlocks(text: string, buttons?: Record<string, unknown>[]): Record<string, unknown>[];
|
package/dist/slack-chat.js
CHANGED
|
@@ -9,9 +9,7 @@ import { processChatEvent, applyChatAction, planChatNotifications, channelAccess
|
|
|
9
9
|
import { proposalPreview, proposalLink, proposalOutcomeText, armedCardText, armedYesLabel, } from "./chat-channel.js";
|
|
10
10
|
import { MATE_MESSAGE_MAX_CHARS } from "./mate.js";
|
|
11
11
|
import { renderReply } from "./reply-shape.js";
|
|
12
|
-
import {} from "./
|
|
13
|
-
import { SlackState, slackHash, } from "./slack-state.js";
|
|
14
|
-
import { ChatDeliveryError, partContent } from "./chat-delivery-state.js";
|
|
12
|
+
import { ChatDeliveryError, ChatState, chatHash, partContent } from "./chat-delivery-state.js";
|
|
15
13
|
import { readChatPart } from "./contracts/chat-content.js";
|
|
16
14
|
import { readSlackBlockActions, readSlackButton, SLACK_LINK_ACTION } from "./contracts/slack-callback.js";
|
|
17
15
|
import { object, slackId, slackTs, slackMember, SlackError, uploadSlackBytes, } from "./slack-api.js";
|
|
@@ -65,12 +63,12 @@ export function receiveSlack(state, identity, type, raw, now) {
|
|
|
65
63
|
const match = /^pair ([a-f0-9]{32})$/.exec(event.text.trim());
|
|
66
64
|
kind = match ? "pair" : "message";
|
|
67
65
|
payload = match
|
|
68
|
-
? { hash:
|
|
66
|
+
? { hash: chatHash(match[1]) }
|
|
69
67
|
: {
|
|
70
68
|
text: event.text.slice(0, MATE_MESSAGE_MAX_CHARS + 1),
|
|
71
69
|
originalLength: event.text.length,
|
|
72
70
|
};
|
|
73
|
-
id =
|
|
71
|
+
id = chatHash(`${identity.installation}:event:${body.event_id}`);
|
|
74
72
|
}
|
|
75
73
|
else if (type === "interactive" && body.type === "block_actions") {
|
|
76
74
|
const interaction = readSlackBlockActions(body);
|
|
@@ -89,7 +87,7 @@ export function receiveSlack(state, identity, type, raw, now) {
|
|
|
89
87
|
const button = readSlackButton(interaction.value);
|
|
90
88
|
payload = button.ok ? { token: button.value.value } : { problem: button.issues.map(issue => issue.line).join("; ") };
|
|
91
89
|
const tapped = actions[0];
|
|
92
|
-
id =
|
|
90
|
+
id = chatHash(`${identity.installation}:action:${member}:${ts}:${tapped?.value ?? ""}:${tapped?.action_ts ?? ""}`);
|
|
93
91
|
}
|
|
94
92
|
else
|
|
95
93
|
return false;
|
|
@@ -132,7 +130,7 @@ export function receiveSlack(state, identity, type, raw, now) {
|
|
|
132
130
|
}
|
|
133
131
|
const delivery = (options) => ({
|
|
134
132
|
...options,
|
|
135
|
-
state: new
|
|
133
|
+
state: new ChatState(options.store, "slack"),
|
|
136
134
|
label: "Slack",
|
|
137
135
|
member: (member, channel) => slackMember(options.api, options.identity, member, channel),
|
|
138
136
|
// A 👍 on the owner's message where the app has reactions:write; Slack shows bots no typing indicator.
|
|
@@ -202,26 +200,20 @@ function linkButton(origin, link) {
|
|
|
202
200
|
}
|
|
203
201
|
}
|
|
204
202
|
export async function deliverSlackPart(options) {
|
|
205
|
-
const state = new
|
|
203
|
+
const state = new ChatState(options.store, "slack"), { store, identity } = options, now = nowOf(options);
|
|
206
204
|
if (!state.owns(identity.installation, options.owner, now) ||
|
|
207
205
|
!options.current())
|
|
208
206
|
return false;
|
|
209
|
-
const row = state.
|
|
210
|
-
.prepare("SELECT p.* FROM slack_part p JOIN slack_event e ON e.id=p.event WHERE e.installation=? AND p.state='pending' AND (e.kind!='notice' OR ?=1) AND (p.next_at IS NULL OR p.next_at<=?) ORDER BY p.id LIMIT 1")
|
|
211
|
-
.get(identity.installation, options.canNotify?.() === false ? 0 : 1, now.toISOString());
|
|
207
|
+
const row = state.nextPart(identity.installation, options.canNotify?.() !== false, now);
|
|
212
208
|
if (!row)
|
|
213
209
|
return false;
|
|
214
210
|
const event = state.event(row.event), binding = event.binding === null ? null : state.bindingById(event.binding);
|
|
215
211
|
if (!binding) {
|
|
216
|
-
state.
|
|
217
|
-
.prepare("UPDATE slack_part SET state='dropped',problem='Chat access changed' WHERE id=?")
|
|
218
|
-
.run(row.id);
|
|
212
|
+
state.dropPart(row.id, "Chat access changed");
|
|
219
213
|
return true;
|
|
220
214
|
}
|
|
221
215
|
if (new Date(row.created).getTime() + 86_400_000 < now.getTime()) {
|
|
222
|
-
state.
|
|
223
|
-
.prepare("UPDATE slack_part SET state='dropped',problem='Delivery expired; open the saved chat to recover it' WHERE id=?")
|
|
224
|
-
.run(row.id);
|
|
216
|
+
state.dropPart(row.id, "Delivery expired; open the saved chat to recover it");
|
|
225
217
|
return true;
|
|
226
218
|
}
|
|
227
219
|
try {
|
|
@@ -236,15 +228,15 @@ export async function deliverSlackPart(options) {
|
|
|
236
228
|
!repos.includes(store.lookupRef(content.task)?.repo ?? ""))
|
|
237
229
|
throw new SlackError("Connected projects changed");
|
|
238
230
|
// A screenshot sent with a result goes in that result's thread once its message is placed, and waits until then.
|
|
239
|
-
const result = content.shot?.follows == null ?
|
|
240
|
-
if (result !==
|
|
241
|
-
state.
|
|
231
|
+
const result = content.shot?.follows == null ? null : state.part(content.shot.follows);
|
|
232
|
+
if (result !== null && !slackTs(result.message) && result.state === "pending") {
|
|
233
|
+
state.holdPart(row.id, shotWaitsUntil(result.next_at, now));
|
|
242
234
|
return true;
|
|
243
235
|
}
|
|
244
|
-
const follows = result?.
|
|
236
|
+
const follows = result?.message;
|
|
245
237
|
const thread = slackTs(follows) ? String(follows) : event.thread;
|
|
246
238
|
if (content.image && content.shot && resultShotsPruned(options.evidenceRoot, content.image.run)) {
|
|
247
|
-
state.
|
|
239
|
+
state.dropPart(row.id, "Removed by retention");
|
|
248
240
|
return true;
|
|
249
241
|
}
|
|
250
242
|
if (content.image) {
|
|
@@ -263,17 +255,13 @@ export async function deliverSlackPart(options) {
|
|
|
263
255
|
typeof allocated.upload_url !== "string")
|
|
264
256
|
throw new SlackError("Slack did not identify the upload");
|
|
265
257
|
file = allocated.file_id;
|
|
266
|
-
state.
|
|
267
|
-
.prepare("UPDATE slack_part SET file=? WHERE id=?")
|
|
268
|
-
.run(file, row.id);
|
|
258
|
+
state.setPartFile(row.id, file);
|
|
269
259
|
await access(options, binding, session?.ceilingDigest);
|
|
270
260
|
const fresh = verifyResultImage(store, options.evidenceRoot, repos, content.image);
|
|
271
261
|
if (!fresh.ok)
|
|
272
262
|
throw new SlackError("Screenshot evidence changed before upload");
|
|
273
263
|
await (options.upload ?? uploadSlackBytes)(allocated.upload_url, fresh.bytes);
|
|
274
|
-
state.
|
|
275
|
-
.prepare("UPDATE slack_part SET uploaded=1 WHERE id=?")
|
|
276
|
-
.run(row.id);
|
|
264
|
+
state.setPartFile(row.id, null, true);
|
|
277
265
|
}
|
|
278
266
|
await access(options, binding, session?.ceilingDigest);
|
|
279
267
|
if (!verifyResultImage(store, options.evidenceRoot, repos, content.image)
|
|
@@ -289,9 +277,7 @@ export async function deliverSlackPart(options) {
|
|
|
289
277
|
.map(object)
|
|
290
278
|
.find((receipt) => (receipt.thread_ts ?? "") === thread && slackTs(receipt.ts));
|
|
291
279
|
if (found) {
|
|
292
|
-
state.
|
|
293
|
-
.prepare("UPDATE slack_part SET state='sent',message=?,problem=NULL WHERE id=?")
|
|
294
|
-
.run(String(found.ts), row.id);
|
|
280
|
+
state.partSent(row.id, String(found.ts), nowOf(options), false);
|
|
295
281
|
return true;
|
|
296
282
|
}
|
|
297
283
|
}
|
|
@@ -309,9 +295,7 @@ export async function deliverSlackPart(options) {
|
|
|
309
295
|
if (!Array.isArray(completed.files) ||
|
|
310
296
|
!completed.files.some((value) => object(value).id === file))
|
|
311
297
|
throw new SlackError("Slack did not confirm the file identity", 15_000, true);
|
|
312
|
-
state.
|
|
313
|
-
.prepare("UPDATE slack_part SET state='sent',problem=NULL WHERE id=?")
|
|
314
|
-
.run(row.id);
|
|
298
|
+
state.partSent(row.id, null, nowOf(options), false);
|
|
315
299
|
return true;
|
|
316
300
|
}
|
|
317
301
|
}
|
|
@@ -328,9 +312,7 @@ export async function deliverSlackPart(options) {
|
|
|
328
312
|
? armedCardText(proposal, preview.text)
|
|
329
313
|
: preview.text.replace("\n\nConfirm or Dismiss below. Nothing changes until you confirm.", "");
|
|
330
314
|
if (preview.buttons && preview.text.length <= 10_000) {
|
|
331
|
-
const tokens = state.
|
|
332
|
-
.prepare("SELECT token,phase FROM slack_action WHERE part=? AND consumed IS NULL AND expires>? ORDER BY rowid")
|
|
333
|
-
.all(row.id, now.toISOString());
|
|
315
|
+
const tokens = state.partTokens(row.id, now);
|
|
334
316
|
buttons = tokens.map((action) => ({
|
|
335
317
|
type: "button",
|
|
336
318
|
text: {
|
|
@@ -399,6 +381,10 @@ export async function deliverSlackPart(options) {
|
|
|
399
381
|
value: one.token,
|
|
400
382
|
}))
|
|
401
383
|
: []),
|
|
384
|
+
// A result's, plan's, failure's or pull request's own buttons (chat-decide.ts): its acts, then its links.
|
|
385
|
+
...(content.decide?.rows.flat() ?? []).flatMap((one, index) => "token" in one
|
|
386
|
+
? [{ type: "button", text: { type: "plain_text", text: one.label }, action_id: `toolroll_decide_${index}`, value: one.token, ...(index === 0 ? { style: "primary" } : {}) }]
|
|
387
|
+
: linkButton(options.origin(), one.link).map(button => ({ ...button, action_id: `toolroll_link_${index + 10}` }))),
|
|
402
388
|
...linkButton(options.origin(), content.link),
|
|
403
389
|
// [Look first] beside [Merge] or [Accept and finish]: each button's own action id.
|
|
404
390
|
...(content.also ?? []).flatMap((one, index) => linkButton(options.origin(), one).map(button => ({ ...button, action_id: `toolroll_link_${index + 2}` }))),
|
|
@@ -428,17 +414,13 @@ export async function deliverSlackPart(options) {
|
|
|
428
414
|
});
|
|
429
415
|
if (!slackTs(answer.ts) || (target && answer.ts !== target))
|
|
430
416
|
throw new SlackError("Slack did not confirm the message identity", 15_000, true);
|
|
431
|
-
state.
|
|
432
|
-
|
|
433
|
-
.run(answer.ts, row.id);
|
|
434
|
-
state.db
|
|
435
|
-
.prepare("UPDATE slack_runtime SET problem=NULL WHERE installation=? AND owner=?")
|
|
436
|
-
.run(identity.installation, options.owner);
|
|
417
|
+
state.partSent(row.id, String(answer.ts), nowOf(options));
|
|
418
|
+
state.setProblem(identity.installation, options.owner, null);
|
|
437
419
|
return true;
|
|
438
420
|
}
|
|
439
421
|
catch (error) {
|
|
440
422
|
if (error instanceof ChatDeliveryError && error.permanent) {
|
|
441
|
-
state.
|
|
423
|
+
state.dropPart(row.id, error.message);
|
|
442
424
|
return true;
|
|
443
425
|
}
|
|
444
426
|
// No permission to upload files here: one plain line instead of the result's screenshots.
|
|
@@ -450,18 +432,12 @@ export async function deliverSlackPart(options) {
|
|
|
450
432
|
const problem = error instanceof SlackError
|
|
451
433
|
? error
|
|
452
434
|
: new SlackError("Slack delivery is waiting to retry", 15_000, true);
|
|
453
|
-
state.
|
|
454
|
-
.prepare("UPDATE slack_runtime SET problem=? WHERE installation=? AND owner=?")
|
|
455
|
-
.run(problem.message, identity.installation, options.owner);
|
|
435
|
+
state.setProblem(identity.installation, options.owner, problem.message);
|
|
456
436
|
const until = new Date(nowOf(options).getTime() +
|
|
457
437
|
Math.max(problem.retryMs, [5000, 15000, 60000, 300000][Math.min(row.attempts, 3)])).toISOString();
|
|
458
|
-
state.
|
|
459
|
-
.prepare("UPDATE slack_part SET attempts=attempts+1,uncertain=uncertain+?,next_at=?,problem=? WHERE id=?")
|
|
460
|
-
.run(problem.uncertain ? 1 : 0, until, problem.message, row.id);
|
|
438
|
+
state.partFailed(row.id, { problem: problem.message, until, uncertain: problem.uncertain });
|
|
461
439
|
if (problem.code === "ratelimited")
|
|
462
|
-
state.
|
|
463
|
-
.prepare("UPDATE slack_runtime SET retry_at=? WHERE installation=?")
|
|
464
|
-
.run(until, identity.installation);
|
|
440
|
+
state.deferUntil(identity.installation, until);
|
|
465
441
|
return true;
|
|
466
442
|
}
|
|
467
443
|
}
|
package/dist/slack-settings.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadPrimary } from "./webhooks.js";
|
|
2
2
|
import { loadSlackCredentials } from "./slack-api.js";
|
|
3
|
-
import {
|
|
3
|
+
import { ChatState } from "./chat-delivery-state.js";
|
|
4
4
|
const escape = (text) => text
|
|
5
5
|
.replaceAll("&", "&")
|
|
6
6
|
.replaceAll("<", "<")
|
|
@@ -8,15 +8,11 @@ const escape = (text) => text
|
|
|
8
8
|
.replaceAll('"', """)
|
|
9
9
|
.replaceAll("'", "'");
|
|
10
10
|
export function slackSettingsHtml(store, dir, csrf, options = {}) {
|
|
11
|
-
const credentials = loadSlackCredentials(dir), state = new
|
|
12
|
-
const runtime = credentials
|
|
13
|
-
? state.db
|
|
14
|
-
.prepare("SELECT connected,problem,lease_until FROM slack_runtime WHERE installation=?")
|
|
15
|
-
.get(credentials.installation)
|
|
16
|
-
: null;
|
|
11
|
+
const credentials = loadSlackCredentials(dir), state = new ChatState(store, "slack"), bindings = credentials ? state.bindings(credentials.installation).filter(one => state.live(one)) : [], binding = options.who === undefined ? null : bindings.find(one => one.approver === options.who) ?? null, others = bindings.length - (binding === null ? 0 : 1);
|
|
12
|
+
const runtime = credentials ? state.runtime(credentials.installation) : null;
|
|
17
13
|
const now = options.now ?? new Date(), live = runtime &&
|
|
18
|
-
|
|
19
|
-
runtime.
|
|
14
|
+
runtime.leaseUntil !== null &&
|
|
15
|
+
runtime.leaseUntil > now.toISOString() &&
|
|
20
16
|
runtime.connected;
|
|
21
17
|
const hidden = `<input type="hidden" name="csrf" value="${escape(csrf)}">`;
|
|
22
18
|
const password = '<label>Your Toolroll password<input style="min-height:44px" type="password" name="password" autocomplete="current-password" required></label>';
|
|
@@ -44,12 +40,7 @@ export function slackSettingsHtml(store, dir, csrf, options = {}) {
|
|
|
44
40
|
'<a class="button-link" style="min-height:44px;white-space:nowrap" href="/settings/slack">Check connection</a>';
|
|
45
41
|
}
|
|
46
42
|
else if (binding && state.live(binding)) {
|
|
47
|
-
const pending =
|
|
48
|
-
.prepare("SELECT count(*) n FROM slack_part p JOIN slack_event e ON e.id=p.event WHERE e.binding=? AND p.state='pending'")
|
|
49
|
-
.get(binding.id)?.n ?? 0);
|
|
50
|
-
const failed = Number(state.db
|
|
51
|
-
.prepare("SELECT count(*) n FROM slack_part p JOIN slack_event e ON e.id=p.event WHERE e.binding=? AND p.state='dropped'")
|
|
52
|
-
.get(binding.id)?.n ?? 0);
|
|
43
|
+
const { pending, dropped: failed } = state.partCounts(binding.id);
|
|
53
44
|
content +=
|
|
54
45
|
`<p>Your Slack account is paired.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} paired too.` : ""}${pending ? ` ${pending} replies waiting to send.` : ""}${failed ? ` ${failed} replies could not be delivered. Open the saved chat to recover them.` : ""}</p>` +
|
|
55
46
|
"<p>Try “What needs my attention?” or “Show the evidence for the latest result.”</p>" +
|
package/dist/slack.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomBytes } from "node:crypto";
|
|
|
3
3
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
4
4
|
import { SocketModeClient, LogLevel } from "@slack/socket-mode";
|
|
5
5
|
import { loadSlackCredentials, slackApi } from "./slack-api.js";
|
|
6
|
-
import {
|
|
6
|
+
import { ChatState } from "./chat-delivery-state.js";
|
|
7
7
|
import { receiveSlack, processSlackEvent, deliverSlackPart, planSlackNotifications, planSlackRooms, } from "./slack-chat.js";
|
|
8
8
|
/** The bot and socket tokens must belong to the same app, including after reconnect. */
|
|
9
9
|
export class ToolrollSlackSocket extends SocketModeClient {
|
|
@@ -29,7 +29,7 @@ export class ToolrollSlackSocket extends SocketModeClient {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
export async function followSlack(options) {
|
|
32
|
-
const owner = randomBytes(16).toString("hex"), state = new
|
|
32
|
+
const owner = randomBytes(16).toString("hex"), state = new ChatState(options.store, "slack");
|
|
33
33
|
const pause = async (ms) => {
|
|
34
34
|
try {
|
|
35
35
|
await sleep(ms, undefined, { signal: options.signal });
|
|
@@ -55,9 +55,7 @@ export async function followSlack(options) {
|
|
|
55
55
|
current.appToken === credentials.appToken &&
|
|
56
56
|
state.owns(credentials.installation, owner));
|
|
57
57
|
};
|
|
58
|
-
const problem = (text) => state.
|
|
59
|
-
.prepare("UPDATE slack_runtime SET problem=? WHERE installation=? AND owner=?")
|
|
60
|
-
.run(text, credentials.installation, owner);
|
|
58
|
+
const problem = (text) => state.setProblem(credentials.installation, owner, text);
|
|
61
59
|
// SDK log output can include envelopes/tokens. Only fixed status phrases are saved.
|
|
62
60
|
const client = new ToolrollSlackSocket({
|
|
63
61
|
appToken: credentials.appToken,
|
|
@@ -93,9 +91,7 @@ export async function followSlack(options) {
|
|
|
93
91
|
}, 10_000);
|
|
94
92
|
client.on("connected", () => {
|
|
95
93
|
connected = true;
|
|
96
|
-
state.
|
|
97
|
-
.prepare("UPDATE slack_runtime SET connected=?,problem=NULL WHERE installation=? AND owner=?")
|
|
98
|
-
.run(new Date().toISOString(), credentials.installation, owner);
|
|
94
|
+
state.setConnected(credentials.installation, owner, new Date());
|
|
99
95
|
});
|
|
100
96
|
client.on("disconnected", () => {
|
|
101
97
|
connected = false;
|
|
@@ -131,11 +127,7 @@ export async function followSlack(options) {
|
|
|
131
127
|
try {
|
|
132
128
|
await client.start();
|
|
133
129
|
while (same()) {
|
|
134
|
-
|
|
135
|
-
.prepare("SELECT retry_at FROM slack_runtime WHERE installation=?")
|
|
136
|
-
.get(credentials.installation)?.retry_at;
|
|
137
|
-
if (!connected ||
|
|
138
|
-
(typeof retry === "string" && retry > new Date().toISOString())) {
|
|
130
|
+
if (!connected || state.retryAt(credentials.installation) > new Date().toISOString()) {
|
|
139
131
|
await pause(1000);
|
|
140
132
|
continue;
|
|
141
133
|
}
|
|
@@ -158,9 +150,7 @@ export async function followSlack(options) {
|
|
|
158
150
|
alive = false;
|
|
159
151
|
options.signal.removeEventListener("abort", stop);
|
|
160
152
|
await client.disconnect().catch(() => { });
|
|
161
|
-
state.
|
|
162
|
-
.prepare("UPDATE slack_runtime SET owner=NULL,lease_until=NULL,connected=NULL WHERE installation=? AND owner=?")
|
|
163
|
-
.run(credentials.installation, owner);
|
|
153
|
+
state.stop(credentials.installation, owner);
|
|
164
154
|
}
|
|
165
155
|
await pause(5000);
|
|
166
156
|
}
|