@fungi.computer/shiitake 0.4.0 → 0.6.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 +1 -10
- package/dist/agent-control.d.ts +98 -5
- package/dist/agent-control.js +111 -8
- package/dist/client-entry.d.ts +20 -0
- package/dist/client-entry.js +5 -0
- package/dist/client-visitor.d.ts +31 -0
- package/dist/client-visitor.js +226 -0
- package/dist/client-wire.d.ts +5 -0
- package/dist/client-wire.js +6 -0
- package/dist/client.d.ts +4 -3
- package/dist/client.js +1 -0
- package/dist/delegation.d.ts +100 -77
- package/dist/delegation.js +73 -23
- package/dist/effect.d.ts +25 -0
- package/dist/effect.js +14 -1
- package/dist/extensions.d.ts +21 -2
- package/dist/extensions.js +1 -1
- package/dist/fleet.d.ts +22 -2
- package/dist/fleet.js +127 -36
- package/dist/hosted.js +3 -0
- package/dist/http-routes.d.ts +11 -0
- package/dist/internal/accepted-run-executor.js +2 -1
- package/dist/internal/admission-door.d.ts +5 -0
- package/dist/internal/admission-door.js +34 -3
- package/dist/internal/contracts.d.ts +14 -1
- package/dist/internal/coordinator.js +8 -2
- package/dist/internal/effect-client.js +1 -1
- package/dist/internal/effect-contracts.d.ts +4 -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 +13 -5
- 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/run-scope.d.ts +14 -0
- package/dist/internal/run-scope.js +32 -9
- package/dist/internal/runtime-coordinator.js +8 -1
- package/dist/internal/runtime-reconciliation.js +119 -24
- 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 +9 -1
- package/dist/internal/woodstock/fleet-store.d.ts +1 -0
- package/dist/internal/woodstock/fleet-store.js +28 -1
- package/dist/internal/woodstock/index.d.ts +4 -0
- package/dist/internal/woodstock/index.js +7 -0
- package/dist/internal/woodstock/session-delegator.d.ts +12 -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 +2 -0
- package/dist/internal/woodstock/session-store.js +20 -0
- package/dist/internal/woodstock/work-senders.d.ts +32 -0
- package/dist/internal/woodstock/work-senders.js +70 -0
- package/dist/remote-client.js +7 -2
- package/dist/server-visitor-stream.d.ts +13 -0
- package/dist/server-visitor-stream.js +115 -0
- package/dist/session-attention.d.ts +30 -0
- package/dist/session-attention.js +14 -0
- package/docs/api/agent-control.md +209 -13
- package/docs/api/client-entry.md +3 -0
- package/docs/api/client.md +136 -1
- package/docs/api/cloudflare.md +7 -1
- package/docs/api/delegation.md +234 -61
- package/docs/api/effect.md +16 -6
- package/docs/api/extensions.md +65 -4
- package/docs/api/fleet.md +69 -16
- package/docs/api/index.md +6 -4
- package/package.json +10 -6
package/dist/extensions.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FileTool } from "@fungi.computer/knapsack";
|
|
2
|
-
import type { AttentionItem } from "./session-attention.js";
|
|
3
|
-
export { AttentionItemSchema, type AttentionItem, type SessionAttention, } from "./session-attention.js";
|
|
2
|
+
import type { AttentionItem, AttentionRecipient } from "./session-attention.js";
|
|
3
|
+
export { AttentionItemSchema, AttentionRecipientSchema, type AttentionItem, type AttentionRecipient, type SessionAttention, } from "./session-attention.js";
|
|
4
4
|
import type { Message, Usage } from "@fungi.computer/mule/effect";
|
|
5
5
|
import type { ModuleRegistration } from "@fungi.computer/mycelium";
|
|
6
6
|
import * as Brand from "effect/Brand";
|
|
@@ -261,6 +261,8 @@ export type SendInputV0 = Readonly<{
|
|
|
261
261
|
when: "now" | "idle";
|
|
262
262
|
/** Stored with the admission and returned on `SettledRunV0.origin`. */
|
|
263
263
|
correlation?: string;
|
|
264
|
+
/** This work continues earlier work of the Session and keeps its sender. */
|
|
265
|
+
continuesWorkId?: string;
|
|
264
266
|
}>;
|
|
265
267
|
/** Door outcome for one extension admission. */
|
|
266
268
|
export type SendResultV0 = Readonly<{
|
|
@@ -290,17 +292,26 @@ export type CommandSessionIdsV0 = {
|
|
|
290
292
|
agent: null;
|
|
291
293
|
session: string;
|
|
292
294
|
};
|
|
295
|
+
/** Host-authenticated caller context; never decoded from command JSON or HTTP. */
|
|
296
|
+
export type ExtensionCommandCallerV0 = Readonly<{
|
|
297
|
+
agentId: string;
|
|
298
|
+
sessionId: string;
|
|
299
|
+
}>;
|
|
293
300
|
/** Handlers for every declared command, run in one core transaction. */
|
|
294
301
|
export type CommandHandlersV0<C extends CommandsV0> = {
|
|
295
302
|
readonly [K in keyof C]: (call: Readonly<{
|
|
296
303
|
sessionId: CommandSessionIdsV0[C[K]["scope"]];
|
|
297
304
|
input: C[K]["input"]["Type"];
|
|
305
|
+
/** Present only when the owning in-process host authenticates a caller. */
|
|
306
|
+
caller?: ExtensionCommandCallerV0;
|
|
298
307
|
}>) => C[K]["output"]["Type"] | Effect.Effect<C[K]["output"]["Type"], ExtensionCommandError | ExtensionStateError | AdmissionError, ExtensionTx | ExtensionRead>;
|
|
299
308
|
};
|
|
300
309
|
/** Transaction-scoped, extension-owned attention on an existing Session. */
|
|
301
310
|
export type AttentionV0 = Readonly<{
|
|
302
311
|
/** Upsert this extension’s item and publish the committed Session change. */
|
|
303
312
|
set: (sessionId: string, item: AttentionItem) => Effect.Effect<void, ExtensionStateError, ExtensionTx>;
|
|
313
|
+
/** Whether any extension holds an inbox item asking a person to act (a question or approval). */
|
|
314
|
+
awaitsPerson: (sessionId: string) => Effect.Effect<boolean, ExtensionStateError, ExtensionRead>;
|
|
304
315
|
/** Clear this extension’s item; other owners cannot be changed. */
|
|
305
316
|
clear: (sessionId: string, id: string) => Effect.Effect<void, ExtensionStateError, ExtensionTx>;
|
|
306
317
|
}>;
|
|
@@ -322,10 +333,18 @@ export type ExtensionContextV0<S extends CollectionsV0, Config extends Json, D e
|
|
|
322
333
|
sessionPosition: (sessionId: string) => Effect.Effect<number, ExtensionStateError, ExtensionRead>;
|
|
323
334
|
/** Observe accepted steering without consuming Mule's next control delivery. Requires run_tools. */
|
|
324
335
|
hasPendingSteer: (run: Pick<RunIdentityV0, "sessionId" | "workId">) => Effect.Effect<boolean, ExtensionStateError, ExtensionRead>;
|
|
336
|
+
/**
|
|
337
|
+
* The Agent Session that sent this run, or null when a person (or the host)
|
|
338
|
+
* did. Attention an extension raises for the run waits on it first.
|
|
339
|
+
* Requires run_tools.
|
|
340
|
+
*/
|
|
341
|
+
workSender: (run: Pick<RunIdentityV0, "sessionId" | "workId">) => Effect.Effect<AttentionRecipient | null, ExtensionStateError, ExtensionRead>;
|
|
325
342
|
/** Whether this run durably recorded a successful tool outcome. Requires run_tools. */
|
|
326
343
|
hasSuccessfulToolResult: (run: Pick<RunIdentityV0, "sessionId" | "workId">, toolCallId: string) => Effect.Effect<boolean, ExtensionStateError, ExtensionRead>;
|
|
327
344
|
/** Publish a Session observation after this transaction commits. */
|
|
328
345
|
observe: (sessionId: string) => Effect.Effect<void, never, ExtensionTx>;
|
|
346
|
+
/** Process-local live-view demand, excluding exact-command settlement waiters. */
|
|
347
|
+
isObserved: (sessionId: string) => boolean;
|
|
329
348
|
/**
|
|
330
349
|
* Run one write transaction from outside a hook (for example a Mycelium
|
|
331
350
|
* tool execute). Core commits, publishes, and requests a wake when it
|
package/dist/extensions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { AttentionItemSchema, } from "./session-attention.js";
|
|
1
|
+
export { AttentionItemSchema, AttentionRecipientSchema, } from "./session-attention.js";
|
|
2
2
|
import * as Brand from "effect/Brand";
|
|
3
3
|
import * as Context from "effect/Context";
|
|
4
4
|
import * as Data from "effect/Data";
|
package/dist/fleet.d.ts
CHANGED
|
@@ -180,11 +180,22 @@ export declare function createAgentDirectory(sql: WatchdogSqliteOwner["sql"]): {
|
|
|
180
180
|
/** Whether the retained erasure roster covers this store. */
|
|
181
181
|
erasureRosterComplete: () => boolean;
|
|
182
182
|
};
|
|
183
|
+
/**
|
|
184
|
+
* Whether an Agent may read one command's settlement: the Agent whose mail the
|
|
185
|
+
* command answered, or the Session's delegator of record for work no Agent's
|
|
186
|
+
* mail admitted (goal, loop and person turns, the work its reports carry).
|
|
187
|
+
* Another Agent's correspondence stays with that Agent.
|
|
188
|
+
*/
|
|
189
|
+
export declare function settlementReadableBy(facts: Pick<CommandSettlement, "acceptedSender" | "delegator">, agentId: string): boolean;
|
|
183
190
|
/** Replay identity and work, with the parent supplied exclusively by the grant. */
|
|
184
191
|
export declare const agentDelegationTaskSchema: z.ZodLazy<z.ZodObject<{
|
|
185
192
|
requestId: z.ZodString;
|
|
186
193
|
agentId: z.ZodString;
|
|
187
194
|
sessionId: z.ZodOptional<z.ZodString>;
|
|
195
|
+
delivery: z.ZodOptional<z.ZodEnum<{
|
|
196
|
+
followUp: "followUp";
|
|
197
|
+
steer: "steer";
|
|
198
|
+
}>>;
|
|
188
199
|
text: z.ZodString;
|
|
189
200
|
}, z.core.$strict>>;
|
|
190
201
|
/** A caller reuses this task unchanged while polling Pending. */
|
|
@@ -255,8 +266,9 @@ export declare function delegationSendMeta(sender: Readonly<{
|
|
|
255
266
|
name: string;
|
|
256
267
|
}>, invocationMeta?: Json): DelegationSendMeta;
|
|
257
268
|
/**
|
|
258
|
-
* The host metadata for one authenticated reply line
|
|
259
|
-
*
|
|
269
|
+
* The host metadata for one authenticated reply line. An ordinary reply is
|
|
270
|
+
* authored by its replying Agent; a delivery failure is a Hub/system fact,
|
|
271
|
+
* while the verified reply retains the failed recipient's identity.
|
|
260
272
|
*/
|
|
261
273
|
export declare function delegationReplyMeta(delegation: AuthenticatedDelegationReply, replierName: string): DelegationReplyMeta;
|
|
262
274
|
/**
|
|
@@ -279,6 +291,10 @@ export declare function createFleetCorrespondence(options: Omit<FleetDelegationO
|
|
|
279
291
|
start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<DelegationReceipt>;
|
|
280
292
|
/** Match a reply to frozen mail and restore the caller's delegation path. */
|
|
281
293
|
authenticateReply: (mail: BirdDogMail) => AuthenticatedDelegationReply | undefined;
|
|
294
|
+
/** Bind exhausted local delivery to its frozen sender correspondence. */
|
|
295
|
+
recordLocalUndeliverable: (original: BirdDogMail, reply: BirdDogMail) => boolean;
|
|
296
|
+
/** Prevent replay after a system undeliverable reply is admitted. */
|
|
297
|
+
recordUndeliverableReply: (mailKey: string) => boolean;
|
|
282
298
|
/** Read an existing acceptance receipt without reserving or delivering mail. */
|
|
283
299
|
receipt: (requestId: string) => Accepted | undefined;
|
|
284
300
|
/** Consume a recipient's stable `<mailKey>:accepted` notice. */
|
|
@@ -292,6 +308,10 @@ export declare function createFleetDelegation(options: FleetDelegationOptions):
|
|
|
292
308
|
start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<DelegationReceipt>;
|
|
293
309
|
/** Match a reply to frozen mail and restore the caller's delegation path. */
|
|
294
310
|
authenticateReply: (mail: BirdDogMail) => AuthenticatedDelegationReply | undefined;
|
|
311
|
+
/** Bind exhausted local delivery to its frozen sender correspondence. */
|
|
312
|
+
recordLocalUndeliverable: (original: BirdDogMail, reply: BirdDogMail) => boolean;
|
|
313
|
+
/** Prevent replay after a system undeliverable reply is admitted. */
|
|
314
|
+
recordUndeliverableReply: (mailKey: string) => boolean;
|
|
295
315
|
/** Read an existing acceptance receipt without reserving or delivering mail. */
|
|
296
316
|
receipt: (requestId: string) => Accepted | undefined;
|
|
297
317
|
/** Consume a recipient's stable `<mailKey>:accepted` notice. */
|
package/dist/fleet.js
CHANGED
|
@@ -269,11 +269,23 @@ export function createAgentDirectory(sql) {
|
|
|
269
269
|
erasureRosterComplete: store.erasureRosterComplete,
|
|
270
270
|
};
|
|
271
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Whether an Agent may read one command's settlement: the Agent whose mail the
|
|
274
|
+
* command answered, or the Session's delegator of record for work no Agent's
|
|
275
|
+
* mail admitted (goal, loop and person turns, the work its reports carry).
|
|
276
|
+
* Another Agent's correspondence stays with that Agent.
|
|
277
|
+
*/
|
|
278
|
+
export function settlementReadableBy(facts, agentId) {
|
|
279
|
+
return (facts.acceptedSender === agentId ||
|
|
280
|
+
(facts.acceptedSender === null && facts.delegator === agentId));
|
|
281
|
+
}
|
|
272
282
|
/** Replay identity and work, with the parent supplied exclusively by the grant. */
|
|
273
283
|
export const agentDelegationTaskSchema = z.lazy(() => z.strictObject({
|
|
274
284
|
requestId: identity,
|
|
275
285
|
agentId: identity,
|
|
276
286
|
sessionId: identity.optional(),
|
|
287
|
+
/** A live steer into the recipient's running turn; queued follow-up when omitted. */
|
|
288
|
+
delivery: z.enum(["followUp", "steer"]).optional(),
|
|
277
289
|
text: z
|
|
278
290
|
.string()
|
|
279
291
|
.min(1)
|
|
@@ -318,16 +330,21 @@ export function delegationSendMeta(sender, invocationMeta) {
|
|
|
318
330
|
};
|
|
319
331
|
}
|
|
320
332
|
/**
|
|
321
|
-
* The host metadata for one authenticated reply line
|
|
322
|
-
*
|
|
333
|
+
* The host metadata for one authenticated reply line. An ordinary reply is
|
|
334
|
+
* authored by its replying Agent; a delivery failure is a Hub/system fact,
|
|
335
|
+
* while the verified reply retains the failed recipient's identity.
|
|
323
336
|
*/
|
|
324
337
|
export function delegationReplyMeta(delegation, replierName) {
|
|
325
|
-
|
|
326
|
-
|
|
338
|
+
const author = delegation.reply.outcome === "undeliverable" &&
|
|
339
|
+
delegation.reply.reason === "recipient_offline"
|
|
340
|
+
? authorMeta({ id: "hub/system", name: "Hub/system" })
|
|
341
|
+
: authorMeta({
|
|
327
342
|
id: delegation.reply.agentId,
|
|
328
343
|
name: replierName,
|
|
329
344
|
kind: "agent",
|
|
330
|
-
})
|
|
345
|
+
});
|
|
346
|
+
return {
|
|
347
|
+
...author,
|
|
331
348
|
delegation: {
|
|
332
349
|
ancestors: [...delegation.ancestors],
|
|
333
350
|
maxDepth: delegation.maxDepth,
|
|
@@ -341,7 +358,7 @@ const delegationMetadata = z.object({
|
|
|
341
358
|
const delegationRowSchema = z.lazy(() => z.strictObject({
|
|
342
359
|
input: z.string(),
|
|
343
360
|
mail: z.string(),
|
|
344
|
-
state: z.enum(["pending", "held", "accepted"]),
|
|
361
|
+
state: z.enum(["pending", "held", "accepted", "undeliverable"]),
|
|
345
362
|
receipt: z.string().nullable(),
|
|
346
363
|
}));
|
|
347
364
|
const heldRecipientOffline = {
|
|
@@ -351,7 +368,7 @@ const heldRecipientOffline = {
|
|
|
351
368
|
};
|
|
352
369
|
const decodeMail = Schema.decodeUnknownSync(Schema.fromJsonString(BirdDogMailSchema));
|
|
353
370
|
function bindRecipientSession(mail, sessionId) {
|
|
354
|
-
if (mail.from.
|
|
371
|
+
if (mail.from.kind !== "agent")
|
|
355
372
|
throw new DirectoryError(503, "partial_init");
|
|
356
373
|
return {
|
|
357
374
|
...mail,
|
|
@@ -390,7 +407,8 @@ export function createFleetCorrespondence(options) {
|
|
|
390
407
|
throw new AgentGrantError("delegation_loop");
|
|
391
408
|
if (ancestors.length >= maxDepth)
|
|
392
409
|
throw new AgentGrantError("delegation_depth");
|
|
393
|
-
const
|
|
410
|
+
const path = { ancestors: [...ancestors, parent.agentId], maxDepth };
|
|
411
|
+
const delegation = task.delivery === "steer" ? { ...path, steer: true } : path;
|
|
394
412
|
const outgoingMeta = {
|
|
395
413
|
...(objectMeta.success
|
|
396
414
|
? objectMeta.data
|
|
@@ -434,7 +452,7 @@ export function createFleetCorrespondence(options) {
|
|
|
434
452
|
throw new DirectoryError(503, "partial_init");
|
|
435
453
|
if (row.input !== input)
|
|
436
454
|
throw new DirectoryError(409, "request_conflict");
|
|
437
|
-
if (row.state === "held")
|
|
455
|
+
if (row.state === "held" || row.state === "undeliverable")
|
|
438
456
|
return heldRecipientOffline;
|
|
439
457
|
if (row.state === "accepted") {
|
|
440
458
|
if (row.receipt === null)
|
|
@@ -466,25 +484,55 @@ export function createFleetCorrespondence(options) {
|
|
|
466
484
|
*/
|
|
467
485
|
function authenticateReply(mail) {
|
|
468
486
|
// This owner sends to one correspondent, so BirdDog's reply index is zero.
|
|
469
|
-
|
|
487
|
+
// A report names the idle Session's delegator mail and the settled command.
|
|
470
488
|
if (mail.replyTo.length !== 0 ||
|
|
471
489
|
mail.reply === undefined ||
|
|
472
|
-
mail.reply.outcome === "accepted"
|
|
473
|
-
!mail.key.endsWith(suffix))
|
|
490
|
+
mail.reply.outcome === "accepted")
|
|
474
491
|
return undefined;
|
|
475
|
-
const
|
|
476
|
-
|
|
492
|
+
const reportAt = mail.key.lastIndexOf(":report:");
|
|
493
|
+
// A report's key names the command it settles.
|
|
494
|
+
const isReport = reportAt >= 0 &&
|
|
495
|
+
"commandId" in mail.reply &&
|
|
496
|
+
mail.key.slice(reportAt) === `:report:${mail.reply.commandId}:0`;
|
|
497
|
+
const mailKey = isReport
|
|
498
|
+
? mail.key.slice(0, reportAt)
|
|
499
|
+
: mail.key.endsWith(":reply:0")
|
|
500
|
+
? mail.key.slice(0, -":reply:0".length)
|
|
501
|
+
: undefined;
|
|
502
|
+
if (mailKey === undefined)
|
|
503
|
+
return undefined;
|
|
504
|
+
const row = read(mailKey);
|
|
505
|
+
if (row === undefined || row.state === "undeliverable")
|
|
477
506
|
return undefined;
|
|
478
507
|
const sent = decodeMail(row.mail);
|
|
479
|
-
if (
|
|
480
|
-
mail.from.sessionId === undefined)
|
|
508
|
+
if (sent.key !== mailKey)
|
|
481
509
|
return undefined;
|
|
482
|
-
|
|
483
|
-
mail.
|
|
484
|
-
mail.
|
|
485
|
-
|
|
510
|
+
const systemUndeliverable = mail.reply.outcome === "undeliverable" &&
|
|
511
|
+
mail.reply.reason === "recipient_offline" &&
|
|
512
|
+
mail.from.kind === "system";
|
|
513
|
+
if (systemUndeliverable) {
|
|
514
|
+
if (row.state !== "held" ||
|
|
515
|
+
sent.from.kind !== "agent" ||
|
|
516
|
+
mail.key !== `${sent.key}:reply:0` ||
|
|
517
|
+
mail.to.agentId !== sent.from.agentId ||
|
|
518
|
+
mail.to.sessionId !== sent.from.sessionId)
|
|
519
|
+
return undefined;
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
if (sent.from.kind !== "agent" ||
|
|
523
|
+
mail.from.kind !== "agent" ||
|
|
524
|
+
mail.from.agentId !== sent.to.agentId ||
|
|
525
|
+
mail.from.sessionId !== sent.to.sessionId ||
|
|
526
|
+
mail.to.agentId !== sent.from.agentId ||
|
|
527
|
+
mail.to.sessionId !== sent.from.sessionId)
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
if (mail.reply.outcome === "undeliverable" &&
|
|
531
|
+
mail.reply.reason === "recipient_offline" &&
|
|
532
|
+
!systemUndeliverable)
|
|
486
533
|
return undefined;
|
|
487
|
-
if (
|
|
534
|
+
if (!isReport &&
|
|
535
|
+
mail.reply.outcome !== "undeliverable" &&
|
|
488
536
|
row.state === "accepted" &&
|
|
489
537
|
row.receipt !== null) {
|
|
490
538
|
const sentResult = parseAccepted(row.receipt);
|
|
@@ -496,16 +544,28 @@ export function createFleetCorrespondence(options) {
|
|
|
496
544
|
return undefined;
|
|
497
545
|
let reply;
|
|
498
546
|
if (mail.reply.outcome === "undeliverable") {
|
|
499
|
-
const
|
|
547
|
+
const agentId = systemUndeliverable
|
|
548
|
+
? sent.to.agentId
|
|
549
|
+
: mail.from.kind === "agent"
|
|
550
|
+
? mail.from.agentId
|
|
551
|
+
: undefined;
|
|
552
|
+
if (agentId === undefined)
|
|
553
|
+
return undefined;
|
|
554
|
+
const undeliverable = { agentId, ...mail.reply };
|
|
555
|
+
const sessionId = systemUndeliverable
|
|
556
|
+
? sent.to.sessionId
|
|
557
|
+
: mail.from.kind === "agent"
|
|
558
|
+
? mail.from.sessionId
|
|
559
|
+
: undefined;
|
|
500
560
|
reply =
|
|
501
|
-
|
|
561
|
+
sessionId === undefined
|
|
502
562
|
? undeliverable
|
|
503
|
-
: { ...undeliverable, sessionId
|
|
563
|
+
: { ...undeliverable, sessionId };
|
|
504
564
|
}
|
|
505
565
|
else {
|
|
506
|
-
|
|
507
|
-
if (sessionId === undefined)
|
|
566
|
+
if (mail.from.kind !== "agent")
|
|
508
567
|
return undefined;
|
|
568
|
+
const sessionId = mail.from.sessionId;
|
|
509
569
|
reply = { agentId: mail.from.agentId, sessionId, ...mail.reply };
|
|
510
570
|
}
|
|
511
571
|
return {
|
|
@@ -514,6 +574,29 @@ export function createFleetCorrespondence(options) {
|
|
|
514
574
|
reply,
|
|
515
575
|
};
|
|
516
576
|
}
|
|
577
|
+
/** Bind an exhausted local Carrier row to its frozen sender correspondence. */
|
|
578
|
+
function recordLocalUndeliverable(original, reply) {
|
|
579
|
+
const row = read(original.key);
|
|
580
|
+
if (row === undefined ||
|
|
581
|
+
(row.state !== "pending" && row.state !== "held") ||
|
|
582
|
+
original.from.kind !== "agent" ||
|
|
583
|
+
original.reply !== undefined ||
|
|
584
|
+
JSON.stringify(decodeMail(row.mail)) !== JSON.stringify(original) ||
|
|
585
|
+
reply.key !== `${original.key}:reply:0` ||
|
|
586
|
+
reply.from.kind !== "system" ||
|
|
587
|
+
reply.reply?.outcome !== "undeliverable" ||
|
|
588
|
+
reply.reply.reason !== "recipient_offline" ||
|
|
589
|
+
reply.to.agentId !== original.from.agentId ||
|
|
590
|
+
reply.to.sessionId !== original.from.sessionId ||
|
|
591
|
+
reply.replyTo.length !== 0)
|
|
592
|
+
return false;
|
|
593
|
+
store.recordHeld(original.key);
|
|
594
|
+
return read(original.key)?.state === "held";
|
|
595
|
+
}
|
|
596
|
+
/** Make a successfully admitted system disposition replay-ineligible. */
|
|
597
|
+
function recordUndeliverableReply(mailKey) {
|
|
598
|
+
return store.recordUndeliverable(mailKey);
|
|
599
|
+
}
|
|
517
600
|
/** Read only the existing acceptance receipt; do not reserve or deliver mail. */
|
|
518
601
|
function receipt(requestId) {
|
|
519
602
|
const row = read(requestId);
|
|
@@ -524,9 +607,11 @@ export function createFleetCorrespondence(options) {
|
|
|
524
607
|
/** Atomically bind the recipient Session carried by a late acceptance notice. */
|
|
525
608
|
function recordAcceptedNotice(mail) {
|
|
526
609
|
const suffix = ":accepted";
|
|
610
|
+
if (mail.from.kind !== "agent")
|
|
611
|
+
return undefined;
|
|
612
|
+
const sender = mail.from;
|
|
527
613
|
if (mail.replyTo.length !== 0 ||
|
|
528
614
|
mail.reply?.outcome !== "accepted" ||
|
|
529
|
-
mail.from.sessionId === undefined ||
|
|
530
615
|
!mail.key.endsWith(suffix))
|
|
531
616
|
return undefined;
|
|
532
617
|
const requestId = mail.key.slice(0, -suffix.length);
|
|
@@ -534,29 +619,31 @@ export function createFleetCorrespondence(options) {
|
|
|
534
619
|
if (row === undefined)
|
|
535
620
|
return undefined;
|
|
536
621
|
const sent = decodeMail(row.mail);
|
|
622
|
+
if (sent.from.kind !== "agent")
|
|
623
|
+
return undefined;
|
|
624
|
+
const sentSender = sent.from;
|
|
537
625
|
const path = delegationMetadata.safeParse(sent.meta);
|
|
538
|
-
if (
|
|
539
|
-
!path.success ||
|
|
626
|
+
if (!path.success ||
|
|
540
627
|
path.data.delegation === undefined ||
|
|
541
628
|
mail.key !== `${sent.key}:accepted` ||
|
|
542
|
-
|
|
629
|
+
sender.agentId !== sent.to.agentId ||
|
|
543
630
|
(sent.to.sessionId !== undefined &&
|
|
544
|
-
sent.to.sessionId !==
|
|
545
|
-
mail.to.agentId !==
|
|
546
|
-
mail.to.sessionId !==
|
|
631
|
+
sent.to.sessionId !== sender.sessionId) ||
|
|
632
|
+
mail.to.agentId !== sentSender.agentId ||
|
|
633
|
+
mail.to.sessionId !== sentSender.sessionId)
|
|
547
634
|
return undefined;
|
|
548
635
|
if (row.state === "accepted" && row.receipt !== null) {
|
|
549
636
|
const current = parseAccepted(row.receipt);
|
|
550
637
|
if (current.commandId !== mail.reply.commandId ||
|
|
551
|
-
current.sessionId !==
|
|
638
|
+
current.sessionId !== sender.sessionId)
|
|
552
639
|
return undefined;
|
|
553
640
|
}
|
|
554
641
|
const accepted = {
|
|
555
642
|
status: "accepted",
|
|
556
643
|
commandId: mail.reply.commandId,
|
|
557
|
-
sessionId:
|
|
644
|
+
sessionId: sender.sessionId,
|
|
558
645
|
};
|
|
559
|
-
store.recordReceipt(requestId, JSON.stringify(accepted), row.mail, JSON.stringify(bindRecipientSession(sent,
|
|
646
|
+
store.recordReceipt(requestId, JSON.stringify(accepted), row.mail, JSON.stringify(bindRecipientSession(sent, sender.sessionId)));
|
|
560
647
|
return accepted;
|
|
561
648
|
}
|
|
562
649
|
return {
|
|
@@ -564,6 +651,10 @@ export function createFleetCorrespondence(options) {
|
|
|
564
651
|
start,
|
|
565
652
|
/** Match a reply to frozen mail and restore the caller's delegation path. */
|
|
566
653
|
authenticateReply,
|
|
654
|
+
/** Bind exhausted local delivery to its frozen sender correspondence. */
|
|
655
|
+
recordLocalUndeliverable,
|
|
656
|
+
/** Prevent replay after a system undeliverable reply is admitted. */
|
|
657
|
+
recordUndeliverableReply,
|
|
567
658
|
/** Read an existing acceptance receipt without reserving or delivering mail. */
|
|
568
659
|
receipt,
|
|
569
660
|
/** Consume a recipient's stable `<mailKey>:accepted` notice. */
|
package/dist/hosted.js
CHANGED
|
@@ -166,12 +166,15 @@ function hostedPort(manifest, revision, grants) {
|
|
|
166
166
|
}),
|
|
167
167
|
attention: Object.freeze({
|
|
168
168
|
set: () => Effect.fail(unavailable("Attention")),
|
|
169
|
+
awaitsPerson: () => Effect.fail(unavailable("Attention")),
|
|
169
170
|
clear: () => Effect.fail(unavailable("Attention")),
|
|
170
171
|
}),
|
|
171
172
|
hasPendingSteer: () => Effect.fail(unavailable("Steering")),
|
|
173
|
+
workSender: () => Effect.fail(unavailable("Run sender")),
|
|
172
174
|
sessionPosition: () => Effect.fail(unavailable("Session timeline")),
|
|
173
175
|
hasSuccessfulToolResult: () => Effect.fail(unavailable("A tool result")),
|
|
174
176
|
observe: () => Effect.die(unavailable("Observation")),
|
|
177
|
+
isObserved: () => false,
|
|
175
178
|
transact: () => Effect.fail(new ShiitakeError("extensions", "rejected", "A hosted extension has no Agent transaction")),
|
|
176
179
|
}),
|
|
177
180
|
});
|
package/dist/http-routes.d.ts
CHANGED
|
@@ -502,6 +502,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
502
502
|
};
|
|
503
503
|
output: {
|
|
504
504
|
readonly acceptedSender: string | null;
|
|
505
|
+
readonly delegator: string | null;
|
|
505
506
|
readonly terminal: {
|
|
506
507
|
readonly commandId: string;
|
|
507
508
|
readonly outcome: "completed" | "cancelled" | "failed" | "interrupted";
|
|
@@ -531,6 +532,11 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
531
532
|
};
|
|
532
533
|
}[];
|
|
533
534
|
readonly toolCalls: number;
|
|
535
|
+
readonly waitingOnYou: readonly {
|
|
536
|
+
readonly itemId: string;
|
|
537
|
+
readonly kind: string;
|
|
538
|
+
readonly summary: string;
|
|
539
|
+
}[];
|
|
534
540
|
} | null;
|
|
535
541
|
outputFormat: "json";
|
|
536
542
|
status: 200;
|
|
@@ -873,6 +879,11 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
873
879
|
readonly extensionId: string;
|
|
874
880
|
readonly expiresAt?: number | undefined;
|
|
875
881
|
readonly inbox?: false | undefined;
|
|
882
|
+
readonly waitingOn?: {
|
|
883
|
+
readonly agentId: string;
|
|
884
|
+
readonly sessionId: string;
|
|
885
|
+
readonly name: string;
|
|
886
|
+
} | undefined;
|
|
876
887
|
}[];
|
|
877
888
|
readonly context?: {
|
|
878
889
|
readonly tokens: number;
|
|
@@ -9,7 +9,7 @@ import { ExtensionId } from "../extension-contract.js";
|
|
|
9
9
|
import { publishOwnerObservation } from "./observation-kernel.js";
|
|
10
10
|
import { returnedFailureReason } from "./run-failure-reason.js";
|
|
11
11
|
import { coreProtocolDigest } from "./extension-roster.js";
|
|
12
|
-
import { runAcceptedCommand, runAcceptedCompaction, ToolSetupError, } from "./run-scope.js";
|
|
12
|
+
import { runAcceptedCommand, runAcceptedCompaction, RunRejectedError, ToolSetupError, } from "./run-scope.js";
|
|
13
13
|
import { CompactionCommitError } from "./compaction/session-compaction.js";
|
|
14
14
|
import { BrokenSessionError, isCompactCommand, loadAcceptedRun, readExtensionAdmissionOrigin, recordWorkSettlement, } from "./woodstock/index.js";
|
|
15
15
|
/** Bounded process-loss recovery for every accepted Agent turn. */
|
|
@@ -25,6 +25,7 @@ function runtimeFailure(message) {
|
|
|
25
25
|
function failedDecision(error) {
|
|
26
26
|
const category = `${error.boundary}:${error.cause}`;
|
|
27
27
|
const diagnostic = error instanceof ToolSetupError ||
|
|
28
|
+
error instanceof RunRejectedError ||
|
|
28
29
|
error instanceof CompactionCommitError ||
|
|
29
30
|
error instanceof BrokenSessionError
|
|
30
31
|
? error.diagnostic
|
|
@@ -34,6 +34,11 @@ export type AdmitRequest = Readonly<{
|
|
|
34
34
|
reply?: VerifiedAgentReply;
|
|
35
35
|
/** Synchronous owner-local side effect committed with a mail admission. */
|
|
36
36
|
onAccepted?: (sessionId: string, commandId: string) => void;
|
|
37
|
+
/**
|
|
38
|
+
* New work that continues earlier work in this Session (for example, the
|
|
39
|
+
* follow-up that delivers an answer) keeps that work's sender.
|
|
40
|
+
*/
|
|
41
|
+
continuesWorkId?: string | undefined;
|
|
37
42
|
}>;
|
|
38
43
|
/** Outcome of one door admission. */
|
|
39
44
|
export type AdmitResult = Readonly<{
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { messageAuthor } from "@fungi.computer/agar/agents";
|
|
1
2
|
import * as Effect from "effect/Effect";
|
|
2
3
|
import * as Schema from "effect/Schema";
|
|
3
4
|
import { checkHostMeta } from "./host-meta.js";
|
|
4
5
|
import { ShiitakeError } from "../errors.js";
|
|
5
6
|
import { decodeClientAcceptedCommand, decodeClientSessionId, decodeClientSessionCreationRequestId, requireDecoded, } from "./client-boundary.js";
|
|
6
7
|
import { publishOwnerObservation } from "./observation-kernel.js";
|
|
7
|
-
import { AdmissionOriginSchema, acceptSessionCommandOwned, readSessionCommandAdmissionByRequest, withdrawSessionRequestOwned, assertSessionRequestNotWithdrawn, hasStoredSession, VerifiedAgentReplySchema, } from "./woodstock/index.js";
|
|
8
|
+
import { AdmissionOriginSchema, acceptSessionCommandOwned, readSessionCommandAdmissionByRequest, withdrawSessionRequestOwned, assertSessionRequestNotWithdrawn, hasStoredSession, readWorkSender, recordWorkSender, recordSessionDelegator, VerifiedAgentReplySchema, } from "./woodstock/index.js";
|
|
8
9
|
const decodeOrigin = Schema.decodeUnknownResult(AdmissionOriginSchema, {
|
|
9
10
|
onExcessProperty: "error",
|
|
10
11
|
});
|
|
@@ -16,14 +17,37 @@ function decodeReply(request) {
|
|
|
16
17
|
return undefined;
|
|
17
18
|
const reply = requireDecoded(decodeVerifiedReply(request.reply), "Verified Agent reply is invalid");
|
|
18
19
|
const mail = request.mail;
|
|
20
|
+
const agentAuthored = mail?.from.kind === "agent" &&
|
|
21
|
+
mail.from.agentId === reply.agentId &&
|
|
22
|
+
mail.from.sessionId === reply.sessionId;
|
|
23
|
+
const systemUndeliverable = mail?.from.kind === "system" &&
|
|
24
|
+
reply.outcome === "undeliverable" &&
|
|
25
|
+
reply.reason === "recipient_offline";
|
|
19
26
|
if (mail?.replyTo.length !== 0 ||
|
|
20
|
-
|
|
21
|
-
mail.from.sessionId !== reply.sessionId ||
|
|
27
|
+
(!agentAuthored && !systemUndeliverable) ||
|
|
22
28
|
request.command.type !== "prompt" ||
|
|
23
29
|
request.command.delivery !== "steer")
|
|
24
30
|
throw new ShiitakeError("contract", "invalid", "Verified Agent reply does not match its mail");
|
|
25
31
|
return reply;
|
|
26
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The Agent Session whose work this admission is: the sender of delegated
|
|
35
|
+
* mail that expects a reply, or the sender of the earlier work this one
|
|
36
|
+
* continues. Replies and every other admission have none.
|
|
37
|
+
*/
|
|
38
|
+
function workSender(owner, request) {
|
|
39
|
+
const mail = request.mail;
|
|
40
|
+
if (mail?.from.kind === "agent" && mail.replyTo.length > 0)
|
|
41
|
+
return {
|
|
42
|
+
agentId: mail.from.agentId,
|
|
43
|
+
sessionId: mail.from.sessionId,
|
|
44
|
+
// The host-authenticated author names the sender; mail.meta is the sender's own claim.
|
|
45
|
+
name: messageAuthor(request.meta)?.name ?? mail.from.agentId,
|
|
46
|
+
};
|
|
47
|
+
return request.continuesWorkId === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: readWorkSender(owner, request.sessionId, request.continuesWorkId);
|
|
50
|
+
}
|
|
27
51
|
/** Bind the door to one owner, run contract, Watchdog queue and host wake. */
|
|
28
52
|
export function openAdmissionDoor(input) {
|
|
29
53
|
/** The door's one decode, done before any owner operation when possible. */
|
|
@@ -36,6 +60,7 @@ export function openAdmissionDoor(input) {
|
|
|
36
60
|
mail: request.mail,
|
|
37
61
|
reply: decodeReply(request),
|
|
38
62
|
onAccepted: request.onAccepted,
|
|
63
|
+
continuesWorkId: request.continuesWorkId,
|
|
39
64
|
meta: request.meta === undefined
|
|
40
65
|
? undefined
|
|
41
66
|
: checkHostMeta(input.hostMeta, request.meta),
|
|
@@ -70,6 +95,12 @@ export function openAdmissionDoor(input) {
|
|
|
70
95
|
input.enqueue(work, request.mail);
|
|
71
96
|
}
|
|
72
97
|
: undefined, targetWorkId, request.reply);
|
|
98
|
+
if (accepted.delivery === "new" && targetWorkId === undefined) {
|
|
99
|
+
const sender = workSender(input.owner, request);
|
|
100
|
+
if (sender !== undefined)
|
|
101
|
+
recordWorkSender(input.owner, sessionId, accepted.accepted.commandId, sender);
|
|
102
|
+
}
|
|
103
|
+
recordSessionDelegator(input.owner, sessionId, request.mail);
|
|
73
104
|
request.onAccepted?.(sessionId, accepted.accepted.commandId);
|
|
74
105
|
touched.set(sessionId, (touched.get(sessionId) ?? false) || accepted.delivery === "new");
|
|
75
106
|
return Object.freeze({
|
|
@@ -3,7 +3,7 @@ import type { SessionTerminal, ShiitakeAgent } from "../client.js";
|
|
|
3
3
|
import type { GrantsDelivery, GrantsDeliveryResult } from "./woodstock/index.js";
|
|
4
4
|
import type { Json } from "../extensions.js";
|
|
5
5
|
import type { ExtensionRosterV0 } from "../extensions.js";
|
|
6
|
-
import type { Api, AssistantMessage, Model, MuleStreamFunction } from "@fungi.computer/mule/effect";
|
|
6
|
+
import type { Api, AssistantMessage, Model, MuleStreamFunction, ProviderRejection } from "@fungi.computer/mule/effect";
|
|
7
7
|
import type { AccessOptions, CapabilitySource, ExecutionOptions, ModuleRegistration, Sandbox } from "@fungi.computer/mycelium";
|
|
8
8
|
import type { ModelPreference } from "./model-preference.js";
|
|
9
9
|
import type { WatchdogSqliteValue, WatchdogSqliteRow, WatchdogSqliteOwner, WatchdogSqliteCursor } from "@fungi.computer/watchdog";
|
|
@@ -47,6 +47,8 @@ export type ModelAcquisition = Readonly<{
|
|
|
47
47
|
* Unknown messages must return undefined.
|
|
48
48
|
*/
|
|
49
49
|
failureReason?: (message: AssistantMessage) => string | undefined;
|
|
50
|
+
/** Bounded provider facts for this door's exact failure, never SDK prose. */
|
|
51
|
+
rejection?: (message: AssistantMessage) => ProviderRejection | undefined;
|
|
50
52
|
/** Provider prose for this door's exact failed turn; never a diagnostic. */
|
|
51
53
|
providerError?: (message: AssistantMessage) => string | undefined;
|
|
52
54
|
/** Release the acquired model resources. */
|
|
@@ -253,6 +255,8 @@ export type { HostMetaOptions, StandardHostMetaResult, StandardHostMetaSchema, }
|
|
|
253
255
|
export type CommandSettlement = Readonly<{
|
|
254
256
|
/** Original Agent sender recorded at acceptance, never the terminal's canceller. */
|
|
255
257
|
acceptedSender: string | null;
|
|
258
|
+
/** Agent that first sent this Session work and receives its reports, if any. */
|
|
259
|
+
delegator: string | null;
|
|
256
260
|
/** Durable terminal and the command that ended the work's opaque metadata. */
|
|
257
261
|
terminal: SessionTerminal;
|
|
258
262
|
/** Last completed assistant text; cancelled work returns no answer. */
|
|
@@ -261,4 +265,13 @@ export type CommandSettlement = Readonly<{
|
|
|
261
265
|
usage: readonly Pick<AssistantMessage, "provider" | "model" | "usage">[];
|
|
262
266
|
/** Distinct tool-call intentions belonging to this exact work. */
|
|
263
267
|
toolCalls: number;
|
|
268
|
+
/**
|
|
269
|
+
* Open items of the Session that now wait on the Agent that sent this work.
|
|
270
|
+
* Child-authored data: summaries inform, only the sender's host checks decide.
|
|
271
|
+
*/
|
|
272
|
+
waitingOnYou: readonly Readonly<{
|
|
273
|
+
itemId: string;
|
|
274
|
+
kind: string;
|
|
275
|
+
summary: string;
|
|
276
|
+
}>[];
|
|
264
277
|
}>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { nanoid } from "@fungi.computer/cairn";
|
|
2
2
|
import { parseModelPreference } from "./model-preference.js";
|
|
3
3
|
import { acceptedNotice } from "@fungi.computer/bird-dog/effect";
|
|
4
|
+
import { FungiMessageMetaSchema } from "@fungi.computer/agar/agents";
|
|
4
5
|
import * as Effect from "effect/Effect";
|
|
5
6
|
import * as Option from "effect/Option";
|
|
6
7
|
import * as Stream from "effect/Stream";
|
|
@@ -348,7 +349,7 @@ export function openCoordinator(options, runtime) {
|
|
|
348
349
|
})), Effect.asVoid))),
|
|
349
350
|
}),
|
|
350
351
|
extension: (extensionId) => Object.freeze({
|
|
351
|
-
call: (command, input) => runtime.assertOpen.pipe(Effect.andThen(runtime.extensions.call({ scope: "session", sessionId }, extensionId, command, input))),
|
|
352
|
+
call: (command, input, caller) => runtime.assertOpen.pipe(Effect.andThen(runtime.extensions.call({ scope: "session", sessionId }, extensionId, command, input, caller))),
|
|
352
353
|
}),
|
|
353
354
|
read: (input) => runtime.assertOpen.pipe(Effect.andThen(readSessionWithContext(options, runtime, sessionId, input))),
|
|
354
355
|
readSettlement: (commandId, timeoutMs = 0) => Effect.scoped(Effect.gen(function* () {
|
|
@@ -410,6 +411,11 @@ export function openCoordinator(options, runtime) {
|
|
|
410
411
|
}),
|
|
411
412
|
});
|
|
412
413
|
}
|
|
414
|
+
/** Whether the sender asked this mail to steer the recipient's running turn. */
|
|
415
|
+
function steersRunningTurn(mail) {
|
|
416
|
+
const meta = FungiMessageMetaSchema.safeParse(mail.meta);
|
|
417
|
+
return meta.success && meta.data.delegation?.steer === true;
|
|
418
|
+
}
|
|
413
419
|
/**
|
|
414
420
|
* Accept one addressed mail envelope; the door decodes it as a prompt. `reply`
|
|
415
421
|
* is the host's authentication of a delegated Agent's reply; only then does
|
|
@@ -458,7 +464,7 @@ export function acceptAddressedMail(runtime, mail, meta, reply, stageMail, sessi
|
|
|
458
464
|
}
|
|
459
465
|
return admitUserWork(runtime, mail.to.sessionId, {
|
|
460
466
|
requestId: mail.key,
|
|
461
|
-
delivery: reply
|
|
467
|
+
delivery: reply !== undefined || steersRunningTurn(mail) ? "steer" : "followUp",
|
|
462
468
|
type: "prompt",
|
|
463
469
|
text: mail.content,
|
|
464
470
|
}, meta, mail, reply);
|
|
@@ -72,7 +72,7 @@ function sessionFacade(address, session) {
|
|
|
72
72
|
extension: (extensionId) => {
|
|
73
73
|
const extension = session.extension(extensionId);
|
|
74
74
|
return Object.freeze({
|
|
75
|
-
call: (command, input) => runShiitakePromise(extension.call(command, input)),
|
|
75
|
+
call: (command, input, caller) => runShiitakePromise(extension.call(command, input, caller)),
|
|
76
76
|
});
|
|
77
77
|
},
|
|
78
78
|
read: (input) => runShiitakePromise(session.read(input)),
|