eve 0.42.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 (137) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/src/channel/forwarded-principal.d.ts +4 -4
  3. package/dist/src/compiler/extension-compatibility.d.ts +7 -5
  4. package/dist/src/compiler/extension-compatibility.js +1 -1
  5. package/dist/src/compiler/manifest.d.ts +3 -0
  6. package/dist/src/compiler/manifest.js +1 -1
  7. package/dist/src/compiler/normalize-manifest.js +1 -1
  8. package/dist/src/compiler/normalize-tool.js +1 -1
  9. package/dist/src/context/build-dynamic-tools.d.ts +5 -5
  10. package/dist/src/context/build-dynamic-tools.js +1 -1
  11. package/dist/src/context/dynamic-subagent-lifecycle.js +1 -1
  12. package/dist/src/context/dynamic-tool-lifecycle.d.ts +8 -27
  13. package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
  14. package/dist/src/context/keys.d.ts +10 -14
  15. package/dist/src/context/keys.js +1 -1
  16. package/dist/src/context/provider.d.ts +5 -1
  17. package/dist/src/context/run-step.d.ts +3 -2
  18. package/dist/src/context/run-step.js +1 -1
  19. package/dist/src/execution/agent-handle-dispatch.d.ts +3 -0
  20. package/dist/src/execution/agent-handle-dispatch.js +1 -1
  21. package/dist/src/execution/delegation-tool.d.ts +9 -1
  22. package/dist/src/execution/delegation-tool.js +1 -1
  23. package/dist/src/execution/dispatch-runtime-actions-shared.d.ts +14 -1
  24. package/dist/src/execution/dispatch-runtime-actions-shared.js +1 -1
  25. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  26. package/dist/src/execution/history-view.d.ts +9 -0
  27. package/dist/src/execution/history-view.js +1 -0
  28. package/dist/src/execution/next-driver-action.d.ts +33 -1
  29. package/dist/src/execution/node-step.d.ts +5 -2
  30. package/dist/src/execution/node-step.js +1 -1
  31. package/dist/src/execution/remote-agent-dispatch.d.ts +2 -0
  32. package/dist/src/execution/remote-agent-dispatch.js +1 -1
  33. package/dist/src/execution/session-callback-step.d.ts +2 -2
  34. package/dist/src/execution/session-callback-step.js +1 -1
  35. package/dist/src/execution/tasks/child/steps.js +1 -1
  36. package/dist/src/execution/tasks/child/update.d.ts +2 -2
  37. package/dist/src/execution/tasks/child/update.js +1 -1
  38. package/dist/src/execution/tasks/child/workflow.js +1 -1
  39. package/dist/src/execution/tasks/parent/agent-views.d.ts +2 -1
  40. package/dist/src/execution/tasks/parent/agent-views.js +1 -1
  41. package/dist/src/execution/tasks/parent/continuation-dispatch.d.ts +6 -0
  42. package/dist/src/execution/tasks/parent/continuation-dispatch.js +1 -1
  43. package/dist/src/execution/tasks/parent/control-shared.d.ts +1 -1
  44. package/dist/src/execution/tasks/parent/control-shared.js +1 -1
  45. package/dist/src/execution/tasks/parent/delegate.d.ts +20 -6
  46. package/dist/src/execution/tasks/parent/delegate.js +1 -1
  47. package/dist/src/execution/tasks/parent/dispatch-task-step.js +1 -1
  48. package/dist/src/execution/tasks/parent/dispatch.d.ts +13 -1
  49. package/dist/src/execution/tasks/parent/dispatch.js +1 -1
  50. package/dist/src/execution/tasks/parent/hitl-proxy-steps.js +1 -1
  51. package/dist/src/execution/tasks/parent/tool-execution.d.ts +46 -0
  52. package/dist/src/execution/tasks/parent/tool-execution.js +1 -0
  53. package/dist/src/execution/tool-auth.d.ts +12 -3
  54. package/dist/src/execution/tool-auth.js +1 -1
  55. package/dist/src/execution/turn-cancellation-control.js +1 -1
  56. package/dist/src/execution/turn-workflow.js +1 -1
  57. package/dist/src/execution/wire/session-inbox-wire.v1.js +1 -1
  58. package/dist/src/execution/workflow-steps.d.ts +1 -54
  59. package/dist/src/execution/workflow-steps.js +1 -1
  60. package/dist/src/harness/advertised-tools.js +1 -1
  61. package/dist/src/harness/background-tools.d.ts +37 -0
  62. package/dist/src/harness/background-tools.js +1 -0
  63. package/dist/src/harness/execute-tool.d.ts +10 -1
  64. package/dist/src/harness/handles/transitions.d.ts +36 -0
  65. package/dist/src/harness/handles/transitions.js +1 -1
  66. package/dist/src/harness/messages.d.ts +2 -0
  67. package/dist/src/harness/messages.js +1 -1
  68. package/dist/src/harness/tool-loop.js +2 -2
  69. package/dist/src/harness/tools.d.ts +5 -1
  70. package/dist/src/harness/tools.js +1 -1
  71. package/dist/src/harness/types.d.ts +13 -0
  72. package/dist/src/internal/application/package.js +1 -1
  73. package/dist/src/internal/authored-definition/schema-backed.js +1 -1
  74. package/dist/src/internal/authored-module-loader.js +1 -1
  75. package/dist/src/internal/workflow-bundle/dynamic-capability-transform-plugin.js +1 -1
  76. package/dist/src/internal/workflow-bundle/dynamic-tool-ast-references.d.ts +13 -0
  77. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.d.ts +7 -31
  78. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.js +4 -4
  79. package/dist/src/packages/eve-catalog/src/index.js +1 -1
  80. package/dist/src/public/channels/eve.d.ts +7 -7
  81. package/dist/src/public/channels/eve.js +1 -1
  82. package/dist/src/public/definitions/remote-agent.d.ts +7 -5
  83. package/dist/src/public/definitions/tool.d.ts +23 -0
  84. package/dist/src/public/definitions/tool.js +1 -1
  85. package/dist/src/public/tools/approval/approval-helpers.js +1 -1
  86. package/dist/src/public/tools/index.d.ts +1 -1
  87. package/dist/src/runtime/framework-tools/connection-search-dynamic.js +2 -2
  88. package/dist/src/runtime/framework-tools/subagent/local.d.ts +41 -0
  89. package/dist/src/runtime/framework-tools/subagent/local.js +1 -0
  90. package/dist/src/runtime/framework-tools/subagent/remote.d.ts +23 -0
  91. package/dist/src/runtime/framework-tools/subagent/remote.js +1 -0
  92. package/dist/src/runtime/framework-tools/tasks.d.ts +11 -2
  93. package/dist/src/runtime/framework-tools/tasks.js +1 -1
  94. package/dist/src/runtime/resolve-tool.js +1 -1
  95. package/dist/src/runtime/session-callback-route.js +1 -1
  96. package/dist/src/setup/integrations/linq/management.d.ts +5 -0
  97. package/dist/src/setup/integrations/linq/management.js +1 -0
  98. package/dist/src/setup/integrations/linq/setup.d.ts +5 -1
  99. package/dist/src/setup/integrations/linq/setup.js +3 -3
  100. package/dist/src/setup/scaffold/create/add-to-project.js +1 -1
  101. package/dist/src/setup/scaffold/create/project.js +4 -10
  102. package/dist/src/setup/scaffold/create/web-template.d.ts +0 -1
  103. package/dist/src/setup/scaffold/create/web-template.js +1 -1
  104. package/dist/src/setup/scaffold/update/channels.d.ts +1 -1
  105. package/dist/src/setup/scaffold/update/channels.js +2 -2
  106. package/dist/src/setup/scaffold/update/package-json.d.ts +0 -2
  107. package/dist/src/setup/scaffold/update/package-json.js +1 -1
  108. package/dist/src/setup/scaffold/workspace-root.d.ts +0 -1
  109. package/dist/src/setup/scaffold/workspace-root.js +1 -1
  110. package/dist/src/shared/durable-dynamic-tool-callbacks.d.ts +34 -0
  111. package/dist/src/shared/durable-dynamic-tool-callbacks.js +1 -0
  112. package/dist/src/shared/dynamic-tool-definition.d.ts +2 -4
  113. package/dist/src/shared/empty-delivery.js +1 -1
  114. package/dist/src/shared/history-view.d.ts +22 -0
  115. package/dist/src/shared/history-view.js +1 -0
  116. package/dist/src/shared/json.d.ts +2 -0
  117. package/dist/src/shared/json.js +1 -1
  118. package/dist/src/shared/tool-definition.d.ts +4 -1
  119. package/dist/src/shared/tool-task.d.ts +107 -0
  120. package/dist/src/shared/tool-task.js +1 -0
  121. package/dist/src/tasks/delivery-context.d.ts +12 -0
  122. package/dist/src/tasks/delivery-context.js +17 -0
  123. package/dist/src/tasks/json.d.ts +25 -10
  124. package/dist/src/tasks/json.js +1 -1
  125. package/dist/src/tasks/session-index.d.ts +5 -3
  126. package/dist/src/tasks/session-index.js +1 -1
  127. package/dist/src/tasks/transitions.d.ts +40 -5
  128. package/dist/src/tasks/transitions.js +1 -1
  129. package/dist/src/tasks/types.d.ts +46 -4
  130. package/dist/src/tasks/types.js +1 -1
  131. package/docs/channels/linq.mdx +1 -1
  132. package/docs/getting-started.mdx +36 -36
  133. package/docs/guides/auth-and-route-protection.md +6 -2
  134. package/docs/guides/dynamic-capabilities.md +12 -10
  135. package/docs/guides/remote-agents.md +10 -2
  136. package/docs/tools/overview.mdx +19 -0
  137. package/package.json +1 -1
