eve 0.41.0 → 0.43.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 (161) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/src/channel/channel-operations.d.ts +2 -2
  3. package/dist/src/channel/channel-operations.js +1 -1
  4. package/dist/src/channel/forwarded-principal.d.ts +4 -4
  5. package/dist/src/channel/session.d.ts +2 -2
  6. package/dist/src/channel/session.js +1 -1
  7. package/dist/src/cli/dev/tui/tool-presentation.js +1 -1
  8. package/dist/src/client/index.d.ts +1 -1
  9. package/dist/src/client/index.js +1 -1
  10. package/dist/src/compiler/extension-compatibility.d.ts +9 -7
  11. package/dist/src/compiler/extension-compatibility.js +1 -1
  12. package/dist/src/compiler/manifest.d.ts +3 -0
  13. package/dist/src/compiler/manifest.js +1 -1
  14. package/dist/src/compiler/normalize-manifest.js +1 -1
  15. package/dist/src/compiler/normalize-tool.js +1 -1
  16. package/dist/src/context/build-dynamic-tools.d.ts +5 -5
  17. package/dist/src/context/build-dynamic-tools.js +1 -1
  18. package/dist/src/context/dynamic-subagent-lifecycle.js +1 -1
  19. package/dist/src/context/dynamic-tool-lifecycle.d.ts +8 -27
  20. package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
  21. package/dist/src/context/keys.d.ts +10 -14
  22. package/dist/src/context/keys.js +1 -1
  23. package/dist/src/context/provider.d.ts +5 -1
  24. package/dist/src/context/run-step.d.ts +3 -2
  25. package/dist/src/context/run-step.js +1 -1
  26. package/dist/src/execution/agent-handle-dispatch.d.ts +3 -0
  27. package/dist/src/execution/agent-handle-dispatch.js +1 -1
  28. package/dist/src/execution/delegation-tool.d.ts +9 -1
  29. package/dist/src/execution/delegation-tool.js +1 -1
  30. package/dist/src/execution/dispatch-runtime-actions-shared.d.ts +14 -1
  31. package/dist/src/execution/dispatch-runtime-actions-shared.js +1 -1
  32. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  33. package/dist/src/execution/history-view.d.ts +9 -0
  34. package/dist/src/execution/history-view.js +1 -0
  35. package/dist/src/execution/next-driver-action.d.ts +33 -1
  36. package/dist/src/execution/node-step.d.ts +5 -2
  37. package/dist/src/execution/node-step.js +1 -1
  38. package/dist/src/execution/remote-agent-dispatch.d.ts +2 -0
  39. package/dist/src/execution/remote-agent-dispatch.js +1 -1
  40. package/dist/src/execution/session-callback-step.d.ts +2 -2
  41. package/dist/src/execution/session-callback-step.js +1 -1
  42. package/dist/src/execution/tasks/child/steps.js +1 -1
  43. package/dist/src/execution/tasks/child/update.d.ts +2 -2
  44. package/dist/src/execution/tasks/child/update.js +1 -1
  45. package/dist/src/execution/tasks/child/workflow.js +1 -1
  46. package/dist/src/execution/tasks/parent/agent-views.d.ts +2 -1
  47. package/dist/src/execution/tasks/parent/agent-views.js +1 -1
  48. package/dist/src/execution/tasks/parent/continuation-dispatch.d.ts +6 -0
  49. package/dist/src/execution/tasks/parent/continuation-dispatch.js +1 -1
  50. package/dist/src/execution/tasks/parent/control-shared.d.ts +1 -1
  51. package/dist/src/execution/tasks/parent/control-shared.js +1 -1
  52. package/dist/src/execution/tasks/parent/delegate.d.ts +20 -6
  53. package/dist/src/execution/tasks/parent/delegate.js +1 -1
  54. package/dist/src/execution/tasks/parent/dispatch-task-step.js +1 -1
  55. package/dist/src/execution/tasks/parent/dispatch.d.ts +13 -1
  56. package/dist/src/execution/tasks/parent/dispatch.js +1 -1
  57. package/dist/src/execution/tasks/parent/hitl-proxy-steps.js +1 -1
  58. package/dist/src/execution/tasks/parent/tool-execution.d.ts +46 -0
  59. package/dist/src/execution/tasks/parent/tool-execution.js +1 -0
  60. package/dist/src/execution/tool-auth.d.ts +12 -3
  61. package/dist/src/execution/tool-auth.js +1 -1
  62. package/dist/src/execution/turn-cancellation-control.js +1 -1
  63. package/dist/src/execution/turn-workflow.js +1 -1
  64. package/dist/src/execution/wire/session-inbox-wire.v1.js +1 -1
  65. package/dist/src/execution/workflow-steps.d.ts +1 -54
  66. package/dist/src/execution/workflow-steps.js +1 -1
  67. package/dist/src/harness/advertised-tools.js +1 -1
  68. package/dist/src/harness/background-tools.d.ts +37 -0
  69. package/dist/src/harness/background-tools.js +1 -0
  70. package/dist/src/harness/execute-tool.d.ts +10 -1
  71. package/dist/src/harness/handles/transitions.d.ts +36 -0
  72. package/dist/src/harness/handles/transitions.js +1 -1
  73. package/dist/src/harness/messages.d.ts +2 -0
  74. package/dist/src/harness/messages.js +1 -1
  75. package/dist/src/harness/tool-loop.js +2 -2
  76. package/dist/src/harness/tools.d.ts +5 -1
  77. package/dist/src/harness/tools.js +1 -1
  78. package/dist/src/harness/types.d.ts +13 -0
  79. package/dist/src/internal/application/package.js +1 -1
  80. package/dist/src/internal/authored-definition/schema-backed.js +1 -1
  81. package/dist/src/internal/authored-module-loader.js +1 -1
  82. package/dist/src/internal/workflow-bundle/dynamic-capability-transform-plugin.js +1 -1
  83. package/dist/src/internal/workflow-bundle/dynamic-tool-ast-references.d.ts +13 -0
  84. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.d.ts +7 -31
  85. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.js +4 -4
  86. package/dist/src/packages/eve-catalog/src/index.js +1 -1
  87. package/dist/src/public/channels/discord/hitl.d.ts +3 -3
  88. package/dist/src/public/channels/discord/hitl.js +1 -1
  89. package/dist/src/public/channels/eve.d.ts +7 -7
  90. package/dist/src/public/channels/eve.js +1 -1
  91. package/dist/src/public/channels/mcp.js +1 -1
  92. package/dist/src/public/channels/slack/hitl.d.ts +4 -5
  93. package/dist/src/public/channels/slack/hitl.js +2 -2
  94. package/dist/src/public/channels/slack/interactions.js +1 -1
  95. package/dist/src/public/channels/slack/session-operations.d.ts +2 -2
  96. package/dist/src/public/channels/slack/slackChannel.d.ts +4 -4
  97. package/dist/src/public/channels/teams/hitl.d.ts +2 -2
  98. package/dist/src/public/channels/teams/hitl.js +1 -1
  99. package/dist/src/public/channels/telegram/hitl.d.ts +4 -4
  100. package/dist/src/public/channels/telegram/hitl.js +1 -1
  101. package/dist/src/public/definitions/remote-agent.d.ts +7 -5
  102. package/dist/src/public/definitions/tool.d.ts +23 -0
  103. package/dist/src/public/definitions/tool.js +1 -1
  104. package/dist/src/public/tools/approval/approval-helpers.js +1 -1
  105. package/dist/src/public/tools/index.d.ts +1 -1
  106. package/dist/src/runtime/framework-tools/ask-question.js +1 -1
  107. package/dist/src/runtime/framework-tools/connection-search-dynamic.js +2 -2
  108. package/dist/src/runtime/framework-tools/subagent/local.d.ts +41 -0
  109. package/dist/src/runtime/framework-tools/subagent/local.js +1 -0
  110. package/dist/src/runtime/framework-tools/subagent/remote.d.ts +23 -0
  111. package/dist/src/runtime/framework-tools/subagent/remote.js +1 -0
  112. package/dist/src/runtime/framework-tools/tasks.d.ts +13 -12
  113. package/dist/src/runtime/framework-tools/tasks.js +1 -1
  114. package/dist/src/runtime/input/types.d.ts +19 -1
  115. package/dist/src/runtime/input/types.js +1 -1
  116. package/dist/src/runtime/prompt/compose.js +1 -1
  117. package/dist/src/runtime/resolve-tool.js +1 -1
  118. package/dist/src/runtime/session-callback-route.js +1 -1
  119. package/dist/src/setup/integrations/linq/management.d.ts +5 -0
  120. package/dist/src/setup/integrations/linq/management.js +1 -0
  121. package/dist/src/setup/integrations/linq/setup.d.ts +5 -1
  122. package/dist/src/setup/integrations/linq/setup.js +3 -3
  123. package/dist/src/setup/scaffold/create/add-to-project.js +1 -1
  124. package/dist/src/setup/scaffold/create/project.js +4 -10
  125. package/dist/src/setup/scaffold/create/web-template.d.ts +0 -1
  126. package/dist/src/setup/scaffold/create/web-template.js +1 -1
  127. package/dist/src/setup/scaffold/update/channels.d.ts +1 -1
  128. package/dist/src/setup/scaffold/update/channels.js +2 -2
  129. package/dist/src/setup/scaffold/update/package-json.d.ts +0 -2
  130. package/dist/src/setup/scaffold/update/package-json.js +1 -1
  131. package/dist/src/setup/scaffold/workspace-root.d.ts +0 -1
  132. package/dist/src/setup/scaffold/workspace-root.js +1 -1
  133. package/dist/src/shared/durable-dynamic-tool-callbacks.d.ts +34 -0
  134. package/dist/src/shared/durable-dynamic-tool-callbacks.js +1 -0
  135. package/dist/src/shared/dynamic-tool-definition.d.ts +2 -4
  136. package/dist/src/shared/empty-delivery.js +1 -1
  137. package/dist/src/shared/history-view.d.ts +22 -0
  138. package/dist/src/shared/history-view.js +1 -0
  139. package/dist/src/shared/json.d.ts +2 -0
  140. package/dist/src/shared/json.js +1 -1
  141. package/dist/src/shared/tool-definition.d.ts +4 -1
  142. package/dist/src/shared/tool-task.d.ts +107 -0
  143. package/dist/src/shared/tool-task.js +1 -0
  144. package/dist/src/tasks/delivery-context.d.ts +12 -0
  145. package/dist/src/tasks/delivery-context.js +17 -0
  146. package/dist/src/tasks/json.d.ts +25 -10
  147. package/dist/src/tasks/json.js +1 -1
  148. package/dist/src/tasks/session-index.d.ts +5 -3
  149. package/dist/src/tasks/session-index.js +1 -1
  150. package/dist/src/tasks/transitions.d.ts +40 -5
  151. package/dist/src/tasks/transitions.js +1 -1
  152. package/dist/src/tasks/types.d.ts +46 -4
  153. package/dist/src/tasks/types.js +1 -1
  154. package/docs/channels/custom.mdx +12 -0
  155. package/docs/channels/linq.mdx +1 -1
  156. package/docs/getting-started.mdx +36 -36
  157. package/docs/guides/auth-and-route-protection.md +6 -2
  158. package/docs/guides/dynamic-capabilities.md +12 -10
  159. package/docs/guides/remote-agents.md +10 -2
  160. package/docs/tools/overview.mdx +19 -0
  161. package/package.json +1 -1
