@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 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, sessionId }: FlueContext) {
39
- // `session` -- Your session with the agent, including sandbox, message history, etc.
40
- // By default, calling `init()` with no arguments gets you a completely empty agent,
41
- // with no skills, AGENTS.md, or files.
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 session = await init({ sandbox });
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
- // session. Override per-call with `{ model: '...' }` on prompt()/skill().
118
- const session = await init({
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-20250514',
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 session gets a real container via Daytona. The container has
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 sessionId, and reuse that
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 session = await init({
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 session.shell(`git clone ${payload.repo} /workspace/project`);
177
- await session.shell('npm install', { cwd: '/workspace/project' });
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
- ### Trigger From the CLI
308
+ ### Local Development (`flue dev`)
189
309
 
190
- Build and run any agent locally, perfect for fast local testing or running in CI.
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 run hello --target node --session-id test-1 \
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 anywhere. Cloudflare and any Node.js host are supported today, with more coming in the future.
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
- return [
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 ShellOptions, D as TaskOptions, E as SkillOptions, O as ToolDef, S as SessionStore, b as SessionEnv, d as FlueContext, f as FlueEvent, g as PromptResponse, h as PromptOptions, l as CommandSupport, m as FlueSession, p as FlueEventCallback, r as BashLike, s as Command, t as AgentConfig, u as FileStat, v as SandboxFactory, w as ShellResult, x as SessionInit, y as SessionData } from "./types-C0nqbu6Z.mjs";
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
- sessionId: string;
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 CommandSupport, type FileStat, type FlueContext, FlueContextConfig, FlueContextInternal, type FlueEvent, type FlueEventCallback, type FlueSession, type PromptOptions, type PromptResponse, type SandboxFactory, type SessionData, type SessionEnv, type SessionInit, type SessionStore, type ShellOptions, type ShellResult, type SkillOptions, type TaskOptions, type ToolDef, Type, createFlueContext };
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-BYG0nVbQ.mjs";
2
- import { n as Session } from "./session-CiAMTsLZ.mjs";
3
- import { bashToSessionEnv } from "./sandbox.mjs";
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 sessionId() {
12
- return config.sessionId;
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
- if (initialized) throw new Error("[flue] init() can only be called once per request.");
22
- initialized = true;
23
- const sandbox = options?.sandbox;
24
- const env = await resolveSessionEnv(config.sessionId, sandbox, config);
25
- const store = options?.persist ?? config.defaultStore;
26
- const savedData = await store.load(config.sessionId);
27
- const localContext = await discoverSessionContext(env);
28
- const sessionModel = options?.model && config.agentConfig.resolveModel ? config.agentConfig.resolveModel(options.model) : config.agentConfig.model;
29
- const sessionConfig = {
30
- ...config.agentConfig,
31
- systemPrompt: localContext.systemPrompt,
32
- skills: localContext.skills,
33
- model: sessionModel
34
- };
35
- return new Session(config.sessionId, sessionConfig, env, store, savedData, currentEventCallback, options?.commands);
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
- /** Resolve sandbox option to SessionEnv: empty → local → BashLike → platform hook → SandboxFactory. */
47
- async function resolveSessionEnv(sessionId, sandbox, config) {
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 (isBashLike(sandbox)) return bashToSessionEnv(sandbox);
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({ sessionId });
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 { S as SessionStore, b as SessionEnv, s as Command } from "../types-C0nqbu6Z.mjs";
2
- import { t as CommandExecutor } from "../command-helpers-C8SHLdaA.mjs";
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 setCloudflareContext(ctx: CloudflareContext): void;
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, clearCloudflareContext, defineCommand, getCloudflareContext, getVirtualSandbox, setCloudflareContext, store };
35
+ export { type CloudflareContext, type VirtualSandboxOptions, cfSandboxToSessionEnv, defineCommand, getCloudflareContext, getVirtualSandbox, runWithCloudflareContext, store };
@@ -1,20 +1,26 @@
1
- import "../agent-BYG0nVbQ.mjs";
2
- import "../session-CiAMTsLZ.mjs";
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-CxRhK1my.mjs";
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
- let currentContext = null;
9
- function setCloudflareContext(ctx) {
10
- currentContext = ctx;
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
- if (!currentContext) throw new Error("[flue:cloudflare] Not running in a Cloudflare context. This function can only be called inside a Cloudflare Worker or Durable Object.");
14
- return currentContext;
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
- return new Bash({
85
- fs: new InMemoryFs(),
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(_id, data) {
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
- sessionData: data
221
+ sessions
213
222
  });
214
223
  },
215
- async load(_id) {
224
+ async load(id) {
216
225
  const { agentInstance } = getCloudflareContext();
217
- return agentInstance.state?.sessionData ?? null;
226
+ return agentInstance.state?.sessions?.[id] ?? null;
218
227
  },
219
- async delete(_id) {
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
- sessionData: null
234
+ sessions
224
235
  });
225
236
  }
226
237
  };
227
238
  }
228
239
 
229
240
  //#endregion
230
- export { cfSandboxToSessionEnv, clearCloudflareContext, defineCommand, getCloudflareContext, getVirtualSandbox, setCloudflareContext, store };
241
+ export { cfSandboxToSessionEnv, defineCommand, getCloudflareContext, getVirtualSandbox, runWithCloudflareContext, store };
@@ -1,4 +1,4 @@
1
- import { w as ShellResult } from "./types-C0nqbu6Z.mjs";
1
+ import { D as ShellResult } from "./types-T8pE1xIS.mjs";
2
2
 
3
3
  //#region src/command-helpers.d.ts
4
4
  /**
package/dist/index.d.mts CHANGED
@@ -1,7 +1,15 @@
1
- import { C as ShellOptions, D as TaskOptions, E as SkillOptions, O as ToolDef, S as SessionStore, T as Skill, _ as Role, a as BuildOptions, b as SessionEnv, c as CommandDef, d as FlueContext, f as FlueEvent, g as PromptResponse, h as PromptOptions, i as BuildContext, l as CommandSupport, m as FlueSession, n as AgentInfo, o as BuildPlugin, p as FlueEventCallback, r as BashLike, s as Command, t as AgentConfig, u as FileStat, v as SandboxFactory, w as ShellResult, x as SessionInit, y as SessionData } from "./types-C0nqbu6Z.mjs";
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<void>;
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
- declare function createTools(env: SessionEnv): AgentTool<any>[];
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 CommandSupport, type FileStat, type FlueContext, type FlueEvent, type FlueEventCallback, type FlueSession, type PromptOptions, type PromptResponse, type Role, type SandboxFactory, type SessionData, type SessionEnv, type SessionInit, type SessionStore, type ShellOptions, type ShellResult, type Skill, type SkillOptions, type TaskOptions, type ToolDef, build, createTools, resolveWorkspaceFromCwd };
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 };