@fungi.computer/shiitake 0.2.0 → 0.3.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.
Files changed (112) hide show
  1. package/README.md +155 -34
  2. package/dist/agent-control.d.ts +77 -5
  3. package/dist/agent-control.js +271 -65
  4. package/dist/client-entry.d.ts +15 -8
  5. package/dist/client-entry.js +7 -5
  6. package/dist/client-wire.d.ts +24 -8
  7. package/dist/client-wire.js +53 -11
  8. package/dist/client.d.ts +56 -11
  9. package/dist/client.js +1 -1
  10. package/dist/cloudflare.d.ts +42 -23
  11. package/dist/cloudflare.js +76 -54
  12. package/dist/cold-sessions.d.ts +28 -0
  13. package/dist/cold-sessions.js +1 -0
  14. package/dist/delegation.d.ts +214 -0
  15. package/dist/delegation.js +319 -0
  16. package/dist/effect.d.ts +1 -1
  17. package/dist/errors.d.ts +2 -2
  18. package/dist/errors.js +2 -2
  19. package/dist/extension-contract.d.ts +10 -2
  20. package/dist/extensions.d.ts +6 -2
  21. package/dist/fleet.d.ts +43 -9
  22. package/dist/fleet.js +88 -25
  23. package/dist/hosted.js +1 -0
  24. package/dist/http-routes.d.ts +106 -22
  25. package/dist/http-routes.js +18 -1
  26. package/dist/index.d.ts +3 -2
  27. package/dist/internal/accepted-run-executor.d.ts +5 -0
  28. package/dist/internal/accepted-run-executor.js +10 -4
  29. package/dist/internal/admission-door.d.ts +3 -0
  30. package/dist/internal/admission-door.js +12 -2
  31. package/dist/internal/client-boundary.js +7 -1
  32. package/dist/internal/compaction/compaction-slot.d.ts +36 -0
  33. package/dist/internal/compaction/compaction-slot.js +41 -0
  34. package/dist/internal/compaction/compaction.d.ts +7 -4
  35. package/dist/internal/compaction/compaction.js +59 -30
  36. package/dist/internal/compaction/session-compaction.d.ts +8 -16
  37. package/dist/internal/compaction/session-compaction.js +358 -119
  38. package/dist/internal/compaction/settings.js +5 -0
  39. package/dist/internal/compaction/utils.d.ts +19 -16
  40. package/dist/internal/compaction/utils.js +30 -84
  41. package/dist/internal/contracts.d.ts +40 -13
  42. package/dist/internal/coordinator.js +28 -10
  43. package/dist/internal/effect-client.d.ts +4 -2
  44. package/dist/internal/effect-client.js +29 -0
  45. package/dist/internal/effect-contracts.d.ts +17 -4
  46. package/dist/internal/extension-runtime.d.ts +5 -0
  47. package/dist/internal/extension-runtime.js +184 -89
  48. package/dist/internal/host-meta.d.ts +7 -0
  49. package/dist/internal/model-acquisition.js +7 -1
  50. package/dist/internal/model-preference.d.ts +2 -0
  51. package/dist/internal/model-preference.js +2 -1
  52. package/dist/internal/promise-facade.js +4 -16
  53. package/dist/internal/run-failure-reason.d.ts +10 -0
  54. package/dist/internal/run-failure-reason.js +19 -0
  55. package/dist/internal/run-scope.d.ts +4 -1
  56. package/dist/internal/run-scope.js +38 -5
  57. package/dist/internal/runtime-coordinator.d.ts +2 -1
  58. package/dist/internal/runtime-coordinator.js +65 -4
  59. package/dist/internal/runtime-core.js +1 -0
  60. package/dist/internal/runtime-reconciliation.js +42 -6
  61. package/dist/internal/session-context.d.ts +1 -2
  62. package/dist/internal/session-context.js +8 -30
  63. package/dist/internal/session-status.d.ts +2 -1
  64. package/dist/internal/session-status.js +4 -1
  65. package/dist/internal/woodstock/client-projection.d.ts +6 -5
  66. package/dist/internal/woodstock/client-projection.js +79 -53
  67. package/dist/internal/woodstock/cold-sessions.d.ts +4 -0
  68. package/dist/internal/woodstock/cold-sessions.js +90 -0
  69. package/dist/internal/woodstock/extension-store.d.ts +2 -0
  70. package/dist/internal/woodstock/extension-store.js +4 -0
  71. package/dist/internal/woodstock/fleet-store.d.ts +5 -1
  72. package/dist/internal/woodstock/fleet-store.js +17 -5
  73. package/dist/internal/woodstock/index.d.ts +11 -3
  74. package/dist/internal/woodstock/index.js +33 -14
  75. package/dist/internal/woodstock/progress.d.ts +14 -1
  76. package/dist/internal/woodstock/progress.js +97 -19
  77. package/dist/internal/woodstock/run-recovery.js +6 -4
  78. package/dist/internal/woodstock/session-attention.d.ts +1 -1
  79. package/dist/internal/woodstock/session-attention.js +2 -2
  80. package/dist/internal/woodstock/session-entry.d.ts +109 -42
  81. package/dist/internal/woodstock/session-entry.js +63 -20
  82. package/dist/internal/woodstock/session-inbox.js +2 -0
  83. package/dist/internal/woodstock/session-store.d.ts +104 -24
  84. package/dist/internal/woodstock/session-store.js +937 -246
  85. package/dist/internal/woodstock/storage-admission.d.ts +8 -8
  86. package/dist/internal/woodstock/storage-admission.js +8 -34
  87. package/dist/internal/woodstock/storage-conformance.d.ts +15 -0
  88. package/dist/internal/woodstock/storage-conformance.js +489 -0
  89. package/dist/model-preference.d.ts +2 -2
  90. package/dist/model-preference.js +2 -2
  91. package/dist/node.js +1 -1
  92. package/dist/remote-client.js +8 -1
  93. package/dist/session-attention.d.ts +8 -1
  94. package/dist/session-attention.js +6 -1
  95. package/dist/testing.js +1 -0
  96. package/docs/api/README.md +18 -16
  97. package/docs/api/agent-control.md +171 -1
  98. package/docs/api/client-entry.md +26 -10
  99. package/docs/api/client.md +153 -37
  100. package/docs/api/cloudflare.md +76 -23
  101. package/docs/api/delegation.md +485 -0
  102. package/docs/api/effect.md +20 -2
  103. package/docs/api/extensions.md +21 -9
  104. package/docs/api/fleet.md +159 -104
  105. package/docs/api/http.md +4 -1
  106. package/docs/api/index.md +116 -20
  107. package/docs/api/internal/woodstock/storage-conformance.md +81 -0
  108. package/docs/api/model-preference.md +17 -0
  109. package/docs/api/testing.md +16 -4
  110. package/package.json +26 -14
  111. package/dist/internal/browser-sessions.d.ts +0 -31
  112. package/dist/internal/browser-sessions.js +0 -173