@@ -0,0 +1,12 @@
1
+ import type { SessionStateMap } from "#harness/types.js";
2
+ export declare const TASK_DELIVERY_CONTEXT_LABEL = "[Task state]";
3
+ export declare const TASK_DELIVERY_PENDING_INSTRUCTION = "Background task control: incomplete cohort\nThis framework-authored instruction overrides any earlier instruction to report, summarize, acknowledge, or otherwise handle background results.\n\nThe accompanying [Task state] message is runtime-authored and lists tasks started by the same parent turn. At least one of those tasks is still pending, so the combined report is not ready.\n\nAction:\n- You may call tools only if the newly delivered task result requires immediate action.\n- Otherwise, take no action.\n\nDelivery:\n- Do not report completed tasks or partial results.\n- Do not provide progress, status, an acknowledgement, or a waiting message.\n- After any necessary tool calls, your entire final text response must be exactly <eve-empty-delivery/> and no other text.\n\nIncorrect: \"Two of three tasks have completed.\"\nIncorrect: \"Still waiting for the remaining task.\"\nCorrect: <eve-empty-delivery/>";
4
+ export declare const TASK_DELIVERY_SETTLED_INSTRUCTION = "Background task reporting\nThis turn was triggered by background task activity. The accompanying [Task state] message is runtime-authored and lists tasks started by the same parent turn, all settled, with every available terminal output. Do not reply with <eve-empty-delivery/>. Send one user-facing response that combines their useful results.";
5
+ /** Returns model context and cohort phase for tasks started by the same parent turn as this delivery. */
6
+ export declare function resolveTaskDeliveryContext(input: {
7
+ readonly state: SessionStateMap | undefined;
8
+ readonly taskDeliveryId: string;
9
+ }): {
10
+ readonly context: string;
11
+ readonly phase: "pending" | "settled";
12
+ } | undefined;
@@ -0,0 +1,17 @@
1
+ import{getSessionTaskIndex}from"#tasks/session-index.js";import{EMPTY_DELIVERY_SENTINEL}from"#shared/empty-delivery.js";const TASK_DELIVERY_CONTEXT_LABEL=`[Task state]`,TASK_DELIVERY_PENDING_INSTRUCTION=`Background task control: incomplete cohort
2
+ This framework-authored instruction overrides any earlier instruction to report, summarize, acknowledge, or otherwise handle background results.
3
+
4
+ The accompanying ${TASK_DELIVERY_CONTEXT_LABEL} message is runtime-authored and lists tasks started by the same parent turn. At least one of those tasks is still pending, so the combined report is not ready.
5
+
6
+ Action:
7
+ - You may call tools only if the newly delivered task result requires immediate action.
8
+ - Otherwise, take no action.
9
+
10
+ Delivery:
11
+ - Do not report completed tasks or partial results.
12
+ - Do not provide progress, status, an acknowledgement, or a waiting message.
13
+ - After any necessary tool calls, your entire final text response must be exactly ${EMPTY_DELIVERY_SENTINEL} and no other text.
14
+
15
+ Incorrect: "Two of three tasks have completed."
16
+ Incorrect: "Still waiting for the remaining task."
17
+ Correct: ${EMPTY_DELIVERY_SENTINEL}`,TASK_DELIVERY_SETTLED_INSTRUCTION=`Background task reporting\nThis turn was triggered by background task activity. The accompanying ${TASK_DELIVERY_CONTEXT_LABEL} message is runtime-authored and lists tasks started by the same parent turn, all settled, with every available terminal output. Do not reply with ${EMPTY_DELIVERY_SENTINEL}. Send one user-facing response that combines their useful results.`;function resolveTaskDeliveryContext(t){let n=getSessionTaskIndex(t.state),r=n.find(e=>t.taskDeliveryId.startsWith(`${e.taskId}:`));if(r===void 0)return;let i=n.filter(e=>e.createdByTurnId===r.createdByTurnId),a=i.every(e=>e.terminalView!==void 0),o=i.map(e=>({name:e.metadata.name,output:a?e.terminalView?.lastOutput:void 0,status:e.terminalView?.status??`pending`,taskId:e.taskId}));return{context:`${TASK_DELIVERY_CONTEXT_LABEL}\n${JSON.stringify({tasks:o})}`,phase:a?`settled`:`pending`}}export{TASK_DELIVERY_CONTEXT_LABEL,TASK_DELIVERY_PENDING_INSTRUCTION,TASK_DELIVERY_SETTLED_INSTRUCTION,resolveTaskDeliveryContext};
@@ -3,7 +3,7 @@ import type { JsonValue } from "#shared/json.js";
3
3
  import type { TaskView } from "#tasks/types.js";
4
4
  /** Model-visible task shape. Unlisted private fields are stripped by zod. */
5
5
  declare const taskViewJsonSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
6
- metadata: z.ZodObject<{
6
+ metadata: z.ZodUnion<readonly [z.ZodObject<{
7
7
  agentId: z.ZodString;
8
8
  kind: z.ZodLiteral<"subagent">;
9
9
  mode: z.ZodEnum<{
@@ -11,11 +11,14 @@ declare const taskViewJsonSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
11
11
  remote: "remote";
12
12
  }>;
13
13
  name: z.ZodString;
14
- }, z.core.$strip>;
14
+ }, z.core.$strip>, z.ZodObject<{
15
+ kind: z.ZodString;
16
+ name: z.ZodString;
17
+ }, z.core.$strip>]>;
15
18
  taskId: z.ZodString;
16
19
  status: z.ZodLiteral<"working">;
