@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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type AttentionRecipient } from "../../session-attention.js";
|
|
2
|
+
import type { SqliteOwner } from "../contracts.js";
|
|
3
|
+
/** One open item of a Session that waits on the Agent that sent its work. */
|
|
4
|
+
export type WaitingOnYou = Readonly<{
|
|
5
|
+
itemId: string;
|
|
6
|
+
kind: string;
|
|
7
|
+
summary: string;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Woodstock records which Agent Session sent each accepted run, beside the
|
|
11
|
+
* run's admission. Attention that an extension raises during a run waits on
|
|
12
|
+
* this sender first; a run no Agent sent has no row.
|
|
13
|
+
*/
|
|
14
|
+
export declare function prepareWorkSenders(owner: SqliteOwner): void;
|
|
15
|
+
/** Record the sender of one accepted run; a replay writes the same row. */
|
|
16
|
+
export declare function recordWorkSender(owner: SqliteOwner, sessionId: string, workId: string, sender: AttentionRecipient): void;
|
|
17
|
+
/** The Agent Session that sent this run, or undefined when none did. */
|
|
18
|
+
export declare function readWorkSender(owner: SqliteOwner, sessionId: string, workId: string): AttentionRecipient | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* The Session's open attention that waits on the sender of this run, newest
|
|
21
|
+
* first as stored, bounded. Escalated items wait on someone else and are
|
|
22
|
+
* absent; a run no Agent sent names none.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readWaitingOnSender(owner: SqliteOwner, sessionId: string, workId: string): readonly WaitingOnYou[];
|
|
25
|
+
/**
|
|
26
|
+
* The Session's open attention that waits on any of these Agent Sessions,
|
|
27
|
+
* newest first as stored, bounded: what a reply or report to them names.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readWaitingOn(owner: SqliteOwner, sessionId: string, recipients: readonly Readonly<{
|
|
30
|
+
agentId: string;
|
|
31
|
+
sessionId: string;
|
|
32
|
+
}>[]): readonly WaitingOnYou[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import { AttentionRecipientSchema, } from "../../session-attention.js";
|
|
3
|
+
import { readSessionAttention } from "./session-attention.js";
|
|
4
|
+
/** The most open items one settlement names as waiting on its sender. */
|
|
5
|
+
const maximumWaitingOnYou = 8;
|
|
6
|
+
const decodeRow = Schema.decodeUnknownSync(Schema.Struct({
|
|
7
|
+
agent_id: Schema.String,
|
|
8
|
+
sender_session_id: Schema.String,
|
|
9
|
+
name: Schema.String,
|
|
10
|
+
}));
|
|
11
|
+
const decodeRecipient = Schema.decodeUnknownSync(AttentionRecipientSchema);
|
|
12
|
+
/**
|
|
13
|
+
* Woodstock records which Agent Session sent each accepted run, beside the
|
|
14
|
+
* run's admission. Attention that an extension raises during a run waits on
|
|
15
|
+
* this sender first; a run no Agent sent has no row.
|
|
16
|
+
*/
|
|
17
|
+
export function prepareWorkSenders(owner) {
|
|
18
|
+
owner.sql.exec(`CREATE TABLE IF NOT EXISTS session_work_senders (
|
|
19
|
+
session_id TEXT NOT NULL,
|
|
20
|
+
work_id TEXT NOT NULL,
|
|
21
|
+
agent_id TEXT NOT NULL,
|
|
22
|
+
sender_session_id TEXT NOT NULL,
|
|
23
|
+
name TEXT NOT NULL,
|
|
24
|
+
PRIMARY KEY (session_id, work_id)
|
|
25
|
+
)`);
|
|
26
|
+
}
|
|
27
|
+
/** Record the sender of one accepted run; a replay writes the same row. */
|
|
28
|
+
export function recordWorkSender(owner, sessionId, workId, sender) {
|
|
29
|
+
owner.sql.exec(`INSERT OR IGNORE INTO session_work_senders
|
|
30
|
+
(session_id, work_id, agent_id, sender_session_id, name)
|
|
31
|
+
VALUES (?, ?, ?, ?, ?)`, sessionId, workId, sender.agentId, sender.sessionId, sender.name);
|
|
32
|
+
}
|
|
33
|
+
/** The Agent Session that sent this run, or undefined when none did. */
|
|
34
|
+
export function readWorkSender(owner, sessionId, workId) {
|
|
35
|
+
const raw = owner.sql
|
|
36
|
+
.exec("SELECT agent_id, sender_session_id, name FROM session_work_senders WHERE session_id = ? AND work_id = ?", sessionId, workId)
|
|
37
|
+
.toArray()[0];
|
|
38
|
+
if (raw === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
const row = decodeRow(raw);
|
|
41
|
+
return decodeRecipient({
|
|
42
|
+
agentId: row.agent_id,
|
|
43
|
+
sessionId: row.sender_session_id,
|
|
44
|
+
name: row.name,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The Session's open attention that waits on the sender of this run, newest
|
|
49
|
+
* first as stored, bounded. Escalated items wait on someone else and are
|
|
50
|
+
* absent; a run no Agent sent names none.
|
|
51
|
+
*/
|
|
52
|
+
export function readWaitingOnSender(owner, sessionId, workId) {
|
|
53
|
+
const sender = readWorkSender(owner, sessionId, workId);
|
|
54
|
+
return sender === undefined ? [] : readWaitingOn(owner, sessionId, [sender]);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The Session's open attention that waits on any of these Agent Sessions,
|
|
58
|
+
* newest first as stored, bounded: what a reply or report to them names.
|
|
59
|
+
*/
|
|
60
|
+
export function readWaitingOn(owner, sessionId, recipients) {
|
|
61
|
+
return readSessionAttention(owner, sessionId)
|
|
62
|
+
.filter((item) => recipients.some((recipient) => item.waitingOn?.agentId === recipient.agentId &&
|
|
63
|
+
item.waitingOn.sessionId === recipient.sessionId))
|
|
64
|
+
.slice(0, maximumWaitingOnYou)
|
|
65
|
+
.map((item) => ({
|
|
66
|
+
itemId: item.id,
|
|
67
|
+
kind: item.kind,
|
|
68
|
+
summary: item.summary,
|
|
69
|
+
}));
|
|
70
|
+
}
|
package/dist/node.js
CHANGED
|
@@ -50,8 +50,13 @@ export function nodeRuntimeHost(observeFailure) {
|
|
|
50
50
|
wakeTimer = setTimeout(() => {
|
|
51
51
|
wakeTimer = undefined;
|
|
52
52
|
wakeAt = undefined;
|
|
53
|
+
// A capped wait may finish before the actual deadline.
|
|
54
|
+
if (at > Date.now()) {
|
|
55
|
+
scheduleWake(at);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
53
58
|
void bound?.requestWake().catch(reportFailure);
|
|
54
|
-
}, Math.max(0, at - Date.now()));
|
|
59
|
+
}, Math.min(2_147_483_647, Math.max(0, at - Date.now())));
|
|
55
60
|
};
|
|
56
61
|
const runtime = Object.freeze({
|
|
57
62
|
bind: ({ reconcile, pendingWork }) => {
|
|
@@ -133,7 +138,11 @@ export function nodeRuntimeHost(observeFailure) {
|
|
|
133
138
|
return Object.freeze({
|
|
134
139
|
runtime,
|
|
135
140
|
scheduleWake,
|
|
136
|
-
close: () =>
|
|
141
|
+
close: () => {
|
|
142
|
+
scheduleWake(null);
|
|
143
|
+
closed = true;
|
|
144
|
+
return bound?.close() ?? Promise.resolve();
|
|
145
|
+
},
|
|
137
146
|
});
|
|
138
147
|
}
|
|
139
148
|
/**
|
package/dist/remote-client.js
CHANGED
|
@@ -72,7 +72,12 @@ function remoteEvents(url, clientClosed, sources, eventSource) {
|
|
|
72
72
|
closed: false,
|
|
73
73
|
};
|
|
74
74
|
const emit = (kind, event) => {
|
|
75
|
-
Queue.offerUnsafe(queue, { kind, event })
|
|
75
|
+
if (!Queue.offerUnsafe(queue, { kind, event })) {
|
|
76
|
+
// A dropped frame breaks this watch's ordering. Fail instead
|
|
77
|
+
// of delivering an incomplete history to the client.
|
|
78
|
+
Queue.failCauseUnsafe(queue, Cause.fail(unavailable("Session stream client fell behind")));
|
|
79
|
+
Effect.runSync(closeRemoteSource(active, sources));
|
|
80
|
+
}
|
|
76
81
|
};
|
|
77
82
|
active.source.addEventListener("snapshot", (event) => {
|
|
78
83
|
emit("snapshot", event);
|
|
@@ -91,7 +96,7 @@ function remoteEvents(url, clientClosed, sources, eventSource) {
|
|
|
91
96
|
return active;
|
|
92
97
|
},
|
|
93
98
|
catch: () => unavailable("Session stream could not open"),
|
|
94
|
-
}), (active) => closeRemoteSource(active, sources))).pipe(Stream.mapEffect(decodeRemoteEvent), Stream.interruptWhen(clientClosed.await));
|
|
99
|
+
}), (active) => closeRemoteSource(active, sources)), { bufferSize: 256, strategy: "dropping" }).pipe(Stream.mapEffect(decodeRemoteEvent), Stream.interruptWhen(clientClosed.await));
|
|
95
100
|
}
|
|
96
101
|
function sessionUrl(baseUrl, sessionId) {
|
|
97
102
|
return `${baseUrl}/sessions/${encodeURIComponent(sessionId)}`;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SessionSnapshot, SessionWatch } from "./client.js";
|
|
2
|
+
export type VisitorStreamError = "channel_visitor_stream_expiry_failed" | "channel_visitor_stream_close_failed" | "channel_visitor_stream_heartbeat_failed" | "channel_visitor_stream_failed";
|
|
3
|
+
export declare function visitorStream(options: Readonly<{
|
|
4
|
+
watch: SessionWatch;
|
|
5
|
+
visitorId: string;
|
|
6
|
+
filter: (snapshot: SessionSnapshot) => SessionSnapshot;
|
|
7
|
+
expiresAt: number;
|
|
8
|
+
now: () => number;
|
|
9
|
+
release: () => void;
|
|
10
|
+
canRead: () => Promise<boolean>;
|
|
11
|
+
read: () => Promise<SessionSnapshot>;
|
|
12
|
+
onError: (event: VisitorStreamError, cause: unknown) => void;
|
|
13
|
+
}>): ReadableStream<Uint8Array>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { SSEStreamingApi } from "hono/streaming";
|
|
2
|
+
import { visitorSnapshot } from "./client-visitor.js";
|
|
3
|
+
export function visitorStream(options) {
|
|
4
|
+
const { readable, writable } = new TransformStream();
|
|
5
|
+
const stream = new SSEStreamingApi(writable, readable);
|
|
6
|
+
let closing;
|
|
7
|
+
let ending = false;
|
|
8
|
+
let heartbeat;
|
|
9
|
+
const expiry = setTimeout(() => {
|
|
10
|
+
void close().catch((cause) => {
|
|
11
|
+
options.onError("channel_visitor_stream_expiry_failed", cause);
|
|
12
|
+
});
|
|
13
|
+
}, Math.max(0, options.expiresAt - options.now()));
|
|
14
|
+
const close = () => {
|
|
15
|
+
if (closing !== undefined)
|
|
16
|
+
return closing;
|
|
17
|
+
ending = true;
|
|
18
|
+
clearTimeout(expiry);
|
|
19
|
+
clearTimeout(heartbeat);
|
|
20
|
+
options.release();
|
|
21
|
+
closing = Promise.resolve().then(async () => {
|
|
22
|
+
stream.abort();
|
|
23
|
+
try {
|
|
24
|
+
await options.watch.close();
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
await stream.close();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return closing;
|
|
31
|
+
};
|
|
32
|
+
stream.onAbort(() => {
|
|
33
|
+
void close().catch((cause) => {
|
|
34
|
+
options.onError("channel_visitor_stream_close_failed", cause);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
const admitted = async () => !ending &&
|
|
38
|
+
options.now() < options.expiresAt &&
|
|
39
|
+
(await options.canRead()) &&
|
|
40
|
+
!ending;
|
|
41
|
+
const beat = async () => {
|
|
42
|
+
if (!(await admitted())) {
|
|
43
|
+
await close();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
await stream.write(": heartbeat\n\n");
|
|
47
|
+
if (!ending)
|
|
48
|
+
heartbeat = setTimeout(() => {
|
|
49
|
+
void beat().catch((cause) => {
|
|
50
|
+
options.onError("channel_visitor_stream_heartbeat_failed", cause);
|
|
51
|
+
void close().catch((closeCause) => {
|
|
52
|
+
options.onError("channel_visitor_stream_close_failed", closeCause);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}, 30_000);
|
|
56
|
+
};
|
|
57
|
+
const publish = async (snapshot) => {
|
|
58
|
+
if (!(await admitted()))
|
|
59
|
+
return false;
|
|
60
|
+
const baseline = visitorSnapshot(options.filter(snapshot), options.visitorId);
|
|
61
|
+
await stream.writeSSE({ event: "session", data: JSON.stringify(baseline) });
|
|
62
|
+
return !ending;
|
|
63
|
+
};
|
|
64
|
+
const pump = async () => {
|
|
65
|
+
try {
|
|
66
|
+
let snapshot = options.watch.snapshot;
|
|
67
|
+
if (!(await publish(snapshot)))
|
|
68
|
+
return;
|
|
69
|
+
heartbeat = setTimeout(() => {
|
|
70
|
+
void beat().catch((cause) => {
|
|
71
|
+
options.onError("channel_visitor_stream_heartbeat_failed", cause);
|
|
72
|
+
void close().catch((closeCause) => {
|
|
73
|
+
options.onError("channel_visitor_stream_close_failed", closeCause);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}, 30_000);
|
|
77
|
+
for await (const frame of options.watch.changes) {
|
|
78
|
+
if (ending)
|
|
79
|
+
break;
|
|
80
|
+
if (frame.kind === "tool_output")
|
|
81
|
+
continue;
|
|
82
|
+
if (frame.kind === "progress" || frame.kind === "activity") {
|
|
83
|
+
if (!(await admitted()))
|
|
84
|
+
break;
|
|
85
|
+
snapshot = { ...snapshot, activity: frame.activity };
|
|
86
|
+
const baseline = visitorSnapshot(options.filter(snapshot), options.visitorId);
|
|
87
|
+
if (baseline.activity.queue.state === "idle") {
|
|
88
|
+
if (frame.kind === "activity")
|
|
89
|
+
await stream.writeSSE({
|
|
90
|
+
event: "session",
|
|
91
|
+
data: JSON.stringify(baseline),
|
|
92
|
+
});
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
await stream.writeSSE({
|
|
96
|
+
event: "frame",
|
|
97
|
+
data: JSON.stringify(frame.kind === "activity"
|
|
98
|
+
? { kind: "activity", activity: baseline.activity }
|
|
99
|
+
: {
|
|
100
|
+
...frame,
|
|
101
|
+
progress: {
|
|
102
|
+
...frame.progress,
|
|
103
|
+
thinking: { offset: 0, append: "" },
|
|
104
|
+
},
|
|
105
|
+
activity: baseline.activity,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
snapshot =
|
|
111
|
+
frame.kind === "reset" ? frame.baseline : await options.read();
|
|
112
|
+
if (!(await publish(snapshot)))
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
await close();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
void pump().catch((cause) => {
|
|
121
|
+
options.onError("channel_visitor_stream_failed", cause);
|
|
122
|
+
});
|
|
123
|
+
return stream.responseReadable.pipeThrough(new TransformStream());
|
|
124
|
+
}
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import * as Schema from "effect/Schema";
|
|
2
|
+
/**
|
|
3
|
+
* The Agent Session that sent the work an item belongs to. The host derives it
|
|
4
|
+
* from the accepted mail; it is the only party the item waits on until it is
|
|
5
|
+
* escalated.
|
|
6
|
+
*/
|
|
7
|
+
export declare const AttentionRecipientSchema: Schema.Struct<{
|
|
8
|
+
readonly agentId: Schema.NonEmptyString;
|
|
9
|
+
readonly sessionId: Schema.NonEmptyString;
|
|
10
|
+
readonly name: Schema.NonEmptyString;
|
|
11
|
+
}>;
|
|
12
|
+
/** Decoded Agent Session an attention item waits on. */
|
|
13
|
+
export type AttentionRecipient = typeof AttentionRecipientSchema.Type;
|
|
2
14
|
/**
|
|
3
15
|
* An extension-owned request for human attention; expiry is owned by its timer.
|
|
4
16
|
* Items marked `inbox: false` remain visible without changing the inbox state.
|
|
17
|
+
* An item with `waitingOn` waits on that Agent first and is never a person's
|
|
18
|
+
* inbox item until it is escalated past every Agent.
|
|
5
19
|
*/
|
|
6
20
|
export declare const AttentionItemSchema: Schema.Struct<{
|
|
7
21
|
readonly id: Schema.NonEmptyString;
|
|
@@ -11,6 +25,12 @@ export declare const AttentionItemSchema: Schema.Struct<{
|
|
|
11
25
|
readonly expiresAt: Schema.optional<Schema.Int>;
|
|
12
26
|
/** This item does not change the Session's inbox state; omission does. */
|
|
13
27
|
readonly inbox: Schema.optional<Schema.Literal<false>>;
|
|
28
|
+
/** The Agent this item waits on; absent means a person. */
|
|
29
|
+
readonly waitingOn: Schema.optional<Schema.Struct<{
|
|
30
|
+
readonly agentId: Schema.NonEmptyString;
|
|
31
|
+
readonly sessionId: Schema.NonEmptyString;
|
|
32
|
+
readonly name: Schema.NonEmptyString;
|
|
33
|
+
}>>;
|
|
14
34
|
}>;
|
|
15
35
|
/** Input to the granted attention door, scoped to the calling extension. */
|
|
16
36
|
export type AttentionItem = typeof AttentionItemSchema.Type;
|
|
@@ -23,6 +43,11 @@ export declare const SessionAttentionSchema: Schema.Struct<{
|
|
|
23
43
|
readonly since: Schema.Int;
|
|
24
44
|
readonly expiresAt: Schema.optional<Schema.Int>;
|
|
25
45
|
readonly inbox: Schema.optional<Schema.Literal<false>>;
|
|
46
|
+
readonly waitingOn: Schema.optional<Schema.Struct<{
|
|
47
|
+
readonly agentId: Schema.NonEmptyString;
|
|
48
|
+
readonly sessionId: Schema.NonEmptyString;
|
|
49
|
+
readonly name: Schema.NonEmptyString;
|
|
50
|
+
}>>;
|
|
26
51
|
}>;
|
|
27
52
|
/** One open attention item in a Session observation. */
|
|
28
53
|
export type SessionAttention = typeof SessionAttentionSchema.Type;
|
|
@@ -39,4 +64,9 @@ export declare const SessionAttentionListSchema: Schema.$Array<Schema.Struct<{
|
|
|
39
64
|
readonly since: Schema.Int;
|
|
40
65
|
readonly expiresAt: Schema.optional<Schema.Int>;
|
|
41
66
|
readonly inbox: Schema.optional<Schema.Literal<false>>;
|
|
67
|
+
readonly waitingOn: Schema.optional<Schema.Struct<{
|
|
68
|
+
readonly agentId: Schema.NonEmptyString;
|
|
69
|
+
readonly sessionId: Schema.NonEmptyString;
|
|
70
|
+
readonly name: Schema.NonEmptyString;
|
|
71
|
+
}>>;
|
|
42
72
|
}>>;
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import * as Schema from "effect/Schema";
|
|
2
2
|
const identity = Schema.NonEmptyString.check(Schema.isMaxLength(256));
|
|
3
3
|
const instant = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
4
|
+
/**
|
|
5
|
+
* The Agent Session that sent the work an item belongs to. The host derives it
|
|
6
|
+
* from the accepted mail; it is the only party the item waits on until it is
|
|
7
|
+
* escalated.
|
|
8
|
+
*/
|
|
9
|
+
export const AttentionRecipientSchema = Schema.Struct({
|
|
10
|
+
agentId: identity,
|
|
11
|
+
sessionId: identity,
|
|
12
|
+
name: identity,
|
|
13
|
+
});
|
|
4
14
|
/**
|
|
5
15
|
* An extension-owned request for human attention; expiry is owned by its timer.
|
|
6
16
|
* Items marked `inbox: false` remain visible without changing the inbox state.
|
|
17
|
+
* An item with `waitingOn` waits on that Agent first and is never a person's
|
|
18
|
+
* inbox item until it is escalated past every Agent.
|
|
7
19
|
*/
|
|
8
20
|
export const AttentionItemSchema = Schema.Struct({
|
|
9
21
|
id: identity,
|
|
@@ -13,6 +25,8 @@ export const AttentionItemSchema = Schema.Struct({
|
|
|
13
25
|
expiresAt: Schema.optional(instant),
|
|
14
26
|
/** This item does not change the Session's inbox state; omission does. */
|
|
15
27
|
inbox: Schema.optional(Schema.Literal(false)),
|
|
28
|
+
/** The Agent this item waits on; absent means a person. */
|
|
29
|
+
waitingOn: Schema.optional(AttentionRecipientSchema),
|
|
16
30
|
});
|
|
17
31
|
/** Public identity includes the owner so two extensions can use the same id. */
|
|
18
32
|
export const SessionAttentionSchema = AttentionItemSchema.mapFields((fields) => ({
|
|
@@ -31,7 +31,7 @@ new AgentArchiveRefused(reason, message): AgentArchiveRefused;
|
|
|
31
31
|
|
|
32
32
|
| Parameter | Type |
|
|
33
33
|
| --------- | ---------------------------------------------------------------------------------------------- |
|
|
34
|
-
| `reason` | \| `"
|
|
34
|
+
| `reason` | \| `"archived"` \| `"running"` \| `"pending"` \| `"not_creator"` \| `"self"` \| `"unreadable"` |
|
|
35
35
|
| `message` | `string` |
|
|
36
36
|
|
|
37
37
|
###### Returns
|
|
@@ -62,9 +62,9 @@ Error.name;
|
|
|
62
62
|
|
|
63
63
|
```ts
|
|
64
64
|
readonly reason:
|
|
65
|
+
| "archived"
|
|
65
66
|
| "running"
|
|
66
67
|
| "pending"
|
|
67
|
-
| "archived"
|
|
68
68
|
| "not_creator"
|
|
69
69
|
| "self"
|
|
70
70
|
| "unreadable";
|
|
@@ -75,8 +75,9 @@ readonly reason:
|
|
|
75
75
|
### AgentCreateRefused
|
|
76
76
|
|
|
77
77
|
A host's refusal of an Agent creating an Agent: the creator asked to give the
|
|
78
|
-
created Agent more than it holds,
|
|
79
|
-
|
|
78
|
+
created Agent more than it holds, the host cannot tell which Agent is creating,
|
|
79
|
+
or the request id belongs to another creating Agent or Session. It reaches the
|
|
80
|
+
caller as an `agents.create` failure.
|
|
80
81
|
|
|
81
82
|
#### Extends
|
|
82
83
|
|
|
@@ -92,10 +93,10 @@ new AgentCreateRefused(reason, message): AgentCreateRefused;
|
|
|
92
93
|
|
|
93
94
|
###### Parameters
|
|
94
95
|
|
|
95
|
-
| Parameter | Type
|
|
96
|
-
| --------- |
|
|
97
|
-
| `reason` | `"exceeds_creator"` \| `"no_creator"` |
|
|
98
|
-
| `message` | `string`
|
|
96
|
+
| Parameter | Type |
|
|
97
|
+
| --------- | ------------------------------------------------------------- |
|
|
98
|
+
| `reason` | `"exceeds_creator"` \| `"no_creator"` \| `"request_conflict"` |
|
|
99
|
+
| `message` | `string` |
|
|
99
100
|
|
|
100
101
|
###### Returns
|
|
101
102
|
|
|
@@ -124,11 +125,62 @@ Error.name;
|
|
|
124
125
|
##### reason
|
|
125
126
|
|
|
126
127
|
```ts
|
|
127
|
-
readonly reason: "exceeds_creator" | "no_creator";
|
|
128
|
+
readonly reason: "exceeds_creator" | "no_creator" | "request_conflict";
|
|
128
129
|
```
|
|
129
130
|
|
|
130
131
|
## Type Aliases
|
|
131
132
|
|
|
133
|
+
### AgentControlAnswerInput
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
type AgentControlAnswerInput = Readonly<{
|
|
137
|
+
agentId: string;
|
|
138
|
+
sessionId: string;
|
|
139
|
+
askId: string;
|
|
140
|
+
requestId: string;
|
|
141
|
+
answers: MyceliumJsonValue;
|
|
142
|
+
}>;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Sender-authenticated submission for one child Session's routed Ask.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### AgentControlEscalateInput
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
type AgentControlEscalateInput = Readonly<{
|
|
153
|
+
agentId: string;
|
|
154
|
+
sessionId: string;
|
|
155
|
+
itemId: string;
|
|
156
|
+
note?: string;
|
|
157
|
+
}>;
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Sender-authenticated escalation of one item a child Session left waiting.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### AgentControlAnswerOutput
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
type AgentControlAnswerOutput = z.infer<typeof answerOutput>;
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Typed outcome of a sender's answer to a child Ask.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### AgentControlEscalateOutput
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
type AgentControlEscalateOutput = z.infer<typeof escalateOutput>;
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Typed outcome of a sender's escalation of a child's item.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
132
184
|
### ArchiveRefusalReason
|
|
133
185
|
|
|
134
186
|
```ts
|
|
@@ -173,10 +225,17 @@ One host-offered model usable as an Agent's create or setModel choice.
|
|
|
173
225
|
```ts
|
|
174
226
|
type AgentDelegationInput = Readonly<{
|
|
175
227
|
agentId: string;
|
|
176
|
-
sessionId: string;
|
|
177
228
|
text: string;
|
|
178
229
|
requestId: string;
|
|
179
|
-
|
|
230
|
+
}> &
|
|
231
|
+
| Readonly<{
|
|
232
|
+
sessionId: string;
|
|
233
|
+
delivery: "steer";
|
|
234
|
+
}>
|
|
235
|
+
| Readonly<{
|
|
236
|
+
sessionId?: string;
|
|
237
|
+
delivery: "followUp";
|
|
238
|
+
goal?: string | false;
|
|
180
239
|
}>;
|
|
181
240
|
```
|
|
182
241
|
|
|
@@ -226,6 +285,16 @@ type AgentControlDirectory = Readonly<{
|
|
|
226
285
|
invocation?,
|
|
227
286
|
) => Promise<Accepted | Held>;
|
|
228
287
|
recall?: (input, sourceSessionId, signal) => Promise<CancelResult>;
|
|
288
|
+
answer?: (
|
|
289
|
+
input,
|
|
290
|
+
senderSessionId,
|
|
291
|
+
signal,
|
|
292
|
+
) => Promise<AgentControlAnswerOutput>;
|
|
293
|
+
escalate?: (
|
|
294
|
+
input,
|
|
295
|
+
senderSessionId,
|
|
296
|
+
signal,
|
|
297
|
+
) => Promise<AgentControlEscalateOutput>;
|
|
229
298
|
create?: (input, signal) => Promise<AgentControlAgent>;
|
|
230
299
|
setModel?: (input, signal) => Promise<void>;
|
|
231
300
|
archive?: (input, signal) => Promise<void>;
|
|
@@ -248,6 +317,126 @@ Host-owned discovery and per-operation access to ordinary Agents.
|
|
|
248
317
|
|
|
249
318
|
## Variables
|
|
250
319
|
|
|
320
|
+
### answerOutput
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
const answerOutput: ZodDiscriminatedUnion<
|
|
324
|
+
[
|
|
325
|
+
ZodObject<
|
|
326
|
+
{
|
|
327
|
+
status: ZodLiteral<"answered">;
|
|
328
|
+
via: ZodEnum<{
|
|
329
|
+
in_turn: "in_turn";
|
|
330
|
+
follow_up: "follow_up";
|
|
331
|
+
}>;
|
|
332
|
+
commandId: ZodExactOptional<ZodString>;
|
|
333
|
+
},
|
|
334
|
+
$strict
|
|
335
|
+
>,
|
|
336
|
+
ZodObject<
|
|
337
|
+
{
|
|
338
|
+
status: ZodLiteral<"not_found">;
|
|
339
|
+
},
|
|
340
|
+
$strict
|
|
341
|
+
>,
|
|
342
|
+
ZodObject<
|
|
343
|
+
{
|
|
344
|
+
status: ZodLiteral<"closed">;
|
|
345
|
+
reason: ZodEnum<{
|
|
346
|
+
cancelled: "cancelled";
|
|
347
|
+
undeliverable: "undeliverable";
|
|
348
|
+
answered: "answered";
|
|
349
|
+
declined: "declined";
|
|
350
|
+
withdrawn: "withdrawn";
|
|
351
|
+
expired: "expired";
|
|
352
|
+
}>;
|
|
353
|
+
},
|
|
354
|
+
$strict
|
|
355
|
+
>,
|
|
356
|
+
ZodObject<
|
|
357
|
+
{
|
|
358
|
+
status: ZodLiteral<"refused">;
|
|
359
|
+
reason: ZodEnum<{
|
|
360
|
+
not_sender: "not_sender";
|
|
361
|
+
no_provenance: "no_provenance";
|
|
362
|
+
}>;
|
|
363
|
+
},
|
|
364
|
+
$strict
|
|
365
|
+
>,
|
|
366
|
+
ZodObject<
|
|
367
|
+
{
|
|
368
|
+
status: ZodLiteral<"unavailable">;
|
|
369
|
+
reason: ZodLiteral<"unsupported">;
|
|
370
|
+
},
|
|
371
|
+
$strict
|
|
372
|
+
>,
|
|
373
|
+
],
|
|
374
|
+
"status"
|
|
375
|
+
>;
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Typed outcome of a sender's answer to a child Ask.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
### escalateOutput
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
const escalateOutput: ZodDiscriminatedUnion<
|
|
386
|
+
[
|
|
387
|
+
ZodObject<
|
|
388
|
+
{
|
|
389
|
+
status: ZodLiteral<"escalated">;
|
|
390
|
+
to: ZodLiteral<"person">;
|
|
391
|
+
},
|
|
392
|
+
$strict
|
|
393
|
+
>,
|
|
394
|
+
ZodObject<
|
|
395
|
+
{
|
|
396
|
+
status: ZodLiteral<"not_found">;
|
|
397
|
+
},
|
|
398
|
+
$strict
|
|
399
|
+
>,
|
|
400
|
+
ZodObject<
|
|
401
|
+
{
|
|
402
|
+
status: ZodLiteral<"closed">;
|
|
403
|
+
reason: ZodEnum<{
|
|
404
|
+
cancelled: "cancelled";
|
|
405
|
+
undeliverable: "undeliverable";
|
|
406
|
+
answered: "answered";
|
|
407
|
+
declined: "declined";
|
|
408
|
+
withdrawn: "withdrawn";
|
|
409
|
+
expired: "expired";
|
|
410
|
+
}>;
|
|
411
|
+
},
|
|
412
|
+
$strict
|
|
413
|
+
>,
|
|
414
|
+
ZodObject<
|
|
415
|
+
{
|
|
416
|
+
status: ZodLiteral<"refused">;
|
|
417
|
+
reason: ZodEnum<{
|
|
418
|
+
not_sender: "not_sender";
|
|
419
|
+
no_provenance: "no_provenance";
|
|
420
|
+
}>;
|
|
421
|
+
},
|
|
422
|
+
$strict
|
|
423
|
+
>,
|
|
424
|
+
ZodObject<
|
|
425
|
+
{
|
|
426
|
+
status: ZodLiteral<"unavailable">;
|
|
427
|
+
reason: ZodLiteral<"unsupported">;
|
|
428
|
+
},
|
|
429
|
+
$strict
|
|
430
|
+
>,
|
|
431
|
+
],
|
|
432
|
+
"status"
|
|
433
|
+
>;
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Typed outcome of a sender's escalation of a child's item.
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
251
440
|
### archiveRefusalReasons
|
|
252
441
|
|
|
253
442
|
```ts
|
|
@@ -279,10 +468,16 @@ function waitForAgentSettlement(
|
|
|
279
468
|
): Promise<
|
|
280
469
|
Readonly<{
|
|
281
470
|
acceptedSender: string | null;
|
|
471
|
+
delegator: string | null;
|
|
282
472
|
terminal: SessionTerminal;
|
|
283
473
|
content: string;
|
|
284
474
|
usage: readonly Pick<AssistantMessage, "model" | "provider" | "usage">[];
|
|
285
475
|
toolCalls: number;
|
|
476
|
+
waitingOnYou: readonly Readonly<{
|
|
477
|
+
itemId: string;
|
|
478
|
+
kind: string;
|
|
479
|
+
summary: string;
|
|
480
|
+
}>[];
|
|
286
481
|
}>
|
|
287
482
|
>;
|
|
288
483
|
```
|
|
@@ -301,10 +496,12 @@ Await one exact durable terminal without opening a transcript observation.
|
|
|
301
496
|
|
|
302
497
|
#### Returns
|
|
303
498
|
|
|
304
|
-
`Promise`\<`Readonly`\<\{ `acceptedSender`: `string` \| `null`; `
|
|
305
|
-
[`SessionTerminal`](client.md#sessionterminal);
|
|
306
|
-
readonly `Pick`\<`AssistantMessage`, `"model"` \|
|
|
307
|
-
`toolCalls`: `number`;
|
|
499
|
+
`Promise`\<`Readonly`\<\{ `acceptedSender`: `string` \| `null`; `delegator`:
|
|
500
|
+
`string` \| `null`; `terminal`: [`SessionTerminal`](client.md#sessionterminal);
|
|
501
|
+
`content`: `string`; `usage`: readonly `Pick`\<`AssistantMessage`, `"model"` \|
|
|
502
|
+
`"provider"` \| `"usage"`\>[]; `toolCalls`: `number`; `waitingOnYou`: readonly
|
|
503
|
+
`Readonly`\<\{ `itemId`: `string`; `kind`: `string`; `summary`: `string`;
|
|
504
|
+
\}\>[]; \}\>\>
|
|
308
505
|
|
|
309
506
|
---
|
|
310
507
|
|