@flue/sdk 0.2.0 → 0.3.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/README.md +155 -20
- package/dist/{agent-BYG0nVbQ.mjs → agent-BB4lwAd5.mjs} +24 -3
- package/dist/client.d.mts +4 -3
- package/dist/client.mjs +45 -26
- package/dist/cloudflare/index.d.mts +4 -9
- package/dist/cloudflare/index.mjs +32 -21
- package/dist/{command-helpers-C8SHLdaA.d.mts → command-helpers-DdAfbnom.d.mts} +1 -1
- package/dist/index.d.mts +48 -5
- package/dist/index.mjs +746 -179
- package/dist/internal.d.mts +17 -3
- package/dist/internal.mjs +37 -4
- package/dist/mcp-BVF-sOBZ.d.mts +22 -0
- package/dist/mcp-DOgMtp8y.mjs +285 -0
- package/dist/node/index.d.mts +2 -2
- package/dist/node/index.mjs +1 -1
- package/dist/sandbox.d.mts +4 -3
- package/dist/sandbox.mjs +58 -28
- package/dist/{session-CiAMTsLZ.mjs → session-DukL3zwF.mjs} +629 -269
- package/dist/{types-C0nqbu6Z.d.mts → types-T8pE1xIS.d.mts} +156 -53
- package/package.json +12 -4
- /package/dist/{command-helpers-CxRhK1my.mjs → command-helpers-hTZKWK13.mjs} +0 -0
package/README.md
CHANGED
|
@@ -35,11 +35,10 @@ import * as v from 'valibot';
|
|
|
35
35
|
export const triggers = { webhook: true };
|
|
36
36
|
|
|
37
37
|
// The agent handler. Where the orchestration of the agent lives.
|
|
38
|
-
export default async function ({ init, payload
|
|
39
|
-
// `
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const session = await init();
|
|
38
|
+
export default async function ({ init, payload }: FlueContext) {
|
|
39
|
+
// `agent` -- Your initialized agent runtime including sandbox, tools, skills, etc.
|
|
40
|
+
const agent = await init({ model: 'anthropic/claude-sonnet-4-6' });
|
|
41
|
+
const session = await agent.session();
|
|
43
42
|
|
|
44
43
|
// prompt() sends a message in the session, triggering action.
|
|
45
44
|
const result = await session.prompt(`Translate this to ${payload.language}: "${payload.text}"`, {
|
|
@@ -73,7 +72,8 @@ export default async function ({ init, payload, env }: FlueContext) {
|
|
|
73
72
|
// The agent can grep, glob, and read articles with bash, but
|
|
74
73
|
// without needing to spin up an entire container sandbox.
|
|
75
74
|
const sandbox = await getVirtualSandbox(env.KNOWLEDGE_BASE);
|
|
76
|
-
const
|
|
75
|
+
const agent = await init({ sandbox, model: 'openrouter/moonshotai/kimi-k2.6' });
|
|
76
|
+
const session = await agent.session();
|
|
77
77
|
|
|
78
78
|
return await session.prompt(
|
|
79
79
|
`You are a support agent. Search the knowledge base for articles
|
|
@@ -114,11 +114,12 @@ export default async function ({ init, payload }: FlueContext) {
|
|
|
114
114
|
// discovered automatically from the workspace directory.
|
|
115
115
|
//
|
|
116
116
|
// `model` sets the default model for every prompt/skill call in this
|
|
117
|
-
//
|
|
118
|
-
const
|
|
117
|
+
// agent. Override per-call with `{ model: '...' }` on prompt()/skill().
|
|
118
|
+
const agent = await init({
|
|
119
119
|
sandbox: 'local',
|
|
120
|
-
model: 'anthropic/claude-opus-4-
|
|
120
|
+
model: 'anthropic/claude-opus-4-7',
|
|
121
121
|
});
|
|
122
|
+
const session = await agent.session();
|
|
122
123
|
|
|
123
124
|
// Skills can be referenced either by their frontmatter `name:` (shown below)
|
|
124
125
|
// or by a relative path under `.agents/skills/` — e.g.
|
|
@@ -158,23 +159,35 @@ import { daytona } from '@flue/connectors/daytona';
|
|
|
158
159
|
export const triggers = { webhook: true };
|
|
159
160
|
|
|
160
161
|
export default async function ({ init, payload, env }: FlueContext) {
|
|
161
|
-
// Each
|
|
162
|
+
// Each agent gets a real container via Daytona. The container has
|
|
162
163
|
// a full Linux environment with persistent filesystem and shell.
|
|
163
164
|
//
|
|
164
165
|
// For simplicity, we always create a new sandbox here. You could also
|
|
165
|
-
// first check for an existing sandbox for the
|
|
166
|
+
// first check for an existing sandbox for the agent id, and reuse that
|
|
166
167
|
// instead to best pick up where you last left off in the conversation.
|
|
167
168
|
const client = new Daytona({ apiKey: env.DAYTONA_API_KEY });
|
|
168
169
|
const sandbox = await client.create();
|
|
169
|
-
const
|
|
170
|
+
const setupAgent = await init({
|
|
170
171
|
sandbox: daytona(sandbox, { cleanup: true }),
|
|
172
|
+
model: 'openai/gpt-5.5',
|
|
171
173
|
});
|
|
174
|
+
const setup = await setupAgent.session();
|
|
172
175
|
|
|
173
176
|
// For simplicity, we clone the target repo into the sandbox here.
|
|
174
177
|
// You could also bake these into the container image snapshot for a
|
|
175
178
|
// faster / near-instant startup.
|
|
176
|
-
await
|
|
177
|
-
await
|
|
179
|
+
await setup.shell(`git clone ${payload.repo} /workspace/project`);
|
|
180
|
+
await setup.shell('npm install', { cwd: '/workspace/project' });
|
|
181
|
+
|
|
182
|
+
// Start a second agent in the cloned repo. It shares the same sandbox, but
|
|
183
|
+
// discovers AGENTS.md and skills from /workspace/project.
|
|
184
|
+
const projectAgent = await init({
|
|
185
|
+
id: 'project',
|
|
186
|
+
sandbox: daytona(sandbox),
|
|
187
|
+
cwd: '/workspace/project',
|
|
188
|
+
model: 'openai/gpt-5.5',
|
|
189
|
+
});
|
|
190
|
+
const session = await projectAgent.session();
|
|
178
191
|
|
|
179
192
|
// Coding agents don't hide the agent DX from the user, so no need to
|
|
180
193
|
// wrap the user's prompt in anything. Just send it to the agent directly
|
|
@@ -183,24 +196,146 @@ export default async function ({ init, payload, env }: FlueContext) {
|
|
|
183
196
|
}
|
|
184
197
|
```
|
|
185
198
|
|
|
199
|
+
### Remote MCP Tools
|
|
200
|
+
|
|
201
|
+
MCP is available as a runtime tool adapter. Connect to a remote MCP server in trusted code, pass its tools to `init()`, and keep secrets in `env` instead of filesystem context or prompts.
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
// .flue/agents/assistant.ts
|
|
205
|
+
import { connectMcpServer, type FlueContext } from '@flue/sdk/client';
|
|
206
|
+
|
|
207
|
+
export const triggers = { webhook: true };
|
|
208
|
+
|
|
209
|
+
export default async function ({ init, payload, env }: FlueContext) {
|
|
210
|
+
const github = await connectMcpServer('github', {
|
|
211
|
+
url: 'https://mcp.github.com/mcp',
|
|
212
|
+
headers: {
|
|
213
|
+
Authorization: `Bearer ${env.GITHUB_TOKEN}`,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const agent = await init({
|
|
219
|
+
model: 'anthropic/claude-sonnet-4-6',
|
|
220
|
+
tools: github.tools,
|
|
221
|
+
});
|
|
222
|
+
const session = await agent.session();
|
|
223
|
+
return await session.prompt(payload.prompt);
|
|
224
|
+
} finally {
|
|
225
|
+
await github.close();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`connectMcpServer()` defaults to modern streamable HTTP. For legacy SSE servers, pass `transport: 'sse'`. Flue does not auto-detect transports, spawn local stdio MCP servers, or handle OAuth callbacks in this first version.
|
|
231
|
+
|
|
232
|
+
## Agents And Sessions
|
|
233
|
+
|
|
234
|
+
Every agent invocation runs inside an initialized agent runtime. For HTTP agents, the agent ID is the last path segment:
|
|
235
|
+
|
|
236
|
+
```txt
|
|
237
|
+
POST /agents/<agent-name>/<id>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
By default, `agent.session()` opens the default session for that agent ID. Reuse the same agent ID to continue the same default conversation. Use a new agent ID to start fresh.
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Start a conversation (port 3583 is `flue dev`'s default)
|
|
244
|
+
curl http://localhost:3583/agents/hello/session-abc \
|
|
245
|
+
-H "Content-Type: application/json" \
|
|
246
|
+
-d '{"name": "Alice"}'
|
|
247
|
+
|
|
248
|
+
# Continue that conversation
|
|
249
|
+
curl http://localhost:3583/agents/hello/session-abc \
|
|
250
|
+
-H "Content-Type: application/json" \
|
|
251
|
+
-d '{"name": "Alice"}'
|
|
252
|
+
|
|
253
|
+
# Start a separate conversation
|
|
254
|
+
curl http://localhost:3583/agents/hello/session-xyz \
|
|
255
|
+
-H "Content-Type: application/json" \
|
|
256
|
+
-d '{"name": "Alice"}'
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Agents own sandbox state such as files written during a run. Sessions persist message history and conversation metadata inside an agent. On Cloudflare, session data is backed by Durable Objects and survives across requests. On Node.js, sessions are stored in memory by default unless you provide a custom store.
|
|
260
|
+
|
|
261
|
+
In production, generate a stable agent ID for the sandbox/runtime scope you want to preserve. Use `agent.session(threadId)` when you need multiple conversations inside the same agent.
|
|
262
|
+
|
|
263
|
+
### Tasks
|
|
264
|
+
|
|
265
|
+
Use `session.task()` to run a focused, one-shot child agent in a detached session. Tasks share the same sandbox/filesystem, but get their own message history and discover `AGENTS.md` plus `.agents/skills/` from their working directory. The same `task` tool is also available to the LLM during `prompt()` and `skill()` calls, so the agent can delegate parallel research or exploration work itself.
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
const session = await agent.session();
|
|
269
|
+
|
|
270
|
+
const research = await session.task('Research the auth flow and summarize the key files.', {
|
|
271
|
+
cwd: '/workspace/project',
|
|
272
|
+
role: 'researcher',
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const answer = await session.prompt(
|
|
276
|
+
`Use this research to draft the implementation plan:\n\n${research.text}`,
|
|
277
|
+
);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Roles can be set at the agent, session, or call level. Precedence is `call role > session role > agent role`. Role instructions are applied as call-scoped system prompt overlays, not injected into the persisted user message history.
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
const agent = await init({ model: 'anthropic/claude-sonnet-4-6', role: 'coder' });
|
|
284
|
+
const session = await agent.session('review-thread', { role: 'reviewer' });
|
|
285
|
+
|
|
286
|
+
await session.prompt('Review the latest changes.'); // uses reviewer
|
|
287
|
+
await session.task('Research related issues.', { role: 'researcher' }); // uses researcher
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Custom Virtual Sandboxes
|
|
291
|
+
|
|
292
|
+
For most agents, use the built-in virtual sandbox or `sandbox: 'local'`. If you need to customize just-bash directly, pass a Bash factory. The factory must return a fresh Bash-like runtime each time; share the filesystem object in the closure to persist files across sessions and prompts.
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import { Bash, InMemoryFs } from 'just-bash';
|
|
296
|
+
|
|
297
|
+
const fs = new InMemoryFs();
|
|
298
|
+
|
|
299
|
+
const agent = await init({
|
|
300
|
+
sandbox: () => new Bash({ fs, cwd: '/workspace', python: true }),
|
|
301
|
+
model: 'anthropic/claude-sonnet-4-6',
|
|
302
|
+
});
|
|
303
|
+
const session = await agent.session();
|
|
304
|
+
```
|
|
305
|
+
|
|
186
306
|
## Running Agents
|
|
187
307
|
|
|
188
|
-
###
|
|
308
|
+
### Local Development (`flue dev`)
|
|
189
309
|
|
|
190
|
-
|
|
310
|
+
Long-running watch-mode dev server. Rebuilds and reloads on file changes — edit an agent, re-run `curl`, see your change.
|
|
191
311
|
|
|
192
312
|
```bash
|
|
193
|
-
flue
|
|
313
|
+
flue dev --target node # Node.js dev server
|
|
314
|
+
flue dev --target cloudflare # Cloudflare Workers (via wrangler) dev server
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Defaults to port `3583` ("FLUE" on a phone keypad). Override with `--port`.
|
|
318
|
+
|
|
319
|
+
`flue dev --target cloudflare` requires `wrangler` as a peer dependency in your project (`npm install --save-dev wrangler`).
|
|
320
|
+
|
|
321
|
+
### Trigger From the CLI (`flue run`)
|
|
322
|
+
|
|
323
|
+
Build and run any agent locally, perfect for running in CI or for one-shot scripted invocations. Production-shaped — builds the deployable artifact and starts it once.
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
flue run hello --target node --id test-1 \
|
|
194
327
|
--payload '{"text": "Hello world", "language": "French"}'
|
|
195
328
|
```
|
|
196
329
|
|
|
197
|
-
### Trigger From HTTP Endpoint
|
|
330
|
+
### Trigger From HTTP Endpoint (`flue build`)
|
|
198
331
|
|
|
199
332
|
Build and deploy your agents as a web server, perfect for hosted agents.
|
|
200
333
|
|
|
201
|
-
`flue build` builds to a `./dist` directory, which you can then deploy
|
|
334
|
+
`flue build` builds to a `./dist` directory, which you can then deploy. Cloudflare and any Node.js host are supported today, with more coming in the future.
|
|
202
335
|
|
|
203
336
|
```
|
|
204
|
-
flue build --target node # Node.js server
|
|
337
|
+
flue build --target node # Node.js server (single bundled .mjs)
|
|
205
338
|
flue build --target cloudflare # Cloudflare Workers + Durable Objects
|
|
206
339
|
```
|
|
340
|
+
|
|
341
|
+
For Cloudflare, `flue build` produces an unbundled TypeScript entry that `wrangler deploy` bundles itself — the same path `flue dev --target cloudflare` uses. Dev and deploy go through the same bundler, so what works in dev will work in production.
|
|
@@ -137,10 +137,11 @@ const BUILTIN_TOOL_NAMES = new Set([
|
|
|
137
137
|
"edit",
|
|
138
138
|
"bash",
|
|
139
139
|
"grep",
|
|
140
|
-
"glob"
|
|
140
|
+
"glob",
|
|
141
|
+
"task"
|
|
141
142
|
]);
|
|
142
|
-
function createTools(env) {
|
|
143
|
-
|
|
143
|
+
function createTools(env, options) {
|
|
144
|
+
const tools = [
|
|
144
145
|
createReadTool(env),
|
|
145
146
|
createWriteTool(env),
|
|
146
147
|
createEditTool(env),
|
|
@@ -148,6 +149,8 @@ function createTools(env) {
|
|
|
148
149
|
createGrepTool(env),
|
|
149
150
|
createGlobTool(env)
|
|
150
151
|
];
|
|
152
|
+
if (options?.task) tools.push(createTaskTool(options.task, options.roles ?? {}));
|
|
153
|
+
return tools;
|
|
151
154
|
}
|
|
152
155
|
function createReadTool(env) {
|
|
153
156
|
return {
|
|
@@ -288,6 +291,24 @@ function createBashTool(env) {
|
|
|
288
291
|
}
|
|
289
292
|
};
|
|
290
293
|
}
|
|
294
|
+
function createTaskTool(runTask, roles) {
|
|
295
|
+
const roleNames = Object.keys(roles);
|
|
296
|
+
return {
|
|
297
|
+
name: "task",
|
|
298
|
+
label: "Run Task",
|
|
299
|
+
description: "Delegate a focused task to a detached child agent with its own context. Use this for independent research, file exploration, or parallel work. The task returns only its final answer to this conversation." + (roleNames.length > 0 ? ` Available roles: ${roleNames.join(", ")}.` : " No roles are currently defined."),
|
|
300
|
+
parameters: Type.Object({
|
|
301
|
+
description: Type.Optional(Type.String({ description: "Short human-readable label for the delegated work" })),
|
|
302
|
+
prompt: Type.String({ description: "Focused instructions for the child agent" }),
|
|
303
|
+
role: Type.Optional(Type.String({ description: "Role to use for the child agent" })),
|
|
304
|
+
cwd: Type.Optional(Type.String({ description: "Working directory for the child agent. AGENTS.md and skills are discovered from here." }))
|
|
305
|
+
}),
|
|
306
|
+
async execute(_toolCallId, params, signal) {
|
|
307
|
+
throwIfAborted(signal);
|
|
308
|
+
return runTask(params, signal);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
291
312
|
function formatBashResult(result, command) {
|
|
292
313
|
const { text: output } = truncateTail((result.stdout + (result.stderr ? "\n" + result.stderr : "")).trim(), MAX_READ_LINES, MAX_READ_BYTES);
|
|
293
314
|
if (result.exitCode !== 0) throw new Error(`${output}\n\nCommand exited with code ${result.exitCode}`);
|
package/dist/client.d.mts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { A as TaskOptions, C as SessionEnv, D as ShellResult, E as ShellOptions, M as ToolParameters, S as SessionData, T as SessionStore, _ as FlueSessions, a as BashLike, d as FileStat, f as FlueAgent, g as FlueSession, h as FlueEventCallback, i as BashFactory, j as ToolDef, k as SkillOptions, l as Command, m as FlueEvent, p as FlueContext, r as AgentInit, t as AgentConfig, v as PromptOptions, w as SessionOptions, x as SandboxFactory, y as PromptResponse } from "./types-T8pE1xIS.mjs";
|
|
2
|
+
import { i as connectMcpServer, n as McpServerOptions, r as McpTransport, t as McpServerConnection } from "./mcp-BVF-sOBZ.mjs";
|
|
2
3
|
import { Type } from "@mariozechner/pi-ai";
|
|
3
4
|
|
|
4
5
|
//#region src/client.d.ts
|
|
5
6
|
interface FlueContextConfig {
|
|
6
|
-
|
|
7
|
+
id: string;
|
|
7
8
|
payload: any;
|
|
8
9
|
env: Record<string, any>;
|
|
9
10
|
agentConfig: AgentConfig;
|
|
@@ -22,4 +23,4 @@ interface FlueContextInternal extends FlueContext {
|
|
|
22
23
|
}
|
|
23
24
|
declare function createFlueContext(config: FlueContextConfig): FlueContextInternal;
|
|
24
25
|
//#endregion
|
|
25
|
-
export { type BashLike, type Command, type
|
|
26
|
+
export { type AgentInit, type BashFactory, type BashLike, type Command, type FileStat, type FlueAgent, type FlueContext, FlueContextConfig, FlueContextInternal, type FlueEvent, type FlueEventCallback, type FlueSession, type FlueSessions, type McpServerConnection, type McpServerOptions, type McpTransport, type PromptOptions, type PromptResponse, type SandboxFactory, type SessionData, type SessionEnv, type SessionOptions, type SessionStore, type ShellOptions, type ShellResult, type SkillOptions, type TaskOptions, type ToolDef, type ToolParameters, Type, connectMcpServer, createFlueContext };
|
package/dist/client.mjs
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { r as discoverSessionContext } from "./agent-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { r as discoverSessionContext } from "./agent-BB4lwAd5.mjs";
|
|
2
|
+
import { a as assertRoleExists } from "./session-DukL3zwF.mjs";
|
|
3
|
+
import { bashFactoryToSessionEnv, createCwdSessionEnv } from "./sandbox.mjs";
|
|
4
|
+
import { n as AgentClient, t as connectMcpServer } from "./mcp-DOgMtp8y.mjs";
|
|
4
5
|
import { Type } from "@mariozechner/pi-ai";
|
|
5
6
|
|
|
6
7
|
//#region src/client.ts
|
|
7
8
|
function createFlueContext(config) {
|
|
8
|
-
let initialized = false;
|
|
9
9
|
let currentEventCallback;
|
|
10
|
+
const initializedAgentIds = /* @__PURE__ */ new Set();
|
|
10
11
|
return {
|
|
11
|
-
get
|
|
12
|
-
return config.
|
|
12
|
+
get id() {
|
|
13
|
+
return config.id;
|
|
13
14
|
},
|
|
14
15
|
get payload() {
|
|
15
16
|
return config.payload;
|
|
@@ -18,21 +19,28 @@ function createFlueContext(config) {
|
|
|
18
19
|
return config.env;
|
|
19
20
|
},
|
|
20
21
|
async init(options) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
const id = options?.id ?? config.id;
|
|
23
|
+
if (initializedAgentIds.has(id)) throw new Error(`[flue] init() has already been called for agent "${id}" in this request.`);
|
|
24
|
+
initializedAgentIds.add(id);
|
|
25
|
+
try {
|
|
26
|
+
assertRoleExists(config.agentConfig.roles, options?.role);
|
|
27
|
+
const sandbox = options?.sandbox;
|
|
28
|
+
const baseEnv = await resolveSessionEnv(id, sandbox, config, options?.cwd);
|
|
29
|
+
const env = options?.cwd ? createCwdSessionEnv(baseEnv, options.cwd) : baseEnv;
|
|
30
|
+
const store = options?.persist ?? config.defaultStore;
|
|
31
|
+
const localContext = await discoverSessionContext(env);
|
|
32
|
+
const agentModel = options?.model && config.agentConfig.resolveModel ? config.agentConfig.resolveModel(options.model) : config.agentConfig.model;
|
|
33
|
+
return new AgentClient(id, {
|
|
34
|
+
...config.agentConfig,
|
|
35
|
+
systemPrompt: localContext.systemPrompt,
|
|
36
|
+
skills: localContext.skills,
|
|
37
|
+
model: agentModel,
|
|
38
|
+
role: options?.role ?? config.agentConfig.role
|
|
39
|
+
}, env, store, currentEventCallback, options?.commands, options?.tools);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
initializedAgentIds.delete(id);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
36
44
|
},
|
|
37
45
|
setEventCallback(callback) {
|
|
38
46
|
currentEventCallback = callback;
|
|
@@ -43,17 +51,28 @@ function createFlueContext(config) {
|
|
|
43
51
|
function isBashLike(value) {
|
|
44
52
|
return typeof value === "object" && value !== null && "exec" in value && "getCwd" in value && "fs" in value && typeof value.exec === "function" && typeof value.getCwd === "function" && typeof value.fs === "object";
|
|
45
53
|
}
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
function isBashFactory(value) {
|
|
55
|
+
return typeof value === "function";
|
|
56
|
+
}
|
|
57
|
+
function isSandboxFactory(value) {
|
|
58
|
+
return typeof value === "object" && value !== null && "createSessionEnv" in value && typeof value.createSessionEnv === "function";
|
|
59
|
+
}
|
|
60
|
+
/** Resolve sandbox option to SessionEnv: empty → local → BashFactory → platform hook → SandboxFactory. */
|
|
61
|
+
async function resolveSessionEnv(id, sandbox, config, cwd) {
|
|
48
62
|
if (sandbox === void 0 || sandbox === "empty") return config.createDefaultEnv();
|
|
49
63
|
if (sandbox === "local") return config.createLocalEnv();
|
|
50
|
-
if (
|
|
64
|
+
if (isBashFactory(sandbox)) return bashFactoryToSessionEnv(sandbox);
|
|
65
|
+
if (isBashLike(sandbox)) throw new Error("[flue] init({ sandbox }) no longer accepts a Bash-like object directly. Pass a BashFactory instead, e.g. `sandbox: () => new Bash({ fs })`.");
|
|
51
66
|
if (config.resolveSandbox) {
|
|
52
67
|
const resolved = await config.resolveSandbox(sandbox);
|
|
53
68
|
if (resolved) return resolved;
|
|
54
69
|
}
|
|
55
|
-
return sandbox.createSessionEnv({
|
|
70
|
+
if (isSandboxFactory(sandbox)) return sandbox.createSessionEnv({
|
|
71
|
+
id,
|
|
72
|
+
cwd
|
|
73
|
+
});
|
|
74
|
+
throw new Error("[flue] Invalid sandbox option passed to init().");
|
|
56
75
|
}
|
|
57
76
|
|
|
58
77
|
//#endregion
|
|
59
|
-
export { Type, createFlueContext };
|
|
78
|
+
export { Type, connectMcpServer, createFlueContext };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as CommandExecutor } from "../command-helpers-
|
|
1
|
+
import { C as SessionEnv, T as SessionStore, l as Command } from "../types-T8pE1xIS.mjs";
|
|
2
|
+
import { t as CommandExecutor } from "../command-helpers-DdAfbnom.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/cloudflare/virtual-sandbox.d.ts
|
|
5
5
|
interface VirtualSandboxOptions {
|
|
@@ -19,10 +19,6 @@ declare function cfSandboxToSessionEnv(sandbox: any, cwd?: string): Promise<Sess
|
|
|
19
19
|
declare function store(): SessionStore;
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/cloudflare/context.d.ts
|
|
22
|
-
/**
|
|
23
|
-
* Cloudflare environment context injection. Safe because each DO is single-threaded.
|
|
24
|
-
* Set before handler invocation, accessed by runtime primitives, cleared after.
|
|
25
|
-
*/
|
|
26
22
|
interface CloudflareContext {
|
|
27
23
|
env: Record<string, any>;
|
|
28
24
|
agentInstance: {
|
|
@@ -33,8 +29,7 @@ interface CloudflareContext {
|
|
|
33
29
|
sql: any;
|
|
34
30
|
};
|
|
35
31
|
}
|
|
36
|
-
declare function
|
|
32
|
+
declare function runWithCloudflareContext<T>(ctx: CloudflareContext, fn: () => T): T;
|
|
37
33
|
declare function getCloudflareContext(): CloudflareContext;
|
|
38
|
-
declare function clearCloudflareContext(): void;
|
|
39
34
|
//#endregion
|
|
40
|
-
export { type CloudflareContext, type VirtualSandboxOptions, cfSandboxToSessionEnv,
|
|
35
|
+
export { type CloudflareContext, type VirtualSandboxOptions, cfSandboxToSessionEnv, defineCommand, getCloudflareContext, getVirtualSandbox, runWithCloudflareContext, store };
|
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
import "../agent-
|
|
2
|
-
import "../session-
|
|
1
|
+
import "../agent-BB4lwAd5.mjs";
|
|
2
|
+
import "../session-DukL3zwF.mjs";
|
|
3
3
|
import { createSandboxSessionEnv } from "../sandbox.mjs";
|
|
4
|
-
import { t as normalizeExecutor } from "../command-helpers-
|
|
4
|
+
import { t as normalizeExecutor } from "../command-helpers-hTZKWK13.mjs";
|
|
5
5
|
import { Workspace, WorkspaceFileSystem } from "@cloudflare/shell";
|
|
6
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
7
|
|
|
7
8
|
//#region src/cloudflare/context.ts
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Cloudflare environment context injection.
|
|
11
|
+
*
|
|
12
|
+
* Durable Objects are single-threaded, but async executions can still interleave
|
|
13
|
+
* at await points. AsyncLocalStorage keeps Cloudflare runtime primitives scoped
|
|
14
|
+
* to the request/fiber that invoked them instead of sharing a module global.
|
|
15
|
+
*/
|
|
16
|
+
const contextStorage = new AsyncLocalStorage();
|
|
17
|
+
function runWithCloudflareContext(ctx, fn) {
|
|
18
|
+
return contextStorage.run(ctx, fn);
|
|
11
19
|
}
|
|
12
20
|
function getCloudflareContext() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function clearCloudflareContext() {
|
|
17
|
-
currentContext = null;
|
|
21
|
+
const ctx = contextStorage.getStore();
|
|
22
|
+
if (!ctx) throw new Error("[flue:cloudflare] Not running in a Cloudflare context. This function can only be called inside a Cloudflare Worker or Durable Object.");
|
|
23
|
+
return ctx;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
//#endregion
|
|
@@ -81,8 +87,9 @@ async function getVirtualSandbox(bucket, options) {
|
|
|
81
87
|
/* @vite-ignore */
|
|
82
88
|
"just-bash"
|
|
83
89
|
);
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
const fs = new InMemoryFs();
|
|
91
|
+
return () => new Bash({
|
|
92
|
+
fs,
|
|
86
93
|
network: { dangerouslyAllowFullInternetAccess: true }
|
|
87
94
|
});
|
|
88
95
|
}
|
|
@@ -99,7 +106,7 @@ async function getVirtualSandbox(bucket, options) {
|
|
|
99
106
|
);
|
|
100
107
|
const fs = new MountableFs({ base: new InMemoryFs() });
|
|
101
108
|
fs.mount("/workspace", r2Adapter);
|
|
102
|
-
return new Bash({
|
|
109
|
+
return () => new Bash({
|
|
103
110
|
fs,
|
|
104
111
|
cwd: "/workspace",
|
|
105
112
|
network: { dangerouslyAllowFullInternetAccess: true }
|
|
@@ -205,26 +212,30 @@ async function cfSandboxToSessionEnv(sandbox, cwd = "/workspace") {
|
|
|
205
212
|
//#region src/cloudflare/session-store.ts
|
|
206
213
|
function store() {
|
|
207
214
|
return {
|
|
208
|
-
async save(
|
|
215
|
+
async save(id, data) {
|
|
209
216
|
const { agentInstance } = getCloudflareContext();
|
|
217
|
+
const sessions = { ...agentInstance.state?.sessions ?? {} };
|
|
218
|
+
sessions[id] = data;
|
|
210
219
|
agentInstance.setState({
|
|
211
220
|
...agentInstance.state,
|
|
212
|
-
|
|
221
|
+
sessions
|
|
213
222
|
});
|
|
214
223
|
},
|
|
215
|
-
async load(
|
|
224
|
+
async load(id) {
|
|
216
225
|
const { agentInstance } = getCloudflareContext();
|
|
217
|
-
return agentInstance.state?.
|
|
226
|
+
return agentInstance.state?.sessions?.[id] ?? null;
|
|
218
227
|
},
|
|
219
|
-
async delete(
|
|
228
|
+
async delete(id) {
|
|
220
229
|
const { agentInstance } = getCloudflareContext();
|
|
230
|
+
const sessions = { ...agentInstance.state?.sessions ?? {} };
|
|
231
|
+
delete sessions[id];
|
|
221
232
|
agentInstance.setState({
|
|
222
233
|
...agentInstance.state,
|
|
223
|
-
|
|
234
|
+
sessions
|
|
224
235
|
});
|
|
225
236
|
}
|
|
226
237
|
};
|
|
227
238
|
}
|
|
228
239
|
|
|
229
240
|
//#endregion
|
|
230
|
-
export { cfSandboxToSessionEnv,
|
|
241
|
+
export { cfSandboxToSessionEnv, defineCommand, getCloudflareContext, getVirtualSandbox, runWithCloudflareContext, store };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import { AgentTool } from "@mariozechner/pi-agent-core";
|
|
1
|
+
import { A as TaskOptions, C as SessionEnv, D as ShellResult, E as ShellOptions, M as ToolParameters, O as Skill, S as SessionData, T as SessionStore, _ as FlueSessions, a as BashLike, b as Role, c as BuildPlugin, d as FileStat, f as FlueAgent, g as FlueSession, h as FlueEventCallback, i as BashFactory, j as ToolDef, k as SkillOptions, l as Command, m as FlueEvent, n as AgentInfo, o as BuildContext, p as FlueContext, r as AgentInit, s as BuildOptions, t as AgentConfig, u as CommandDef, v as PromptOptions, w as SessionOptions, x as SandboxFactory, y as PromptResponse } from "./types-T8pE1xIS.mjs";
|
|
2
|
+
import { AgentTool, AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
3
3
|
|
|
4
4
|
//#region src/build.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Result returned by {@link build}. `changed` indicates whether any file in
|
|
7
|
+
* `dist/` was actually modified. Callers (notably the dev server) use this to
|
|
8
|
+
* skip restarting downstream processes for no-op rebuilds on agent body edits.
|
|
9
|
+
*/
|
|
10
|
+
interface BuildResult {
|
|
11
|
+
changed: boolean;
|
|
12
|
+
}
|
|
5
13
|
/**
|
|
6
14
|
* Build a workspace into a deployable artifact.
|
|
7
15
|
*
|
|
@@ -12,7 +20,7 @@ import { AgentTool } from "@mariozechner/pi-agent-core";
|
|
|
12
20
|
*
|
|
13
21
|
* AGENTS.md and .agents/skills/ are NOT bundled — discovered at runtime from session cwd.
|
|
14
22
|
*/
|
|
15
|
-
declare function build(options: BuildOptions): Promise<
|
|
23
|
+
declare function build(options: BuildOptions): Promise<BuildResult>;
|
|
16
24
|
/**
|
|
17
25
|
* Resolve a Flue workspace directory from the current working directory,
|
|
18
26
|
* using the two-layout convention. Intended for the CLI when `--workspace` is
|
|
@@ -28,8 +36,43 @@ declare function build(options: BuildOptions): Promise<void>;
|
|
|
28
36
|
*/
|
|
29
37
|
declare function resolveWorkspaceFromCwd(cwd: string): string | null;
|
|
30
38
|
//#endregion
|
|
39
|
+
//#region src/dev.d.ts
|
|
40
|
+
interface DevOptions {
|
|
41
|
+
workspaceDir: string;
|
|
42
|
+
outputDir: string;
|
|
43
|
+
target: 'node' | 'cloudflare';
|
|
44
|
+
/** Defaults to 3583 ("FLUE" on a phone keypad). */
|
|
45
|
+
port?: number;
|
|
46
|
+
}
|
|
47
|
+
/** Default port for `flue dev`. F=3, L=5, U=8, E=3 on a phone keypad. */
|
|
48
|
+
declare const DEFAULT_DEV_PORT = 3583;
|
|
49
|
+
/**
|
|
50
|
+
* Start a Flue dev server. Resolves only when the server is shut down (e.g.
|
|
51
|
+
* via SIGINT). Errors during the initial build/start are thrown synchronously;
|
|
52
|
+
* errors during subsequent rebuilds are logged but do NOT exit the dev server
|
|
53
|
+
* — the user is editing code, after all, and we want to recover when they fix it.
|
|
54
|
+
*/
|
|
55
|
+
declare function dev(options: DevOptions): Promise<void>;
|
|
56
|
+
//#endregion
|
|
31
57
|
//#region src/agent.d.ts
|
|
32
58
|
declare const BUILTIN_TOOL_NAMES: Set<string>;
|
|
33
|
-
|
|
59
|
+
interface TaskToolParams {
|
|
60
|
+
prompt: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
role?: string;
|
|
63
|
+
cwd?: string;
|
|
64
|
+
}
|
|
65
|
+
interface TaskToolResultDetails {
|
|
66
|
+
taskId: string;
|
|
67
|
+
sessionId: string;
|
|
68
|
+
messageId?: string;
|
|
69
|
+
role?: string;
|
|
70
|
+
cwd?: string;
|
|
71
|
+
}
|
|
72
|
+
interface CreateToolsOptions {
|
|
73
|
+
task?: (params: TaskToolParams, signal?: AbortSignal) => Promise<AgentToolResult<TaskToolResultDetails>>;
|
|
74
|
+
roles?: Record<string, Role>;
|
|
75
|
+
}
|
|
76
|
+
declare function createTools(env: SessionEnv, options?: CreateToolsOptions): AgentTool<any>[];
|
|
34
77
|
//#endregion
|
|
35
|
-
export { type AgentConfig, type AgentInfo, BUILTIN_TOOL_NAMES, type BashLike, type BuildContext, type BuildOptions, type BuildPlugin, type Command, type CommandDef, type
|
|
78
|
+
export { type AgentConfig, type AgentInfo, type AgentInit, BUILTIN_TOOL_NAMES, type BashFactory, type BashLike, type BuildContext, type BuildOptions, type BuildPlugin, type Command, type CommandDef, DEFAULT_DEV_PORT, type DevOptions, type FileStat, type FlueAgent, type FlueContext, type FlueEvent, type FlueEventCallback, type FlueSession, type FlueSessions, type PromptOptions, type PromptResponse, type Role, type SandboxFactory, type SessionData, type SessionEnv, type SessionOptions, type SessionStore, type ShellOptions, type ShellResult, type Skill, type SkillOptions, type TaskOptions, type ToolDef, type ToolParameters, build, createTools, dev, resolveWorkspaceFromCwd };
|