eve 0.70.0 → 0.70.2

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 (216) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/src/channel/adapter.js +1 -1
  3. package/dist/src/channel/channel-operations.js +1 -1
  4. package/dist/src/channel/resolve-text.d.ts +8 -0
  5. package/dist/src/channel/resolve-text.js +2 -1
  6. package/dist/src/cli/banner.d.ts +2 -2
  7. package/dist/src/cli/banner.js +1 -1
  8. package/dist/src/cli/commands/init-agent-workspace.js +1 -1
  9. package/dist/src/cli/commands/init.js +1 -1
  10. package/dist/src/cli/dev/tui/prompt-commands.d.ts +7 -7
  11. package/dist/src/cli/dev/tui/prompt-commands.js +1 -1
  12. package/dist/src/cli/dev/tui/runner.d.ts +4 -3
  13. package/dist/src/cli/dev/tui/runner.js +2 -2
  14. package/dist/src/cli/dev/tui/task-activity.d.ts +0 -17
  15. package/dist/src/cli/dev/tui/task-activity.js +1 -1
  16. package/dist/src/cli/dev/tui/task-records.d.ts +17 -0
  17. package/dist/src/cli/dev/tui/task-records.js +1 -0
  18. package/dist/src/cli/dev/tui/terminal-renderer.js +2 -2
  19. package/dist/src/cli/dev/tui/test/mock-terminal.d.ts +7 -1
  20. package/dist/src/cli/dev/tui/test/mock-terminal.js +4 -3
  21. package/dist/src/cli/dev/tui/theme.d.ts +1 -1
  22. package/dist/src/cli/dev/tui/theme.js +1 -1
  23. package/dist/src/cli/dev/tui/tool-presentation.d.ts +15 -2
  24. package/dist/src/cli/dev/tui/tool-presentation.js +1 -1
  25. package/dist/src/cli/dev/tui/transcript-parts.d.ts +10 -2
  26. package/dist/src/cli/dev/tui/transcript-parts.js +2 -2
  27. package/dist/src/cli/dev/tui/transcript.js +1 -1
  28. package/dist/src/compiled/.vendor-stamp.json +8 -8
  29. package/dist/src/compiled/@photon-ai/chat-adapter-imessage/index.js +1 -1
  30. package/dist/src/compiled/@vercel/sandbox/index.js +1 -1
  31. package/dist/src/compiled/@workflow/core/index.js +2 -2
  32. package/dist/src/compiled/@workflow/core/runtime/constants.d.ts +15 -0
  33. package/dist/src/compiled/@workflow/core/runtime/dynamic-workflow.d.ts +182 -0
  34. package/dist/src/compiled/@workflow/core/runtime/helpers.d.ts +7 -0
  35. package/dist/src/compiled/@workflow/core/runtime/helpers.js +1 -1
  36. package/dist/src/compiled/@workflow/core/runtime/quickjs-assets.generated.d.ts +7 -0
  37. package/dist/src/compiled/@workflow/core/runtime/quickjs-entrypoint.d.ts +2 -30
  38. package/dist/src/compiled/@workflow/core/runtime/quickjs-log-view.d.ts +27 -4
  39. package/dist/src/compiled/@workflow/core/runtime/quickjs-runtime.d.ts +55 -3
  40. package/dist/src/compiled/@workflow/core/runtime/quickjs-snapshot-codec.d.ts +74 -0
  41. package/dist/src/compiled/@workflow/core/runtime/start.d.ts +4 -0
  42. package/dist/src/compiled/@workflow/core/runtime/vm-mode.d.ts +44 -0
  43. package/dist/src/compiled/@workflow/core/runtime.d.ts +1 -1
  44. package/dist/src/compiled/@workflow/core/runtime.js +4 -4
  45. package/dist/src/compiled/@workflow/core/schemas.d.ts +1 -1
  46. package/dist/src/compiled/@workflow/core/serialization/compression.d.ts +27 -2
  47. package/dist/src/compiled/@workflow/core/serialization/hardened.d.ts +8 -0
  48. package/dist/src/compiled/@workflow/core/serialization/types.d.ts +1 -0
  49. package/dist/src/compiled/@workflow/core/serialization.d.ts +48 -0
  50. package/dist/src/compiled/@workflow/core/serialization.js +1 -1
  51. package/dist/src/compiled/@workflow/core/set-error-stack.d.ts +12 -0
  52. package/dist/src/compiled/@workflow/core/telemetry/semantic-conventions.d.ts +47 -0
  53. package/dist/src/compiled/@workflow/core/version.d.ts +1 -1
  54. package/dist/src/compiled/@workflow/core/workflow.js +1 -1
  55. package/dist/src/compiled/@workflow/world/events.d.ts +10 -0
  56. package/dist/src/compiled/@workflow/world/index.d.ts +2 -0
  57. package/dist/src/compiled/@workflow/world/interfaces.d.ts +127 -0
  58. package/dist/src/compiled/@workflow/world/queue.d.ts +6 -0
  59. package/dist/src/compiled/@workflow/world/runs.d.ts +9 -2
  60. package/dist/src/compiled/@workflow/world/snapshots.d.ts +60 -0
  61. package/dist/src/compiled/@workflow/world-local/index.js +5 -5
  62. package/dist/src/compiled/@workflow/world-vercel/index.js +19 -17
  63. package/dist/src/compiled/_chunks/node/undici-B61sz6UT.js +95 -0
  64. package/dist/src/compiled/_chunks/workflow/{attribute-changes-D721NpZv.js → attribute-changes-VaTKT4K8.js} +3 -3
  65. package/dist/src/compiled/_chunks/workflow/dist-B7NC1c9-.js +1 -0
  66. package/dist/src/compiled/_chunks/workflow/{functions-DpZiBxgQ.js → functions-CKkqf9V6.js} +1 -1
  67. package/dist/src/compiled/_chunks/workflow/helpers-CP9TjeO8.js +1 -0
  68. package/dist/src/compiled/_chunks/workflow/{logger-D4GJNTk4.js → logger-oqfblE3p.js} +2 -2
  69. package/dist/src/compiled/_chunks/workflow/resume-hook-yq-NAdBu.js +1 -0
  70. package/dist/src/compiled/_chunks/workflow/run-DYKeqbob.js +32 -0
  71. package/dist/src/compiled/_chunks/workflow/serialization-APSKphN5.js +3 -0
  72. package/dist/src/compiled/_chunks/workflow/signal-exit-iIdjQcCK.js +95 -0
  73. package/dist/src/compiled/_chunks/workflow/sleep-Ba8SExMt.js +1 -0
  74. package/dist/src/compiled/_chunks/workflow/{token-BhlnqoHM.js → token-BKeK6DmC.js} +1 -1
  75. package/dist/src/compiled/_chunks/workflow/token-Cv2GYj9A.js +1 -0
  76. package/dist/src/compiled/_chunks/workflow/{token-util-DaeRTzDa.js → token-util-CddTZ_ef.js} +1 -1
  77. package/dist/src/compiled/_chunks/workflow/{wait-until-3_1djUuL.js → wait-until-Dc9s3yyP.js} +1 -1
  78. package/dist/src/compiled/_chunks/workflow/{wrapper-CzrF_dmI.js → wrapper-Dq4OeGmJ.js} +4 -4
  79. package/dist/src/compiled/{gray-matter → js-yaml}/LICENSE +2 -2
  80. package/dist/src/compiled/js-yaml/index.d.ts +12 -0
  81. package/dist/src/compiled/js-yaml/index.js +34 -0
  82. package/dist/src/compiled/shadcn-registry/index.js +4 -4
  83. package/dist/src/context/serialize.d.ts +5 -1
  84. package/dist/src/context/serialize.js +1 -1
  85. package/dist/src/discover/discover-agent.js +1 -1
  86. package/dist/src/discover/extension-specifier.d.ts +1 -0
  87. package/dist/src/discover/extension-specifier.js +1 -1
  88. package/dist/src/discover/extensions.d.ts +1 -1
  89. package/dist/src/discover/extensions.js +1 -1
  90. package/dist/src/discover/project-source.d.ts +5 -0
  91. package/dist/src/discover/project-source.js +1 -1
  92. package/dist/src/evals/loaders/yaml.js +1 -1
  93. package/dist/src/evals/session.d.ts +1 -0
  94. package/dist/src/evals/session.js +1 -1
  95. package/dist/src/evals/types.d.ts +6 -0
  96. package/dist/src/execution/dynamic-workflow/tool.js +1 -1
  97. package/dist/src/execution/legacy-remote-agent/protocol.d.ts +8 -0
  98. package/dist/src/execution/legacy-remote-agent/protocol.js +1 -1
  99. package/dist/src/execution/legacy-session/turn-workflow.js +1 -1
  100. package/dist/src/execution/proxied-deliver-step.d.ts +11 -0
  101. package/dist/src/execution/proxied-deliver-step.js +1 -1
  102. package/dist/src/execution/sandbox/read-file.d.ts +8 -1
  103. package/dist/src/execution/sandbox/read-file.js +3 -3
  104. package/dist/src/execution/session/active-turn.d.ts +4 -1
  105. package/dist/src/execution/session/active-turn.js +1 -1
  106. package/dist/src/execution/session/checkpoint-migrations.d.ts +23 -0
  107. package/dist/src/execution/session/checkpoint-migrations.js +1 -0
  108. package/dist/src/execution/session/entry-input.d.ts +23 -5
  109. package/dist/src/execution/session/entry.js +1 -1
  110. package/dist/src/execution/session/finalization.js +1 -1
  111. package/dist/src/execution/session/handoff-steps.d.ts +24 -5
  112. package/dist/src/execution/session/handoff-steps.js +1 -1
  113. package/dist/src/execution/session/handoff.d.ts +40 -16
  114. package/dist/src/execution/session/handoff.js +1 -1
  115. package/dist/src/execution/session/pending-turn-state.d.ts +2 -0
  116. package/dist/src/execution/session/pending-turn-state.js +1 -1
  117. package/dist/src/execution/session/program.d.ts +16 -3
  118. package/dist/src/execution/session/program.js +1 -1
  119. package/dist/src/execution/session/turn-step-result.js +1 -1
  120. package/dist/src/execution/session/turn-step-types.d.ts +13 -1
  121. package/dist/src/execution/session/turn-step.js +1 -1
  122. package/dist/src/execution/session/turn.d.ts +14 -2
  123. package/dist/src/execution/session/turn.js +1 -1
  124. package/dist/src/execution/tools/agent-router-workflow.js +1 -1
  125. package/dist/src/execution/workflow-runtime.d.ts +4 -4
  126. package/dist/src/execution/workflow-runtime.js +1 -1
  127. package/dist/src/harness/attachment-staging.d.ts +27 -7
  128. package/dist/src/harness/attachment-staging.js +1 -1
  129. package/dist/src/harness/compaction-prompt.js +2 -2
  130. package/dist/src/harness/input-requests.js +1 -1
  131. package/dist/src/harness/messages.js +1 -1
  132. package/dist/src/harness/token-estimate.d.ts +5 -3
  133. package/dist/src/harness/token-estimate.js +1 -1
  134. package/dist/src/harness/tool-loop.js +2 -2
  135. package/dist/src/internal/application/package.js +1 -1
  136. package/dist/src/internal/attachments/media-metadata.d.ts +25 -0
  137. package/dist/src/internal/attachments/media-metadata.js +1 -0
  138. package/dist/src/internal/attachments/sandbox-refs.d.ts +12 -7
  139. package/dist/src/internal/attachments/sandbox-refs.js +1 -1
  140. package/dist/src/internal/authored-module-loader.js +1 -1
  141. package/dist/src/internal/bundler/extension-mount-plugin.d.ts +4 -0
  142. package/dist/src/internal/bundler/extension-mount-plugin.js +1 -1
  143. package/dist/src/internal/bundler/extension-scope-plugin.js +2 -2
  144. package/dist/src/internal/helpers/frontmatter.d.ts +24 -0
  145. package/dist/src/internal/helpers/frontmatter.js +1 -0
  146. package/dist/src/internal/helpers/markdown.js +1 -3
  147. package/dist/src/internal/input-text.d.ts +12 -0
  148. package/dist/src/internal/input-text.js +1 -0
  149. package/dist/src/internal/nitro/host/create-application-nitro.js +1 -1
  150. package/dist/src/internal/nitro/host/nitro-bundler-config.js +1 -1
  151. package/dist/src/internal/node-esm-compat-banner.js +1 -1
  152. package/dist/src/internal/workflow/vercel-world-defaults.d.ts +1 -1
  153. package/dist/src/internal/workflow/vercel-world-defaults.js +1 -1
  154. package/dist/src/public/channels/chat-sdk/input-actions.d.ts +8 -2
  155. package/dist/src/public/channels/chat-sdk/input-actions.js +4 -1
  156. package/dist/src/public/channels/github/defaults.js +2 -3
  157. package/dist/src/public/channels/linear/hitl.js +2 -3
  158. package/dist/src/public/channels/slack/action-status.d.ts +10 -5
  159. package/dist/src/public/channels/slack/action-status.js +1 -1
  160. package/dist/src/public/channels/slack/defaults.js +3 -3
  161. package/dist/src/public/channels/slack/limits.d.ts +1 -1
  162. package/dist/src/public/channels/slack/limits.js +1 -1
  163. package/dist/src/public/channels/slack/slackChannel.d.ts +11 -9
  164. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  165. package/dist/src/public/channels/slack/task-card.js +1 -1
  166. package/dist/src/public/channels/slack/thread-status.d.ts +33 -0
  167. package/dist/src/public/channels/slack/thread-status.js +1 -0
  168. package/dist/src/public/channels/twilio/defaults.js +4 -2
  169. package/dist/src/public/definitions/state.d.ts +2 -0
  170. package/dist/src/public/definitions/state.js +1 -1
  171. package/dist/src/runtime/connections/openapi-spec.d.ts +1 -4
  172. package/dist/src/runtime/connections/openapi-spec.js +1 -1
  173. package/dist/src/setup/scaffold/create/project.js +1 -1
  174. package/dist/src/setup/scaffold/create/web-template.d.ts +4 -4
  175. package/dist/src/setup/scaffold/create/web-template.js +33 -26
  176. package/dist/src/subagents/callback-route.js +1 -1
  177. package/dist/src/subagents/hitl-proxy.d.ts +2 -2
  178. package/dist/src/subagents/hitl-proxy.js +1 -1
  179. package/dist/src/subagents/parent-notification.d.ts +7 -7
  180. package/dist/src/subagents/parent-notification.js +1 -1
  181. package/dist/src/tools/provided/ask-question-workflow.js +1 -1
  182. package/dist/src/tools/provided/read-file.d.ts +7 -0
  183. package/dist/src/tools/provided/read-file.js +2 -2
  184. package/dist/src/tools/provided/sleep-workflow.js +1 -1
  185. package/docs/agent-config.md +2 -2
  186. package/docs/channels/chat-sdk.mdx +1 -1
  187. package/docs/channels/slack.mdx +21 -15
  188. package/docs/channels/twilio.mdx +1 -1
  189. package/docs/concepts/built-in-tools.md +1 -1
  190. package/docs/concepts/default-harness.md +4 -0
  191. package/docs/concepts/execution-model-and-durability.mdx +9 -1
  192. package/docs/concepts/sessions-runs-and-streaming.md +3 -3
  193. package/docs/evals/cases.mdx +1 -0
  194. package/docs/extensions.md +6 -3
  195. package/docs/guides/dev-tui.md +5 -3
  196. package/docs/guides/dynamic-capabilities.md +1 -1
  197. package/docs/sandbox/index.mdx +11 -0
  198. package/docs/tools/overview.mdx +4 -2
  199. package/package.json +8 -8
  200. package/dist/src/compiled/_chunks/node/undici-h5GH4RBM.js +0 -95
  201. package/dist/src/compiled/_chunks/workflow/debug-log-CVPstO1D.js +0 -1
  202. package/dist/src/compiled/_chunks/workflow/dist-BUFGAKRb.js +0 -1
  203. package/dist/src/compiled/_chunks/workflow/frames-Dr9fHWdo.js +0 -3
  204. package/dist/src/compiled/_chunks/workflow/helpers-DkKxg_yn.js +0 -1
  205. package/dist/src/compiled/_chunks/workflow/resume-hook-uTW-jBYz.js +0 -1
  206. package/dist/src/compiled/_chunks/workflow/run-5DmiMN9b.js +0 -1
  207. package/dist/src/compiled/_chunks/workflow/serialization-CNIYl1D3.js +0 -3
  208. package/dist/src/compiled/_chunks/workflow/signal-exit-DZKTacNU.js +0 -95
  209. package/dist/src/compiled/_chunks/workflow/sleep-DFKgN7UG.js +0 -1
  210. package/dist/src/compiled/_chunks/workflow/token-1wSBbi5N.js +0 -1
  211. package/dist/src/compiled/_chunks/workflow/token-util-Cm4Ec1Bg.js +0 -1
  212. package/dist/src/compiled/_chunks/workflow/ws-transport--rYOVIaf.js +0 -1
  213. package/dist/src/compiled/gray-matter/index.d.ts +0 -62
  214. package/dist/src/compiled/gray-matter/index.js +0 -48
  215. package/dist/src/internal/helpers/gray-matter.d.ts +0 -25
  216. package/dist/src/internal/helpers/gray-matter.js +0 -1