@@ -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};
@@ -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
 
@@ -114,9 +114,17 @@ export default defineRemoteAgent({
114
114
  });
115
115
  ```
116
116
 
117
- The create-session request then carries the parent turn's `session.auth.current` and `session.auth.initiator` as a `forwardedPrincipal` body field (`initiator` is optional on the wire; when absent, the receiver seeds both from `current`). Only principal metadata crosses the wire — never tokens or credentials. The receiving deployment mints its own per-user credentials through its own connections.
117
+ The create-session request carries the parent turn's `session.auth.current` and `session.auth.initiator` as a `forwardedPrincipal` body field (`initiator` is optional on the wire; when absent, the receiver seeds both from `current`). Every continuation carries only that turn's `session.auth.current`; the remote session keeps its original `auth.initiator`. Only principal metadata crosses the wire — never tokens or credentials. The receiving deployment resolves its own per-user credentials through its own connections.
118
118
 
119
- Forwarding is explicit on both sides. The receiver names which forwarders it trusts with `eveChannel({ trustedForwarders })` (see [Auth & route protection](./auth-and-route-protection#accepting-forwarded-identity-from-another-deployment)); a receiver that refuses the forwarder — or has no `trustedForwarders` at all — rejects with a 403 and the dispatch fails. One caveat: a receiver on an eve version that predates principal forwarding drops the unknown field and runs the session as your app's service identity (per-user connections there fail with `principal_required`), so upgrade both deployments together when enabling forwarding. When the dispatching turn has no auth at all, the field is omitted and the call proceeds on transport trust alone.
119
+ This makes caller authority turn-scoped even when the remote child session is persistent. If Alice starts the child and Bob later continues it, the follow-up runs with Bob as `auth.current`, not Alice. If the parent turn's auth is `null`, a local child clears `auth.current`, while a remote child uses the freshly verified transport principal; neither inherits Alice. eve's in-step bearer cache is also keyed by the resolved principal and is not serialized across steps. The external authorization provider may preserve each user's server-side OAuth grant, but a later turn can resolve only the grant belonging to its own `auth.current` principal.
120
+
121
+ Identity forwarding does not make a persistent session private to one caller. Conversation history, tool outputs, and other child-session state still persist. If those values must not be visible across users, give each user a distinct child session or enforce that ownership at the application boundary.
122
+
123
+ Forwarding is explicit on both sides. The receiver names which forwarders it trusts with `eveChannel({ trustedForwarders })` (see [Auth & route protection](./auth-and-route-protection#accepting-forwarded-identity-from-another-deployment)); a receiver that refuses the forwarder — or has no `trustedForwarders` at all — rejects with a 403 and the dispatch fails.
124
+
125
+ > ⚠️ **Upgrade both deployments before resuming persistent remote sessions.** A sender with continuation forwarding includes `forwardedPrincipal` on each authenticated follow-up. A receiver that supports forwarding only on session creation rejects that continuation with HTTP 400. eve does not retry without the field because that would run the follow-up as the transport service principal and silently change caller authority. The parent retains the child handle after this failure, so you can retry the same session after upgrading the receiver.
126
+
127
+ A receiver on an eve version that predates all principal forwarding may instead drop the unknown field and run the session as your app's service identity; per-user connections there fail with `principal_required`. On remote requests where the dispatching turn has no auth, the field is omitted and the call proceeds on transport trust alone.
120
128
 
121
129
  ## How remote dispatch and callbacks work
122
130
 
@@ -56,6 +56,25 @@ history or `toModelOutput`; only the final yield does. Treat snapshots as
56
56
  last-write-wins by tool call id, not append-only progress. The durable runtime
57
57
  can retry a step and replay overlapping snapshots.
58
58
 
59
+ ### Experimental background execution
60
+
61
+ `defineTool({ execution: "background" })` receives a third `task` argument. Returning a normal
62
+ value completes the durable task with that value. Returning `task.delegated({ executor, receipt })`
63
+ returns a `working` receipt immediately while an external executor continues the task.
64
+ The root agent must enable `experimental.tasks`; eve rejects background tools at build time otherwise.
65
+
66
+ This contract currently supports framework-owned task executors, including local and remote
67
+ subagents when `experimental.tasks` is enabled. After delegating, an in-process executor reports
68
+ progress with `task.send({ kind: "update", message })` and the terminal result with
69
+ `task.send({ kind: "complete", data })` (or `fail` / `cancel`). `send` is not restart-safe — an
70
+ in-memory callback dies with the process — so the cross-process executor wire remains
71
+ framework-owned and is not yet a stable authored-tool API.
72
+
73
+ Each task-triggered parent turn includes runtime-authored state for tasks started by the same
74
+ parent turn. The model is instructed to keep related intermediate results silent while that state
75
+ contains pending tasks. Once every related task is terminal, the state includes their outputs so
76
+ the model can combine the useful results.
77
+
59
78
  ### The `ctx` parameter
60
79
 
61
80
  `execute` gets a `ctx` carrying the runtime accessors:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve",
3
- "version": "0.42.0",
3
+ "version": "0.43.0",
4
4
  "private": false,
5
5
  "description": "Filesystem-first framework for durable backend AI agents that run anywhere.",
6
6
  "keywords": [