17
20
  }, z.core.$strip>, z.ZodObject<{
18
- metadata: z.ZodObject<{
21
+ metadata: z.ZodUnion<readonly [z.ZodObject<{
19
22
  agentId: z.ZodString;
20
23
  kind: z.ZodLiteral<"subagent">;
21
24
  mode: z.ZodEnum<{
@@ -23,12 +26,15 @@ declare const taskViewJsonSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
23
26
  remote: "remote";
24
27
  }>;
25
28
  name: z.ZodString;
26
- }, z.core.$strip>;
29
+ }, z.core.$strip>, z.ZodObject<{
30
+ kind: z.ZodString;
31
+ name: z.ZodString;
32
+ }, z.core.$strip>]>;
27
33
  taskId: z.ZodString;
28
34
  inputRequests: z.ZodReadonly<z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>>;
29
35
  status: z.ZodLiteral<"input_required">;
30
36
  }, z.core.$strip>, z.ZodObject<{
31
- metadata: z.ZodObject<{
37
+ metadata: z.ZodUnion<readonly [z.ZodObject<{
32
38
  agentId: z.ZodString;
33
39
  kind: z.ZodLiteral<"subagent">;
34
40
  mode: z.ZodEnum<{
@@ -36,7 +42,10 @@ declare const taskViewJsonSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
36
42
  remote: "remote";
37
43
  }>;
38
44
  name: z.ZodString;
39
- }, z.core.$strip>;
45
+ }, z.core.$strip>, z.ZodObject<{
46
+ kind: z.ZodString;
47
+ name: z.ZodString;
48
+ }, z.core.$strip>]>;
40
49
  taskId: z.ZodString;
41
50
  lastOutput: z.ZodObject<{
42
51
  data: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
@@ -44,7 +53,7 @@ declare const taskViewJsonSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
44
53
  }, z.core.$strip>;
45
54
  status: z.ZodLiteral<"completed">;
46
55
  }, z.core.$strip>, z.ZodObject<{
47
- metadata: z.ZodObject<{
56
+ metadata: z.ZodUnion<readonly [z.ZodObject<{
48
57
  agentId: z.ZodString;
49
58
  kind: z.ZodLiteral<"subagent">;
50
59
  mode: z.ZodEnum<{
@@ -52,7 +61,10 @@ declare const taskViewJsonSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
52
61
  remote: "remote";
53
62
  }>;
54
63
  name: z.ZodString;
55
- }, z.core.$strip>;
64
+ }, z.core.$strip>, z.ZodObject<{
65
+ kind: z.ZodString;
66
+ name: z.ZodString;
67
+ }, z.core.$strip>]>;
56
68
  taskId: z.ZodString;
57
69
  lastOutput: z.ZodObject<{
58
70
  data: z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>;
@@ -60,7 +72,7 @@ declare const taskViewJsonSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
60
72
  }, z.core.$strip>;
61
73
  status: z.ZodLiteral<"failed">;
62
74
  }, z.core.$strip>, z.ZodObject<{
63
- metadata: z.ZodObject<{
75
+ metadata: z.ZodUnion<readonly [z.ZodObject<{
64
76
  agentId: z.ZodString;
65
77
  kind: z.ZodLiteral<"subagent">;
66
78
  mode: z.ZodEnum<{
@@ -68,7 +80,10 @@ declare const taskViewJsonSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
68
80
  remote: "remote";
69
81
  }>;
70
82
  name: z.ZodString;
71
- }, z.core.$strip>;
83
+ }, z.core.$strip>, z.ZodObject<{
84
+ kind: z.ZodString;
85
+ name: z.ZodString;
86
+ }, z.core.$strip>]>;
72
87
  taskId: z.ZodString;
73
88
  status: z.ZodLiteral<"cancelled">;
74
89
  }, z.core.$strip>], "status">;
@@ -1 +1 @@
1
- import{z}from"#compiled/zod/index.js";import{jsonValueSchema}from"#shared/json-schemas.js";const taskMetadataJsonSchema=z.object({agentId:z.string(),kind:z.literal(`subagent`),mode:z.enum([`local`,`remote`]),name:z.string()}),taskOutputJsonSchema=z.object({data:jsonValueSchema,type:z.enum([`result`,`error`])}),taskViewJsonBaseShape={metadata:taskMetadataJsonSchema,taskId:z.string()},taskViewJsonSchema=z.discriminatedUnion(`status`,[z.object({...taskViewJsonBaseShape,status:z.literal(`working`)}),z.object({...taskViewJsonBaseShape,inputRequests:z.array(jsonValueSchema).readonly(),status:z.literal(`input_required`)}),z.object({...taskViewJsonBaseShape,lastOutput:taskOutputJsonSchema.extend({type:z.literal(`result`)}),status:z.literal(`completed`)}),z.object({...taskViewJsonBaseShape,lastOutput:taskOutputJsonSchema.extend({type:z.literal(`error`)}),status:z.literal(`failed`)}),z.object({...taskViewJsonBaseShape,status:z.literal(`cancelled`)})]);function taskViewToJson(e){return taskViewJsonSchema.parse(e)}function taskViewsToJson(e){return{tasks:e.map(e=>taskViewToJson(e))}}export{taskViewToJson,taskViewsToJson};
1
+ import{z}from"#compiled/zod/index.js";import{jsonValueSchema}from"#shared/json-schemas.js";import{readSubagentTaskMetadata}from"#tasks/types.js";const taskMetadataJsonSchema=z.union([z.object({agentId:z.string(),kind:z.literal(`subagent`),mode:z.enum([`local`,`remote`]),name:z.string()}),z.object({kind:z.string(),name:z.string()})]),taskOutputJsonSchema=z.object({data:jsonValueSchema,type:z.enum([`result`,`error`])}),taskViewJsonBaseShape={metadata:taskMetadataJsonSchema,taskId:z.string()},taskViewJsonSchema=z.discriminatedUnion(`status`,[z.object({...taskViewJsonBaseShape,status:z.literal(`working`)}),z.object({...taskViewJsonBaseShape,inputRequests:z.array(jsonValueSchema).readonly(),status:z.literal(`input_required`)}),z.object({...taskViewJsonBaseShape,lastOutput:taskOutputJsonSchema.extend({type:z.literal(`result`)}),status:z.literal(`completed`)}),z.object({...taskViewJsonBaseShape,lastOutput:taskOutputJsonSchema.extend({type:z.literal(`error`)}),status:z.literal(`failed`)}),z.object({...taskViewJsonBaseShape,status:z.literal(`cancelled`)})]);function taskViewToJson(e){let t=readSubagentTaskMetadata(e),n=t===void 0?e.metadata:{agentId:t.agentId,kind:t.kind,mode:t.mode,name:t.name};return taskViewJsonSchema.parse({...e,metadata:n})}function taskViewsToJson(e){return{tasks:e.map(e=>taskViewToJson(e))}}export{taskViewToJson,taskViewsToJson};
@@ -1,5 +1,6 @@
1
1
  import type { HarnessSession, SessionStateMap } from "#harness/types.js";