@@ -443,31 +443,36 @@ function AgentMessagePart({
443
443
  }
444
444
 
445
445
  return (
446
- <Tool
447
- defaultOpen={part.state === "approval-requested" || part.state === "approval-responded"}
448
- >
449
- <ToolHeader
450
- state={part.state}
451
- title={part.toolName}
452
- toolName={part.toolName}
453
- type="dynamic-tool"
454
- />
455
- <ToolContent>
456
- {part.toolName === "bash" ? (
457
- <BashToolContent errorText={part.errorText} input={part.input} output={part.output} />
458
- ) : (
459
- <ToolInput input={part.input} />
460
- )}
461
- <InputRequestActions
462
- canRespond={canRespond}
463
- part={part}
464
- onInputResponses={onInputResponses}
446
+ <>
447
+ <Tool>
448
+ <ToolHeader
449
+ state={part.state}
450
+ title={part.toolName}
451
+ toolName={part.toolName}
452
+ type="dynamic-tool"
465
453
  />
466
- {part.toolName === "bash" ? null : (
467
- <ToolOutput errorText={part.errorText} output={part.output} />
468
- )}
469
- </ToolContent>
470
- </Tool>
454
+ <ToolContent>
455
+ {part.toolName === "bash" ? (
456
+ <BashToolContent
457
+ errorText={part.errorText}
458
+ input={part.input}
459
+ output={part.output}
460
+ />
461
+ ) : (
462
+ <>
463
+ <ToolInput input={part.input} />
464
+ <ToolOutput errorText={part.errorText} output={part.output} />
465
+ </>
466
+ )}
467
+ </ToolContent>
468
+ </Tool>
469
+ {/* Kept outside the collapsed card so a pending approval is always visible. */}
470
+ <InputRequestActions
471
+ canRespond={canRespond}
472
+ part={part}
473
+ onInputResponses={onInputResponses}
474
+ />
475
+ </>
471
476
  );
472
477
  }
473
478
  }