package/dist/fleet.js CHANGED
@@ -71,6 +71,7 @@ const agentRowSchema = z.lazy(() => z.strictObject({
71
71
  agent_id: z.string(),
72
72
  name: z.string(),
73
73
  actor_user_id: z.string(),
74
+ created_by: z.string().min(1).max(256).nullable(),
74
75
  created_at: z.number().int().nonnegative(),
75
76
  updated_at: z.number().int().nonnegative(),
76
77
  }));
@@ -86,6 +87,7 @@ export const agentCreationSchema = z.lazy(() => z.strictObject({
86
87
  name: z.string().min(1).max(256),
87
88
  requestId: z.string().min(1).max(256),
88
89
  configuration: z.string().min(1).max(4096),
90
+ createdBy: z.string().min(1).max(256).nullable().optional(),
89
91
  }));
90
92
  /** Owner-only rename input: an Agent's new display name. */
91
93
  export const agentRenameSchema = z.lazy(() => z.strictObject({
@@ -110,6 +112,7 @@ function asDirectoryAgent(row) {
110
112
  throw new DirectoryError(503, "partial_init");
111
113
  return Object.freeze({
112
114
  agentId: agent.value,
115
+ createdBy: parsed.data.created_by,
113
116
  name: parsed.data.name,
114
117
  createdAt: parsed.data.created_at,
115
118
  updatedAt: parsed.data.updated_at,
@@ -142,6 +145,8 @@ export function createAgentDirectory(sql) {
142
145
  const existing = readStored(agentId.value);
143
146
  if (existing === null)
144
147
  throw new DirectoryError(503, "partial_init");
148
+ if (existing.createdBy !== (command.createdBy ?? null))
149
+ throw new DirectoryError(409, "request_conflict");
145
150
  return existing;
146
151
  }
147
152
  return null;
@@ -165,7 +170,7 @@ export function createAgentDirectory(sql) {
165
170
  throw new DirectoryError(409, "name_taken");
166
171
  const agentId = agentIds.agent.generate();
167
172
  const now = Date.now();
168
- store.reserve(agentId, command.name, actorUserId, now, command.requestId, command.configuration);
173
+ store.reserve(agentId, command.name, actorUserId, now, command.requestId, command.configuration, command.createdBy ?? null);
169
174
  const created = readStored(agentId);
170
175
  if (created === null)
171
176
  throw new DirectoryError(503, "partial_init");
@@ -258,6 +263,7 @@ export const agentDelegationTaskSchema = z.lazy(() => z.strictObject({
258
263
  }));
259
264
  /** Exact host facts encoded over internal transports, without transcript content. */
260
265
  export const commandSettlementSchema = Schema.Struct({
266
+ acceptedSender: Schema.NullOr(Schema.String),
261
267
  terminal: SessionTerminalSchema,
262
268
  content: Schema.String,
263
269
  usage: Schema.Array(Schema.Struct({ provider: Schema.String, model: Schema.String, usage })),
@@ -287,7 +293,7 @@ export async function delegationProvenance(session, toolCallCode) {
287
293
  * stay on that reply's own line and never travel onward.
288
294
  */
289
295
  export function delegationSendMeta(sender, invocationMeta) {
290
- const author = authorMeta(sender);
296
+ const author = authorMeta({ ...sender, kind: "agent" });
291
297
  const inherited = invocationMeta === undefined
292
298
  ? undefined
293
299
  : FungiMessageMetaSchema.parse(invocationMeta).delegation;
@@ -307,7 +313,11 @@ export function delegationSendMeta(sender, invocationMeta) {
307
313
  */
308
314
  export function delegationReplyMeta(delegation, replierName) {
309
315
  return {
310
- ...authorMeta({ id: delegation.reply.agentId, name: replierName }),
316
+ ...authorMeta({
317
+ id: delegation.reply.agentId,
318
+ name: replierName,
319
+ kind: "agent",
320
+ }),
311
321
  delegation: {
312
322
  ancestors: [...delegation.ancestors],
313
323
  maxDepth: delegation.maxDepth,
@@ -325,11 +335,11 @@ const delegationRowSchema = z.lazy(() => z.strictObject({
325
335
  }));
326
336
  const decodeMail = Schema.decodeUnknownSync(Schema.fromJsonString(BirdDogMailSchema));
327
337
  /**
328
- * Hosted BirdDog submission and Pending observation. The durable row freezes
338
+ * BirdDog submission and reply authentication. The durable row freezes
329
339
  * correspondence before transport; an ambiguous delivery reuses that exact mail.
330
340
  * Shiitake owns work deduplication, execution and settlement; Post owns replies.
331
341
  */
332
- export function createFleetDelegation(options) {
342
+ export function createFleetCorrespondence(options) {
333
343
  const store = createFleetDelegationStore(options.storage);
334
344
  function read(requestId) {
335
345
  const row = store.read(requestId);
@@ -351,7 +361,8 @@ export function createFleetDelegation(options) {
351
361
  : undefined;
352
362
  const ancestors = inherited?.ancestors ?? [];
353
363
  const maxDepth = Math.min(grant.maxDepth ?? 8, inherited?.maxDepth ?? 8);
354
- if (ancestors.includes(task.agentId) || parent.agentId === task.agentId)
364
+ // Ancestry bounds work depth; it does not remove authorized peer targets.
365
+ if (parent.agentId === task.agentId)
355
366
  throw new AgentGrantError("delegation_loop");
356
367
  if (ancestors.length >= maxDepth)
357
368
  throw new AgentGrantError("delegation_depth");
@@ -407,22 +418,6 @@ export function createFleetDelegation(options) {
407
418
  store.recordReceipt(task.requestId, JSON.stringify(receipt));
408
419
  return receipt;
409
420
  }
410
- async function delegate(grant, task) {
411
- const accepted = await start(grant, task);
412
- const row = read(task.requestId);
413
- if (row === undefined)
414
- throw new DirectoryError(503, "partial_init");
415
- const mail = decodeMail(row.mail);
416
- const settlement = await options.readSettlement(mail.to.agentId, mail.to.sessionId, accepted.commandId);
417
- return settlement === undefined
418
- ? pending(1000)
419
- : {
420
- kind: "settled",
421
- agentId: mail.to.agentId,
422
- sessionId: mail.to.sessionId,
423
- ...settlement,
424
- };
425
- }
426
421
  /**
427
422
  * Authenticate one arriving reply against the frozen mail this owner sent.
428
423
  * It must be BirdDog's single reply key for that mail, mirror its sender and
@@ -464,11 +459,79 @@ export function createFleetDelegation(options) {
464
459
  reply,
465
460
  };
466
461
  }
462
+ /** Read only the existing acceptance receipt; do not reserve or deliver mail. */
463
+ function receipt(requestId) {
464
+ const encoded = read(requestId)?.receipt;
465
+ return encoded === undefined || encoded === null
466
+ ? undefined
467
+ : parseAccepted(encoded);
468
+ }
467
469
  return {
468
- /** Authenticate a reply against its frozen mail and restore the caller's path. */
469
- authenticateReply,
470
- /** Submit once and return the existing Shiitake admission receipt. */
470
+ /** Submit frozen mail or reuse its recorded Session admission receipt. */
471
471
  start,
472
+ /** Match a reply to frozen mail and restore the caller's delegation path. */
473
+ authenticateReply,
474
+ /** Read an existing acceptance receipt without reserving or delivering mail. */
475
+ receipt,
476
+ };
477
+ }
478
+ /** Local hosts may observe exact work directly; remote correspondence never pulls it. */
479
+ export function createFleetDelegation(options) {
480
+ const correspondence = createFleetCorrespondence(options);
481
+ const store = createFleetDelegationStore(options.storage);
482
+ async function delegate(grant, task) {
483
+ const accepted = await correspondence.start(grant, task);
484
+ const raw = store.read(task.requestId);
485
+ if (raw === undefined)
486
+ throw new DirectoryError(503, "partial_init");
487
+ const mail = decodeMail(delegationRowSchema.parse(raw).mail);
488
+ const settlement = await options.readSettlement(mail.to.agentId, mail.to.sessionId, accepted.commandId);
489
+ return settlement === undefined
490
+ ? pending(1000)
491
+ : {
492
+ kind: "settled",
493
+ agentId: mail.to.agentId,
494
+ sessionId: mail.to.sessionId,
495
+ ...settlement,
496
+ };
497
+ }
498
+ /**
499
+ * The newest delegation this owner sent whose work has not settled at its
500
+ * target, so the owner is still waiting for a reply; undefined when none.
501
+ */
502
+ async function outstanding() {
503
+ for (const raw of store.sent()) {
504
+ const row = delegationRowSchema.parse(raw);
505
+ if (row.receipt === null)
506
+ continue;
507
+ const { to } = decodeMail(row.mail);
508
+ const { commandId } = parseAccepted(row.receipt);
509
+ const target = {
510
+ agentId: to.agentId,
511
+ sessionId: to.sessionId,
512
+ commandId,
513
+ };
514
+ let settlement;
515
+ try {
516
+ settlement = await options.readSettlement(to.agentId, to.sessionId, commandId);
517
+ }
518
+ catch (cause) {
519
+ // Work whose state cannot be read is still owed; say why.
520
+ return {
521
+ ...target,
522
+ unreadable: cause instanceof Error ? cause.message : "unreadable",
523
+ };
524
+ }
525
+ if (settlement === undefined)
526
+ return target;
527
+ }
528
+ return undefined;
529
+ }
530
+ return {
531
+ /** The delegated work this owner still waits on, if any. */
532
+ outstanding,
533
+ /** Authenticate a reply against its frozen mail and restore the caller's path. */
534
+ ...correspondence,
472
535
  /** Submit or poll the exact turn, yielding Agar Pending until settled. */
473
536
  delegate,
474
537
  };
package/dist/hosted.js CHANGED
@@ -169,6 +169,7 @@ function hostedPort(manifest, revision, grants) {
169
169
  clear: () => Effect.fail(unavailable("Attention")),
170
170
  }),
171
171
  hasPendingSteer: () => Effect.fail(unavailable("Steering")),
172
+ sessionPosition: () => Effect.fail(unavailable("Session timeline")),
172
173
  hasSuccessfulToolResult: () => Effect.fail(unavailable("A tool result")),
173
174
  observe: () => Effect.die(unavailable("Observation")),
174
175
  transact: () => Effect.fail(new ShiitakeError("extensions", "rejected", "A hosted extension has no Agent transaction")),
@@ -42,7 +42,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
42
42
  };
43
43
  };
44
44
  output: {
45
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "storage_pressure" | "context_too_large";
45
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
46
46
  readonly _tag: "ShiitakeError";
47
47
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
48
48
  readonly message: string;
@@ -109,7 +109,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
109
109
  };
110
110
  };
111
111
  output: {
112
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "storage_pressure" | "context_too_large";
112
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
113
113
  readonly _tag: "ShiitakeError";
114
114
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
115
115
  readonly message: string;
@@ -150,7 +150,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
150
150
  $get: {
151
151
  input: {};
152
152
  output: {
153
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "storage_pressure" | "context_too_large";
153
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
154
154
  readonly _tag: "ShiitakeError";
155
155
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
156
156
  readonly message: string;
@@ -178,6 +178,34 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
178
178
  status: 200;
179
179
  };
180
180
  };
181
+ } & {
182
+ "/sessions/:sessionId/pending/latest": {
183
+ $get: {
184
+ input: {
185
+ param: {
186
+ sessionId: string;
187
+ };
188
+ };
189
+ output: {
190
+ readonly requestId: string;
191
+ readonly meta?: any;
192
+ readonly origin?: {
193
+ readonly extensionId: string;
194
+ readonly correlation: string | null;
195
+ } | undefined;
196
+ readonly commandId: string;
197
+ readonly text: string;
198
+ readonly images?: readonly {
199
+ readonly type: "image";
200
+ readonly data: string;
201
+ readonly mimeType: string;
202
+ }[] | undefined;
203
+ readonly delivery: import("./client.js").PromptDelivery;
204
+ } | null;
205
+ outputFormat: "json";
206
+ status: 200;
207
+ };
208
+ };
181
209
  } & {
182
210
  "/sessions/:sessionId/model-preference": {
183
211
  $get: {
@@ -210,7 +238,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
210
238
  };
211
239
  };
212
240
  output: {
213
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "storage_pressure" | "context_too_large";
241
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
214
242
  readonly _tag: "ShiitakeError";
215
243
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
216
244
  readonly message: string;
@@ -235,6 +263,47 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
235
263
  status: 204;
236
264
  };
237
265
  };
266
+ } & {
267
+ "/sessions/:sessionId/withdraw": {
268
+ $post: {
269
+ input: {
270
+ json: {
271
+ readonly requestId: string;
272
+ };
273
+ } & {
274
+ param: {
275
+ sessionId: string;
276
+ };
277
+ };
278
+ output: {
279
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
280
+ readonly _tag: "ShiitakeError";
281
+ readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
282
+ readonly message: string;
283
+ readonly requestId?: string | undefined;
284
+ };
285
+ outputFormat: "json";
286
+ status: 400;
287
+ } | {
288
+ input: {
289
+ json: {
290
+ readonly requestId: string;
291
+ };
292
+ } & {
293
+ param: {
294
+ sessionId: string;
295
+ };
296
+ };
297
+ output: {
298
+ readonly status: "accepted";
299
+ readonly commandId: string;
300
+ } | {
301
+ readonly status: "unknown";
302
+ };
303
+ outputFormat: "json";
304
+ status: 200;
305
+ };
306
+ };
238
307
  } & {
239
308
  "/sessions/:sessionId/command": {
240
309
  $post: {
@@ -282,7 +351,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
282
351
  };
283
352
  };
284
353
  output: {
285
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "storage_pressure" | "context_too_large";
354
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
286
355
  readonly _tag: "ShiitakeError";
287
356
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
288
357
  readonly message: string;
@@ -384,6 +453,8 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
384
453
  output: {
385
454
  readonly status: "accepted";
386
455
  readonly commandId: string;
456
+ } | {
457
+ readonly status: "unknown";
387
458
  } | {
388
459
  readonly status: "too_late";
389
460
  readonly commandId: string;
@@ -409,7 +480,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
409
480
  };
410
481
  };
411
482
  output: {
412
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "storage_pressure" | "context_too_large";
483
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
413
484
  readonly _tag: "ShiitakeError";
414
485
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
415
486
  readonly message: string;
@@ -435,7 +506,9 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
435
506
  readonly hasMore: boolean;
436
507
  readonly oldestCursor?: string | null | undefined;
437
508
  readonly messages: readonly {
509
+ readonly requestId?: string | undefined;
438
510
  readonly meta?: any;
511
+ readonly data?: any;
439
512
  readonly origin?: {
440
513
  readonly extensionId: string;
441
514
  readonly correlation: string | null;
@@ -499,6 +572,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
499
572
  };
500
573
  thoughtSignature?: string | undefined;
501
574
  namespace?: string | undefined;
575
+ argumentsError?: string | undefined;
502
576
  } | {
503
577
  type: "thinking";
504
578
  thinking: string;
@@ -555,20 +629,10 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
555
629
  rawStopReason?: string | undefined;
556
630
  endTurn?: boolean | undefined;
557
631
  timestamp: number;
632
+ durationMs?: number | undefined;
558
633
  } | {
559
- role: "toolResult";
560
634
  toolCallId: string;
561
- toolName: string;
562
- content: ({
563
- type: "text";
564
- text: string;
565
- textSignature?: string | undefined;
566
- } | {
567
- type: "image";
568
- data: string;
569
- mimeType: string;
570
- })[];
571
- details?: any;
635
+ role: "toolResult";
572
636
  usage?: {
573
637
  input: number;
574
638
  output: number;
@@ -585,6 +649,18 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
585
649
  total: number;
586
650
  };
587
651
  } | undefined;
652
+ content: ({
653
+ type: "text";
654
+ text: string;
655
+ textSignature?: string | undefined;
656
+ } | {
657
+ type: "image";
658
+ data: string;
659
+ mimeType: string;
660
+ })[];
661
+ timestamp: number;
662
+ durationMs?: number | undefined;
663
+ toolName: string;
588
664
  nestedCalls?: {
589
665
  calls: {
590
666
  id: string;
@@ -600,7 +676,6 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
600
676
  complete: boolean;
601
677
  } | undefined;
602
678
  isError: boolean;
603
- timestamp: number;
604
679
  };
605
680
  }[];
606
681
  readonly terminals: readonly {
@@ -623,7 +698,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
623
698
  readonly sequence: number;
624
699
  readonly at: number;
625
700
  readonly trigger: import("./client.js").SessionCompactionTrigger;
626
- readonly result: "nothing_to_compact" | "failed";
701
+ readonly result: "nothing_to_compact" | "failed" | "stale" | "insufficient";
627
702
  readonly diagnostic: string | null;
628
703
  }[] | undefined;
629
704
  readonly sandboxFailures?: readonly {
@@ -632,7 +707,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
632
707
  readonly executionId: string;
633
708
  readonly adapter: string;
634
709
  readonly buildSha: string | null;
635
- readonly tag: "Unknown" | "SourceInvalid" | "GuestThrew" | "DeadlineExceeded" | "Cancelled" | "DispatchLost" | "SandboxCrashed" | "BadResponse" | "ResultTooLarge" | "ResultNotJson" | "CapabilityFailed";
710
+ readonly tag: "Unknown" | "SourceInvalid" | "GuestThrew" | "DeadlineExceeded" | "SandboxBusy" | "Cancelled" | "DispatchLost" | "SandboxCrashed" | "BadResponse" | "ResultTooLarge" | "ResultNotJson" | "CapabilityFailed";
636
711
  readonly fields: {
637
712
  readonly [x: string]: any;
638
713
  };
@@ -641,6 +716,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
641
716
  readonly pending: {
642
717
  readonly total: number;
643
718
  readonly prompts: readonly {
719
+ readonly requestId: string;
644
720
  readonly meta?: any;
645
721
  readonly origin?: {
646
722
  readonly extensionId: string;
@@ -698,6 +774,13 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
698
774
  readonly textTruncated: boolean;
699
775
  readonly thinkingTruncated: boolean;
700
776
  } | null;
777
+ readonly toolOutput: readonly {
778
+ readonly toolCallId: string;
779
+ readonly revision: number;
780
+ readonly tail: string;
781
+ readonly droppedBytes: number;
782
+ readonly totalBytes: number;
783
+ }[];
701
784
  readonly extensions: {
702
785
  readonly [x: string]: {
703
786
  readonly status: "running";
@@ -717,6 +800,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
717
800
  readonly since: number;
718
801
  readonly extensionId: string;
719
802
  readonly expiresAt?: number | undefined;
803
+ readonly inbox?: false | undefined;
720
804
  }[];
721
805
  readonly context?: {
722
806
  readonly tokens: number;
@@ -759,7 +843,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
759
843
  };
760
844
  };
761
845
  output: {
762
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "storage_pressure" | "context_too_large";
846
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
763
847
  readonly _tag: "ShiitakeError";
764
848
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
765
849
  readonly message: string;
@@ -171,6 +171,10 @@ export function createShiitakeHttpRoutes(resolveSession, listSessions, createSes
171
171
  }
172
172
  const page = await listSessions(cursor);
173
173
  return context.json(page, 200);
174
+ })
175
+ .get("/sessions/:sessionId/pending/latest", async (context) => {
176
+ const session = await resolveSession(decodeClientSessionId(context.req.param("sessionId")), context.get("shiitakeMeta"));
177
+ return context.json(await session.latestPending(), 200);
174
178
  })
175
179
  .get("/sessions/:sessionId/model-preference", async (context) => {
176
180
  const session = await resolveSession(decodeClientSessionId(context.req.param("sessionId")), context.get("shiitakeMeta"));
@@ -187,6 +191,15 @@ export function createShiitakeHttpRoutes(resolveSession, listSessions, createSes
187
191
  const session = await resolveSession(decodeClientSessionId(context.req.param("sessionId")), context.get("shiitakeMeta"));
188
192
  await session.modelPreference.set(context.req.valid("json"));
189
193
  return context.body(null, 204);
194
+ })
195
+ .post("/sessions/:sessionId/withdraw", validator("json", (value, context) => {
196
+ const decoded = decodeClientSessionCreationRequest(value);
197
+ if (decoded._tag === "Failure")
198
+ return context.json(toShiitakeErrorWire(invalidRequest("Withdrawal request is invalid")), 400);
199
+ return decoded.success;
200
+ }), async (context) => {
201
+ const session = await resolveSession(decodeClientSessionId(context.req.param("sessionId")), context.get("shiitakeMeta"));
202
+ return context.json(await session.withdraw(context.req.valid("json").requestId), 200);
190
203
  })
191
204
  .post("/sessions/:sessionId/command", validator("json", (value, context) => {
192
205
  const decoded = decodeClientSessionCommandRequest(value);
@@ -238,5 +251,9 @@ export function createShiitakeHttpRoutes(resolveSession, listSessions, createSes
238
251
  includeSandboxFailures: query.includeSandboxFailures === "true",
239
252
  }));
240
253
  })
241
- .onError((error, context) => context.json(toShiitakeErrorWire(error), 503));
254
+ .onError((error, context) => context.json(toShiitakeErrorWire(error), error instanceof ShiitakeError &&
255
+ error.boundary === "runtime" &&
256
+ error.cause === "rejected"
257
+ ? 403
258
+ : 503));
242
259
  }
package/dist/index.d.ts CHANGED
@@ -5,9 +5,10 @@ import type { VerifiedAgentReply } from "./internal/woodstock/index.js";
5
5
  import type { BirdDogEffect } from "@fungi.computer/bird-dog/effect";
6
6
  import type { Accepted } from "./client.js";
7
7
  import type { ShiitakeOptions, ShiitakeOwnerAgent } from "./internal/contracts.js";
8
- export type { AbortCommand, AgentExtensionControls, BranchCommand, CompactCommand, CancelCommand, CancelResult, Accepted, ForkCommand, ForkedSession, PromptCommand, PromptDelivery, SendOptions, ReadOptions, SessionCommand, SessionCommandHandler, SessionExtensionControls, SessionExtensionSlots, SessionFrame, SessionMessage, SessionProgress, SessionContext, SessionSnapshot, SessionInboxState, SessionSummary, SessionSummaryPage, SessionSummaryRow, SessionActivity, AgentRunningWork, SessionTerminal, SessionCompactionMarker, SessionCompactionNotice, SessionCompactionTrigger, ShiitakeEventSourceConstructor, SessionWatch, ShiitakeAgent, ShiitakeSession, SubscribeOptions, TooLate, WatchOptions, } from "./client.js";
8
+ export type { AbortCommand, AgentExtensionControls, BranchCommand, CompactCommand, CancelCommand, CancelResult, Accepted, ForkCommand, ForkedSession, OperatorCommand, TitleCommand, PromptCommand, PromptDelivery, SendOptions, ReadOptions, SessionCommand, SessionCommandHandler, SessionExtensionControls, SessionExtensionSlots, SessionFrame, SessionMessage, SessionProgress, SessionToolOutput, SessionContext, SessionSnapshot, SessionInboxState, SessionSummary, SessionSummaryPage, SessionSummaryRow, SessionActivity, AgentRunningWork, SessionTerminal, SessionCompactionMarker, SessionCompactionNotice, SessionCompactionTrigger, ShiitakeEventSourceConstructor, SessionWatch, ShiitakeAgent, ShiitakeSession, SubscribeOptions, TooLate, UnknownRequest, WatchOptions, } from "./client.js";
9
+ export type { ColdSessionPage, ColdSessionPageRequest, ColdSessionRow, } from "./cold-sessions.js";
9
10
  export { parseForkedSession, parseSessionFrame, parseSessionSnapshot, } from "./client.js";
10
- export type { InstructionSnapshot, InstructionSource, ModelAcquisition, ModelDescription, ModelSource, RuntimeHost, RuntimePendingWork, SqliteCursor, SqliteOwner, SqliteValue, SqliteRow, StorageBudget, CompactionBudget, CompactionOptions, GrantsDelivery, GrantsDeliveryResult, ShiitakeOptions, ShiitakeOwnerAgent, ToolAccess, HostMetaOptions, StandardHostMetaResult, StandardHostMetaSchema, ToolSource, } from "./internal/contracts.js";
11
+ export type { InstructionSnapshot, InstructionSource, ModelAcquisition, ModelDescription, ModelSource, RuntimeHost, RuntimePendingWork, SqliteCursor, SqliteOwner, SqliteValue, SqliteRow, StoragePressureCheck, CompactionBudget, CompactionOptions, GrantsDelivery, GrantsDeliveryResult, ShiitakeOptions, ShiitakeOwnerAgent, ToolAccess, HostMetaOptions, StandardHostMetaResult, StandardHostMetaSchema, ToolSource, } from "./internal/contracts.js";
11
12
  export type { BorrowedCompactionViewV0, CompactionHandoffErrorV0, CompactionHandoffResultV0, CompactionHandoffV0, ExtensionCapabilityV0, ExtensionGrantV0, ExtensionRosterV0, ExtensionSlotV0, ExtensionStatusV0, RosteredExtensionV0, } from "./extensions.js";
12
13
  export { ShiitakeError, type ShiitakeErrorBoundary, type ShiitakeErrorCause, } from "./errors.js";
13
14
  /** Public construction namespace for the Promise-facing durable harness. */
@@ -18,6 +18,11 @@ export declare function executeAcceptedRun(options: ShiitakeEffectOptions, coreD
18
18
  readonly kind: "outcome_unknown";
19
19
  } | {
20
20
  kind: "failed" | "cancelled";
21
+ reason?: never;
21
22
  } | {
22
23
  kind: "completed";
24
+ reason?: never;
25
+ } | {
26
+ kind: "failed";
27
+ reason: string;
23
28
  }, never, never>;
@@ -7,10 +7,11 @@ import * as Schema from "effect/Schema";
7
7
  import { ShiitakeError } from "../errors.js";
8
8
  import { ExtensionId } from "../extension-contract.js";
9
9
  import { publishOwnerObservation } from "./observation-kernel.js";
10
+ import { returnedFailureReason } from "./run-failure-reason.js";
10
11
  import { coreProtocolDigest } from "./extension-roster.js";
11
12
  import { runAcceptedCommand, runAcceptedCompaction, ToolSetupError, } from "./run-scope.js";
12
13
  import { CompactionCommitError } from "./compaction/session-compaction.js";
13
- import { isCompactCommand, loadAcceptedRun, readExtensionAdmissionOrigin, recordWorkSettlement, } from "./woodstock/index.js";
14
+ import { BrokenSessionError, isCompactCommand, loadAcceptedRun, readExtensionAdmissionOrigin, recordWorkSettlement, } from "./woodstock/index.js";
14
15
  /** Bounded process-loss recovery for every accepted Agent turn. */
15
16
  export const agentTurnRecovery = Object.freeze({ maxRecoveries: 2 });
16
17
  function runtimeFailure(message) {
@@ -23,7 +24,9 @@ function runtimeFailure(message) {
23
24
  */
24
25
  function failedDecision(error) {
25
26
  const category = `${error.boundary}:${error.cause}`;
26
- const diagnostic = error instanceof ToolSetupError || error instanceof CompactionCommitError
27
+ const diagnostic = error instanceof ToolSetupError ||
28
+ error instanceof CompactionCommitError ||
29
+ error instanceof BrokenSessionError
27
30
  ? error.diagnostic
28
31
  : error.boundary === "model"
29
32
  ? error.message
@@ -114,7 +117,7 @@ export function executeAcceptedRun(options, coreDigest, job, extensions) {
114
117
  : yield* coreProtocolDigest(fileTools.map((tool) => tool.descriptor.name)),
115
118
  extensions: extensions?.runParticipants() ?? [],
116
119
  };
117
- const run = yield* runAcceptedCommand(options, prepared, executionContract, extensionModules, fileTools, extensionHandoff, extensions === undefined
120
+ const { run, failureReason } = yield* runAcceptedCommand(options, prepared, executionContract, extensionModules, fileTools, extensionHandoff, extensions === undefined
118
121
  ? undefined
119
122
  : () => extensions.promptSections(runIdentity, participants));
120
123
  switch (run.status) {
@@ -123,7 +126,10 @@ export function executeAcceptedRun(options, coreDigest, job, extensions) {
123
126
  case "cancelled":
124
127
  return { kind: "cancelled" };
125
128
  case "failed":
126
- return { kind: "failed" };
129
+ return {
130
+ kind: "failed",
131
+ reason: failureReason ?? returnedFailureReason(run),
132
+ };
127
133
  }
128
134
  }).pipe(Effect.catch((error) => logRunFailed(job.jobId, error).pipe(Effect.as(failedDecision(error)))));
129
135
  }
@@ -1,6 +1,7 @@
1
1
  import type { BirdDogMail } from "@fungi.computer/bird-dog";
2
2
  import * as Effect from "effect/Effect";
3
3
  import type { AcceptedWork } from "./client-boundary.js";
4
+ import type { WithdrawResult } from "../client.js";
4
5
  import type { Json } from "../extensions.js";
5
6
  import { type HostMetaOptions } from "./host-meta.js";
6
7
  import { ShiitakeError } from "../errors.js";
@@ -50,6 +51,8 @@ export type AdmitWithin = (request: AdmitRequest) => AdmitResult;
50
51
  export type AdmissionBoundary = "request" | "reconcile";
51
52
  /** The one accepted-command door. Nothing else writes accepted commands. */
52
53
  export type AdmissionDoor = Readonly<{
54
+ /** In the same owner transaction as admission, keep the receipt or fence the key. */
55
+ withdraw: (sessionId: string, requestId: string) => Effect.Effect<WithdrawResult, ShiitakeError>;
53
56
  /** Admit one prompt in its own transaction. */
54
57
  admit: (request: AdmitRequest, boundary?: AdmissionBoundary) => Effect.Effect<AdmitResult, ShiitakeError>;
55
58
  /**
@@ -2,9 +2,9 @@ import * as Effect from "effect/Effect";
2
2
  import * as Schema from "effect/Schema";
3
3
  import { checkHostMeta } from "./host-meta.js";
4
4
  import { ShiitakeError } from "../errors.js";
5
- import { decodeClientAcceptedCommand, decodeClientSessionId, requireDecoded, } from "./client-boundary.js";
5
+ import { decodeClientAcceptedCommand, decodeClientSessionId, decodeClientSessionCreationRequestId, requireDecoded, } from "./client-boundary.js";
6
6
  import { publishOwnerObservation } from "./observation-kernel.js";
7
- import { AdmissionOriginSchema, acceptSessionCommandOwned, readSessionCommandAdmissionByRequest, hasStoredSession, VerifiedAgentReplySchema, } from "./woodstock/index.js";
7
+ import { AdmissionOriginSchema, acceptSessionCommandOwned, readSessionCommandAdmissionByRequest, withdrawSessionRequestOwned, assertSessionRequestNotWithdrawn, hasStoredSession, VerifiedAgentReplySchema, } from "./woodstock/index.js";
8
8
  const decodeOrigin = Schema.decodeUnknownResult(AdmissionOriginSchema, {
9
9
  onExcessProperty: "error",
10
10
  });
@@ -42,6 +42,7 @@ export function openAdmissionDoor(input) {
42
42
  const admitDecoded = (request, touched) => {
43
43
  const { sessionId, origin } = request;
44
44
  const command = request.command;
45
+ assertSessionRequestNotWithdrawn(input.owner, sessionId, command.requestId);
45
46
  if (request.requireExistingSession &&
46
47
  !hasStoredSession(input.owner, sessionId))
47
48
  throw new ShiitakeError("contract", "closed", "Session target is unavailable");
@@ -95,6 +96,15 @@ export function openAdmissionDoor(input) {
95
96
  const transact = (body, boundary = "request") => run((admit) => body((request) => admit(decode(request))), boundary);
96
97
  return Object.freeze({
97
98
  transact,
99
+ withdraw: (sessionId, requestId) => Effect.try({
100
+ try: () => ({
101
+ sessionId: decodeClientSessionId(sessionId),
102
+ requestId: decodeClientSessionCreationRequestId({ requestId }),
103
+ }),
104
+ catch: (error) => error instanceof ShiitakeError
105
+ ? error
106
+ : new ShiitakeError("contract", "invalid", "Withdrawal request is invalid"),
107
+ }).pipe(Effect.flatMap((decoded) => run(() => withdrawSessionRequestOwned(input.owner, decoded.sessionId, decoded.requestId), "request"))),
98
108
  admit: (request, boundary) => Effect.try({
99
109
  try: () => decode(request),
100
110
  catch: (error) => error instanceof ShiitakeError
@@ -5,7 +5,13 @@ const encoder = new TextEncoder();
5
5
  function boundedText(maximumBytes) {
6
6
  return Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(maximumBytes), Schema.makeFilter((value) => encoder.encode(value).byteLength <= maximumBytes));
7
7
  }
8
- const boundedId = boundedText(256);
8
+ /** A lone UTF-16 surrogate; SQLite's UTF-8 text columns cannot store one. */
9
+ const loneSurrogate = /\p{Cs}/u;
10
+ /**
11
+ * Session and request ids are indexed SQLite text, so they must be well-formed
12
+ * Unicode: a lone surrogate would be stored as U+FFFD and never match again.
13
+ */
14
+ const boundedId = boundedText(256).check(Schema.makeFilter((value) => !loneSurrogate.test(value)));
9
15
  /**
10
16
  * The one prompt-text grammar. Human, mail, host and extension prompts are all
11
17
  * admitted through the accepted-command door under this bound. Text is