2
- import type { TaskMetadata, TaskView } from "#tasks/types.js";
2
+ import type { TaskExecutorBinding } from "#shared/tool-task.js";
3
+ import { type DurableTaskMetadata, type TaskView } from "#tasks/types.js";
3
4
  /**
4
5
  * Session-state key for the parent's live-task index.
5
6
  *
@@ -27,8 +28,9 @@ export interface SessionTaskIndexEntry {
27
28
  readonly taskInboxToken: string;
28
29
  readonly createdByStepIndex?: number;
29
30
  readonly createdByTurnId: string;
30
- readonly metadata: TaskMetadata;
31
- readonly operationId: string;
31
+ readonly executor?: TaskExecutorBinding;
32
+ readonly metadata: DurableTaskMetadata;
33
+ readonly operationId?: string;
32
34
  }
33
35
  /**
34
36
  * Reads and validates the task index from session state.
@@ -1 +1 @@
1
- import{z}from"#compiled/zod/index.js";const SESSION_TASKS_STATE_KEY=`eve.tasks`,taskMetadataSchema=z.strictObject({agentId:z.string().min(1),kind:z.literal(`subagent`),mode:z.enum([`local`,`remote`]),name:z.string().min(1)}),taskViewBaseShape={executor:z.strictObject({childSessionId:z.string().min(1).optional(),childTurnId:z.string().min(1).optional(),lifecycle:z.enum([`parked`,`terminal`]).optional()}).optional(),metadata:taskMetadataSchema,taskId:z.string().min(1),usage:z.strictObject({cacheReadTokens:z.number().nonnegative(),cacheWriteTokens:z.number().nonnegative(),inputTokens:z.number().nonnegative(),outputTokens:z.number().nonnegative()}).optional()},taskViewSchema=z.discriminatedUnion(`status`,[z.strictObject({...taskViewBaseShape,lastOutput:z.strictObject({data:z.custom(),type:z.literal(`result`)}),status:z.literal(`completed`)}),z.strictObject({...taskViewBaseShape,lastOutput:z.strictObject({data:z.custom(),type:z.literal(`error`)}),status:z.literal(`failed`)}),z.strictObject({...taskViewBaseShape,status:z.literal(`cancelled`)})]),sessionTaskIndexEntrySchema=z.strictObject({taskInboxToken:z.string().min(1),createdByStepIndex:z.number().int().nonnegative().optional(),createdByTurnId:z.string().min(1),metadata:taskMetadataSchema,operationId:z.string().min(1),taskId:z.string().min(1),taskRunId:z.string().min(1),terminalView:taskViewSchema.optional()}),sessionTaskIndexSchema=z.strictObject({tasks:z.array(sessionTaskIndexEntrySchema)}).refine(e=>new Set(e.tasks.map(e=>e.taskId)).size===e.tasks.length,{message:`Task ids must be unique.`}).refine(e=>e.tasks.every(e=>e.terminalView===void 0||e.terminalView.taskId===e.taskId&&sameTaskMetadata(e.terminalView.metadata,e.metadata)),{message:`Cached terminal views must match their task index entry.`});function getSessionTaskIndex(e){let n=e?.[SESSION_TASKS_STATE_KEY];if(n===void 0)return[];let r=sessionTaskIndexSchema.safeParse(n);if(!r.success)throw Error(`Corrupt task index under session state key "${SESSION_TASKS_STATE_KEY}": ${r.error.message}`);return r.data.tasks}function cacheTerminalTaskView(e,n){if(!isValidTerminalView(n))throw Error(`Cannot cache invalid terminal task "${n.taskId}".`);let r=getSessionTaskIndex(e),i=r.findIndex(e=>e.taskId===n.taskId);if(i<0)return e;if(!sameTaskMetadata(r[i].metadata,n.metadata))throw Error(`Task view metadata does not match index entry "${n.taskId}".`);let a=[...r];return a[i]={...a[i],terminalView:n},{...e,[SESSION_TASKS_STATE_KEY]:{tasks:a}}}function isValidTerminalView(e){if(e.inputRequests!==void 0)return!1;switch(e.status){case`completed`:return e.lastOutput?.type===`result`;case`failed`:return e.lastOutput?.type===`error`;case`cancelled`:return e.lastOutput===void 0;case`working`:return!1}}function sameTaskMetadata(e,t){return e.agentId===t.agentId&&e.kind===t.kind&&e.mode===t.mode&&e.name===t.name}function findSessionTaskEntry(e,t){return getSessionTaskIndex(e).find(e=>e.taskId===t)}function recordSessionTask(e,n){let r=[...getSessionTaskIndex(e.state).filter(e=>e.taskId!==n.taskId),n];return{...e,state:{...e.state,[SESSION_TASKS_STATE_KEY]:{tasks:r}}}}export{SESSION_TASKS_STATE_KEY,cacheTerminalTaskView,findSessionTaskEntry,getSessionTaskIndex,recordSessionTask};
1
+ import{z}from"#compiled/zod/index.js";import{sameTaskMetadata}from"#tasks/types.js";const SESSION_TASKS_STATE_KEY=`eve.tasks`,taskMetadataSchema=z.union([z.strictObject({agentId:z.string().min(1),kind:z.literal(`subagent`),mode:z.enum([`local`,`remote`]),name:z.string().min(1)}),z.strictObject({kind:z.string().min(1),name:z.string().min(1)})]),taskViewBaseShape={executor:z.strictObject({binding:z.strictObject({data:z.record(z.string(),z.custom()),kind:z.string().min(1)}).optional(),childSessionId:z.string().min(1).optional(),childTurnId:z.string().min(1).optional(),lifecycle:z.enum([`parked`,`terminal`]).optional()}).optional(),metadata:taskMetadataSchema,taskId:z.string().min(1),usage:z.strictObject({cacheReadTokens:z.number().nonnegative(),cacheWriteTokens:z.number().nonnegative(),inputTokens:z.number().nonnegative(),outputTokens:z.number().nonnegative()}).optional()},taskViewSchema=z.discriminatedUnion(`status`,[z.strictObject({...taskViewBaseShape,lastOutput:z.strictObject({data:z.custom(),type:z.literal(`result`)}),status:z.literal(`completed`)}),z.strictObject({...taskViewBaseShape,lastOutput:z.strictObject({data:z.custom(),type:z.literal(`error`)}),status:z.literal(`failed`)}),z.strictObject({...taskViewBaseShape,status:z.literal(`cancelled`)})]),sessionTaskIndexEntrySchema=z.strictObject({taskInboxToken:z.string().min(1),createdByStepIndex:z.number().int().nonnegative().optional(),createdByTurnId:z.string().min(1),executor:z.strictObject({data:z.record(z.string(),z.custom()),kind:z.string().min(1)}).optional(),metadata:taskMetadataSchema,operationId:z.string().min(1).optional(),taskId:z.string().min(1),taskRunId:z.string().min(1),terminalView:taskViewSchema.optional()}),sessionTaskIndexSchema=z.strictObject({tasks:z.array(sessionTaskIndexEntrySchema)}).refine(e=>new Set(e.tasks.map(e=>e.taskId)).size===e.tasks.length,{message:`Task ids must be unique.`}).refine(e=>e.tasks.every(e=>e.terminalView===void 0||e.terminalView.taskId===e.taskId&&sameTaskMetadata(e.terminalView.metadata,e.metadata)),{message:`Cached terminal views must match their task index entry.`});function getSessionTaskIndex(e){let t=e?.[SESSION_TASKS_STATE_KEY];if(t===void 0)return[];let n=sessionTaskIndexSchema.safeParse(t);if(!n.success)throw Error(`Corrupt task index under session state key "${SESSION_TASKS_STATE_KEY}": ${n.error.message}`);return n.data.tasks}function cacheTerminalTaskView(e,n){if(!isValidTerminalView(n))throw Error(`Cannot cache invalid terminal task "${n.taskId}".`);let r=getSessionTaskIndex(e),i=r.findIndex(e=>e.taskId===n.taskId);if(i<0)return e;if(!sameTaskMetadata(r[i].metadata,n.metadata))throw Error(`Task view metadata does not match index entry "${n.taskId}".`);let a=[...r];return a[i]={...a[i],terminalView:n},{...e,[SESSION_TASKS_STATE_KEY]:{tasks:a}}}function isValidTerminalView(e){if(e.inputRequests!==void 0)return!1;switch(e.status){case`completed`:return e.lastOutput?.type===`result`;case`failed`:return e.lastOutput?.type===`error`;case`cancelled`:return e.lastOutput===void 0;case`working`:return!1}}function findSessionTaskEntry(e,t){return getSessionTaskIndex(e).find(e=>e.taskId===t)}function recordSessionTask(e,t){let n=[...getSessionTaskIndex(e.state).filter(e=>e.taskId!==t.taskId),t];return{...e,state:{...e.state,[SESSION_TASKS_STATE_KEY]:{tasks:n}}}}export{SESSION_TASKS_STATE_KEY,cacheTerminalTaskView,findSessionTaskEntry,getSessionTaskIndex,recordSessionTask};
@@ -1,23 +1,58 @@
1
1
  import type { TaskCommand, TaskView } from "#tasks/types.js";
2
2
  /**
3
- * Outcome of applying one command to a task view.
3
+ * Action taken on one command applied to a task view.
4
4
  *
5
- * - `accepted`: the state changed; the new view must be appended.
5
+ * - `accepted`: the view changed; the new view must be appended.
6
6
  * - `noop`: the command is recognized and benign (idempotent cancel,
7
7
  * stale answer); nothing changed and nothing is appended.
8
8
  * - `rejected`: the command is invalid for the current status; the
9
9
  * reason is diagnostic only.
10
10
  */
11
11
  type TaskTransitionResult = {
12
- readonly outcome: "accepted";
12
+ readonly action: "accepted";
13
13
  readonly view: TaskView;
14
14
  } | {
15
- readonly outcome: "noop";
15
+ readonly action: "noop";
16
16
  readonly view: TaskView;
17
17
  } | {
18
- readonly outcome: "rejected";
18
+ readonly action: "rejected";
19
19
  readonly view: TaskView;
20
20
  readonly reason: string;
21
21
  };
22
+ /**
23
+ * Pure transition function for the task lifecycle:
24
+ *
25
+ * ```text
26
+ * working <-> input_required
27
+ * | |
28
+ * +-----> completed
29
+ * +-----> failed
30
+ * +-----> cancelled
31
+ * ```
32
+ *
33
+ * Terminal states are final: a late child result can never revive a
34
+ * cancelled task, and repeated cancellation is idempotent. The durable
35
+ * task run is the only caller that persists accepted views, which is
36
+ * what serializes competing completion, cancellation, and input
37
+ * transitions.
38
+ *
39
+ * Orthogonal to the status, the view carries private executor state
40
+ * (`TaskExecutorState`) with its own lifecycle. These commands move
41
+ * executor fields, never the status above:
42
+ *
43
+ * ```text
44
+ * binding: unset --bind-------> bound (replay noop, conflict rejected)
45
+ * address: unset --start-turn-> session pinned (turn id updated per delivery)
46
+ * verdict: unset --complete/fail/cancel/settle-executor-> parked | terminal
47
+ * ```
48
+ *
49
+ * - `bind` writes the executor binding exactly once: replaying the
50
+ * same binding is a noop, a different one is rejected.
51
+ * - `start-turn` records the child session/turn ids used to address
52
+ * the executor, rejecting a child-session mismatch.
53
+ * - Terminal commands and a late `settle-executor` record the child's
54
+ * lifecycle verdict: `parked` means the child session survived and
55
+ * can take another delivery, `terminal` means it ended with the turn.
56
+ */
22
57
  export declare function applyTaskTransition(view: TaskView, command: TaskCommand): TaskTransitionResult;
23
58
  export {};
