@pinet/slack-bridge 0.2.2 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +316 -451
- package/dist/activity-log.d.ts +10 -3
- package/dist/agent-event-runtime.d.ts +0 -2
- package/dist/agent-event-runtime.js +0 -1
- package/dist/agent-prompt-guidance.d.ts +1 -6
- package/dist/agent-prompt-guidance.js +1 -6
- package/dist/broker/adapters/slack.d.ts +4 -1
- package/dist/broker/adapters/slack.js +16 -9
- package/dist/broker/client.d.ts +21 -4
- package/dist/broker/client.js +65 -25
- package/dist/broker/hibernation-activation-authority.d.ts +22 -0
- package/dist/broker/hibernation-activation-authority.js +51 -0
- package/dist/broker/hibernation-activation.d.ts +75 -0
- package/dist/broker/hibernation-activation.js +95 -0
- package/dist/broker/hibernation-command-router.d.ts +54 -0
- package/dist/broker/hibernation-command-router.js +124 -0
- package/dist/broker/hibernation-runtime-adapters.d.ts +79 -0
- package/dist/broker/hibernation-runtime-adapters.js +473 -0
- package/dist/broker/hibernation-runtime-helpers.d.ts +146 -0
- package/dist/broker/hibernation-runtime-helpers.js +243 -0
- package/dist/broker/index.d.ts +16 -1
- package/dist/broker/index.js +68 -10
- package/dist/broker/lock-conflict.d.ts +109 -0
- package/dist/broker/lock-conflict.js +409 -0
- package/dist/broker/socket-server.d.ts +26 -0
- package/dist/broker/socket-server.js +287 -4
- package/dist/broker-delivery.d.ts +1 -0
- package/dist/broker-delivery.js +10 -1
- package/dist/broker-runtime.d.ts +14 -2
- package/dist/broker-runtime.js +114 -3
- package/dist/canvases.d.ts +12 -2
- package/dist/canvases.js +1 -3
- package/dist/compaction-gate.d.ts +7 -0
- package/dist/compaction-gate.js +35 -0
- package/dist/deploy-manifest.d.ts +8 -2
- package/dist/deploy-manifest.js +9 -7
- package/dist/follower-runtime.d.ts +5 -2
- package/dist/follower-runtime.js +39 -5
- package/dist/git-metadata.d.ts +5 -9
- package/dist/git-metadata.js +23 -42
- package/dist/guardrails.js +5 -0
- package/dist/helpers.d.ts +77 -27
- package/dist/helpers.js +150 -39
- package/dist/hibernation-config.d.ts +13 -0
- package/dist/hibernation-config.js +14 -0
- package/dist/home-tab.d.ts +13 -2
- package/dist/imessage-tools.d.ts +4 -1
- package/dist/inbox-drain-runtime.d.ts +1 -0
- package/dist/inbox-drain-runtime.js +5 -1
- package/dist/index.js +341 -108
- package/dist/pinet-commands.d.ts +14 -1
- package/dist/pinet-commands.js +93 -22
- package/dist/pinet-mesh-ops.d.ts +6 -4
- package/dist/pinet-mesh-ops.js +9 -1
- package/dist/pinet-runtime-composition.d.ts +2 -1
- package/dist/pinet-runtime-composition.js +14 -3
- package/dist/pinet-session-formatting.d.ts +26 -2
- package/dist/pinet-session-formatting.js +2 -0
- package/dist/pinet-tools.d.ts +14 -0
- package/dist/pinet-tools.js +355 -50
- package/dist/prompts/broker/default.md +3 -3
- package/dist/prompts/broker/tmux.md +3 -3
- package/dist/runtime-agent-context.d.ts +3 -3
- package/dist/runtime-agent-context.js +12 -6
- package/dist/runtime-mode.js +4 -0
- package/dist/session-ui-runtime.js +3 -1
- package/dist/single-player-runtime.d.ts +1 -1
- package/dist/single-player-runtime.js +21 -5
- package/dist/slack-access.d.ts +10 -2
- package/dist/slack-access.js +64 -22
- package/dist/slack-block-kit.d.ts +44 -2
- package/dist/slack-block-kit.js +5 -5
- package/dist/slack-file-access.d.ts +12 -1
- package/dist/slack-file-access.js +4 -4
- package/dist/slack-message-context.d.ts +9 -2
- package/dist/slack-modals.d.ts +4 -1
- package/dist/slack-modals.js +5 -5
- package/dist/slack-pinet-runtime-adapter.d.ts +1 -0
- package/dist/slack-pinet-runtime-adapter.js +10 -0
- package/dist/slack-presence.d.ts +5 -4
- package/dist/slack-request-runtime.d.ts +3 -2
- package/dist/slack-scope-diagnostics.d.ts +27 -2
- package/dist/slack-socket-dedup.d.ts +8 -5
- package/dist/slack-tools.d.ts +10 -0
- package/dist/slack-tools.js +51 -64
- package/dist/slack-upload.d.ts +17 -1
- package/dist/subtree-broker-runtime.d.ts +83 -2
- package/dist/subtree-broker-runtime.js +774 -109
- package/dist/tool-output-limits.d.ts +11 -0
- package/dist/tool-output-limits.js +20 -0
- package/dist/tool-registration-runtime.d.ts +3 -0
- package/dist/tool-registration-runtime.js +27 -0
- package/package.json +8 -8
|
@@ -79,6 +79,16 @@ export function createSlackPinetRuntimeAdapterFactory(deps) {
|
|
|
79
79
|
onAppHomeOpened: async ({ userId }) => {
|
|
80
80
|
await deps.onAppHomeOpened(userId, ctx);
|
|
81
81
|
},
|
|
82
|
+
onSocketOpen: () => deps.setExtStatus(ctx, "ok"),
|
|
83
|
+
onSocketReconnectScheduled: () => deps.setExtStatus(ctx, "reconnecting"),
|
|
84
|
+
onSocketError: (message, source) => {
|
|
85
|
+
if (source === "connection") {
|
|
86
|
+
deps.setExtStatus(ctx, "error");
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
ctx.ui.notify(`Slack event failed: ${message}`, "error");
|
|
90
|
+
}
|
|
91
|
+
},
|
|
82
92
|
onSlashCommand: deps.onSlashCommand
|
|
83
93
|
? (event) => deps.onSlashCommand?.(event, ctx) ?? null
|
|
84
94
|
: undefined,
|
package/dist/slack-presence.d.ts
CHANGED
|
@@ -21,11 +21,12 @@ export interface SlackPresenceSnapshot {
|
|
|
21
21
|
lastActivity?: number;
|
|
22
22
|
online?: boolean;
|
|
23
23
|
}
|
|
24
|
+
export type SlackPresenceTimestampValue = string | number | null | undefined;
|
|
24
25
|
export interface SlackDndInfoLike {
|
|
25
|
-
dnd_enabled?:
|
|
26
|
-
next_dnd_end_ts?:
|
|
27
|
-
snooze_enabled?:
|
|
28
|
-
snooze_endtime?:
|
|
26
|
+
dnd_enabled?: boolean | null;
|
|
27
|
+
next_dnd_end_ts?: SlackPresenceTimestampValue;
|
|
28
|
+
snooze_enabled?: boolean | null;
|
|
29
|
+
snooze_endtime?: SlackPresenceTimestampValue;
|
|
29
30
|
}
|
|
30
31
|
export declare function stripSlackUserReference(value: string): string;
|
|
31
32
|
export declare function isSlackUserId(value: string): boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type SlackResult } from "./slack-api.js";
|
|
2
|
-
export type
|
|
1
|
+
import { callSlackApi, type SlackResult } from "./slack-api.js";
|
|
2
|
+
export type SlackRequestBody = NonNullable<Parameters<typeof callSlackApi>[2]>;
|
|
3
|
+
export type SlackRequestCall = (method: string, token: string, body?: SlackRequestBody) => Promise<SlackResult>;
|
|
3
4
|
export interface SlackRequestRuntime {
|
|
4
5
|
slack: SlackRequestCall;
|
|
5
6
|
reset: () => void;
|
|
@@ -1,7 +1,31 @@
|
|
|
1
|
+
export type SlackScopeDiagnosticsSurface = "files" | "bookmarks" | "pins";
|
|
2
|
+
export type SlackScopeProbeBody = Record<string, unknown>;
|
|
3
|
+
interface SlackFileInfoScopeProbeBody extends SlackScopeProbeBody {
|
|
4
|
+
file: string;
|
|
5
|
+
}
|
|
6
|
+
interface SlackCompleteUploadScopeProbeFile {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
}
|
|
10
|
+
interface SlackCompleteUploadScopeProbeBody extends SlackScopeProbeBody {
|
|
11
|
+
files: SlackCompleteUploadScopeProbeFile[];
|
|
12
|
+
channel_id: string;
|
|
13
|
+
}
|
|
14
|
+
interface SlackChannelScopeProbeBody extends SlackScopeProbeBody {
|
|
15
|
+
channel_id: string;
|
|
16
|
+
}
|
|
17
|
+
interface SlackBookmarkRemoveScopeProbeBody extends SlackChannelScopeProbeBody {
|
|
18
|
+
bookmark_id: string;
|
|
19
|
+
}
|
|
20
|
+
interface SlackPinnedItemScopeProbeBody extends SlackScopeProbeBody {
|
|
21
|
+
channel: string;
|
|
22
|
+
timestamp?: string;
|
|
23
|
+
}
|
|
24
|
+
export type SlackScopeProbeRequestBody = SlackFileInfoScopeProbeBody | SlackCompleteUploadScopeProbeBody | SlackChannelScopeProbeBody | SlackBookmarkRemoveScopeProbeBody | SlackPinnedItemScopeProbeBody;
|
|
1
25
|
export type SlackScopeDiagnosticsStatus = "not_checked" | "pending" | "healthy" | "drift" | "unavailable";
|
|
2
26
|
export interface SlackScopeProbeResult {
|
|
3
27
|
key: string;
|
|
4
|
-
surface:
|
|
28
|
+
surface: SlackScopeDiagnosticsSurface;
|
|
5
29
|
method: string;
|
|
6
30
|
expectedScopes: string[];
|
|
7
31
|
status: "ok" | "missing" | "unavailable";
|
|
@@ -14,7 +38,7 @@ export interface SlackScopeDiagnostics {
|
|
|
14
38
|
status: SlackScopeDiagnosticsStatus;
|
|
15
39
|
checkedAt: string | null;
|
|
16
40
|
summary: string;
|
|
17
|
-
surfaces:
|
|
41
|
+
surfaces: SlackScopeDiagnosticsSurface[];
|
|
18
42
|
missingScopes: string[];
|
|
19
43
|
results: SlackScopeProbeResult[];
|
|
20
44
|
error?: string;
|
|
@@ -29,3 +53,4 @@ export declare function createPendingSlackScopeDiagnostics(): SlackScopeDiagnost
|
|
|
29
53
|
export declare function detectSlackScopeDiagnostics(options: DetectSlackScopeDiagnosticsOptions): Promise<SlackScopeDiagnostics>;
|
|
30
54
|
export declare function formatSlackScopeDiagnosticsStatus(diagnostics: SlackScopeDiagnostics): string;
|
|
31
55
|
export declare function buildSlackScopeDriftWarning(diagnostics: SlackScopeDiagnostics): string | null;
|
|
56
|
+
export {};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export declare const SLACK_SOCKET_DELIVERY_DEDUP_TTL_MS: number;
|
|
2
2
|
export declare const SLACK_SOCKET_DELIVERY_DEDUP_MAX_SIZE = 10000;
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export declare function
|
|
7
|
-
export declare function
|
|
3
|
+
export type SlackDedupEventPayload = Record<string, unknown>;
|
|
4
|
+
export type SlackInteractiveDedupPayload = Record<string, unknown>;
|
|
5
|
+
export type SlackSocketDedupFrame = Record<string, unknown>;
|
|
6
|
+
export declare function extractSlackEventDedupKey(evt: SlackDedupEventPayload): string | null;
|
|
7
|
+
export declare function extractSlackBlockActionDedupKey(payload: SlackInteractiveDedupPayload): string | null;
|
|
8
|
+
export declare function extractSlackViewSubmissionDedupKey(payload: SlackInteractiveDedupPayload): string | null;
|
|
9
|
+
export declare function extractSlackInteractiveDedupKey(payload: SlackInteractiveDedupPayload): string | null;
|
|
10
|
+
export declare function extractSlackSocketDedupKey(frame: SlackSocketDedupFrame): string | null;
|
package/dist/slack-tools.d.ts
CHANGED
|
@@ -26,10 +26,19 @@ export interface SlackPinetDeliveryResult {
|
|
|
26
26
|
source: string;
|
|
27
27
|
}
|
|
28
28
|
export interface SlackPinetDeliveryPort {
|
|
29
|
+
/**
|
|
30
|
+
* True when Pinet is configured/enabled for this session, regardless of
|
|
31
|
+
* live broker connectivity. When true, threaded Slack replies MUST route
|
|
32
|
+
* through the broker; direct Slack fallback is refused even if the broker
|
|
33
|
+
* is momentarily unavailable, to preserve broker-enforced thread
|
|
34
|
+
* ownership (see gugu91/pinet#855).
|
|
35
|
+
*/
|
|
36
|
+
isEnabled: () => boolean;
|
|
29
37
|
isAvailable: () => boolean;
|
|
30
38
|
sendSlackMessage: (input: SlackPinetDeliveryInput) => Promise<SlackPinetDeliveryResult>;
|
|
31
39
|
}
|
|
32
40
|
export interface RegisterSlackToolsDeps {
|
|
41
|
+
additionalSendPromptGuidelines?: string[];
|
|
33
42
|
getBotToken: () => string;
|
|
34
43
|
getDefaultChannel: () => string | undefined;
|
|
35
44
|
getSecurityPrompt: () => string;
|
|
@@ -40,6 +49,7 @@ export interface RegisterSlackToolsDeps {
|
|
|
40
49
|
getAgentOwnerToken: () => string;
|
|
41
50
|
getLastDmChannel: () => string | null;
|
|
42
51
|
updateBadge: () => void;
|
|
52
|
+
markSubtreeInboxIdsDelivered: (inboxIds: number[]) => void;
|
|
43
53
|
resolveUser: (userId: string) => Promise<string>;
|
|
44
54
|
threadContext: SlackToolsThreadContextPort;
|
|
45
55
|
resolveChannel: (nameOrId: string) => Promise<string>;
|
package/dist/slack-tools.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { getSubtreeInboxIds } from "./broker-delivery.js";
|
|
3
4
|
import { buildSlackCanvasCreateRequest, buildSlackCanvasEditRequest, buildSlackCanvasSectionsLookupRequest, extractSlackCanvasCommentsPage, extractSlackChannelCanvasId, normalizeSlackCanvasCommentsLimit, normalizeSlackCanvasCreateKind, normalizeSlackCanvasUpdateMode, pickSlackCanvasSectionId, } from "./canvases.js";
|
|
4
5
|
import { normalizeSlackBlocksInput, summarizeSlackBlocksForPolicy } from "./slack-block-kit.js";
|
|
5
6
|
import { encodeSlackModalPrivateMetadata, normalizeSlackModalViewInput } from "./slack-modals.js";
|
|
@@ -146,7 +147,7 @@ const SLACK_DISPATCHER_EXAMPLES = {
|
|
|
146
147
|
},
|
|
147
148
|
],
|
|
148
149
|
};
|
|
149
|
-
function
|
|
150
|
+
function isSlackToolObject(value) {
|
|
150
151
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
151
152
|
}
|
|
152
153
|
function normalizeSlackDispatcherActionName(value) {
|
|
@@ -180,25 +181,6 @@ function isAbortError(error) {
|
|
|
180
181
|
function getErrorMessage(error) {
|
|
181
182
|
return error instanceof Error ? error.message : String(error);
|
|
182
183
|
}
|
|
183
|
-
function isPinetDeliveryFallbackError(error) {
|
|
184
|
-
const lower = getErrorMessage(error).toLowerCase();
|
|
185
|
-
if (lower.includes("already owned"))
|
|
186
|
-
return false;
|
|
187
|
-
return (lower.includes("not running") ||
|
|
188
|
-
lower.includes("unexpected state") ||
|
|
189
|
-
lower.includes("unavailable") ||
|
|
190
|
-
lower.includes("not connected") ||
|
|
191
|
-
lower.includes("disconnected") ||
|
|
192
|
-
lower.includes("timeout") ||
|
|
193
|
-
lower.includes("timed out") ||
|
|
194
|
-
lower.includes("econn") ||
|
|
195
|
-
lower.includes("socket") ||
|
|
196
|
-
lower.includes("no transport source") ||
|
|
197
|
-
lower.includes("no transport channel") ||
|
|
198
|
-
lower.includes("only allows local file paths") ||
|
|
199
|
-
lower.includes("no adapter") ||
|
|
200
|
-
lower.includes("identity is unavailable"));
|
|
201
|
-
}
|
|
202
184
|
function classifySlackDispatcherError(error) {
|
|
203
185
|
const message = getErrorMessage(error);
|
|
204
186
|
const lower = message.toLowerCase();
|
|
@@ -311,11 +293,11 @@ function classifySlackDispatcherError(error) {
|
|
|
311
293
|
};
|
|
312
294
|
}
|
|
313
295
|
function extractToolResponseText(response) {
|
|
314
|
-
if (!
|
|
296
|
+
if (!isSlackToolObject(response) || !Array.isArray(response.content))
|
|
315
297
|
return undefined;
|
|
316
298
|
const lines = response.content
|
|
317
299
|
.map((item) => {
|
|
318
|
-
if (!
|
|
300
|
+
if (!isSlackToolObject(item))
|
|
319
301
|
return undefined;
|
|
320
302
|
return typeof item.text === "string" ? item.text : undefined;
|
|
321
303
|
})
|
|
@@ -323,18 +305,20 @@ function extractToolResponseText(response) {
|
|
|
323
305
|
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
324
306
|
}
|
|
325
307
|
function extractToolResponseDetails(response) {
|
|
326
|
-
return
|
|
308
|
+
return isSlackToolObject(response) && "details" in response ? response.details : undefined;
|
|
327
309
|
}
|
|
328
310
|
function extractToolResponseFullDetails(response) {
|
|
329
|
-
return
|
|
311
|
+
return isSlackToolObject(response) && "fullDetails" in response
|
|
312
|
+
? response.fullDetails
|
|
313
|
+
: undefined;
|
|
330
314
|
}
|
|
331
315
|
function normalizeSlackOutputOptions(action, args) {
|
|
332
|
-
if (args != null && !
|
|
316
|
+
if (args != null && !isSlackToolObject(args)) {
|
|
333
317
|
throw new Error("slack args must be an object.");
|
|
334
318
|
}
|
|
335
319
|
const ownsFormatArg = SLACK_ACTIONS_WITH_FORMAT_ARG.has(action);
|
|
336
|
-
const rawResponseFormat =
|
|
337
|
-
const rawFormat = rawResponseFormat ?? (
|
|
320
|
+
const rawResponseFormat = isSlackToolObject(args) ? args.response_format : undefined;
|
|
321
|
+
const rawFormat = rawResponseFormat ?? (isSlackToolObject(args) && !ownsFormatArg ? args.format : undefined);
|
|
338
322
|
const normalizedFormat = rawFormat == null ? "cli" : String(rawFormat).trim().toLowerCase();
|
|
339
323
|
if (normalizedFormat !== "cli" && normalizedFormat !== "json") {
|
|
340
324
|
throw new Error(ownsFormatArg
|
|
@@ -342,14 +326,14 @@ function normalizeSlackOutputOptions(action, args) {
|
|
|
342
326
|
: 'format must be "cli" or "json" when provided.');
|
|
343
327
|
}
|
|
344
328
|
const format = normalizedFormat;
|
|
345
|
-
const rawFull =
|
|
329
|
+
const rawFull = isSlackToolObject(args) ? args.full : undefined;
|
|
346
330
|
if (rawFull != null && typeof rawFull !== "boolean") {
|
|
347
331
|
throw new Error("full must be a boolean when provided.");
|
|
348
332
|
}
|
|
349
333
|
return { format, full: rawFull === true };
|
|
350
334
|
}
|
|
351
335
|
function getSlackEnvelopeCliText(envelope) {
|
|
352
|
-
if (envelope.status === "succeeded" &&
|
|
336
|
+
if (envelope.status === "succeeded" && isSlackToolObject(envelope.data)) {
|
|
353
337
|
const text = envelope.data.text;
|
|
354
338
|
if (typeof text === "string" && text.length > 0)
|
|
355
339
|
return text;
|
|
@@ -423,6 +407,7 @@ function buildSlackSendPromptGuidelines() {
|
|
|
423
407
|
return [
|
|
424
408
|
"Use slack_send for replies in the current Slack assistant thread; always reply where the task came from.",
|
|
425
409
|
"For rich Block Kit JSON examples or modal/canvas patterns, load the slack-bridge skill instead of relying on tool schemas.",
|
|
410
|
+
"If slack_send fails with 'broker is unavailable' or 'already owned by another agent', do NOT retry via post_channel or a different tool \u2014 report the blocker in the same thread (or to the broker) and wait for ownership to be transferred; direct posting would bypass thread ownership (#855).",
|
|
426
411
|
];
|
|
427
412
|
}
|
|
428
413
|
function getSlackCanvasSummary(markdown) {
|
|
@@ -624,32 +609,31 @@ export function registerSlackTools(pi, deps) {
|
|
|
624
609
|
}
|
|
625
610
|
async function deliverSlackMessage(input) {
|
|
626
611
|
const blocks = input.blocks ? normalizeSlackBlocksInput(input.blocks) : undefined;
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
}
|
|
612
|
+
// Threaded Slack replies routed through Pinet MUST NOT fall back to direct
|
|
613
|
+
// Slack when the broker is unavailable or the delivery errors: direct
|
|
614
|
+
// Slack posting bypasses broker thread-ownership enforcement and lets any
|
|
615
|
+
// worker with a valid bot token take over a Slack thread it does not own.
|
|
616
|
+
// See gugu91/pinet#855. Single-player mode (isEnabled === false) and
|
|
617
|
+
// top-level channel posts (no threadTs) remain unaffected.
|
|
618
|
+
if (input.threadTs && pinetDelivery?.isEnabled()) {
|
|
619
|
+
if (!pinetDelivery.isAvailable()) {
|
|
620
|
+
throw new Error("Cannot post to Slack thread: Pinet broker is unavailable and direct Slack fallback would bypass thread-ownership enforcement. Wait for the broker to reconnect, or ask the broker to transfer this thread.");
|
|
621
|
+
}
|
|
622
|
+
const result = await pinetDelivery.sendSlackMessage({
|
|
623
|
+
threadId: input.threadTs,
|
|
624
|
+
channel: input.channel,
|
|
625
|
+
text: input.text,
|
|
626
|
+
...(blocks ? { blocks } : {}),
|
|
627
|
+
...(input.files ? { files: input.files } : {}),
|
|
628
|
+
});
|
|
629
|
+
return {
|
|
630
|
+
threadTs: input.threadTs,
|
|
631
|
+
channel: result.channel,
|
|
632
|
+
blocksCount: blocks?.length ?? 0,
|
|
633
|
+
delivery: "pinet",
|
|
634
|
+
adapter: result.adapter,
|
|
635
|
+
messageId: result.messageId,
|
|
636
|
+
};
|
|
653
637
|
}
|
|
654
638
|
if (input.files && input.files.length > 0) {
|
|
655
639
|
if (blocks && blocks.length > 0) {
|
|
@@ -676,7 +660,6 @@ export function registerSlackTools(pi, deps) {
|
|
|
676
660
|
channel: input.channel,
|
|
677
661
|
blocksCount: blocks?.length ?? 0,
|
|
678
662
|
delivery: "slack",
|
|
679
|
-
...(fallbackReason ? { fallbackReason } : {}),
|
|
680
663
|
};
|
|
681
664
|
}
|
|
682
665
|
const body = {
|
|
@@ -693,14 +676,13 @@ export function registerSlackTools(pi, deps) {
|
|
|
693
676
|
if (input.threadTs)
|
|
694
677
|
body.thread_ts = input.threadTs;
|
|
695
678
|
const response = await slack("chat.postMessage", getBotToken(), body);
|
|
696
|
-
const message =
|
|
679
|
+
const message = isSlackToolObject(response.message) ? response.message : null;
|
|
697
680
|
const ts = typeof message?.ts === "string" ? message.ts : undefined;
|
|
698
681
|
return {
|
|
699
682
|
...(ts ? { ts } : {}),
|
|
700
683
|
channel: input.channel,
|
|
701
684
|
blocksCount: blocks?.length ?? 0,
|
|
702
685
|
delivery: "slack",
|
|
703
|
-
...(fallbackReason ? { fallbackReason } : {}),
|
|
704
686
|
};
|
|
705
687
|
}
|
|
706
688
|
const slackActionRegistry = new Map();
|
|
@@ -712,10 +694,10 @@ export function registerSlackTools(pi, deps) {
|
|
|
712
694
|
slackActionRegistry.set(action, definition);
|
|
713
695
|
}
|
|
714
696
|
function buildSlackDispatcherHelpEnvelope(args) {
|
|
715
|
-
if (args != null && !
|
|
697
|
+
if (args != null && !isSlackToolObject(args)) {
|
|
716
698
|
return buildSlackActionFailureEnvelope(new Error("slack help args must be an object."));
|
|
717
699
|
}
|
|
718
|
-
const topic =
|
|
700
|
+
const topic = isSlackToolObject(args) && typeof args.topic === "string" ? args.topic : undefined;
|
|
719
701
|
if (topic) {
|
|
720
702
|
const action = normalizeSlackDispatcherActionName(topic);
|
|
721
703
|
const definition = slackActionRegistry.get(action);
|
|
@@ -790,7 +772,7 @@ export function registerSlackTools(pi, deps) {
|
|
|
790
772
|
if (action === "help") {
|
|
791
773
|
return buildSlackDispatcherResponse(buildSlackDispatcherHelpEnvelope(args), output);
|
|
792
774
|
}
|
|
793
|
-
if (!
|
|
775
|
+
if (!isSlackToolObject(args)) {
|
|
794
776
|
return buildSlackDispatcherResponse(buildSlackActionFailureEnvelope(new Error("slack args must be an object.")), output);
|
|
795
777
|
}
|
|
796
778
|
const definition = slackActionRegistry.get(action);
|
|
@@ -1268,6 +1250,10 @@ export function registerSlackTools(pi, deps) {
|
|
|
1268
1250
|
: "";
|
|
1269
1251
|
lines.push(`${prefix} (${message.timestamp}): ${message.text}${metadataSuffix}`);
|
|
1270
1252
|
}
|
|
1253
|
+
const subtreeInboxIds = getSubtreeInboxIds(pending);
|
|
1254
|
+
if (subtreeInboxIds.length > 0) {
|
|
1255
|
+
deps.markSubtreeInboxIdsDelivered(subtreeInboxIds);
|
|
1256
|
+
}
|
|
1271
1257
|
return {
|
|
1272
1258
|
content: [
|
|
1273
1259
|
{
|
|
@@ -1425,7 +1411,10 @@ export function registerSlackTools(pi, deps) {
|
|
|
1425
1411
|
label: "Slack Send",
|
|
1426
1412
|
description: "Send a message in a Slack assistant thread.",
|
|
1427
1413
|
promptSnippet: "Reply in a Slack assistant thread. When you receive a task: ACK briefly, do the work, report blockers immediately, report the outcome when done. Always reply where the task came from.",
|
|
1428
|
-
promptGuidelines:
|
|
1414
|
+
promptGuidelines: [
|
|
1415
|
+
...buildSlackSendPromptGuidelines(),
|
|
1416
|
+
...(deps.additionalSendPromptGuidelines ?? []),
|
|
1417
|
+
],
|
|
1429
1418
|
parameters: Type.Object({
|
|
1430
1419
|
text: Type.String({ description: "Message text (Slack markdown)" }),
|
|
1431
1420
|
thread_ts: Type.Optional(Type.String({
|
|
@@ -1479,7 +1468,6 @@ export function registerSlackTools(pi, deps) {
|
|
|
1479
1468
|
filesCount: Array.isArray(params.files) ? params.files.length : 0,
|
|
1480
1469
|
...(delivery.adapter ? { adapter: delivery.adapter } : {}),
|
|
1481
1470
|
...(delivery.messageId ? { messageId: delivery.messageId } : {}),
|
|
1482
|
-
...(delivery.fallbackReason ? { fallbackReason: delivery.fallbackReason } : {}),
|
|
1483
1471
|
},
|
|
1484
1472
|
};
|
|
1485
1473
|
},
|
|
@@ -2194,7 +2182,6 @@ export function registerSlackTools(pi, deps) {
|
|
|
2194
2182
|
filesCount: Array.isArray(params.files) ? params.files.length : 0,
|
|
2195
2183
|
...(delivery.adapter ? { adapter: delivery.adapter } : {}),
|
|
2196
2184
|
...(delivery.messageId ? { messageId: delivery.messageId } : {}),
|
|
2197
|
-
...(delivery.fallbackReason ? { fallbackReason: delivery.fallbackReason } : {}),
|
|
2198
2185
|
},
|
|
2199
2186
|
};
|
|
2200
2187
|
},
|
package/dist/slack-upload.d.ts
CHANGED
|
@@ -17,8 +17,24 @@ export interface PreparedSlackUpload {
|
|
|
17
17
|
source: "content" | "path";
|
|
18
18
|
resolvedPath?: string;
|
|
19
19
|
}
|
|
20
|
+
export interface SlackUploadMetadataPayload extends Record<string, unknown> {
|
|
21
|
+
filename: string;
|
|
22
|
+
length: number;
|
|
23
|
+
snippet_type?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface SlackCompleteUploadFilePayload {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SlackCompleteUploadPayload extends Record<string, unknown> {
|
|
30
|
+
files: SlackCompleteUploadFilePayload[];
|
|
31
|
+
channel_id: string;
|
|
32
|
+
thread_ts?: string;
|
|
33
|
+
initial_comment?: string;
|
|
34
|
+
}
|
|
35
|
+
export type SlackUploadApiBody = SlackUploadMetadataPayload | SlackCompleteUploadPayload;
|
|
20
36
|
export interface SlackUploadDeps {
|
|
21
|
-
slack: (method: string, token: string, body?:
|
|
37
|
+
slack: (method: string, token: string, body?: SlackUploadApiBody) => Promise<SlackResult>;
|
|
22
38
|
token: string;
|
|
23
39
|
fetchImpl?: (url: string, init: RequestInit) => Promise<Pick<Response, "ok" | "status" | "statusText" | "text">>;
|
|
24
40
|
}
|
|
@@ -2,21 +2,51 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import type { AgentSessionSummary } from "./broker/types.js";
|
|
3
3
|
import type { PinetReadOptions, PinetReadResult } from "@pinet/pinet-core/pinet-read-formatting";
|
|
4
4
|
import { type InboxMessage, type PinetControlCommand, type PinetRemoteControlRequestResult, type SlackBridgeSettings } from "./helpers.js";
|
|
5
|
+
import type { BrokerDB } from "./broker/schema.js";
|
|
5
6
|
export interface SubtreeBrokerPaths {
|
|
6
7
|
rootDir: string;
|
|
7
8
|
socketPath: string;
|
|
8
9
|
dbPath: string;
|
|
9
10
|
lockPath: string;
|
|
10
11
|
}
|
|
11
|
-
export
|
|
12
|
-
|
|
12
|
+
export type WorkerRuntimeSpec = {
|
|
13
|
+
runtimeKind: "tmux";
|
|
14
|
+
sessionName: string;
|
|
15
|
+
tmuxSocketPath: string | null;
|
|
16
|
+
} | {
|
|
17
|
+
runtimeKind: "herdr";
|
|
13
18
|
sessionName: string;
|
|
19
|
+
herdrSession: string;
|
|
20
|
+
herdrConfigDir: string;
|
|
21
|
+
herdrPaneId: string | null;
|
|
22
|
+
herdrShellPid: number | null;
|
|
23
|
+
};
|
|
24
|
+
type HerdrWorkerRuntimeSpec = Extract<WorkerRuntimeSpec, {
|
|
25
|
+
runtimeKind: "herdr";
|
|
26
|
+
}>;
|
|
27
|
+
export type SubtreeWorkerRecord = WorkerRuntimeSpec & {
|
|
28
|
+
launchId: string;
|
|
14
29
|
repoPath: string;
|
|
15
30
|
role: string;
|
|
16
31
|
laneId: string | null;
|
|
17
32
|
agentId: string | null;
|
|
18
33
|
startedAt: string;
|
|
19
34
|
monitorCommand: string;
|
|
35
|
+
};
|
|
36
|
+
interface WorkerRuntimeController<T extends WorkerRuntimeSpec> {
|
|
37
|
+
createLaunchSpec: (sessionName: string) => T;
|
|
38
|
+
monitorCommand: (spec: T) => string;
|
|
39
|
+
launch: (spec: T, launcherPath: string, launchEnv: Record<string, string>, onRuntimeOwned?: (spec: T) => void) => Promise<void>;
|
|
40
|
+
cleanup: (spec: T) => Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export interface HerdrCommandOptions {
|
|
43
|
+
env: NodeJS.ProcessEnv;
|
|
44
|
+
detached?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export type HerdrCommandRunner = (args: string[], options: HerdrCommandOptions) => Promise<string>;
|
|
47
|
+
export interface HerdrWorkerRuntimeControllerOptions {
|
|
48
|
+
herdrSession: string;
|
|
49
|
+
herdrConfigDir: string;
|
|
20
50
|
}
|
|
21
51
|
export interface SubtreeBrokerStatus {
|
|
22
52
|
active: boolean;
|
|
@@ -50,16 +80,34 @@ export interface SubtreeAgentRecord {
|
|
|
50
80
|
subtreeRole?: string | null;
|
|
51
81
|
laneId?: string | null;
|
|
52
82
|
}
|
|
83
|
+
export interface SubtreeSpawnLaunchHandle {
|
|
84
|
+
launchId: string;
|
|
85
|
+
tmuxSessionName: string;
|
|
86
|
+
socketPath: string;
|
|
87
|
+
state: "launched_unregistered";
|
|
88
|
+
runtimeKind?: WorkerRuntimeSpec["runtimeKind"];
|
|
89
|
+
monitorCommand?: string;
|
|
90
|
+
}
|
|
91
|
+
export declare class SubtreeSpawnRegistrationTimeoutError extends Error {
|
|
92
|
+
readonly handle: SubtreeSpawnLaunchHandle;
|
|
93
|
+
constructor(timeoutMs: number, handle: SubtreeSpawnLaunchHandle);
|
|
94
|
+
}
|
|
95
|
+
export declare class SubtreeSpawnLaunchError extends Error {
|
|
96
|
+
readonly handle: SubtreeSpawnLaunchHandle;
|
|
97
|
+
constructor(error: Error, handle: SubtreeSpawnLaunchHandle, runtimeKind?: WorkerRuntimeSpec["runtimeKind"]);
|
|
98
|
+
}
|
|
53
99
|
export interface SubtreeSpawnInput {
|
|
54
100
|
task: string;
|
|
55
101
|
repo: string;
|
|
56
102
|
role?: string;
|
|
57
103
|
laneId?: string;
|
|
58
104
|
waitForRegistrationMs?: number;
|
|
105
|
+
cleanupHandle?: SubtreeSpawnLaunchHandle;
|
|
59
106
|
}
|
|
60
107
|
export interface SubtreeSpawnResult {
|
|
61
108
|
status: "started";
|
|
62
109
|
launchId: string;
|
|
110
|
+
runtimeKind: WorkerRuntimeSpec["runtimeKind"];
|
|
63
111
|
sessionName: string;
|
|
64
112
|
repoPath: string;
|
|
65
113
|
role: string;
|
|
@@ -85,19 +133,43 @@ export interface SubtreeBrokerRuntimeDeps {
|
|
|
85
133
|
getAgentMetadata: (role: "broker" | "worker") => Promise<Record<string, unknown>>;
|
|
86
134
|
getMeshRoleFromMetadata: (metadata: Record<string, unknown> | undefined, fallback?: "broker" | "worker") => "broker" | "worker";
|
|
87
135
|
pushInboxMessages: (messages: InboxMessage[]) => void;
|
|
136
|
+
discardQueuedInboxMessages: () => void;
|
|
88
137
|
updateBadge: () => void;
|
|
89
138
|
maybeDrainInboxIfIdle: (ctx: ExtensionContext) => boolean;
|
|
139
|
+
deliverSteeringMessage: (text: string, ctx: ExtensionContext) => boolean;
|
|
90
140
|
requestRemoteControl: (command: PinetControlCommand, ctx: ExtensionContext) => PinetRemoteControlRequestResult;
|
|
91
141
|
runRemoteControl: (command: PinetControlCommand, ctx: ExtensionContext) => void;
|
|
92
142
|
formatError: (error: unknown) => string;
|
|
143
|
+
runTmuxCommand?: (args: string[]) => Promise<void>;
|
|
144
|
+
runHerdrCommand?: HerdrCommandRunner;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The authoritative hibernation runtime surface for this process's subtree
|
|
148
|
+
* broker. The subtree broker is the authority that spawns and OWNS its workers
|
|
149
|
+
* and authors their durable runtime specs, so hibernate/wake must resolve the
|
|
150
|
+
* target, read its authz spec, and drive the orchestrator against THIS same
|
|
151
|
+
* authoritative DB end to end. The explicit trust boundary: the operator command
|
|
152
|
+
* may only address workers this subtree broker owns. Null when no subtree broker
|
|
153
|
+
* is running (nothing is command-addressable).
|
|
154
|
+
*/
|
|
155
|
+
export interface SubtreeHibernationRuntimeControl {
|
|
156
|
+
/** The single authoritative DB that owns the spawned workers + their specs. */
|
|
157
|
+
db: BrokerDB;
|
|
158
|
+
/** Broker instance id recorded on lifecycle leases; matches startup recovery. */
|
|
159
|
+
brokerInstanceId: string;
|
|
160
|
+
/** Base PINET_* env re-establishing the mesh connection for a woken worker. */
|
|
161
|
+
baseLaunchEnv: Record<string, string>;
|
|
93
162
|
}
|
|
94
163
|
export interface SubtreeBrokerRuntime {
|
|
95
164
|
start: (ctx: ExtensionContext) => Promise<SubtreeBrokerStatus>;
|
|
165
|
+
getHibernationRuntimeControl: () => SubtreeHibernationRuntimeControl | null;
|
|
96
166
|
stop: (options?: {
|
|
97
167
|
releaseIdentity?: boolean;
|
|
98
168
|
stopChildren?: boolean;
|
|
99
169
|
}) => Promise<void>;
|
|
100
170
|
getStatus: () => SubtreeBrokerStatus;
|
|
171
|
+
drainInbox: (ctx: ExtensionContext) => void;
|
|
172
|
+
markDelivered: (inboxIds: number[]) => void;
|
|
101
173
|
readInbox: (options?: PinetReadOptions) => PinetReadResult | null;
|
|
102
174
|
sendMessage: (target: string, body: string, metadata?: Record<string, unknown>) => Promise<{
|
|
103
175
|
messageId: number;
|
|
@@ -109,4 +181,13 @@ export interface SubtreeBrokerRuntime {
|
|
|
109
181
|
isActive: () => boolean;
|
|
110
182
|
}
|
|
111
183
|
export declare function buildSubtreeBrokerPaths(stableId: string): SubtreeBrokerPaths;
|
|
184
|
+
export declare function createHerdrWorkerRuntimeController(runHerdrCommand: HerdrCommandRunner, options: HerdrWorkerRuntimeControllerOptions): WorkerRuntimeController<HerdrWorkerRuntimeSpec>;
|
|
185
|
+
export declare function getExtensionEntryPath(): string;
|
|
186
|
+
/**
|
|
187
|
+
* Broker env var NAMES (never values) a spawned — or later WOKEN — worker
|
|
188
|
+
* re-exports to re-establish itself. Single source of truth shared by the
|
|
189
|
+
* ordinary spawn launcher and the Phase B wake path so both stay in lockstep.
|
|
190
|
+
*/
|
|
191
|
+
export declare const SUBTREE_INHERITED_ENV_KEYS: string[];
|
|
112
192
|
export declare function createSubtreeBrokerRuntime(deps: SubtreeBrokerRuntimeDeps): SubtreeBrokerRuntime;
|
|
193
|
+
export {};
|