experimental-ash 0.16.0 → 0.16.1
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 +6 -0
- package/dist/docs/public/agent-ts.md +6 -23
- package/dist/docs/public/context-control.md +2 -2
- package/dist/docs/public/evals.md +2 -15
- package/dist/docs/public/project-layout.md +1 -1
- package/dist/docs/public/skills.md +3 -4
- package/dist/docs/public/subagents.md +1 -1
- package/dist/docs/public/tools.md +0 -1
- package/dist/docs/public/typescript-api.md +4 -3
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/public/channels/slack/interactions.d.ts +0 -5
- package/dist/src/public/channels/slack/interactions.js +20 -11
- package/dist/src/public/channels/slack/slackChannel.d.ts +18 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# experimental-ash
|
|
2
2
|
|
|
3
|
+
## 0.16.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8cf749c: Expose the clicking actor as a `user` field on `SlackInteractionAction` so `onInteraction` handlers can attribute resolutions without re-parsing the raw payload.
|
|
8
|
+
|
|
3
9
|
## 0.16.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -33,11 +33,13 @@ Use `agent.ts` for:
|
|
|
33
33
|
|
|
34
34
|
- model selection
|
|
35
35
|
- metadata you want preserved in runtime traces
|
|
36
|
-
- human-in-the-loop policy
|
|
37
36
|
- hosted-build packaging controls
|
|
38
37
|
- compaction settings
|
|
39
38
|
- provider-specific model options
|
|
40
39
|
|
|
40
|
+
Per-tool approval (formerly "human-in-the-loop policy") lives on each tool via `needsApproval`,
|
|
41
|
+
not in `agent.ts`. See [Human In The Loop](./human-in-the-loop.md).
|
|
42
|
+
|
|
41
43
|
For OpenTelemetry configuration, use `instrumentation.ts` instead. See
|
|
42
44
|
[`instrumentation.ts`](./instrumentation.md).
|
|
43
45
|
|
|
@@ -130,29 +132,10 @@ server output instead of being bundled.
|
|
|
130
132
|
The shared per-run workspace is configured on the sandbox, not on `agent.ts`. See
|
|
131
133
|
[Workspace](./workspace.md) and [Sandboxes](./sandbox.md).
|
|
132
134
|
|
|
133
|
-
##
|
|
134
|
-
|
|
135
|
-
`humanInTheLoop` controls whether Ash should pause for approval before executing model-visible
|
|
136
|
-
tools.
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
export default defineAgent({
|
|
140
|
-
humanInTheLoop: {
|
|
141
|
-
mode: "require-approval",
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Supported fields:
|
|
147
|
-
|
|
148
|
-
- `mode: "require-approval" | "auto-allow"`
|
|
149
|
-
- `allow?: readonly string[]`
|
|
150
|
-
- `ask?: readonly string[]`
|
|
151
|
-
|
|
152
|
-
Use `require-approval` when the default should be "ask first". Use `auto-allow` when the default
|
|
153
|
-
should be "run immediately" and only a few named tools should still require approval.
|
|
135
|
+
## Human In The Loop
|
|
154
136
|
|
|
155
|
-
|
|
137
|
+
Approval policy lives on individual tools via `needsApproval` in `defineTool({...})`, not on
|
|
138
|
+
`agent.ts`. See [Human In The Loop](./human-in-the-loop.md) for the runtime flow, `input.requested`
|
|
156
139
|
events, and HTTP response payloads.
|
|
157
140
|
|
|
158
141
|
## Telemetry
|
|
@@ -66,7 +66,6 @@ Use the weather tool before answering forecast or temperature questions.
|
|
|
66
66
|
|
|
67
67
|
```md
|
|
68
68
|
---
|
|
69
|
-
name: research
|
|
70
69
|
description: Research unfamiliar topics before answering with confidence.
|
|
71
70
|
---
|
|
72
71
|
|
|
@@ -84,7 +83,8 @@ prompt.
|
|
|
84
83
|
### Skill Rules That Matter
|
|
85
84
|
|
|
86
85
|
- packaged `SKILL.md` files must include YAML frontmatter
|
|
87
|
-
- `
|
|
86
|
+
- `description` is required in packaged skill frontmatter; identity is derived from the directory
|
|
87
|
+
name
|
|
88
88
|
- flat markdown skills may omit frontmatter
|
|
89
89
|
- tools stay visible whether or not a skill is activated
|
|
90
90
|
|
|
@@ -39,9 +39,7 @@ import { defineEvalSuite } from "experimental-ash/evals";
|
|
|
39
39
|
import { Run } from "experimental-ash/evals/scores";
|
|
40
40
|
|
|
41
41
|
export default defineEvalSuite({
|
|
42
|
-
id: "weather-smoke",
|
|
43
42
|
model: "openai/gpt-5.4-mini",
|
|
44
|
-
name: "Weather Smoke",
|
|
45
43
|
description: "Basic message and tool-usage coverage for the weather agent.",
|
|
46
44
|
cases: [
|
|
47
45
|
{
|
|
@@ -55,13 +53,12 @@ export default defineEvalSuite({
|
|
|
55
53
|
```
|
|
56
54
|
|
|
57
55
|
`defineEvalSuite(...)` validates the suite shape and returns a normalized suite object that the CLI
|
|
58
|
-
can discover and run.
|
|
56
|
+
can discover and run. Suite identity is derived from the `.eval.ts` file path — authored
|
|
57
|
+
definitions do not carry an `id` or `name`.
|
|
59
58
|
|
|
60
59
|
Every suite must provide:
|
|
61
60
|
|
|
62
|
-
- `id`
|
|
63
61
|
- `model`
|
|
64
|
-
- `name`
|
|
65
62
|
- `scores`
|
|
66
63
|
- either `cases` or `load`
|
|
67
64
|
|
|
@@ -88,9 +85,7 @@ You can provide cases directly:
|
|
|
88
85
|
|
|
89
86
|
```ts
|
|
90
87
|
export default defineEvalSuite({
|
|
91
|
-
id: "smoke",
|
|
92
88
|
model: "openai/gpt-5.4-mini",
|
|
93
|
-
name: "Smoke",
|
|
94
89
|
cases: [
|
|
95
90
|
{
|
|
96
91
|
id: "hello",
|
|
@@ -110,9 +105,7 @@ import { loadYaml } from "experimental-ash/evals/loaders";
|
|
|
110
105
|
import { Text } from "experimental-ash/evals/scores";
|
|
111
106
|
|
|
112
107
|
export default defineEvalSuite({
|
|
113
|
-
id: "marketing-sql",
|
|
114
108
|
model: "openai/gpt-5.4-mini",
|
|
115
|
-
name: "Marketing SQL",
|
|
116
109
|
async load() {
|
|
117
110
|
const doc = await loadYaml("evals/data/cases.yaml");
|
|
118
111
|
return (doc.evals as readonly { task: string; prompt: string; sql: string }[]).map((row) => ({
|
|
@@ -131,9 +124,7 @@ message.
|
|
|
131
124
|
|
|
132
125
|
```ts
|
|
133
126
|
defineEvalSuite({
|
|
134
|
-
id: "weather-task",
|
|
135
127
|
model: "openai/gpt-5.4-mini",
|
|
136
|
-
name: "Weather Task",
|
|
137
128
|
task: {
|
|
138
129
|
prompt: (testCase) => `Answer the user: ${String(testCase.input)}`,
|
|
139
130
|
parseOutput: (result) => result.finalMessage,
|
|
@@ -149,9 +140,7 @@ Suite thresholds let you relax a scorer from the default exact-match requirement
|
|
|
149
140
|
|
|
150
141
|
```ts
|
|
151
142
|
defineEvalSuite({
|
|
152
|
-
id: "weather",
|
|
153
143
|
model: "openai/gpt-5.4-mini",
|
|
154
|
-
name: "Weather",
|
|
155
144
|
cases: [{ id: "hello", input: "Hello", expected: "Hello" }],
|
|
156
145
|
scores: [Run.didNotFail(), Text.includes()],
|
|
157
146
|
thresholds: {
|
|
@@ -180,8 +169,6 @@ import { defineEvalSuite } from "experimental-ash/evals";
|
|
|
180
169
|
import { Autoevals, Run } from "experimental-ash/evals/scores";
|
|
181
170
|
|
|
182
171
|
export default defineEvalSuite({
|
|
183
|
-
id: "support-quality",
|
|
184
|
-
name: "Support Quality",
|
|
185
172
|
model: "openai/gpt-5.4-mini",
|
|
186
173
|
cases: [{ id: "refund", input: "How do I get a refund?", expected: "refund policy" }],
|
|
187
174
|
scores: [Run.didNotFail(), Autoevals.factuality()],
|
|
@@ -34,7 +34,7 @@ my-agent/
|
|
|
34
34
|
| Path | Purpose | Notes |
|
|
35
35
|
| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
36
|
| `instructions.md` or `instructions.ts` | Base instructions prompt | Exactly one instructions prompt is expected. Module-backed sources execute once at build time; the resulting markdown is baked into the compiled manifest. The legacy `system.{md,ts,...}` slot is still discovered with a deprecation warning. |
|
|
37
|
-
| `agent.ts` | Runtime config | Model, metadata,
|
|
37
|
+
| `agent.ts` | Runtime config | Model, metadata, build, compaction, modelOptions |
|
|
38
38
|
| `instrumentation.ts` | Telemetry config | OTel exporter setup and AI SDK span settings; auto-discovered and run before agent code |
|
|
39
39
|
| `channels/` | HTTP or messaging entrypoints | Root-only today |
|
|
40
40
|
| `connections/` | External service connections (MCP) | Each file defines one connection; name derived from filename |
|
|
@@ -45,7 +45,7 @@ The smallest skill is just a markdown file.
|
|
|
45
45
|
Use the weather tool before answering forecast or temperature questions.
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
For flat skills, Ash derives the skill id
|
|
48
|
+
For flat skills, Ash derives the skill id from the file path.
|
|
49
49
|
|
|
50
50
|
You can also add frontmatter when you want to control the description explicitly:
|
|
51
51
|
|
|
@@ -63,7 +63,6 @@ Use a packaged skill when the procedure needs sibling files.
|
|
|
63
63
|
|
|
64
64
|
```md
|
|
65
65
|
---
|
|
66
|
-
name: research
|
|
67
66
|
description: Research unfamiliar topics before answering with confidence.
|
|
68
67
|
---
|
|
69
68
|
|
|
@@ -99,7 +98,8 @@ export default defineTool({
|
|
|
99
98
|
|
|
100
99
|
- flat skills can be simple markdown files under `skills/*.md`
|
|
101
100
|
- packaged skills live under `skills/<name>/SKILL.md`
|
|
102
|
-
- packaged `SKILL.md` files must include `
|
|
101
|
+
- packaged `SKILL.md` files must include `description` frontmatter; skill identity is derived from
|
|
102
|
+
the directory name
|
|
103
103
|
- flat skills may omit frontmatter
|
|
104
104
|
|
|
105
105
|
## Write Good Descriptions
|
|
@@ -145,7 +145,6 @@ If markdown is not enough, you can author a skill in TypeScript with `defineSkil
|
|
|
145
145
|
import { defineSkill } from "experimental-ash/skills";
|
|
146
146
|
|
|
147
147
|
export default defineSkill({
|
|
148
|
-
name: "research",
|
|
149
148
|
description: "Research unfamiliar topics before answering with confidence.",
|
|
150
149
|
markdown:
|
|
151
150
|
"When the task is novel or ambiguous, gather evidence first, then answer with the key facts and the remaining uncertainty.",
|
|
@@ -263,7 +263,6 @@ unbounded text, cap it in the executor before returning:
|
|
|
263
263
|
|
|
264
264
|
```ts
|
|
265
265
|
import { defineTool } from "experimental-ash/tools";
|
|
266
|
-
import { truncateHead } from "experimental-ash/sandbox/truncate-output";
|
|
267
266
|
import { z } from "zod";
|
|
268
267
|
|
|
269
268
|
export default defineTool({
|
|
@@ -20,8 +20,9 @@ Source of truth:
|
|
|
20
20
|
Use these to author the filesystem-backed surface. Each concern has its own subpath:
|
|
21
21
|
`experimental-ash/tools` for tools, `experimental-ash/hooks` for hooks,
|
|
22
22
|
`experimental-ash/sandbox` for the sandbox, `experimental-ash/skills` for skills,
|
|
23
|
-
`experimental-ash/
|
|
24
|
-
|
|
23
|
+
`experimental-ash/schedules` for schedules, `experimental-ash/instructions` for the instructions
|
|
24
|
+
prompt, `experimental-ash/context` for context helpers, and the main `experimental-ash` barrel
|
|
25
|
+
for `defineAgent`.
|
|
25
26
|
|
|
26
27
|
- `defineAgent(...)` - additive runtime config for `agent.ts`. Subagents reuse the same helper at
|
|
27
28
|
`subagents/<id>/agent.ts`; the only difference is that subagents must declare a `description` so
|
|
@@ -41,7 +42,7 @@ barrel for agent config, schedules, and systems.
|
|
|
41
42
|
|
|
42
43
|
Framework defaults reachable for spread/wrap composition:
|
|
43
44
|
|
|
44
|
-
- `bash`, `readFile`, `writeFile`, `webFetch`, `todo`, `loadSkill` (`experimental-ash/tools/defaults`)
|
|
45
|
+
- `bash`, `glob`, `grep`, `readFile`, `writeFile`, `webFetch`, `webSearch`, `todo`, `loadSkill` (`experimental-ash/tools/defaults`)
|
|
45
46
|
|
|
46
47
|
Most apps use `defineAgent`, `defineTool`, and `defineSandbox` the most.
|
|
47
48
|
|
|
@@ -6,7 +6,7 @@ import { ASH_PACKAGE_NAME } from "#package-name.js";
|
|
|
6
6
|
let cachedPackageInfo;
|
|
7
7
|
// The package build stamps the published version into `dist` so bundled
|
|
8
8
|
// deployments can still report package metadata without resolving package.json.
|
|
9
|
-
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.16.
|
|
9
|
+
const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.16.1";
|
|
10
10
|
const BUNDLED_FALLBACK_PACKAGE_VERSION_PLACEHOLDER = "__ASH_PACKAGE_VERSION_PLACEHOLDER__";
|
|
11
11
|
const WORKFLOW_MODULE_ALIASES = {
|
|
12
12
|
"workflow/api": "src/compiled/@workflow/core/runtime.js",
|
|
@@ -27,11 +27,6 @@ interface ParsedBlockActionsPayload {
|
|
|
27
27
|
readonly channelId: string;
|
|
28
28
|
readonly threadTs: string;
|
|
29
29
|
readonly teamId: string | undefined;
|
|
30
|
-
/**
|
|
31
|
-
* Slack actor that authored the click. Used to attribute the answered
|
|
32
|
-
* card via `Answered by <@userId>` after a HITL response is delivered.
|
|
33
|
-
*/
|
|
34
|
-
readonly userId: string | undefined;
|
|
35
30
|
/**
|
|
36
31
|
* The full block list off the clicked message. Preserved on the
|
|
37
32
|
* answered-card update so the original prompt stays visible after the
|
|
@@ -29,30 +29,37 @@ export function parseBlockActionsPayload(body) {
|
|
|
29
29
|
const actions = body.actions;
|
|
30
30
|
if (!Array.isArray(actions))
|
|
31
31
|
return null;
|
|
32
|
+
// `channel` and `message` are Optional on block_actions payloads — only
|
|
33
|
+
// present when the action was triggered from a message in a channel.
|
|
32
34
|
const channel = body.channel?.id;
|
|
33
35
|
const message = body.message;
|
|
34
36
|
const threadTs = message?.thread_ts ?? message?.ts;
|
|
35
37
|
if (!channel || !threadTs)
|
|
36
38
|
return null;
|
|
39
|
+
// `team` is Required but can be `null` for org-installed apps.
|
|
40
|
+
// `user` is Required and always carries `id`.
|
|
37
41
|
const team = body.team;
|
|
38
|
-
const
|
|
39
|
-
const teamId = team?.id ??
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const userBlock = body.user;
|
|
43
|
+
const teamId = team?.id ?? userBlock.team_id;
|
|
44
|
+
const user = {
|
|
45
|
+
id: userBlock.id,
|
|
46
|
+
username: userBlock.username,
|
|
47
|
+
name: userBlock.name,
|
|
48
|
+
};
|
|
49
|
+
const messageBlocks = message?.blocks ?? [];
|
|
43
50
|
return {
|
|
44
51
|
actions: actions.map((a) => ({
|
|
45
52
|
actionId: String(a.action_id ?? ""),
|
|
46
53
|
value: a.value != null ? String(a.value) : undefined,
|
|
47
54
|
blockId: a.block_id != null ? String(a.block_id) : undefined,
|
|
48
55
|
selectedOptionValue: extractSelectedOptionValue(a),
|
|
49
|
-
messageTs,
|
|
56
|
+
messageTs: message?.ts,
|
|
50
57
|
label: extractActionLabel(a),
|
|
58
|
+
user,
|
|
51
59
|
})),
|
|
52
60
|
channelId: channel,
|
|
53
61
|
threadTs,
|
|
54
62
|
teamId,
|
|
55
|
-
userId,
|
|
56
63
|
messageBlocks,
|
|
57
64
|
};
|
|
58
65
|
}
|
|
@@ -130,7 +137,7 @@ export async function handleInteractionPost(rawBody, ctx, deps) {
|
|
|
130
137
|
channelId: interaction.channelId,
|
|
131
138
|
threadTs: interaction.threadTs,
|
|
132
139
|
teamId: interaction.teamId ?? null,
|
|
133
|
-
triggeringUserId: interaction.
|
|
140
|
+
triggeringUserId: interaction.actions[0]?.user.id ?? null,
|
|
134
141
|
},
|
|
135
142
|
})
|
|
136
143
|
.catch((error) => {
|
|
@@ -222,9 +229,11 @@ async function handleViewSubmission(payload, ctx, _deps) {
|
|
|
222
229
|
const text = typeof raw === "string" ? raw : "";
|
|
223
230
|
if (text.length === 0)
|
|
224
231
|
return ack;
|
|
232
|
+
// `user` is Required on view_submission payloads; `team_id` is on the
|
|
233
|
+
// user object in modern payloads but not guaranteed in all examples.
|
|
225
234
|
const user = payload.user;
|
|
226
|
-
const triggeringUserId =
|
|
227
|
-
const teamId =
|
|
235
|
+
const triggeringUserId = user.id;
|
|
236
|
+
const teamId = user.team_id ?? null;
|
|
228
237
|
ctx.waitUntil(ctx
|
|
229
238
|
.send({ inputResponses: [{ requestId: metadata.requestId, text }] }, {
|
|
230
239
|
auth: null,
|
|
@@ -260,7 +269,7 @@ async function updateAnsweredHitlCard(interaction, deps) {
|
|
|
260
269
|
const blocks = buildAnsweredBlocks({
|
|
261
270
|
promptBlock: findPromptBlock(interaction.messageBlocks),
|
|
262
271
|
answerLabel,
|
|
263
|
-
userId:
|
|
272
|
+
userId: hitlAction.user.id,
|
|
264
273
|
});
|
|
265
274
|
const token = await resolveSlackBotToken(deps.config.credentials?.botToken);
|
|
266
275
|
const response = await fetch("https://slack.com/api/chat.update", {
|
|
@@ -117,6 +117,24 @@ export interface SlackInteractionAction {
|
|
|
117
117
|
* the "answered" card without re-fetching the original request.
|
|
118
118
|
*/
|
|
119
119
|
readonly label?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Slack actor who triggered the interaction. Lets `onInteraction`
|
|
122
|
+
* handlers attribute resolutions back to the clicker (e.g. "Filed by
|
|
123
|
+
* <@U0123456789>") without re-parsing the raw payload. Always present
|
|
124
|
+
* — Slack requires `user` on every `block_actions` payload.
|
|
125
|
+
*/
|
|
126
|
+
readonly user: SlackInteractionUser;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Slack actor surfaced on {@link SlackInteractionAction.user}. Mirrors
|
|
130
|
+
* `body.user` from the inbound `block_actions` payload.
|
|
131
|
+
*/
|
|
132
|
+
export interface SlackInteractionUser {
|
|
133
|
+
readonly id: string;
|
|
134
|
+
/** Modern canonical display handle. */
|
|
135
|
+
readonly username?: string;
|
|
136
|
+
/** Legacy display handle, kept for older workspaces. */
|
|
137
|
+
readonly name?: string;
|
|
120
138
|
}
|
|
121
139
|
/**
|
|
122
140
|
* Result of an `onAppMention` or `onDirectMessage` callback. Return
|