@@ -2023,6 +2028,8 @@ export const MessageContent = ({ children, className, ...props }: MessageContent
2023
2028
  className={cn(
2024
2029
  "is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
2025
2030
  "group-[.is-user]:ml-auto group-[.is-user]:rounded-2xl group-[.is-user]:bg-primary group-[.is-user]:px-4 group-[.is-user]:py-2.5 group-[.is-user]:text-primary-foreground",
2031
+ // Streamdown links default to text-primary, which matches the user bubble background.
2032
+ "group-[.is-user]:[&_[data-streamdown=link]]:text-current",
2026
2033
  "group-[.is-assistant]:w-full group-[.is-assistant]:text-foreground",
2027
2034
  "group-data-[optimistic=true]:opacity-70",
2028
2035
  className,
@@ -3278,7 +3285,7 @@ export const PromptInputTextarea = ({
3278
3285
 
3279
3286
  return (
3280
3287
  <InputGroupTextarea
3281
- className={cn("field-sizing-content max-h-48 min-h-18 text-sm!", className)}
3288
+ className={cn("field-sizing-content max-h-48 min-h-18 text-base md:text-sm", className)}
3282
3289
  name="message"
3283
3290
  onCompositionEnd={handleCompositionEnd}
3284
3291
  onCompositionStart={handleCompositionStart}
@@ -3882,7 +3889,7 @@ export const QuestionInput = ({
3882
3889
  return (
3883
3890
  <Textarea
3884
3891
  className={cn(
3885
- "min-h-16 resize-none rounded-lg text-sm shadow-none focus-visible:border-foreground!",
3892
+ "min-h-16 resize-none rounded-lg text-base md:text-sm shadow-none focus-visible:border-foreground!",
3886
3893
  className,
3887
3894
  )}
3888
3895
  disabled={question.disabled || disabled}
@@ -1 +1 @@
1
- import{z}from"#compiled/zod/index.js";import{inputRequestSchema}from"#shared/input.js";import{jsonValueSchema}from"#shared/json-schemas.js";import{resumeHook}from"#internal/workflow/runtime.js";import{agentTurnOutcomeWithCostSchema}from"#shared/agent-turn-outcome.js";const sessionInputCallbackSchema=z.object({callId:z.string().min(1),childContinuationToken:z.string().min(1),childSessionId:z.string().min(1),childSessionInbox:z.object({sessionId:z.string().min(1)}).strict().optional(),inputSource:z.string().min(1).optional(),remote:z.object({name:z.string().min(1),url:z.string().url(),forwardPrincipal:z.boolean().optional(),sessionId:z.string().min(1),resolverId:z.string().min(1).optional()}).strict().optional(),kind:z.literal(`subagent-input-request`),subagentName:z.string().min(1),event:z.object({requests:z.array(inputRequestSchema).min(1),sequence:z.number(),stepIndex:z.number(),taskId:z.string().optional(),turnId:z.string()})}),sessionAuthorizationCallbackSchema=z.object({callId:z.string().min(1),childSessionId:z.string().min(1),kind:z.literal(`subagent-authorization-event`),subagentName:z.string().min(1),event:z.discriminatedUnion(`type`,[z.object({type:z.literal(`approval.candidate`),data:z.object({candidateId:z.string(),outcome:z.enum([`pending`,`rejected`,`failed`,`timed-out`,`stale`]),requestId:z.string(),responderPrincipalId:z.string(),reason:z.string().optional(),sequence:z.number(),stepIndex:z.number(),turnId:z.string()}).passthrough()}),z.object({type:z.literal(`approval.settled`),data:z.object({outcome:z.enum([`approved`,`cancelled`]),requestId:z.string(),responderPrincipalId:z.string(),sequence:z.number(),stepIndex:z.number(),turnId:z.string()}).passthrough()}),z.object({type:z.literal(`authorization.required`),data:z.object({description:z.string(),name:z.string(),sequence:z.number(),stepIndex:z.number(),turnId:z.string(),webhookUrl:z.string().optional(),attemptId:z.string().optional(),taskId:z.string().optional(),principalId:z.string().optional()}).passthrough()}),z.object({type:z.literal(`authorization.completed`),data:z.object({name:z.string(),outcome:z.enum([`authorized`,`declined`,`failed`,`timed-out`]),sequence:z.number(),stepIndex:z.number(),turnId:z.string(),attemptId:z.string().optional(),taskId:z.string().optional(),principalId:z.string().optional()}).passthrough()})])}),sessionResultCallbackSchema=z.discriminatedUnion(`kind`,[z.object({callId:z.string().min(1),kind:z.literal(`turn.completed`),outcome:agentTurnOutcomeWithCostSchema,output:jsonValueSchema.optional(),subagentName:z.string().min(1)}),z.object({callId:z.string().min(1),error:jsonValueSchema,kind:z.literal(`turn.failed`),outcome:agentTurnOutcomeWithCostSchema,subagentName:z.string().min(1)})]);async function handleSessionCallbackRequest(e,t){let n=t.params.token;if(typeof n!=`string`||n.length===0)return Response.json({error:`Missing callback token.`,ok:!1},{status:400});let r;try{r=await e.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}let i=callbackKind(r),a=i===`subagent-input-request`?sessionInputCallbackSchema.safeParse(r):i===`subagent-authorization-event`?sessionAuthorizationCallbackSchema.safeParse(r):void 0;if(a!==void 0&&!a.success)return Response.json({error:`Invalid session input callback.`,ok:!1},{status:400});let o=a===void 0?projectSessionCallbackResult(r):void 0;if(o instanceof Response)return o;try{await resumeHook(n,a?.success?a.data:{kind:`runtime-action-result`,results:[o]})}catch{return Response.json({error:`Session callback not pending.`,ok:!1},{status:404})}return Response.json({ok:!0},{status:202})}function callbackKind(e){if(typeof e==`object`&&e)return Reflect.get(e,`kind`)}function projectSessionCallbackResult(e){if(typeof e!=`object`||!e)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let t=callbackKind(e);if(t!==`turn.completed`&&t!==`turn.failed`)return Response.json({error:`Unsupported callback kind.`,ok:!1},{status:400});let n=sessionResultCallbackSchema.safeParse(e);if(!n.success)return Response.json({error:`Invalid session result callback.`,ok:!1},{status:400});let r=n.data;return r.kind===`turn.completed`?{callId:r.callId,kind:`subagent-result`,origin:`child`,outcome:r.outcome,output:r.output??``,subagentName:r.subagentName,usage:r.outcome.usageDelta}:{callId:r.callId,isError:!0,kind:`subagent-result`,origin:`child`,outcome:r.outcome,output:r.error,subagentName:r.subagentName}}export{handleSessionCallbackRequest};
1
+ import{z}from"#compiled/zod/index.js";import{inputRequestSchema}from"#shared/input.js";import{jsonValueSchema}from"#shared/json-schemas.js";import{renameLegacyTaskCallback}from"#execution/legacy-remote-agent/protocol.js";import{resumeHook}from"#internal/workflow/runtime.js";import{agentTurnOutcomeWithCostSchema}from"#shared/agent-turn-outcome.js";const sessionInputCallbackSchema=z.object({callId:z.string().min(1),childContinuationToken:z.string().min(1),childSessionId:z.string().min(1),childSessionInbox:z.object({sessionId:z.string().min(1)}).strict().optional(),inputSource:z.string().min(1).optional(),remote:z.object({name:z.string().min(1),url:z.string().url(),forwardPrincipal:z.boolean().optional(),sessionId:z.string().min(1),resolverId:z.string().min(1).optional()}).strict().optional(),kind:z.literal(`subagent-input-request`),subagentName:z.string().min(1),event:z.object({requests:z.array(inputRequestSchema).min(1),sequence:z.number(),stepIndex:z.number(),taskId:z.string().optional(),turnId:z.string()})}),sessionAuthorizationCallbackSchema=z.object({callId:z.string().min(1),childSessionId:z.string().min(1),kind:z.literal(`subagent-authorization-event`),subagentName:z.string().min(1),event:z.discriminatedUnion(`type`,[z.object({type:z.literal(`approval.candidate`),data:z.object({candidateId:z.string(),outcome:z.enum([`pending`,`rejected`,`failed`,`timed-out`,`stale`]),requestId:z.string(),responderPrincipalId:z.string(),reason:z.string().optional(),sequence:z.number(),stepIndex:z.number(),turnId:z.string()}).passthrough()}),z.object({type:z.literal(`approval.settled`),data:z.object({outcome:z.enum([`approved`,`cancelled`]),requestId:z.string(),responderPrincipalId:z.string(),sequence:z.number(),stepIndex:z.number(),turnId:z.string()}).passthrough()}),z.object({type:z.literal(`authorization.required`),data:z.object({description:z.string(),name:z.string(),sequence:z.number(),stepIndex:z.number(),turnId:z.string(),webhookUrl:z.string().optional(),attemptId:z.string().optional(),taskId:z.string().optional(),principalId:z.string().optional()}).passthrough()}),z.object({type:z.literal(`authorization.completed`),data:z.object({name:z.string(),outcome:z.enum([`authorized`,`declined`,`failed`,`timed-out`]),sequence:z.number(),stepIndex:z.number(),turnId:z.string(),attemptId:z.string().optional(),taskId:z.string().optional(),principalId:z.string().optional()}).passthrough()})])}),sessionResultCallbackSchema=z.discriminatedUnion(`kind`,[z.object({callId:z.string().min(1),kind:z.literal(`turn.completed`),outcome:agentTurnOutcomeWithCostSchema,output:jsonValueSchema.optional(),subagentName:z.string().min(1)}),z.object({callId:z.string().min(1),error:jsonValueSchema,kind:z.literal(`turn.failed`),outcome:agentTurnOutcomeWithCostSchema,subagentName:z.string().min(1)})]);async function handleSessionCallbackRequest(e,t){let n=t.params.token;if(typeof n!=`string`||n.length===0)return Response.json({error:`Missing callback token.`,ok:!1},{status:400});let r;try{r=renameLegacyTaskCallback(await e.json())}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}let i=callbackKind(r),a=i===`subagent-input-request`?sessionInputCallbackSchema.safeParse(r):i===`subagent-authorization-event`?sessionAuthorizationCallbackSchema.safeParse(r):void 0;if(a!==void 0&&!a.success)return Response.json({error:`Invalid session input callback.`,ok:!1},{status:400});let o=a===void 0?projectSessionCallbackResult(r):void 0;if(o instanceof Response)return o;try{await resumeHook(n,a?.success?a.data:{kind:`runtime-action-result`,results:[o]})}catch{return Response.json({error:`Session callback not pending.`,ok:!1},{status:404})}return Response.json({ok:!0},{status:202})}function callbackKind(e){if(typeof e==`object`&&e)return Reflect.get(e,`kind`)}function projectSessionCallbackResult(e){if(typeof e!=`object`||!e)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let t=callbackKind(e);if(t!==`turn.completed`&&t!==`turn.failed`)return Response.json({error:`Unsupported callback kind.`,ok:!1},{status:400});let n=sessionResultCallbackSchema.safeParse(e);if(!n.success)return Response.json({error:`Invalid session result callback.`,ok:!1},{status:400});let r=n.data;return r.kind===`turn.completed`?{callId:r.callId,kind:`subagent-result`,origin:`child`,outcome:r.outcome,output:r.output??``,subagentName:r.subagentName,usage:r.outcome.usageDelta}:{callId:r.callId,isError:!0,kind:`subagent-result`,origin:`child`,outcome:r.outcome,output:r.error,subagentName:r.subagentName}}export{handleSessionCallbackRequest};
@@ -67,8 +67,8 @@ export interface RoutedDeliverPayload {
67
67
  *
68
68
  * With `resolveMessage`, a plain-text message is also resolved against pending
69
69
  * `ctx.ask()` questions: when exactly one question is pending, a matching option or
70
- * permitted free text answers it and consumes the message. Otherwise the
71
- * message stays with the parent.
70
+ * permitted free text answers it and consumes the message along with its
71
+ * `context`. Otherwise the message stays with the parent.
72
72
  */
73
73
  export declare function routeDeliverPayload(input: {
74
74
  readonly allowRoute?: (requestId: string, route: ProxyInputRequest) => boolean;
@@ -1 +1 @@
1
- import{createInputRequestedEvent,createTurnWaitingEvent}from"#protocol/message.js";import{resolveTextToResponse}from"#channel/resolve-text.js";import{getHarnessEmissionState}from"#harness/emission.js";import{getSessionUsage}from"#harness/turn-tag-state.js";import{getProxyInputRequests,toProxyInputRequestEntries}from"#harness/proxy-input-requests.js";import{resolveInputOutcome}from"#harness/input-request-resolution.js";import{SESSION_LIMIT_STOP_OPTION_ID}from"#harness/session-limit-continuation.js";async function emitProxiedInputRequest(e){return await e.emit(createInputRequestedEvent({requests:e.hookPayload.event.requests,sequence:e.hookPayload.event.sequence,stepIndex:e.hookPayload.event.stepIndex,taskId:e.hookPayload.event.taskId,turnId:e.hookPayload.event.turnId})),await emitTurnWaiting(e.emit,e.session),toProxyInputRequestEntries(e.hookPayload)}async function emitProxiedAuthorizationEvent(e){await e.emit(e.hookPayload.event),e.hookPayload.event.type===`authorization.required`&&await emitTurnWaiting(e.emit,e.session)}async function emitTurnWaiting(e,t){let n=getHarnessEmissionState(t.state);await e(createTurnWaitingEvent({on:`input`,sequence:n.sequence,turnId:n.turnId,usage:getSessionUsage(t)}))}function routeDeliverPayload(e){let t=getProxyInputRequests(e.state),routable=(t,n)=>n!==void 0&&e.allowRoute?.(t,n)!==!1,n=resolveMessageAgainstQuestions({enabled:e.resolveMessage===!0,entries:t,payload:e.payload,routable}),r=[...e.payload.inputResponses??[],...n.responses],i=new Map,a=[],o,bucketFor=e=>{let t=JSON.stringify([e.childContinuationToken,e.childSessionInbox?.sessionId??``,e.remote?.sessionId??``,e.event.sequence,e.event.stepIndex,e.event.turnId,e.inputSource??null]),n=i.get(t);if(n!==void 0)return n;let r={childContinuationToken:e.childContinuationToken,remote:e.remote,event:e.event,parentRequestIds:[],responses:[],routes:[],...e.childSessionInbox!==void 0&&{childSessionInbox:e.childSessionInbox},...e.workflowAsk!==void 0&&{workflowAsk:e.workflowAsk}};return i.set(t,r),r},s=new Set;for(let e of r){let n=t.get(e.requestId);if(n===void 0||!routable(e.requestId,n)){a.push(e);continue}if(s.has(e.requestId))continue;s.add(e.requestId),n.kind===`session-limit`&&e.optionId===SESSION_LIMIT_STOP_OPTION_ID&&(o={kind:`cancel-turn`});let r=bucketFor(n);r.parentRequestIds.push(e.requestId),r.responses.push(e),r.routes.push(n)}let c=[...i.values()].map(({workflowAsk:e,remote:n,childContinuationToken:r,childSessionInbox:i,event:a,parentRequestIds:o,responses:s,routes:c})=>{let l=new Set(o),u=new Set(l);for(let e of c)if(e.batch!==void 0&&batchResolves({batch:e.batch,childContinuationToken:r,entries:t,responseIds:l}))for(let t of e.batch.requestIds)u.add(t);return{childContinuationToken:r,payload:{inputResponses:s},resolved:{event:a,resolutions:resolveRetiredRequests({entries:t,responses:s,retireRequestIds:u})},...i!==void 0&&{childSessionInbox:i},...e!==void 0&&{workflowAsk:e},...n!==void 0&&{remote:n},...c[0]?.inputSource!==void 0&&{inputSource:c[0].inputSource}}}),l={};for(let[t,r]of Object.entries(e.payload))t!==`inputResponses`&&r!==void 0&&(t===`message`&&n.consumed||(l[t]=r));return a.length>0&&(l.inputResponses=a),{forChildren:c,forSelf:Object.keys(l).length>0?l:void 0,parentAction:o}}function resolveRetiredRequests(e){let t=new Map(e.responses.map(e=>[e.requestId,e])),n=[];for(let r of e.retireRequestIds){let i=e.entries.get(r);i!==void 0&&n.push(toInputResolution(r,i,t.get(r)))}return n}function toInputResolution(e,t,n){let r=resolveInputOutcome(t.kind,n),i={kind:t.kind,outcome:r,requestId:e};return n===void 0?i:{...i,response:n}}function resolveMessageAgainstQuestions(e){let t={consumed:!1,responses:[]};if(!e.enabled||(e.payload.inputResponses?.length??0)>0||e.payload.message===void 0)return t;let n=[...e.entries].filter(([t,n])=>n.kind===`question`&&e.routable(t,n)),r=n.flatMap(([e,t])=>{let n=t.workflowAsk?.question??t.question;return n===void 0?[]:[{requestId:e,...n}]});if(r.length===0)return t;let[i]=r,a=n.length===1&&i!==void 0&&typeof e.payload.message==`string`?resolveTextToResponse(e.payload.message,i):void 0;return a===void 0?t:{consumed:!0,responses:[a]}}function batchResolves(e){return e.batch.approvalRequestIds.length===0||e.batch.approvalRequestIds.every(t=>{let n=e.entries.get(t);return n===void 0||!sameBatch(n,e)||e.responseIds.has(t)})}function sameBatch(e,t){return e.childContinuationToken===t.childContinuationToken&&e.batch?.requestIds.length===t.batch.requestIds.length&&e.batch.requestIds.every((e,n)=>e===t.batch.requestIds[n])}export{emitProxiedAuthorizationEvent,emitProxiedInputRequest,routeDeliverPayload};
1
+ import{inputTextKey,readAnswerText}from"#internal/input-text.js";import{createInputRequestedEvent,createTurnWaitingEvent}from"#protocol/message.js";import{resolveTextToResponse}from"#channel/resolve-text.js";import{getHarnessEmissionState}from"#harness/emission.js";import{getSessionUsage}from"#harness/turn-tag-state.js";import{getProxyInputRequests,toProxyInputRequestEntries}from"#harness/proxy-input-requests.js";import{resolveInputOutcome}from"#harness/input-request-resolution.js";import{SESSION_LIMIT_STOP_OPTION_ID}from"#harness/session-limit-continuation.js";async function emitProxiedInputRequest(e){return await e.emit(createInputRequestedEvent({requests:e.hookPayload.event.requests,sequence:e.hookPayload.event.sequence,stepIndex:e.hookPayload.event.stepIndex,taskId:e.hookPayload.event.taskId,turnId:e.hookPayload.event.turnId})),await emitTurnWaiting(e.emit,e.session),toProxyInputRequestEntries(e.hookPayload)}async function emitProxiedAuthorizationEvent(e){await e.emit(e.hookPayload.event),e.hookPayload.event.type===`authorization.required`&&await emitTurnWaiting(e.emit,e.session)}async function emitTurnWaiting(e,t){let n=getHarnessEmissionState(t.state);await e(createTurnWaitingEvent({on:`input`,sequence:n.sequence,turnId:n.turnId,usage:getSessionUsage(t)}))}const CONSUMED_MESSAGE_KEYS=new Set([`context`,`message`,inputTextKey]);function routeDeliverPayload(e){let t=getProxyInputRequests(e.state),routable=(t,n)=>n!==void 0&&e.allowRoute?.(t,n)!==!1,n=resolveMessageAgainstQuestions({enabled:e.resolveMessage===!0,entries:t,payload:e.payload,routable}),r=[...e.payload.inputResponses??[],...n.responses],i=new Map,a=[],o,bucketFor=e=>{let t=JSON.stringify([e.childContinuationToken,e.childSessionInbox?.sessionId??``,e.remote?.sessionId??``,e.event.sequence,e.event.stepIndex,e.event.turnId,e.inputSource??null]),n=i.get(t);if(n!==void 0)return n;let r={childContinuationToken:e.childContinuationToken,remote:e.remote,event:e.event,parentRequestIds:[],responses:[],routes:[],...e.childSessionInbox!==void 0&&{childSessionInbox:e.childSessionInbox},...e.workflowAsk!==void 0&&{workflowAsk:e.workflowAsk}};return i.set(t,r),r},s=new Set;for(let e of r){let n=t.get(e.requestId);if(n===void 0||!routable(e.requestId,n)){a.push(e);continue}if(s.has(e.requestId))continue;s.add(e.requestId),n.kind===`session-limit`&&e.optionId===SESSION_LIMIT_STOP_OPTION_ID&&(o={kind:`cancel-turn`});let r=bucketFor(n);r.parentRequestIds.push(e.requestId),r.responses.push(e),r.routes.push(n)}let c=[...i.values()].map(({workflowAsk:e,remote:n,childContinuationToken:r,childSessionInbox:i,event:a,parentRequestIds:o,responses:s,routes:c})=>{let l=new Set(o),u=new Set(l);for(let e of c)if(e.batch!==void 0&&batchResolves({batch:e.batch,childContinuationToken:r,entries:t,responseIds:l}))for(let t of e.batch.requestIds)u.add(t);return{childContinuationToken:r,payload:{inputResponses:s},resolved:{event:a,resolutions:resolveRetiredRequests({entries:t,responses:s,retireRequestIds:u})},...i!==void 0&&{childSessionInbox:i},...e!==void 0&&{workflowAsk:e},...n!==void 0&&{remote:n},...c[0]?.inputSource!==void 0&&{inputSource:c[0].inputSource}}}),l={};for(let[t,r]of Object.entries(e.payload))t!==`inputResponses`&&r!==void 0&&(n.consumed&&CONSUMED_MESSAGE_KEYS.has(t)||(l[t]=r));return a.length>0&&(l.inputResponses=a),{forChildren:c,forSelf:Object.keys(l).length>0?l:void 0,parentAction:o}}function resolveRetiredRequests(e){let t=new Map(e.responses.map(e=>[e.requestId,e])),n=[];for(let r of e.retireRequestIds){let i=e.entries.get(r);i!==void 0&&n.push(toInputResolution(r,i,t.get(r)))}return n}function toInputResolution(e,t,n){let r=resolveInputOutcome(t.kind,n),i={kind:t.kind,outcome:r,requestId:e};return n===void 0?i:{...i,response:n}}function resolveMessageAgainstQuestions(e){let t={consumed:!1,responses:[]};if(!e.enabled||(e.payload.inputResponses?.length??0)>0||e.payload.message===void 0)return t;let n=[...e.entries].filter(([t,n])=>n.kind===`question`&&e.routable(t,n)),r=n.flatMap(([e,t])=>{let n=t.workflowAsk?.question??t.question;return n===void 0?[]:[{requestId:e,...n}]});if(r.length===0)return t;let[i]=r,a=readAnswerText(e.payload),o=n.length===1&&i!==void 0&&a!==void 0?resolveTextToResponse(a,i):void 0;return o===void 0?t:{consumed:!0,responses:[o]}}function batchResolves(e){return e.batch.approvalRequestIds.length===0||e.batch.approvalRequestIds.every(t=>{let n=e.entries.get(t);return n===void 0||!sameBatch(n,e)||e.responseIds.has(t)})}function sameBatch(e,t){return e.childContinuationToken===t.childContinuationToken&&e.batch?.requestIds.length===t.batch.requestIds.length&&e.batch.requestIds.every((e,n)=>e===t.batch.requestIds[n])}export{emitProxiedAuthorizationEvent,emitProxiedInputRequest,routeDeliverPayload};
@@ -2,7 +2,6 @@
2
2
  import type { TurnCaller } from "#channel/types.js";
3
3
  import type { AgentTurnOutcome } from "#shared/agent-turn-outcome.js";
4
4
  import type { TokenUsage } from "#shared/token-usage.js";
5
- import { type SessionStateTransition } from "#execution/session/state-delta.js";
6
5
  /** Settled turn payload forwarded from the owner to the caller. */
7
6
  export interface SettledTurnNotification {
8
7
  readonly output: unknown;
@@ -27,7 +26,7 @@ export declare function notifyTurnCallerStep(input: {
27
26
  }): Promise<void>;
28
27
  /** Settles a workflow-owned caller after the child turn is cooperatively cancelled. */
29
28
  export declare function notifyCancelledTaskCallerStep(input: {
30
- readonly caller: TurnCaller | undefined;
29
+ readonly caller: TurnCaller;
31
30
  readonly sessionId: string;
32
31
  readonly usage?: TokenUsage;
33
32
  }): Promise<void>;
@@ -35,8 +34,9 @@ export declare function notifyCancelledTaskCallerStep(input: {
35
34
  export declare function resolveInitialTurnCallerStep(input: {
36
35
  readonly serializedContext: Record<string, unknown>;
37
36
  }): Promise<TurnCaller | undefined>;
38
- /** Rebinds child event forwarding to the caller that owns the next accepted turn. */
39
- export declare function bindTurnCallerContextStep(input: {
40
- readonly caller: TurnCaller | undefined;
41
- readonly serializedContext: Record<string, unknown>;
42
- }): Promise<SessionStateTransition>;
37
+ /**
38
+ * Points child event forwarding at the caller that owns the next accepted turn,
39
+ * so the turn's input requests and progress reach that caller and not the
40
+ * previous one.
41
+ */
42
+ export declare function bindTurnCallerContext(caller: TurnCaller | undefined, serializedContext: Record<string, unknown>): Record<string, unknown>;
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{SessionCallbackKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{parseJsonValue}from"#shared/json.js";import{ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{parseSessionCallback}from"#channel/session-callback.js";import{deserializeContext}from"#context/serialize.js";import{withSessionStateDelta}from"#execution/session/state-delta.js";import{postSessionCallbackRequest}from"#execution/session-callback-request.js";import{HookNotFoundError}from"#compiled/@workflow/errors/index.js";import{resumeHook}from"#internal/workflow/runtime.js";import{SUBAGENT_ADAPTER_KIND,isSubagentAdapterState}from"#subagents/adapter-state.js";const log=createLogger(`execution.delegated-parent-notification`),SUBAGENT_EXECUTION_FAILED=`SUBAGENT_EXECUTION_FAILED`,ZERO_TOKEN_USAGE={cacheReadTokens:0,cacheWriteTokens:0,inputTokens:0,outputTokens:0};async function notifyTurnCallerStep(e){"use step";if(e.caller===void 0)return;let t=createSettledTurnResult({caller:e.caller,lifecycle:e.lifecycle,sessionId:e.sessionId,settled:e.settled});if(e.caller.replyTo.kind===`callback`){await postSettledTurnCallback({result:t,sessionId:e.sessionId,url:e.caller.replyTo.url});return}await resumeSettledTurnHook(e.caller.replyTo.token,t)}async function notifyCancelledTaskCallerStep(e){"use step";if(e.caller===void 0)return;let t=e.usage??ZERO_TOKEN_USAGE,n={code:SUBAGENT_EXECUTION_FAILED,message:`The agent invocation was cancelled.`},r={callId:e.caller.callId,isError:!0,kind:`subagent-result`,origin:`child`,outcome:{kind:`parked`,result:{kind:`cancelled`},usageDelta:t},output:n,subagentName:e.caller.subagentName},i=e.usage===void 0?r:{...r,usage:e.usage};if(e.caller.replyTo.kind===`callback`){await postSettledTurnCallback({result:i,sessionId:e.sessionId,url:e.caller.replyTo.url});return}await resumeSettledTurnHook(e.caller.replyTo.token,i)}function createSettledTurnResult(e){let t=e.settled.usage??ZERO_TOKEN_USAGE;if(e.settled.isError===!0){let n={code:SUBAGENT_EXECUTION_FAILED,message:toErrorMessage(e.settled.output)};return{callId:e.caller.callId,isError:!0,kind:`subagent-result`,origin:`child`,outcome:{kind:e.lifecycle,result:{error:n,kind:`failed`},usageDelta:t},output:n,subagentName:e.caller.subagentName}}let n=parseJsonValue(e.settled.output),r={callId:e.caller.callId,kind:`subagent-result`,origin:`child`,outcome:{kind:e.lifecycle,result:{kind:`succeeded`,output:n},usageDelta:t},output:n,subagentName:e.caller.subagentName};return e.settled.usage===void 0?r:{...r,usage:e.settled.usage}}async function resolveInitialTurnCallerStep(e){"use step";let t=e.serializedContext[SessionCallbackKey.name];if(t!==void 0){let e=parseSessionCallback(t);if(!e.ok)throw Error(`Serialized session callback is invalid.`,{cause:e.cause});return{callId:e.callback.callId,replyTo:{kind:`callback`,token:e.callback.token,url:e.callback.url},subagentName:e.callback.subagentName}}let n=(await deserializeContext(e.serializedContext)).get(ChannelKey);if(n?.kind===SUBAGENT_ADAPTER_KIND&&isSubagentAdapterState(n.state))return{callId:n.state.callId,replyTo:{kind:`hook`,token:n.state.parentContinuationToken},subagentName:n.state.subagentName}}async function bindTurnCallerContextStep(e){"use step";return await withSessionStateDelta(e,async({caller:e,serializedContext:t})=>({serializedContext:bindTurnCallerContext(e,t)}))}function bindTurnCallerContext(e,t){if(e===void 0)return t;if(e.replyTo.kind===`callback`){let n={callId:e.callId,subagentName:e.subagentName,token:e.replyTo.token,url:e.replyTo.url};return{...t,[SessionCallbackKey.name]:n}}let n=t[ChannelKey.name];if(typeof n!=`object`||!n||Reflect.get(n,`kind`)!==SUBAGENT_ADAPTER_KIND||!isSubagentAdapterState(Reflect.get(n,`state`)))throw Error(`Delegated local turn is missing its subagent adapter binding.`);let r={...Reflect.get(n,`state`),callId:e.callId,parentContinuationToken:e.replyTo.token,subagentName:e.subagentName};return{...t,[ChannelKey.name]:{...n,state:r}}}async function postSettledTurnCallback(e){let{sessionId:t}=e;if(e.result.isError===!0){await postCallbackPayload({payload:{callId:e.result.callId,error:e.result.output,kind:`turn.failed`,outcome:e.result.outcome,sessionId:t,subagentName:e.result.subagentName},url:e.url});return}await postCallbackPayload({payload:{callId:e.result.callId,kind:`turn.completed`,outcome:e.result.outcome,output:e.result.output,sessionId:t,subagentName:e.result.subagentName},url:e.url})}async function postCallbackPayload(e){let t=await postSessionCallbackRequest({body:e.payload,url:e.url});if(!t.ok)throw Error(`Turn callback failed with HTTP ${t.status}.`)}async function resumeSettledTurnHook(e,t){try{await resumeHook(e,{kind:`runtime-action-result`,results:[t]})}catch(n){if(!HookNotFoundError.is(n))throw n;log.warn(`turn caller hook no longer exists`,{callId:t.callId,callerToken:e})}}export{bindTurnCallerContextStep,notifyCancelledTaskCallerStep,notifyTurnCallerStep,resolveInitialTurnCallerStep};
1
+ import{createLogger}from"#internal/logging.js";import{SessionCallbackKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{parseJsonValue}from"#shared/json.js";import{ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{parseSessionCallback}from"#channel/session-callback.js";import{deserializeContext}from"#context/serialize.js";import{postSessionCallbackRequest}from"#execution/session-callback-request.js";import{HookNotFoundError}from"#compiled/@workflow/errors/index.js";import{resumeHook}from"#internal/workflow/runtime.js";import{SUBAGENT_ADAPTER_KIND,isSubagentAdapterState}from"#subagents/adapter-state.js";const log=createLogger(`execution.delegated-parent-notification`),SUBAGENT_EXECUTION_FAILED=`SUBAGENT_EXECUTION_FAILED`,ZERO_TOKEN_USAGE={cacheReadTokens:0,cacheWriteTokens:0,inputTokens:0,outputTokens:0};async function notifyTurnCallerStep(e){"use step";if(e.caller===void 0)return;let t=createSettledTurnResult({caller:e.caller,lifecycle:e.lifecycle,sessionId:e.sessionId,settled:e.settled});if(e.caller.replyTo.kind===`callback`){await postSettledTurnCallback({result:t,sessionId:e.sessionId,url:e.caller.replyTo.url});return}await resumeSettledTurnHook(e.caller.replyTo.token,t)}async function notifyCancelledTaskCallerStep(e){"use step";let t=e.usage??ZERO_TOKEN_USAGE,n={code:SUBAGENT_EXECUTION_FAILED,message:`The agent invocation was cancelled.`},r={callId:e.caller.callId,isError:!0,kind:`subagent-result`,origin:`child`,outcome:{kind:`parked`,result:{kind:`cancelled`},usageDelta:t},output:n,subagentName:e.caller.subagentName},i=e.usage===void 0?r:{...r,usage:e.usage};if(e.caller.replyTo.kind===`callback`){await postSettledTurnCallback({result:i,sessionId:e.sessionId,url:e.caller.replyTo.url});return}await resumeSettledTurnHook(e.caller.replyTo.token,i)}function createSettledTurnResult(e){let t=e.settled.usage??ZERO_TOKEN_USAGE;if(e.settled.isError===!0){let n={code:SUBAGENT_EXECUTION_FAILED,message:toErrorMessage(e.settled.output)};return{callId:e.caller.callId,isError:!0,kind:`subagent-result`,origin:`child`,outcome:{kind:e.lifecycle,result:{error:n,kind:`failed`},usageDelta:t},output:n,subagentName:e.caller.subagentName}}let n=parseJsonValue(e.settled.output),r={callId:e.caller.callId,kind:`subagent-result`,origin:`child`,outcome:{kind:e.lifecycle,result:{kind:`succeeded`,output:n},usageDelta:t},output:n,subagentName:e.caller.subagentName};return e.settled.usage===void 0?r:{...r,usage:e.settled.usage}}async function resolveInitialTurnCallerStep(e){"use step";let t=e.serializedContext[SessionCallbackKey.name];if(t!==void 0){let e=parseSessionCallback(t);if(!e.ok)throw Error(`Serialized session callback is invalid.`,{cause:e.cause});return{callId:e.callback.callId,replyTo:{kind:`callback`,token:e.callback.token,url:e.callback.url},subagentName:e.callback.subagentName}}let n=(await deserializeContext(e.serializedContext)).get(ChannelKey);if(n?.kind===SUBAGENT_ADAPTER_KIND&&isSubagentAdapterState(n.state))return{callId:n.state.callId,replyTo:{kind:`hook`,token:n.state.parentContinuationToken},subagentName:n.state.subagentName}}function bindTurnCallerContext(e,t){if(e===void 0)return t;if(e.replyTo.kind===`callback`){let n={callId:e.callId,subagentName:e.subagentName,token:e.replyTo.token,url:e.replyTo.url};return{...t,[SessionCallbackKey.name]:n}}let n=t[ChannelKey.name];if(typeof n!=`object`||!n||Reflect.get(n,`kind`)!==SUBAGENT_ADAPTER_KIND||!isSubagentAdapterState(Reflect.get(n,`state`)))throw Error(`Delegated local turn is missing its subagent adapter binding.`);let r={...Reflect.get(n,`state`),callId:e.callId,parentContinuationToken:e.replyTo.token,subagentName:e.subagentName};return{...t,[ChannelKey.name]:{...n,state:r}}}async function postSettledTurnCallback(e){let{sessionId:t}=e;if(e.result.isError===!0){await postCallbackPayload({payload:{callId:e.result.callId,error:e.result.output,kind:`turn.failed`,outcome:e.result.outcome,sessionId:t,subagentName:e.result.subagentName},url:e.url});return}await postCallbackPayload({payload:{callId:e.result.callId,kind:`turn.completed`,outcome:e.result.outcome,output:e.result.output,sessionId:t,subagentName:e.result.subagentName},url:e.url})}async function postCallbackPayload(e){let t=await postSessionCallbackRequest({body:e.payload,url:e.url});if(!t.ok)throw Error(`Turn callback failed with HTTP ${t.status}.`)}async function resumeSettledTurnHook(e,t){try{await resumeHook(e,{kind:`runtime-action-result`,results:[t]})}catch(n){if(!HookNotFoundError.is(n))throw n;log.warn(`turn caller hook no longer exists`,{callId:t.callId,callerToken:e})}}export{bindTurnCallerContext,notifyCancelledTaskCallerStep,notifyTurnCallerStep,resolveInitialTurnCallerStep};
@@ -1 +1 @@
1
- async function executeAskQuestionTool(e,t){"use workflow";return toAskQuestionOutput(await t.ask(toAskQuestionRequest(e)))}function toAskQuestionRequest(e){let t=e.options?.map(e=>({description:e.description,id:e.label,label:e.label}));return{allowFreeform:!0,display:t===void 0?`text`:`select`,prompt:e.question,...t!==void 0&&{options:t}}}function toAskQuestionOutput(e){switch(e.status){case`answered`:return{answer:e.optionId??e.text??``,status:`answered`};case`cancelled`:return{interrupted:!0};case`unavailable`:return{status:`unavailable`}}}executeAskQuestionTool.workflowId=`workflow//eve@0.70.0//executeAskQuestionTool`;export{executeAskQuestionTool,toAskQuestionOutput,toAskQuestionRequest};
1
+ async function executeAskQuestionTool(e,t){"use workflow";return toAskQuestionOutput(await t.ask(toAskQuestionRequest(e)))}function toAskQuestionRequest(e){let t=e.options?.map(e=>({description:e.description,id:e.label,label:e.label}));return{allowFreeform:!0,display:t===void 0?`text`:`select`,prompt:e.question,...t!==void 0&&{options:t}}}function toAskQuestionOutput(e){switch(e.status){case`answered`:return{answer:e.optionId??e.text??``,status:`answered`};case`cancelled`:return{interrupted:!0};case`unavailable`:return{status:`unavailable`}}}executeAskQuestionTool.workflowId=`workflow//eve@0.70.2//executeAskQuestionTool`;export{executeAskQuestionTool,toAskQuestionOutput,toAskQuestionRequest};
@@ -6,6 +6,13 @@ export interface ReadFileToolInput {
6
6
  }
7
7
  export interface ReadFileToolOutput {
8
8
  content: string;
9
+ /** Set when the file is a PNG, JPEG, GIF, or WebP image the model sees as an image. */
10
+ image?: {
11
+ height?: number;
12
+ mediaType: string;
13
+ size: number;
14
+ width?: number;
15
+ };
9
16
  nextOffset?: number;
10
17
  path: string;
11
18
  totalLines: number;
@@ -1,2 +1,2 @@
1
- import{defineJsonSchema}from"#tools/schema.js";import{defineTool}from"#tools/definition.js";import{toolLabel}from"#tools/tool-label.js";import{executeReadFileOnSandbox}from"#execution/sandbox/read-file.js";const READ_FILE_INPUT_SCHEMA=defineJsonSchema({type:`object`,properties:{filePath:{type:`string`,description:`The absolute path to the file to read. A leading $HOME is supported.`},limit:{type:`integer`,minimum:1,description:`Maximum number of lines to return. Defaults to 2000.`},offset:{type:`integer`,minimum:1,description:`1-based line number to start from. Defaults to 1.`}},required:[`filePath`],additionalProperties:!1}),READ_FILE_OUTPUT_SCHEMA=defineJsonSchema({type:`object`,properties:{content:{type:`string`},nextOffset:{type:`integer`,minimum:1},path:{type:`string`},totalLines:{type:`integer`,minimum:0},truncated:{type:`boolean`}},required:[`content`,`path`,`totalLines`,`truncated`],additionalProperties:!1}),readFile=defineTool({label:{start:e=>toolLabel(`Read`,e.filePath)},description:[`Read a file from the local filesystem. If the path does not exist, an error is returned.`,``,`Usage:`,`- The filePath parameter should be an absolute path or begin with $HOME/.`,`- By default, this tool returns up to 2000 lines from the start of the file.`,`- The offset parameter is the line number to start from (1-indexed).`,`- To read later sections, call this tool again with a larger offset.`,'- Contents are returned with each line prefixed by its line number as `<line>: <content>`. For example, if a file has contents "foo\\n", you will receive "1: foo\\n".',`- Any line longer than 2000 characters is truncated.`,`- Call this tool in parallel when you know there are multiple files you want to read.`,`- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.`].join(`
2
- `),async execute(e,t){return await executeReadFileOnSandbox(await t.getSandbox(),e)},inputSchema:READ_FILE_INPUT_SCHEMA,outputSchema:READ_FILE_OUTPUT_SCHEMA});export{READ_FILE_INPUT_SCHEMA,READ_FILE_OUTPUT_SCHEMA,readFile as default,readFile};
1
+ import{contextStorage}from"#context/container.js";import{SandboxKey}from"#context/keys.js";import{basename}from"node:path";import{defineJsonSchema}from"#tools/schema.js";import{resolveAbsoluteFilePath}from"#execution/sandbox/require-sandbox.js";import{defineTool}from"#tools/definition.js";import{toolLabel}from"#tools/tool-label.js";import{toolOutput,toolOutputPart}from"#tools/model-output.js";import{executeReadFileOnSandbox}from"#execution/sandbox/read-file.js";const READ_FILE_INPUT_SCHEMA=defineJsonSchema({type:`object`,properties:{filePath:{type:`string`,description:`The absolute path to the file to read. A leading $HOME is supported.`},limit:{type:`integer`,minimum:1,description:`Maximum number of lines to return. Defaults to 2000.`},offset:{type:`integer`,minimum:1,description:`1-based line number to start from. Defaults to 1.`}},required:[`filePath`],additionalProperties:!1}),READ_FILE_OUTPUT_SCHEMA=defineJsonSchema({type:`object`,properties:{content:{type:`string`},image:{type:`object`,properties:{height:{type:`integer`,minimum:1},mediaType:{type:`string`},size:{type:`integer`,minimum:0},width:{type:`integer`,minimum:1}},required:[`mediaType`,`size`],additionalProperties:!1},nextOffset:{type:`integer`,minimum:1},path:{type:`string`},totalLines:{type:`integer`,minimum:0},truncated:{type:`boolean`}},required:[`content`,`path`,`totalLines`,`truncated`],additionalProperties:!1}),readFile=defineTool({label:{start:e=>toolLabel(`Read`,e.filePath)},description:[`Read a file from the local filesystem. If the path does not exist, an error is returned.`,``,`Usage:`,`- The filePath parameter should be an absolute path or begin with $HOME/.`,`- By default, this tool returns up to 2000 lines from the start of the file.`,`- The offset parameter is the line number to start from (1-indexed).`,`- To read later sections, call this tool again with a larger offset.`,'- Contents are returned with each line prefixed by its line number as `<line>: <content>`. For example, if a file has contents "foo\\n", you will receive "1: foo\\n".',`- Any line longer than 2000 characters is truncated.`,"- PNG, JPEG, GIF, and WebP files up to 3 MiB are shown to you as images, including files named in `Attached file <path>` references.",`- Call this tool in parallel when you know there are multiple files you want to read.`,`- Avoid tiny repeated slices (30 line chunks). If you need more context, read a larger window.`].join(`
2
+ `),async execute(e,t){return await executeReadFileOnSandbox(await t.getSandbox(),e)},inputSchema:READ_FILE_INPUT_SCHEMA,outputSchema:READ_FILE_OUTPUT_SCHEMA,async toModelOutput(e){if(e.image===void 0)return toolOutput.json(e);let t=await contextStorage.getStore()?.get(SandboxKey)?.get(),n=t==null?null:await t.readBinaryFile({path:await resolveAbsoluteFilePath(t,e.path)});return n===null?toolOutput.text(`${e.content} The image could not be loaded.`):toolOutput.content([toolOutputPart.text(e.content),toolOutputPart.file(Buffer.from(n).toString(`base64`),{filename:basename(e.path),mediaType:e.image.mediaType})])}});export{READ_FILE_INPUT_SCHEMA,READ_FILE_OUTPUT_SCHEMA,readFile as default,readFile};
@@ -1 +1 @@
1
- import{sleep}from"#compiled/@workflow/core/index.js";async function executeSleepTool(e,t){"use workflow";let n=new Promise(e=>t.abortSignal.addEventListener(`abort`,()=>e(`interrupted`),{once:!0})),r=sleep(Math.ceil(e.seconds*1e3)).then(()=>`elapsed`);return await Promise.race([r,n])===`elapsed`?{waitedSeconds:e.seconds}:{interrupted:!0}}executeSleepTool.workflowId=`workflow//eve@0.70.0//executeSleepTool`;export{executeSleepTool};
1
+ import{sleep}from"#compiled/@workflow/core/index.js";async function executeSleepTool(e,t){"use workflow";let n=new Promise(e=>t.abortSignal.addEventListener(`abort`,()=>e(`interrupted`),{once:!0})),r=sleep(Math.ceil(e.seconds*1e3)).then(()=>`elapsed`);return await Promise.race([r,n])===`elapsed`?{waitedSeconds:e.seconds}:{interrupted:!0}}executeSleepTool.workflowId=`workflow//eve@0.70.2//executeSleepTool`;export{executeSleepTool};
@@ -180,8 +180,8 @@ export default defineAgent({
180
180
  `sessionTimeoutMs` sets the lifetime for every session, including delegated
181
181
  sessions. It defaults to 30 days and starts at creation. Each successful
182
182
  deployment handoff or legacy-session import restarts the original configured
183
- duration. Process restarts, ordinary messages, and failed or skipped handoffs
184
- keep the existing deadline. At the deadline, eve lets an active turn settle,
183
+ duration. Process restarts, ordinary messages, compaction handoffs, and failed
184
+ or skipped handoffs keep the existing deadline. At the deadline, eve lets an active turn settle,
185
185
  then emits `session.completed` and releases every continuation address; the next
186
186
  qualifying channel message starts fresh. Set it to `false` to disable the
187
187
  timeout. Expiration does not delete stored session data.
@@ -179,7 +179,7 @@ chatSdkChannel({
179
179
 
180
180
  ### Human-in-the-loop (HITL)
181
181
 
182
- HITL prompts render as a Chat SDK `Card` with buttons. Button clicks resume the parked session automatically — the channel wires `bot.onAction` to `respond` for you. Change the action-id prefix with `inputActionPrefix` (default `eve_input:`) if your app already uses it, and provide `resolveInputAuth` to carry user or tenant auth across the resume:
182
+ HITL prompts render as a Chat SDK `Card` with buttons. Button clicks resume the parked session automatically — the channel wires `bot.onAction` to `respond` for you. Adapters that can't render cards, such as Photon iMessage, get fallback text with the options numbered. A reply forwarded through `send` with an option's number or label answers the pending request, so `1` or `approve` runs an approval-gated tool and `2` or `cancel` stops it. If the question allows a custom answer, or has no options, the message asks for a typed reply and any other text is taken as that answer. Change the action-id prefix with `inputActionPrefix` (default `eve_input:`) if your app already uses it, and provide `resolveInputAuth` to carry user or tenant auth across the resume:
183
183
 
184
184
  ```ts
185
185
  chatSdkChannel({
@@ -457,20 +457,26 @@ The bases say where a call may go. `apiBaseUrl` is the base Slack Web API method
457
457
 
458
458
  eve shows an agent's progress in two places. Short work, such as thinking or a tool call, appears in the thread status and never posts a message. [Tasks](/docs/tools/tasks), which keep working while the conversation continues, appear in a task card: one message per turn that eve updates in place. Replies post as ordinary messages, so the thread holds the conversation, the task card, and nothing else.
459
459
 
460
- | Moment | Thread status |
461
- | ---------------------------------- | ------------------------------------------------------------------------------- |
462
- | A mention or DM arrives | `Thinking...`, right away, while the message hook is still deciding |
463
- | The message hook drops the message | Cleared |
464
- | The turn starts | `Working...` |
465
- | The model reasons | The first line of its reasoning |
466
- | The model calls tools | Its narration before the calls, or an action label such as `Search useEveAgent` |
467
- | The turn waits on its tasks | `Waiting on researcher...`, or `Waiting on 3 tasks...` |
468
- | The model starts its next step | `Reviewing results...` after a task finishes, otherwise `Thinking...` |
469
- | A reply posts | Cleared by Slack |
470
-
471
- Slack clears a thread status after two minutes without a message, so during a long wait the task card is what shows the work is still going.
472
-
473
- Action labels follow the same rule as the task card titles described below, plus `+N more` when the model requests several actions at once. They leave out the model's own `task_wait` and `task_cancel` calls. Reasoning snippets build progressively: extensions of at least four characters appear immediately, while smaller streamed deltas use a five-second refresh interval to avoid one Slack request per token.
460
+ | Moment | Thread status |
461
+ | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
462
+ | A mention or DM arrives | `Thinking...`, right away, while the message hook is still deciding |
463
+ | The message hook drops the message | Cleared |
464
+ | The turn starts | `Thinking...` |
465
+ | The model reasons | Its latest reasoning heading, or its latest finished sentence |
466
+ | The model calls tools | Its narration before the calls, or the first action label, such as `Search useEveAgent`, plus `+N more` for the step's other calls |
467
+ | A tool reports progress | The tool's `label.delta` text |
468
+ | A tool finishes | The tool's `label.complete` text, when it defines one; otherwise the status stays |
469
+ | The model starts its next step | `Reviewing researcher's results...` after a task finishes, otherwise the same status again |
470
+ | The model streams a long reply | `Writing a reply...`, once the text runs past 280 characters |
471
+ | The turn waits on its tasks | `Waiting on researcher...`, or `Waiting on 3 tasks...` |
472
+ | The turn waits on an approval, answer, or sign-in | Cleared until the turn resumes, then the status it had |
473
+ | A reply posts | Cleared by Slack |
474
+
475
+ A model step never resets the status to a generic line. The status keeps naming the latest work until something newer replaces it, so a user watching the thread sees `Checking recent deploys`, then `Found 4 failed deploys`, then `Reading build logs`, without `Thinking...` in between.
476
+
477
+ Slack removes a thread status after two minutes without a message from the app, and clears it whenever the app posts in the thread. eve sets the current status again at each model step, after it posts a new task card, and as a tool reports progress. A tool that runs longer than two minutes without reporting progress loses its status. Report progress with [`label.delta`](/docs/tools#label-tool-activity), or run the work as a [task](/docs/tools/tasks), whose card shows it is still going.
478
+
479
+ Action labels follow the same rule as the task card titles described below. They leave out the model's own `task_wait` and `task_cancel` calls. Reasoning shows one piece at a time: a heading when the model titles its reasoning, as reasoning summaries do, otherwise the latest finished sentence. Each piece of reasoning, and each progress label, stays up for at least three seconds, so the status changes at a readable pace instead of sending a Slack request per token. Every status is cut to 50 characters at a word boundary.
474
480
 
475
481
  Replies up to 12,000 characters render inline. Longer replies are uploaded unchanged as a Markdown snippet named `eve-response.md` with a short in-thread note, which requires the `files:write` bot scope.
476
482
 
@@ -792,7 +798,7 @@ Private file downloads resolve credentials from the session's app installation w
792
798
 
793
799
  Downloads cover Slack's own file hosts plus URLs under a configured [`api.fileBaseUrl`](#point-the-channel-at-a-different-slack-api-host), and travel on `api.fetch` when one is configured.
794
800
 
795
- Staged files live in the session sandbox under `/workspace/attachments`. Session history keeps a reference to that path, not a second copy of the Slack file. If the backend can no longer reattach the original sandbox, eve cannot restore the attachment bytes from history; later turns receive a missing-file result. Store files outside the sandbox when the agent must retain them independently of sandbox availability.
801
+ Staged files live in the session sandbox under `/workspace/.eve/attachments`. Session history keeps a reference to that path, not a second copy of the Slack file. If the backend can no longer reattach the original sandbox, eve cannot restore the attachment bytes from history; later turns receive a missing-file result. Store files outside the sandbox when the agent must retain them independently of sandbox availability.
796
802
 
797
803
  ## What to read next
798
804
 
@@ -62,7 +62,7 @@ The default `message.completed` handler sends the reply as SMS through Twilio's
62
62
 
63
63
  ### Human-in-the-loop (HITL)
64
64
 
65
- SMS and voice have no native button or card affordance, so HITL prompts do not render as interactive controls. The agent's `input.requested` event reaches your `events["input.requested"]` handler if you declare one. Handle it by sending the prompt as text and mapping the caller's reply back to the input request yourself.
65
+ SMS has no buttons or cards, so the default `input.requested` handler sends each question and tool approval as an SMS with its options numbered. A reply with an option's number or label answers the pending request, so `1` or `approve` runs an approval-gated tool and `2` or `cancel` stops it. If the question allows a custom answer, any other text is taken as that answer. Callers on a voice call get the question by SMS too. To render questions differently, declare your own `events["input.requested"]` handler.
66
66
 
67
67
  ### Proactive sessions
68
68
 
@@ -64,7 +64,7 @@ export default disableTool();
64
64
 
65
65
  ### `read_file`
66
66
 
67
- `read_file` reads text files from the sandbox with line-numbered output. It accepts absolute paths and paths beginning with `$HOME/`.
67
+ `read_file` reads text files from the sandbox with line-numbered output, and shows PNG, JPEG, GIF, and WebP images up to 3 MiB to the model as images. It accepts absolute paths and paths beginning with `$HOME/`.
68
68
 
69
69
  ```sh
70
70
  eve add tool/read_file
@@ -36,6 +36,10 @@ if a turn is running, eve queues it until that turn settles. A successful manual
36
36
  compaction emits the same `compaction.requested` and `compaction.completed`
37
37
  events as automatic compaction, followed by `session.waiting`.
38
38
 
39
+ After either kind of compaction, eve moves the idle session to a fresh workflow
40
+ run on the same deployment, so a long session's run history does not keep
41
+ growing. See [Execution model and durability](./execution-model-and-durability#compaction-handoff).
42
+
39
43
  To discard model-message history instead of summarizing it, call the corresponding
40
44
  `clear()` method on any of those handles. Clearing preserves the session identity,
41
45
  system prompt, configured tools and skills, durable state, limits, and sandbox.
@@ -25,10 +25,18 @@ The Workflow SDK is not inherently tied to Vercel. In local development and in a
25
25
 
26
26
  When a Vercel production deployment changes, a new delivery to an idle session hands the session's settled state to the exact deployment that accepted the request. This applies whether the delivery arrives through the session ID or through any channel continuation address the session has claimed. The session keeps its original ID and event stream, while the next model turn uses that deployment's current system-role instructions, model, and tools. Static user-role instructions stay pinned in the durable conversation history and are not seeded again. A successful handoff restarts the session's original configured [`sessionTimeoutMs`](../agent-config#runtime-limits) duration; disabled timeouts stay disabled. Failed or skipped handoffs keep the existing deadline.
27
27
 
28
- Before taking ownership, the target deployment validates the checkpoint's stored tasks and subagent handles and checks that no pending work remains. This includes settled tasks. If that validation fails, the previous owner recovers the session and processes the triggering delivery. Task and handle state preserve unknown metadata fields, while known fields and lifecycle rules must still be valid. Authored session state remains opaque to this validation.
28
+ Before taking ownership, the target deployment upgrades the checkpoint when an older eve release wrote it, then checks that no pending work remains. A deployment accepts checkpoints from eve 0.66.0 and later, so upgrading eve does not leave long-lived sessions, such as a Slack thread, on their old deployment. An older release cannot read a newer checkpoint: after a rollback, a session that already moved to the newer deployment stays there. Extension state saved before eve 0.69 moves to its mount when exactly one mount of that extension package defines that state. State that the target deployment no longer defines, such as a removed `defineState` key, is dropped, and the target deployment's runtime logs show a `dropping unknown context key during deserialization` warning. When two or more mounts of one extension package define that state, eve cannot choose an owner, and the session stays on its current deployment. Idle subagent sessions that eve 0.66–0.68 kept for later reuse are stopped after the handoff. Authored session state remains opaque to this validation.
29
+
30
+ If the target deployment rejects the session, the previous owner keeps it and processes the triggering delivery. When the target deployment cannot read the checkpoint, its Vercel runtime logs show a `session handoff refused` warning with the checkpoint version and the reason. Owners on this release or later also write a `session handoff failed` warning to their own runtime logs, with the target deployment and the reason.
29
31
 
30
32
  eve does not move live work between deployments. A session with a running turn, pending human input or authorization, queued messages, or active tasks and subagents stays on its current deployment. After that work settles, a later delivery can move the idle session; the earlier deployment signal is not remembered.
31
33
 
34
+ ### Compaction handoff
35
+
36
+ Every workflow run records each step it executes, so a session that ran on one run for weeks would carry its whole history in that run. To bound this, eve moves a session to a fresh run on its current deployment after [compaction](./default-harness#compaction) rewrites the conversation history. This applies to automatic compaction during a turn and to a client or channel compaction request.
37
+
38
+ The move happens once the turn settles and nothing else is waiting: no queued messages, running tasks or subagents, or pending human input. The new run receives the compacted history and settled state, then waits for the next message. If a single message reaches an otherwise idle session before the move completes, eve moves the session with that message, and the message's turn runs on the new run. If work is still pending, eve tries again after a later turn settles. The session keeps its ID, event stream, continuation addresses, and deadline; compaction never extends [`sessionTimeoutMs`](../agent-config#runtime-limits). If the new run cannot take ownership, the current run keeps the session, writes a `session handoff failed` warning to its runtime logs, and tries again later.
39
+
32
40
  ### Upgrading sessions from the former execution model
33
41
 
34
42
  You can upgrade directly from a release that used a session driver and separate
@@ -12,8 +12,8 @@ controls, and streams. Every operation targets that exact session; none follows
12
12
  or creates a replacement implicitly.
13
13
 
14
14
  The session ID currently identifies the original Workflow run that owns the
15
- event stream. A deployment handoff changes the executing run, not the session
16
- ID or stream. Stream namespaces belong to a run; eve does not support
15
+ event stream. A deployment or compaction handoff changes the executing run, not
16
+ the session ID or stream. Stream namespaces belong to a run; eve does not support
17
17
  caller-assigned session IDs or globally addressed streams.
18
18
 
19
19
  Authored channels also have channel-local continuation tokens. A token addresses
@@ -24,7 +24,7 @@ by the eve HTTP session API. See [Custom channels](../channels/custom#channel-op
24
24
  Sessions last 30 days by default; configure `limits.sessionTimeoutMs` in
25
25
  `agent.ts`, or set it to `false` to disable the deadline. A successful deployment
26
26
  handoff or legacy-session import restarts the original configured duration.
27
- Ordinary messages and process restarts keep the existing deadline. At expiration, eve
27
+ Ordinary messages, compaction handoffs, and process restarts keep the existing deadline. At expiration, eve
28
28
  lets an active turn settle, emits `session.completed`, and releases the
29
29
  session's continuation addresses so the next qualifying channel message starts fresh. Stored
30
30
  session data is not deleted. See [Agent config](../agent-config#runtime-limits).
@@ -110,6 +110,7 @@ Every `t.session()` or `t.send()` creates a new independent conversation. Contin
110
110
  - `session.send(message, options?)` sends a follow-up on that session and waits for it to settle.
111
111
  - `session.start(message, options?)` starts a text turn and returns as soon as the server accepts it. The live turn exposes `.session`, `.sessionId`, `.waitForEvent(...)`, `.cancel()`, and `.result()`.
112
112
  - `session.cancel()` requests cooperative cancellation of that session's active turn. Both `accepted` and `no_active_turn` are successful outcomes.
113
+ - `session.compact()` compacts that session's history between turns and waits for it to finish. It resolves to the captured events from `compaction.requested` through `session.waiting`, so assertions such as `.event("compaction.completed")` work on the result. It throws when the session is no longer active.
113
114
  - `session.sendFile(text, path, mediaType?)` attaches a local file as a data URL.
114
115
  - `session.agent(started).stream(options?)` follows a delegated child's stream through the parent with the eval client's credentials. Pass an `agent.started` event from that session. It also reaches children on another deployment, which `t.target.watchTurn(...)` cannot address.
115
116
  - `session.requireInputRequest(filter?)` records a gate, requires exactly one pending request, and returns it. Filters match tool name, action input, prompt, display, and option ids.
@@ -210,10 +210,13 @@ Extension state belongs to the logical mount path (for example, `extensions/crm`
210
210
 
211
211
  ### Upgrade from package-scoped extension state
212
212
 
213
- Deployments before this release stored extension state under package-prefixed keys. eve does not migrate that state to mount-owned keys or reset it during restore.
213
+ Deployments before eve 0.69 stored extension state under package-prefixed keys, such as `acme-crm.requests`. When a session from one of those deployments hands off to a newer deployment, eve moves each package-prefixed value to the mount that uses that package.
214
214
 
215
- - Session handoffs across this upgrade boundary are rejected in both directions, including for agents without extensions. Keep each session's owning deployment available until the session finishes, or start a new session on the deployment you want to use.
216
- - Local context snapshots use a separate state-layout check. Older snapshots for agents with extensions are incompatible; snapshots for agents without extensions can restore if they contain no unrecognized state keys.
215
+ - The value moves when exactly one mount of that package defines that state name.
216
+ - When no mount of that package defines that state name, for example because the extension removed it or the package was renamed, eve drops the value. The target deployment's runtime logs show a `dropping unknown context key during deserialization` warning with the key.
217
+ - When two or more mounts use the package and define that state name, eve cannot tell which mount owns the value, so the handoff is rejected and the session stays on its current deployment. Keep that deployment available until the session finishes, or start a new session on the deployment you want to use.
218
+ - Older deployments cannot read sessions saved by a newer release, so a session that already moved does not hand back after a rollback.
219
+ - Local context snapshots follow the same rules.
217
220
 
218
221
  ### Use an extension in a workspace
219
222
 
@@ -22,8 +22,8 @@ Before your first message, the empty composer may suggest asking your local agen
22
22
  | Command | Description |
23
23
  | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
24
24
  | `/model` | Choose the model, speed, and reasoning. Pass a model ID to set it directly: `/model provider/model-id`. |
25
- | `/reset` | Start a fresh session. |
26
- | `/clear` | Clear the session's model-message history. `/new` is an alias. |
25
+ | `/new` | Start a fresh session and clear the screen. `/reset` is an alias. |
26
+ | `/clear` | Clear the session's model-message history and keep the session. |
27
27
  | `/compact` | Compact the current session's context. |
28
28
  | `/cancel` | Cancel the current turn without discarding settled context. |
29
29
  | `/login` | Connect a ChatGPT subscription, Vercel account, or provider API key. |
@@ -98,7 +98,9 @@ Type a message and press `Enter` to send it. When the agent asks a question or r
98
98
 
99
99
  The activity line shows **Thinking** while the model reasons or waits to respond, **Generating** while it writes a response or tool input, and **Running** while tools execute. A blinking dot and elapsed time indicate progress, with token counts shown when available. While [tasks](../tools/tasks) work, the drawer header replaces this line: it shows the parent's current activity, task count, and turn elapsed time without token counts. **Waiting** means the parent is waiting on work; **Working** appears without a turn timer when tasks remain active between turns.
100
100
 
101
- A task, such as a call to a subagent, writes one line to the transcript when it starts and one when it finishes, fails, or is stopped. The finished line names how long the task took and, for a subagent, what it did, such as `Read 3 files, Ran 2 commands`. Task starts use the same tool-call styling as synchronous calls. While tasks work, an activity drawer above the prompt shows each task's name, current activity, and elapsed time without repeating its launch prompt. Subagents use labels such as `subagent(self-modification)`, with elapsed time beside the name and the latest observed tool grouped beneath an elbow. The tool remains visible between calls rather than switching to a starting placeholder. During questions and approvals, activity stays visible above the request with a shared divider when screen space permits; the request takes priority on small terminals. Background work started by a followed subagent appears beneath that subagent; deeper work uses an ownership path instead of further indentation. The panel caps its height and prioritizes tasks needing approval when work overflows. The UI does not show the model's own `task_wait` and `task_cancel` calls; their effect appears as the **Waiting** header and a stopped task's line.
101
+ A tool call shows the tool's own `label`, or eve's copy for built-in tools such as `Read README.md`. Any other call shows a readable tool name without its extension or connection prefix, such as `List issues` for `linear__list_issues`. Approval prompts show the exact tool name unless the tool provides a title.
102
+
103
+ A task, such as a call to a subagent, writes one line to the transcript when it starts and one when it finishes, fails, or is stopped. The finished line names how long the task took and, for a subagent, what it did, such as `Read 3 files, Ran 2 commands`. Task starts use the same tool-call styling as synchronous calls. While tasks work, an activity drawer above the prompt shows each task's name, current activity, and elapsed time without repeating its launch prompt. Subagents use readable labels, such as `subagent(stock price)` for a `stock-price` subagent or `subagent(worker)` for the `code__worker` subagent of the code extension, with elapsed time beside the name and the latest observed tool grouped beneath an elbow. The tool remains visible between calls rather than switching to a starting placeholder. During questions and approvals, activity stays visible above the request with a shared divider when screen space permits; the request takes priority on small terminals. Background work started by a followed subagent appears beneath that subagent; deeper work uses an ownership path instead of further indentation. The panel caps its height and prioritizes tasks needing approval when work overflows. The UI does not show the model's own `task_wait` and `task_cancel` calls; their effect appears as the **Waiting** header and a stopped task's line.
102
104
 
103
105
  The prompt stays open while the agent works, including turns started elsewhere, such as a scheduled run. While a turn is running, `Enter` sends your message immediately as steering. Before assistant output begins, the runtime interrupts pending model generation and continues the same turn with your correction. Executing tools finish safely. After output begins, steering applies at the next workflow boundary and preserves streamed text.
104
106
 
@@ -55,7 +55,7 @@ export default defineAgent({
55
55
  });
56
56
  ```
57
57
 
58
- eve stages byte-backed `file` parts under `/workspace/attachments` before
58
+ eve stages byte-backed `file` parts under `/workspace/.eve/attachments` before
59
59
  `step.started`, but keeps their media type in `ctx.messages`. When an image
60
60
  reaches the provider, vision models can process it and non-vision models reject
61
61
  it. eve does not reroute automatically. See [Inbound
@@ -149,6 +149,17 @@ interface SandboxProviderResourceTree {
149
149
 
150
150
  Docker and microsandbox mount the compiled trees read-only under `/eve/resources`, copy workspace seeds into writable `/workspace`, and expose skills read-only where possible. Vercel's snapshot environment writes workspace and skill contents before capturing its snapshot. Custom providers decide whether to upload, mount, or write each tree, but must expose the final target paths before authored preparation runs.
151
151
 
152
+ ## Inbound attachments
153
+
154
+ eve writes byte-backed files that reach the agent into the sandbox under `/workspace/.eve/attachments/` and keeps only a reference in session history. This covers files attached to user messages and files that tools return to the model with `toolOutputPart.file` or as MCP image, audio, and blob content.
155
+
156
+ Before every model call, eve reads the referenced files back from the sandbox:
157
+
158
+ - An image up to 3 MiB or a PDF up to 20 MiB attached to a message reaches the model as a file. Larger files and other types reach the model as a text reference, such as `Attached file /workspace/.eve/attachments/<hash>/report.csv (text/csv)`, that the agent can open with `read_file` or `bash`.
159
+ - A file returned by a tool always reaches the model as a file.
160
+
161
+ Each call sends the same bytes for the same file, so the model still sees attachments from earlier turns and the provider's prompt cache stays valid. Compaction replaces files in older tool results with their text reference. The `read_file` tool shows PNG, JPEG, GIF, and WebP files up to 3 MiB to the model as images, so the agent can look at an image again after compaction.
162
+
152
163
  ## Custom providers
153
164
 
154
165
  Define a provider with `defineSandboxProvider()` from `eve/sandbox/provider`. The five generic parameters describe environment options, live options, the prepared artifact, minimal serialized session state, and the provider-specific sandbox session type:
@@ -307,9 +307,11 @@ export default defineTool({
307
307
 
308
308
  The `toolOutput.text` and `toolOutput.json` builders construct the other two output shapes; hand-written literals remain valid everywhere.
309
309
 
310
- File payloads must be base64 strings — raw bytes (`Uint8Array`, `Buffer`) are rejected because they do not survive eve's durable JSON boundary. Keep payloads small: a content-part image is persisted in session history and re-sent on every subsequent model call, and eve warns above 3 MiB. Sending image parts to a model without vision support fails with that provider's error, the same as image parts in user messages.
310
+ File payloads must be base64 strings — raw bytes (`Uint8Array`, `Buffer`) are rejected because they do not survive eve's durable JSON boundary. eve warns above 3 MiB. Sending image parts to a model without vision support fails with that provider's error, the same as image parts in user messages.
311
311
 
312
- When older turns are compacted, file payloads are dropped from the summary and replaced with a text stub naming the file and media type — the model cannot re-see a compacted image. Content parts are for "look at this now"; if the agent may need an artifact again later, write it to the [sandbox](/docs/sandbox) and return its path.
312
+ eve writes each returned file to the session sandbox and keeps only a reference in session history, so the file's bytes are not stored again on every workflow step. Every later model call sends the same file, which keeps it visible on later turns and keeps the provider's prompt cache valid. See [Inbound attachments](/docs/sandbox#inbound-attachments). Compaction counts an image at roughly what providers bill for it rather than its base64 length.
313
+
314
+ When compaction drops older tool results, each file becomes a text reference such as `Attached file /workspace/.eve/attachments/<hash>/chart.png (image/png)`. The built-in `read_file` tool shows PNG, JPEG, GIF, and WebP files up to 3 MiB to the model as images, so the agent can open a compacted image again.
313
315
 
314
316
  Do not return secrets, credentials, unnecessary personal data, or unbounded sensitive content from tools. Filter, minimize, and redact tool outputs before returning them.
315
317
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve",
3
- "version": "0.70.0",
3
+ "version": "0.70.2",
4
4
  "private": false,
5
5
  "description": "Filesystem-first framework for durable backend AI agents that run anywhere.",
6
6
  "keywords": [
@@ -547,12 +547,12 @@
547
547
  "@vercel/otel": "2.1.3",
548
548
  "@vercel/sandbox": "3.3.0",
549
549
  "@vercel/sdk": "1.28.8",
550
- "@workflow/core": "5.0.0-beta.57",
551
- "@workflow/errors": "5.0.0-beta.24",
552
- "@workflow/serde": "5.0.0-beta.2",
553
- "@workflow/world": "5.0.0-beta.39",
554
- "@workflow/world-local": "5.0.0-beta.48",
555
- "@workflow/world-vercel": "5.0.0-beta.52",
550
+ "@workflow/core": "5.0.1",
551
+ "@workflow/errors": "5.0.1",
552
+ "@workflow/serde": "5.0.0",
553
+ "@workflow/world": "5.0.1",
554
+ "@workflow/world-local": "5.0.1",
555
+ "@workflow/world-vercel": "5.0.1",
556
556
  "ai": "^7.0.105",
557
557
  "chat": "4.41.0",
558
558
  "chokidar": "5.0.0",
@@ -561,8 +561,8 @@
561
561
  "emulate": "0.6.0",
562
562
  "env-runner": "0.1.16",
563
563
  "eventsource-parser": "3.1.0",
564
- "gray-matter": "4.0.3",
565
564
  "jose": "6.2.3",
565
+ "js-yaml": "4.3.2",
566
566
  "jsonc-parser": "3.3.1",
567
567
  "just-bash": "3.1.0",
568
568
  "just-secrets": "0.0.2",