experimental-ash 0.18.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/docs/internals/context.md +7 -0
- package/dist/docs/public/channels/README.md +5 -0
- package/dist/docs/public/channels/slack.md +58 -4
- package/dist/docs/public/hooks.md +4 -2
- package/dist/docs/public/sandbox.md +71 -49
- package/dist/docs/public/typescript-api.md +6 -1
- package/dist/src/channel/adapter.js +12 -2
- package/dist/src/channel/routes.d.ts +9 -1
- package/dist/src/channel/send.js +3 -3
- package/dist/src/channel/types.d.ts +3 -1
- package/dist/src/chunks/{dev-authored-source-watcher-j7YWh2Gx.js → dev-authored-source-watcher-L3_pagDa.js} +1 -1
- package/dist/src/chunks/{host-DkTSR6YJ.js → host-e2GUqnTr.js} +3 -3
- package/dist/src/chunks/{paths-Dwv0Eash.js → paths-BBleOGpa.js} +25 -25
- package/dist/src/chunks/{prewarm-CQYfka30.js → prewarm-DEymC5M-.js} +1 -1
- package/dist/src/cli/commands/info.js +1 -1
- package/dist/src/cli/run.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +1 -1
- package/dist/src/compiled/@vercel/sandbox/index.d.ts +6 -1
- package/dist/src/context/hook-lifecycle.js +5 -1
- package/dist/src/evals/cli/eval.js +1 -1
- package/dist/src/execution/sandbox/bindings/local.d.ts +14 -1
- package/dist/src/execution/sandbox/bindings/local.js +5 -1
- package/dist/src/execution/sandbox/bindings/vercel.d.ts +6 -0
- package/dist/src/execution/sandbox/bindings/vercel.js +12 -1
- package/dist/src/execution/sandbox/lazy-backend.d.ts +15 -0
- package/dist/src/execution/sandbox/lazy-backend.js +33 -0
- package/dist/src/execution/workflow-entry.d.ts +2 -4
- package/dist/src/execution/workflow-entry.js +1 -1
- package/dist/src/harness/messages.js +15 -0
- package/dist/src/harness/types.d.ts +6 -7
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-definition/sandbox.d.ts +8 -2
- package/dist/src/internal/authored-definition/sandbox.js +10 -2
- package/dist/src/internal/workflow-bundle/vercel-workflow-output.js +0 -2
- package/dist/src/public/channels/slack/api.d.ts +2 -27
- package/dist/src/public/channels/slack/api.js +6 -82
- package/dist/src/public/channels/slack/defaults.js +2 -2
- package/dist/src/public/channels/slack/hitl.js +6 -3
- package/dist/src/public/channels/slack/inbound.js +1 -1
- package/dist/src/public/channels/slack/index.d.ts +3 -0
- package/dist/src/public/channels/slack/index.js +2 -0
- package/dist/src/public/channels/slack/limits.d.ts +19 -0
- package/dist/src/public/channels/slack/limits.js +23 -0
- package/dist/src/public/channels/slack/mrkdwn.d.ts +38 -0
- package/dist/src/public/channels/slack/mrkdwn.js +89 -0
- package/dist/src/public/channels/slack/slackChannel.d.ts +12 -4
- package/dist/src/public/channels/slack/slackChannel.js +5 -8
- package/dist/src/public/channels/slack/thread.d.ts +26 -0
- package/dist/src/public/channels/slack/thread.js +45 -0
- package/dist/src/public/definitions/defineChannel.d.ts +1 -1
- package/dist/src/public/sandbox/backends/default.d.ts +16 -1
- package/dist/src/public/sandbox/backends/default.js +7 -19
- package/dist/src/public/sandbox/backends/local.d.ts +7 -4
- package/dist/src/public/sandbox/backends/local.js +7 -5
- package/dist/src/public/sandbox/backends/vercel.d.ts +9 -3
- package/dist/src/public/sandbox/backends/vercel.js +9 -3
- package/dist/src/public/sandbox/index.d.ts +2 -1
- package/dist/src/public/sandbox/local-sandbox.d.ts +7 -0
- package/dist/src/public/sandbox/local-sandbox.js +1 -0
- package/dist/src/public/sandbox/vercel-sandbox.d.ts +13 -1
- package/dist/src/runtime/resolve-sandbox.js +5 -1
- package/dist/src/runtime/types.d.ts +10 -1
- package/dist/src/shared/sandbox-definition.d.ts +16 -1
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* picks whichever is set so the renderer can pick a widget kind on
|
|
12
12
|
* UX grounds without changing the read path.
|
|
13
13
|
*/
|
|
14
|
-
import { truncateModalTitle, truncatePlainText } from "#public/channels/slack/limits.js";
|
|
14
|
+
import { truncateModalTitle, truncatePlainText, truncateSectionText, } from "#public/channels/slack/limits.js";
|
|
15
15
|
/**
|
|
16
16
|
* Wire-format prefix every framework HITL widget mints onto its
|
|
17
17
|
* `action_id`. Exposed so end-user adapters that render their own
|
|
@@ -93,7 +93,10 @@ export function isHitlAction(actionId) {
|
|
|
93
93
|
* Always emits at least the prompt section.
|
|
94
94
|
*/
|
|
95
95
|
export function renderInputRequestBlocks(request) {
|
|
96
|
-
const prompt = {
|
|
96
|
+
const prompt = {
|
|
97
|
+
text: { text: truncateSectionText(request.prompt), type: "mrkdwn" },
|
|
98
|
+
type: "section",
|
|
99
|
+
};
|
|
97
100
|
const actionId = `${HITL_ACTION_PREFIX}${request.requestId}`;
|
|
98
101
|
const options = request.options;
|
|
99
102
|
const acceptsFreeform = request.allowFreeform === true || !options || options.length === 0;
|
|
@@ -146,7 +149,7 @@ export function renderInputRequestBlocks(request) {
|
|
|
146
149
|
export function buildFreeformModalView(input) {
|
|
147
150
|
const title = input.prompt ? truncateModalTitle(input.prompt) : "Your answer";
|
|
148
151
|
const promptBlocks = input.prompt
|
|
149
|
-
? [{ type: "section", text: { type: "mrkdwn", text: input.prompt } }]
|
|
152
|
+
? [{ type: "section", text: { type: "mrkdwn", text: truncateSectionText(input.prompt) } }]
|
|
150
153
|
: [];
|
|
151
154
|
return {
|
|
152
155
|
type: "modal",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* naming the actor, channel, and thread so the agent's prompt always
|
|
13
13
|
* knows who and where it is talking.
|
|
14
14
|
*/
|
|
15
|
-
import { slackMrkdwnToGfm } from "#public/channels/slack/
|
|
15
|
+
import { slackMrkdwnToGfm } from "#public/channels/slack/mrkdwn.js";
|
|
16
16
|
/**
|
|
17
17
|
* Parses a Slack `app_mention` event into a {@link SlackMessage}.
|
|
18
18
|
*
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
export type { ModelMessage } from "ai";
|
|
1
2
|
export { slackChannel, type SlackApiResponse, type SlackBotToken, type SlackChannel, type SlackChannelConfig, type SlackChannelCredentials, type SlackChannelEvents, type SlackChannelState, type SlackContext, type SlackEventContext, type SlackHandle, type SlackInboundResult, type SlackInboundResultOrPromise, type SlackInitialMessage, type SlackInteractionAction, type SlackMentionResult, type SlackMentionResultOrPromise, type SlackReceiveArgs, type SlackThread, type SlackWebhookVerifier, } from "#public/channels/slack/slackChannel.js";
|
|
2
3
|
export type { SlackAttachment, SlackAuthor, SlackInboundContext, SlackMessage, } from "#public/channels/slack/inbound.js";
|
|
3
4
|
export { slackContinuationToken, type SlackPostInput, type SlackPostedMessage, type SlackThreadMessage, type SlackUploadFilesOptions, type SlackUploadFilesResult, } from "#public/channels/slack/api.js";
|
|
5
|
+
export { defaultSlackAuth } from "#public/channels/slack/defaults.js";
|
|
6
|
+
export { loadThreadContextMessages, type LoadThreadContextMessagesOptions, type ThreadContextSince, } from "#public/channels/slack/thread.js";
|
|
4
7
|
export { cardToBlocks, cardToFallbackText, type BlockKitBlock, } from "#public/channels/slack/blocks.js";
|
|
5
8
|
/**
|
|
6
9
|
* Card builders and element types re-exported from the vendored chat
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { slackChannel, } from "#public/channels/slack/slackChannel.js";
|
|
2
2
|
export { slackContinuationToken, } from "#public/channels/slack/api.js";
|
|
3
|
+
export { defaultSlackAuth } from "#public/channels/slack/defaults.js";
|
|
4
|
+
export { loadThreadContextMessages, } from "#public/channels/slack/thread.js";
|
|
3
5
|
export { cardToBlocks, cardToFallbackText, } from "#public/channels/slack/blocks.js";
|
|
4
6
|
/**
|
|
5
7
|
* Card builders and element types re-exported from the vendored chat
|
|
@@ -20,6 +20,15 @@ export declare const SLACK_TYPING_STATUS_MAX_LENGTH = 50;
|
|
|
20
20
|
* options and button labels are capped at 75 characters by Slack.
|
|
21
21
|
*/
|
|
22
22
|
export declare const SLACK_BLOCK_KIT_PLAIN_TEXT_MAX_LENGTH = 75;
|
|
23
|
+
/**
|
|
24
|
+
* Block Kit `section` blocks cap `text.text` at 3000 chars. Anything
|
|
25
|
+
* longer fails the whole post with `invalid_blocks`.
|
|
26
|
+
*/
|
|
27
|
+
export declare const SLACK_SECTION_TEXT_MAX_LENGTH = 3000;
|
|
28
|
+
/**
|
|
29
|
+
* Top-level `text` field on `chat.postMessage` is capped at 40000 chars.
|
|
30
|
+
*/
|
|
31
|
+
export declare const SLACK_MESSAGE_TEXT_MAX_LENGTH = 40000;
|
|
23
32
|
/**
|
|
24
33
|
* `views.open` modal title is capped at 24 characters.
|
|
25
34
|
*/
|
|
@@ -37,6 +46,16 @@ export declare function truncateTypingStatus(status: string): string;
|
|
|
37
46
|
*/
|
|
38
47
|
export declare function truncatePlainText(value: string): string;
|
|
39
48
|
export declare function truncatePlainText(value: string | undefined): string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Caps a section block's `text.text` at the Slack limit with a
|
|
51
|
+
* trailing ellipsis.
|
|
52
|
+
*/
|
|
53
|
+
export declare function truncateSectionText(value: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Caps a `chat.postMessage` `text` field at the Slack limit with a
|
|
56
|
+
* trailing ellipsis.
|
|
57
|
+
*/
|
|
58
|
+
export declare function truncateMessageText(value: string): string;
|
|
40
59
|
/**
|
|
41
60
|
* Caps a modal title at the Slack limit with a trailing ellipsis.
|
|
42
61
|
*/
|
|
@@ -20,6 +20,15 @@ export const SLACK_TYPING_STATUS_MAX_LENGTH = 50;
|
|
|
20
20
|
* options and button labels are capped at 75 characters by Slack.
|
|
21
21
|
*/
|
|
22
22
|
export const SLACK_BLOCK_KIT_PLAIN_TEXT_MAX_LENGTH = 75;
|
|
23
|
+
/**
|
|
24
|
+
* Block Kit `section` blocks cap `text.text` at 3000 chars. Anything
|
|
25
|
+
* longer fails the whole post with `invalid_blocks`.
|
|
26
|
+
*/
|
|
27
|
+
export const SLACK_SECTION_TEXT_MAX_LENGTH = 3000;
|
|
28
|
+
/**
|
|
29
|
+
* Top-level `text` field on `chat.postMessage` is capped at 40000 chars.
|
|
30
|
+
*/
|
|
31
|
+
export const SLACK_MESSAGE_TEXT_MAX_LENGTH = 40000;
|
|
23
32
|
/**
|
|
24
33
|
* `views.open` modal title is capped at 24 characters.
|
|
25
34
|
*/
|
|
@@ -38,6 +47,20 @@ export function truncatePlainText(value) {
|
|
|
38
47
|
return undefined;
|
|
39
48
|
return truncateWithEllipsis(value, SLACK_BLOCK_KIT_PLAIN_TEXT_MAX_LENGTH);
|
|
40
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Caps a section block's `text.text` at the Slack limit with a
|
|
52
|
+
* trailing ellipsis.
|
|
53
|
+
*/
|
|
54
|
+
export function truncateSectionText(value) {
|
|
55
|
+
return truncateWithEllipsis(value, SLACK_SECTION_TEXT_MAX_LENGTH);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Caps a `chat.postMessage` `text` field at the Slack limit with a
|
|
59
|
+
* trailing ellipsis.
|
|
60
|
+
*/
|
|
61
|
+
export function truncateMessageText(value) {
|
|
62
|
+
return truncateWithEllipsis(value, SLACK_MESSAGE_TEXT_MAX_LENGTH);
|
|
63
|
+
}
|
|
41
64
|
/**
|
|
42
65
|
* Caps a modal title at the Slack limit with a trailing ellipsis.
|
|
43
66
|
*/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack mrkdwn ↔ GitHub-flavored markdown converters and the bare
|
|
3
|
+
* `@mention` rewriter used by the outbound post pipeline. These are
|
|
4
|
+
* pure string utilities — no Slack API I/O, no I/O at all — so they
|
|
5
|
+
* live separately from the binding constructor and request shape code
|
|
6
|
+
* in {@link ./api.ts}.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Rewrites bare `@USER_ID` tokens (the form Slack apps and humans tend
|
|
10
|
+
* to type) into the `<@USER_ID>` mention syntax Slack actually renders.
|
|
11
|
+
* Anything already wrapped in `<...>` is left untouched.
|
|
12
|
+
*/
|
|
13
|
+
export declare function rewriteBareMentions(text: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Best-effort GFM → Slack mrkdwn converter used only in contexts that
|
|
16
|
+
* do not support `markdown_text` (e.g. `files.completeUploadExternal`'s
|
|
17
|
+
* `initial_comment` field).
|
|
18
|
+
*
|
|
19
|
+
* The main `{ markdown }` post path sends `markdown_text` directly
|
|
20
|
+
* to `chat.postMessage` and does not go through this converter.
|
|
21
|
+
*/
|
|
22
|
+
export declare function gfmToSlackMrkdwn(input: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Best-effort Slack mrkdwn → GFM converter applied to the text of
|
|
25
|
+
* every inbound Slack message before the harness sees it.
|
|
26
|
+
*
|
|
27
|
+
* - `<@U123>` → `@U123`
|
|
28
|
+
* - `<#C123|name>` → `#name` (or `#C123` when no name)
|
|
29
|
+
* - `<!channel>` etc. → `@channel`
|
|
30
|
+
* - `<https://x|label>` → `[label](https://x)`
|
|
31
|
+
* - `<https://x>` → `https://x`
|
|
32
|
+
* - `*bold*` (paired) → `**bold**`
|
|
33
|
+
* - `~strike~` (paired) → `~~strike~~`
|
|
34
|
+
*
|
|
35
|
+
* Inline `_italic_` and code spans pass through unchanged because both
|
|
36
|
+
* formats render them identically.
|
|
37
|
+
*/
|
|
38
|
+
export declare function slackMrkdwnToGfm(input: string): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack mrkdwn ↔ GitHub-flavored markdown converters and the bare
|
|
3
|
+
* `@mention` rewriter used by the outbound post pipeline. These are
|
|
4
|
+
* pure string utilities — no Slack API I/O, no I/O at all — so they
|
|
5
|
+
* live separately from the binding constructor and request shape code
|
|
6
|
+
* in {@link ./api.ts}.
|
|
7
|
+
*/
|
|
8
|
+
const BARE_MENTION_RE = /(?<![<\w])@(\w+)/gu;
|
|
9
|
+
/**
|
|
10
|
+
* Rewrites bare `@USER_ID` tokens (the form Slack apps and humans tend
|
|
11
|
+
* to type) into the `<@USER_ID>` mention syntax Slack actually renders.
|
|
12
|
+
* Anything already wrapped in `<...>` is left untouched.
|
|
13
|
+
*/
|
|
14
|
+
export function rewriteBareMentions(text) {
|
|
15
|
+
return text.replace(BARE_MENTION_RE, "<@$1>");
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Best-effort GFM → Slack mrkdwn converter used only in contexts that
|
|
19
|
+
* do not support `markdown_text` (e.g. `files.completeUploadExternal`'s
|
|
20
|
+
* `initial_comment` field).
|
|
21
|
+
*
|
|
22
|
+
* The main `{ markdown }` post path sends `markdown_text` directly
|
|
23
|
+
* to `chat.postMessage` and does not go through this converter.
|
|
24
|
+
*/
|
|
25
|
+
export function gfmToSlackMrkdwn(input) {
|
|
26
|
+
const segments = splitCodeFences(input);
|
|
27
|
+
return segments
|
|
28
|
+
.map((segment) => (segment.kind === "code" ? segment.text : convertInline(segment.text)))
|
|
29
|
+
.join("");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Best-effort Slack mrkdwn → GFM converter applied to the text of
|
|
33
|
+
* every inbound Slack message before the harness sees it.
|
|
34
|
+
*
|
|
35
|
+
* - `<@U123>` → `@U123`
|
|
36
|
+
* - `<#C123|name>` → `#name` (or `#C123` when no name)
|
|
37
|
+
* - `<!channel>` etc. → `@channel`
|
|
38
|
+
* - `<https://x|label>` → `[label](https://x)`
|
|
39
|
+
* - `<https://x>` → `https://x`
|
|
40
|
+
* - `*bold*` (paired) → `**bold**`
|
|
41
|
+
* - `~strike~` (paired) → `~~strike~~`
|
|
42
|
+
*
|
|
43
|
+
* Inline `_italic_` and code spans pass through unchanged because both
|
|
44
|
+
* formats render them identically.
|
|
45
|
+
*/
|
|
46
|
+
export function slackMrkdwnToGfm(input) {
|
|
47
|
+
const segments = splitCodeFences(input);
|
|
48
|
+
return segments
|
|
49
|
+
.map((segment) => (segment.kind === "code" ? segment.text : decodeInline(segment.text)))
|
|
50
|
+
.join("");
|
|
51
|
+
}
|
|
52
|
+
function splitCodeFences(input) {
|
|
53
|
+
const segments = [];
|
|
54
|
+
const fenceRe = /```[\s\S]*?```|`[^`\n]+`/gu;
|
|
55
|
+
let lastIndex = 0;
|
|
56
|
+
for (const match of input.matchAll(fenceRe)) {
|
|
57
|
+
const start = match.index ?? 0;
|
|
58
|
+
if (start > lastIndex) {
|
|
59
|
+
segments.push({ kind: "text", text: input.slice(lastIndex, start) });
|
|
60
|
+
}
|
|
61
|
+
segments.push({ kind: "code", text: match[0] });
|
|
62
|
+
lastIndex = start + match[0].length;
|
|
63
|
+
}
|
|
64
|
+
if (lastIndex < input.length) {
|
|
65
|
+
segments.push({ kind: "text", text: input.slice(lastIndex) });
|
|
66
|
+
}
|
|
67
|
+
return segments;
|
|
68
|
+
}
|
|
69
|
+
function convertInline(input) {
|
|
70
|
+
let out = input;
|
|
71
|
+
out = out.replace(/\*\*([^*\n]+)\*\*/gu, "*$1*");
|
|
72
|
+
out = out.replace(/__([^_\n]+)__/gu, "*$1*");
|
|
73
|
+
out = out.replace(/~~([^~\n]+)~~/gu, "~$1~");
|
|
74
|
+
out = out.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/gu, "<$2|$1>");
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
function decodeInline(input) {
|
|
78
|
+
let out = input;
|
|
79
|
+
out = out.replace(/<!(channel|here|everyone)>/gu, "@$1");
|
|
80
|
+
out = out.replace(/<@([A-Z0-9]+)\|([^>]+)>/gu, "@$2");
|
|
81
|
+
out = out.replace(/<@([A-Z0-9]+)>/gu, "@$1");
|
|
82
|
+
out = out.replace(/<#([A-Z0-9]+)\|([^>]+)>/gu, "#$2");
|
|
83
|
+
out = out.replace(/<#([A-Z0-9]+)>/gu, "#$1");
|
|
84
|
+
out = out.replace(/<(https?:\/\/[^|>\s]+)\|([^>]+)>/gu, "[$2]($1)");
|
|
85
|
+
out = out.replace(/<(https?:\/\/[^>\s]+)>/gu, "$1");
|
|
86
|
+
out = out.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/gu, "$1**$2**");
|
|
87
|
+
out = out.replace(/(^|[^~])~([^~\n]+)~(?!~)/gu, "$1~~$2~~");
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ModelMessage } from "ai";
|
|
1
2
|
import type { TypedReceiveRoute } from "#channel/receive-args.js";
|
|
2
3
|
import type { SessionHandle } from "#channel/session.js";
|
|
3
4
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
@@ -158,12 +159,19 @@ export interface SlackInteractionUser {
|
|
|
158
159
|
readonly name?: string;
|
|
159
160
|
}
|
|
160
161
|
/**
|
|
161
|
-
* Result of an `onAppMention` or `onDirectMessage` callback. Return
|
|
162
|
-
*
|
|
163
|
-
*
|
|
162
|
+
* Result of an `onAppMention` or `onDirectMessage` callback. Return an
|
|
163
|
+
* object (auth may be `null`) to dispatch a turn, or `null` to silently
|
|
164
|
+
* drop the inbound message.
|
|
165
|
+
*
|
|
166
|
+
* `modelContext` mirrors lifecycle hook results: it contributes
|
|
167
|
+
* ephemeral messages to the next model call only, without writing them
|
|
168
|
+
* to durable session history. Use `role: "system"` for Slack thread
|
|
169
|
+
* background context so it lands before the user's mention; see
|
|
170
|
+
* `docs/public/channels/slack.md`.
|
|
164
171
|
*/
|
|
165
172
|
export type SlackMentionResult = {
|
|
166
|
-
auth: SessionAuthContext | null;
|
|
173
|
+
readonly auth: SessionAuthContext | null;
|
|
174
|
+
readonly modelContext?: readonly ModelMessage[];
|
|
167
175
|
} | null;
|
|
168
176
|
export type SlackMentionResultOrPromise = SlackMentionResult | Promise<SlackMentionResult>;
|
|
169
177
|
/**
|
|
@@ -14,13 +14,7 @@ function rebuildSlackContext(state, session, credentials) {
|
|
|
14
14
|
channelId: state.channelId ?? "",
|
|
15
15
|
threadTs: state.threadTs ?? "",
|
|
16
16
|
teamId: state.teamId ?? undefined,
|
|
17
|
-
|
|
18
|
-
// thread (programmatic start, schedule firing, etc.) becomes the
|
|
19
|
-
// thread root. We update durable adapter state so subsequent
|
|
20
|
-
// posts thread under it, and re-key the parked session so a
|
|
21
|
-
// follow-up `@mention` reply in the same thread resumes the same
|
|
22
|
-
// workflow.
|
|
23
|
-
onAnchor(ts) {
|
|
17
|
+
onThreadTsChanged(ts) {
|
|
24
18
|
state.threadTs = ts;
|
|
25
19
|
if (state.channelId) {
|
|
26
20
|
session.setContinuationToken(slackContinuationToken(state.channelId, ts));
|
|
@@ -236,7 +230,10 @@ async function dispatchInboundMessage(input) {
|
|
|
236
230
|
? prependSlackContext(baseMessage, inboundContext)
|
|
237
231
|
: baseMessage;
|
|
238
232
|
try {
|
|
239
|
-
await input.send(
|
|
233
|
+
await input.send({
|
|
234
|
+
message: turnMessage,
|
|
235
|
+
modelContext: result.modelContext,
|
|
236
|
+
}, {
|
|
240
237
|
auth: result.auth,
|
|
241
238
|
continuationToken: slackContinuationToken(message.channelId, message.threadTs),
|
|
242
239
|
state: {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SlackThread, SlackThreadMessage } from "#public/channels/slack/api.js";
|
|
2
|
+
export type ThreadContextSince = "thread-root" | "last-agent-reply" | ((message: SlackThreadMessage) => boolean);
|
|
3
|
+
export interface LoadThreadContextMessagesOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Boundary for returned context messages. Defaults to `"thread-root"`.
|
|
6
|
+
*
|
|
7
|
+
* Use `"last-agent-reply"` to include only user/thread messages
|
|
8
|
+
* since the last agent-authored Slack reply. Pass a predicate
|
|
9
|
+
* function for custom boundaries, such as "since the last message
|
|
10
|
+
* that mentioned a particular user".
|
|
11
|
+
*/
|
|
12
|
+
readonly since?: ThreadContextSince;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Loads messages that are useful as background context for the current
|
|
16
|
+
* Slack thread turn.
|
|
17
|
+
*
|
|
18
|
+
* Returns an empty array when `message` is the thread root. For thread
|
|
19
|
+
* replies, refreshes the bound Slack thread and returns its recent
|
|
20
|
+
* messages before the triggering message, filtered by {@link options}.
|
|
21
|
+
* Formatting and model-message role choice stay with the caller.
|
|
22
|
+
*/
|
|
23
|
+
export declare function loadThreadContextMessages(thread: Pick<SlackThread, "recentMessages" | "refresh">, message: {
|
|
24
|
+
readonly threadTs: string;
|
|
25
|
+
readonly ts: string;
|
|
26
|
+
}, options?: LoadThreadContextMessagesOptions): Promise<SlackThreadMessage[]>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads messages that are useful as background context for the current
|
|
3
|
+
* Slack thread turn.
|
|
4
|
+
*
|
|
5
|
+
* Returns an empty array when `message` is the thread root. For thread
|
|
6
|
+
* replies, refreshes the bound Slack thread and returns its recent
|
|
7
|
+
* messages before the triggering message, filtered by {@link options}.
|
|
8
|
+
* Formatting and model-message role choice stay with the caller.
|
|
9
|
+
*/
|
|
10
|
+
export async function loadThreadContextMessages(thread, message, options = {}) {
|
|
11
|
+
if (isThreadRootMessage(message)) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
await thread.refresh();
|
|
15
|
+
const currentIndex = thread.recentMessages.findIndex((entry) => entry.ts === message.ts);
|
|
16
|
+
const candidateMessages = currentIndex === -1 ? thread.recentMessages : thread.recentMessages.slice(0, currentIndex);
|
|
17
|
+
const priorMessages = candidateMessages.filter((entry) => entry.threadTs === message.threadTs && entry.ts !== message.ts);
|
|
18
|
+
return applySinceBoundary(priorMessages, options.since);
|
|
19
|
+
}
|
|
20
|
+
function isThreadRootMessage(message) {
|
|
21
|
+
return message.threadTs === message.ts;
|
|
22
|
+
}
|
|
23
|
+
function findLastIndex(items, predicate) {
|
|
24
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
25
|
+
if (predicate(items[index])) {
|
|
26
|
+
return index;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return -1;
|
|
30
|
+
}
|
|
31
|
+
function applySinceBoundary(messages, since) {
|
|
32
|
+
const boundary = since ?? "thread-root";
|
|
33
|
+
if (typeof boundary === "function") {
|
|
34
|
+
const lastMatchingIndex = findLastIndex(messages, boundary);
|
|
35
|
+
return messages.slice(lastMatchingIndex + 1);
|
|
36
|
+
}
|
|
37
|
+
switch (boundary) {
|
|
38
|
+
case "thread-root":
|
|
39
|
+
return [...messages];
|
|
40
|
+
case "last-agent-reply": {
|
|
41
|
+
const lastAgentReplyIndex = findLastIndex(messages, (entry) => entry.isMe);
|
|
42
|
+
return messages.slice(lastAgentReplyIndex + 1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -37,7 +37,7 @@ export interface ChannelConfig<TState = undefined, TCtx = void> {
|
|
|
37
37
|
* Builds the per-step channel context handed to `events` and
|
|
38
38
|
* `deliver`. Receives the live {@link SessionHandle} so factories
|
|
39
39
|
* that need to wire late-bound callbacks (e.g. Slack's auto-anchor
|
|
40
|
-
* `
|
|
40
|
+
* `onThreadTsChanged` calling `session.setContinuationToken(...)`) can close
|
|
41
41
|
* over it. State mutations made inside the returned context flow
|
|
42
42
|
* back through `adapter.state` automatically.
|
|
43
43
|
*/
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
|
|
2
|
+
import type { LocalSandboxCreateOptions } from "#public/sandbox/local-sandbox.js";
|
|
3
|
+
import type { VercelSandboxCreateOptions } from "#public/sandbox/vercel-sandbox.js";
|
|
4
|
+
/**
|
|
5
|
+
* Input to {@link defaultBackend}: a separate options bag per inner
|
|
6
|
+
* backend. The framework picks one bag at runtime based on `process.env.VERCEL`
|
|
7
|
+
* and passes it to the chosen factory; the other is ignored.
|
|
8
|
+
*/
|
|
9
|
+
export interface DefaultBackendOptions {
|
|
10
|
+
readonly local?: LocalSandboxCreateOptions;
|
|
11
|
+
readonly vercel?: VercelSandboxCreateOptions;
|
|
12
|
+
}
|
|
2
13
|
/**
|
|
3
14
|
* Constructs an env-aware sandbox backend that delegates to
|
|
4
15
|
* {@link vercelBackend} on hosted Vercel (where `process.env.VERCEL`
|
|
5
16
|
* is truthy) and to {@link localBackend} everywhere else.
|
|
17
|
+
*
|
|
18
|
+
* Optionally accepts a keyed options bag (`{ local, vercel }`) so each
|
|
19
|
+
* inner backend receives its own typed create options without forcing
|
|
20
|
+
* authors to pin to one backend up front.
|
|
6
21
|
*/
|
|
7
|
-
export declare function defaultBackend(): SandboxBackend;
|
|
22
|
+
export declare function defaultBackend(opts?: DefaultBackendOptions): SandboxBackend;
|
|
@@ -1,27 +1,15 @@
|
|
|
1
|
+
import { lazyBackend } from "#execution/sandbox/lazy-backend.js";
|
|
1
2
|
import { localBackend } from "#public/sandbox/backends/local.js";
|
|
2
3
|
import { vercelBackend } from "#public/sandbox/backends/vercel.js";
|
|
3
4
|
/**
|
|
4
5
|
* Constructs an env-aware sandbox backend that delegates to
|
|
5
6
|
* {@link vercelBackend} on hosted Vercel (where `process.env.VERCEL`
|
|
6
7
|
* is truthy) and to {@link localBackend} everywhere else.
|
|
8
|
+
*
|
|
9
|
+
* Optionally accepts a keyed options bag (`{ local, vercel }`) so each
|
|
10
|
+
* inner backend receives its own typed create options without forcing
|
|
11
|
+
* authors to pin to one backend up front.
|
|
7
12
|
*/
|
|
8
|
-
export function defaultBackend() {
|
|
9
|
-
|
|
10
|
-
function resolve() {
|
|
11
|
-
if (resolved === undefined) {
|
|
12
|
-
resolved = process.env.VERCEL ? vercelBackend() : localBackend();
|
|
13
|
-
}
|
|
14
|
-
return resolved;
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
get name() {
|
|
18
|
-
return resolve().name;
|
|
19
|
-
},
|
|
20
|
-
create(input) {
|
|
21
|
-
return resolve().create(input);
|
|
22
|
-
},
|
|
23
|
-
async prewarm(input) {
|
|
24
|
-
await resolve().prewarm(input);
|
|
25
|
-
},
|
|
26
|
-
};
|
|
13
|
+
export function defaultBackend(opts) {
|
|
14
|
+
return lazyBackend(() => process.env.VERCEL ? vercelBackend(opts?.vercel) : localBackend(opts?.local));
|
|
27
15
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
|
|
2
|
+
import type { LocalSandboxCreateOptions } from "#public/sandbox/local-sandbox.js";
|
|
2
3
|
/**
|
|
3
4
|
* Constructs the built-in local sandbox backend.
|
|
4
5
|
*
|
|
@@ -7,8 +8,10 @@ import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
|
|
|
7
8
|
* under the application root. It is the default backend on developer
|
|
8
9
|
* machines (`pnpm ash dev`).
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Accepts an `opts` parameter for parity with other backends, but
|
|
12
|
+
* `LocalSandboxCreateOptions` is empty today — `just-bash` does not
|
|
13
|
+
* currently expose any configuration worth surfacing. New options would
|
|
14
|
+
* be added by widening `LocalSandboxCreateOptions` and routing them
|
|
15
|
+
* into the binding without changing this signature.
|
|
13
16
|
*/
|
|
14
|
-
export declare function localBackend(): SandboxBackend;
|
|
17
|
+
export declare function localBackend(opts?: LocalSandboxCreateOptions): SandboxBackend;
|
|
@@ -7,10 +7,12 @@ import { createLocalSandboxBackend } from "#execution/sandbox/bindings/local.js"
|
|
|
7
7
|
* under the application root. It is the default backend on developer
|
|
8
8
|
* machines (`pnpm ash dev`).
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Accepts an `opts` parameter for parity with other backends, but
|
|
11
|
+
* `LocalSandboxCreateOptions` is empty today — `just-bash` does not
|
|
12
|
+
* currently expose any configuration worth surfacing. New options would
|
|
13
|
+
* be added by widening `LocalSandboxCreateOptions` and routing them
|
|
14
|
+
* into the binding without changing this signature.
|
|
13
15
|
*/
|
|
14
|
-
export function localBackend() {
|
|
15
|
-
return createLocalSandboxBackend();
|
|
16
|
+
export function localBackend(opts) {
|
|
17
|
+
return createLocalSandboxBackend({ createOptions: opts });
|
|
16
18
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { SandboxBackend } from "#public/definitions/sandbox-backend.js";
|
|
2
|
-
import type { VercelSandboxBootstrapUseOptions, VercelSandboxSessionUseOptions } from "#public/sandbox/vercel-sandbox.js";
|
|
2
|
+
import type { VercelSandboxBootstrapUseOptions, VercelSandboxCreateOptions, VercelSandboxSessionUseOptions } from "#public/sandbox/vercel-sandbox.js";
|
|
3
3
|
/**
|
|
4
4
|
* Constructs the built-in Vercel sandbox backend.
|
|
5
5
|
*
|
|
6
|
+
* The optional `opts` parameter is forwarded to the Vercel SDK's
|
|
7
|
+
* `Sandbox.create(...)` for every fresh sandbox the framework creates
|
|
8
|
+
* (template at prewarm, session at first-time create). On resume
|
|
9
|
+
* (`Sandbox.get`), no create happens, so opts are not re-applied.
|
|
10
|
+
*
|
|
6
11
|
* `bootstrap({ use })` applies its options to the template via
|
|
7
12
|
* `sandbox.update(...)`; those settings persist into the snapshot.
|
|
8
13
|
* `onSession({ use })` applies its options to the live session via the
|
|
9
|
-
* SDK's `update` under the hood
|
|
14
|
+
* SDK's `update` under the hood — overriding any overlapping field
|
|
15
|
+
* from `opts`.
|
|
10
16
|
*/
|
|
11
|
-
export declare function vercelBackend(): SandboxBackend<VercelSandboxBootstrapUseOptions, VercelSandboxSessionUseOptions>;
|
|
17
|
+
export declare function vercelBackend(opts?: VercelSandboxCreateOptions): SandboxBackend<VercelSandboxBootstrapUseOptions, VercelSandboxSessionUseOptions>;
|
|
@@ -2,11 +2,17 @@ import { createVercelSandboxBackend } from "#execution/sandbox/bindings/vercel.j
|
|
|
2
2
|
/**
|
|
3
3
|
* Constructs the built-in Vercel sandbox backend.
|
|
4
4
|
*
|
|
5
|
+
* The optional `opts` parameter is forwarded to the Vercel SDK's
|
|
6
|
+
* `Sandbox.create(...)` for every fresh sandbox the framework creates
|
|
7
|
+
* (template at prewarm, session at first-time create). On resume
|
|
8
|
+
* (`Sandbox.get`), no create happens, so opts are not re-applied.
|
|
9
|
+
*
|
|
5
10
|
* `bootstrap({ use })` applies its options to the template via
|
|
6
11
|
* `sandbox.update(...)`; those settings persist into the snapshot.
|
|
7
12
|
* `onSession({ use })` applies its options to the live session via the
|
|
8
|
-
* SDK's `update` under the hood
|
|
13
|
+
* SDK's `update` under the hood — overriding any overlapping field
|
|
14
|
+
* from `opts`.
|
|
9
15
|
*/
|
|
10
|
-
export function vercelBackend() {
|
|
11
|
-
return createVercelSandboxBackend();
|
|
16
|
+
export function vercelBackend(opts) {
|
|
17
|
+
return createVercelSandboxBackend({ createOptions: opts });
|
|
12
18
|
}
|
|
@@ -9,4 +9,5 @@ export { SandboxTemplateNotProvisionedError } from "#public/definitions/sandbox-
|
|
|
9
9
|
export { defaultBackend } from "#public/sandbox/backends/default.js";
|
|
10
10
|
export { localBackend } from "#public/sandbox/backends/local.js";
|
|
11
11
|
export { vercelBackend } from "#public/sandbox/backends/vercel.js";
|
|
12
|
-
export type {
|
|
12
|
+
export type { LocalSandboxCreateOptions } from "#public/sandbox/local-sandbox.js";
|
|
13
|
+
export type { VercelSandboxBootstrapUseOptions, VercelSandboxCreateOptions, VercelSandboxSessionUseOptions, } from "#public/sandbox/vercel-sandbox.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options accepted by `localBackend(opts)`. Reserved for future
|
|
3
|
+
* widening: today the local backend exposes no consumer-controllable
|
|
4
|
+
* create options, so this is an empty object. The parameter exists on
|
|
5
|
+
* the factory so adding real fields here later is purely additive.
|
|
6
|
+
*/
|
|
7
|
+
export type LocalSandboxCreateOptions = Record<string, never>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
import type { SandboxUpdateParams } from "#compiled/@vercel/sandbox/index.js";
|
|
1
|
+
import type { Sandbox as SdkSandbox, SandboxUpdateParams } from "#compiled/@vercel/sandbox/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options accepted by `vercelBackend(opts)`. Forwarded directly to the
|
|
4
|
+
* Vercel SDK's `Sandbox.create(...)` for every fresh sandbox the
|
|
5
|
+
* framework creates (template at prewarm time, session at first-time
|
|
6
|
+
* session-create). Skipped on resume (`Sandbox.get`) since no create
|
|
7
|
+
* happens there.
|
|
8
|
+
*
|
|
9
|
+
* Framework-injected fields (`name`, `persistent`, `source`, `signal`)
|
|
10
|
+
* are excluded — the framework owns those and overrides any author-
|
|
11
|
+
* supplied values.
|
|
12
|
+
*/
|
|
13
|
+
export type VercelSandboxCreateOptions = Omit<NonNullable<Parameters<typeof SdkSandbox.create>[0]>, "name" | "persistent" | "source" | "signal">;
|
|
2
14
|
/**
|
|
3
15
|
* Options accepted by the Vercel backend's `bootstrap({ use })` hook.
|
|
4
16
|
* Aliases the Vercel SDK's `SandboxUpdateParams` because bootstrap
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { lazyBackend } from "#execution/sandbox/lazy-backend.js";
|
|
1
2
|
import { expectObjectRecord } from "#internal/authored-module.js";
|
|
2
3
|
import { defaultBackend } from "#public/sandbox/backends/default.js";
|
|
3
4
|
import { toErrorMessage } from "#shared/errors.js";
|
|
@@ -47,8 +48,11 @@ function resolveBackend(value, logicalPath) {
|
|
|
47
48
|
if (value === undefined) {
|
|
48
49
|
return defaultBackend();
|
|
49
50
|
}
|
|
51
|
+
if (typeof value === "function") {
|
|
52
|
+
return lazyBackend(value);
|
|
53
|
+
}
|
|
50
54
|
if (typeof value !== "object" || value === null) {
|
|
51
|
-
throw new ResolveAgentError(`Sandbox "${logicalPath}" exposed a non-object "backend" field. Use vercelBackend(), localBackend(),
|
|
55
|
+
throw new ResolveAgentError(`Sandbox "${logicalPath}" exposed a non-object "backend" field. Use vercelBackend(), localBackend(), another factory that returns a SandboxBackend value, or a zero-arg callback returning one.`, { logicalPath });
|
|
52
56
|
}
|
|
53
57
|
const record = value;
|
|
54
58
|
if (typeof record.name !== "string" || record.name.length === 0) {
|
|
@@ -15,6 +15,7 @@ import type { SourceRef, ModuleSourceRef, SkillPackageSourceRef, MarkdownSourceR
|
|
|
15
15
|
import type { InternalSkillDefinition } from "#shared/skill-definition.js";
|
|
16
16
|
import type { InternalAgentDefinition } from "#shared/agent-definition.js";
|
|
17
17
|
import type { InternalToolDefinitionWithExecuteFn } from "#shared/tool-definition.js";
|
|
18
|
+
import type { SandboxBackend } from "#shared/sandbox-backend.js";
|
|
18
19
|
import type { SandboxDefinition } from "#shared/sandbox-definition.js";
|
|
19
20
|
/**
|
|
20
21
|
* Runtime-owned source ref describing one additive config module import.
|
|
@@ -82,7 +83,15 @@ export interface ResolvedConnectionDefinition extends ResolvedModuleSourceRef {
|
|
|
82
83
|
* `vercelBackend()` and `localBackend()` based on the current
|
|
83
84
|
* environment).
|
|
84
85
|
*/
|
|
85
|
-
export type ResolvedSandboxDefinition = Readonly<SandboxDefinition
|
|
86
|
+
export type ResolvedSandboxDefinition = Readonly<Omit<SandboxDefinition, "backend">> & ResolvedModuleSourceRef & {
|
|
87
|
+
/**
|
|
88
|
+
* Resolved backend value. The authored `SandboxDefinition.backend`
|
|
89
|
+
* accepts either a `SandboxBackend` or a `() => SandboxBackend`; by
|
|
90
|
+
* the time it reaches the runtime the function form has been
|
|
91
|
+
* unwrapped via `lazyBackend(...)` so consumers always see a plain
|
|
92
|
+
* value.
|
|
93
|
+
*/
|
|
94
|
+
readonly backend: SandboxBackend;
|
|
86
95
|
readonly description?: string;
|
|
87
96
|
};
|
|
88
97
|
/**
|