@@ -1 +1 @@
1
- import{isTerminalTaskStatus,readTaskInputRequestId}from"#tasks/types.js";function terminalView(e,t,n){let r={executor:t.lifecycle===void 0?e.executor:{...e.executor,lifecycle:t.lifecycle},metadata:e.metadata,taskId:e.taskId};switch(t.usage!==void 0&&(r.usage=t.usage),n.status){case`completed`:return{...r,lastOutput:n.lastOutput,status:`completed`};case`failed`:return{...r,lastOutput:n.lastOutput,status:`failed`};case`cancelled`:return{...r,status:`cancelled`}}}function applyTaskTransition(n,r){if(isTerminalTaskStatus(n.status)){if(r.kind===`settle-executor`){let e={...n.executor,lifecycle:`terminal`};if(sameUsage(n.usage,r.usage)&&n.executor?.lifecycle===`terminal`)return{outcome:`noop`,view:n};let t={...n,executor:e};return{outcome:`accepted`,view:r.usage===void 0?t:{...t,usage:r.usage}}}return r.kind===`cancel`&&n.status===`cancelled`?{outcome:`noop`,view:n}:{outcome:`rejected`,reason:`Task "${n.taskId}" is already ${n.status}; "${r.kind}" cannot change a terminal task.`,view:n}}switch(r.kind){case`complete`:return{outcome:`accepted`,view:terminalView(n,r,{lastOutput:{data:r.data,type:`result`},status:`completed`})};case`fail`:case`reject-dispatch`:return{outcome:`accepted`,view:terminalView(n,r,{lastOutput:{data:r.data,type:`error`},status:`failed`})};case`cancel`:return{outcome:`accepted`,view:terminalView(n,r,{status:`cancelled`})};case`settle-executor`:return{outcome:`rejected`,reason:`Task "${n.taskId}" is not terminal; usage settles with its terminal command.`,view:n};case`require-authorization`:{let e={blockedOn:`authorization`,requestId:r.requestId};return n.status===`input_required`?n.inputRequests.some(e=>readTaskInputRequestId(e)===r.requestId)?{outcome:`noop`,view:n}:{outcome:`accepted`,view:{...n,inputRequests:[...n.inputRequests,e]}}:{outcome:`accepted`,view:{inputRequests:[e],executor:n.executor,metadata:n.metadata,status:`input_required`,taskId:n.taskId}}}case`require-input`:return isValidInputRequestBatch(r.inputRequests)?{outcome:`accepted`,view:{inputRequests:r.inputRequests,executor:n.executor,metadata:n.metadata,status:`input_required`,taskId:n.taskId}}:{outcome:`rejected`,reason:`Task "${n.taskId}" received an invalid input request batch.`,view:n};case`ready`:return{outcome:`accepted`,view:n};case`answered`:{if(n.status!==`input_required`)return{outcome:`noop`,view:n};let e=new Set(r.requestIds),i=n.inputRequests,a=i.filter(n=>{let r=readTaskInputRequestId(n);return r===void 0||!e.has(r)});return a.length===i.length?{outcome:`noop`,view:n}:a.length>0?{outcome:`accepted`,view:{inputRequests:a,executor:n.executor,metadata:n.metadata,status:`input_required`,taskId:n.taskId}}:{outcome:`accepted`,view:{executor:n.executor,metadata:n.metadata,status:`working`,taskId:n.taskId}}}case`start-turn`:return r.taskId===n.taskId?n.executor?.childSessionId!==void 0&&n.executor.childSessionId!==r.childSessionId?{outcome:`rejected`,reason:`Task child session "${r.childSessionId}" does not match "${n.executor.childSessionId}".`,view:n}:n.executor?.childSessionId===r.childSessionId&&n.executor.childTurnId===r.childTurnId?{outcome:`noop`,view:n}:{outcome:`accepted`,view:{...n,executor:{...n.executor,childSessionId:r.childSessionId,childTurnId:r.childTurnId}}}:{outcome:`rejected`,reason:`Task turn identity "${r.taskId}" does not match "${n.taskId}".`,view:n}}}function isValidInputRequestBatch(e){if(e.length===0)return!1;let n=e.map(readTaskInputRequestId);return n.every(e=>e!==void 0&&e.length>0)&&new Set(n).size===n.length}function sameUsage(e,t){return e===void 0||t===void 0?e===t:e?.cacheReadTokens===t.cacheReadTokens&&e.cacheWriteTokens===t.cacheWriteTokens&&e.inputTokens===t.inputTokens&&e.outputTokens===t.outputTokens}export{applyTaskTransition};
1
+ import{jsonValuesEqual}from"#shared/json.js";import{isTerminalTaskStatus,readTaskInputRequestId}from"#tasks/types.js";function terminalView(e,t,n){let r={executor:t.lifecycle===void 0?e.executor:{...e.executor,lifecycle:t.lifecycle},metadata:e.metadata,taskId:e.taskId};switch(t.usage!==void 0&&(r.usage=t.usage),n.status){case`completed`:return{...r,lastOutput:n.lastOutput,status:`completed`};case`failed`:return{...r,lastOutput:n.lastOutput,status:`failed`};case`cancelled`:return{...r,status:`cancelled`}}}function applyTaskTransition(r,i){if(i.kind===`bind`){let t=r.executor?.binding;return t!==void 0&&t.kind===i.executor.kind&&jsonValuesEqual(t.data,i.executor.data)?{action:`noop`,view:r}:t===void 0?{action:`accepted`,view:{...r,executor:{...r.executor,binding:i.executor}}}:{action:`rejected`,reason:`Task "${r.taskId}" already has an executor binding.`,view:r}}if(isTerminalTaskStatus(r.status)){if(i.kind===`settle-executor`){let e={...r.executor,lifecycle:`terminal`};if(sameUsage(r.usage,i.usage)&&r.executor?.lifecycle===`terminal`)return{action:`noop`,view:r};let t={...r,executor:e};return{action:`accepted`,view:i.usage===void 0?t:{...t,usage:i.usage}}}return i.kind===`cancel`&&r.status===`cancelled`?{action:`noop`,view:r}:{action:`rejected`,reason:`Task "${r.taskId}" is already ${r.status}; "${i.kind}" cannot change a terminal task.`,view:r}}switch(i.kind){case`complete`:return{action:`accepted`,view:terminalView(r,i,{lastOutput:{data:i.data,type:`result`},status:`completed`})};case`fail`:case`reject-dispatch`:return{action:`accepted`,view:terminalView(r,i,{lastOutput:{data:i.data,type:`error`},status:`failed`})};case`cancel`:return{action:`accepted`,view:terminalView(r,i,{status:`cancelled`})};case`settle-executor`:return{action:`rejected`,reason:`Task "${r.taskId}" is not terminal; usage settles with its terminal command.`,view:r};case`require-authorization`:{let e={blockedOn:`authorization`,requestId:i.requestId};return r.status===`input_required`?r.inputRequests.some(e=>readTaskInputRequestId(e)===i.requestId)?{action:`noop`,view:r}:{action:`accepted`,view:{...r,inputRequests:[...r.inputRequests,e]}}:{action:`accepted`,view:{inputRequests:[e],executor:r.executor,metadata:r.metadata,status:`input_required`,taskId:r.taskId}}}case`require-input`:return isValidInputRequestBatch(i.inputRequests)?{action:`accepted`,view:{inputRequests:i.inputRequests,executor:r.executor,metadata:r.metadata,status:`input_required`,taskId:r.taskId}}:{action:`rejected`,reason:`Task "${r.taskId}" received an invalid input request batch.`,view:r};case`ready`:return{action:`accepted`,view:r};case`answered`:{if(r.status!==`input_required`)return{action:`noop`,view:r};let e=new Set(i.requestIds),t=r.inputRequests,a=t.filter(t=>{let r=readTaskInputRequestId(t);return r===void 0||!e.has(r)});return a.length===t.length?{action:`noop`,view:r}:a.length>0?{action:`accepted`,view:{inputRequests:a,executor:r.executor,metadata:r.metadata,status:`input_required`,taskId:r.taskId}}:{action:`accepted`,view:{executor:r.executor,metadata:r.metadata,status:`working`,taskId:r.taskId}}}case`start-turn`:return i.taskId===r.taskId?r.executor?.childSessionId!==void 0&&r.executor.childSessionId!==i.childSessionId?{action:`rejected`,reason:`Task child session "${i.childSessionId}" does not match "${r.executor.childSessionId}".`,view:r}:r.executor?.childSessionId===i.childSessionId&&r.executor.childTurnId===i.childTurnId?{action:`noop`,view:r}:{action:`accepted`,view:{...r,executor:{...r.executor,childSessionId:i.childSessionId,childTurnId:i.childTurnId}}}:{action:`rejected`,reason:`Task turn identity "${i.taskId}" does not match "${r.taskId}".`,view:r}}}function isValidInputRequestBatch(e){if(e.length===0)return!1;let t=e.map(readTaskInputRequestId);return t.every(e=>e!==void 0&&e.length>0)&&new Set(t).size===t.length}function sameUsage(e,t){return e===void 0||t===void 0?e===t:e?.cacheReadTokens===t.cacheReadTokens&&e.cacheWriteTokens===t.cacheWriteTokens&&e.inputTokens===t.inputTokens&&e.outputTokens===t.outputTokens}export{applyTaskTransition};
@@ -1,4 +1,6 @@
1
- import type { JsonValue } from "#shared/json.js";
1
+ import type { AgentAddress, AgentIdentity } from "#harness/handles/store.js";
2
+ import { type JsonValue } from "#shared/json.js";
3
+ import type { TaskExecutorBinding } from "#shared/tool-task.js";
2
4
  import type { SubagentAuthorizationEvent } from "#channel/types.js";
3
5
  /**
4
6
  * Task lifecycle contract for `experimental.tasks`.
@@ -33,10 +35,45 @@ export interface TaskMetadata {
33
35
  /** Authored subagent name the parent dispatched. */
34
36
  readonly name: string;
