eve 0.80.1 → 0.81.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/dist/src/channel/channel-address.d.ts +1 -0
- package/dist/src/channel/channel-address.js +1 -1
- package/dist/src/channel/inactive-prompt.d.ts +46 -0
- package/dist/src/channel/inactive-prompt.js +1 -0
- package/dist/src/channel/principal-identity.d.ts +2 -0
- package/dist/src/channel/principal-identity.js +1 -1
- package/dist/src/channel/session-not-active-error.d.ts +16 -0
- package/dist/src/channel/session-not-active-error.js +1 -0
- package/dist/src/chunks/{use-eve-agent-Vsl-S0yI.js → use-eve-agent-Bp6nbhfV.js} +6 -0
- package/dist/src/chunks/{use-eve-agent-Ch-Q-s12.js → use-eve-agent-DLkjJIm3.js} +6 -0
- package/dist/src/compiled/.vendor-stamp.json +2 -2
- package/dist/src/compiled/@ai-sdk/mcp/index.js +1 -1
- package/dist/src/compiled/@eve/catalog/index.js +1 -1
- package/dist/src/compiler/extension-compatibility.d.ts +18 -18
- package/dist/src/compiler/extension-compatibility.js +1 -1
- package/dist/src/context/provider.d.ts +5 -0
- package/dist/src/context/providers/connection.js +1 -1
- package/dist/src/context/run-step.js +1 -1
- package/dist/src/eve-channel/create-request.d.ts +2 -1
- package/dist/src/eve-channel/index.js +1 -1
- package/dist/src/eve-channel/request.d.ts +2 -1
- package/dist/src/eve-channel/request.js +1 -2
- package/dist/src/eve-channel/support.d.ts +9 -1
- package/dist/src/eve-channel/support.js +2 -1
- package/dist/src/eve-channel/types.d.ts +18 -0
- package/dist/src/execution/agent-sessions/steps.js +1 -1
- package/dist/src/execution/dynamic-workflow/tool.js +1 -1
- package/dist/src/execution/hook-ownership.d.ts +8 -0
- package/dist/src/execution/hook-ownership.js +1 -1
- package/dist/src/execution/sandbox/bindings/vercel-lookup.d.ts +6 -0
- package/dist/src/execution/sandbox/bindings/vercel-lookup.js +1 -1
- package/dist/src/execution/sandbox/bindings/vercel.js +2 -2
- package/dist/src/execution/session/entry-input.d.ts +19 -5
- package/dist/src/execution/session/entry-input.js +1 -1
- package/dist/src/execution/session/entry.js +1 -1
- package/dist/src/execution/session/handoff-steps.d.ts +75 -13
- package/dist/src/execution/session/handoff-steps.js +1 -1
- package/dist/src/execution/session/handoff.d.ts +69 -27
- package/dist/src/execution/session/handoff.js +1 -1
- package/dist/src/execution/session/program.js +1 -1
- package/dist/src/execution/session/run-watch.d.ts +22 -0
- package/dist/src/execution/session/run-watch.js +1 -0
- package/dist/src/execution/session-inbox/address.d.ts +4 -1
- package/dist/src/execution/session-inbox/end-stranded-session.js +1 -1
- package/dist/src/execution/session-inbox/hook-release.d.ts +9 -2
- package/dist/src/execution/session-inbox/hook-release.js +1 -1
- package/dist/src/execution/session-inbox/inbox.d.ts +66 -6
- package/dist/src/execution/session-inbox/inbox.js +1 -1
- package/dist/src/execution/session-inbox/resume.d.ts +4 -3
- package/dist/src/execution/tools/agent-router-workflow.js +1 -1
- package/dist/src/execution/workflow-runtime.d.ts +6 -11
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/extensions/code/extension/extension.d.ts +2 -0
- package/dist/src/extensions/code/extension/extension.js +1 -1
- package/dist/src/extensions/git/extension/extension.d.ts +3 -0
- package/dist/src/extensions/git/extension/extension.js +1 -1
- package/dist/src/extensions/git/extension/lib/attribution.d.ts +22 -0
- package/dist/src/extensions/git/extension/lib/attribution.js +4 -0
- package/dist/src/extensions/git/extension/lib/gh-tool.js +1 -1
- package/dist/src/extensions/git/extension/lib/github-shell.d.ts +13 -1
- package/dist/src/extensions/git/extension/lib/github-shell.js +1 -1
- package/dist/src/extensions/git/extension/lib/sandbox.d.ts +1 -1
- package/dist/src/harness/authorization-event-fields.d.ts +3 -0
- package/dist/src/harness/authorization-event-fields.js +1 -1
- package/dist/src/harness/hitl/approvals.d.ts +9 -9
- package/dist/src/harness/hitl/approvals.js +1 -1
- package/dist/src/harness/hitl/approved-calls.d.ts +4 -4
- package/dist/src/harness/hitl/approved-calls.js +1 -1
- package/dist/src/harness/hitl/candidates.d.ts +7 -1
- package/dist/src/harness/hitl/candidates.js +1 -1
- package/dist/src/harness/hitl/coordinator.d.ts +2 -2
- package/dist/src/harness/hitl/coordinator.js +1 -1
- package/dist/src/harness/hitl/index.d.ts +2 -2
- package/dist/src/harness/hitl/index.js +1 -1
- package/dist/src/harness/hitl/intake.js +1 -1
- package/dist/src/harness/hitl/sign-ins.d.ts +10 -0
- package/dist/src/harness/hitl/sign-ins.js +1 -1
- package/dist/src/harness/inline-tool-authorization.d.ts +10 -11
- package/dist/src/harness/inline-tool-authorization.js +1 -1
- package/dist/src/harness/model-call/call.js +1 -1
- package/dist/src/harness/session-machine/current.d.ts +5 -0
- package/dist/src/harness/session-machine/current.js +1 -1
- package/dist/src/harness/session-machine/state.d.ts +15 -3
- package/dist/src/harness/session-machine/transitions.d.ts +23 -5
- package/dist/src/harness/session-machine/transitions.js +1 -1
- package/dist/src/harness/step/after-model.js +1 -1
- package/dist/src/instrumentation/content.d.ts +1 -1
- package/dist/src/instrumentation/content.js +1 -1
- package/dist/src/instrumentation/lifecycle.d.ts +4 -2
- package/dist/src/internal/application/stamped-package-version.js +1 -1
- package/dist/src/internal/authored-definition/connection.js +1 -1
- package/dist/src/internal/authored-definition/source-identity.js +1 -1
- package/dist/src/internal/mcp/skills.js +1 -1
- package/dist/src/internal/workflow/development-world-client.js +1 -1
- package/dist/src/protocol/message.d.ts +30 -0
- package/dist/src/protocol/message.js +1 -1
- package/dist/src/protocol/session-projection.d.ts +8 -1
- package/dist/src/protocol/session-projection.js +1 -1
- package/dist/src/public/channels/auth.js +1 -1
- package/dist/src/public/channels/chat-sdk/chatSdkChannel.js +1 -1
- package/dist/src/public/channels/chat-sdk/input-actions.d.ts +5 -0
- package/dist/src/public/channels/chat-sdk/input-actions.js +2 -2
- package/dist/src/public/channels/chat-sdk/messageToUserContent.d.ts +2 -1
- package/dist/src/public/channels/chat-sdk/messageToUserContent.js +1 -1
- package/dist/src/public/channels/discord/defaults.d.ts +6 -1
- package/dist/src/public/channels/discord/defaults.js +2 -2
- package/dist/src/public/channels/discord/discordChannel.js +1 -1
- package/dist/src/public/channels/photon/attachments.d.ts +5 -2
- package/dist/src/public/channels/photon/attachments.js +1 -1
- package/dist/src/public/channels/photon/photonIMessageChannel.js +1 -1
- package/dist/src/public/channels/slack/api.d.ts +8 -1
- package/dist/src/public/channels/slack/api.js +1 -1
- package/dist/src/public/channels/slack/approval-cards.js +2 -2
- package/dist/src/public/channels/slack/interaction-cards.d.ts +19 -6
- package/dist/src/public/channels/slack/interaction-cards.js +1 -1
- package/dist/src/public/channels/slack/interactions.js +1 -1
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/teams/hitl.d.ts +7 -0
- package/dist/src/public/channels/teams/hitl.js +1 -1
- package/dist/src/public/channels/teams/teamsChannel.js +1 -1
- package/dist/src/public/channels/telegram/hitl.d.ts +2 -0
- package/dist/src/public/channels/telegram/hitl.js +2 -2
- package/dist/src/public/channels/telegram/inbound.d.ts +2 -0
- package/dist/src/public/channels/telegram/inbound.js +1 -1
- package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
- package/dist/src/public/definitions/channel.js +1 -1
- package/dist/src/runtime/connections/mcp-client.d.ts +1 -1
- package/dist/src/runtime/connections/mcp-client.js +1 -1
- package/dist/src/runtime/connections/registry-types.d.ts +4 -1
- package/dist/src/runtime/connections/registry.d.ts +3 -2
- package/dist/src/runtime/connections/scoped-authorization.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/shared/gateway-routing.d.ts +20 -0
- package/dist/src/shared/gateway-routing.js +1 -0
- package/dist/src/shared/skill-package.js +1 -1
- package/dist/src/svelte/index.js +1 -1
- package/dist/src/svelte/use-eve-agent.js +1 -1
- package/dist/src/tools/provided/ask-question-workflow.js +1 -1
- package/dist/src/tools/provided/sleep-workflow.js +1 -1
- package/dist/src/tracing/eve/agent-otel-provider.js +1 -1
- package/dist/src/tracing/eve/gateway.d.ts +5 -0
- package/dist/src/tracing/eve/gateway.js +1 -1
- package/dist/src/tracing/eve/operation-input.d.ts +0 -7
- package/dist/src/tracing/eve/operation-input.js +1 -1
- package/dist/src/tracing/eve/policy.d.ts +1 -1
- package/dist/src/tracing/eve/policy.js +1 -1
- package/dist/src/tracing/eve/session.d.ts +2 -0
- package/dist/src/tracing/eve/session.js +1 -1
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/README.md +1 -0
- package/docs/agent-config.md +22 -0
- package/docs/channels/chat-sdk.mdx +4 -2
- package/docs/channels/eve.mdx +27 -0
- package/docs/channels/photon.mdx +4 -2
- package/docs/channels/slack.mdx +4 -0
- package/docs/channels/teams.mdx +1 -1
- package/docs/channels/telegram.mdx +1 -1
- package/docs/code-extension.mdx +20 -6
- package/docs/concepts/execution-model-and-durability.mdx +16 -7
- package/docs/connections/mcp.mdx +4 -0
- package/docs/connections/overview.mdx +2 -2
- package/docs/evals/assertions.mdx +21 -0
- package/docs/guides/instrumentation/otel.mdx +20 -0
- package/docs/patterns/meta.json +1 -0
- package/docs/patterns/voice-notes.md +67 -0
- package/docs/sandbox/index.mdx +2 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isObject}from"#shared/guards.js";import{
|
|
1
|
+
import{isObject}from"#shared/guards.js";import{createHook,getWorkflowMetadata,sleep}from"#compiled/@workflow/core/index.js";import{claimHookOwnership,disposeHook,isHookConflictError}from"#execution/hook-ownership.js";import{hasRunEndedStep,reportHandoverPartStuckStep,reportSessionHandoffRetainedStep,reportSessionTakenOverOutsideAttemptStep,sendSessionHandoverPartStep,startSuccessorStep}from"#execution/session/handoff-steps.js";import{abandonHandoverIfRunEnds,watchRunEnd}from"#execution/session/run-watch.js";import{readAcceptedDeploymentId}from"#execution/session/accepted-deployment.js";const SESSION_CHECKPOINT_VERSION=15;function sessionAnchorToken(e){return`${e}:anchor`}function handoffLeaseToken(e){return`eve:handoff:lease:${e}`}const HANDOFF_LEASE_RETENTION=`7d`;function verdictRunId(e){return e.kind===`verdict`&&e.verdict.kind!==`active`?e.verdict.runId:void 0}async function sendSessionHandover(e){let{attemptKey:t,sessionId:n}=e,r=e.payloads.filter(e=>e.kind!==`handover`);for(let[e,i]of r.entries())if(await sendHandoverPart({attemptKey:t,index:e,payloads:[i]},n)===`session-ended`)return;await sendHandoverPart({attemptKey:t,final:!0,index:r.length,payloads:[]},n)}async function sendHandoverPart(e,t){for(let n=1,r=1;;n++)try{return await sendSessionHandoverPartStep({part:e,sessionId:t})}catch(i){n===HANDOVER_PART_FAILURES_BEFORE_REPORT&&await reportHandoverPartStuckStep({attemptKey:e.attemptKey,error:isObject(i)&&typeof i.message==`string`?i.message:void 0,failures:n,index:e.index,sessionId:t}).catch(()=>{}),await sleep(r*1e3),r=Math.min(r*2,60)}}const HANDOVER_PART_FAILURES_BEFORE_REPORT=5;var SessionHandoff=class{input;anchor;incompatibleTargetDeploymentIds=new Set;attempts=0;worldUnsupported=!1;constructor(e){this.input=e}async tryTransfer(e,t){let{delivery:n}=e,r=readAcceptedDeploymentId(n);return r===void 0?{kind:`retained`,reason:`missing-deployment`}:r===this.input.deploymentId?{kind:`retained`,reason:`same-deployment`}:this.incompatibleTargetDeploymentIds.has(r)?{kind:`retained`,reason:`known-incompatible`}:e.handoffEligible?await this.transfer(t,r,{delivery:n}):{kind:`retained`,reason:`busy`}}async tryCompactionTransfer(e,t){let{deploymentId:n}=this.input;return this.incompatibleTargetDeploymentIds.has(n)?{kind:`retained`,reason:`known-incompatible`}:await this.transfer(e,n,{...t,reason:`compaction`})}async transfer(e,t,n){let{inbox:r}=this.input;if(this.worldUnsupported)return{kind:`retained`,reason:`unsupported-world`};if(r.awaitingHandover)return{kind:`retained`,reason:`busy`};let i=getWorkflowMetadata().workflowRunId,a=this.attempts++,o=`${i}:${String(a)}`,s=r.claimedTokens,c=createHook({token:`${i}:handoff:${String(a)}`});this.ensureAnchor();let l=new AbortController,u=Date.now();try{let a;try{let r=await startSuccessorStep({...n,activationToken:c.token,anchorRunId:this.input.sessionId,attemptKey:o,checkpoint:{...this.input.checkpoint,...e,version:15},hookTokens:s,sourceRunId:i,targetDeploymentId:t});if(r.kind===`not-idle`)return{kind:`retained`,reason:`not-idle`};if(r.kind===`unsupported`)return this.worldUnsupported=!0,{kind:`retained`,reason:`unsupported-world`};a=r.runId}catch(e){let n=await this.fenceAttempt(o);if(n.kind!==`live`)return n.kind===`done`&&await this.holdEveryToken(s),await this.reportRetained(`activation-failed`,t,e),{kind:`retained`,reason:`activation-failed`};a=n.runId}let d=r.whenTakenOver(l.signal).then(()=>({kind:`taken-over`})),f,p;for(;;){if(f===void 0&&p===void 0&&r.hasPending()){let e=u+3e5-Date.now();p=sleep(Math.max(0,e)).then(()=>({kind:`deadline`}))}let e;try{e=await Promise.race([d,c.then(e=>({kind:`verdict`,verdict:e})),f===void 0?p??r.whenArrival().then(()=>({kind:`arrival`})):f.ended.then(()=>({kind:`candidate-ended`}))])}finally{f?.stop()}if(e.kind===`taken-over`)return await this.handOver(o);if(e.kind===`arrival`)continue;let n=e.kind===`candidate-ended`?a:verdictRunId(e),i=await this.fenceAttempt(o,n);if(i.kind===`live`){a=i.runId,f=watchRunEnd(a);continue}i.kind===`done`&&i.runId!==verdictRunId(e)&&await this.holdEveryToken(s);let l=e.kind===`verdict`?await this.recover(e.verdict,{attemptKey:o,candidateRunId:a,hookTokens:s,targetDeploymentId:t}):this.recoverFromSilentCandidate(e.kind);return await this.reportRetained(l.reason,t,l.error),{kind:`retained`,reason:l.reason}}}finally{l.abort(),await disposeHook(c)}}async adoptTakeover(){return await reportSessionTakenOverOutsideAttemptStep({sessionId:this.input.sessionId}),this.ensureAnchor(),{kind:`transferred`}}async fenceAttempt(e,t){let n=createHook({experimental_minRetention:`7d`,token:handoffLeaseToken(e)});try{await claimHookOwnership(n)}catch(e){if(!isHookConflictError(e)||typeof e.conflictingRunId!=`string`)throw e;let n=e.conflictingRunId;return n===t||await hasRunEndedStep({runId:n})?{kind:`done`,runId:n}:{kind:`live`,runId:n}}return{kind:`won`}}async handOver(e){return await sendSessionHandover({attemptKey:e,payloads:this.input.inbox.drain(),sessionId:this.input.sessionId}),{kind:`transferred`}}async holdEveryToken(e){await this.input.inbox.claimSessionHooks(e,{force:!0})}recoverFromSilentCandidate(e){return{error:Error(e===`deadline`?`The successor run did not report within 5 minutes.`:`The successor run ended before the handoff settled.`),reason:`activation-failed`}}async recover(e,t){let{inbox:n}=this.input;switch(e.kind){case`active`:return{error:Error(`Successor reported active without a takeover.`),reason:`activation-failed`};case`incompatible`:return this.incompatibleTargetDeploymentIds.add(t.targetDeploymentId),e.payloads.length>0&&n.restore(e.payloads),{reason:`checkpoint-incompatible`};case`failed`:return e.claimedHooks===!0?(await n.claimSessionHooks(t.hookTokens,{force:!0,handover:t.attemptKey}),abandonHandoverIfRunEnds(n,{attemptKey:t.attemptKey,sourceRunId:e.runId??t.candidateRunId})):isHookConflictError(e.error)&&this.incompatibleTargetDeploymentIds.add(t.targetDeploymentId),e.payloads.length>0&&n.restore(e.payloads),{error:e.error,reason:`activation-failed`}}}ensureAnchor(){this.input.isInitialOwner&&this.anchor===void 0&&(this.anchor=createHook({token:sessionAnchorToken(this.input.sessionId)}))}async awaitAnchoredResult(){if(this.anchor===void 0)throw Error(`Session anchor was never claimed.`);try{return await this.anchor}finally{await this.disposeAnchor()}}async disposeAnchor(){if(this.anchor===void 0)return;let e=this.anchor;this.anchor=void 0,await disposeHook(e)}async reportRetained(e,t,n){let r=isObject(n)&&typeof n.message==`string`?n.message:void 0;await reportSessionHandoffRetainedStep({error:r,reason:e,sessionId:this.input.sessionId,sourceDeploymentId:this.input.deploymentId,targetDeploymentId:t})}};export{HANDOFF_LEASE_RETENTION,SESSION_CHECKPOINT_VERSION,SessionHandoff,handoffLeaseToken,sendSessionHandover,sessionAnchorToken};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{STUB_CONTEXT_KEY}from"#tool-stubs/types.js";import{workingTasks}from"#execution/tasks/table.js";import{settleCancelledTurnStep}from"#execution/settle-cancelled-turn-step.js";import"#execution/session-inbox/inbox.js";import{signalSessionAnchorStep}from"#execution/session/handoff-steps.js";import{SessionInputQueue}from"#execution/session/input-queue.js";import{createTurnControl}from"#execution/session/turn-control.js";import{SessionHandoff,sessionAnchorToken}from"#execution/session/handoff.js";import{notifyCancelledTaskCallerStep,notifyTurnCallerStep,resolveInitialTurnCallerStep}from"#subagents/parent-notification.js";import{withStubPlayback}from"#execution/tool-stubs/playback.js";import{nextTurnDelivery}from"#execution/session/next-input.js";import{SessionExecution}from"#execution/session/turn.js";import{SessionStateCursor}from"#execution/session/state-cursor.js";import{cancelWorkingTasks,sessionTaskTable}from"#execution/tasks/session.js";import{finalizeSession}from"#execution/session/finalization.js";function createSafeOuterWorkflowError(){let e=Error(`Agent workflow failed. Inspect the private session trace for details.`);return e.name=`EveWorkflowFailure`,e}async function runPreparedSession(e,t){let n=e.serializedContext[STUB_CONTEXT_KEY];n!==void 0&&n.rootSessionId===void 0&&(e.serializedContext[STUB_CONTEXT_KEY]={...n,rootSessionId:e.sessionId});let r=new SessionStateCursor({history:e.history,inbox:t,sessionWritable:e.sessionWritable,serializedContext:e.serializedContext,sessionState:e.sessionState}),i={caller:e.caller,terminalEmitted:!1},a=new SessionHandoff({checkpoint:{capabilities:e.capabilities,retention:e.retention,sessionTimeoutMs:e.sessionTimeoutMs},deploymentId:e.deploymentId,inbox:t,isInitialOwner:e.anchor.kind===`self`,sessionId:e.sessionId}),o={output:``,isError:!0},s;try{return o=await withStubPlayback(n,e.sessionId,async()=>{try{s=await runSessionLoop(e,{cursor:r,handoff:a,inbox:t,progress:i})}finally{await t.dispose()}return s.kind===`transferred`?e.anchor.kind===`self`?(o=await a.awaitAnchoredResult(),o):{output:``}:(o=await finalizeSession(s.outcome,{caller:i.caller,cursor:r,sessionWritable:e.sessionWritable}),i.terminalEmitted=!0,o)}),o}catch(t){throw i.terminalEmitted||await finalizeSession({error:t,kind:`failed`,turnId:i.turnId},{caller:i.caller,cursor:r,sessionWritable:e.sessionWritable}),createSafeOuterWorkflowError()}finally{await t.dispose(),await reportResultToAnchor(e,o,a,s)}}async function failSession(e){let t;try{t=await resolveInitialTurnCallerStep({serializedContext:e.serializedContext})}catch{}throw await finalizeSession({error:e.error,kind:`failed`},{caller:t,cursor:{serializedContext:e.serializedContext,sessionState:e.sessionState},sessionWritable:e.sessionWritable}),createSafeOuterWorkflowError()}async function reportResultToAnchor(e,t,n,r){switch(e.anchor.kind){case`self`:await n.disposeAnchor(),await e.anchor.notify?.(t);return;case`successor`:if(r?.kind===`transferred`)return;await signalSessionAnchorStep({result:t,token:sessionAnchorToken(e.sessionId)});return}}async function runSessionLoop(e,t){let{cursor:n,handoff:r,inbox:i,progress:a}=t,o=new SessionInputQueue,s=new SessionExecution({capabilities:e.capabilities,cursor:n,inbox:i,queue:o,sessionId:e.sessionId}),c=e.sessionTimeoutControl,nextParkedActivity=async()=>{for(;;){let e=await nextTurnDelivery({cursor:n,hasWorkingTasks:()=>workingTasks(sessionTaskTable(n)).length>0,inbox:i,queue:o});if(e.kind===`workflow`){await s.handleWorkflowMessage(e.message);continue}if(e.kind===`cancel-working-tasks`){await cancelWorkingTasks(n,`turn_cancelled`);continue}return e}},l=e.initialTurnControl??(e.anchor.kind===`self`?createTurnControl():void 0),u=0,d=!1,runTurn=async e=>{let t=a.caller,n=l;l=void 0,a.turnId=`turn_${String(u++)}`;let r=await s.runTurn(e,{caller:t,control:n});return r.caller!==void 0&&(a.caller=r.caller),r.compacted===!0&&(d=!0),r},transferState=()=>({history:n.history,serializedContext:n.serializedContext,sessionState:n.sessionState}),dueCompactionHandoff=()=>d&&a.caller===void 0&&workingTasks(sessionTaskTable(n)).length===0?{sessionTimeoutDeadline:e.sessionTimeoutDeadline}:void 0,tryCompactionHandoff=async()=>{let e=dueCompactionHandoff();if(e===void 0||o.pendingCount>0||i.hasPending())return;let t=await r.tryCompactionTransfer(transferState(),e);return t.kind===`transferred`?t:void 0},runDeliveredTurn=async e=>{let t=await r.tryTransfer(e,transferState()
|
|
1
|
+
import{STUB_CONTEXT_KEY}from"#tool-stubs/types.js";import{workingTasks}from"#execution/tasks/table.js";import{settleCancelledTurnStep}from"#execution/settle-cancelled-turn-step.js";import"#execution/session-inbox/inbox.js";import{signalSessionAnchorStep}from"#execution/session/handoff-steps.js";import{SessionInputQueue}from"#execution/session/input-queue.js";import{createTurnControl}from"#execution/session/turn-control.js";import{SessionHandoff,sessionAnchorToken}from"#execution/session/handoff.js";import{notifyCancelledTaskCallerStep,notifyTurnCallerStep,resolveInitialTurnCallerStep}from"#subagents/parent-notification.js";import{withStubPlayback}from"#execution/tool-stubs/playback.js";import{nextTurnDelivery}from"#execution/session/next-input.js";import{SessionExecution}from"#execution/session/turn.js";import{SessionStateCursor}from"#execution/session/state-cursor.js";import{cancelWorkingTasks,sessionTaskTable}from"#execution/tasks/session.js";import{finalizeSession}from"#execution/session/finalization.js";function createSafeOuterWorkflowError(){let e=Error(`Agent workflow failed. Inspect the private session trace for details.`);return e.name=`EveWorkflowFailure`,e}async function runPreparedSession(e,t){let n=e.serializedContext[STUB_CONTEXT_KEY];n!==void 0&&n.rootSessionId===void 0&&(e.serializedContext[STUB_CONTEXT_KEY]={...n,rootSessionId:e.sessionId});let r=new SessionStateCursor({history:e.history,inbox:t,sessionWritable:e.sessionWritable,serializedContext:e.serializedContext,sessionState:e.sessionState}),i={caller:e.caller,terminalEmitted:!1},a=new SessionHandoff({checkpoint:{capabilities:e.capabilities,retention:e.retention,sessionTimeoutMs:e.sessionTimeoutMs},deploymentId:e.deploymentId,inbox:t,isInitialOwner:e.anchor.kind===`self`,sessionId:e.sessionId}),o={output:``,isError:!0},s;try{return o=await withStubPlayback(n,e.sessionId,async()=>{try{s=await runSessionLoop(e,{cursor:r,handoff:a,inbox:t,progress:i})}finally{await t.dispose()}return s.kind===`transferred`?e.anchor.kind===`self`?(o=await a.awaitAnchoredResult(),o):{output:``}:(o=await finalizeSession(s.outcome,{caller:i.caller,cursor:r,sessionWritable:e.sessionWritable}),i.terminalEmitted=!0,o)}),o}catch(t){throw i.terminalEmitted||await finalizeSession({error:t,kind:`failed`,turnId:i.turnId},{caller:i.caller,cursor:r,sessionWritable:e.sessionWritable}),createSafeOuterWorkflowError()}finally{await t.dispose(),await reportResultToAnchor(e,o,a,s)}}async function failSession(e){let t;try{t=await resolveInitialTurnCallerStep({serializedContext:e.serializedContext})}catch{}throw await finalizeSession({error:e.error,kind:`failed`},{caller:t,cursor:{serializedContext:e.serializedContext,sessionState:e.sessionState},sessionWritable:e.sessionWritable}),createSafeOuterWorkflowError()}async function reportResultToAnchor(e,t,n,r){switch(e.anchor.kind){case`self`:await n.disposeAnchor(),await e.anchor.notify?.(t);return;case`successor`:if(r?.kind===`transferred`)return;await signalSessionAnchorStep({result:t,token:sessionAnchorToken(e.sessionId)});return}}async function runSessionLoop(e,t){let{cursor:n,handoff:r,inbox:i,progress:a}=t,o=new SessionInputQueue,s=new SessionExecution({capabilities:e.capabilities,cursor:n,inbox:i,queue:o,sessionId:e.sessionId}),c=e.sessionTimeoutControl,nextParkedActivity=async()=>{for(;;){let e=await nextTurnDelivery({cursor:n,hasWorkingTasks:()=>workingTasks(sessionTaskTable(n)).length>0,inbox:i,queue:o});if(e.kind===`workflow`){await s.handleWorkflowMessage(e.message);continue}if(e.kind===`cancel-working-tasks`){await cancelWorkingTasks(n,`turn_cancelled`);continue}return e}},l=e.initialTurnControl??(e.anchor.kind===`self`?createTurnControl():void 0),u=0,d=!1,runTurn=async e=>{let t=a.caller,n=l;l=void 0,a.turnId=`turn_${String(u++)}`;let r=await s.runTurn(e,{caller:t,control:n});return r.caller!==void 0&&(a.caller=r.caller),r.compacted===!0&&(d=!0),r},transferState=()=>({history:n.history,serializedContext:n.serializedContext,sessionState:n.sessionState}),dueCompactionHandoff=()=>d&&a.caller===void 0&&workingTasks(sessionTaskTable(n)).length===0?{sessionTimeoutDeadline:e.sessionTimeoutDeadline}:void 0,tryCompactionHandoff=async()=>{let e=dueCompactionHandoff();if(e===void 0||o.pendingCount>0||i.hasPending())return;let t=await r.tryCompactionTransfer(transferState(),e);return t.kind===`transferred`?t:void 0},runDeliveredTurn=async e=>{let t=await r.tryTransfer(e,transferState());return t.kind===`transferred`?t:(e.delivery.caller!==void 0&&(a.caller=e.delivery.caller),{action:await runTurn({delivery:e.delivery}),kind:`action`})},settleCancelledTurn=async e=>{let t=await n.advanceWithHistory(t=>settleCancelledTurnStep({...t,reportUsage:e}));return a.caller=void 0,t},awaitPrewarmedAction=async()=>{for(;;){let e=await nextParkedActivity();switch(e.kind){case`closed`:return i.takenOver?await r.adoptTakeover():{kind:`terminal`,outcome:{kind:`expired`}};case`expired`:case`reset`:return{kind:`terminal`,outcome:{kind:`expired`}};case`clear`:case`compact`:continue;case`turn`:return await runDeliveredTurn(e);case`cancel-turn`:continue}}},runInitialAction=async()=>{switch(e.start.kind){case`first-message`:return await awaitPrewarmedAction();case`parked`:return{action:{kind:`park`},kind:`action`};case`turn`:{let{input:t}=e.start;return{action:await runTurn(t===void 0?void 0:{delivery:t}),kind:`action`}}}};try{let[t,n]=await Promise.allSettled([runInitialAction(),c?.start()]);if(n.status===`rejected`)throw n.reason;if(t.status===`rejected`)throw t.reason;let o=t.value;if(o.kind!==`action`)return o;let l=o.action;for(;;){if(l.kind===`done`)return{kind:`terminal`,outcome:{action:l,kind:`done`}};if(l.cancelled===!0){let{caller:t}=a,n=await settleCancelledTurn(t!==void 0);if(t!==void 0){let r={caller:t,sessionId:e.sessionId};await notifyCancelledTaskCallerStep(n.usage===void 0?r:{...r,usage:n.usage})}}else l.settled!==void 0&&(a.caller!==void 0&&await notifyTurnCallerStep({caller:a.caller,lifecycle:`parked`,sessionId:e.sessionId,settled:l.settled}),a.caller=void 0);let t=await tryCompactionHandoff();if(t!==void 0)return t;let n=await nextParkedActivity();switch(n.kind){case`closed`:return i.takenOver?await r.adoptTakeover():{kind:`terminal`,outcome:{kind:`expired`}};case`expired`:case`reset`:return{kind:`terminal`,outcome:{kind:`expired`}};case`clear`:case`compact`:l=await runTurn({control:n.kind});continue;case`cancel-turn`:await s.cancelTurnWork(),await settleCancelledTurn(!1),l={...l,settled:void 0};continue;case`turn`:{let e=await runDeliveredTurn(n);if(e.kind!==`action`)return e;l=e.action;continue}}}}finally{l?.dispose(),await c?.dispose()}}export{failSession,runPreparedSession};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SessionInboxHandle } from "#execution/session-inbox/inbox.js";
|
|
2
|
+
export interface RunWatch {
|
|
3
|
+
/** Resolves once the run has ended; never settles once stopped. */
|
|
4
|
+
readonly ended: Promise<void>;
|
|
5
|
+
stop(): void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Watches a counterpart run so a wait on it has a way out when that run is
|
|
9
|
+
* gone. Durable sleeps between checks keep it deterministic under replay;
|
|
10
|
+
* `stop()` ends it before its next check.
|
|
11
|
+
*/
|
|
12
|
+
export declare function watchRunEnd(runId: string): RunWatch;
|
|
13
|
+
/**
|
|
14
|
+
* Opens the inbox's handover gate with nothing restored if `sourceRunId` ends
|
|
15
|
+
* before its handover arrives: an ended run can never send it. The watch
|
|
16
|
+
* starts only once a read waits on the gate, so a handover that arrives
|
|
17
|
+
* before it is needed costs nothing.
|
|
18
|
+
*/
|
|
19
|
+
export declare function abandonHandoverIfRunEnds(inbox: SessionInboxHandle, handover: {
|
|
20
|
+
readonly attemptKey: string;
|
|
21
|
+
readonly sourceRunId: string;
|
|
22
|
+
}): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{sleep}from"#compiled/@workflow/core/index.js";import{hasRunEndedStep}from"#execution/session/handoff-steps.js";function watchRunEnd(e){let t=!1;return{ended:(async()=>{for(;!t&&(await sleep(`10s`),!t);)if(await hasRunEndedStep({runId:e}).catch(()=>!1)&&!t)return;return await new Promise(()=>{})})(),stop(){t=!0}}}function abandonHandoverIfRunEnds(e,t){e.whenBlockedOnHandover().then(()=>{if(!e.awaitingHandover)return;let n=watchRunEnd(t.sourceRunId);e.whenHandoverSettled().then(()=>n.stop()),n.ended.then(()=>e.abandonHandover(t.attemptKey))})}export{abandonHandoverIfRunEnds,watchRunEnd};
|
|
@@ -21,5 +21,8 @@ export declare function sessionInboxHookToken(token: string): string;
|
|
|
21
21
|
export declare function isSessionInboxHookToken(token: string): boolean;
|
|
22
22
|
/** Logical address behind a physical session inbox hook token; `undefined` for any other hook. */
|
|
23
23
|
export declare function logicalSessionInboxToken(token: string): string | undefined;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Marker an owner older than handoff version 3 leaves while it releases its
|
|
26
|
+
* hooks, so ingress can tell "handoff in progress" from "no session".
|
|
27
|
+
*/
|
|
25
28
|
export declare function sessionHandoffMarkerToken(token: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{logicalSessionInboxToken,sessionCommandHookToken,sessionInboxHookToken}from"#execution/session-inbox/address.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{encodeMessageStreamEvent,stampMessageStreamEvent}from"#protocol/message.js";import{cancelRun,getRun,getWorld}from"#internal/workflow/runtime.js";import{isInactiveWorkflowRunError}from"#internal/workflow/is-inactive-workflow-run-error.js";import{logicalSessionToken,requireSessionId}from"#execution/session-inbox/resume.js";import{sessionFailed}from"#harness/session-machine/transitions.js";import{findSessionHookHolder}from"#execution/session-inbox/owner.js";import{EVE_SESSION_ATTRIBUTE,EVE_TYPE_ATTRIBUTE}from"#execution/eve-workflow-attributes.js";import{waitForSessionHooksRelease}from"#execution/session-inbox/hook-release.js";const log=createLogger(`execution.session-inbox`);async function endStrandedSession(e,t,n){let r=typeof t==`string`?await resolveStrandedSessionId(e):t.sessionId,{ownerRunId:i}=e,a=await getWorld();await assertStillServesSession(r,i);let o=await listOwnedSessionTokens(a,i);return await publishTerminalEvent(r,n),await cancelRuns(a,[i,r],`Session ended: its deployment was retired`),await waitForSessionHooksRelease([sessionCommandHookToken(r),logicalSessionToken(t),...o],i),log.warn(`Reset stranded session`,{currentEveVersion:resolveInstalledPackageInfo().version,previousEveVersion:e.eveVersion??`unknown`,previousSessionId:r,trigger:n}),r}async function assertStillServesSession(e,t){let n=await findSessionHookHolder(sessionInboxHookToken(sessionCommandHookToken(e)));if(n!==void 0&&n.runId!==t)throw Error(`Session "${e}" changed owners while it was being reset. Nothing was ended; retry the request.`)}async function publishTerminalEvent(e,t){let n=getRun(e).getWritable().getWriter(),r=(async()=>(await n.write(encodeMessageStreamEvent(stampMessageStreamEvent(sessionFailed({code:`session_stranded`,details:{trigger:t},message:`This session is no longer available.`,sessionId:e,usage:void 0})))),await n.close(),!0))();r.catch(()=>{});let i,a=new Promise(e=>{i=setTimeout(()=>e(!1),5e3)});try{await Promise.race([r,a])||log.warn(`Timed out finishing the stranded session stream`,{sessionId:e})}catch{log.warn(`Could not finish the stranded session stream`,{sessionId:e})}finally{clearTimeout(i),n.releaseLock()}}async function cancelRuns(e,t,n){for(let r of new Set(t))try{await cancelRun(e,r,{cancelReason:n})}catch(e){if(!isInactiveWorkflowRunError(e))throw e}}async function resolveStrandedSessionId(e){let{attributes:t,runId:n}=e.ownerRun,r=t[EVE_SESSION_ATTRIBUTE];if(r!==void 0)return r;if(e.hook!==void 0)try{return requireSessionId(await e.hook.metadata)}catch{}if(t[EVE_TYPE_ATTRIBUTE]!==void 0)return n;throw Error(`Cannot reset stranded session owner run "${n}": it does not record which session it serves. Cancel the run directly.`)}async function listOwnedSessionTokens(e,t){let n=[],r;for(let i=0;i<10;i++){let i=await e.hooks.list({pagination:{cursor:r,limit:100},resolveData:`none`,runId:t});for(let e of i.data){let t=logicalSessionInboxToken(e.token);t!==void 0&&n.push(t)}if(!i.hasMore||i.cursor===null)break;r=i.cursor}return n}export{endStrandedSession};
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{logicalSessionInboxToken,sessionCommandHookToken,sessionInboxHookToken}from"#execution/session-inbox/address.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{encodeMessageStreamEvent,stampMessageStreamEvent}from"#protocol/message.js";import{cancelRun,getRun,getWorld}from"#internal/workflow/runtime.js";import{isInactiveWorkflowRunError}from"#internal/workflow/is-inactive-workflow-run-error.js";import{logicalSessionToken,requireSessionId}from"#execution/session-inbox/resume.js";import{sessionFailed}from"#harness/session-machine/transitions.js";import{findSessionHookHolder}from"#execution/session-inbox/owner.js";import{EVE_SESSION_ATTRIBUTE,EVE_TYPE_ATTRIBUTE}from"#execution/eve-workflow-attributes.js";import{waitForSessionHooksRelease}from"#execution/session-inbox/hook-release.js";const log=createLogger(`execution.session-inbox`);async function endStrandedSession(e,t,n){let r=typeof t==`string`?await resolveStrandedSessionId(e):t.sessionId,{ownerRunId:i}=e,a=await getWorld();await assertStillServesSession(r,i);let o=await listOwnedSessionTokens(a,i);return await publishTerminalEvent(r,n),await cancelRuns(a,[i,r],`Session ended: its deployment was retired`),await waitForSessionHooksRelease([sessionCommandHookToken(r),logicalSessionToken(t),...o],{ownerRunId:i,sessionId:r}),log.warn(`Reset stranded session`,{currentEveVersion:resolveInstalledPackageInfo().version,previousEveVersion:e.eveVersion??`unknown`,previousSessionId:r,trigger:n}),r}async function assertStillServesSession(e,t){let n=await findSessionHookHolder(sessionInboxHookToken(sessionCommandHookToken(e)));if(n!==void 0&&n.runId!==t)throw Error(`Session "${e}" changed owners while it was being reset. Nothing was ended; retry the request.`)}async function publishTerminalEvent(e,t){let n=getRun(e).getWritable().getWriter(),r=(async()=>(await n.write(encodeMessageStreamEvent(stampMessageStreamEvent(sessionFailed({code:`session_stranded`,details:{trigger:t},message:`This session is no longer available.`,sessionId:e,usage:void 0})))),await n.close(),!0))();r.catch(()=>{});let i,a=new Promise(e=>{i=setTimeout(()=>e(!1),5e3)});try{await Promise.race([r,a])||log.warn(`Timed out finishing the stranded session stream`,{sessionId:e})}catch{log.warn(`Could not finish the stranded session stream`,{sessionId:e})}finally{clearTimeout(i),n.releaseLock()}}async function cancelRuns(e,t,n){for(let r of new Set(t))try{await cancelRun(e,r,{cancelReason:n})}catch(e){if(!isInactiveWorkflowRunError(e))throw e}}async function resolveStrandedSessionId(e){let{attributes:t,runId:n}=e.ownerRun,r=t[EVE_SESSION_ATTRIBUTE];if(r!==void 0)return r;if(e.hook!==void 0)try{return requireSessionId(await e.hook.metadata)}catch{}if(t[EVE_TYPE_ATTRIBUTE]!==void 0)return n;throw Error(`Cannot reset stranded session owner run "${n}": it does not record which session it serves. Cancel the run directly.`)}async function listOwnedSessionTokens(e,t){let n=[],r;for(let i=0;i<10;i++){let i=await e.hooks.list({pagination:{cursor:r,limit:100},resolveData:`none`,runId:t});for(let e of i.data){let t=logicalSessionInboxToken(e.token);t!==void 0&&n.push(t)}if(!i.hasMore||i.cursor===null)break;r=i.cursor}return n}export{endStrandedSession};
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
/** Longest ingress waits for a session hook to change hands. */
|
|
2
2
|
export declare const SESSION_HOOK_HANDOVER_TIMEOUT_MS = 30000;
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Waits until no run serving `session` holds any of the logical addresses. A
|
|
5
|
+
* handoff successor serves the same session, so only the session's end (or
|
|
6
|
+
* an unrelated run claiming the address afterwards) satisfies the wait.
|
|
7
|
+
*/
|
|
8
|
+
export declare function waitForSessionHooksRelease(logicalTokens: Iterable<string>, session: {
|
|
9
|
+
readonly ownerRunId: string;
|
|
10
|
+
readonly sessionId: string;
|
|
11
|
+
}): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{sessionInboxHookToken}from"#execution/session-inbox/address.js";import{findSessionHookHolder}from"#execution/session-inbox/owner.js";const SESSION_HOOK_HANDOVER_TIMEOUT_MS=3e4;async function waitForSessionHooksRelease(e,t){await Promise.all([...new Set(e)].map(e=>waitForHookRelease(sessionInboxHookToken(e),t)))}async function waitForHookRelease(e,t){let n=Date.now()+SESSION_HOOK_HANDOVER_TIMEOUT_MS;for(;;){if(
|
|
1
|
+
import{sessionInboxHookToken}from"#execution/session-inbox/address.js";import{requireSessionId}from"#execution/session-inbox/resume.js";import{findSessionHookHolder}from"#execution/session-inbox/owner.js";const SESSION_HOOK_HANDOVER_TIMEOUT_MS=3e4;async function waitForSessionHooksRelease(e,t){await Promise.all([...new Set(e)].map(e=>waitForHookRelease(sessionInboxHookToken(e),t)))}async function waitForHookRelease(e,t){let n=Date.now()+SESSION_HOOK_HANDOVER_TIMEOUT_MS;for(;;){if(!await servesSession(e,t))return;if(Date.now()>=n)throw Error(`Timed out waiting for session "${t.sessionId}" to release inbox "${e}".`);await new Promise(e=>setTimeout(e,20))}}async function servesSession(e,t){let n=await findSessionHookHolder(e);if(n===void 0)return!1;try{if(n.hook!==void 0)return requireSessionId(await n.hook.metadata)===t.sessionId}catch{}return n.runId===t.ownerRunId}export{SESSION_HOOK_HANDOVER_TIMEOUT_MS,waitForSessionHooksRelease};
|
|
@@ -11,12 +11,31 @@ export interface SessionFailurePayload {
|
|
|
11
11
|
readonly kind: "session-failure";
|
|
12
12
|
readonly error: string;
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* One part of the previous owner's accepted-but-unread payloads after a
|
|
16
|
+
* handoff. The runtime sends these commands; channel request bodies cannot
|
|
17
|
+
* supply them.
|
|
18
|
+
*/
|
|
19
|
+
export interface SessionHandoverPayload {
|
|
20
|
+
readonly kind: "handover";
|
|
21
|
+
/** The handoff attempt this answers; see {@link SessionHookBatchClaimOptions.handover}. */
|
|
22
|
+
readonly attemptKey: string;
|
|
23
|
+
/** Parts apply in index order; a retried send repeats its index. */
|
|
24
|
+
readonly index: number;
|
|
25
|
+
/** The last part; reads resume once every part before it has been applied. */
|
|
26
|
+
readonly final?: true;
|
|
27
|
+
readonly payloads: readonly Exclude<SessionInboxPayload, SessionHandoverPayload>[];
|
|
28
|
+
}
|
|
29
|
+
export type SessionInboxPayload = SessionFailurePayload | SessionHandoverPayload | HookPayload | SessionCommand | WorkflowToolRunMessage | AuthorizationCallbackPayload;
|
|
30
|
+
/**
|
|
31
|
+
* While a handover is awaited (see {@link SessionHookBatchClaimOptions.handover}),
|
|
32
|
+
* reads expose nothing; interrupt handlers still fire on arrival.
|
|
33
|
+
*/
|
|
15
34
|
export interface SessionInboxReader {
|
|
16
35
|
/**
|
|
17
36
|
* Waits for and removes the next accepted payload in arrival order, or
|
|
18
|
-
* `undefined` once
|
|
19
|
-
* time; the owner program is strictly sequential.
|
|
37
|
+
* `undefined` once no hook can deliver any more (released or taken over).
|
|
38
|
+
* Only one consumer waits at a time; the owner program is strictly sequential.
|
|
20
39
|
*/
|
|
21
40
|
next(): Promise<SessionInboxPayload | undefined>;
|
|
22
41
|
/** Removes every payload accepted so far, in arrival order. */
|
|
@@ -33,21 +52,62 @@ export interface SessionInboxReader {
|
|
|
33
52
|
* consumer still processes it in order.
|
|
34
53
|
*/
|
|
35
54
|
onInterrupt(handler: (payload: SessionInboxPayload) => void): () => void;
|
|
36
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Observes readable deliveries without consuming them; replays unread
|
|
57
|
+
* deliveries on subscription and when a handover arrives, so handlers must
|
|
58
|
+
* be idempotent.
|
|
59
|
+
*/
|
|
37
60
|
onDelivery(handler: (payload: SessionInboxPayload) => void): () => void;
|
|
38
61
|
/** Requeues payloads a successor handed back, ahead of everything pumped since. */
|
|
39
62
|
restore(payloads: readonly SessionInboxPayload[]): void;
|
|
40
63
|
}
|
|
64
|
+
export interface SessionHookClaimOptions {
|
|
65
|
+
/**
|
|
66
|
+
* Take the address over from the run that holds it instead of conflicting.
|
|
67
|
+
* Still rejects with `HookConflictError` when the holder's runtime cannot be
|
|
68
|
+
* taken from; throws `FatalError` when the World does not support it. A
|
|
69
|
+
* token this inbox already holds is claimed again on a fresh hook.
|
|
70
|
+
*/
|
|
71
|
+
readonly force?: boolean;
|
|
72
|
+
}
|
|
73
|
+
export interface SessionHookBatchClaimOptions extends SessionHookClaimOptions {
|
|
74
|
+
/**
|
|
75
|
+
* Expect a `handover` for this attempt key. Before any hook is created,
|
|
76
|
+
* reads stop exposing anything; as its parts arrive on any hook, their
|
|
77
|
+
* payloads take their place after what was queued at this call and ahead of
|
|
78
|
+
* everything accepted since. Reads resume after the final part.
|
|
79
|
+
*/
|
|
80
|
+
readonly handover?: string;
|
|
81
|
+
}
|
|
41
82
|
export interface SessionInboxOwnership {
|
|
42
83
|
/** Logical tokens currently claimed, in claim order. */
|
|
43
84
|
readonly claimedTokens: readonly string[];
|
|
44
|
-
claimSessionHook(token: string): Promise<void>;
|
|
85
|
+
claimSessionHook(token: string, options?: SessionHookClaimOptions): Promise<void>;
|
|
45
86
|
/** Registers every token as one batch; all claims settle before the first failure propagates. */
|
|
46
|
-
claimSessionHooks(tokens: readonly string[]): Promise<void>;
|
|
87
|
+
claimSessionHooks(tokens: readonly string[], options?: SessionHookBatchClaimOptions): Promise<void>;
|
|
47
88
|
}
|
|
48
89
|
export interface SessionInbox extends SessionInboxReader, SessionInboxOwnership {
|
|
49
90
|
}
|
|
50
91
|
export interface SessionInboxHandle extends SessionInbox {
|
|
92
|
+
/** A handover is expected and has not arrived; the session must not move again until it does. */
|
|
93
|
+
readonly awaitingHandover: boolean;
|
|
94
|
+
/** Resolves once no handover is awaited: it arrived, was abandoned, or the inbox closed. */
|
|
95
|
+
whenHandoverSettled(): Promise<void>;
|
|
96
|
+
/** Stops waiting for `attemptKey`'s handover, which can no longer arrive; queued payloads become readable. */
|
|
97
|
+
abandonHandover(attemptKey: string): void;
|
|
98
|
+
/** Resolves the first time a read waits on an awaited handover. */
|
|
99
|
+
whenBlockedOnHandover(): Promise<void>;
|
|
100
|
+
/** Resolves when the next payload arrives on any hook, readable or not. */
|
|
101
|
+
whenArrival(): Promise<void>;
|
|
102
|
+
/** Another run force-claimed every hook this inbox held. */
|
|
103
|
+
readonly takenOver: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Resolves once another run has force-claimed every hook this inbox held.
|
|
106
|
+
* Every payload delivered before the takeover is queued by then. Rejects if
|
|
107
|
+
* a reader fails, if the inbox is released or disposed first, or once
|
|
108
|
+
* `signal` aborts.
|
|
109
|
+
*/
|
|
110
|
+
whenTakenOver(signal?: AbortSignal): Promise<void>;
|
|
51
111
|
dispose(): Promise<void>;
|
|
52
112
|
/** Disposes every hook and returns each payload the hooks accepted but the owner never read. */
|
|
53
113
|
release(): Promise<SessionInboxPayload[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{sessionInboxHookToken}from"#execution/session-inbox/address.js";import{createHook,getWorkflowMetadata}from"#compiled/@workflow/core/index.js";import{claimHookOwnership,disposeHook}from"#execution/hook-ownership.js";import{releaseSessionHooksStep}from"#execution/session-inbox/release-step.js";function isInterrupt(e){return e.kind===`cancel`||e.kind===`reset`||e.kind===`session-timeout`||e.kind===`session-failure`}function createSessionInbox(e){let t=[],n=[],r=new Set,i=new Set,a=new Set,o,notify=()=>{for(let e of r)e();r.clear()},wait=()=>new Promise(e=>r.add(e)),closed=()=>t.every(e=>e.closed||e.stopping),pump=async e=>{let
|
|
1
|
+
import{sessionInboxHookToken}from"#execution/session-inbox/address.js";import{createHook,getWorkflowMetadata}from"#compiled/@workflow/core/index.js";import{claimHookOwnership,disposeHook,isHookForceClaimedError}from"#execution/hook-ownership.js";import{releaseSessionHooksStep}from"#execution/session-inbox/release-step.js";function isInterrupt(e){return e.kind===`cancel`||e.kind===`reset`||e.kind===`session-timeout`||e.kind===`session-failure`}function isDelivery(e){return e.kind===`send`||e.kind===`deliver`}function createSessionInbox(e){let t=[],n=[],r=new Set,i=new Set,a=new Set,o,s,c=new Set,l=new Set,u=!1,d=!1,notify=()=>{for(let e of r)e();r.clear()},wait=()=>new Promise(e=>r.add(e)),closed=()=>t.every(e=>e.closed||e.stopping),gated=()=>s!==void 0&&t.length>0,hasReadable=()=>!gated()&&n.length>0,throwIfFailed=()=>{if(o!==void 0)throw o.error},observe=(e,t,n)=>{for(let r of t)if(n(r))for(let t of e)t(r)},openHandover=()=>{s=void 0,observe(a,n,isDelivery),notify()},blockedOnHandover=()=>{if(gated()){for(let e of c)e();c.clear()}},acceptHandover=e=>{let t=s;if(t===void 0||e.attemptKey!==t.attemptKey||e.index<t.nextPart||t.parts.has(e.index))return;t.parts.set(e.index,e);let r=[];for(;;){let e=t.parts.get(t.nextPart);if(e===void 0)break;if(e.final===!0){observe(i,r,isInterrupt),openHandover();return}t.parts.delete(t.nextPart),t.nextPart+=1,n.splice(t.index,0,...e.payloads),t.index+=e.payloads.length,r.push(...e.payloads)}observe(i,r,isInterrupt),notify()},pump=async e=>{let r=e.hook[Symbol.asyncIterator]();try{for(;!e.stopping;){let e=await r.next();if(e.done)break;let t=e.value;for(let e of l)e();if(l.clear(),t.kind===`workflow-batch`){n.push(...t.messages),notify();continue}if(t.kind===`handover`){acceptHandover(t);continue}n.push(t),gated()||observe(a,[t],isDelivery),observe(i,[t],isInterrupt),notify()}}catch(n){if(e.stopping||e.replaced)return;isHookForceClaimedError(n)?(t.splice(t.indexOf(e),1),u=!0):o={error:n}}finally{e.closed=!0,notify()}},stop=async()=>{let e=t.splice(0);s=void 0,u=!1,d=!0;for(let t of e)t.stopping=!0;notify(),await Promise.all(e.map(({hook:e})=>disposeHook(e)));let r=n.splice(0);return notify(),r},claimSessionHook=async(n,r={})=>{if(!n)throw Error(`A session alias requires a nonempty continuation token.`);let i=r.force===!0,a=t.find(e=>e.token===n);if(a!==void 0&&!i)return await a.registered;if(a===void 0&&t.length>=256)throw Error(`A session may claim at most 256 addresses.`);let o={token:n,hook:createHook({token:sessionInboxHookToken(n),metadata:{sessionId:e},...i&&{experimental_force:!0}}),stopping:!1,closed:!1,replaced:!1};a===void 0?t.push(o):(a.replaced=!0,t.splice(t.indexOf(a),1,o)),d=!1;try{o.registered=claimHookOwnership(o.hook),await o.registered,u=!1,pump(o)}catch(e){throw a===void 0||a.closed?t.splice(t.indexOf(o),1):(a.replaced=!1,t.splice(t.indexOf(o),1,a)),e}};return{get claimedTokens(){return t.map(({token:e})=>e)},get awaitingHandover(){return s!==void 0},async whenHandoverSettled(){for(;s!==void 0;)await wait()},abandonHandover(e){s?.attemptKey===e&&openHandover()},whenBlockedOnHandover(){return new Promise(e=>c.add(e))},whenArrival(){return new Promise(e=>l.add(e))},get takenOver(){return u&&t.length===0},claimSessionHook,async claimSessionHooks(e,t={}){t.handover!==void 0&&(s={attemptKey:t.handover,index:n.length,nextPart:0,parts:new Map});let r=await Promise.allSettled([...new Set(e)].map(e=>claimSessionHook(e,t)));for(let e of r)if(e.status===`rejected`)throw e.reason},async whenTakenOver(e){for(e?.addEventListener(`abort`,notify,{once:!0});t.length>0||!u;){if(throwIfFailed(),d)throw Error(`The session inbox was released before a takeover.`);if(e?.aborted===!0)throw e.reason;await wait()}},async next(){for(;;){if(throwIfFailed(),hasReadable())return n.shift();if(closed())return;blockedOnHandover(),await wait()}},drain(){return throwIfFailed(),gated()?[]:n.splice(0)},takeLeading(e){throwIfFailed();let t=[];if(gated())return t;for(let r=n[0];r!==void 0&&e(r);r=n[0])t.push(r),n.shift();return t},hasPending(){return throwIfFailed(),hasReadable()},async whenPending(){for(;o===void 0&&!hasReadable()&&!closed();)blockedOnHandover(),await wait()},onInterrupt(e){return i.add(e),()=>i.delete(e)},onDelivery(e){if(a.add(e),!gated())for(let t of n)isDelivery(t)&&e(t);return()=>a.delete(e)},restore(e){if(t.length===0)throw Error(`Cannot restore session commands before reclaiming the session hooks.`);n.unshift(...e),s!==void 0&&(s.index+=e.length),notify()},async dispose(){await stop()},async release(){return t.length>0&&await releaseSessionHooksStep({ownerRunId:getWorkflowMetadata().workflowRunId,tokens:t.map(({hook:e})=>e.token)}),await stop()}}}function isWorkflowMessage(e){return e.kind===`agent-started`||e.kind===`started`||e.kind===`report`||e.kind===`reply`||e.kind===`request`||e.kind===`withdraw`||e.kind===`usage`||e.kind===`outcome`}export{createSessionInbox,isInterrupt,isWorkflowMessage};
|
|
@@ -7,9 +7,10 @@ export interface ResumedSessionInboxHook {
|
|
|
7
7
|
readonly sessionId: Promise<string>;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Resumes the current owner with the current command shape.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Resumes the current owner with the current command shape. Owners on
|
|
11
|
+
* handoff version 3 never leave an address unowned. An older owner releases
|
|
12
|
+
* its hooks before its successor claims them and leaves a marker for that
|
|
13
|
+
* interval, so delivery retries instead of
|
|
13
14
|
* reporting the session gone and letting a channel start a replacement.
|
|
14
15
|
* The retry window bounds this request, not the handoff: a marker that
|
|
15
16
|
* outlives it throws {@link SessionHandoffPendingError}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{decide}from"#ai/decide.js";async function runAgentRouterTask(e,t){"use workflow";let n=await chooseTarget(e.message,descriptions(t),t.abortSignal),{message:r,status:i}=await(await t.agent(n).send(e.message,{signal:t.abortSignal})).result();if(i===`failed`)throw Error(`Agent "${n}" failed to handle the task.`);return r??null}async function chooseTarget(e,t,n){"use step";let r=Object.keys(t);if(r.length===0)throw Error(`agentRouter requires at least one available agent with a description.`);return r.length===1?r[0]:(await decide({abortSignal:n,state:{message:e},questions:{route:{type:`choice`,instructions:`Which subagent should handle this task?`,criteria:t}}})).answers.route.choice}function descriptions(e){return Object.fromEntries(Object.entries(e.agents).flatMap(([e,t])=>{let n=t.description.trim();return n.length===0?[]:[[e,n]]}))}runAgentRouterTask.workflowId=`workflow//eve@0.
|
|
1
|
+
import{decide}from"#ai/decide.js";async function runAgentRouterTask(e,t){"use workflow";let n=await chooseTarget(e.message,descriptions(t),t.abortSignal),{message:r,status:i}=await(await t.agent(n).send(e.message,{signal:t.abortSignal})).result();if(i===`failed`)throw Error(`Agent "${n}" failed to handle the task.`);return r??null}async function chooseTarget(e,t,n){"use step";let r=Object.keys(t);if(r.length===0)throw Error(`agentRouter requires at least one available agent with a description.`);return r.length===1?r[0]:(await decide({abortSignal:n,state:{message:e},questions:{route:{type:`choice`,instructions:`Which subagent should handle this task?`,criteria:t}}})).answers.route.choice}function descriptions(e){return Object.fromEntries(Object.entries(e.agents).flatMap(([e,t])=>{let n=t.description.trim();return n.length===0?[]:[[e,n]]}))}runAgentRouterTask.workflowId=`workflow//eve@0.81.1//runAgentRouterTask`;export{runAgentRouterTask};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { DispatchSessionInput, Runtime, SessionCommand, SessionCommandResult } from "#channel/types.js";
|
|
2
2
|
import { type Run, type StartOptionsWithoutDeploymentId, type WorkflowFunction, type WorkflowMetadata } from "#internal/workflow/runtime.js";
|
|
3
3
|
import type { RuntimeCompiledArtifactsSource } from "#runtime/compiled-artifacts-source.js";
|
|
4
|
-
import { type SessionHandoffStart } from "#execution/session/entry-input.js";
|
|
5
|
-
import type { SessionCheckpoint } from "#execution/session/handoff.js";
|
|
6
4
|
import type { DynamicSubagentAgentConfig } from "#runtime/subagents/dynamic-agent-config.js";
|
|
7
5
|
interface WorkflowHookRecord {
|
|
8
6
|
readonly runId: string;
|
|
@@ -39,15 +37,6 @@ export declare function createWorkflowRuntime(config: {
|
|
|
39
37
|
readonly dynamicSubagentAgentConfig?: DynamicSubagentAgentConfig;
|
|
40
38
|
readonly nodeId?: string;
|
|
41
39
|
}): Runtime;
|
|
42
|
-
export type SessionOwnerStartInput = SessionHandoffStart & {
|
|
43
|
-
readonly activationToken: string;
|
|
44
|
-
/** Original run whose stream stays the public session stream. */
|
|
45
|
-
readonly anchorRunId: string;
|
|
46
|
-
readonly checkpoint: SessionCheckpoint;
|
|
47
|
-
readonly targetDeploymentId: string;
|
|
48
|
-
};
|
|
49
|
-
/** Starts a successor owner and binds its output to the original session stream. */
|
|
50
|
-
export declare function startSessionOwnerStep(input: SessionOwnerStartInput): Promise<void>;
|
|
51
40
|
/**
|
|
52
41
|
* Sends one command to a session through its stable command inbox. A caller
|
|
53
42
|
* holding the session id asked for that exact session, so a send to a
|
|
@@ -79,4 +68,10 @@ export declare function pollUntil<T>(check: () => Promise<T | undefined>, timeou
|
|
|
79
68
|
export declare function hasRunEnded(runId: string): Promise<boolean>;
|
|
80
69
|
/** Starts a workflow on the deployment executing this call. */
|
|
81
70
|
export declare function startWorkflowOnCurrentDeployment<TArgs extends unknown[], TResult>(workflow: WorkflowFunction<TArgs, TResult> | WorkflowMetadata, args: TArgs, options?: StartOptionsWithoutDeploymentId): Promise<Run<unknown> | Run<TResult>>;
|
|
71
|
+
/**
|
|
72
|
+
* Starts a run on an exact deployment. Every run eve starts records the eve
|
|
73
|
+
* version that started it: ingress and the stranded replay guard read it to
|
|
74
|
+
* decide whether this build can still execute the run.
|
|
75
|
+
*/
|
|
76
|
+
export declare function startWorkflowOnDeployment<TArgs extends unknown[], TResult>(workflow: WorkflowFunction<TArgs, TResult> | WorkflowMetadata, args: TArgs, deploymentId: string, options?: StartOptionsWithoutDeploymentId): Promise<Run<unknown> | Run<TResult>>;
|
|
82
77
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger,logError}from"#internal/logging.js";import{sessionCommandHookToken,sessionInboxHookToken}from"#execution/session-inbox/address.js";import{SessionPredecessorKey}from"#context/keys.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{walkCauseChain}from"#shared/errors.js";import{validateToolStubTargets}from"#tool-stubs/validate-targets.js";import{getHookByToken,getRun,getWorld,start}from"#internal/workflow/runtime.js";import{SessionStrandedError,describeStrandedSession}from"#channel/session-stranded-error.js";import{getCompiledRuntimeAgentBundle}from"#runtime/sessions/compiled-agent-cache.js";import{ROOT_RUNTIME_AGENT_NODE_ID}from"#runtime/graph.js";import{serializeContext}from"#context/serialize.js";import{AcceptedSessionIdentityError,SessionHandoffPendingError,logicalSessionToken,resolveSessionInbox,resumeSessionInbox}from"#execution/session-inbox/resume.js";import{resolveEffectiveAgentRuntime}from"#execution/effective-agent-config.js";import{context,trace}from"#compiled/@opentelemetry/api/index.js";import{EntityConflictError,HookNotFoundError,RunExpiredError,WorkflowRunNotFoundError}from"#compiled/@workflow/errors/index.js";import{StrandedSessionOwnerError,assertRunnableSessionInbox,findSessionHookHolder}from"#execution/session-inbox/owner.js";import{initializeSessionInstrumentation}from"#instrumentation/runtime.js";import{EVE_VERSION_ATTRIBUTE,buildSessionAttributes,buildSubagentRootAttributes,readParentLineage}from"#execution/eve-workflow-attributes.js";import{endStrandedSession}from"#execution/session-inbox/end-stranded-session.js";import{SESSION_HOOK_HANDOVER_TIMEOUT_MS,waitForSessionHooksRelease}from"#execution/session-inbox/hook-release.js";import{CONNECTION_EVENT_DELIVERY_WORKFLOW_NAME,CONNECTION_EVENT_WORKFLOW_NAME,SESSION_TIMEOUT_WORKFLOW_NAME,WORKFLOW_ENTRY_NAME,WORKFLOW_TOOL_RUN_WORKFLOW_NAME}from"#execution/stable-workflow-names.js";import{normalizeEveAttributes}from"#runtime/attributes/normalize.js";import{buildRunContext}from"#execution/runtime-context.js";import{readSessionEventHistory,readSessionEventStream,readSessionStreamTailIndex}from"#execution/session-event-stream.js";import{SESSION_HANDOFF_VERSION}from"#execution/session/entry-input.js";import{buildInvocationAttributes}from"#internal/invocation/metadata.js";import{isAgentTraceContext}from"#tracing/eve/agent-trace-context.js";const EVE_PACKAGE_INFO=resolveInstalledPackageInfo(),STABLE_ID_BASE=EVE_PACKAGE_INFO.name,log=createLogger(`execution.workflow-runtime`),workflowEntryReference={workflowId:`workflow//${STABLE_ID_BASE}//${WORKFLOW_ENTRY_NAME}`},sessionTimeoutWorkflowReference={workflowId:`workflow//${STABLE_ID_BASE}//${SESSION_TIMEOUT_WORKFLOW_NAME}`},workflowToolRunWorkflowReference={workflowId:`workflow//${STABLE_ID_BASE}//${WORKFLOW_TOOL_RUN_WORKFLOW_NAME}`},connectionEventWorkflowReference={workflowId:`workflow//${STABLE_ID_BASE}//${CONNECTION_EVENT_WORKFLOW_NAME}`},connectionEventDeliveryWorkflowReference={workflowId:`workflow//${STABLE_ID_BASE}//${CONNECTION_EVENT_DELIVERY_WORKFLOW_NAME}`};function createWorkflowRuntime(e){let startSession=async(t,n)=>{let r=await getCompiledRuntimeAgentBundle({compiledArtifactsSource:e.compiledArtifactsSource,nodeId:e.nodeId});t.toolStubs!==void 0&&t.toolStubs.rootSessionId===void 0&&validateToolStubTargets(t.toolStubs.rules,r.graph);let i=buildRunContext({bundle:r,dynamicSubagentAgentConfig:e.dynamicSubagentAgentConfig,run:t});n!==void 0&&i.set(SessionPredecessorKey,n);let a=resolveEffectiveAgentRuntime(r,i),o=initializeSessionInstrumentation({agentName:a.turnAgent.id,ctx:i}),s=a.limits?.sessionTimeoutMs,c=r.resolvedAgent.config?.experimental?.workflow?.retention??(o.action===`record`&&o.recordInputs&&o.recordOutputs?void 0:0),l=serializeContext(i),u=readParentLineage(l),d={kind:`initial`,input:t.input,ownerDeploymentId:await resolveCurrentWorkflowDeploymentId(),serializedContext:l};t.limits!==void 0&&(d.limits=t.limits),t.continuationConflictCommand!==void 0&&(d.continuationConflictCommand=t.continuationConflictCommand),s!==void 0&&(d.sessionTimeoutMs=s),c!==void 0&&(d.retention=c);let f={...u.sessionId===void 0?buildSessionAttributes({serializedContext:l}):buildSubagentRootAttributes({identity:{nodeId:r.nodeId??ROOT_RUNTIME_AGENT_NODE_ID},parentCallId:u.callId,parentSessionId:u.sessionId,parentTurnId:u.turnId,rootSessionId:u.rootSessionId??u.sessionId,serializedContext:l}),...t.externalInvocation===void 0?{}:buildInvocationAttributes(t.externalInvocation)},p;try{let e={allowReservedAttributes:!0,attributes:normalizeEveAttributes(f)};c!==void 0&&(e.experimental_retention=c),p=await startWorkflowOnDeployment(workflowEntryReference,[d],d.ownerDeploymentId,e)}catch(e){throw logError(log,`failed to start workflow run`,e,{continuationToken:t.continuationToken}),e}let m,getEvents=()=>(m??=readSessionEventStream(p.runId),m);return{get events(){return getEvents()},sessionId:p.runId}};return{async createSession(e){return await startSession(e)},async dispatchContinuation(e){let{command:t,continuationToken:n,successor:r}=e;try{return await dispatchWorkflowCommand(n,t)}catch(e){if(!(e instanceof StrandedSessionOwnerError))throw e;if(t.kind!==`send`||r===void 0)return await settleStrandedCommand(n,t,e);if((t.payload.inputResponses?.length??0)>0)throw sessionStrandedError(e,`Its pending requests can no longer be answered; send a new message to start a fresh session.`);let i=await endStrandedSession(e,n,`message`);return{sessionId:(await startSession(r,{sessionId:i})).sessionId,status:`accepted`}}},async dispatchSession(e){return await dispatchWorkflowSessionCommand(e)},async getEventStream(e,t){if(t?.follow===!1)return readSessionEventHistory(e,t.startIndex);try{await assertRunnableSessionInbox(sessionInboxHookToken(sessionCommandHookToken(e)))}catch(e){throw e instanceof StrandedSessionOwnerError?sessionStrandedError(e):e}return readSessionEventStream(e,t?.startIndex)},async getStreamTailIndex(e){return await readSessionStreamTailIndex(e)},resolveContinuation}}async function startSessionOwnerStep(e){"use step";let{activationToken:t,anchorRunId:n,checkpoint:r,targetDeploymentId:i,...a}=e,o={...a,activationToken:t,checkpoint:r,handoffVersion:SESSION_HANDOFF_VERSION,kind:`handoff`,ownerDeploymentId:i,sessionWritable:getRun(n).getWritable(),sessionId:n};await startWorkflowOnDeployment(workflowEntryReference,[o],i,r.retention===void 0?void 0:{experimental_retention:r.retention})}async function dispatchWorkflowCommand(e,t){let n;try{let r=await resumeSessionInbox(e,t);n={runId:r.ownerRunId,sessionId:await r.sessionId}}catch(n){if(n instanceof StrandedSessionOwnerError)throw n;let r=t.kind===`send`&&typeof e!=`string`,i={status:`session_not_active`,retryable:!0};if(n instanceof SessionHandoffPendingError){if(r)return i;throw n}if(isInactiveCommandTarget(n))return r&&await isRunActive(e.sessionId)?i:inactiveCommandResult(t);throw logError(log,`failed to dispatch session command`,n,{command:t.kind,token:e}),n}return t.kind===`reset`&&await waitForSessionHooksRelease([sessionCommandHookToken(n.sessionId),logicalSessionToken(e)],n.runId),activeCommandResult(t,n.sessionId)}async function settleStrandedCommand(e,t,n){switch(t.kind){case`reset`:return{previousSessionId:await endStrandedSession(n,e,`reset`),status:`reset`};case`send`:case`clear`:throw sessionStrandedError(n);default:return inactiveCommandResult(t)}}function sessionStrandedError(e,t){let n=e.eveVersion===void 0?{}:{eveVersion:e.eveVersion};return new SessionStrandedError(n,describeStrandedSession(n,t))}function activeCommandResult(e,t){return e.kind===`reset`?{previousSessionId:t,status:`reset`}:e.kind===`send`&&e.delivery!==void 0?{sessionId:t,status:`accepted`,deliveryId:e.delivery.deliveryId}:{sessionId:t,status:`accepted`}}function inactiveCommandResult(e){return e.kind===`send`?{status:`session_not_active`}:e.kind===`cancel`?{status:`no_active_turn`}:{status:`no_active_session`}}async function dispatchWorkflowSessionCommand(e){let t={sessionId:e.sessionId};try{return await dispatchWorkflowCommand(t,e.command)}catch(n){if(!(n instanceof StrandedSessionOwnerError))throw n;return await settleStrandedCommand(t,e.command,n)}}async function isRunActive(e){try{let t=await getRun(e).status;return t===`pending`||t===`running`}catch(e){if(isInactiveCommandTarget(e))return!1;throw e}}function isInactiveCommandTarget(e){if(e instanceof AcceptedSessionIdentityError)return!1;if(HookNotFoundError.is(e))return!0;for(let t of walkCauseChain(e))if(WorkflowRunNotFoundError.is(t)||RunExpiredError.is(t)||EntityConflictError.is(t))return!0;return!1}async function resolveContinuation(e){try{return await resolveSessionInbox(e)}catch(t){if(HookNotFoundError.is(t))return;throw logError(log,`failed to resolve session by continuation token`,t,{continuationToken:e}),t}}async function waitForCommandHookOwner(e){let t=await pollUntil(async()=>{try{return normalizeWorkflowHook(await getHookByToken(e))}catch(e){if(!HookNotFoundError.is(e))throw e;return}},SESSION_HOOK_HANDOVER_TIMEOUT_MS);if(t===void 0)throw new HookNotFoundError(e);return t}async function waitForSessionInbox(e){let t=sessionInboxHookToken(sessionCommandHookToken(e)),n=await pollUntil(async()=>await findSessionHookHolder(t)!==void 0||!await hasRunEnded(e)&&void 0,SESSION_HOOK_HANDOVER_TIMEOUT_MS);if(n===void 0)throw Error(`Session "${e}" did not start taking commands within ${SESSION_HOOK_HANDOVER_TIMEOUT_MS/1e3} seconds.`);return n}async function pollUntil(e,t){let n=Date.now()+t;for(let t=20;;t=Math.min(t*2,200)){let r=await e();if(r!==void 0||Date.now()>=n)return r;await new Promise(e=>setTimeout(e,t))}}async function hasRunEnded(e){try{let t=await getRun(e).status;return t!==`pending`&&t!==`running`}catch(e){for(let t of walkCauseChain(e))if(WorkflowRunNotFoundError.is(t))return!1;if(isInactiveCommandTarget(e))return!0;throw e}}async function startWorkflowOnCurrentDeployment(e,t,n){return await startWorkflowOnDeployment(e,t,await resolveCurrentWorkflowDeploymentId(),n)}async function startWorkflowOnDeployment(e,t,n,r){return await withWorkflowStartContext(async()=>{if(n.length===0||n===`latest`)throw Error(`Workflow starts require an exact deployment id.`);return await start(e,t,{...r,allowReservedAttributes:!0,attributes:{...r?.attributes,[EVE_VERSION_ATTRIBUTE]:EVE_PACKAGE_INFO.version},deploymentId:n})})}async function resolveCurrentWorkflowDeploymentId(){let e=process.env.VERCEL_DEPLOYMENT_ID?.trim()||await(await getWorld()).getDeploymentId();if(e.length===0||e===`latest`)throw Error(`Workflow runtime could not resolve an exact deployment id.`);return e}async function withWorkflowStartContext(e){let t=context.active(),n=isAgentTraceContext(t)?trace.deleteSpan(t):t;return await context.with(n,e)}function normalizeWorkflowHook(e){if(typeof e!=`object`||!e||!(`runId`in e))throw Error(`Workflow hook did not include a run id.`);let t=e.runId;if(typeof t!=`string`||t.length===0)throw Error(`Workflow hook did not include a run id.`);return{runId:t}}export{connectionEventDeliveryWorkflowReference,connectionEventWorkflowReference,createWorkflowRuntime,dispatchWorkflowSessionCommand,hasRunEnded,pollUntil,resolveContinuation,sessionTimeoutWorkflowReference,startSessionOwnerStep,startWorkflowOnCurrentDeployment,waitForCommandHookOwner,waitForSessionInbox,workflowEntryReference,workflowToolRunWorkflowReference};
|
|
1
|
+
import{createLogger,logError}from"#internal/logging.js";import{sessionCommandHookToken,sessionInboxHookToken}from"#execution/session-inbox/address.js";import{SessionPredecessorKey}from"#context/keys.js";import{SessionStrandedError,describeStrandedSession}from"#channel/session-stranded-error.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{walkCauseChain}from"#shared/errors.js";import{validateToolStubTargets}from"#tool-stubs/validate-targets.js";import{getHookByToken,getRun,getWorld,start}from"#internal/workflow/runtime.js";import{getCompiledRuntimeAgentBundle}from"#runtime/sessions/compiled-agent-cache.js";import{ROOT_RUNTIME_AGENT_NODE_ID}from"#runtime/graph.js";import{serializeContext}from"#context/serialize.js";import{AcceptedSessionIdentityError,SessionHandoffPendingError,logicalSessionToken,resolveSessionInbox,resumeSessionInbox}from"#execution/session-inbox/resume.js";import{resolveEffectiveAgentRuntime}from"#execution/effective-agent-config.js";import{context,trace}from"#compiled/@opentelemetry/api/index.js";import{EntityConflictError,HookNotFoundError,RunExpiredError,WorkflowRunNotFoundError}from"#compiled/@workflow/errors/index.js";import{StrandedSessionOwnerError,assertRunnableSessionInbox,findSessionHookHolder}from"#execution/session-inbox/owner.js";import{initializeSessionInstrumentation}from"#instrumentation/runtime.js";import{EVE_VERSION_ATTRIBUTE,buildSessionAttributes,buildSubagentRootAttributes,readParentLineage}from"#execution/eve-workflow-attributes.js";import{endStrandedSession}from"#execution/session-inbox/end-stranded-session.js";import{SESSION_HOOK_HANDOVER_TIMEOUT_MS,waitForSessionHooksRelease}from"#execution/session-inbox/hook-release.js";import{CONNECTION_EVENT_DELIVERY_WORKFLOW_NAME,CONNECTION_EVENT_WORKFLOW_NAME,SESSION_TIMEOUT_WORKFLOW_NAME,WORKFLOW_ENTRY_NAME,WORKFLOW_TOOL_RUN_WORKFLOW_NAME}from"#execution/stable-workflow-names.js";import{normalizeEveAttributes}from"#runtime/attributes/normalize.js";import{buildRunContext}from"#execution/runtime-context.js";import{readSessionEventHistory,readSessionEventStream,readSessionStreamTailIndex}from"#execution/session-event-stream.js";import{buildInvocationAttributes}from"#internal/invocation/metadata.js";import{isAgentTraceContext}from"#tracing/eve/agent-trace-context.js";const EVE_PACKAGE_INFO=resolveInstalledPackageInfo(),STABLE_ID_BASE=EVE_PACKAGE_INFO.name,log=createLogger(`execution.workflow-runtime`),workflowEntryReference={workflowId:`workflow//${STABLE_ID_BASE}//${WORKFLOW_ENTRY_NAME}`},sessionTimeoutWorkflowReference={workflowId:`workflow//${STABLE_ID_BASE}//${SESSION_TIMEOUT_WORKFLOW_NAME}`},workflowToolRunWorkflowReference={workflowId:`workflow//${STABLE_ID_BASE}//${WORKFLOW_TOOL_RUN_WORKFLOW_NAME}`},connectionEventWorkflowReference={workflowId:`workflow//${STABLE_ID_BASE}//${CONNECTION_EVENT_WORKFLOW_NAME}`},connectionEventDeliveryWorkflowReference={workflowId:`workflow//${STABLE_ID_BASE}//${CONNECTION_EVENT_DELIVERY_WORKFLOW_NAME}`};function createWorkflowRuntime(e){let startSession=async(t,n)=>{let r=await getCompiledRuntimeAgentBundle({compiledArtifactsSource:e.compiledArtifactsSource,nodeId:e.nodeId});t.toolStubs!==void 0&&t.toolStubs.rootSessionId===void 0&&validateToolStubTargets(t.toolStubs.rules,r.graph);let i=buildRunContext({bundle:r,dynamicSubagentAgentConfig:e.dynamicSubagentAgentConfig,run:t});n!==void 0&&i.set(SessionPredecessorKey,n);let a=resolveEffectiveAgentRuntime(r,i),o=initializeSessionInstrumentation({agentName:a.turnAgent.id,ctx:i}),s=a.limits?.sessionTimeoutMs,c=r.resolvedAgent.config?.experimental?.workflow?.retention??(o.action===`record`&&o.recordInputs&&o.recordOutputs?void 0:0),l=serializeContext(i),u=readParentLineage(l),d={kind:`initial`,input:t.input,ownerDeploymentId:await resolveCurrentWorkflowDeploymentId(),serializedContext:l};t.limits!==void 0&&(d.limits=t.limits),t.continuationConflictCommand!==void 0&&(d.continuationConflictCommand=t.continuationConflictCommand),s!==void 0&&(d.sessionTimeoutMs=s),c!==void 0&&(d.retention=c);let f={...u.sessionId===void 0?buildSessionAttributes({serializedContext:l}):buildSubagentRootAttributes({identity:{nodeId:r.nodeId??ROOT_RUNTIME_AGENT_NODE_ID},parentCallId:u.callId,parentSessionId:u.sessionId,parentTurnId:u.turnId,rootSessionId:u.rootSessionId??u.sessionId,serializedContext:l}),...t.externalInvocation===void 0?{}:buildInvocationAttributes(t.externalInvocation)},p;try{let e={allowReservedAttributes:!0,attributes:normalizeEveAttributes(f)};c!==void 0&&(e.experimental_retention=c),p=await startWorkflowOnDeployment(workflowEntryReference,[d],d.ownerDeploymentId,e)}catch(e){throw logError(log,`failed to start workflow run`,e,{continuationToken:t.continuationToken}),e}let m,getEvents=()=>(m??=readSessionEventStream(p.runId),m);return{get events(){return getEvents()},sessionId:p.runId}};return{async createSession(e){return await startSession(e)},async dispatchContinuation(e){let{command:t,continuationToken:n,successor:r}=e;try{return await dispatchWorkflowCommand(n,t)}catch(e){if(!(e instanceof StrandedSessionOwnerError))throw e;if(t.kind!==`send`||r===void 0)return await settleStrandedCommand(n,t,e);if((t.payload.inputResponses?.length??0)>0)throw sessionStrandedError(e,`Its pending requests can no longer be answered; send a new message to start a fresh session.`);let i=await endStrandedSession(e,n,`message`);return{sessionId:(await startSession(r,{sessionId:i})).sessionId,status:`accepted`}}},async dispatchSession(e){return await dispatchWorkflowSessionCommand(e)},async getEventStream(e,t){if(t?.follow===!1)return readSessionEventHistory(e,t.startIndex);try{await assertRunnableSessionInbox(sessionInboxHookToken(sessionCommandHookToken(e)))}catch(e){throw e instanceof StrandedSessionOwnerError?sessionStrandedError(e):e}return readSessionEventStream(e,t?.startIndex)},async getStreamTailIndex(e){return await readSessionStreamTailIndex(e)},resolveContinuation}}async function dispatchWorkflowCommand(e,t){let n;try{let r=await resumeSessionInbox(e,t);n={runId:r.ownerRunId,sessionId:await r.sessionId}}catch(n){if(n instanceof StrandedSessionOwnerError)throw n;let r=t.kind===`send`&&typeof e!=`string`,i={status:`session_not_active`,retryable:!0};if(n instanceof SessionHandoffPendingError){if(r)return i;throw n}if(isInactiveCommandTarget(n))return r&&await isRunActive(e.sessionId)?i:inactiveCommandResult(t);throw logError(log,`failed to dispatch session command`,n,{command:t.kind,token:e}),n}return t.kind===`reset`&&await waitForSessionHooksRelease([sessionCommandHookToken(n.sessionId),logicalSessionToken(e)],{ownerRunId:n.runId,sessionId:n.sessionId}),activeCommandResult(t,n.sessionId)}async function settleStrandedCommand(e,t,n){switch(t.kind){case`reset`:return{previousSessionId:await endStrandedSession(n,e,`reset`),status:`reset`};case`send`:case`clear`:throw sessionStrandedError(n);default:return inactiveCommandResult(t)}}function sessionStrandedError(e,t){let n=e.eveVersion===void 0?{}:{eveVersion:e.eveVersion};return new SessionStrandedError(n,describeStrandedSession(n,t))}function activeCommandResult(e,t){return e.kind===`reset`?{previousSessionId:t,status:`reset`}:e.kind===`send`&&e.delivery!==void 0?{sessionId:t,status:`accepted`,deliveryId:e.delivery.deliveryId}:{sessionId:t,status:`accepted`}}function inactiveCommandResult(e){return e.kind===`send`?{status:`session_not_active`}:e.kind===`cancel`?{status:`no_active_turn`}:{status:`no_active_session`}}async function dispatchWorkflowSessionCommand(e){let t={sessionId:e.sessionId};try{return await dispatchWorkflowCommand(t,e.command)}catch(n){if(!(n instanceof StrandedSessionOwnerError))throw n;return await settleStrandedCommand(t,e.command,n)}}async function isRunActive(e){try{let t=await getRun(e).status;return t===`pending`||t===`running`}catch(e){if(isInactiveCommandTarget(e))return!1;throw e}}function isInactiveCommandTarget(e){if(e instanceof AcceptedSessionIdentityError)return!1;if(HookNotFoundError.is(e))return!0;for(let t of walkCauseChain(e))if(WorkflowRunNotFoundError.is(t)||RunExpiredError.is(t)||EntityConflictError.is(t))return!0;return!1}async function resolveContinuation(e){try{return await resolveSessionInbox(e)}catch(t){if(HookNotFoundError.is(t))return;throw logError(log,`failed to resolve session by continuation token`,t,{continuationToken:e}),t}}async function waitForCommandHookOwner(e){let t=await pollUntil(async()=>{try{return normalizeWorkflowHook(await getHookByToken(e))}catch(e){if(!HookNotFoundError.is(e))throw e;return}},SESSION_HOOK_HANDOVER_TIMEOUT_MS);if(t===void 0)throw new HookNotFoundError(e);return t}async function waitForSessionInbox(e){let t=sessionInboxHookToken(sessionCommandHookToken(e)),n=await pollUntil(async()=>await findSessionHookHolder(t)!==void 0||!await hasRunEnded(e)&&void 0,SESSION_HOOK_HANDOVER_TIMEOUT_MS);if(n===void 0)throw Error(`Session "${e}" did not start taking commands within ${SESSION_HOOK_HANDOVER_TIMEOUT_MS/1e3} seconds.`);return n}async function pollUntil(e,t){let n=Date.now()+t;for(let t=20;;t=Math.min(t*2,200)){let r=await e();if(r!==void 0||Date.now()>=n)return r;await new Promise(e=>setTimeout(e,t))}}async function hasRunEnded(e){try{let t=await getRun(e).status;return t!==`pending`&&t!==`running`}catch(e){for(let t of walkCauseChain(e))if(WorkflowRunNotFoundError.is(t))return!1;if(isInactiveCommandTarget(e))return!0;throw e}}async function startWorkflowOnCurrentDeployment(e,t,n){return await startWorkflowOnDeployment(e,t,await resolveCurrentWorkflowDeploymentId(),n)}async function startWorkflowOnDeployment(e,t,n,r){return await withWorkflowStartContext(async()=>{if(n.length===0||n===`latest`)throw Error(`Workflow starts require an exact deployment id.`);return await start(e,t,{...r,allowReservedAttributes:!0,attributes:{...r?.attributes,[EVE_VERSION_ATTRIBUTE]:EVE_PACKAGE_INFO.version},deploymentId:n})})}async function resolveCurrentWorkflowDeploymentId(){let e=process.env.VERCEL_DEPLOYMENT_ID?.trim()||await(await getWorld()).getDeploymentId();if(e.length===0||e===`latest`)throw Error(`Workflow runtime could not resolve an exact deployment id.`);return e}async function withWorkflowStartContext(e){let t=context.active(),n=isAgentTraceContext(t)?trace.deleteSpan(t):t;return await context.with(n,e)}function normalizeWorkflowHook(e){if(typeof e!=`object`||!e||!(`runId`in e))throw Error(`Workflow hook did not include a run id.`);let t=e.runId;if(typeof t!=`string`||t.length===0)throw Error(`Workflow hook did not include a run id.`);return{runId:t}}export{connectionEventDeliveryWorkflowReference,connectionEventWorkflowReference,createWorkflowRuntime,dispatchWorkflowSessionCommand,hasRunEnded,pollUntil,resolveContinuation,sessionTimeoutWorkflowReference,startWorkflowOnCurrentDeployment,startWorkflowOnDeployment,waitForCommandHookOwner,waitForSessionInbox,workflowEntryReference,workflowToolRunWorkflowReference};
|
|
@@ -4,11 +4,13 @@ import type { GitHubConfig } from "eve/extensions/git/sandbox";
|
|
|
4
4
|
export type CredentialPolicyBroker = (sandbox: SandboxSession, rules: Record<string, Record<string, string>>) => Promise<void>;
|
|
5
5
|
export type { GitHubLeaseRule } from "eve/extensions/git/sandbox";
|
|
6
6
|
export type GitHubLeaseBroker = GitHubConfig["broker"];
|
|
7
|
+
export type GitHubAttribution = NonNullable<GitHubConfig["attribution"]>;
|
|
7
8
|
declare const _default: import("eve/extension").ExtensionHandle<z.ZodObject<{
|
|
8
9
|
github: z.ZodOptional<z.ZodObject<{
|
|
9
10
|
connector: z.ZodString;
|
|
10
11
|
org: z.ZodString;
|
|
11
12
|
broker: z.ZodCustom<(sandbox: SandboxSession, rules: Readonly<Record<string, readonly import("eve/extensions/git/sandbox").GitHubLeaseRule[]>> | null) => Promise<void>, (sandbox: SandboxSession, rules: Readonly<Record<string, readonly import("eve/extensions/git/sandbox").GitHubLeaseRule[]>> | null) => Promise<void>>;
|
|
13
|
+
attribution: z.ZodOptional<z.ZodCustom<(ctx: import("../../../tools/definition.ts").ToolContext) => import("eve/extensions/git/sandbox").GitHubCoAuthor | null | PromiseLike<import("eve/extensions/git/sandbox").GitHubCoAuthor | null>, (ctx: import("../../../tools/definition.ts").ToolContext) => import("eve/extensions/git/sandbox").GitHubCoAuthor | null | PromiseLike<import("eve/extensions/git/sandbox").GitHubCoAuthor | null>>>;
|
|
12
14
|
}, z.core.$strip>>;
|
|
13
15
|
vercel: z.ZodOptional<z.ZodObject<{
|
|
14
16
|
connector: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z}from"#compiled/zod/index.js";import{defineExtension}from"eve/extension";const fn=()=>z.custom(e=>typeof e==`function`),delivery=z.enum([`firewall`,`command`]).default(`firewall`),reasoning=z.enum([`provider-default`,`none`,`minimal`,`low`,`medium`,`high`,`xhigh`]),openaiReasoningEffort=z.enum([`none`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`]);var extension_default=defineExtension({config:z.object({github:z.object({connector:z.string().min(1),org:z.string().min(1),broker:fn()}).optional(),vercel:z.object({connector:z.string().min(1),delivery}).optional(),broker:fn().optional(),worker:z.object({model:z.string().min(1),reasoning,openaiReasoningEffort:openaiReasoningEffort.optional()}).optional()})});export{extension_default as default};
|
|
1
|
+
import{z}from"#compiled/zod/index.js";import{defineExtension}from"eve/extension";const fn=()=>z.custom(e=>typeof e==`function`),delivery=z.enum([`firewall`,`command`]).default(`firewall`),reasoning=z.enum([`provider-default`,`none`,`minimal`,`low`,`medium`,`high`,`xhigh`]),openaiReasoningEffort=z.enum([`none`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`]);var extension_default=defineExtension({config:z.object({github:z.object({connector:z.string().min(1),org:z.string().min(1),broker:fn(),attribution:fn().optional()}).optional(),vercel:z.object({connector:z.string().min(1),delivery}).optional(),broker:fn().optional(),worker:z.object({model:z.string().min(1),reasoning,openaiReasoningEffort:openaiReasoningEffort.optional()}).optional()})});export{extension_default as default};
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { GitHubAuthorize, GitHubConfig } from "./lib/github-shell.ts";
|
|
3
3
|
export type { GitHubLeaseRule } from "./lib/github-shell.ts";
|
|
4
|
+
export type { GitHubCoAuthor } from "./lib/github-shell.ts";
|
|
4
5
|
export type { GitHubAuthorize, GitHubAuthorizeRequest } from "./lib/github-shell.ts";
|
|
5
6
|
export type GitHubLeaseBroker = GitHubConfig["broker"];
|
|
7
|
+
export type GitHubAttribution = NonNullable<GitHubConfig["attribution"]>;
|
|
6
8
|
declare const _default: import("eve/extension").ExtensionHandle<z.ZodObject<{
|
|
7
9
|
github: z.ZodOptional<z.ZodObject<{
|
|
8
10
|
connector: z.ZodString;
|
|
9
11
|
org: z.ZodString;
|
|
10
12
|
broker: z.ZodCustom<(sandbox: import("../../../shared/sandbox-session.ts").SandboxSession, rules: Readonly<Record<string, readonly import("./lib/github-shell.ts").GitHubLeaseRule[]>> | null) => Promise<void>, (sandbox: import("../../../shared/sandbox-session.ts").SandboxSession, rules: Readonly<Record<string, readonly import("./lib/github-shell.ts").GitHubLeaseRule[]>> | null) => Promise<void>>;
|
|
13
|
+
attribution: z.ZodOptional<z.ZodCustom<(ctx: import("../../../tools/definition.ts").ToolContext) => import("./lib/attribution.ts").GitHubCoAuthor | null | PromiseLike<import("./lib/attribution.ts").GitHubCoAuthor | null>, (ctx: import("../../../tools/definition.ts").ToolContext) => import("./lib/attribution.ts").GitHubCoAuthor | null | PromiseLike<import("./lib/attribution.ts").GitHubCoAuthor | null>>>;
|
|
11
14
|
authorize: z.ZodOptional<z.ZodCustom<GitHubAuthorize, GitHubAuthorize>>;
|
|
12
15
|
}, z.core.$strip>>;
|
|
13
16
|
}, z.core.$strip>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z}from"#compiled/zod/index.js";import{defineExtension}from"eve/extension";const fn=()=>z.custom(e=>typeof e==`function`);var extension_default=defineExtension({config:z.object({github:z.object({connector:z.string().min(1),org:z.string().min(1),broker:fn(),authorize:fn().optional()}).optional()})});export{extension_default as default};
|
|
1
|
+
import{z}from"#compiled/zod/index.js";import{defineExtension}from"eve/extension";const fn=()=>z.custom(e=>typeof e==`function`);var extension_default=defineExtension({config:z.object({github:z.object({connector:z.string().min(1),org:z.string().min(1),broker:fn(),attribution:fn().optional(),authorize:fn().optional()}).optional()})});export{extension_default as default};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SandboxSession } from "eve/sandbox";
|
|
2
|
+
/** The person a commit or pull request is created for. */
|
|
3
|
+
export interface GitHubCoAuthor {
|
|
4
|
+
readonly email: string;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
}
|
|
7
|
+
/** Format a validated `Co-authored-by` trailer. */
|
|
8
|
+
export declare function coAuthorTrailer(coAuthor: GitHubCoAuthor): string;
|
|
9
|
+
/**
|
|
10
|
+
* Append a trailer unless the body already carries it. Git and GitHub read
|
|
11
|
+
* only the last paragraph as trailers, so an existing trailer block is
|
|
12
|
+
* extended rather than followed by a new paragraph.
|
|
13
|
+
*/
|
|
14
|
+
export declare function withTrailer(body: string, trailer: string): string;
|
|
15
|
+
/** True for the commands whose bodies carry attribution. */
|
|
16
|
+
export declare function isAttributable(argv: readonly string[]): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Add the trailer to the bodies eve publishes: `gh-signed-commit` commit
|
|
19
|
+
* messages and `gh pr create` descriptions. Other commands are unchanged.
|
|
20
|
+
* A `--body-file` is copied to `bodyFile`, so the model's file stays intact.
|
|
21
|
+
*/
|
|
22
|
+
export declare function attributeCommand(argv: readonly string[], trailer: string, sandbox: Pick<SandboxSession, "readTextFile" | "writeTextFile">, workingDirectory: string, bodyFile: string): Promise<string[]>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{posix}from"node:path";const BODY_FLAGS=new Set([`-b`,`--body`]),BODY_FILE_FLAGS=new Set([`-F`,`--body-file`]),TRAILER_LINE=/^[A-Za-z0-9][A-Za-z0-9-]*: \S/u;function coAuthorTrailer(e){let t=e.name.trim(),n=e.email.trim();if(!t||/[\r\n<>]/u.test(t))throw Error(`Invalid co-author name ${JSON.stringify(e.name)}.`);if(!/^[^\s<>@]+@[^\s<>@]+$/u.test(n))throw Error(`Invalid co-author email ${JSON.stringify(e.email)}.`);return`Co-authored-by: ${t} <${n}>`}function withTrailer(e,t){if(e.split(/\r?\n/u).map(e=>e.trim().toLowerCase()).includes(t.toLowerCase()))return e;let n=e.trimEnd();if(!n)return t;let r=n.split(/\r?\n[ \t]*\r?\n/u),i=r.at(-1).split(/\r?\n/u);return`${n}${r.length>1&&i.every(e=>TRAILER_LINE.test(e))?`
|
|
2
|
+
`:`
|
|
3
|
+
|
|
4
|
+
`}${t}`}function isAttributable(e){return e[0]===`gh-signed-commit`||e[0]===`gh`&&e[1]===`pr`&&e[2]===`create`}async function attributeCommand(e,t,n,r,i){let a=[...e];if(e[0]===`gh-signed-commit`){let e=a.lastIndexOf(`-b`);return e===-1?a.push(`-b`,t):a[e+1]=withTrailer(a[e+1]??``,t),a}if(e[0]!==`gh`||e[1]!==`pr`||e[2]!==`create`)return a;for(let e=a.length-1;e>=3;--e){let o=a[e],[s,c]=splitInline(o);if(BODY_FLAGS.has(s)){let n=withTrailer(c??a[e+1]??``,t);return c===void 0?a[e+1]=n:a[e]=`${s}=${n}`,a}if(BODY_FILE_FLAGS.has(s)){let o=c??a[e+1];if(!o||o===`-`)return a;let l=await n.readTextFile({path:posix.resolve(r,o)});if(l===null)throw Error(`Pull request body file ${JSON.stringify(o)} not found.`);return await n.writeTextFile({content:withTrailer(l,t),path:i}),c===void 0?a[e+1]=i:a[e]=`${s}=${i}`,a}}return a}function splitInline(e){if(!e.startsWith(`--`)){if(!e.startsWith(`-`)||e.length<=2)return[e,void 0];let t=e.slice(2);return[e.slice(0,2),t.startsWith(`=`)?t.slice(1):t]}let t=e.indexOf(`=`);return t===-1?[e,void 0]:[e.slice(0,t),e.slice(t+1)]}export{attributeCommand,coAuthorTrailer,isAttributable,withTrailer};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{executeGitHubShell,githubShellApproval}from"./github-shell.js";import{githubShellInputSchema}from"./github-shell-schema.js";import{z}from"#compiled/zod/index.js";import{defineTool}from"eve/tools";const GH_TOOL_DESCRIPTION=`Run one authenticated GitHub command in the sandbox. The full gh CLI surface is available. Commands run without an approval prompt; description records the GitHub-side result the command intends to produce. Declare exactly one repository in the configured GitHub organization: Connect mints the real token for only that repository, and the sandbox process receives only a placeholder GH_TOKEN that the firewall exchanges on matching GitHub requests. GitHub rejects access outside the token's server-side repository scope; commands that explicitly name another repository with -R, --repo, --repo=, or a repository argument to gh repo clone, view, or fork are denied before minting. Use simple argv quoting only, never environment assignments, pipes, redirects, substitutions, or other shell syntax. gh alias and extension commands may execute local code, but remain sandboxed. Use git here only for authenticated fetch, pull, push, or ls-remote against a strictly validated origin, and use gh-signed-commit only with --repo. Use ordinary bash for local git status, diff, add, commit, and rebase.`;function defineGhTool(e,t=GH_TOOL_DESCRIPTION){return defineTool({description:t,approval:({toolInput:e})=>githubShellApproval(e),inputSchema:githubShellInputSchema,outputSchema:z.object({exitCode:z.number().int(),stderr:z.string(),stdout:z.string(),truncated:z.boolean()}),execute(t,n){return executeGitHubShell(t,e(),{auth:n.session.auth.current,getSandbox:()=>n.getSandbox(),sessionId:n.session.id})}})}export{GH_TOOL_DESCRIPTION,defineGhTool};
|
|
1
|
+
import{executeGitHubShell,githubShellApproval}from"./github-shell.js";import{githubShellInputSchema}from"./github-shell-schema.js";import{z}from"#compiled/zod/index.js";import{defineTool}from"eve/tools";const GH_TOOL_DESCRIPTION=`Run one authenticated GitHub command in the sandbox. The full gh CLI surface is available. Commands run without an approval prompt; description records the GitHub-side result the command intends to produce. Declare exactly one repository in the configured GitHub organization: Connect mints the real token for only that repository, and the sandbox process receives only a placeholder GH_TOKEN that the firewall exchanges on matching GitHub requests. GitHub rejects access outside the token's server-side repository scope; commands that explicitly name another repository with -R, --repo, --repo=, or a repository argument to gh repo clone, view, or fork are denied before minting. Use simple argv quoting only, never environment assignments, pipes, redirects, substitutions, or other shell syntax. gh alias and extension commands may execute local code, but remain sandboxed. Use git here only for authenticated fetch, pull, push, or ls-remote against a strictly validated origin, and use gh-signed-commit only with --repo. Use ordinary bash for local git status, diff, add, commit, and rebase.`;function defineGhTool(e,t=GH_TOOL_DESCRIPTION){return defineTool({description:t,approval:({toolInput:e})=>githubShellApproval(e),inputSchema:githubShellInputSchema,outputSchema:z.object({exitCode:z.number().int(),stderr:z.string(),stdout:z.string(),truncated:z.boolean()}),execute(t,n){return executeGitHubShell(t,e(),{context:n,auth:n.session.auth.current,getSandbox:()=>n.getSandbox(),sessionId:n.session.id})}})}export{GH_TOOL_DESCRIPTION,defineGhTool};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { getToken } from "@vercel/connect";
|
|
2
2
|
import type { SessionAuthContext } from "eve/context";
|
|
3
3
|
import type { SandboxSession } from "eve/sandbox";
|
|
4
|
+
import type { ToolContext } from "eve/tools";
|
|
5
|
+
import { type GitHubCoAuthor } from "./attribution.ts";
|
|
6
|
+
export type { GitHubCoAuthor } from "./attribution.ts";
|
|
4
7
|
export interface GitHubPermission {
|
|
5
8
|
readonly access: "write";
|
|
6
9
|
readonly provider: "github";
|
|
@@ -19,6 +22,14 @@ export interface GitHubShellOutput {
|
|
|
19
22
|
readonly truncated: boolean;
|
|
20
23
|
}
|
|
21
24
|
export interface GitHubConfig {
|
|
25
|
+
/**
|
|
26
|
+
* Resolves who the current turn publishes for. When it returns a person,
|
|
27
|
+
* `gh-signed-commit` messages and `gh pr create` bodies end with their
|
|
28
|
+
* `Co-authored-by` trailer. Return `null` to publish without one. It
|
|
29
|
+
* receives the `gh` tool's context, so `ctx.getToken` can resolve the
|
|
30
|
+
* requester's linked accounts.
|
|
31
|
+
*/
|
|
32
|
+
readonly attribution?: (ctx: ToolContext) => GitHubCoAuthor | null | PromiseLike<GitHubCoAuthor | null>;
|
|
22
33
|
readonly authorize?: GitHubAuthorize;
|
|
23
34
|
readonly broker: (sandbox: SandboxSession, rules: Readonly<Record<string, readonly GitHubLeaseRule[]>> | null) => Promise<void>;
|
|
24
35
|
readonly connector: string;
|
|
@@ -52,6 +63,8 @@ export interface GitHubLeaseRule {
|
|
|
52
63
|
}[];
|
|
53
64
|
}
|
|
54
65
|
interface GitHubShellDependencies {
|
|
66
|
+
/** The calling tool's context, passed to `attribution`. */
|
|
67
|
+
readonly context?: ToolContext;
|
|
55
68
|
readonly auth?: SessionAuthContext | null;
|
|
56
69
|
readonly getConnectToken?: typeof getToken;
|
|
57
70
|
readonly getSandbox: () => Promise<SandboxSession>;
|
|
@@ -64,4 +77,3 @@ export declare function githubShellApproval(input: GitHubShellInput | undefined)
|
|
|
64
77
|
};
|
|
65
78
|
export declare function executeGitHubShell(input: GitHubShellInput, config: GitHubConfig | undefined, dependencies: GitHubShellDependencies): Promise<GitHubShellOutput>;
|
|
66
79
|
export declare function parseCommand(command: string): string[];
|
|
67
|
-
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{shellQuote}from"./shell.js";import{validateRepositoryRoot}from"./repository-root.js";import{GITHUB_TOOLING_PATHS}from"./tooling.js";import{posix}from"node:path";import{getToken}from"#compiled/@vercel/connect/index.js";const MAX_OUTPUT_BYTES=1e5,leaseQueues=new Map,ALLOWED_GIT_REMOTE_COMMANDS=new Set([`fetch`,`ls-remote`,`pull`,`push`]);function githubShellApproval(e){if(!e)return`not-applicable`;try{return validateGitHubShellInput(e),`not-applicable`}catch(e){return{type:`denied`,reason:e instanceof Error?e.message:`Invalid GitHub command.`}}}async function executeGitHubShell(e,t,n){if(!t)throw Error(`GitHub command access is not configured for this agent.`);let r=validateGitHubShellInput(e),i=await n.getSandbox(),a=resolveWorkingDirectory(i,e.workingDirectory);(r[0]===`git`||r[0]===`gh-signed-commit`)&&(a=await validateRepositoryRoot(i,a));let o=e.permissions[0].repositories,s=await assertCommandTargets(r,o,a,i),c=repositoryNames(o,t.org),
|
|
1
|
+
import{attributeCommand,coAuthorTrailer,isAttributable}from"./attribution.js";import{shellQuote}from"./shell.js";import{validateRepositoryRoot}from"./repository-root.js";import{GITHUB_TOOLING_PATHS}from"./tooling.js";import{posix}from"node:path";import{getToken}from"#compiled/@vercel/connect/index.js";const MAX_OUTPUT_BYTES=1e5,leaseQueues=new Map,ALLOWED_GIT_REMOTE_COMMANDS=new Set([`fetch`,`ls-remote`,`pull`,`push`]);function githubShellApproval(e){if(!e)return`not-applicable`;try{return validateGitHubShellInput(e),`not-applicable`}catch(e){return{type:`denied`,reason:e instanceof Error?e.message:`Invalid GitHub command.`}}}async function executeGitHubShell(e,t,n){if(!t)throw Error(`GitHub command access is not configured for this agent.`);let r=validateGitHubShellInput(e),i=await n.getSandbox(),a=resolveWorkingDirectory(i,e.workingDirectory);(r[0]===`git`||r[0]===`gh-signed-commit`)&&(a=await validateRepositoryRoot(i,a));let o=e.permissions[0].repositories,s=await assertCommandTargets(r,o,a,i),c=crypto.randomUUID(),l=`/tmp/eve-git-body-${c}.md`,u=repositoryNames(o,t.org),d={authorizationDetails:[{type:`github_app_installation`,org:t.org,repositories:u}],subject:{type:`app`}},f=await(n.getConnectToken??getToken)(t.connector,d);if(!f)throw Error(`Connect returned an empty token for ${JSON.stringify(t.connector)}.`);let p=o[0];if(t.authorize&&!await t.authorize({auth:n.auth??null,repository:p,token:f}))throw Error(`The current caller is not authorized to access ${p}.`);let m=t.attribution&&n.context&&isAttributable(r)?await t.attribution(n.context):null,h=m?await attributeCommand(r,coAuthorTrailer(m),i,a,l):r,g=executablePath(h[0]),_=s?[`-c`,`protocol.allow=never`,`-c`,`protocol.https.allow=always`,h[1],`https://github.com/${s}.git`,...h.slice(3)]:h.slice(1),v=`cd ${shellQuote(a)} && exec ${shellQuote(g)} ${_.map(shellQuote).join(` `)}`,y=`Basic ${Buffer.from(`x-access-token:${f}`).toString(`base64`)}`,b=`eve-github-${c}`,x=`/tmp/eve-code-gh-${c}`,S=`Basic ${Buffer.from(`x-access-token:${b}`).toString(`base64`)}`,C;try{C=await withGitHubCredentialLease(i,n.sessionId,{authorization:y,placeholder:b,placeholderAuthorization:S,token:f},()=>{let e={GH_CONFIG_DIR:x,GH_PROMPT_DISABLED:`1`};return r[0]!==`git`&&(e.GH_TOKEN=b),e.GIT_CONFIG_COUNT=`2`,e.GIT_CONFIG_KEY_0=`http.https://github.com/.extraheader`,e.GIT_CONFIG_VALUE_0=`Authorization: ${S}`,e.GIT_CONFIG_KEY_1=`core.hooksPath`,e.GIT_CONFIG_VALUE_1=`/dev/null`,e.GIT_TERMINAL_PROMPT=`0`,e.PATH=`/usr/local/bin:/usr/bin:/bin`,i.run({command:v,env:e})},t.broker)}finally{await i.removePath({path:x,recursive:!0,force:!0}),await i.removePath({path:l,force:!0})}let w=sanitizeOutput(C.stdout,f,y),T=sanitizeOutput(C.stderr,f,y);return{exitCode:C.exitCode,stdout:w.value,stderr:T.value,truncated:w.truncated||T.truncated}}async function withGitHubCredentialLease(e,t,n,r,i){let a=leaseQueues.get(t)??Promise.resolve(),o,s=new Promise(e=>{o=e}),c=a.then(()=>s);leaseQueues.set(t,c),await a;try{return await i(e,{"api.github.com":[{match:{headers:[{key:{exact:`authorization`},value:{regex:`^(?:token|Bearer) ${n.placeholder}$`}}]},transform:[{headers:{authorization:`Bearer ${n.token}`}}]}],"github.com":[{match:{headers:[{key:{exact:`authorization`},value:{exact:n.placeholderAuthorization}}]},transform:[{headers:{authorization:n.authorization}}]}]}),await r()}finally{try{await i(e,null)}finally{o(),leaseQueues.get(t)===c&&leaseQueues.delete(t)}}}function parseCommand(e){let t=e.trim();if(!t)throw Error(`GitHub command must not be empty.`);let n=[],r=``,i=null,a=!1,o=!1;for(let e of t){if(a){r+=e,a=!1,o=!0;continue}if(e===`\\`&&i!==`single`){a=!0,o=!0;continue}if(i===`single`){e===`'`?i=null:r+=e;continue}if(i===`double`){if(e===`"`)i=null;else if(e===`$`||e==="`")throw unsupportedSyntax();else r+=e;continue}if(e===`'`)i=`single`,o=!0;else if(e===`"`)i=`double`,o=!0;else if(/\s/u.test(e))o&&=(n.push(r),r=``,!1);else if(";&|<>$`(){}".includes(e))throw unsupportedSyntax();else r+=e,o=!0}if(a||i)throw Error(`GitHub command contains an unfinished quote or escape.`);if(o&&n.push(r),n.length===0)throw Error(`GitHub command must not be empty.`);return n}function validateGitHubShellInput(e){let t=parseCommand(e.command),n=e.permissions[0];if(e.permissions.length!==1||n?.provider!==`github`)throw Error(`Exactly one GitHub permission is required.`);if(n.access!==`write`)throw Error(`GitHub App commands require explicit write-capable access.`);if(!e.description.trim())throw Error(`description must describe the intended result.`);let[r,i]=t;if(r===`gh`){if(!i)throw Error(`A gh subcommand is required.`)}else if(r===`git`){if(!i||!ALLOWED_GIT_REMOTE_COMMANDS.has(i))throw Error(`Authenticated git supports only fetch, ls-remote, pull, and push; clone with gh repo clone.`)}else if(r!==`gh-signed-commit`)throw Error(`GitHub commands must use gh, an authenticated git remote command, or gh-signed-commit.`);return t}async function assertCommandTargets(e,t,n,r){let i=new Set(t.map(e=>e.toLowerCase())),a,o;if(e[0]===`git`){if(e[2]!==`origin`)throw Error(`Authenticated git commands must use the validated origin remote.`);let t=e[1]===`push`,i=await r.run({command:`cd ${shellQuote(n)} && git remote get-url ${t?`--push `:``}origin`,env:{GIT_TERMINAL_PROMPT:`0`,PATH:`/usr/bin:/bin`}});if(i.exitCode!==0)throw Error(`Could not resolve the origin GitHub repository.`);a=repositoryFromRemote(i.stdout.trim()),o=[a]}else if(e[0]===`gh-signed-commit`){if(o=repoFlagTargets(e.slice(1)),o.length===0)throw Error(`gh-signed-commit requires an explicit --repo owner/repository target.`)}else o=repoFlagTargets(e.slice(1)),e[1]===`repo`&&(e[2]===`clone`||e[2]===`view`||e[2]===`fork`)&&e[3]&&!e[3].startsWith(`-`)&&o.push(canonicalRepository(e[3]));for(let e of o)if(!i.has(e.toLowerCase()))throw Error(`Command target ${JSON.stringify(e)} is not in the declared repositories.`);return a}function repoFlagTargets(e){let t=[];for(let n=0;n<e.length;n+=1){let r=e[n];if(r===`--repo`||r===`-R`){let i=e[n+1];if(!i)throw Error(`${r} requires an owner/repository value.`);t.push(canonicalRepository(i)),n+=1}else r?.startsWith(`--repo=`)&&t.push(canonicalRepository(r.slice(7)))}return t}function repositoryFromRemote(e){let t=/^https:\/\/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/iu.exec(e);if(t?.[1])return canonicalRepository(t[1]);let n=/^git@github\.com:([^/]+\/[^/]+?)(?:\.git)?$/iu.exec(e);if(n?.[1])return canonicalRepository(n[1]);throw Error(`The origin remote is not a canonical GitHub repository URL.`)}function canonicalRepository(e){let t=e.replace(/\.git$/iu,``);if(!/^[A-Za-z0-9-]+\/[A-Za-z0-9._-]+$/u.test(t))throw Error(`Invalid GitHub repository ${JSON.stringify(e)}.`);return t}function repositoryNames(e,t){if(e.length!==1)throw Error(`Exactly one GitHub repository is required.`);let n=new Set;for(let r of e){let e=/^([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+)$/u.exec(r);if(!e)throw Error(`Invalid GitHub repository ${JSON.stringify(r)}.`);if(e[1]?.toLowerCase()!==t.toLowerCase())throw Error(`Repository ${JSON.stringify(r)} is outside ${t}.`);n.add(e[2])}if(n.size!==1)throw Error(`Exactly one GitHub repository is required.`);return[...n]}function executablePath(e){return e===`gh`?GITHUB_TOOLING_PATHS.ghReal:e===`gh-signed-commit`?GITHUB_TOOLING_PATHS.trustedSignedCommit:`git`}function resolveWorkingDirectory(e,t){let n=posix.resolve(`/`,e.resolvePath(``)),r=posix.resolve(`/`,e.resolvePath(t?.trim()||``));if(r!==n&&!r.startsWith(`${n}/`))throw Error(`GitHub command workingDirectory must stay inside the sandbox workspace.`);return r}function sanitizeOutput(e,t,n){let r=e.replaceAll(t,`[redacted]`).replaceAll(n,`[redacted]`);return Buffer.byteLength(r)<=MAX_OUTPUT_BYTES?{value:r,truncated:!1}:{value:`[output truncated: showing final ${MAX_OUTPUT_BYTES} bytes]\n${Buffer.from(r).subarray(-1e5).toString()}`,truncated:!0}}function unsupportedSyntax(){return Error(`GitHub commands support argv syntax only, not pipes, redirects, substitutions, or shell operators.`)}export{executeGitHubShell,githubShellApproval,parseCommand};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Consumer-facing helpers exported as `eve/extensions/git/sandbox`. */
|
|
2
|
-
export { executeGitHubShell, githubShellApproval, type GitHubConfig, type GitHubLeaseRule, type GitHubShellInput, type GitHubShellOutput, } from "./github-shell.ts";
|
|
2
|
+
export { executeGitHubShell, githubShellApproval, type GitHubCoAuthor, type GitHubConfig, type GitHubLeaseRule, type GitHubShellInput, type GitHubShellOutput, } from "./github-shell.ts";
|
|
3
3
|
export { GH_SIGNED_COMMIT_SOURCE, GH_SIGNED_COMMIT_VERSION } from "./signed-commit.ts";
|
|
4
4
|
export { GITHUB_TOOLING_PATHS } from "./tooling.ts";
|
|
5
5
|
export { defineGhTool, GH_TOOL_DESCRIPTION } from "./gh-tool.ts";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AuthorizationChallenge } from "#harness/authorization.js";
|
|
2
|
+
import type { AuthorizationPrincipal } from "#protocol/message.js";
|
|
2
3
|
/**
|
|
3
4
|
* Fields every `authorization.*` event copies from its challenge. Emitters
|
|
4
5
|
* spread this and add only event-local fields, so a new challenge-derived
|
|
@@ -8,6 +9,8 @@ export declare function authorizationEventFields(challenge: AuthorizationChallen
|
|
|
8
9
|
attemptId: string | undefined;
|
|
9
10
|
authorization: import("../connections/errors.ts").ConnectionAuthorizationChallenge;
|
|
10
11
|
candidateId: string | undefined;
|
|
12
|
+
grant: string | undefined;
|
|
11
13
|
name: string;
|
|
14
|
+
principal: AuthorizationPrincipal | undefined;
|
|
12
15
|
principalId: string | undefined;
|
|
13
16
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function authorizationEventFields(e){return{attemptId:e.attemptId,authorization:e.challenge,candidateId:e.candidateId,name:e.name,principalId:e.principalId}}export{authorizationEventFields};
|
|
1
|
+
function authorizationEventFields(e){return{attemptId:e.attemptId,authorization:e.challenge,candidateId:e.candidateId,grant:e.grant,name:e.name,principal:principalIdentity(e.principal),principalId:e.principalId}}function principalIdentity(e){if(e!==void 0)return e.type===`app`?{type:`app`}:e.issuer===void 0?{id:e.id,type:`user`}:{id:e.id,issuer:e.issuer,type:`user`}}export{authorizationEventFields};
|