@lunora/agent 1.0.0-alpha.8 → 1.0.0-alpha.81
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/dist/channels.d.mts +2 -2
- package/dist/channels.d.ts +2 -2
- package/dist/channels.mjs +1 -181
- package/dist/component.d.mts +2 -0
- package/dist/component.d.ts +2 -0
- package/dist/component.mjs +2 -418
- package/dist/inbound.d.mts +7 -5
- package/dist/inbound.d.ts +7 -5
- package/dist/inbound.mjs +1 -32
- package/dist/index.d.mts +49 -8
- package/dist/index.d.ts +49 -8
- package/dist/index.mjs +1 -18
- package/dist/naming.mjs +1 -8
- package/dist/packem_shared/AGENT_MODULE-M4D1EejI.mjs +1 -0
- package/dist/packem_shared/VoiceSessionDO-DHtOAsd9.mjs +1 -0
- package/dist/packem_shared/adaptMcpResult-CVlq-_TO.mjs +2 -0
- package/dist/packem_shared/agentAsTool-Xg7YCl5P.mjs +1 -0
- package/dist/packem_shared/base64-5eyBfWO3.mjs +1 -0
- package/dist/packem_shared/braintrustTelemetry-CoYK-pq7.mjs +1 -0
- package/dist/packem_shared/branch-marker-boZ00zmk.mjs +1 -0
- package/dist/packem_shared/buildModelMessages-Y4tGo1T9.mjs +5 -0
- package/dist/packem_shared/codeTool-DC62JfaY.mjs +1 -0
- package/dist/packem_shared/collectAgenticMemoryTools-BeMWT2qt.mjs +1 -0
- package/dist/packem_shared/combineTelemetry-DgE9W9G8.mjs +1 -0
- package/dist/packem_shared/common-CJSjtsfv.mjs +1 -0
- package/dist/packem_shared/compileAgentWorkflow-D4q6vB3j.mjs +1 -0
- package/dist/packem_shared/component-shared-IZozysYq.mjs +1 -0
- package/dist/packem_shared/consoleTelemetry-BZY3Y-Ly.mjs +1 -0
- package/dist/packem_shared/createAgentContext-iS35-Xrs.mjs +1 -0
- package/dist/packem_shared/createAgentGenerate-9bZ2YhpN.mjs +3 -0
- package/dist/packem_shared/createDispatchRunner-pLyQ70FK-2_j6ocGT.mjs +1 -0
- package/dist/packem_shared/defineAgent-YTqqBCP3.mjs +3 -0
- package/dist/packem_shared/defineSkill-DGGvDWNq.mjs +1 -0
- package/dist/packem_shared/functionTool-D3CP4f8-.mjs +1 -0
- package/dist/packem_shared/memory-JI0lUu84.mjs +1 -0
- package/dist/packem_shared/normalizeEntityName-Ce0X6Afj.mjs +2 -0
- package/dist/packem_shared/otlpTelemetry-CywiFp-X.mjs +1 -0
- package/dist/packem_shared/runAgentLoop-oAmn3OyN.mjs +3 -0
- package/dist/packem_shared/runVoiceTurn-B9EXi1BC.mjs +1 -0
- package/dist/packem_shared/sandboxComponent-BtOkUYpY.mjs +5 -0
- package/dist/packem_shared/sentryTelemetry-CPqEbFPA.mjs +1 -0
- package/dist/packem_shared/{types.d-boAM2Yi1.d.mts → types.d-DTKxPuh_.d.mts} +239 -22
- package/dist/packem_shared/{types.d-boAM2Yi1.d.ts → types.d-DTKxPuh_.d.ts} +239 -22
- package/dist/packem_shared/voice-turn-DhxJgQW1.mjs +1 -0
- package/dist/reply.d.mts +40 -0
- package/dist/reply.d.ts +40 -0
- package/dist/reply.mjs +1 -0
- package/dist/sandbox.d.mts +14 -13
- package/dist/sandbox.d.ts +14 -13
- package/dist/sandbox.mjs +1 -113
- package/dist/skill-markdown.d.mts +36 -0
- package/dist/skill-markdown.d.ts +36 -0
- package/dist/skill-markdown.mjs +1 -0
- package/dist/telemetry/index.d.mts +64 -1
- package/dist/telemetry/index.d.ts +64 -1
- package/dist/telemetry/index.mjs +1 -5
- package/package.json +18 -9
- package/dist/packem_shared/AGENT_MODULE-Dnt_-AAT.mjs +0 -24
- package/dist/packem_shared/VoiceSessionDO-DLoXsHGF.mjs +0 -297
- package/dist/packem_shared/adaptMcpResult-wtNMvLoP.mjs +0 -65
- package/dist/packem_shared/agentAsTool-CUHlWsmt.mjs +0 -98
- package/dist/packem_shared/base64-BVwtgRJV.mjs +0 -18
- package/dist/packem_shared/braintrustTelemetry-wuGDErob.mjs +0 -47
- package/dist/packem_shared/buildModelMessages-BWFigaoo.mjs +0 -69
- package/dist/packem_shared/codeTool-CjgJOC9t.mjs +0 -122
- package/dist/packem_shared/collectAgenticMemoryTools-QrzpV-WX.mjs +0 -97
- package/dist/packem_shared/combineTelemetry-DCyaaWAI.mjs +0 -43
- package/dist/packem_shared/common-DAeFCot5.mjs +0 -61
- package/dist/packem_shared/compileAgentWorkflow-DXNKigj0.mjs +0 -75
- package/dist/packem_shared/consoleTelemetry-z2MiP1jt.mjs +0 -93
- package/dist/packem_shared/createAgentContext-4xJGXNR4.mjs +0 -50
- package/dist/packem_shared/createAgentGenerate-BQv9YJ01.mjs +0 -192
- package/dist/packem_shared/createDispatchRunner-DSbp_dph-ZHTtxy3f.mjs +0 -69
- package/dist/packem_shared/defineAgent-DAwAZC9P.mjs +0 -148
- package/dist/packem_shared/defineSkill-Ctf_S-rz.mjs +0 -22
- package/dist/packem_shared/functionTool-D6lCa2jB.mjs +0 -20
- package/dist/packem_shared/graph-component-Bbaxxymp.mjs +0 -217
- package/dist/packem_shared/memory-D4FPcBsX.mjs +0 -12
- package/dist/packem_shared/normalizeEntityName-BouctxLC.mjs +0 -3
- package/dist/packem_shared/otlpTelemetry-9ypXork9.mjs +0 -163
- package/dist/packem_shared/runAgentLoop-B3Q2o-Uz.mjs +0 -493
- package/dist/packem_shared/runVoiceTurn-LnqLvCRR.mjs +0 -211
- package/dist/packem_shared/sandboxComponent-DR3pTwBL.mjs +0 -194
- package/dist/packem_shared/sentryTelemetry-CgqFJyLO.mjs +0 -36
|
@@ -23,7 +23,7 @@ interface AgentStepLike {
|
|
|
23
23
|
/**
|
|
24
24
|
* Durably hibernate until an external event of `type` arrives, then return
|
|
25
25
|
* its payload. Used for human-in-the-loop approvals: a run pauses on
|
|
26
|
-
* `approval
|
|
26
|
+
* `approval:<toolCallId>` until a client resolves it. Like `do`, a resolved
|
|
27
27
|
* wait is memoized — a replay returns the recorded decision without pausing
|
|
28
28
|
* again. Signature mirrors `@lunora/workflow`'s `WorkflowStepLike`.
|
|
29
29
|
*/
|
|
@@ -42,13 +42,26 @@ interface AgentStepLike {
|
|
|
42
42
|
* `idempotencyKey`.
|
|
43
43
|
*
|
|
44
44
|
* The `idempotencyKey` is the deterministic durable-step name
|
|
45
|
-
* (`tool
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* (`tool:<name>:<toolCallId>`, further suffixed per script step for a
|
|
46
|
+
* `codeTool`). A COMPLETED tool step is never re-run on a workflow replay
|
|
47
|
+
* (native step memoization) — but a step that FAILS mid-body is retried
|
|
48
|
+
* at-least-once, so a side-effecting tool (charge a card, send a mail) must
|
|
49
|
+
* dedupe on this key itself. `functionTool` forwards it to the dispatched
|
|
50
|
+
* function as `args.idempotencyKey` (pinned after the model input, so it
|
|
51
|
+
* can't be overridden) — a function that wants to dedupe declares
|
|
52
|
+
* `idempotencyKey: v.optional(v.string())` in its own args and checks it; a
|
|
53
|
+
* function that ignores it is unaffected (an undeclared arg field is dropped,
|
|
54
|
+
* not rejected).
|
|
49
55
|
* @experimental
|
|
50
56
|
*/
|
|
51
57
|
interface AgentToolContext {
|
|
58
|
+
/**
|
|
59
|
+
* How many sub-agent delegations deep this run already is — 0 for a run a
|
|
60
|
+
* user started, one more per `agent.asTool` hop. The loop copies it off
|
|
61
|
+
* {@link AgentRunInput.depth}; `agent.asTool` reads it to refuse spawning a
|
|
62
|
+
* child past the delegation-depth bound (see `as-tool.ts`). Absent is 0.
|
|
63
|
+
*/
|
|
64
|
+
depth?: number;
|
|
52
65
|
/** The Worker environment bindings. */
|
|
53
66
|
env: Record<string, unknown>;
|
|
54
67
|
/**
|
|
@@ -102,11 +115,45 @@ interface AgentToolContext {
|
|
|
102
115
|
* skip the write when it is already present.
|
|
103
116
|
*/
|
|
104
117
|
setState: (state: Record<string, unknown>) => Promise<void>;
|
|
118
|
+
/**
|
|
119
|
+
* The durable-step handle (`step.do`/`waitForEvent`). ALWAYS present:
|
|
120
|
+
* `agent-loop.ts` threads it into every tool's context unconditionally, and
|
|
121
|
+
* it is required rather than optional so a missing handle is a compile
|
|
122
|
+
* error instead of a silent durability downgrade. `codeTool` uses it to give
|
|
123
|
+
* each script step its OWN nested durable boundary — see `code-tool.ts` — so
|
|
124
|
+
* a failure at script step 3 retries only step 3, not steps 1–2's
|
|
125
|
+
* already-committed side effects. Cloudflare Workflows supports a `step.do`
|
|
126
|
+
* nested inside another `step.do`'s callback (the codeTool call's own
|
|
127
|
+
* enclosing step). Most tools never touch this directly; a test driving
|
|
128
|
+
* `execute`/`runToolScript` by hand passes a pass-through double.
|
|
129
|
+
*/
|
|
130
|
+
step: AgentStepLike;
|
|
105
131
|
/** The thread this tool call belongs to. */
|
|
106
132
|
threadKey: string;
|
|
107
133
|
/** The provider-issued tool-call id. */
|
|
108
134
|
toolCallId: string;
|
|
109
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* The view of {@link AgentToolContext} handed to a `needsApproval` gate
|
|
138
|
+
* function — every field except `setState`, `step`, and `reportProgress`.
|
|
139
|
+
*
|
|
140
|
+
* `setState` is dropped because a gate that mutates thread state is a side
|
|
141
|
+
* effect inside a decision predicate, and `reportProgress` because emitting a
|
|
142
|
+
* live event is the same thing in observable form — the decision is what the
|
|
143
|
+
* loop reports, not the deciding. `step` goes because the loop already runs the
|
|
144
|
+
* gate inside a durable step of its own, so a gate has no business opening
|
|
145
|
+
* another.
|
|
146
|
+
*
|
|
147
|
+
* `getState` and `run` stay: reads are legitimate gate inputs (gate on the
|
|
148
|
+
* caller's plan tier, on a spend total), and the gate resolves inside its own
|
|
149
|
+
* durable step, so they are replay-safe there. Note this makes the type a
|
|
150
|
+
* NARROWING, not a proof of purity — `run` takes any
|
|
151
|
+
* {@link AgentFunctionReference}, so a gate can still dispatch a mutation
|
|
152
|
+
* before approval. Nothing in the type system distinguishes a query reference
|
|
153
|
+
* from a mutation one; keeping the gate side-effect-free is the author's.
|
|
154
|
+
* @experimental
|
|
155
|
+
*/
|
|
156
|
+
type AgentApprovalContext = Omit<AgentToolContext, "reportProgress" | "setState" | "step">;
|
|
110
157
|
/**
|
|
111
158
|
* An agent tool. Unlike a raw AI SDK tool, `execute` is NOT handed to the
|
|
112
159
|
* model call — the loop runs it itself inside a named durable step so a
|
|
@@ -127,17 +174,25 @@ interface AgentToolDefinition<Input = unknown, Output = unknown> {
|
|
|
127
174
|
* Gate the tool behind a human approval (mirrors the AI SDK's
|
|
128
175
|
* `needsApproval`). When it resolves truthy the durable run PAUSES — the
|
|
129
176
|
* thread moves to `"awaiting_input"` and the workflow hibernates on
|
|
130
|
-
* `approval
|
|
177
|
+
* `approval:<toolCallId>` — until a client calls `agents:agentResolveApproval`.
|
|
131
178
|
* On approve the tool runs exactly as normal; on reject it is skipped and a
|
|
132
179
|
* tool result explaining the rejection is persisted so the next turn recovers.
|
|
133
180
|
* A boolean gates statically; a function gates per input. Default: `false`
|
|
134
|
-
* (unchanged behavior).
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
181
|
+
* (unchanged behavior).
|
|
182
|
+
*
|
|
183
|
+
* The boolean/`undefined` forms are compile-time constants re-derived
|
|
184
|
+
* identically on every replay — no durable step. The FUNCTION form runs
|
|
185
|
+
* inside its OWN durable step (`tool:approval-gate:<toolCallId>`, distinct
|
|
186
|
+
* from the tool's own step), so it now runs exactly once per call, not once
|
|
187
|
+
* per replay. It must still be otherwise pure: deterministic given its
|
|
188
|
+
* inputs (no `Date.now()`/`Math.random()`) and free of side effects — the
|
|
189
|
+
* context it receives is {@link AgentApprovalContext}, which has neither
|
|
190
|
+
* `setState` nor `reportProgress`; state writes and progress events belong
|
|
191
|
+
* only in `execute`, inside the tool's own memoized step. It still holds
|
|
192
|
+
* `run`, which the type cannot narrow to reads — see
|
|
193
|
+
* {@link AgentApprovalContext}.
|
|
139
194
|
*/
|
|
140
|
-
needsApproval?: ((input: Input, context:
|
|
195
|
+
needsApproval?: ((input: Input, context: AgentApprovalContext) => boolean | Promise<boolean>) | boolean;
|
|
141
196
|
}
|
|
142
197
|
/**
|
|
143
198
|
* Author-supplied tool config (see {@link AgentToolDefinition}).
|
|
@@ -252,7 +307,7 @@ interface AgentMemoryOptions {
|
|
|
252
307
|
* (`key: "default"`) from {@link AgentConfig.memory}, then one per skill that
|
|
253
308
|
* carries `knowledge` (keyed by the skill's name). The key names the durable
|
|
254
309
|
* step — the default source keeps the historic `"memory:retrieve"`, a skill
|
|
255
|
-
* source uses `"memory:retrieve
|
|
310
|
+
* source uses `"memory:retrieve:<key>"` — so replay stays deterministic.
|
|
256
311
|
* @experimental
|
|
257
312
|
*/
|
|
258
313
|
interface AgentMemorySource extends AgentMemoryOptions {
|
|
@@ -308,7 +363,7 @@ interface SkillConfig {
|
|
|
308
363
|
knowledge?: AgentMemoryOptions;
|
|
309
364
|
/**
|
|
310
365
|
* The skill's identifier — namespaces this skill's `knowledge` memory source
|
|
311
|
-
* (the durable step `memory:retrieve
|
|
366
|
+
* (the durable step `memory:retrieve:<name>`). Must be identifier-shaped.
|
|
312
367
|
*/
|
|
313
368
|
name: string;
|
|
314
369
|
/**
|
|
@@ -318,6 +373,12 @@ interface SkillConfig {
|
|
|
318
373
|
*/
|
|
319
374
|
tools?: Record<string, AnyAgentTool>;
|
|
320
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* The code-side parts of a markdown-authored skill — everything `SKILL.md`
|
|
378
|
+
* cannot carry. `name` and `instructions` come from the file itself.
|
|
379
|
+
* @experimental
|
|
380
|
+
*/
|
|
381
|
+
type SkillMarkdownExtras = Omit<SkillConfig, "instructions" | "name">;
|
|
321
382
|
/**
|
|
322
383
|
* A `defineSkill` result — config plus the brand the agent merge checks.
|
|
323
384
|
* @experimental
|
|
@@ -358,11 +419,43 @@ interface AgentStepFinishInfo {
|
|
|
358
419
|
}
|
|
359
420
|
/**
|
|
360
421
|
* Called after each LLM turn with that turn's text, tool calls, and usage. Runs
|
|
361
|
-
* inside a named durable step (`agent:step-finish
|
|
422
|
+
* inside a named durable step (`agent:step-finish:<turn>`) so it fires exactly
|
|
362
423
|
* once per turn even across a workflow replay.
|
|
363
424
|
* @experimental
|
|
364
425
|
*/
|
|
365
426
|
type AgentOnStepFinish = (info: AgentStepFinishInfo) => Promise<void> | void;
|
|
427
|
+
/**
|
|
428
|
+
* What {@link AgentConfig.onReply} is called with when a triggered run reaches
|
|
429
|
+
* its final answer.
|
|
430
|
+
* @experimental
|
|
431
|
+
*/
|
|
432
|
+
interface AgentReplyInfo {
|
|
433
|
+
/** The worker `env` — where an outbound credential (a bot token, a mailer binding) lives. */
|
|
434
|
+
env: Record<string, unknown>;
|
|
435
|
+
/** Where to reply, captured by the mapper that started this run. */
|
|
436
|
+
replyRef: AgentReplyRef;
|
|
437
|
+
/** The run's final answer. */
|
|
438
|
+
result: AgentRunResult;
|
|
439
|
+
/** The thread this run belongs to. */
|
|
440
|
+
threadKey: string;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Send a triggered run's answer back where it came from — the outbound half of
|
|
444
|
+
* an `onEmail` / `onInbound` trigger.
|
|
445
|
+
*
|
|
446
|
+
* Called once, automatically, when a run started with a `replyRef` produces a
|
|
447
|
+
* final answer; a run without one (an ordinary in-app `ctx.agents.<name>.run`)
|
|
448
|
+
* never calls it. It runs inside a named durable step, so a transient failure
|
|
449
|
+
* is retried and a workflow replay does not send the answer twice.
|
|
450
|
+
*
|
|
451
|
+
* Delivery is the app's call, because only the app has the credential: email
|
|
452
|
+
* has a home already (`replyToEmail(mailer, ref, body)` from
|
|
453
|
+
* `@lunora/agent/reply`), while Slack/GitHub/Discord bot tokens have no
|
|
454
|
+
* framework-owned store yet — read them from `info.env` and make the provider
|
|
455
|
+
* call.
|
|
456
|
+
* @experimental
|
|
457
|
+
*/
|
|
458
|
+
type AgentOnReply = (info: AgentReplyInfo) => Promise<void> | void;
|
|
366
459
|
/**
|
|
367
460
|
* The input {@link AgentConfig.prepareStep} sees before a turn runs.
|
|
368
461
|
* @experimental
|
|
@@ -404,6 +497,39 @@ type AgentPrepareStep = (input: AgentPrepareStepInput) => AgentPrepareStepResult
|
|
|
404
497
|
* start a run from an inbound email.
|
|
405
498
|
* @experimental
|
|
406
499
|
*/
|
|
500
|
+
/**
|
|
501
|
+
* Where a triggered run should send its answer back to — captured by the
|
|
502
|
+
* mapper, from fields the verified inbound payload already carries, and carried
|
|
503
|
+
* through the run so {@link AgentConfig.onReply} can answer in the same place
|
|
504
|
+
* the question was asked.
|
|
505
|
+
*
|
|
506
|
+
* A discriminated union rather than an opaque blob because each channel threads
|
|
507
|
+
* differently: email by RFC 5322 `In-Reply-To`/`References`, Slack by
|
|
508
|
+
* `thread_ts`, GitHub by issue number, Discord by channel (plus the triggering
|
|
509
|
+
* message for a true reply).
|
|
510
|
+
* @experimental
|
|
511
|
+
*/
|
|
512
|
+
type AgentReplyRef = {
|
|
513
|
+
channel: "discord";
|
|
514
|
+
channelId: string;
|
|
515
|
+
messageId?: string;
|
|
516
|
+
} | {
|
|
517
|
+
channel: "email";
|
|
518
|
+
from: string;
|
|
519
|
+
messageId: string;
|
|
520
|
+
references?: string;
|
|
521
|
+
to: string[];
|
|
522
|
+
} | {
|
|
523
|
+
channel: "github";
|
|
524
|
+
commentId?: number;
|
|
525
|
+
issueNumber: number;
|
|
526
|
+
owner: string;
|
|
527
|
+
repo: string;
|
|
528
|
+
} | {
|
|
529
|
+
channel: "slack";
|
|
530
|
+
channelId: string;
|
|
531
|
+
threadTs: string;
|
|
532
|
+
};
|
|
407
533
|
interface AgentEmailRun {
|
|
408
534
|
/** The user message that starts (or continues) the thread — the model's prompt. */
|
|
409
535
|
input: string;
|
|
@@ -414,6 +540,13 @@ interface AgentEmailRun {
|
|
|
414
540
|
* `email.from`.
|
|
415
541
|
*/
|
|
416
542
|
owner?: string;
|
|
543
|
+
/**
|
|
544
|
+
* Where to send the answer — see {@link AgentReplyRef}. Populate it from the
|
|
545
|
+
* event the mapper already has (`captureEmailReplyRef(email)` does it for
|
|
546
|
+
* email) and the run calls {@link AgentConfig.onReply} with it once the
|
|
547
|
+
* final answer is ready. Omit it and the run simply never replies.
|
|
548
|
+
*/
|
|
549
|
+
replyRef?: AgentReplyRef;
|
|
417
550
|
/** The thread key — reuse to continue a conversation (e.g. a ticket id parsed from the subject). */
|
|
418
551
|
threadKey: string;
|
|
419
552
|
/** Optional thread title, set on first creation. */
|
|
@@ -484,6 +617,22 @@ interface AgentInboundChannel {
|
|
|
484
617
|
interface AgentConfig {
|
|
485
618
|
/** Restrict the tools the model may call, by name. Default: all tools. */
|
|
486
619
|
activeTools?: ReadonlyArray<string>;
|
|
620
|
+
/**
|
|
621
|
+
* How long a human-in-the-loop tool approval may stay pending before the
|
|
622
|
+
* run stops waiting — a Cloudflare Workflows duration: milliseconds, or a
|
|
623
|
+
* `"<n> <unit>"` string like `"3 days"` (the unit set is the host's, so a
|
|
624
|
+
* typo is a compile error). Default `"3 days"`.
|
|
625
|
+
*
|
|
626
|
+
* On timeout the call is treated as REJECTED (the run records why and
|
|
627
|
+
* continues down the normal rejection path), so a run whose approver never
|
|
628
|
+
* answers ends instead of hibernating forever.
|
|
629
|
+
*
|
|
630
|
+
* CLAMPED to one week. A longer wait would outlive the thread's
|
|
631
|
+
* abandoned-run horizon, letting a new run reclaim the thread while the
|
|
632
|
+
* approval is still pending — which is the exact failure this timeout
|
|
633
|
+
* exists to prevent, so it cannot be configured back into existence.
|
|
634
|
+
*/
|
|
635
|
+
approvalTimeout?: `${number} ${"day" | "hour" | "minute" | "month" | "second" | "week" | "year"}${"s" | ""}` | number;
|
|
487
636
|
/**
|
|
488
637
|
* Automatic thread-history compaction. When the persisted history exceeds
|
|
489
638
|
* `maxMessages`, the loop summarizes the older messages (all but the most
|
|
@@ -530,7 +679,7 @@ interface AgentConfig {
|
|
|
530
679
|
model: AgentModelInput;
|
|
531
680
|
/**
|
|
532
681
|
* Optional override for the deployed workflow name (`wrangler.jsonc`
|
|
533
|
-
* `workflows[].name`). Defaults to `agent
|
|
682
|
+
* `workflows[].name`). Defaults to `agent-<kebab-cased export name>`. Does
|
|
534
683
|
* NOT change the binding name, which is always derived from the export
|
|
535
684
|
* name (`support` → `AGENT_SUPPORT`).
|
|
536
685
|
*/
|
|
@@ -543,7 +692,16 @@ interface AgentConfig {
|
|
|
543
692
|
*
|
|
544
693
|
* - `"reject"` (default) — fail the new run fast with a `CONFLICT` error.
|
|
545
694
|
* - `"replace"` — terminate the in-flight instance and take the thread over.
|
|
546
|
-
* - `"queue"` —
|
|
695
|
+
* - `"queue"` — park the new run behind the one in flight (FIFO, up to five deep) and hibernate it until the thread is handed over.
|
|
696
|
+
*
|
|
697
|
+
* Each parked run is a live workflow instance waiting on an event, so the
|
|
698
|
+
* queue's depth cap is a real resource bound: past it, a start is rejected
|
|
699
|
+
* exactly as `"reject"` would. A `"replace"` arriving later supersedes the
|
|
700
|
+
* run in FLIGHT, not the queue — parked runs still take their turn after it.
|
|
701
|
+
*
|
|
702
|
+
* A dispatch with no instance id (the inbound-email / inbound-channel
|
|
703
|
+
* paths) cannot be parked, because nothing later can tell it apart from
|
|
704
|
+
* another such dispatch to wake it; `"queue"` rejects those.
|
|
547
705
|
*
|
|
548
706
|
* A workflow REPLAY re-enters the bootstrap under the SAME instance id and
|
|
549
707
|
* is never a concurrent run (the guard compares the stored instance id).
|
|
@@ -563,6 +721,12 @@ interface AgentConfig {
|
|
|
563
721
|
* signature over the raw body before calling `map`.
|
|
564
722
|
*/
|
|
565
723
|
onInbound?: AgentInboundChannel;
|
|
724
|
+
/**
|
|
725
|
+
* Called with the final answer when the run was triggered from a channel
|
|
726
|
+
* that gave it a `replyRef` — see {@link AgentOnReply}. This is how an
|
|
727
|
+
* inbound-triggered agent answers where it was asked.
|
|
728
|
+
*/
|
|
729
|
+
onReply?: AgentOnReply;
|
|
566
730
|
/** Called after each LLM turn — see {@link AgentOnStepFinish}. */
|
|
567
731
|
onStepFinish?: AgentOnStepFinish;
|
|
568
732
|
/**
|
|
@@ -577,7 +741,7 @@ interface AgentConfig {
|
|
|
577
741
|
* Opt this agent into being STARTED over the public RPC boundary — i.e. via
|
|
578
742
|
* the auto-registered `agents:agentRun` mutation an HTTP-only client (e.g.
|
|
579
743
|
* the `@lunora/mcp` server) calls. Default `false`: an agent is startable
|
|
580
|
-
* only from server-side app code (`ctx.agents
|
|
744
|
+
* only from server-side app code (`ctx.agents.<name>.run(...)`), so declaring
|
|
581
745
|
* an agent does NOT expose it to arbitrary RPC callers. Fail-closed — the run
|
|
582
746
|
* mutation refuses an agent that has not opted in, regardless of any MCP-side
|
|
583
747
|
* `allowAgents` configuration. A started thread is still owner-scoped to
|
|
@@ -649,8 +813,20 @@ interface AgentVoiceConfig {
|
|
|
649
813
|
/**
|
|
650
814
|
* Spoken on connect before the first user turn — a fixed greeting synthesized
|
|
651
815
|
* through the TTS model. Omit for a silent-until-spoken-to session.
|
|
816
|
+
*
|
|
817
|
+
* Synthesized once per THREAD, not once per socket: the greeting's persisted
|
|
818
|
+
* row is keyed per thread, and a reconnect onto a thread that already exists
|
|
819
|
+
* gets the `ready` frame without paying for the same line again.
|
|
652
820
|
*/
|
|
653
821
|
greeting?: string;
|
|
822
|
+
/**
|
|
823
|
+
* Cap on how many turns one voice socket may run before it is closed with
|
|
824
|
+
* code `4002`. Every turn is a full LLM generation plus sentence-by-sentence
|
|
825
|
+
* TTS — billed and persisted — on a hibernatable socket that can live for
|
|
826
|
+
* days, and the one-turn-in-flight guard throttles nothing. Defaults to 100;
|
|
827
|
+
* a client that hits the cap reconnects for a fresh budget.
|
|
828
|
+
*/
|
|
829
|
+
maxTurns?: number;
|
|
654
830
|
/**
|
|
655
831
|
* TTS voice/speaker id forwarded to the TTS model (e.g. a Deepgram Aura voice
|
|
656
832
|
* like `"aura-asteria-en"`). Model-specific; omitted when unset so the model
|
|
@@ -685,10 +861,10 @@ interface AgentSubToolInput {
|
|
|
685
861
|
interface AgentAsToolOptions {
|
|
686
862
|
/** What the sub-agent does — shown to the parent's model (it decides from it). */
|
|
687
863
|
description: string;
|
|
688
|
-
/** Cap on child-run status polls before giving up. Default 120. */
|
|
864
|
+
/** Cap on child-run status polls before giving up — a positive integer. Default 120. */
|
|
689
865
|
maxPolls?: number;
|
|
690
866
|
/**
|
|
691
|
-
* The child agent's export name — selects its `AGENT_
|
|
867
|
+
* The child agent's export name — selects its `AGENT_<NAME>` Workflow
|
|
692
868
|
* binding (e.g. `"researcher"` → `AGENT_RESEARCHER`). The model-facing tool
|
|
693
869
|
* name is the KEY assigned in the parent's `tools` map, not this.
|
|
694
870
|
*/
|
|
@@ -725,11 +901,39 @@ interface AgentDefinition extends AgentConfig {
|
|
|
725
901
|
*/
|
|
726
902
|
memorySources?: ReadonlyArray<AgentMemorySource>;
|
|
727
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* What `agentEnsureThread` reports back to the loop.
|
|
906
|
+
*
|
|
907
|
+
* A discriminated union rather than a bag of optional booleans: the four
|
|
908
|
+
* outcomes are mutually exclusive, and the data each carries only exists for its
|
|
909
|
+
* own case. `queued` has a position, `replaced` has the instance it took the
|
|
910
|
+
* thread from, and the other two have nothing — encoding that as five
|
|
911
|
+
* independent optional fields made every reader re-derive which combination was
|
|
912
|
+
* legal.
|
|
913
|
+
* @experimental
|
|
914
|
+
*/
|
|
915
|
+
type EnsureThreadOutcome = {
|
|
916
|
+
outcome: "continued" | "created";
|
|
917
|
+
} | {
|
|
918
|
+
outcome: "queued";
|
|
919
|
+
position: number;
|
|
920
|
+
} | {
|
|
921
|
+
outcome: "replaced";
|
|
922
|
+
priorInstanceId: string;
|
|
923
|
+
};
|
|
728
924
|
/**
|
|
729
925
|
* Params of one agent run (the compiled workflow's payload).
|
|
730
926
|
* @experimental
|
|
731
927
|
*/
|
|
732
928
|
interface AgentRunInput {
|
|
929
|
+
/**
|
|
930
|
+
* Sub-agent delegation depth. A run a user starts omits it (0); each
|
|
931
|
+
* `agent.asTool` hop stamps its child one deeper, and the tool refuses to
|
|
932
|
+
* delegate past the bound — `maxTurns` bounds one level's turns, this bounds
|
|
933
|
+
* the TREE (every hop mints a distinct child `threadKey`, so the per-thread
|
|
934
|
+
* run-queue cap never applies across them).
|
|
935
|
+
*/
|
|
936
|
+
depth?: number;
|
|
733
937
|
/** The user message that starts (or continues) the thread. */
|
|
734
938
|
input: string;
|
|
735
939
|
/**
|
|
@@ -741,6 +945,8 @@ interface AgentRunInput {
|
|
|
741
945
|
* the first run.
|
|
742
946
|
*/
|
|
743
947
|
owner?: string;
|
|
948
|
+
/** Where a triggered run sends its answer — see {@link AgentReplyRef}. */
|
|
949
|
+
replyRef?: AgentReplyRef;
|
|
744
950
|
/** The thread key — reuse to continue a conversation. */
|
|
745
951
|
threadKey: string;
|
|
746
952
|
/** Optional thread title, set on first creation. */
|
|
@@ -773,6 +979,17 @@ interface AgentRunResult {
|
|
|
773
979
|
*/
|
|
774
980
|
interface AgentFunctionPaths {
|
|
775
981
|
appendMessage: string;
|
|
982
|
+
/**
|
|
983
|
+
* The internal `agents:agentCompleteRun` mutation the loop dispatches at the
|
|
984
|
+
* end of a run: it writes the terminal status AND hands the thread to the
|
|
985
|
+
* next queued run in the same mutation.
|
|
986
|
+
*/
|
|
987
|
+
completeRun: string;
|
|
988
|
+
/**
|
|
989
|
+
* The internal `agents:agentDeleteMessage` mutation the loop dispatches to
|
|
990
|
+
* retire the HITL approval marker once the decision has landed.
|
|
991
|
+
*/
|
|
992
|
+
deleteMessage: string;
|
|
776
993
|
ensureThread: string;
|
|
777
994
|
/** The internal `agents:agentEpisodeRecall` query the loop dispatches for an episodic-kind read. */
|
|
778
995
|
episodeRecall: string;
|
|
@@ -1078,7 +1295,7 @@ interface AgentRunHandle {
|
|
|
1078
1295
|
id: string;
|
|
1079
1296
|
}
|
|
1080
1297
|
/**
|
|
1081
|
-
* The `ctx.agents
|
|
1298
|
+
* The `ctx.agents.<name>` producer handle.
|
|
1082
1299
|
* @experimental
|
|
1083
1300
|
*/
|
|
1084
1301
|
interface AgentHandle {
|
|
@@ -1111,4 +1328,4 @@ interface AgentHandle {
|
|
|
1111
1328
|
/** Read a run's workflow status by instance id. */
|
|
1112
1329
|
status: (id: string) => Promise<unknown>;
|
|
1113
1330
|
}
|
|
1114
|
-
export { AgentDefinition as A,
|
|
1331
|
+
export { AgentVoiceConfig as $, AgentDefinition as A, AgentApprovalContext as B, AgentEmailMapper as C, AgentEmailRun as D, AgentGenerateOptions as E, AgentGenerateResult as F, AgentInstructionsContext as G, AgentLiveEvent as H, InboundChannelEvent as I, AgentMemoryOptions as J, AgentMemorySource as K, AgentMessageStatus as L, AgentOnReply as M, AgentOnStepFinish as N, AgentPrepareStep as O, AgentPrepareStepInput as P, AgentPrepareStepResult as Q, AgentProgressEvent as R, SkillMarkdownExtras as S, AgentReplyInfo as T, AgentRunHandle as U, AgentStepFinishInfo as V, AgentStepInfo as W, AgentThreadStatus as X, AgentTokenDelta as Y, AgentToolCall as Z, AgentUsage as _, AgentReplyRef as a, AgentWorkflowBindingLike as a0, AgentWorkflowInstanceLike as a1, EnsureThreadOutcome as a2, AgentToolContext as b, AgentToolDefinition as c, SkillDefinition as d, AgentCompact as e, AgentEpisodeExtract as f, AgentGraphExtract as g, AgentGenerate as h, AgentTokenSink as i, AgentRunInput as j, AgentFunctionPaths as k, AgentRunFunction as l, AgentStepLike as m, AgentStreamGenerate as n, AgentRunResult as o, AgentMessageRow as p, AgentConfig as q, AnyAgentTool as r, AgentAsToolOptions as s, AgentSubToolInput as t, AgentBindingSpec as u, AgentHandle as v, AgentToolConfig as w, AgentFunctionReference as x, AgentModelInput as y, SkillConfig as z };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{f as P,a as q}from"./base64-5eyBfWO3.mjs";import{compactHistory as z}from"./runAgentLoop-oAmn3OyN.mjs";import{buildModelMessages as G}from"./buildModelMessages-Y4tGo1T9.mjs";import{toFunctionReference as T}from"./AGENT_MODULE-M4D1EejI.mjs";const D=new TextDecoder;new TextEncoder;const N="=",Q=t=>{if(t)try{const e=t[0]==="{"?t:D.decode(P(t)),n=JSON.parse(e);if(n&&typeof n=="object"&&!Array.isArray(n))return n}catch{}},ct=t=>{if(t){if(!t.startsWith(N))return t;try{return D.decode(P(t.slice(N.length)))}catch{return}}},dt=t=>{const e=Number(t);return Number.isFinite(e)&&e>0?e:void 0},ut=t=>typeof t=="number"&&Date.now()>=t,ft=t=>{try{t.send(JSON.stringify({code:"TOKEN_EXPIRED",error:{code:"TOKEN_EXPIRED",message:"authentication token expired"},type:"error"})),t.close?.(4001,"token_expired")}catch{}},Y=16e3,Z=1,tt=16,M=/[.!?]\s/u,et=/\s/u,nt=async function*(t){if(t instanceof Uint8Array){yield t;return}if(t instanceof ReadableStream){const e=t.getReader();try{for(;;){const{done:n,value:r}=await e.read();if(n)break;yield r}}finally{e.releaseLock()}return}yield*t},yt=(t,e=Y,n=Z,r=tt)=>{const i=n*r/8,p=e*i,m=new ArrayBuffer(44+t.byteLength),s=new DataView(m),u=(l,E)=>{for(let d=0;d<E.length;d+=1)s.setUint8(l+d,E.codePointAt(d)??0)};u(0,"RIFF"),s.setUint32(4,36+t.byteLength,!0),u(8,"WAVE"),u(12,"fmt "),s.setUint32(16,16,!0),s.setUint16(20,1,!0),s.setUint16(22,n,!0),s.setUint32(24,e,!0),s.setUint32(28,p,!0),s.setUint16(32,i,!0),s.setUint16(34,r,!0),u(36,"data"),s.setUint32(40,t.byteLength,!0);const a=new Uint8Array(m);return a.set(t,44),a},st=t=>{const e=[];let n=t,r=M.exec(n);for(;r;){let i=r.index+1;for(;i<n.length&&et.test(n[i]??"");)i+=1;const p=n.slice(0,i).trim();p.length>0&&e.push(p),n=n.slice(i),r=M.exec(n)}return{rest:n,sentences:e}},pt=async t=>{const{agent:e,compact:n,connectionId:r,env:i,exportName:p,owner:m,paths:s,pcm:u,run:a,send:l,sendAudio:E,signal:d,streamGenerate:C,synthesize:H,text:L,threadKey:o,transcribe:F,turn:U,waitForDrain:O}=t,h=()=>d.aborted,v=T(s.appendMessage),$=T(s.ensureThread),B=T(s.listMessages),b=T(s.patchThread);await a($,{agent:p,key:o,...e.initialState===void 0?{}:{initialState:e.initialState},...m===void 0?{}:{owner:m}});try{const f=(L??(u?await F(u):"")).trim();if(f.length===0)return await a(b,{key:o,status:"idle"}),{assistantText:"",interrupted:!1,userText:""};l({text:f,type:"user_transcript"}),await a(v,{content:f,messageKey:`voice:${r}:${String(U)}:user`,role:"user",threadKey:o}),await a(b,{key:o,status:"running"});const I=typeof e.instructions=="function"?e.instructions({env:i,input:f,threadKey:o}):e.instructions,K=await a(B,{key:o}),{history:j,summary:R}=await z({agent:e,compact:n,env:i},K),W=G({history:j,...I===void 0?{}:{instructions:I},...R===void 0?{}:{summary:R}});let g="",w="",S=Promise.resolve();const V=async c=>{if(h()||c.length===0)return;let x=!1;try{for await(const y of nt(await H(c,d))){if(h()||(await O?.(),h()))break;E(y),x=!0}}catch(y){l({message:y instanceof Error?y.message:String(y),type:"error"})}x&&(w=w.length>0?`${w} ${c}`:c)},_=c=>{S=S.then(async()=>V(c))},X=await C({messages:W,signal:d},c=>{if(h())return;l({text:c,type:"assistant_delta"}),g+=c;const{rest:x,sentences:y}=st(g);g=x;for(const J of y)_(J)});!h()&&g.trim().length>0&&_(g.trim()),g="",await S;const A=h(),k=A?w:X.text;return await a(v,{content:k,messageKey:`voice:${r}:${String(U)}:assistant`,role:"assistant",threadKey:o}),await a(b,{key:o,status:"idle"}),l(A?{type:"interrupted"}:{text:k,type:"assistant_done"}),{assistantText:k,interrupted:A,userText:f}}catch(f){throw await a(b,{key:o,status:"idle"}),f}},lt=t=>Q(t),ht=t=>{if(typeof t=="object"&&t!==null&&"text"in t){const{text:e}=t;return typeof e=="string"?e:""}return""},gt=t=>{if(t instanceof Uint8Array||t instanceof ReadableStream)return t;if(t instanceof Response&&t.body)return t.body;if(typeof t=="object"&&t!==null&&"audio"in t){const{audio:e}=t;if(typeof e=="string")return q(e);if(e instanceof ReadableStream||e instanceof Uint8Array)return e}return new Uint8Array(0)};export{dt as a,ft as b,yt as c,ct as d,ht as e,gt as f,ut as i,lt as p,pt as r,nt as t};
|
package/dist/reply.d.mts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Mailer } from '@lunora/mail';
|
|
2
|
+
import { InboundEmail } from '@lunora/mail/inbound';
|
|
3
|
+
import { a as AgentReplyRef } from "./packem_shared/types.d-DTKxPuh_.mjs";
|
|
4
|
+
import 'ai';
|
|
5
|
+
/** The email arm of {@link AgentReplyRef}, narrowed for the helpers below. */
|
|
6
|
+
type EmailReplyRef = Extract<AgentReplyRef, {
|
|
7
|
+
channel: "email";
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Capture a reply reference from an already-parsed inbound email — call it in
|
|
11
|
+
* an `onEmail` mapper and put the result on the run's `replyRef`.
|
|
12
|
+
*
|
|
13
|
+
* Returns `undefined` when the message carries no `Message-ID`: there is
|
|
14
|
+
* nothing to thread a reply against, and an unthreaded reply lands as a new
|
|
15
|
+
* conversation in the recipient's client, which is worse than not replying.
|
|
16
|
+
*/
|
|
17
|
+
declare const captureEmailReplyRef: (email: InboundEmail) => EmailReplyRef | undefined;
|
|
18
|
+
/** RFC 5322 threading: append the replied-to id to any existing `References` chain. */
|
|
19
|
+
declare const buildReferences: (replyRef: EmailReplyRef) => string;
|
|
20
|
+
/** Subject + body of a reply. */
|
|
21
|
+
interface ReplyBody {
|
|
22
|
+
subject: string;
|
|
23
|
+
text: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Send a threaded reply through the app's own `@lunora/mail` mailer.
|
|
27
|
+
*
|
|
28
|
+
* The mailer is a parameter rather than something this module constructs: the
|
|
29
|
+
* app already configures one (API key or `send_email` binding), and passing it
|
|
30
|
+
* in keeps this module free of any runtime dependency on `@lunora/mail`.
|
|
31
|
+
*
|
|
32
|
+
* The reply goes to the original sender, from the mailer's configured default.
|
|
33
|
+
* An app running several aliases that wants the reply to come from whichever
|
|
34
|
+
* mailbox received the message passes `from: replyRef.to[0]` itself — a
|
|
35
|
+
* decision only the app can make.
|
|
36
|
+
*/
|
|
37
|
+
declare const replyToEmail: (mailer: Mailer, replyRef: EmailReplyRef, body: ReplyBody) => Promise<{
|
|
38
|
+
id: string;
|
|
39
|
+
}>;
|
|
40
|
+
export { type EmailReplyRef, ReplyBody, buildReferences, captureEmailReplyRef, replyToEmail };
|
package/dist/reply.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Mailer } from '@lunora/mail';
|
|
2
|
+
import { InboundEmail } from '@lunora/mail/inbound';
|
|
3
|
+
import { a as AgentReplyRef } from "./packem_shared/types.d-DTKxPuh_.js";
|
|
4
|
+
import 'ai';
|
|
5
|
+
/** The email arm of {@link AgentReplyRef}, narrowed for the helpers below. */
|
|
6
|
+
type EmailReplyRef = Extract<AgentReplyRef, {
|
|
7
|
+
channel: "email";
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Capture a reply reference from an already-parsed inbound email — call it in
|
|
11
|
+
* an `onEmail` mapper and put the result on the run's `replyRef`.
|
|
12
|
+
*
|
|
13
|
+
* Returns `undefined` when the message carries no `Message-ID`: there is
|
|
14
|
+
* nothing to thread a reply against, and an unthreaded reply lands as a new
|
|
15
|
+
* conversation in the recipient's client, which is worse than not replying.
|
|
16
|
+
*/
|
|
17
|
+
declare const captureEmailReplyRef: (email: InboundEmail) => EmailReplyRef | undefined;
|
|
18
|
+
/** RFC 5322 threading: append the replied-to id to any existing `References` chain. */
|
|
19
|
+
declare const buildReferences: (replyRef: EmailReplyRef) => string;
|
|
20
|
+
/** Subject + body of a reply. */
|
|
21
|
+
interface ReplyBody {
|
|
22
|
+
subject: string;
|
|
23
|
+
text: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Send a threaded reply through the app's own `@lunora/mail` mailer.
|
|
27
|
+
*
|
|
28
|
+
* The mailer is a parameter rather than something this module constructs: the
|
|
29
|
+
* app already configures one (API key or `send_email` binding), and passing it
|
|
30
|
+
* in keeps this module free of any runtime dependency on `@lunora/mail`.
|
|
31
|
+
*
|
|
32
|
+
* The reply goes to the original sender, from the mailer's configured default.
|
|
33
|
+
* An app running several aliases that wants the reply to come from whichever
|
|
34
|
+
* mailbox received the message passes `from: replyRef.to[0]` itself — a
|
|
35
|
+
* decision only the app can make.
|
|
36
|
+
*/
|
|
37
|
+
declare const replyToEmail: (mailer: Mailer, replyRef: EmailReplyRef, body: ReplyBody) => Promise<{
|
|
38
|
+
id: string;
|
|
39
|
+
}>;
|
|
40
|
+
export { type EmailReplyRef, ReplyBody, buildReferences, captureEmailReplyRef, replyToEmail };
|
package/dist/reply.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=e=>{if(e.messageId!==void 0)return{channel:"email",from:e.from,messageId:e.messageId,to:e.to,...e.references===void 0?{}:{references:e.references}}},n=e=>e.references===void 0?e.messageId:`${e.references} ${e.messageId}`,c=async(e,s,r)=>e.send({headers:{"In-Reply-To":s.messageId,References:n(s)},subject:r.subject,text:r.text,to:s.from});export{n as buildReferences,t as captureEmailReplyRef,c as replyToEmail};
|
package/dist/sandbox.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { b as AgentToolContext, c as AgentToolDefinition } from "./packem_shared/types.d-DTKxPuh_.mjs";
|
|
2
2
|
import '@lunora/mail/inbound';
|
|
3
3
|
import 'ai';
|
|
4
4
|
/**
|
|
@@ -25,8 +25,8 @@ type BrowserToolInput = {
|
|
|
25
25
|
/**
|
|
26
26
|
* The model-provided input to a {@link containerTool} call — a discriminated
|
|
27
27
|
* union on `op`. `fetch` sends an HTTP request to the container; `exec` asks it
|
|
28
|
-
* to run a command (
|
|
29
|
-
*
|
|
28
|
+
* to run a command (delegated to `ctx.containers.<name>.exec`, the first-class
|
|
29
|
+
* exec contract in `@lunora/container`; the container app serves its route).
|
|
30
30
|
* @experimental
|
|
31
31
|
*/
|
|
32
32
|
type ContainerToolInput = {
|
|
@@ -112,12 +112,13 @@ interface ContainerToolOptions {
|
|
|
112
112
|
description?: string;
|
|
113
113
|
/**
|
|
114
114
|
* Gate a call behind a human approval. Defaults to gating any command
|
|
115
|
-
* execution
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* Pass a boolean or your own
|
|
120
|
-
* replay-stable input, so keep it
|
|
115
|
+
* execution — an `exec` — plus any `fetch` using a non-idempotent method
|
|
116
|
+
* (POST/PUT/PATCH/DELETE), so a prompt-injected model cannot reach some
|
|
117
|
+
* other mutating route unattended. A read-only `fetch` runs unattended. The
|
|
118
|
+
* exec route itself needs no special case: `@lunora/container` reserves
|
|
119
|
+
* `/__lunora/*` and refuses a `fetch` into it. Pass a boolean or your own
|
|
120
|
+
* predicate to change that. Evaluated from replay-stable input, so keep it
|
|
121
|
+
* deterministic.
|
|
121
122
|
*/
|
|
122
123
|
needsApproval?: ((input: ContainerToolInput) => boolean) | boolean;
|
|
123
124
|
}
|
|
@@ -149,19 +150,19 @@ interface ContainerToolOptions {
|
|
|
149
150
|
declare const browserTool: (options?: BrowserToolOptions) => AgentToolDefinition<BrowserToolInput, string>;
|
|
150
151
|
/**
|
|
151
152
|
* A batteries-included agent tool that talks to a declared Cloudflare
|
|
152
|
-
* Container. `name` is the `ctx.containers
|
|
153
|
+
* Container. `name` is the `ctx.containers.<name>` key (the `lunora/containers.ts`
|
|
153
154
|
* export). One tool exposes `fetch` (HTTP request) and `exec` (run a command);
|
|
154
155
|
* the model picks via `op`. The call dispatches to the auto-registered
|
|
155
156
|
* `sandbox:invoke` action, which carries `ctx.containers`.
|
|
156
157
|
*
|
|
157
158
|
* By default a read-only (GET/HEAD/OPTIONS, or method-omitted) `fetch` runs
|
|
158
159
|
* unattended while everything else is gated behind a human approval — an
|
|
159
|
-
* `exec`; a `fetch` whose path resolves to the privileged
|
|
160
|
+
* `exec`; a `fetch` whose path resolves to the privileged exec route (both
|
|
160
161
|
* reach the same command-execution path in the container, so gating on the
|
|
161
|
-
* `op` name alone would let a `fetch` to
|
|
162
|
+
* `op` name alone would let a `fetch` to it run a command unattended);
|
|
162
163
|
* and a `fetch` using a non-idempotent method (POST/PUT/PATCH/DELETE) to ANY
|
|
163
164
|
* route, since a prompt-injected model could otherwise mutate container state
|
|
164
|
-
* through some other privileged route just by avoiding the literal
|
|
165
|
+
* through some other privileged route just by avoiding the literal exec
|
|
165
166
|
* path. A `fetch` can still reach any *other read* route unattended, so scope
|
|
166
167
|
* the container's GET routes accordingly. Pass `opts.needsApproval` to widen
|
|
167
168
|
* or disable the gate.
|