35
37
  }
38
+ export type SubagentTaskMetadata = TaskMetadata;
39
+ /** Executor-neutral metadata retained by a non-subagent durable task. */
40
+ export interface BackgroundTaskMetadata {
41
+ readonly kind: string;
42
+ readonly name: string;
43
+ }
44
+ export type DurableTaskMetadata = BackgroundTaskMetadata | TaskMetadata;
45
+ export declare function createSubagentTaskMetadata(metadata: Omit<SubagentTaskMetadata, "kind" | "name"> & {
46
+ readonly name: string;
47
+ }): SubagentTaskMetadata;
48
+ export declare function readSubagentTaskMetadata(task: {
49
+ readonly executor?: TaskExecutorBinding | TaskExecutorState;
50
+ readonly metadata: DurableTaskMetadata;
51
+ }): SubagentTaskMetadata | undefined;
52
+ /**
53
+ * Durable subagent executor binding payload: the child's stable identity and
54
+ * private delivery address, copied from the parent's agent handle store at
55
+ * delegation time. Every field derives deterministically from the
56
+ * originating call (operation-hashed id, dispatch-chosen continuation
57
+ * token), so a replayed delegation produces a deep-equal binding — the
58
+ * equality the task run's `bind` replay check compares.
59
+ */
60
+ export interface SubagentExecutorData {
61
+ readonly address: AgentAddress;
62
+ readonly identity: AgentIdentity;
63
+ }
64
+ export declare function createSubagentExecutorBinding(executor: SubagentExecutorData): TaskExecutorBinding;
65
+ export declare function readSubagentExecutor(executor: TaskExecutorBinding | TaskExecutorState | undefined): SubagentExecutorData | undefined;
66
+ export declare function sameTaskMetadata(left: DurableTaskMetadata, right: DurableTaskMetadata): boolean;
36
67
  /** Private executor state retained for cancellation and address reconciliation. */
37
68
  export interface TaskExecutorState {
69
+ /** Write-once executor address recorded by the `bind` command. */
70
+ readonly binding?: TaskExecutorBinding;
38
71
  readonly childSessionId?: string;
39
72
  readonly childTurnId?: string;
73
+ /**
74
+ * Child-session verdict from the last settled turn: `parked` survived
75
+ * and can take another delivery, `terminal` ended with the turn.
76
+ */
40
77
  readonly lifecycle?: "parked" | "terminal";
41
78
  }
42
79
  /**
@@ -92,7 +129,7 @@ export declare function readTaskInputRequestId(request: TaskInputRequest): strin
92
129
  /** Fields shared by every durable task view. */
