@fungi.computer/shiitake 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -21
- package/dist/agent-control.d.ts +94 -2
- package/dist/agent-control.js +147 -26
- package/dist/client-entry.d.ts +20 -0
- package/dist/client-entry.js +5 -0
- package/dist/client-visitor.d.ts +33 -0
- package/dist/client-visitor.js +231 -0
- package/dist/client-wire.d.ts +9 -0
- package/dist/client-wire.js +24 -3
- package/dist/client.d.ts +27 -6
- package/dist/client.js +23 -0
- package/dist/cloudflare.d.ts +20 -0
- package/dist/cloudflare.js +4 -1
- package/dist/delegation.d.ts +79 -53
- package/dist/delegation.js +26 -21
- package/dist/effect.d.ts +26 -1
- package/dist/effect.js +14 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -0
- package/dist/extensions.d.ts +22 -3
- package/dist/extensions.js +1 -1
- package/dist/fleet.d.ts +25 -2
- package/dist/fleet.js +108 -24
- package/dist/hosted.js +3 -0
- package/dist/http-routes.d.ts +23 -9
- package/dist/http.d.ts +1 -0
- package/dist/http.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/internal/accepted-run-executor.js +2 -1
- package/dist/internal/admission-door.d.ts +6 -1
- package/dist/internal/admission-door.js +33 -3
- package/dist/internal/contracts.d.ts +19 -1
- package/dist/internal/coordinator.js +115 -52
- package/dist/internal/effect-client.js +1 -1
- package/dist/internal/effect-contracts.d.ts +9 -2
- package/dist/internal/extension-command-registry.d.ts +2 -1
- package/dist/internal/extension-command-registry.js +3 -1
- package/dist/internal/extension-runtime.d.ts +3 -1
- package/dist/internal/extension-runtime.js +18 -8
- package/dist/internal/model-acquisition.js +2 -0
- package/dist/internal/observation-kernel.d.ts +2 -0
- package/dist/internal/observation-kernel.js +17 -6
- package/dist/internal/promise-facade.js +2 -0
- package/dist/internal/run-scope.d.ts +14 -0
- package/dist/internal/run-scope.js +58 -15
- package/dist/internal/runtime-coordinator.d.ts +14 -0
- package/dist/internal/runtime-coordinator.js +60 -11
- package/dist/internal/runtime-core.d.ts +3 -0
- package/dist/internal/runtime-core.js +1 -0
- package/dist/internal/runtime-reconciliation.js +123 -25
- package/dist/internal/session-context.d.ts +5 -5
- package/dist/internal/session-context.js +80 -83
- package/dist/internal/session-status.d.ts +2 -2
- package/dist/internal/session-status.js +5 -3
- package/dist/internal/woodstock/agent-reply.d.ts +5 -0
- package/dist/internal/woodstock/agent-reply.js +3 -0
- package/dist/internal/woodstock/client-projection.js +13 -11
- package/dist/internal/woodstock/fleet-store.d.ts +3 -2
- package/dist/internal/woodstock/fleet-store.js +24 -4
- package/dist/internal/woodstock/index.d.ts +7 -3
- package/dist/internal/woodstock/index.js +23 -6
- package/dist/internal/woodstock/progress.d.ts +11 -1
- package/dist/internal/woodstock/progress.js +126 -25
- package/dist/internal/woodstock/session-delegator.d.ts +13 -0
- package/dist/internal/woodstock/session-delegator.js +44 -0
- package/dist/internal/woodstock/session-entry.d.ts +12 -2
- package/dist/internal/woodstock/session-store.d.ts +16 -2
- package/dist/internal/woodstock/session-store.js +349 -66
- package/dist/internal/woodstock/work-senders.d.ts +32 -0
- package/dist/internal/woodstock/work-senders.js +70 -0
- package/dist/node.js +11 -2
- package/dist/remote-client.js +7 -2
- package/dist/server-visitor-stream.d.ts +13 -0
- package/dist/server-visitor-stream.js +124 -0
- package/dist/session-attention.d.ts +30 -0
- package/dist/session-attention.js +14 -0
- package/docs/api/agent-control.md +212 -15
- package/docs/api/client-entry.md +3 -0
- package/docs/api/client.md +209 -5
- package/docs/api/cloudflare.md +23 -1
- package/docs/api/delegation.md +284 -77
- package/docs/api/effect.md +100 -12
- package/docs/api/extensions.md +73 -12
- package/docs/api/fleet.md +138 -25
- package/docs/api/http.md +28 -0
- package/docs/api/index.md +43 -17
- package/docs/api/testing.md +9 -7
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -55,10 +55,12 @@ without waiting for the command response or matching text.
|
|
|
55
55
|
npm install @fungi.computer/shiitake
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
The package ships ESM only
|
|
59
|
-
packages [Cairn](../cairn/README.md), [Mule](../mule/README.md),
|
|
58
|
+
The package ships ESM only. Its runtime dependencies include Effect, Zod, Pi and
|
|
59
|
+
the Fungi packages [Cairn](../cairn/README.md), [Mule](../mule/README.md),
|
|
60
60
|
[Mycelium](../mycelium/README.md), [Watchdog](../watchdog/README.md),
|
|
61
|
-
[Bird Dog](../bird-dog/README.md) and Knapsack
|
|
61
|
+
[Bird Dog](../bird-dog/README.md) and Knapsack. See the [manifest](package.json)
|
|
62
|
+
for the complete list. Hono `^4.13.5` is a peer dependency for the HTTP
|
|
63
|
+
adapters.
|
|
62
64
|
|
|
63
65
|
`Shiitake.make(options)` is the Promise constructor. The Effect 4 constructor is
|
|
64
66
|
`make` from `@fungi.computer/shiitake/effect`. Both use the same owner. The host
|
|
@@ -134,6 +136,17 @@ a Session performs no I/O. A client uses `createShiitakeClient` from the
|
|
|
134
136
|
`/client` entry with the host's authorized transport. Releasing that client does
|
|
135
137
|
not cancel host work.
|
|
136
138
|
|
|
139
|
+
Live `progress` frames carry `SessionProgressDelta`: independent text and
|
|
140
|
+
thinking `{ offset, append }` values, with offsets in UTF-16 string units.
|
|
141
|
+
`appendSessionProgress` from `/client` folds appends idempotently; a gap waits
|
|
142
|
+
for the owner's reset. A read/watch/reset baseline carries only the latest 64
|
|
143
|
+
KiB per channel, its start offsets, and accurate truncation flags. A watch
|
|
144
|
+
continues after that tail; the eventual durable message is always complete.
|
|
145
|
+
Progress stays process-local, in bounded chunks, and never writes SQLite. Tiny
|
|
146
|
+
provider deltas are coalesced (first append immediately, then each 4,096 string
|
|
147
|
+
units or within 100 ms); durable completion carries any remaining partial chunk.
|
|
148
|
+
Tool tails travel only in `tool_output`, not in message progress.
|
|
149
|
+
|
|
137
150
|
`session.readSettlement(commandId, { timeoutMs, signal })` returns that exact
|
|
138
151
|
command's `CommandSettlement` (terminal, answer, usage and tool-call count),
|
|
139
152
|
independent of the selected transcript branch. `timeoutMs` defaults to zero and
|
|
@@ -354,24 +367,31 @@ The `/agent-control` source exposes
|
|
|
354
367
|
`agents.setModel({agentId, model, reasoning?})` and `agents.models({})` when its
|
|
355
368
|
host authorizes those operations. `name` labels the new Agent and `reasoning`
|
|
356
369
|
(`minimal` to `max`, one of `modelReasoningLevels`) needs a `model`.
|
|
357
|
-
`agents.create`
|
|
358
|
-
|
|
359
|
-
`
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
`
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
`
|
|
374
|
-
|
|
370
|
+
`agents.create` creates the Agent and its first Session through the same
|
|
371
|
+
idempotent Session-creation door as `agents.createSession`. It returns `agentId`
|
|
372
|
+
and a usable `sessionId`: send work immediately with `agents.send` and
|
|
373
|
+
`delivery: "steer"`, without a separate `createSession` call. Retrying the same
|
|
374
|
+
creation request reuses that first Session.
|
|
375
|
+
|
|
376
|
+
It also returns the Agent's `name`, the requested `model` and `reasoning` (the
|
|
377
|
+
stored default; absent when it inherits), and the `workingDirectory` its tools
|
|
378
|
+
use; `agents.list` reports the same facts for existing Agents. A host that can
|
|
379
|
+
archive also accepts `archived: true` there to list only the archived ones
|
|
380
|
+
(other hosts refuse that input). A host can offer `agents.archive({agentId})`
|
|
381
|
+
and `agents.unarchive`: an Agent archiving an Agent it created hides it from
|
|
382
|
+
lists and delegation without deleting anything, and the host refuses any other
|
|
383
|
+
target, the caller itself, or an Agent with a running command or delegated work
|
|
384
|
+
still waiting. Each refusal, and a send to an archived Agent, reaches the caller
|
|
385
|
+
as a `CapabilityFailed` diagnostic such as `archive:not_creator: …`,
|
|
386
|
+
`archive:self`, `archive:running`, `archive:pending`, `archive:unreadable` (its
|
|
387
|
+
store predates the schema) or `archive:archived`. `agents.wait` accepts
|
|
388
|
+
`timeoutMs` from 1 to 30000. Models use `provider/model` spelling and must be
|
|
389
|
+
offered by that host; `agents.models` returns at most 200 offered addresses from
|
|
390
|
+
signed-in or configured providers. A host model refusal reaches the caller as a
|
|
391
|
+
`CapabilityFailed` diagnostic such as `model:rejected: …` with up to ten
|
|
392
|
+
addresses from the host's offered catalog. If catalog diagnostics are
|
|
393
|
+
unavailable, the original refusal remains; other failures propagate unchanged.
|
|
394
|
+
The `/model-preference` resolver orders explicit Session, explicit Agent, the
|
|
375
395
|
person's last manual switch, Team default, then available login. Hosts own those
|
|
376
396
|
stored layers and credential admission: choosing a model never bypasses an
|
|
377
397
|
owner's Team or Agent key-source restriction. A host can refuse an unrunnable
|
|
@@ -402,6 +422,24 @@ a host-supplied fetch.
|
|
|
402
422
|
interrupted tools against the existing replay rules. They are local proofs, not
|
|
403
423
|
a guarantee about another deployment's storage or sandbox.
|
|
404
424
|
|
|
425
|
+
## Breaking live-progress API
|
|
426
|
+
|
|
427
|
+
The S4 offset/append contract replaces cumulative progress frames:
|
|
428
|
+
|
|
429
|
+
- `progress` frames carry `SessionProgressDelta`, with separate text and
|
|
430
|
+
thinking `{ offset, append }` channels. Offsets count UTF-16 code units.
|
|
431
|
+
- `SessionProgress` is the bounded snapshot shape and now requires `textOffset`
|
|
432
|
+
and `thinkingOffset`, alongside its truncation flags.
|
|
433
|
+
- `appendSessionProgress(current, delta)` is the new shared fold for progress
|
|
434
|
+
frames. Seed it from the **same watch's `snapshot.progress`**, replace it on a
|
|
435
|
+
reset, and retain it across unrelated updates until the message completes or
|
|
436
|
+
the queue becomes idle. A separate earlier `read()` is not a watch baseline.
|
|
437
|
+
- The visitor stream sends live `activity` frames as well as `progress` frames.
|
|
438
|
+
An activity frame updates activity facts; it is not a replacement snapshot.
|
|
439
|
+
|
|
440
|
+
All producers and consumers must use this contract together. There is no
|
|
441
|
+
cumulative-frame compatibility format.
|
|
442
|
+
|
|
405
443
|
## Client entries
|
|
406
444
|
|
|
407
445
|
A host selects client entries separately from its durable extension roster.
|
package/dist/agent-control.d.ts
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
1
|
-
import { type CapabilitySource, type CapabilitySession } from "@fungi.computer/mycelium";
|
|
1
|
+
import { type CapabilitySource, type MyceliumJsonValue, type CapabilitySession } from "@fungi.computer/mycelium";
|
|
2
2
|
import type { Accepted, CancelResult, Held, ShiitakeAgent, ShiitakeSession } from "./client.js";
|
|
3
|
+
import { z } from "zod";
|
|
3
4
|
import { type ModelPreference } from "./model-preference.js";
|
|
5
|
+
/** Sender-authenticated submission for one child Session's routed Ask. */
|
|
6
|
+
export type AgentControlAnswerInput = Readonly<{
|
|
7
|
+
agentId: string;
|
|
8
|
+
sessionId: string;
|
|
9
|
+
askId: string;
|
|
10
|
+
requestId: string;
|
|
11
|
+
answers: MyceliumJsonValue;
|
|
12
|
+
}>;
|
|
13
|
+
/** Sender-authenticated escalation of one item a child Session left waiting. */
|
|
14
|
+
export type AgentControlEscalateInput = Readonly<{
|
|
15
|
+
agentId: string;
|
|
16
|
+
sessionId: string;
|
|
17
|
+
itemId: string;
|
|
18
|
+
note?: string;
|
|
19
|
+
}>;
|
|
20
|
+
/** Typed outcome of a sender's answer to a child Ask. */
|
|
21
|
+
export declare const answerOutput: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
22
|
+
status: z.ZodLiteral<"answered">;
|
|
23
|
+
via: z.ZodEnum<{
|
|
24
|
+
in_turn: "in_turn";
|
|
25
|
+
follow_up: "follow_up";
|
|
26
|
+
}>;
|
|
27
|
+
commandId: z.ZodExactOptional<z.ZodString>;
|
|
28
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
29
|
+
status: z.ZodLiteral<"not_found">;
|
|
30
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
31
|
+
status: z.ZodLiteral<"closed">;
|
|
32
|
+
reason: z.ZodEnum<{
|
|
33
|
+
cancelled: "cancelled";
|
|
34
|
+
undeliverable: "undeliverable";
|
|
35
|
+
answered: "answered";
|
|
36
|
+
declined: "declined";
|
|
37
|
+
withdrawn: "withdrawn";
|
|
38
|
+
expired: "expired";
|
|
39
|
+
}>;
|
|
40
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
41
|
+
status: z.ZodLiteral<"refused">;
|
|
42
|
+
reason: z.ZodEnum<{
|
|
43
|
+
not_sender: "not_sender";
|
|
44
|
+
no_provenance: "no_provenance";
|
|
45
|
+
}>;
|
|
46
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
47
|
+
status: z.ZodLiteral<"unavailable">;
|
|
48
|
+
reason: z.ZodLiteral<"unsupported">;
|
|
49
|
+
}, z.core.$strict>], "status">;
|
|
50
|
+
/** Typed outcome of a sender's escalation of a child's item. */
|
|
51
|
+
export declare const escalateOutput: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
52
|
+
status: z.ZodLiteral<"escalated">;
|
|
53
|
+
to: z.ZodLiteral<"person">;
|
|
54
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
55
|
+
status: z.ZodLiteral<"not_found">;
|
|
56
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
57
|
+
status: z.ZodLiteral<"closed">;
|
|
58
|
+
reason: z.ZodEnum<{
|
|
59
|
+
cancelled: "cancelled";
|
|
60
|
+
undeliverable: "undeliverable";
|
|
61
|
+
answered: "answered";
|
|
62
|
+
declined: "declined";
|
|
63
|
+
withdrawn: "withdrawn";
|
|
64
|
+
expired: "expired";
|
|
65
|
+
}>;
|
|
66
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
67
|
+
status: z.ZodLiteral<"refused">;
|
|
68
|
+
reason: z.ZodEnum<{
|
|
69
|
+
not_sender: "not_sender";
|
|
70
|
+
no_provenance: "no_provenance";
|
|
71
|
+
}>;
|
|
72
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
73
|
+
status: z.ZodLiteral<"unavailable">;
|
|
74
|
+
reason: z.ZodLiteral<"unsupported">;
|
|
75
|
+
}, z.core.$strict>], "status">;
|
|
76
|
+
/** Typed outcome of a sender's answer to a child Ask. */
|
|
77
|
+
export type AgentControlAnswerOutput = z.infer<typeof answerOutput>;
|
|
78
|
+
/** Typed outcome of a sender's escalation of a child's item. */
|
|
79
|
+
export type AgentControlEscalateOutput = z.infer<typeof escalateOutput>;
|
|
4
80
|
/**
|
|
5
81
|
* Why a host refuses an archive or restore: the caller did not create the
|
|
6
82
|
* Agent, is the Agent, the Agent has a running command or delegated work still
|
|
@@ -51,9 +127,15 @@ export type AgentDelegationInput = Readonly<{
|
|
|
51
127
|
sessionId: string;
|
|
52
128
|
delivery: "steer";
|
|
53
129
|
}> | Readonly<{
|
|
54
|
-
/** Omitted
|
|
130
|
+
/** Omitted to open a new Session when the recipient accepts. */
|
|
55
131
|
sessionId?: string;
|
|
56
132
|
delivery: "followUp";
|
|
133
|
+
/**
|
|
134
|
+
* Only for a send that opens a new Session. An objective sets that
|
|
135
|
+
* Session's goal; false opts out. Omitted, the send's first line is
|
|
136
|
+
* the objective. The host's directory applies it.
|
|
137
|
+
*/
|
|
138
|
+
goal?: string | false;
|
|
57
139
|
}>);
|
|
58
140
|
/** Exact authorized destination and caller's original send identity. */
|
|
59
141
|
export type AgentRecallInput = Readonly<{
|
|
@@ -85,6 +167,10 @@ export type AgentControlDirectory = Readonly<{
|
|
|
85
167
|
send?: (input: AgentDelegationInput, sourceSessionId: string, signal: AbortSignal, invocation?: AgentDelegationInvocation) => Promise<Accepted | Held>;
|
|
86
168
|
/** Recall under the same host-owned request identity as send. Never abort active work. */
|
|
87
169
|
recall?: (input: AgentRecallInput, sourceSessionId: string, signal: AbortSignal) => Promise<CancelResult>;
|
|
170
|
+
/** Answer a child Session's Ask as the exact Session that sent it the work. */
|
|
171
|
+
answer?: (input: AgentControlAnswerInput, senderSessionId: string, signal: AbortSignal) => Promise<AgentControlAnswerOutput>;
|
|
172
|
+
/** Pass a child's waiting item on to whom this Session's own work came from. */
|
|
173
|
+
escalate?: (input: AgentControlEscalateInput, senderSessionId: string, signal: AbortSignal) => Promise<AgentControlEscalateOutput>;
|
|
88
174
|
/** Present only when this host authorizes ordinary Agent creation. */
|
|
89
175
|
create?: (input: Readonly<{
|
|
90
176
|
requestId: string;
|
|
@@ -129,10 +215,16 @@ export type AgentControlDirectory = Readonly<{
|
|
|
129
215
|
/** Await one exact durable terminal without opening a transcript observation. */
|
|
130
216
|
export declare function waitForAgentSettlement(session: ShiitakeSession, commandId: string, timeoutMs: number, signal: AbortSignal, startedAt?: number): Promise<Readonly<{
|
|
131
217
|
acceptedSender: string | null;
|
|
218
|
+
delegator: string | null;
|
|
132
219
|
terminal: import("./client.js").SessionTerminal;
|
|
133
220
|
content: string;
|
|
134
221
|
usage: readonly Pick<import("@fungi.computer/mule/effect").AssistantMessage, "provider" | "model" | "usage">[];
|
|
135
222
|
toolCalls: number;
|
|
223
|
+
waitingOnYou: readonly Readonly<{
|
|
224
|
+
itemId: string;
|
|
225
|
+
kind: string;
|
|
226
|
+
summary: string;
|
|
227
|
+
}>[];
|
|
136
228
|
}>>;
|
|
137
229
|
/**
|
|
138
230
|
* A host's refusal of an Agent creating an Agent: the creator asked to give the
|
package/dist/agent-control.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CapabilityFailed, Mycelium, } from "@fungi.computer/mycelium";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { ShiitakeError } from "./errors.js";
|
|
4
|
+
import { reportCatchFailure } from "./internal/failure.js";
|
|
4
5
|
import { AGENT_REPLY_TAG } from "./internal/woodstock/index.js";
|
|
5
6
|
import { modelReasoningLevels, parseModelAddress, } from "./model-preference.js";
|
|
6
7
|
const id = z.string().min(1).max(256);
|
|
@@ -48,6 +49,59 @@ const readInput = sessionInput.extend({
|
|
|
48
49
|
/** A page's `oldestCursor`: read the page strictly before it. */
|
|
49
50
|
before: pageCursor.nullable().default(null),
|
|
50
51
|
});
|
|
52
|
+
const answerInput = sessionInput.extend({
|
|
53
|
+
askId: id,
|
|
54
|
+
requestId,
|
|
55
|
+
answers: z.json(),
|
|
56
|
+
});
|
|
57
|
+
const escalateInput = sessionInput.extend({
|
|
58
|
+
itemId: id,
|
|
59
|
+
note: z.string().min(1).max(1_000).exactOptional(),
|
|
60
|
+
});
|
|
61
|
+
const closedReasons = z.enum([
|
|
62
|
+
"answered",
|
|
63
|
+
"declined",
|
|
64
|
+
"withdrawn",
|
|
65
|
+
"expired",
|
|
66
|
+
"cancelled",
|
|
67
|
+
"undeliverable",
|
|
68
|
+
]);
|
|
69
|
+
const notSenderRefusal = z.strictObject({
|
|
70
|
+
status: z.literal("refused"),
|
|
71
|
+
reason: z.enum(["not_sender", "no_provenance"]),
|
|
72
|
+
});
|
|
73
|
+
const unsupported = z.strictObject({
|
|
74
|
+
status: z.literal("unavailable"),
|
|
75
|
+
reason: z.literal("unsupported"),
|
|
76
|
+
});
|
|
77
|
+
/** Typed outcome of a sender's answer to a child Ask. */
|
|
78
|
+
export const answerOutput = z.discriminatedUnion("status", [
|
|
79
|
+
z.strictObject({
|
|
80
|
+
status: z.literal("answered"),
|
|
81
|
+
via: z.enum(["in_turn", "follow_up"]),
|
|
82
|
+
/** The child's follow-up command that carries the answer; wait on it. */
|
|
83
|
+
commandId: id.exactOptional(),
|
|
84
|
+
}),
|
|
85
|
+
z.strictObject({ status: z.literal("not_found") }),
|
|
86
|
+
z.strictObject({ status: z.literal("closed"), reason: closedReasons }),
|
|
87
|
+
notSenderRefusal,
|
|
88
|
+
unsupported,
|
|
89
|
+
]);
|
|
90
|
+
/** Typed outcome of a sender's escalation of a child's item. */
|
|
91
|
+
export const escalateOutput = z.discriminatedUnion("status", [
|
|
92
|
+
z.strictObject({
|
|
93
|
+
status: z.literal("escalated"),
|
|
94
|
+
/** Who the item waits on now. */
|
|
95
|
+
to: z.literal("person"),
|
|
96
|
+
}),
|
|
97
|
+
z.strictObject({ status: z.literal("not_found") }),
|
|
98
|
+
z.strictObject({ status: z.literal("closed"), reason: closedReasons }),
|
|
99
|
+
notSenderRefusal,
|
|
100
|
+
unsupported,
|
|
101
|
+
]);
|
|
102
|
+
const sendGoal = z
|
|
103
|
+
.union([z.string().min(1).max(4_000), z.literal(false)])
|
|
104
|
+
.optional();
|
|
51
105
|
const sendArgs = z.discriminatedUnion("delivery", [
|
|
52
106
|
z.strictObject({
|
|
53
107
|
agentId: id,
|
|
@@ -68,6 +122,7 @@ const sendArgs = z.discriminatedUnion("delivery", [
|
|
|
68
122
|
.min(1)
|
|
69
123
|
.max(256 * 1024),
|
|
70
124
|
delivery: z.literal("followUp"),
|
|
125
|
+
goal: sendGoal,
|
|
71
126
|
}),
|
|
72
127
|
]);
|
|
73
128
|
const sendInput = sendArgs;
|
|
@@ -95,6 +150,7 @@ const agentOutput = z.strictObject({
|
|
|
95
150
|
workingDirectory: z.string().min(1).max(4096).exactOptional(),
|
|
96
151
|
});
|
|
97
152
|
const agentsOutput = z.array(agentOutput);
|
|
153
|
+
const createdAgentOutput = agentOutput.extend({ sessionId: id });
|
|
98
154
|
const offeredModelLimit = 200;
|
|
99
155
|
const modelsOutput = z.strictObject({
|
|
100
156
|
models: z
|
|
@@ -131,11 +187,26 @@ const finalTextFields = {
|
|
|
131
187
|
text: z.string().max(finalTextLimit),
|
|
132
188
|
truncated: z.boolean(),
|
|
133
189
|
};
|
|
190
|
+
/** Items the awaited Agent left waiting on the caller; child-authored text. */
|
|
191
|
+
const waitingOnYouField = {
|
|
192
|
+
waitingOnYou: z
|
|
193
|
+
.array(z.strictObject({
|
|
194
|
+
itemId: id,
|
|
195
|
+
kind: z.string().min(1).max(64),
|
|
196
|
+
summary: z.string().min(1).max(200),
|
|
197
|
+
}))
|
|
198
|
+
.max(8)
|
|
199
|
+
.exactOptional(),
|
|
200
|
+
};
|
|
134
201
|
const terminalOutput = z.union([
|
|
135
|
-
terminalIdentityOutput,
|
|
136
|
-
terminalIdentityOutput.extend(diagnosticField),
|
|
137
|
-
terminalIdentityOutput.extend(finalTextFields),
|
|
138
|
-
terminalIdentityOutput.extend({
|
|
202
|
+
terminalIdentityOutput.extend(waitingOnYouField),
|
|
203
|
+
terminalIdentityOutput.extend({ ...diagnosticField, ...waitingOnYouField }),
|
|
204
|
+
terminalIdentityOutput.extend({ ...finalTextFields, ...waitingOnYouField }),
|
|
205
|
+
terminalIdentityOutput.extend({
|
|
206
|
+
...diagnosticField,
|
|
207
|
+
...finalTextFields,
|
|
208
|
+
...waitingOnYouField,
|
|
209
|
+
}),
|
|
139
210
|
]);
|
|
140
211
|
const acceptedOutput = z.strictObject({
|
|
141
212
|
status: z.literal("accepted"),
|
|
@@ -271,21 +342,32 @@ export class AgentCreateRefused extends Error {
|
|
|
271
342
|
}
|
|
272
343
|
}
|
|
273
344
|
/** Admit the host's model refusal as a typed capability failure; rethrow anything else. */
|
|
274
|
-
function admitModelRefusal(operation, cause) {
|
|
275
|
-
if (cause instanceof ShiitakeError && cause.boundary === "model")
|
|
276
|
-
throw
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
345
|
+
async function admitModelRefusal(operation, cause, models, signal) {
|
|
346
|
+
if (!(cause instanceof ShiitakeError && cause.boundary === "model"))
|
|
347
|
+
throw cause;
|
|
348
|
+
let offered = [];
|
|
349
|
+
try {
|
|
350
|
+
offered = (await models?.(signal)) ?? [];
|
|
351
|
+
}
|
|
352
|
+
catch (catalogFailure) {
|
|
353
|
+
// Catalog diagnostics must not replace the original model refusal.
|
|
354
|
+
signal.throwIfAborted();
|
|
355
|
+
reportCatchFailure("agent-control.model-catalog", catalogFailure);
|
|
356
|
+
}
|
|
357
|
+
const addresses = offered.slice(0, 10).map((model) => model.address);
|
|
358
|
+
const suffix = addresses.length === 0 ? "" : ` Offered: ${addresses.join(", ")}.`;
|
|
359
|
+
throw new CapabilityFailed({
|
|
360
|
+
namespace: "agents",
|
|
361
|
+
operation,
|
|
362
|
+
cause: {
|
|
363
|
+
kind: "OperationFailed",
|
|
364
|
+
message: `model:${cause.cause}: ${cause.message}${suffix}`,
|
|
365
|
+
},
|
|
366
|
+
rawCause: cause,
|
|
367
|
+
});
|
|
286
368
|
}
|
|
287
369
|
/** Admit a creation refusal or a model refusal as a typed failure; rethrow anything else. */
|
|
288
|
-
function admitCreateFailure(cause) {
|
|
370
|
+
function admitCreateFailure(cause, models, signal) {
|
|
289
371
|
if (cause instanceof AgentCreateRefused)
|
|
290
372
|
throw new CapabilityFailed({
|
|
291
373
|
namespace: "agents",
|
|
@@ -296,20 +378,23 @@ function admitCreateFailure(cause) {
|
|
|
296
378
|
},
|
|
297
379
|
rawCause: cause,
|
|
298
380
|
});
|
|
299
|
-
return admitModelRefusal("create", cause);
|
|
381
|
+
return admitModelRefusal("create", cause, models, signal);
|
|
300
382
|
}
|
|
301
383
|
/** Compose one authorized Agent-control projection for a single run lease. */
|
|
302
384
|
export function createAgentControlSource(directory) {
|
|
385
|
+
const createSession = (operation, agentId, requestId, signal) => withAgent(directory, operation, agentId, signal, (agent) => agent.createSession(requestId));
|
|
303
386
|
const create = directory.create;
|
|
304
387
|
const send = directory.send;
|
|
305
388
|
const recall = directory.recall;
|
|
389
|
+
const answer = directory.answer;
|
|
390
|
+
const escalate = directory.escalate;
|
|
306
391
|
const createOperation = create === undefined
|
|
307
392
|
? undefined
|
|
308
393
|
: Mycelium.operation({
|
|
309
394
|
replay: "unsafe",
|
|
310
|
-
description: "Create one ordinary Agent (a subagent to spawn work on) in the current authorized scope
|
|
395
|
+
description: "Create one ordinary Agent (a subagent to spawn work on) and its first Session in the current authorized scope. Returns agentId and a usable sessionId: immediately agents.send with delivery: steer, then collect the reply with agents.wait; no separate createSession is needed. Reuse requestId when retrying the same creation and first Session. Optional name labels the Agent; model is a host-offered provider/model from agents.models; reasoning (minimal, low, medium, high, xhigh or max) needs model. The result also returns name, workingDirectory (the host's project directory), and the requested model/reasoning default, absent when inherited. An unoffered model fails with model:rejected and up to ten offered addresses. A new Agent starts with the base roster; extensions may enable only extension ids the creating Agent holds. An unheld extension fails with create:exceeds_creator and creates nothing.",
|
|
311
396
|
input: createInput,
|
|
312
|
-
output:
|
|
397
|
+
output: createdAgentOutput,
|
|
313
398
|
execute: async ({ requestId, name, model, reasoning, extensions }, context) => {
|
|
314
399
|
let request = { requestId };
|
|
315
400
|
if (name !== undefined)
|
|
@@ -323,7 +408,12 @@ export function createAgentControlSource(directory) {
|
|
|
323
408
|
model: reasoning === undefined ? address : { ...address, reasoning },
|
|
324
409
|
};
|
|
325
410
|
}
|
|
326
|
-
|
|
411
|
+
const created = await create(request, context.signal).catch((cause) => admitCreateFailure(cause, directory.models, context.signal));
|
|
412
|
+
const sessionId = await createSession("create", created.agentId, requestId, context.signal);
|
|
413
|
+
return createdAgentOutput.parse({
|
|
414
|
+
...agentAnswer(created),
|
|
415
|
+
sessionId,
|
|
416
|
+
});
|
|
327
417
|
},
|
|
328
418
|
});
|
|
329
419
|
const createOperations = createOperation === undefined ? {} : { create: createOperation };
|
|
@@ -333,7 +423,7 @@ export function createAgentControlSource(directory) {
|
|
|
333
423
|
: {
|
|
334
424
|
setModel: Mycelium.operation({
|
|
335
425
|
replay: "unsafe",
|
|
336
|
-
description: "Set an authorized Agent's default model, and optionally its thinking level (reasoning: minimal, low, medium, high, xhigh or max), for future inheriting runs. Use a provider/model address from agents.models, for example openai/gpt-6-astra. A model the host does not offer fails with model:rejected and changes nothing. Session overrides and running turns stay unchanged.",
|
|
426
|
+
description: "Set an authorized Agent's default model, and optionally its thinking level (reasoning: minimal, low, medium, high, xhigh or max), for future inheriting runs. Use a provider/model address from agents.models, for example openai/gpt-6-astra. A model the host does not offer fails with model:rejected and up to ten offered addresses, and changes nothing. Session overrides and running turns stay unchanged.",
|
|
337
427
|
input: setModelInput,
|
|
338
428
|
output: z.null(),
|
|
339
429
|
execute: async ({ agentId, model, reasoning }, context) => {
|
|
@@ -343,7 +433,7 @@ export function createAgentControlSource(directory) {
|
|
|
343
433
|
model: reasoning === undefined
|
|
344
434
|
? address
|
|
345
435
|
: { ...address, reasoning },
|
|
346
|
-
}, context.signal).catch((cause) => admitModelRefusal("setModel", cause));
|
|
436
|
+
}, context.signal).catch((cause) => admitModelRefusal("setModel", cause, directory.models, context.signal));
|
|
347
437
|
return null;
|
|
348
438
|
},
|
|
349
439
|
}),
|
|
@@ -404,10 +494,34 @@ export function createAgentControlSource(directory) {
|
|
|
404
494
|
signal.throwIfAborted();
|
|
405
495
|
const sends = new WeakMap();
|
|
406
496
|
const requestOperations = {};
|
|
497
|
+
if (answer !== undefined)
|
|
498
|
+
requestOperations.answer = Mycelium.operation({
|
|
499
|
+
replay: "unsafe",
|
|
500
|
+
description: "Answer a question a sub-agent left waiting on you. A waiting_on_you item in an agent reply names its askId. Only the Session that sent the sub-agent that work can answer; others are refused (not_sender). The answer reaches the sub-agent as one follow-up command, returned as commandId: agents.wait on it to collect the continued work. Reuse requestId when retrying the same answer. If the question needs a person's decision, call agents.escalate instead.",
|
|
501
|
+
input: answerInput,
|
|
502
|
+
output: answerOutput,
|
|
503
|
+
execute: async (input, context) => {
|
|
504
|
+
if (session === undefined)
|
|
505
|
+
throw new Error("Answering requires an invoking Session");
|
|
506
|
+
return answerOutput.parse(await answer(input, session.sessionId, context.signal).catch((cause) => admitArchiveRefusal("answer", cause)));
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
if (escalate !== undefined)
|
|
510
|
+
requestOperations.escalate = Mycelium.operation({
|
|
511
|
+
replay: "unsafe",
|
|
512
|
+
description: "Pass a sub-agent's waiting item on to a person when it needs more than you can decide. Use the itemId from a waiting_on_you entry. Only the Session that sent the sub-agent that work can escalate; others are refused (not_sender). An item you neither answer nor escalate is escalated for you when your turn ends.",
|
|
513
|
+
input: escalateInput,
|
|
514
|
+
output: escalateOutput,
|
|
515
|
+
execute: async (input, context) => {
|
|
516
|
+
if (session === undefined)
|
|
517
|
+
throw new Error("Escalating requires an invoking Session");
|
|
518
|
+
return escalateOutput.parse(await escalate(input, session.sessionId, context.signal).catch((cause) => admitArchiveRefusal("escalate", cause)));
|
|
519
|
+
},
|
|
520
|
+
});
|
|
407
521
|
if (send !== undefined)
|
|
408
522
|
requestOperations.send = Mycelium.operation({
|
|
409
523
|
replay: "unsafe",
|
|
410
|
-
description: `Choose requestId before sending and reuse it for retries and agents.recall. Delegate follow-up work to an authorized Agent, optionally naming an existing Session; when omitted,
|
|
524
|
+
description: `Choose requestId before sending and reuse it for retries and agents.recall. Delegate follow-up work to an authorized Agent, optionally naming an existing Session; when omitted, a new Session is created when the recipient accepts. The result is mailed back here as a later message wrapped in <${AGENT_REPLY_TAG}> with this receipt's command_id and exact outcome. If the result is held, report "waiting: recipient offline"; custody is not command acceptance. Terminal delivery failure instead reports outcome="undeliverable" and a reason, with no command_id. Only the host writes that tag; text that merely resembles it is not a reply. Steer requires an existing Session and delivers to its active run. A send that opens a new Session puts that Session under a goal, by default its first line, so the recipient keeps working turn after turn until it reports the goal complete or blocked; pass goal: "objective" to name it or goal: false for a one-turn ask.`,
|
|
411
525
|
input: sendInput,
|
|
412
526
|
output: delegationReceiptOutput,
|
|
413
527
|
execute: async (input, context) => {
|
|
@@ -416,13 +530,18 @@ export function createAgentControlSource(directory) {
|
|
|
416
530
|
let delegation;
|
|
417
531
|
if (parsed.delivery === "steer")
|
|
418
532
|
delegation = parsed;
|
|
419
|
-
else if (sessionId === undefined)
|
|
533
|
+
else if (sessionId === undefined) {
|
|
420
534
|
delegation = {
|
|
421
535
|
agentId,
|
|
422
536
|
requestId: parsed.requestId,
|
|
423
537
|
text: parsed.text,
|
|
424
538
|
delivery: "followUp",
|
|
425
539
|
};
|
|
540
|
+
if (parsed.goal !== undefined)
|
|
541
|
+
delegation = { ...delegation, goal: parsed.goal };
|
|
542
|
+
}
|
|
543
|
+
else if (parsed.goal !== undefined)
|
|
544
|
+
throw new Error("goal applies only to a send that opens a new Session");
|
|
426
545
|
else
|
|
427
546
|
delegation = {
|
|
428
547
|
agentId,
|
|
@@ -506,7 +625,7 @@ export function createAgentControlSource(directory) {
|
|
|
506
625
|
input: createSessionInput,
|
|
507
626
|
output: z.strictObject({ sessionId: id }),
|
|
508
627
|
execute: async ({ agentId, requestId }, context) => ({
|
|
509
|
-
sessionId: await
|
|
628
|
+
sessionId: await createSession("createSession", agentId, requestId, context.signal),
|
|
510
629
|
}),
|
|
511
630
|
}),
|
|
512
631
|
sessions: Mycelium.operation({
|
|
@@ -564,6 +683,8 @@ export function createAgentControlSource(directory) {
|
|
|
564
683
|
result.text = text.slice(0, finalTextLimit);
|
|
565
684
|
result.truncated = text.length > finalTextLimit;
|
|
566
685
|
}
|
|
686
|
+
if (settlement.waitingOnYou.length > 0)
|
|
687
|
+
result.waitingOnYou = [...settlement.waitingOnYou];
|
|
567
688
|
return terminalOutput.parse(result);
|
|
568
689
|
},
|
|
569
690
|
}),
|
package/dist/client-entry.d.ts
CHANGED
|
@@ -43,6 +43,11 @@ export declare const clientTimelineCellSchemaV1: Schema.Struct<{
|
|
|
43
43
|
readonly id: Schema.String;
|
|
44
44
|
readonly label: Schema.String;
|
|
45
45
|
readonly input: Schema.optional<Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>>;
|
|
46
|
+
/**
|
|
47
|
+
* The action starts a decision the person answers in the composer, so a
|
|
48
|
+
* client takes the composer over for it instead of waiting for a click.
|
|
49
|
+
*/
|
|
50
|
+
readonly awaiting: Schema.optionalKey<Schema.Literal<"question">>;
|
|
46
51
|
}>>>;
|
|
47
52
|
readonly id: Schema.String;
|
|
48
53
|
readonly title: Schema.String;
|
|
@@ -110,6 +115,11 @@ export declare const clientContributionSchemaV1: Schema.Struct<{
|
|
|
110
115
|
readonly id: Schema.String;
|
|
111
116
|
readonly label: Schema.String;
|
|
112
117
|
readonly input: Schema.optional<Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>>;
|
|
118
|
+
/**
|
|
119
|
+
* The action starts a decision the person answers in the composer, so a
|
|
120
|
+
* client takes the composer over for it instead of waiting for a click.
|
|
121
|
+
*/
|
|
122
|
+
readonly awaiting: Schema.optionalKey<Schema.Literal<"question">>;
|
|
113
123
|
}>>>;
|
|
114
124
|
readonly id: Schema.String;
|
|
115
125
|
readonly title: Schema.String;
|
|
@@ -139,6 +149,11 @@ export declare const clientContributionSchemaV1: Schema.Struct<{
|
|
|
139
149
|
readonly id: Schema.String;
|
|
140
150
|
readonly label: Schema.String;
|
|
141
151
|
readonly input: Schema.optional<Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>>;
|
|
152
|
+
/**
|
|
153
|
+
* The action starts a decision the person answers in the composer, so a
|
|
154
|
+
* client takes the composer over for it instead of waiting for a click.
|
|
155
|
+
*/
|
|
156
|
+
readonly awaiting: Schema.optionalKey<Schema.Literal<"question">>;
|
|
142
157
|
}>>>;
|
|
143
158
|
readonly id: Schema.String;
|
|
144
159
|
readonly title: Schema.String;
|
|
@@ -164,6 +179,11 @@ export declare const clientContributionSchemaV1: Schema.Struct<{
|
|
|
164
179
|
readonly id: Schema.String;
|
|
165
180
|
readonly label: Schema.String;
|
|
166
181
|
readonly input: Schema.optional<Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>>;
|
|
182
|
+
/**
|
|
183
|
+
* The action starts a decision the person answers in the composer, so a
|
|
184
|
+
* client takes the composer over for it instead of waiting for a click.
|
|
185
|
+
*/
|
|
186
|
+
readonly awaiting: Schema.optionalKey<Schema.Literal<"question">>;
|
|
167
187
|
}>;
|
|
168
188
|
}>>;
|
|
169
189
|
}>;
|
package/dist/client-entry.js
CHANGED
|
@@ -14,6 +14,11 @@ const commandAction = Schema.Struct({
|
|
|
14
14
|
id: rowIdentitySchemaV1,
|
|
15
15
|
label: rowLabelSchemaV1,
|
|
16
16
|
input: Schema.optional(objectArguments),
|
|
17
|
+
/**
|
|
18
|
+
* The action starts a decision the person answers in the composer, so a
|
|
19
|
+
* client takes the composer over for it instead of waiting for a click.
|
|
20
|
+
*/
|
|
21
|
+
awaiting: Schema.optionalKey(Schema.Literal("question")),
|
|
17
22
|
});
|
|
18
23
|
const commandRow = Schema.Struct({
|
|
19
24
|
...rowSchemaV1.fields,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SessionPendingPrompt, SessionSnapshot } from "./client.js";
|
|
2
|
+
/** The host decides which Agent work may be delivered to this participant. */
|
|
3
|
+
export type ParticipantPolicy = Readonly<{
|
|
4
|
+
/** Whether this Session command is current for the participant. */
|
|
5
|
+
turnIsCurrent: (sessionId: string, commandId: string) => boolean;
|
|
6
|
+
/** Whether this participant may receive the addressed Agent reply. */
|
|
7
|
+
deliverAgentReply: (sessionId: string, commandId: string, reply: Readonly<{
|
|
8
|
+
kind: "message";
|
|
9
|
+
messageId: string;
|
|
10
|
+
}>) => boolean;
|
|
11
|
+
}>;
|
|
12
|
+
/** Filter participant delivery only; the holder's stored Session is unchanged. */
|
|
13
|
+
export declare function participantSnapshot(snapshot: SessionSnapshot, owner: ParticipantPolicy): SessionSnapshot;
|
|
14
|
+
/** What a visitor may see of their own conversation Session. */
|
|
15
|
+
export type VisitorView = Readonly<{
|
|
16
|
+
status: "idle" | "working";
|
|
17
|
+
lines: readonly Readonly<{
|
|
18
|
+
id: string;
|
|
19
|
+
from: "visitor" | "agent" | "operator";
|
|
20
|
+
text: string;
|
|
21
|
+
}>[];
|
|
22
|
+
progress: string | null;
|
|
23
|
+
}>;
|
|
24
|
+
/** One visitor-visible line without internal attribution or tool metadata. */
|
|
25
|
+
export type VisitorLine = VisitorView["lines"][number];
|
|
26
|
+
/** Public pending facts exclude the host's private metadata and admission origin. */
|
|
27
|
+
export type VisitorPendingPrompt = Pick<SessionPendingPrompt, "commandId" | "requestId" | "text" | "delivery" | "images">;
|
|
28
|
+
/** Project one owner-selected prompt without exposing host identity or correlation metadata. */
|
|
29
|
+
export declare function visitorPendingPrompt(prompt: SessionPendingPrompt, visitorId: string): VisitorPendingPrompt;
|
|
30
|
+
/** Visitor-scoped Session facts use the visitor's public address, never the internal Session ID. */
|
|
31
|
+
export declare function visitorSnapshot(snapshot: SessionSnapshot, visitorId: string): SessionSnapshot;
|
|
32
|
+
/** Project only this visitor's host-attributed prompts, their replies, and explicit operator replies. */
|
|
33
|
+
export declare function visitorView(snapshot: SessionSnapshot | null, visitorId: string): VisitorView;
|