93
130
  interface TaskViewBase {
94
131
  readonly taskId: string;
95
- readonly metadata: TaskMetadata;
132
+ readonly metadata: DurableTaskMetadata;
96
133
  /** Private executor state; deliberately excluded from model-visible JSON. */
97
134
  readonly executor?: TaskExecutorState;
98
135
  /**
@@ -140,6 +177,9 @@ export type TaskView = TaskViewBase & ({
140
177
  });
141
178
  /** Commands accepted by the durable task run's transition function. */
142
179
  export type TaskCommand = {
180
+ readonly executor: TaskExecutorBinding;
181
+ readonly kind: "bind";
182
+ } | {
143
183
  readonly kind: "complete";
144
184
  readonly data: JsonValue;
145
185
  readonly lifecycle?: "parked" | "terminal";
@@ -260,8 +300,10 @@ export interface TaskInboundTurnStarted {
260
300
  /** Intermediate progress sent by a running child to its parent agent. */
261
301
  export interface TaskInboundUpdate {
262
302
  readonly callId: string;
263
- readonly childStepIndex: number;
264
- readonly childTurnId: string;
303
+ /** Executor-neutral monotonic index within {@link updateEpoch}. */
304
+ readonly updateIndex: number;
305
+ /** Executor-neutral epoch id used with {@link updateIndex} for update dedupe. */
306
+ readonly updateEpoch: string;
265
307
  readonly kind: "task-update";
266
308
  readonly message: string;
267
309
  }
@@ -1 +1 @@
1
- function readTaskUsage(e){if(typeof e!=`object`||!e||Array.isArray(e))return;let t=readUsageAxis(e,`cacheReadTokens`),n=readUsageAxis(e,`cacheWriteTokens`),r=readUsageAxis(e,`inputTokens`),i=readUsageAxis(e,`outputTokens`);if(!(t===void 0||n===void 0||r===void 0||i===void 0))return{cacheReadTokens:t,cacheWriteTokens:n,inputTokens:r,outputTokens:i}}function readUsageAxis(e,t){let n=Reflect.get(e,t);return typeof n==`number`&&Number.isFinite(n)&&n>=0?n:void 0}const TASK_AUTHORIZATION_REQUEST_ID_PREFIX=`task:authorization`;function taskAuthorizationRequestId(t){return t.type===`approval.candidate`||t.type===`approval.settled`?`${TASK_AUTHORIZATION_REQUEST_ID_PREFIX}:${t.data.requestId}`:`${TASK_AUTHORIZATION_REQUEST_ID_PREFIX}:${t.data.attemptId??t.data.name}`}function readTaskInputRequestId(e){if(typeof e!=`object`||!e||Array.isArray(e))return;let t=Reflect.get(e,`requestId`);return typeof t==`string`?t:void 0}const TASK_VIEW_STREAM_NAMESPACE=`eve.task`;function isTerminalTaskStatus(e){return e===`completed`||e===`failed`||e===`cancelled`}function isReadyTaskStatus(e){return e===`input_required`||isTerminalTaskStatus(e)}export{TASK_VIEW_STREAM_NAMESPACE,isReadyTaskStatus,isTerminalTaskStatus,readTaskInputRequestId,readTaskUsage,taskAuthorizationRequestId};
1
+ import{isJsonObjectValue}from"#shared/json.js";function createSubagentTaskMetadata(e){return{...e,kind:`subagent`}}function readSubagentTaskMetadata(e){if(e.metadata.kind===`subagent`&&`agentId`in e.metadata)return e.metadata;let t=readSubagentExecutor(e.executor);if(t!==void 0)return{agentId:t.identity.id,kind:`subagent`,mode:t.address.kind===`agent/remote`?`remote`:`local`,name:t.identity.name}}function createSubagentExecutorBinding(e){let{address:t,identity:n}=e;return{data:{address:{...t},identity:{id:n.id,name:n.name,nodeId:n.nodeId}},kind:`subagent`}}function readSubagentExecutor(e){let t=e!==void 0&&`kind`in e?e:e?.binding;if(t?.kind!==`subagent`)return;let n=readAgentIdentity(t.data.identity),r=readAgentAddress(t.data.address);return n===void 0||r===void 0?void 0:{address:r,identity:n}}function readAgentIdentity(t){if(t===void 0||!isJsonObjectValue(t))return;let{id:n,name:r,nodeId:i}=t;return typeof n==`string`&&typeof r==`string`&&typeof i==`string`?{id:n,name:r,nodeId:i}:void 0}function readAgentAddress(t){if(t===void 0||!isJsonObjectValue(t))return;let{callbackBaseUrl:n,continuationToken:r,kind:i,sessionId:a,url:o}=t;if(typeof a==`string`){if(i===`agent/local`||i===`agent/self`)return typeof r==`string`?{continuationToken:r,kind:i,sessionId:a}:void 0;if(i===`agent/remote`)return typeof o==`string`&&typeof n==`string`?{callbackBaseUrl:n,kind:i,sessionId:a,url:o}:void 0}}function sameTaskMetadata(e,t){if(e.kind!==t.kind||e.name!==t.name)return!1;let n=`agentId`in e,r=`agentId`in t;return n===r&&(!n||!r||e.agentId===t.agentId&&e.mode===t.mode)}function readTaskUsage(e){if(typeof e!=`object`||!e||Array.isArray(e))return;let t=readUsageAxis(e,`cacheReadTokens`),n=readUsageAxis(e,`cacheWriteTokens`),r=readUsageAxis(e,`inputTokens`),i=readUsageAxis(e,`outputTokens`);if(!(t===void 0||n===void 0||r===void 0||i===void 0))return{cacheReadTokens:t,cacheWriteTokens:n,inputTokens:r,outputTokens:i}}function readUsageAxis(e,t){let n=Reflect.get(e,t);return typeof n==`number`&&Number.isFinite(n)&&n>=0?n:void 0}const TASK_AUTHORIZATION_REQUEST_ID_PREFIX=`task:authorization`;function taskAuthorizationRequestId(e){return e.type===`approval.candidate`||e.type===`approval.settled`?`${TASK_AUTHORIZATION_REQUEST_ID_PREFIX}:${e.data.requestId}`:`${TASK_AUTHORIZATION_REQUEST_ID_PREFIX}:${e.data.attemptId??e.data.name}`}function readTaskInputRequestId(e){if(typeof e!=`object`||!e||Array.isArray(e))return;let t=Reflect.get(e,`requestId`);return typeof t==`string`?t:void 0}const TASK_VIEW_STREAM_NAMESPACE=`eve.task`;function isTerminalTaskStatus(e){return e===`completed`||e===`failed`||e===`cancelled`}function isReadyTaskStatus(e){return e===`input_required`||isTerminalTaskStatus(e)}export{TASK_VIEW_STREAM_NAMESPACE,createSubagentExecutorBinding,createSubagentTaskMetadata,isReadyTaskStatus,isTerminalTaskStatus,readSubagentExecutor,readSubagentTaskMetadata,readTaskInputRequestId,readTaskUsage,sameTaskMetadata,taskAuthorizationRequestId};
@@ -111,6 +111,18 @@ await session.reset({ reason: "Retire this session" });
111
111
  await session.getEventStream({ startIndex: 12 });
112
112
  ```
113
113
 
114
+ `respond()` accepts exact response literals directly. If responses have already
115
+ been widened to `InputResponse[]`—for example, after decoding a platform
116
+ payload—validate them with eve's strict schema before delivery. The validated
117
+ type preserves that proof across channel wrappers:
118
+
119
+ ```ts
120
+ import { parseInputResponses } from "eve/client";
121
+
122
+ const inputResponses = parseInputResponses(decodedResponses);
123
+ await source.respond(inputResponses, { auth });
124
+ ```
125
+
114
126
  Message sends use the channel's `turnPolicy`, which defaults to `"steer"`. An accepted message arriving during an active turn is durably buffered before eve cancels that turn and starts the message as a replacement turn. Configure `turnPolicy: "queue"` on `defineChannel(...)` when active turns should finish in order, or override one send:
115
127
 
116
128
  ```ts
@@ -5,7 +5,7 @@ description: Connect an eve agent to iMessage and SMS through Linq.
5
5
 
6
6
  Use `linqChannel` to receive and reply to iMessage and SMS conversations through Linq.
7
7
 
8
- Run `eve add channel/linq` to choose Vercel Connect or portable credentials. With Vercel Connect, eve signs you in and creates or links a Vercel project when needed. Then choose whether to create a managed Linq account and line or connect an existing Linq account with its partner API token and phone numbers. eve configures the connector and webhook through Connect:
8
+ Run `eve add channel/linq` to choose Vercel Connect or portable credentials. With Vercel Connect, eve signs you in and creates or links a Vercel project when needed. Then choose whether to create a managed Linq account and line or connect an existing account with its partner API token. eve fetches the phone numbers assigned to that account, and you select the numbers for the agent. eve configures the connector and webhook through Connect:
9
9
 
10
10
  ```ts title="agent/channels/linq.ts"
11
11
  import { connectLinqCredentials } from "@vercel/connect/eve";
@@ -24,13 +24,7 @@ Run `eve init` with a project name:
24
24
  npx eve@latest init my-agent
25
25
  ```
26
26
 
27
- The command creates the project, installs dependencies, and initializes Git. If a supported coding-agent REPL is available, eve offers to start the development server, open the REPL, or exit after scaffolding.
28
-
29
- Pass an AI Gateway model ID or reasoning effort when you do not want the defaults:
30
-
31
- ```bash
32
- npx eve@latest init my-agent --model openai/gpt-5.6-terra --reasoning high
33
- ```
27
+ The command creates the project, installs dependencies, and initializes Git. After scaffolding, eve offers to start the development server or, if a supported coding agent is installed, to open the project in the coding agent.
34
28
 
35
29
  To add eve to a project that already has a `package.json`, run this command from its root before you create any `agent/` files:
36
30
 
@@ -40,15 +34,23 @@ npx eve@latest init .
40
34
 
41
35
  eve adds the missing `eve`, `ai`, and `zod` dependencies without changing files the project already owns.
42
36
 
37
+ ### Customize initialization
38
+
39
+ To initialize the agent with a different AI Gateway model or reasoning effort, pass `--model` or `--reasoning`:
40
+
41
+ ```bash
42
+ npx eve@latest init my-agent --model openai/gpt-5.6-terra --reasoning high
43
+ ```
44
+
43
45
  ## Run the agent
44
46
 
45
- If the development server is not already running, start it from the project root:
47
+ Choose **Start eve dev** after scaffolding, or run this from the project root:
46
48
 
47
49
  ```bash
48
50
  npm run dev
49
51
  ```
50
52
 
51
- The TUI sends messages to your agent. Stop the server with `Ctrl+C` when you need your shell for another command.
53
+ This starts an interactive session where you can send messages to your agent.
52
54
 
53
55
  ## Project layout
54
56
 
@@ -56,7 +58,7 @@ eve builds an agent by walking the filesystem under `agent/`. Each directory is
56
58
 
57
59
  ### Naming from paths
58
60
 
59
- Identity comes from the path. You never write a `name` or `id` field on a `define*` call.
61
+ eve derives names from file paths, so you do not configure them separately.
60
62
 
61
63
  | Path | Resolves to |
62
64
  | ------------------------------------- | --------------------- |
@@ -65,7 +67,7 @@ Identity comes from the path. You never write a `name` or `id` field on a `defin
65
67
  | `agent/skills/summarize.md` | skill `summarize` |
66
68
  | `agent/subagents/researcher/agent.ts` | subagent `researcher` |
67
69
 
68
- The root agent takes its name from the enclosing `package.json` `name`, falling back to the app-root directory name when `package.json` has no `name`. A subagent takes its name from its directory.
70
+ The root agent uses its `package.json` `name`, or its app directory name if none is set. A subagent uses its directory name.
69
71
 
70
72
  ### Recommended layout
71
73
 
@@ -93,31 +95,29 @@ my-agent/
93
95
 
94
96
  Evals live beside `agent/`, not inside it.
95
97
 
96
- ### Authored slots
97
-
98
- The Subagents column states whether a local subagent (`subagents/<id>/`) can author the slot. A declared subagent inherits nothing from the root; it discovers its own slots.
99
-
100
- | Path | Description | Subagents | Notes |
101
- | ------------------------------------------------------- | ----------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
102
- | `agent.ts` | Runtime config | Yes | Model, model options, compaction, build, and experimental settings. See [Agents](./agent-config). |
103
- | `instructions.md` / `instructions.ts` / `instructions/` | Base system prompt | Optional | A flat file or directory of `.md` and `.ts` files. Static sources compose at build time. Dynamic sources resolve at runtime. Required on the root, optional on subagents. |
104
- | `instrumentation.ts` | Telemetry config | No | OTel exporter and AI SDK span settings, auto-discovered and run before agent code. Root-only. |
105
- | `channels/` | HTTP and messaging entry points | No | Root-only. |
106
- | `connections/` | External MCP and OpenAPI services | Yes | One connection per file; its name comes from the filename. |
107
- | `hooks/` | Lifecycle and stream-event subscribers | Yes | Module-backed only. Recursive directories are supported. |
108
- | `skills/` | On-demand procedures and capability packs | Yes | Flat markdown, module-backed skills, or packaged skills. Runtime files are seeded under `$HOME/.agents/skills/`, with `/workspace/skills/` as a fallback. |
109
- | `lib/` | Shared authored helper code | Yes | Import-only; not mounted into the workspace. |
110
- | `sandbox.ts` or `sandbox/sandbox.ts` | The agent's single sandbox | Yes | Use `sandbox.ts` for a definition-only override; use `sandbox/sandbox.ts` with `sandbox/workspace/**` to also seed files. The framework default applies when neither is authored. |
111
- | `sandbox/workspace/**` | Files seeded into the sandbox | Yes | Mirrored into `/workspace/` when a session starts. |
112
- | `tools/` | Typed executable integrations | Yes | Module-backed only. |
113
- | `schedules/` | Recurring jobs | No | Each schedule is a default-exported `defineSchedule` module or a markdown prompt with `cron` frontmatter. Recursive nesting is supported. Root-only. |
114
- | `subagents/` | Specialist child agents | Yes | Each child is a local package under `subagents/<id>/`. Nested subagents are supported. |
115
-
116
- ### Runtime files and source files
117
-
118
- eve does not mount the whole authored tree into the runtime sandbox. Files under `agent/sandbox/workspace/**` land in `/workspace/` when a session starts.
119
-
120
- Skill package files land outside the workspace under `$HOME/.agents/skills/`, with `/workspace/skills/` as a fallback when `$HOME` is unavailable. Everything in `lib/` remains import-only source code.
98
+ ### Agent files and directories
99
+
100
+ Each path under `agent/` has a specific purpose. Root agents can use every path below. A subagent has its own files and can use only the paths marked **Yes**.
101
+
102
+ | Path | Use | Available to subagents | Notes |
103
+ | ------------------------------------------------------- | ----------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
104
+ | `agent.ts` | Runtime config | Yes | Model, model options, compaction, build, and experimental settings. See [Agents](./agent-config). |
105
+ | `instructions.md` / `instructions.ts` / `instructions/` | Base system prompt | Optional | A flat file or directory of `.md` and `.ts` files. Static sources compose at build time. Dynamic sources resolve at runtime. Required on the root, optional on subagents. |
106
+ | `instrumentation.ts` | Telemetry config | No | OTel exporter and AI SDK span settings, auto-discovered and run before agent code. Root-only. |
107
+ | `channels/` | HTTP and messaging entry points | No | Root-only. |
108
+ | `connections/` | External MCP and OpenAPI services | Yes | One connection per file; its name comes from the filename. |
109
+ | `hooks/` | Lifecycle and stream-event subscribers | Yes | Module-backed only. Recursive directories are supported. |
110
+ | `skills/` | On-demand procedures and capability packs | Yes | Flat markdown, module-backed skills, or packaged skills. Runtime files are seeded under `$HOME/.agents/skills/`, with `/workspace/skills/` as a fallback. |
111
+ | `lib/` | Shared authored helper code | Yes | Import-only; not mounted into the workspace. |
112
+ | `sandbox.ts` or `sandbox/sandbox.ts` | The agent's single sandbox | Yes | Use `sandbox.ts` for a definition-only override; use `sandbox/sandbox.ts` with `sandbox/workspace/**` to also seed files. The framework default applies when neither is authored. |
113
+ | `sandbox/workspace/**` | Files seeded into the sandbox | Yes | Mirrored into `/workspace/` when a session starts. |
114
+ | `tools/` | Typed executable integrations | Yes | Module-backed only. |
115
+ | `schedules/` | Recurring jobs | No | Each schedule is a default-exported `defineSchedule` module or a markdown prompt with `cron` frontmatter. Recursive nesting is supported. Root-only. |
116
+ | `subagents/` | Specialist child agents | Yes | Each child is a local package under `subagents/<id>/`. Nested subagents are supported. |
117
+
118
+ ### Files available in the sandbox
119
+
120
+ Files under `agent/` define your agent; only files in `agent/sandbox/workspace/` are copied to `/workspace/` when a session starts.
121
121
 
122
122
  ### Local subagents
123
123
 
@@ -223,7 +223,7 @@ Keep secret values (`ROUTE_AUTH_BASIC_PASSWORD`, signing keys) in environment va
223
223
 
224
224
  ## Accepting forwarded identity from another deployment
225
225
 
226
- A `defineRemoteAgent({ forwardPrincipal: true })` caller (see [Remote agents](./remote-agents#forwarding-the-caller-identity)) asserts its end user's principal on the create-session request as a `forwardedPrincipal` body field. By default every such assertion is rejected with `403` — accepting someone else's word for who the user is requires naming exactly which forwarders you trust. Do that with `trustedForwarders` on `eveChannel`:
226
+ A `defineRemoteAgent({ forwardPrincipal: true })` caller (see [Remote agents](./remote-agents#forwarding-the-caller-identity)) asserts its end user's principal on create and continuation requests as a `forwardedPrincipal` body field. By default every such assertion is rejected with `403` — accepting someone else's word for who the user is requires naming exactly which forwarders you trust. Do that with `trustedForwarders` on `eveChannel`:
227
227
 
228
228
  ```ts title="agent/channels/eve.ts"
229
229
  import { eveChannel } from "eve/channels/eve";
@@ -239,7 +239,11 @@ export default eveChannel({
239
239
 
240
240
  The predicate authorizes the _forwarder_ (the verified route-auth principal — who is asserting), not the forwarded principal (what is asserted). Match it precisely: a permissive predicate like `() => true` lets any caller that passes route auth assert any principal, including preview deployments of your own project when `vercelOidc()` is in the walk. `trustedForwarders` exists only on your authored channel — the framework default channel never accepts a forwarded principal, so a receiving deployment must author `agent/channels/eve.ts`.
241
241
 
242
- When the predicate accepts, the forwarded principal replaces the session principal: `ctx.session.auth.current` and `.initiator` carry the forwarded user exactly as if they had called your deployment directly, so user-scoped connections, local subagents, and further `forwardPrincipal` hops all see that user. The forwarder is recorded on the accepted contexts as the `eve:forwarded-by` attribute (always overwritten by the receiver, so a forwarder cannot falsify it). Rejections fail loud: a forwarded body without `trustedForwarders` configured or with a forwarder the predicate refuses is a `403`, and a malformed payload is a `400`. Only principal metadata is ever accepted — tokens and credentials never cross the hop.
242
+ When the predicate accepts a create request, `ctx.session.auth.current` and `.initiator` carry the forwarded user exactly as if they had called your deployment directly. On continuation, only `auth.current` is replaced; `auth.initiator` remains the session creator. User-scoped connections, local subagents, and further `forwardPrincipal` hops therefore see the active turn's caller. The forwarder is recorded on accepted contexts as the `eve:forwarded-by` attribute (always overwritten by the receiver, so a forwarder cannot falsify it). Rejections fail loud: a forwarded body without `trustedForwarders` configured or with a forwarder the predicate refuses is a `403`, and a malformed payload is a `400`. Only principal metadata is ever accepted — tokens and credentials never cross the hop.
243
+
244
+ > ⚠️ Both deployments must support continuation forwarding before you resume persistent remote sessions. A create-only receiver rejects a forwarded continuation with HTTP 400; the sender does not fall back to service authority. See [Forwarding the caller identity](./remote-agents#forwarding-the-caller-identity) for the upgrade behavior.
245
+
246
+ Persistent sessions do not preserve caller authority between turns. Every accepted follow-up replaces `auth.current`, including replacing it with no authenticated caller on internal local delivery; per-user connection lookup is then keyed from that current principal. This prevents a later caller from resolving a prior caller's OAuth grant. It does not hide the persistent session's conversation history or artifacts from a caller who is otherwise allowed to continue that session; session ownership remains an application policy.
243
247
 
244
248
  ## What reaches `ctx.session.auth`
245
249
 
@@ -132,7 +132,7 @@ approval checks.
132
132
 
133
133
  ## Dynamic tools
134
134
 
135
- Pass `defineDynamic` an `events` object whose handlers return either a single `defineTool(...)`, a `Record<string, defineTool(...)>`, or `null` for no tools. Wrap every entry in `defineTool()`. The wrapper stamps them so their `execute` functions survive workflow step boundaries.
135
+ Pass `defineDynamic` an `events` object whose handlers return either a single `defineTool(...)`, a `Record<string, defineTool(...)>`, or `null` for no tools. Wrap every entry in `defineTool()`. eve records durable descriptors for `execute`, approval request and response policies, and `toModelOutput`, so a parked call can reconstruct the same callbacks in a fresh process.
136
136
 
137
137
  Dynamic tool executors receive the same `ToolContext` as static authored tools, including inline provider auth through `ctx.getToken(provider)` and `ctx.requireAuth(provider)`.
138
138
 
@@ -160,11 +160,13 @@ export default defineDynamic({
160
160
  });
161
161
  ```
162
162
 
163
- ### Prefer an inline `execute` function
163
+ ### Author replayable callbacks
164
164
 
165
- Write `execute` as an inline function expression, arrow, or method shorthand placed directly as the property value. The bundler transform stores the function and its closure variables for durable replay without rerunning the resolver.
165
+ Write callback properties as inline function expressions, arrows, method shorthand, or module-level function references. eve transforms authored modules that import `defineTool`, including helper modules outside `agent/tools/`, and stores each callback's referenced closure values independently.
166
166
 
167
- The transform does not detect `execute: myFn`, `execute: makeFn()`, or executors created inside an imported dependency. For a `session.started` tool, eve can reconstruct these live functions by rerunning the owning resolver after a durable resume. Keep session resolvers idempotent and avoid unnecessary side effects. A `turn.started` tool still requires an inline executor to survive a fresh runtime.
167
+ Closure values must be JSON-serializable. Plain objects, arrays, strings, finite numbers, booleans, and `null` are supported; `undefined` object properties are omitted. Functions, class instances, `Date`, `Map`, symbols, non-finite numbers, and cyclic values fail resolution with the tool name and callback phase instead of being serialized lossily.
168
+
169
+ Call expressions such as `execute: makeExecutor()` are not transformed. Put the callback body directly in `defineTool()` inside an authored module; eve-provided factories may also supply pre-registered callbacks. eve rejects a dynamic tool if any present callback lacks durable metadata, and replay never reruns the current resolver to recover one.
168
170
 
169
171
  ### Naming
170
172
 
@@ -181,13 +183,13 @@ A dynamic tool or skill whose name matches an **authored** one **overrides** it
181
183
 
182
184
  ### Events
183
185
 
184
- | Event | Resolver runs | Tools available for |
185
- | ----------------- | ------------------------------------------ | ------------------------------- |
186
- | `session.started` | At session start; sometimes after a pause¹ | Every model call in the session |
187
- | `turn.started` | Once per turn | Every model call in the turn |
188
- | `step.started` | Before each model call | That model call |
186
+ | Event | Resolver runs | Tools available for |
187
+ | ----------------- | ----------------------------------------------------- | ------------------------------- |
188
+ | `session.started` | At session start; may be redelivered during recovery¹ | Every model call in the session |
189
+ | `turn.started` | Once per turn | Every model call in the turn |
190
+ | `step.started` | Before each model call | That model call |
189
191
 
190
- ¹ If a session pauses to wait for approval or other input, eve may run the resolver again when the session continues. Design session resolvers so they can safely run more than once. They do not run before every model call.
192
+ ¹ Workflow recovery can redeliver a resolver event, so keep resolvers idempotent. Replaying a parked callback does not depend on running the resolver again.
191
193
 
192
194
  ### Execution order
193
195