eve 0.11.8 → 0.11.10
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 +18 -0
- package/dist/src/cli/banner.js +1 -1
- package/dist/src/cli/commands/init-git.d.ts +1 -1
- package/dist/src/cli/commands/init-git.js +1 -1
- package/dist/src/cli/commands/init.d.ts +1 -0
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/dev/tui/line-editor.d.ts +53 -19
- package/dist/src/cli/dev/tui/line-editor.js +6 -1
- package/dist/src/cli/dev/tui/live-region.d.ts +2 -0
- package/dist/src/cli/dev/tui/live-region.js +1 -1
- package/dist/src/cli/dev/tui/runner.d.ts +3 -0
- package/dist/src/cli/dev/tui/runner.js +1 -1
- package/dist/src/cli/dev/tui/setup-panel.js +3 -3
- package/dist/src/cli/dev/tui/setup-selection-input.js +2 -1
- package/dist/src/cli/dev/tui/stream-format.d.ts +21 -2
- package/dist/src/cli/dev/tui/stream-format.js +4 -2
- package/dist/src/cli/dev/tui/terminal-renderer.js +10 -10
- package/dist/src/cli/dev/tui/terminal-text.d.ts +11 -0
- package/dist/src/cli/dev/tui/terminal-text.js +1 -1
- package/dist/src/cli/dev/tui/theme.d.ts +2 -0
- package/dist/src/cli/dev/tui/theme.js +1 -1
- package/dist/src/cli/dev/tui/tui.d.ts +3 -0
- package/dist/src/cli/dev/tui/tui.js +1 -1
- package/dist/src/cli/format-elapsed.d.ts +2 -0
- package/dist/src/cli/format-elapsed.js +1 -0
- package/dist/src/cli/run.d.ts +4 -18
- package/dist/src/cli/run.js +2 -2
- package/dist/src/cli/ui/live-row.d.ts +19 -0
- package/dist/src/cli/ui/live-row.js +1 -0
- package/dist/src/compiler/manifest.d.ts +2 -2
- package/dist/src/compiler/manifest.js +1 -1
- package/dist/src/compiler/normalize-agent-config.js +1 -1
- package/dist/src/compiler/normalize-manifest.js +1 -1
- package/dist/src/compiler/normalize-subagent.d.ts +1 -0
- package/dist/src/compiler/normalize-subagent.js +1 -1
- package/dist/src/context/dynamic-skill-lifecycle.js +1 -1
- package/dist/src/evals/assertions/run.d.ts +7 -1
- package/dist/src/evals/assertions/run.js +2 -2
- package/dist/src/evals/context.js +1 -1
- package/dist/src/evals/index.d.ts +1 -1
- package/dist/src/evals/match.d.ts +6 -0
- package/dist/src/evals/types.d.ts +3 -1
- package/dist/src/harness/code-mode.js +1 -1
- package/dist/src/internal/application/compiled-artifacts.d.ts +7 -0
- package/dist/src/internal/application/compiled-artifacts.js +4 -4
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-definition/core.js +1 -1
- package/dist/src/internal/dev-boot-progress.d.ts +13 -0
- package/dist/src/internal/dev-boot-progress.js +1 -0
- package/dist/src/internal/logging.d.ts +9 -0
- package/dist/src/internal/logging.js +1 -1
- package/dist/src/internal/nitro/host/start-development-server.d.ts +2 -5
- package/dist/src/internal/nitro/host/start-development-server.js +2 -2
- package/dist/src/internal/nitro/host/types.d.ts +6 -0
- package/dist/src/internal/nitro/host.d.ts +1 -1
- package/dist/src/internal/nitro/routes/agent-info/build-agent-info-response-from-manifest.js +1 -1
- package/dist/src/internal/nitro/routes/agent-info/build-agent-info-response.js +1 -1
- package/dist/src/internal/workflow/configure-world.d.ts +12 -0
- package/dist/src/internal/workflow/configure-world.js +1 -0
- package/dist/src/public/definitions/agent.d.ts +1 -1
- package/dist/src/public/index.d.ts +1 -1
- package/dist/src/runtime/agent/mock-model-adapter.js +2 -2
- package/dist/src/runtime/framework-tools/skill.js +1 -1
- package/dist/src/runtime/resolve-agent-graph.js +1 -1
- package/dist/src/runtime/skills/sandbox-access.d.ts +3 -2
- package/dist/src/runtime/skills/sandbox-access.js +1 -1
- package/dist/src/setup/cli/select-state.js +1 -1
- package/dist/src/setup/primitives/pm/npm.js +1 -1
- package/dist/src/setup/primitives/pm/run.js +1 -1
- package/dist/src/setup/primitives/pm/types.d.ts +2 -0
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/setup/slack-connect-create.js +1 -1
- package/dist/src/setup/slackbot.js +1 -1
- package/dist/src/shared/agent-definition.d.ts +23 -0
- package/dist/src/shared/text-boundaries.d.ts +19 -0
- package/dist/src/shared/text-boundaries.js +1 -0
- package/docs/agent-config.md +33 -6
- package/docs/concepts/default-harness.md +14 -14
- package/docs/concepts/execution-model-and-durability.md +16 -1
- package/docs/concepts/sessions-runs-and-streaming.md +27 -27
- package/docs/evals/assertions.mdx +1 -0
- package/docs/guides/deployment.md +17 -2
- package/docs/guides/dev-tui.md +10 -7
- package/docs/guides/dynamic-capabilities.md +2 -0
- package/docs/guides/frontend/overview.mdx +1 -1
- package/docs/reference/typescript-api.md +2 -2
- package/package.json +1 -1
|
@@ -11,9 +11,10 @@ export declare function assertSafeSkillId(id: string): asserts id is string;
|
|
|
11
11
|
* Returns the SKILL.md body with any YAML frontmatter stripped, so the
|
|
12
12
|
* model receives plain markdown as the tool result. Throws when the id
|
|
13
13
|
* is unsafe or the file does not exist; the AI SDK forwards the error
|
|
14
|
-
* to the model as a tool-error result.
|
|
14
|
+
* to the model as a tool-error result. `availableNames`, when given, is
|
|
15
|
+
* listed in the not-found error so the model can correct a wrong id.
|
|
15
16
|
*/
|
|
16
|
-
export declare function loadSkillFromSandbox(access: SandboxAccess, id: string): Promise<string>;
|
|
17
|
+
export declare function loadSkillFromSandbox(access: SandboxAccess, id: string, availableNames?: readonly string[]): Promise<string>;
|
|
17
18
|
/**
|
|
18
19
|
* Creates the public runtime skill handle. Existence is checked lazily by
|
|
19
20
|
* each file read against the sandbox.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{WORKSPACE_ROOT}from"#runtime/workspace/types.js";const FRONTMATTER_PATTERN=/^---\r?\n[\s\S]*?\r?\n---\r?\n?/;function assertSafeSkillId(e){if(e.length===0||e.trim()!==e||e.startsWith(`.`)||e.includes(`/`)||e.includes(`\\`)||e.includes(`..`)||/^[A-Za-z]:/.test(e))throw Error(`Expected skill id to be a non-empty safe path segment without whitespace, separators, "." prefix, or "..".`)}async function loadSkillFromSandbox(e,n){assertSafeSkillId(n);let
|
|
1
|
+
import{WORKSPACE_ROOT}from"#runtime/workspace/types.js";const FRONTMATTER_PATTERN=/^---\r?\n[\s\S]*?\r?\n---\r?\n?/;function assertSafeSkillId(e){if(e.length===0||e.trim()!==e||e.startsWith(`.`)||e.includes(`/`)||e.includes(`\\`)||e.includes(`..`)||/^[A-Za-z]:/.test(e))throw Error(`Expected skill id to be a non-empty safe path segment without whitespace, separators, "." prefix, or "..".`)}async function loadSkillFromSandbox(e,n,r=[]){assertSafeSkillId(n);let i=await requireSandboxSession(e),a=skillFilePath(n,`SKILL.md`),o=await i.readTextFile({path:a});if(o===null){let e=r.length>0?` Available skills: ${r.join(`, `)}.`:``;throw Error(`No skill named "${n}" at ${a}.${e}`)}return o.replace(FRONTMATTER_PATTERN,``)}function createSandboxSkillHandle(e,t){return assertSafeSkillId(t),{name:t,file(n){assertSafeSkillRelativePath(n);let r=skillFilePath(t,n);return{async bytes(){let t=await(await requireSandboxSession(e)).readBinaryFile({path:r});if(t===null)throw Error(`Skill file not found: ${r}`);return t},async text(){let t=await(await requireSandboxSession(e)).readTextFile({path:r});if(t===null)throw Error(`Skill file not found: ${r}`);return t}}}}}function assertSafeSkillRelativePath(e){if(e.length===0||e.startsWith(`/`)||e.includes(`\\`)||e.split(`/`).some(e=>e.length===0||e===`.`||e===`..`))throw Error(`Expected skill file path to be a relative path inside the skill directory.`)}async function requireSandboxSession(e){let t=await e.get();if(t===null)throw Error(`The sandbox is not available in the current authored runtime context.`);return t}function skillFilePath(t,n){return`${WORKSPACE_ROOT}/skills/${t}/${n}`}export{assertSafeSkillId,createSandboxSkillHandle,loadSkillFromSandbox};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function submitRowIndex(e){return e.length}function filterOptions(e,t){let n=t.trim().toLowerCase();return n===``?[...e]:e.filter(e=>e.label.toLowerCase().includes(n)||e.value.toLowerCase().includes(n)||(e.hint?.toLowerCase().includes(n)??!1)||(e.focusHint?.toLowerCase().includes(n)??!1))}function isFocusable(e){return!e.disabled&&!e.locked}function isActionable(e){return isFocusable(e)&&!e.completed}function firstFocusableIndex(e,t){let n=e.findIndex(isFocusable);return n>=0?n:t?submitRowIndex(e):0}function stepCursor(e,t,n,r){let i=e.length+ +!!r;if(i===0)return t;let a=t;for(let t=0;t<i;t+=1){if(a=(a+n+i)%i,r&&a===submitRowIndex(e))return a;let t=e[a];if(t&&isFocusable(t))return a}return t}function reduceSelect(
|
|
1
|
+
import{previousGraphemeBoundary}from"#shared/text-boundaries.js";function submitRowIndex(e){return e.length}function filterOptions(e,t){let n=t.trim().toLowerCase();return n===``?[...e]:e.filter(e=>e.label.toLowerCase().includes(n)||e.value.toLowerCase().includes(n)||(e.hint?.toLowerCase().includes(n)??!1)||(e.focusHint?.toLowerCase().includes(n)??!1))}function isFocusable(e){return!e.disabled&&!e.locked}function isActionable(e){return isFocusable(e)&&!e.completed}function firstFocusableIndex(e,t){let n=e.findIndex(isFocusable);return n>=0?n:t?submitRowIndex(e):0}function stepCursor(e,t,n,r){let i=e.length+ +!!r;if(i===0)return t;let a=t;for(let t=0;t<i;t+=1){if(a=(a+n+i)%i,r&&a===submitRowIndex(e))return a;let t=e[a];if(t&&isFocusable(t))return a}return t}function reduceSelect(t,n,r){let i=r.submitRow===!0;switch(n.type){case`char`:{let e=t.filter+n.char;return{...t,filter:e,cursor:firstFocusableIndex(filterOptions(r.options,e),i)}}case`backspace`:{if(t.filter.length===0)return t;let n=t.filter.slice(0,previousGraphemeBoundary(t.filter,t.filter.length));return{...t,filter:n,cursor:firstFocusableIndex(filterOptions(r.options,n),i)}}case`up`:case`down`:{let e=filterOptions(r.options,t.filter),a=n.type===`up`?-1:1,o=stepCursor(e,t.cursor,a,i);return o===t.cursor?t:{...t,cursor:o}}case`toggle`:{let e=filterOptions(r.options,t.filter)[t.cursor];if(e===void 0||!isActionable(e))return t;let n=new Set(t.selected);return n.has(e.value)?n.delete(e.value):n.add(e.value),{...t,selected:n}}}}function initialSelectState(e){let t=e.filter??``,n=filterOptions(e.options,t),r=firstFocusableIndex(n,e.submitRow===!0);if(e.defaultValue!==void 0){let t=n.findIndex(t=>isFocusable(t)&&t.value===e.defaultValue);t>=0&&(r=t)}let i=e.options.filter(e=>e.locked).map(e=>e.value);return{filter:t,cursor:r,selected:new Set([...e.initialValues??[],...i])}}function selectValueAtCursor(e,t){let n=e[t];return n&&isActionable(n)?n.value:void 0}function orderedSelection(e,t){return e.filter(e=>t.has(e.value)).map(e=>e.value)}export{filterOptions,initialSelectState,orderedSelection,reduceSelect,selectValueAtCursor,submitRowIndex};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{applyNoProjectConfiguration,resolveStandardInvocation}from"./shared.js";const npmPackageManager={kind:`npm`,scaffoldFiles:{},applyProjectConfiguration:applyNoProjectConfiguration,devArguments:()=>[`exec`,`--`,`eve`,`dev`],installArguments:e=>[`install`,...e.bypassMinimumReleaseAge===!0?[`--min-release-age=0`]:[]],prepareArguments:(e,t)=>t,resolveInvocation:e=>resolveStandardInvocation(`npm`,e)};export{npmPackageManager};
|
|
1
|
+
import{applyNoProjectConfiguration,resolveStandardInvocation}from"./shared.js";const npmPackageManager={kind:`npm`,scaffoldFiles:{},applyProjectConfiguration:applyNoProjectConfiguration,devArguments:()=>[`exec`,`--`,`eve`,`dev`],installArguments:e=>[`install`,...e.bypassMinimumReleaseAge===!0?[`--min-release-age=0`]:[],...e.progressDetails===!0?[`--loglevel=silly`]:[]],prepareArguments:(e,t)=>t,resolveInvocation:e=>resolveStandardInvocation(`npm`,e)};export{npmPackageManager};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createProcessOutputBuffer}from"../process-output.js";import{armProcessAbort}from"../process-abort.js";import{PNPM_WORKSPACE_MEMBERSHIP_ARGUMENTS,hasAncestorPnpmWorkspace,pnpmWorkspaceClaimsProject}from"./pnpm.js";import{getPackageManagerStrategy}from"./index.js";import{spawn}from"node:child_process";function stdioForRun(e){return e.onOutput?[e.nonInteractive?`ignore`:`inherit`,`pipe`,`pipe`]:e.nonInteractive?[`ignore`,`inherit`,`inherit`]:`inherit`}function spawnPackageManager(n,r,i,s={}){return s.signal?.aborted===!0?Promise.resolve(!1):new Promise(c=>{let l=getPackageManagerStrategy(n),u=l.prepareArguments(r,i),d=l.resolveInvocation(u),f=s.onOutput&&createProcessOutputBuffer(s.onOutput),p=spawn(d.command,[...d.args],{cwd:r,stdio:stdioForRun(s),shell:d.shell,signal:s.signal}),m=armProcessAbort(p,s.signal);p.stdout?.on(`data`,e=>f?.write(`stdout`,e)),p.stderr?.on(`data`,e=>f?.write(`stderr`,e));let h=!1;function settle(e){h||(h=!0,f?.flush(),c(e))}function reportFailure(e){s.onOutput?s.onOutput({stream:`stderr`,text:e}):process.stderr.write(`\n${e}\n`)}p.on(`error`,e=>{s.signal?.aborted===!0||e.name===`AbortError`||(e.code===`ENOENT`?(m(),reportFailure(`${n} not found. Install it before running this step.`),settle(!1)):(m(),reportFailure(`${n} ${i.join(` `)} failed: ${e.message}`),settle(!1)))}),p.on(`close`,e=>{m(),settle(s.signal?.aborted===!0?!1:e===0)})})}function capturePackageManager(n,r,i,s){return s.signal?.aborted===!0?Promise.resolve({ok:!1,stdout:``}):new Promise(c=>{let l=getPackageManagerStrategy(n),u=l.prepareArguments(r,i),d=l.resolveInvocation(u),f=spawn(d.command,[...d.args],{cwd:r,stdio:[`ignore`,`pipe`,`pipe`],shell:d.shell,signal:s.signal}),p=armProcessAbort(f,s.signal),m=[],h=s.onOutput&&createProcessOutputBuffer(s.onOutput);f.stdout?.on(`data`,e=>m.push(e)),f.stderr?.on(`data`,e=>h?.write(`stderr`,e));let g=!1;function settle(e){g||(g=!0,h?.flush(),c({ok:e,stdout:Buffer.concat(m).toString(`utf8`)}))}function reportFailure(e){s.onOutput?s.onOutput({stream:`stderr`,text:e}):process.stderr.write(`\n${e}\n`)}f.on(`error`,e=>{s.signal?.aborted===!0||e.name===`AbortError`||(p(),reportFailure(e.code===`ENOENT`?`${n} not found. Install it before running this step.`:`${n} ${i.join(` `)} failed: ${e.message}`),settle(!1))}),f.on(`close`,e=>{p(),!g&&e!==0&&s.signal?.aborted!==!0&&reportFailure(`${n} ${i.join(` `)} exited with code ${e??`unknown`}.`),settle(s.signal?.aborted===!0?!1:e===0)})})}async function runPackageManagerInstall(e,t,o={}){let s=getPackageManagerStrategy(e),c=o;if(e===`pnpm`&&o.ignoreWorkspace!==!0&&hasAncestorPnpmWorkspace(t)){let r=await capturePackageManager(e,t,PNPM_WORKSPACE_MEMBERSHIP_ARGUMENTS,o);if(!r.ok)return!1;let a=pnpmWorkspaceClaimsProject(r.stdout,t);if(a===void 0)return o.onOutput?.({stream:`stderr`,text:`Could not determine whether the ancestor pnpm workspace includes this project.`}),!1;a||(c={...o,ignoreWorkspace:!0})}return spawnPackageManager(e,t,s.installArguments(c),o)}function eveDevArguments(e){return getPackageManagerStrategy(e).devArguments()}function spawnPnpm(e,t,n={}){return spawnPackageManager(`pnpm`,e,t,n)}function runPnpmInstall(e,t={}){return runPackageManagerInstall(`pnpm`,e,t)}export{eveDevArguments,runPackageManagerInstall,runPnpmInstall,spawnPackageManager,spawnPnpm};
|
|
1
|
+
import{createProcessOutputBuffer}from"../process-output.js";import{armProcessAbort}from"../process-abort.js";import{PNPM_WORKSPACE_MEMBERSHIP_ARGUMENTS,hasAncestorPnpmWorkspace,pnpmWorkspaceClaimsProject}from"./pnpm.js";import{getPackageManagerStrategy}from"./index.js";import{spawn}from"node:child_process";function stdioForRun(e){return e.onOutput?[e.nonInteractive?`ignore`:`inherit`,`pipe`,`pipe`]:e.nonInteractive?[`ignore`,`inherit`,`inherit`]:`inherit`}function spawnPackageManager(n,r,i,s={}){return s.signal?.aborted===!0?Promise.resolve(!1):new Promise(c=>{let l=getPackageManagerStrategy(n),u=l.prepareArguments(r,i),d=l.resolveInvocation(u),f=s.onOutput&&createProcessOutputBuffer(s.onOutput),p=spawn(d.command,[...d.args],{cwd:r,stdio:stdioForRun(s),shell:d.shell,signal:s.signal}),m=armProcessAbort(p,s.signal);p.stdout?.on(`data`,e=>f?.write(`stdout`,e)),p.stderr?.on(`data`,e=>f?.write(`stderr`,e));let h=!1;function settle(e){h||(h=!0,f?.flush(),c(e))}function reportFailure(e){s.onOutput?s.onOutput({stream:`stderr`,text:e}):process.stderr.write(`\n${e}\n`)}p.on(`error`,e=>{s.signal?.aborted===!0||e.name===`AbortError`||(e.code===`ENOENT`?(m(),reportFailure(`${n} not found. Install it before running this step.`),settle(!1)):(m(),reportFailure(`${n} ${i.join(` `)} failed: ${e.message}`),settle(!1)))}),p.on(`close`,e=>{m(),settle(s.signal?.aborted===!0?!1:e===0)})})}function capturePackageManager(n,r,i,s){return s.signal?.aborted===!0?Promise.resolve({ok:!1,stdout:``}):new Promise(c=>{let l=getPackageManagerStrategy(n),u=l.prepareArguments(r,i),d=l.resolveInvocation(u),f=spawn(d.command,[...d.args],{cwd:r,stdio:[`ignore`,`pipe`,`pipe`],shell:d.shell,signal:s.signal}),p=armProcessAbort(f,s.signal),m=[],h=s.onOutput&&createProcessOutputBuffer(s.onOutput);f.stdout?.on(`data`,e=>m.push(e)),f.stderr?.on(`data`,e=>h?.write(`stderr`,e));let g=!1;function settle(e){g||(g=!0,h?.flush(),c({ok:e,stdout:Buffer.concat(m).toString(`utf8`)}))}function reportFailure(e){s.onOutput?s.onOutput({stream:`stderr`,text:e}):process.stderr.write(`\n${e}\n`)}function replayCapturedStdout(){let e=Buffer.concat(m);e.length!==0&&(h?(h.write(`stdout`,e),h.flush()):process.stdout.write(e))}f.on(`error`,e=>{s.signal?.aborted===!0||e.name===`AbortError`||(p(),replayCapturedStdout(),reportFailure(e.code===`ENOENT`?`${n} not found. Install it before running this step.`:`${n} ${i.join(` `)} failed: ${e.message}`),settle(!1))}),f.on(`close`,e=>{p(),!g&&e!==0&&s.signal?.aborted!==!0&&(replayCapturedStdout(),reportFailure(`${n} ${i.join(` `)} exited with code ${e??`unknown`}.`)),settle(s.signal?.aborted===!0?!1:e===0)})})}async function runPackageManagerInstall(e,t,o={}){let s=getPackageManagerStrategy(e),c=o;if(e===`pnpm`&&o.ignoreWorkspace!==!0&&hasAncestorPnpmWorkspace(t)){let r=await capturePackageManager(e,t,PNPM_WORKSPACE_MEMBERSHIP_ARGUMENTS,o);if(!r.ok)return!1;let a=pnpmWorkspaceClaimsProject(r.stdout,t);if(a===void 0)return o.onOutput?.({stream:`stderr`,text:`Could not determine whether the ancestor pnpm workspace includes this project.`}),!1;a||(c={...o,ignoreWorkspace:!0})}return spawnPackageManager(e,t,s.installArguments(c),o)}function eveDevArguments(e){return getPackageManagerStrategy(e).devArguments()}function spawnPnpm(e,t,n={}){return spawnPackageManager(`pnpm`,e,t,n)}function runPnpmInstall(e,t={}){return runPackageManagerInstall(`pnpm`,e,t)}export{eveDevArguments,runPackageManagerInstall,runPnpmInstall,spawnPackageManager,spawnPnpm};
|
|
@@ -15,6 +15,8 @@ export interface PackageManagerInstallOptions {
|
|
|
15
15
|
readonly bypassMinimumReleaseAge?: boolean;
|
|
16
16
|
/** Resolves the project standalone even when an ancestor workspace exists. */
|
|
17
17
|
readonly ignoreWorkspace?: boolean;
|
|
18
|
+
/** Requests verbose package-manager output for a live progress display. */
|
|
19
|
+
readonly progressDetails?: boolean;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Package-manager-specific command and generated-project behavior.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{getPackageManagerStrategy}from"../../primitives/pm/index.js";import{pinnedNodeEngineMajor}from"../../node-engine.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS}from"../update/module-files.js";import{WEB_APP_TEMPLATE_FILES}from"./web-template.js";import{basename,join,resolve}from"node:path";import{mkdir,readdir,stat}from"node:fs/promises";const CURRENT_DIRECTORY_PROJECT_NAME=`.`,ALLOWED_CREATE_IN_PLACE_ENTRIES=new Set([`.DS_Store`,`.git`,`.gitkeep`,`.hg`]),DEFAULT_AI_PACKAGE_VERSION=`7.0.0-beta.178`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.2.2`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.11.
|
|
1
|
+
import{getPackageManagerStrategy}from"../../primitives/pm/index.js";import{pinnedNodeEngineMajor}from"../../node-engine.js";import{pathExists,writeTextFile}from"../files.js";import{resolveVersionToken}from"../version-tokens.js";import{SUPPORTED_AUTHORED_MODULE_FILE_EXTENSIONS}from"../update/module-files.js";import{WEB_APP_TEMPLATE_FILES}from"./web-template.js";import{basename,join,resolve}from"node:path";import{mkdir,readdir,stat}from"node:fs/promises";const CURRENT_DIRECTORY_PROJECT_NAME=`.`,ALLOWED_CREATE_IN_PLACE_ENTRIES=new Set([`.DS_Store`,`.git`,`.gitkeep`,`.hg`]),DEFAULT_AI_PACKAGE_VERSION=`7.0.0-beta.178`,DEFAULT_CONNECT_PACKAGE_VERSION=`0.2.2`,DEFAULT_ZOD_PACKAGE_VERSION=`4.4.3`,DEFAULT_EVE_PACKAGE_CONTRACT={version:`0.11.10`,nodeEngine:`>=24`};function resolveEvePackageContract(e=DEFAULT_EVE_PACKAGE_CONTRACT){return{version:resolveVersionToken(`evePackage.version`,e.version),nodeEngine:resolveVersionToken(`evePackage.nodeEngine`,e.nodeEngine)}}function modelProviderSlug(e){let t=(e.split(`/`)[0]??``).replaceAll(/[^A-Za-z0-9._-]/gu,``);return t.length>0?t:`anthropic`}function byokProviderEnvVar(e){let t=modelProviderSlug(e).toUpperCase().replaceAll(/[^A-Z0-9]/gu,`_`);return`${/^[0-9]/.test(t)?`_`:``}${t}_API_KEY`}function agentTemplateFiles(e){return{"agent/agent.ts":BASE_AGENT_TEMPLATE.replaceAll(`__EVE_INIT_MODEL__`,e),"agent/channels/eve.ts":WEB_APP_TEMPLATE_FILES[`agent/channels/eve.ts`],"agent/instructions.md":AGENT_INSTRUCTIONS_TEMPLATE}}function renderTemplate(e,t){return e.replaceAll(`__EVE_INIT_APP_NAME__`,t.appName).replaceAll(`__EVE_INIT_MODEL__`,t.model).replaceAll(`__EVE_INIT_BYOK_PROVIDER__`,modelProviderSlug(t.model)).replaceAll(`__EVE_INIT_BYOK_ENV_VAR__`,byokProviderEnvVar(t.model)).replaceAll(`__EVE_INIT_PACKAGE_VERSION__`,formatEveDependencySpecifier(t.eveVersion)).replaceAll(`__EVE_INIT_AI_SDK_VERSION__`,t.aiPackageVersion).replaceAll(`__EVE_INIT_CONNECT_VERSION__`,t.connectPackageVersion).replaceAll(`__EVE_INIT_ZOD_VERSION__`,t.zodPackageVersion).replaceAll(`__EVE_INIT_TYPESCRIPT_VERSION__`,t.typescriptPackageVersion).replaceAll(`__EVE_INIT_TYPES_NODE_VERSION__`,t.nodeTypesVersion).replaceAll(`__EVE_INIT_NODE_ENGINE__`,t.nodeEngine)}function formatEveDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}const BASE_AGENT_TEMPLATE=`import { defineAgent } from "eve";
|
|
2
2
|
|
|
3
3
|
export default defineAgent({
|
|
4
4
|
model: "__EVE_INIT_MODEL__",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{parseCreatedSlackConnector}from"./slack-connect.js";import{CONNECT_LOOKUP_TIMEOUT_MS,fetchSlackConnectorDetails}from"./slack-connect-lifecycle.js";import{
|
|
1
|
+
import{parseCreatedSlackConnector}from"./slack-connect.js";import{CONNECT_LOOKUP_TIMEOUT_MS,fetchSlackConnectorDetails}from"./slack-connect-lifecycle.js";import{performance}from"node:perf_hooks";import{setTimeout}from"node:timers/promises";const CREATED_CONNECTOR_PROGRESS=/\bConnector created:\s*(scl_[A-Za-z0-9]+)\b/,realDelay=(e,t)=>setTimeout(e,void 0,{signal:t}),monotonicNow=()=>performance.now();async function pollCreatedSlackConnector(e,r,i,a,o,s){let c=e.delay??realDelay,l=e.now??monotonicNow,u=l()+CONNECT_LOOKUP_TIMEOUT_MS;for(;;){s.throwIfAborted();let t=u-l();if(t<=0)return;let d=await fetchSlackConnectorDetails({deps:e,projectRoot:r,connectorId:i,orgId:a,onOutput:o,timeoutMs:Math.min(1e4,t),signal:s});if(s.throwIfAborted(),d.state===`found`)return d.details.ref;let f=u-l();if(f<=0)return;await c(Math.min(3e3,f),s)}}async function createSlackConnector(t){let n=new AbortController,r=new AbortController,i=new AbortController,o=t.signal?AbortSignal.any([t.signal,n.signal]):n.signal,s=t.signal?AbortSignal.any([t.signal,r.signal]):r.signal,c=t.signal?AbortSignal.any([t.signal,i.signal]):i.signal,l=!1,reportCreated=e=>{l||(l=!0,t.onCreated(e))},u,d,f=new Promise((e,t)=>{u=e,d=t}),p,m,startWorkspaceLookup=e=>{m===void 0&&(m=t.waitForWorkspace(e,c).catch(e=>{if(!(i.signal.aborted&&t.signal?.aborted!==!0))throw e}),m.then(t=>{t!==void 0&&u({ref:e,workspace:t})},d))},createOutput=e=>{t.onOutput(e);let n=e.text.match(CREATED_CONNECTOR_PROGRESS)?.[1];n===void 0||p!==void 0||(p=pollCreatedSlackConnector(t.deps,t.projectRoot,n,t.orgId,t.onOutput,s).catch(e=>{if(!(r.signal.aborted&&t.signal?.aborted!==!0))throw e}),p.then(e=>{e!==void 0&&(reportCreated(e),startWorkspaceLookup(e))},d))},h=t.phase(`Waiting for Slack setup to finish...`,()=>t.deps.runVercelCaptureStdout([`connect`,`create`,`slack`,`--triggers`,`--name`,t.slug,`-F`,`json`],{cwd:t.projectRoot,nonInteractive:!0,onOutput:createOutput,timeoutMs:6e5,signal:o})),g;try{g=await Promise.race([h.then(e=>({via:`cli`,result:e})),f.then(e=>({via:`workspace`,...e}))])}catch(e){throw r.abort(),i.abort(),await Promise.allSettled([p,m].filter(e=>e!==void 0)),e}if(g.via===`workspace`)return n.abort(),await h,t.signal?.throwIfAborted(),{state:`created`,via:`workspace`,ref:g.ref,workspace:g.workspace};if(!g.result.ok)return p!==void 0&&await p,i.abort(),m!==void 0&&await m,t.signal?.throwIfAborted(),{state:`failed`};let _=parseCreatedSlackConnector(g.result.stdout);if(_!==void 0)return reportCreated(_),r.abort(),i.abort(),p!==void 0&&await p,m!==void 0&&await m,t.signal?.throwIfAborted(),{state:`created`,via:`cli`,ref:_};let v=p===void 0?void 0:await p;return i.abort(),m!==void 0&&await m,t.signal?.throwIfAborted(),v===void 0?{state:`unresolved`}:{state:`created`,via:`cli`,ref:v}}export{createSlackConnector};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{parseCreatedSlackConnector,parseSlackConnectorDetails,pickSlackConnector}from"./slack-connect.js";import{CONNECT_LOOKUP_TIMEOUT_MS,attachSlackConnector,cleanupCreatedAttempt,fetchSlackWorkspace,findSlackConnector,listSlackConnectors,readProjectLink}from"./slack-connect-lifecycle.js";import{createSlackConnector}from"./slack-connect-create.js";import{join}from"node:path";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#setup/scaffold/index.js";import{setTimeout}from"node:timers/promises";import{createPromptCommandOutput,withPhase}from"#setup/cli/index.js";import{captureVercel,runVercel,runVercelCaptureStdout}from"#setup/primitives/run-vercel.js";import{
|
|
1
|
+
import{parseCreatedSlackConnector,parseSlackConnectorDetails,pickSlackConnector}from"./slack-connect.js";import{CONNECT_LOOKUP_TIMEOUT_MS,attachSlackConnector,cleanupCreatedAttempt,fetchSlackWorkspace,findSlackConnector,listSlackConnectors,readProjectLink}from"./slack-connect-lifecycle.js";import{createSlackConnector}from"./slack-connect-create.js";import{join}from"node:path";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#setup/scaffold/index.js";import{performance}from"node:perf_hooks";import{setTimeout}from"node:timers/promises";import{createPromptCommandOutput,withPhase}from"#setup/cli/index.js";import{captureVercel,runVercel,runVercelCaptureStdout}from"#setup/primitives/run-vercel.js";import{updateSlackChannelConnectorUid}from"#setup/scaffold/update/update-slack-channel.js";const defaultDeps={captureVercel,runVercel,runVercelCaptureStdout},realDelay=(e,t)=>setTimeout(e,void 0,{signal:t}),monotonicNow=()=>performance.now();async function pollSlackWorkspace(e,t,n,i,a,s){let c=e.delay??realDelay,l=e.now??monotonicNow,u=l()+3e5;for(;;){s?.throwIfAborted();let d=u-l();if(d<=0)return{state:`timed-out`};let f=await fetchSlackWorkspace({deps:e,projectRoot:t,connectorId:n,orgId:i,onOutput:a,timeoutMs:Math.min(CONNECT_LOOKUP_TIMEOUT_MS,d),signal:s});if(s?.throwIfAborted(),f.state!==`pending`)return f;let p=u-l();if(p<=0)return{state:`timed-out`};await c(Math.min(3e3,p),s)}}function isAbortFromSignal(e,t){return t?.aborted===!0&&(e===t.reason||e instanceof Error&&e.name===`AbortError`)}function cleanupFailureResult(e){return{state:`cleanup-failed`,connectorUids:e.connectorUids}}async function runAttempt(e){let{log:t,deps:n,projectRoot:r,orgId:a,onOutput:o,signal:s,phase:c}=e,l,d;if(e.source.state===`existing`)l=e.source.ref;else{let i=await createSlackConnector({deps:n,projectRoot:r,orgId:a,slug:e.slug,onOutput:o,signal:s,phase:c,onCreated:e.onCreated,waitForWorkspace:async(e,t)=>{let i=await pollSlackWorkspace(n,r,e.id,a,o,t);return i.state===`connected`?i.workspace:void 0}});if(i.state===`failed`)return{state:`create-failed`};if(i.state===`unresolved`)return t.warning(`Vercel did not return an exact Slack connector UID for this request, so eve cannot attach or remove it safely.`),{state:`unresolved`};l=i.ref,i.via===`workspace`&&(d=i.workspace)}if(e.source.state===`existing`&&d===void 0){let e=await c(`Waiting for the Slack workspace connection...`,()=>pollSlackWorkspace(n,r,l.id,a,o,s));if(e.state===`timed-out`)return{state:`timed-out`,ref:l};if(e.state===`failed`)return{state:`failed`,ref:l,message:e.message};d=e.workspace}let f=await c(`Configuring Slack event delivery for this agent...`,()=>attachSlackConnector(n,r,l,o,s));return s?.throwIfAborted(),f.state===`attached`?d===void 0?{state:`attached`,ref:l}:{state:`attached`,ref:l,workspace:d}:{state:f.state,ref:l}}async function raceAttemptAgainstChoice(e){let t=new AbortController,n=e.outerSignal?AbortSignal.any([e.outerSignal,t.signal]):t.signal,r=e.run(n);try{let i=await Promise.race([r.then(e=>({via:`work`,outcome:e})),e.prompt.choice.then(e=>({via:`choice`,choice:e}))]);if(i.via===`work`)return i;t.abort();let a;try{a=await r}catch(e){if(!isAbortFromSignal(e,n))throw e}return{via:`choice`,choice:i.choice,settled:a}}catch(e){t.abort();try{await r}catch{}throw e}finally{e.prompt.close()}}async function provisionSlackbot(e,t,n,r=defaultDeps,i={}){i.signal?.throwIfAborted();let o=createPromptCommandOutput(e),u={log:e,deps:r,projectRoot:t,onOutput:o},d=await(r.readProjectLink??readProjectLink)(t),p=d?.projectId,m=d?.orgId,h=`slack/${n}`,g=await withPhase(e,`Checking for an existing Slackbot...`,()=>findSlackConnector(r,t,p,h,o,i.signal));if(i.signal?.throwIfAborted(),g.state===`failed`)return e.warning(`Could not inspect existing Slack connectors, so eve did not create another one. ${g.message}`),{state:`connector-lookup-failed`};if(p===void 0&&g.connectorUids.size>0)return e.warning("Could not verify which Slack connectors belong to this Vercel project, so eve did not create another one. Restore `.vercel/project.json`, then try again."),{state:`connector-lookup-failed`};let finishOutcome=async(t,n,r)=>{let i=n.state===`new`;switch(t.state){case`create-failed`:if(i){let e=await r();if(e.state===`failed`)return cleanupFailureResult(e)}return{state:`create-failed`};case`unresolved`:if(i){let e=await r();if(e.state===`failed`)return cleanupFailureResult(e)}return{state:`create-failed`};case`detach-failed`:return e.warning(`Could not remove the connector's existing trigger destination. Run \`vercel connect detach ${t.ref.uid} --yes\`, then \`vercel connect attach ${t.ref.uid} --triggers --trigger-path ${SLACK_CHANNEL_DEFAULT_ROUTE} --yes\`.`),{state:`detach-failed`,connectorUid:t.ref.uid};case`attach-failed`:return e.warning(`Could not register this project as a trigger destination. Run \`vercel connect attach ${t.ref.uid} --triggers --trigger-path ${SLACK_CHANNEL_DEFAULT_ROUTE} --yes\` to enable event delivery.`),{state:`attach-failed`,connectorUid:t.ref.uid};case`attached`:return t.workspace===void 0?{state:`attached`,connectorUid:t.ref.uid}:{state:`attached`,connectorUid:t.ref.uid,chatUrl:t.workspace.workspaceUrl,workspaceName:t.workspace.workspaceName};case`failed`:return e.warning(`Could not verify the Slack workspace connection. ${t.message}`),{state:`installation-check-failed`,connectorUid:t.ref.uid};case`timed-out`:if(e.warning(`The Slackbot did not connect to a Slack workspace in time.`),i){let e=await r();if(e.state===`failed`)return cleanupFailureResult(e)}return{state:`not-installed`}}},cleanupNewAttempt=async(e,t)=>cleanupCreatedAttempt(u,{expectedUid:h,baselineConnectorUids:e.baselineConnectorUids,createdRef:t});function attemptInput(i,a){return{log:e,deps:r,projectRoot:t,orgId:m,slug:n,source:i,onOutput:o,onCreated:a}}async function runExistingConnector(t){let cleanupCurrentAttempt=async()=>({state:`clean`}),notInstalled=()=>(e.warning(`The existing Slack connector \`${t.ref.uid}\` is not connected to a Slack workspace. eve did not remove it because this run did not create it. If its original browser request is still open, complete it; otherwise run \`vercel connect remove ${t.ref.uid} --disconnect-all --yes\` before trying again.`),{state:`existing-not-installed`,connectorUid:t.ref.uid}),finishExistingOutcome=async e=>e.state===`timed-out`?notInstalled():finishOutcome(e,t,cleanupCurrentAttempt);if(i.awaitChoice!==void 0){let e=await raceAttemptAgainstChoice({prompt:i.awaitChoice({status:`Waiting for the existing Slack workspace connection...`,context:`Complete the original setup in the browser`,actions:[{value:`cancel`,label:`Stop waiting`}]}),outerSignal:i.signal,run:e=>runAttempt({...attemptInput(t,()=>{}),signal:e,phase:(e,t)=>t()})});return e.via===`work`?finishExistingOutcome(e.outcome):e.settled?.state===`attached`?finishExistingOutcome(e.settled):notInstalled()}return finishExistingOutcome(await runAttempt({...attemptInput(t,()=>{}),signal:i.signal,phase:(t,n)=>withPhase(e,t,n)}))}async function runUncontrolledAttempt(t){let n,cleanupCurrentAttempt=()=>cleanupNewAttempt(t,n),r=runAttempt({...attemptInput(t,e=>{n=e}),signal:i.signal,phase:(t,n)=>withPhase(e,t,n)});try{return await finishOutcome(await r,t,cleanupCurrentAttempt)}catch(e){throw i.signal?.aborted===!0&&await cleanupCurrentAttempt(),e}}async function runInteractiveAttempt(n,a){let s,cleanupCurrentAttempt=()=>cleanupNewAttempt(n,s),l=a({status:`Waiting for Slack setup to finish...`,context:`Complete setup in the browser, then wait while eve verifies the connection`,actions:[{value:`retry`,label:`Did your browser not open? Try again`},{value:`cancel`,label:`Cancel`}]}),u;try{u=await raceAttemptAgainstChoice({prompt:l,outerSignal:i.signal,run:t=>runAttempt({...attemptInput(n,e=>{s=e}),signal:t,phase:e.spinner===void 0?(e,t)=>t():(t,n)=>withPhase(e,t,n)})})}catch(e){throw await cleanupCurrentAttempt(),e}if(u.via===`work`)return{state:`finished`,result:await finishOutcome(u.outcome,n,cleanupCurrentAttempt)};if(u.settled?.state===`attached`)return{state:`finished`,result:await finishOutcome(u.settled,n,cleanupCurrentAttempt)};let d=await cleanupCurrentAttempt();if(d.state===`failed`)return{state:`finished`,result:cleanupFailureResult(d)};if(u.choice===`retry`){let n=await withPhase(e,`Checking existing Slack connectors...`,()=>listSlackConnectors(r,t,o,i.signal));return i.signal?.throwIfAborted(),n.state===`failed`?(e.warning(`Could not inspect existing Slack connectors, so eve did not create another one. ${n.message}`),{state:`finished`,result:{state:`connector-lookup-failed`}}):{state:`retry`,source:{state:`new`,baselineConnectorUids:new Set(n.connectors.map(e=>e.uid))}}}return{state:`finished`,result:{state:`cancelled`}}}if(g.state===`found`)return runExistingConnector({state:`existing`,ref:g.connector});let _={state:`new`,baselineConnectorUids:g.connectorUids};if(i.awaitChoice===void 0)return runUncontrolledAttempt(_);for(;;){let e=await runInteractiveAttempt(_,i.awaitChoice);if(e.state===`finished`)return e.result;_=e.source}}async function reconcileSlackUid(e,t,n,r){if(n.state!==`attached`||n.connectorUid===r)return!0;let{patched:i}=await updateSlackChannelConnectorUid(join(t,`agent/channels/slack.ts`),n.connectorUid);return i?!0:(e.warning(`Could not patch agent/channels/slack.ts automatically. Update \`connectSlackCredentials("...")\` to \`"${n.connectorUid}"\` and run \`vercel deploy --prod\`.`),!1)}export{parseCreatedSlackConnector,parseSlackConnectorDetails,pickSlackConnector,provisionSlackbot,reconcileSlackUid};
|
|
@@ -107,6 +107,11 @@ export interface AgentExperimentalDefinition {
|
|
|
107
107
|
* environment variable (`"1"` enables it) for backwards compatibility.
|
|
108
108
|
*/
|
|
109
109
|
readonly codeMode?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Durable Workflow runtime configuration. Root agents may use this to select
|
|
112
|
+
* the Workflow world backing sessions and runs.
|
|
113
|
+
*/
|
|
114
|
+
readonly workflow?: AgentWorkflowDefinition;
|
|
110
115
|
}
|
|
111
116
|
/**
|
|
112
117
|
* Advanced hosted-build controls authored in `agent.ts`.
|
|
@@ -126,6 +131,24 @@ export interface AgentBuildDefinition {
|
|
|
126
131
|
*/
|
|
127
132
|
readonly externalDependencies?: string[];
|
|
128
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Package name for a Workflow world module.
|
|
136
|
+
*
|
|
137
|
+
* The package must export either a default factory or a `createWorld` factory.
|
|
138
|
+
* The factory is called at runtime so credentials and deployment-specific
|
|
139
|
+
* options can come from environment variables instead of the compiled manifest.
|
|
140
|
+
*/
|
|
141
|
+
export type AgentWorkflowWorldDefinition = string;
|
|
142
|
+
/**
|
|
143
|
+
* Advanced durable-runtime configuration for eve's Workflow SDK integration.
|
|
144
|
+
*/
|
|
145
|
+
export interface AgentWorkflowDefinition {
|
|
146
|
+
/**
|
|
147
|
+
* Workflow world module used for durable workflow storage, queueing, hooks,
|
|
148
|
+
* and streaming.
|
|
149
|
+
*/
|
|
150
|
+
readonly world?: AgentWorkflowWorldDefinition;
|
|
151
|
+
}
|
|
129
152
|
/**
|
|
130
153
|
* Compiled-side agent definition. Carries a `name` because the compiler
|
|
131
154
|
* stamps the path-derived `agentId` onto every compiled agent node.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared grapheme-cluster boundaries for editable text. JavaScript offsets
|
|
3
|
+
* stay UTF-16 offsets; segmentation defines which offsets are legal caret
|
|
4
|
+
* positions and which spans editing operations may remove.
|
|
5
|
+
*/
|
|
6
|
+
interface GraphemeSpan {
|
|
7
|
+
readonly text: string;
|
|
8
|
+
readonly start: number;
|
|
9
|
+
readonly end: number;
|
|
10
|
+
}
|
|
11
|
+
/** Splits text into grapheme clusters while preserving `String.slice` offsets. */
|
|
12
|
+
export declare function graphemes(text: string): GraphemeSpan[];
|
|
13
|
+
/** The nearest grapheme boundary strictly before `offset`. */
|
|
14
|
+
export declare function previousGraphemeBoundary(text: string, offset: number): number;
|
|
15
|
+
/** The nearest grapheme boundary strictly after `offset`. */
|
|
16
|
+
export declare function nextGraphemeBoundary(text: string, offset: number): number;
|
|
17
|
+
/** The nearest grapheme boundary at or after `offset`. */
|
|
18
|
+
export declare function graphemeBoundaryAtOrAfter(text: string, offset: number): number;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const graphemeSegmenter=new Intl.Segmenter(void 0,{granularity:`grapheme`});function graphemes(t){return Array.from(graphemeSegmenter.segment(t),({index:e,segment:t})=>({text:t,start:e,end:e+t.length}))}function previousGraphemeBoundary(t,n){let r=Math.max(0,Math.min(n,t.length));return r===0?0:graphemeSegmenter.segment(t).containing(r-1)?.index??0}function nextGraphemeBoundary(t,n){let r=Math.max(0,Math.min(n,t.length));if(r===t.length)return r;let i=graphemeSegmenter.segment(t).containing(r);return i===void 0?t.length:i.index+i.segment.length}function graphemeBoundaryAtOrAfter(t,n){let r=Math.max(0,Math.min(n,t.length));if(r===0||r===t.length)return r;let i=graphemeSegmenter.segment(t).containing(r);return i===void 0||i.index===r?r:i.index+i.segment.length}export{graphemeBoundaryAtOrAfter,graphemes,nextGraphemeBoundary,previousGraphemeBoundary};
|
package/docs/agent-config.md
CHANGED
|
@@ -54,16 +54,43 @@ export default defineAgent({
|
|
|
54
54
|
|
|
55
55
|
See [Default harness](./concepts/default-harness#compaction) for how the loop applies it.
|
|
56
56
|
|
|
57
|
+
## Workflow world
|
|
58
|
+
|
|
59
|
+
By default, eve selects the Workflow SDK world for the host: Vercel Workflow on
|
|
60
|
+
Vercel, and the SDK's local world in local development or `eve start`. Advanced
|
|
61
|
+
self-hosted deployments can select the Workflow world package to use from the
|
|
62
|
+
root `agent.ts`:
|
|
63
|
+
|
|
64
|
+
```ts title="agent/agent.ts"
|
|
65
|
+
import { defineAgent } from "eve";
|
|
66
|
+
|
|
67
|
+
export default defineAgent({
|
|
68
|
+
model: "anthropic/claude-opus-4.8",
|
|
69
|
+
experimental: {
|
|
70
|
+
workflow: {
|
|
71
|
+
world: "@workflow/world-postgres",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Install that package in your app. It should export a default factory or
|
|
78
|
+
`createWorld()` function.
|
|
79
|
+
|
|
80
|
+
Put credentials and host-specific options in runtime environment variables read
|
|
81
|
+
by the world package, not in `agent.ts`. If the installed package must stay
|
|
82
|
+
external in hosted output, list it in `build.externalDependencies`.
|
|
83
|
+
|
|
57
84
|
## Other defineAgent fields
|
|
58
85
|
|
|
59
86
|
`defineAgent` takes a few more fields, all optional. For the exported types, see the [TypeScript API](./reference/typescript-api).
|
|
60
87
|
|
|
61
|
-
| Field | Type
|
|
62
|
-
| -------------- |
|
|
63
|
-
| `modelOptions` | `AgentModelOptionsDefinition`
|
|
64
|
-
| `experimental` | `{ codeMode?: boolean }`
|
|
65
|
-
| `outputSchema` | Standard Schema or a JSON Schema object
|
|
66
|
-
| `build` | `{ externalDependencies?: string[] }`
|
|
88
|
+
| Field | Type | Default | Description |
|
|
89
|
+
| -------------- | ------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
90
|
+
| `modelOptions` | `AgentModelOptionsDefinition` | none | Provider option overrides forwarded to the model call. |
|
|
91
|
+
| `experimental` | `{ codeMode?: boolean; workflow?: { world?: string } }` | flags unset | Opt-in flags that can change or disappear in any release. Treat them as unstable. `codeMode` routes executable tools through a sandboxed code-execution wrapper, where the model writes JavaScript that calls the tools inside the [sandbox](./sandbox). `workflow.world` selects the Workflow world package backing session state, queues, hooks, and streams on the root agent. |
|
|
92
|
+
| `outputSchema` | Standard Schema or a JSON Schema object | none | Structured return type for task-mode runs (a subagent, schedule, or remote job). Interactive conversation turns ignore it unless the client supplies a per-message schema. |
|
|
93
|
+
| `build` | `{ externalDependencies?: string[] }` | none | Hosted-build packaging controls. `externalDependencies` keeps listed packages external while eve compiles authored modules such as tools and channels, and traces those packages into the hosted output. |
|
|
67
94
|
|
|
68
95
|
`codeMode` is experimental and may change or be removed.
|
|
69
96
|
|
|
@@ -24,20 +24,20 @@ Compaction also preserves the framework's own tool state automatically. It reset
|
|
|
24
24
|
|
|
25
25
|
These ship with every agent, no imports. The harness shows the model the tool descriptors first, then executes only what the model actually calls; discovery never runs them. The shell and file tools (`bash`, `read_file`, `write_file`, `glob`, `grep`) live in the app runtime and proxy their work into the agent's single [sandbox](../sandbox); the rest run in the app runtime. The "Where it runs" column below names where each tool's effect lands.
|
|
26
26
|
|
|
27
|
-
| Tool | Does
|
|
28
|
-
| ------------------- |
|
|
29
|
-
| `bash` | Run a shell command.
|
|
30
|
-
| `read_file` | Read a text file with line-numbered output (enables read-before-write).
|
|
31
|
-
| `write_file` | Write a complete file; enforces read-before-write and stale-read detection.
|
|
32
|
-
| `glob` | Find files by glob pattern.
|
|
33
|
-
| `grep` | Search file contents by regex.
|
|
34
|
-
| `web_fetch` | Fetch a URL.
|
|
35
|
-
| `web_search` | Search the web (provider-managed; resolved from the model provider).
|
|
36
|
-
| `todo` | Maintain a durable per-session todo list.
|
|
37
|
-
| `ask_question` | Ask the user a clarifying question or a choice mid-turn and park until they answer. No `execute`; the model calls it with `{ prompt, options?, allowFreeform? }`. See [Human-in-the-loop](
|
|
38
|
-
| `agent` | Delegate a subtask to a copy of itself (shares the parent sandbox + tools, fresh history/state).
|
|
39
|
-
| `load_skill` | Pull an on-demand [skill](../skills)'s instructions into the current turn. Present only when the agent declares skills.
|
|
40
|
-
| `connection_search` | Discover tools across declared [connections](../connections); matched tools become directly callable. Present only when the agent declares connections.
|
|
27
|
+
| Tool | Does | Where it runs |
|
|
28
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
|
29
|
+
| `bash` | Run a shell command. | Sandbox |
|
|
30
|
+
| `read_file` | Read a text file with line-numbered output (enables read-before-write). | Sandbox FS |
|
|
31
|
+
| `write_file` | Write a complete file; enforces read-before-write and stale-read detection. | Sandbox FS |
|
|
32
|
+
| `glob` | Find files by glob pattern. | Sandbox FS |
|
|
33
|
+
| `grep` | Search file contents by regex. | Sandbox FS |
|
|
34
|
+
| `web_fetch` | Fetch a URL. | App runtime |
|
|
35
|
+
| `web_search` | Search the web (provider-managed; resolved from the model provider). | Provider |
|
|
36
|
+
| `todo` | Maintain a durable per-session todo list. | App runtime |
|
|
37
|
+
| `ask_question` | Ask the user a clarifying question or a choice mid-turn and park until they answer. No `execute`; the model calls it with `{ prompt, options?, allowFreeform? }`. See [Human-in-the-loop](/docs/human-in-the-loop). | App runtime |
|
|
38
|
+
| `agent` | Delegate a subtask to a copy of itself (shares the parent sandbox + tools, fresh history/state). | App runtime |
|
|
39
|
+
| `load_skill` | Pull an on-demand [skill](../skills)'s instructions into the current turn. Present only when the agent declares skills. | App runtime |
|
|
40
|
+
| `connection_search` | Discover tools across declared [connections](../connections); matched tools become directly callable. Present only when the agent declares connections. | App runtime |
|
|
41
41
|
|
|
42
42
|
Notes:
|
|
43
43
|
|
|
@@ -19,7 +19,22 @@ The Workflow SDK is not inherently tied to Vercel. In local development and in a
|
|
|
19
19
|
|
|
20
20
|
Nitro hosts the HTTP routes and workflow entrypoints. It does not supply the workflow state store or the sandbox runtime. Those are separate adapters: Workflow uses the active world implementation, and Sandbox uses the backend from `agent/sandbox` or `defaultBackend()`.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
For advanced self-hosted deployments, the root `agent.ts` can select the installed Workflow world package to use with `experimental.workflow.world`:
|
|
23
|
+
|
|
24
|
+
```ts title="agent/agent.ts"
|
|
25
|
+
import { defineAgent } from "eve";
|
|
26
|
+
|
|
27
|
+
export default defineAgent({
|
|
28
|
+
model: "anthropic/claude-opus-4.8",
|
|
29
|
+
experimental: {
|
|
30
|
+
workflow: {
|
|
31
|
+
world: "@workflow/world-postgres",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The world package backs workflow state, queues, hooks, and streams. Keep secrets and deployment-specific options in runtime environment variables read by that package, not in `agent.ts`. See [agent.ts](../agent-config#workflow-world) and [Workflow Worlds](https://workflow-sdk.dev/worlds).
|
|
23
38
|
|
|
24
39
|
## Resuming after a crash
|
|
25
40
|
|
|
@@ -34,33 +34,33 @@ curl http://127.0.0.1:3000/eve/v1/session/<sessionId>/stream
|
|
|
34
34
|
|
|
35
35
|
The stream is newline-delimited JSON (NDJSON), one event per line:
|
|
36
36
|
|
|
37
|
-
| Event | Meaning
|
|
38
|
-
| ------------------------- |
|
|
39
|
-
| `session.started` | A durable session was created.
|
|
40
|
-
| `turn.started` | A new turn began.
|
|
41
|
-
| `message.received` | An inbound user message was accepted.
|
|
42
|
-
| `step.started` | A model step began.
|
|
43
|
-
| `actions.requested` | The model requested tool calls.
|
|
44
|
-
| `action.result` | A tool call returned.
|
|
45
|
-
| `input.requested` | The run paused for human input ([HITL](
|
|
46
|
-
| `subagent.called` | A subagent was delegated; carries `childSessionId` to attach to.
|
|
47
|
-
| `subagent.completed` | A delegated subagent finished.
|
|
48
|
-
| `reasoning.appended` | A reasoning delta (incremental, with cumulative text so far).
|
|
49
|
-
| `reasoning.completed` | The finalized reasoning block.
|
|
50
|
-
| `message.appended` | An assistant text delta (incremental, with cumulative text so far).
|
|
51
|
-
| `message.completed` | A finalized assistant text block.
|
|
52
|
-
| `result.completed` | The finalized structured result for a turn that requested an output schema; carries `result`.
|
|
53
|
-
| `compaction.requested` | Context-window compaction began; carries `modelId`, `sessionId`, `turnId`, `usageInputTokens`.
|
|
54
|
-
| `compaction.completed` | A compaction checkpoint was written to durable history.
|
|
55
|
-
| `authorization.required` | A connection needs OAuth; carries `name`, `description`, and an `authorization` challenge.
|
|
56
|
-
| `authorization.completed` | A connection's authorization resolved; carries `outcome`.
|
|
57
|
-
| `step.completed` | A model step finished; carries `finishReason` and usage.
|
|
58
|
-
| `step.failed` | A model step failed; carries `{ code, message, details? }`.
|
|
59
|
-
| `turn.completed` | The turn finished.
|
|
60
|
-
| `turn.failed` | The turn failed; carries `{ code, message, details? }`.
|
|
61
|
-
| `session.waiting` | The session parked, waiting for the next input (a message, an answer).
|
|
62
|
-
| `session.failed` | The session failed.
|
|
63
|
-
| `session.completed` | The session reached a terminal end.
|
|
37
|
+
| Event | Meaning |
|
|
38
|
+
| ------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `session.started` | A durable session was created. |
|
|
40
|
+
| `turn.started` | A new turn began. |
|
|
41
|
+
| `message.received` | An inbound user message was accepted. |
|
|
42
|
+
| `step.started` | A model step began. |
|
|
43
|
+
| `actions.requested` | The model requested tool calls. |
|
|
44
|
+
| `action.result` | A tool call returned. |
|
|
45
|
+
| `input.requested` | The run paused for human input ([HITL](/docs/human-in-the-loop) approval or `ask_question`); carries `requests`. |
|
|
46
|
+
| `subagent.called` | A subagent was delegated; carries `childSessionId` to attach to. |
|
|
47
|
+
| `subagent.completed` | A delegated subagent finished. |
|
|
48
|
+
| `reasoning.appended` | A reasoning delta (incremental, with cumulative text so far). |
|
|
49
|
+
| `reasoning.completed` | The finalized reasoning block. |
|
|
50
|
+
| `message.appended` | An assistant text delta (incremental, with cumulative text so far). |
|
|
51
|
+
| `message.completed` | A finalized assistant text block. |
|
|
52
|
+
| `result.completed` | The finalized structured result for a turn that requested an output schema; carries `result`. |
|
|
53
|
+
| `compaction.requested` | Context-window compaction began; carries `modelId`, `sessionId`, `turnId`, `usageInputTokens`. |
|
|
54
|
+
| `compaction.completed` | A compaction checkpoint was written to durable history. |
|
|
55
|
+
| `authorization.required` | A connection needs OAuth; carries `name`, `description`, and an `authorization` challenge. |
|
|
56
|
+
| `authorization.completed` | A connection's authorization resolved; carries `outcome`. |
|
|
57
|
+
| `step.completed` | A model step finished; carries `finishReason` and usage. |
|
|
58
|
+
| `step.failed` | A model step failed; carries `{ code, message, details? }`. |
|
|
59
|
+
| `turn.completed` | The turn finished. |
|
|
60
|
+
| `turn.failed` | The turn failed; carries `{ code, message, details? }`. |
|
|
61
|
+
| `session.waiting` | The session parked, waiting for the next input (a message, an answer). |
|
|
62
|
+
| `session.failed` | The session failed. |
|
|
63
|
+
| `session.completed` | The session reached a terminal end. |
|
|
64
64
|
|
|
65
65
|
`reasoning.appended` and `message.appended` stream deltas as they arrive, and each one carries both the new delta and the cumulative text for the current block. The finalized block shows up on `message.completed` and `reasoning.completed`, which is the compatibility path for clients that don't render incremental streaming.
|
|
66
66
|
|
|
@@ -17,6 +17,7 @@ Run-level assertions read the whole run, so they take no value. They are methods
|
|
|
17
17
|
| `t.messageIncludes(token)` | Joined assistant text contains `token` (string or RegExp) |
|
|
18
18
|
| `t.outputEquals(value)` / `t.outputMatches(schema)` | Deep equality or Standard Schema (e.g. Zod) validation of the agent's structured output |
|
|
19
19
|
| `t.calledTool(name, opts?)` | A matching tool call happened (`input`, `output`, `isError`, `times` constraints) |
|
|
20
|
+
| `t.loadedSkill(skill, opts?)` | Sugar for `t.calledTool("load_skill", { input: { skill }, ...opts })` |
|
|
20
21
|
| `t.notCalledTool(name)` | No call to `name` |
|
|
21
22
|
| `t.toolOrder([...names])` | Tool names appear in order (other calls may interleave) |
|
|
22
23
|
| `t.usedNoTools()` | No tool calls at all |
|
|
@@ -21,7 +21,22 @@ Nitro is the HTTP host layer. It gives eve a build artifact that can serve the h
|
|
|
21
21
|
|
|
22
22
|
On Vercel, eve emits Vercel Build Output, the Workflow SDK runs on Vercel Workflow, and `defaultBackend()` selects Vercel Sandbox. Outside Vercel, `eve start` serves the standard Nitro Node output, the Workflow SDK uses its local world by default, and `defaultBackend()` selects a local sandbox backend in availability order. That local workflow world persists run state on disk and has no direct coupling to Vercel; Vercel-only behavior such as latest-deployment routing and dashboard run attributes is additive.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Advanced self-hosted deployments can select a different installed Workflow world package in the root `agent.ts`:
|
|
25
|
+
|
|
26
|
+
```ts title="agent/agent.ts"
|
|
27
|
+
import { defineAgent } from "eve";
|
|
28
|
+
|
|
29
|
+
export default defineAgent({
|
|
30
|
+
model: "anthropic/claude-opus-4.8",
|
|
31
|
+
experimental: {
|
|
32
|
+
workflow: {
|
|
33
|
+
world: "@acme/eve-workflow-world",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The world package should read credentials and host-specific options from runtime environment variables. It should export a default factory or `createWorld()` function. See [Workflow Worlds](https://workflow-sdk.dev/worlds) for the underlying SDK abstraction.
|
|
25
40
|
|
|
26
41
|
## 2. Environment variables and secrets
|
|
27
42
|
|
|
@@ -125,7 +140,7 @@ Eve writes the standard Nitro output under `.output/` instead of Vercel Build Ou
|
|
|
125
140
|
|
|
126
141
|
Self-deployed agents should make the Vercel-specific choices explicit:
|
|
127
142
|
|
|
128
|
-
- Let the Workflow SDK use its default local world, which stores workflow state under `.workflow-data`,
|
|
143
|
+
- Let the Workflow SDK use its default local world, which stores workflow state under `.workflow-data`, configure your host so that directory is on persistent storage, or select another world with `experimental.workflow.world` in the root `agent.ts`.
|
|
129
144
|
- Install the AI SDK package for your provider, then use a direct provider model object and `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` when you want no Gateway dependency.
|
|
130
145
|
- Use `AI_GATEWAY_API_KEY` if you still want Gateway routing from a non-Vercel host.
|
|
131
146
|
- Replace `vercelOidc()` with auth that your host can verify.
|
package/docs/guides/dev-tui.md
CHANGED
|
@@ -58,18 +58,21 @@ The provider row demands attention (a bold yellow "Configure provider" with "Req
|
|
|
58
58
|
|
|
59
59
|
## Keyboard shortcuts
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Chat and freeform `ask_question` inputs behave like a shell line editor.
|
|
62
62
|
|
|
63
63
|
| Key | Action |
|
|
64
64
|
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
65
|
-
| `Enter` |
|
|
66
|
-
| `
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
| `
|
|
65
|
+
| `Enter` | Submit the message or question response. |
|
|
66
|
+
| `Shift+Enter` | Insert a newline without sending (needs a terminal that reports modified keys). |
|
|
67
|
+
| `Ctrl+C` | Interrupt a running turn. At the chat or freeform-question prompt, clear non-empty input; when empty, quit. |
|
|
68
|
+
| `↑` / `↓` | Move between input lines; at a chat-buffer edge, navigate messages you have sent this session. |
|
|
69
|
+
| `←` / `→`, `Home` / `End`, `Ctrl+A` / `Ctrl+E` | Move the caret; Home/End stay within the current line. |
|
|
70
|
+
| `Ctrl+U` / `Ctrl+K` / `Ctrl+W` | Delete to the start of the line, to its end, or through the previous word. |
|
|
70
71
|
| `Ctrl+L` | Cycle the log display mode (`none → all → stderr → sandbox → none`) and briefly show the mode in the status line. |
|
|
71
72
|
| `Ctrl+R` | Redraw the screen. |
|
|
72
73
|
|
|
74
|
+
In terminals that support bracketed paste, pasting multi-line text into chat or a freeform question inserts it intact and renders one row per line rather than submitting at the first line. `Shift+Enter` adds a line by hand. The input grows down to the available terminal height, then scrolls to keep the caret visible; `Enter` submits the whole response.
|
|
75
|
+
|
|
73
76
|
If a turn fails terminally (the server session dies or the connection drops), the TUI starts a fresh session and notes it inline so you can keep going. Server-side context resets with the old session.
|
|
74
77
|
|
|
75
78
|
## Answer the agent inline
|
|
@@ -77,7 +80,7 @@ If a turn fails terminally (the server session dies or the connection drops), th
|
|
|
77
80
|
When the agent needs something from you, the TUI asks inline.
|
|
78
81
|
|
|
79
82
|
- Tool approvals are a `y` or `n`.
|
|
80
|
-
- Option questions let you pick with `↑` / `↓` and `Enter`, or you can
|
|
83
|
+
- Option questions let you pick with `↑` / `↓` and `Enter`, or you can compose a multi-line freeform answer.
|
|
81
84
|
- If a tool needs an authorized [connection](../connections), the URL shows up right in the transcript, and the turn picks back up once you finish the flow.
|
|
82
85
|
|
|
83
86
|
## Control what logs show
|
|
@@ -116,6 +116,8 @@ export default defineDynamic({
|
|
|
116
116
|
|
|
117
117
|
The caller's team gets its own playbook advertised as a loadable skill; everyone else gets nothing.
|
|
118
118
|
|
|
119
|
+
Skills follow the same naming rule as tools: a single `defineSkill(...)` is named after the file slug, while a map return names each entry `slug__key` — even when the map holds one entry, so adding a second skill later never renames the first.
|
|
120
|
+
|
|
119
121
|
## Dynamic instructions
|
|
120
122
|
|
|
121
123
|
A dynamic instructions file resolves the per-session system prompt the same way, returning `defineInstructions(...)` built from the principal, tenant, or external data:
|
|
@@ -100,7 +100,7 @@ Assistant text, reasoning, tool calls, and tool results stream into `data` as th
|
|
|
100
100
|
|
|
101
101
|
## Human-in-the-loop prompts
|
|
102
102
|
|
|
103
|
-
Tools opt into approval with `needsApproval`, and the model can also ask a question with `ask_question` — see [Human-in-the-loop](
|
|
103
|
+
Tools opt into approval with `needsApproval`, and the model can also ask a question with `ask_question` — see [Human-in-the-loop](/docs/human-in-the-loop) for the server-side model. Either way the stream emits an `input.requested` event, and the pending request rides on a `dynamic-tool` part of the latest message at `part.toolMetadata?.eve?.inputRequest`. Read it, then answer through the same session with `send()`:
|
|
104
104
|
|
|
105
105
|
```tsx
|
|
106
106
|
const request = agent.data.messages
|
|
@@ -50,7 +50,7 @@ export default defineTool({
|
|
|
50
50
|
| `defineEvalConfig` | `eve/evals` | `evals/evals.config.ts` | [Evals](../evals/overview) |
|
|
51
51
|
| `useEveAgent` | `eve/react`, `eve/vue`, `eve/svelte` | frontend | [Frontend](../guides/frontend/overview) |
|
|
52
52
|
|
|
53
|
-
A few non-`define*` helpers round out the set: `disableTool` and `ExperimentalWorkflow` from `eve/tools` (see [Default harness](../concepts/default-harness)), the route verbs `GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`WS` from `eve/channels`, the approval predicates `always`/`once`/`never` from `eve/tools/approval`, and the channel auth helpers `localDev`/`vercelOidc`/`placeholderAuth` from `eve/channels/auth`. To wrap a built-in tool, import its default value from `eve/tools/defaults` (`bash`, `readFile`, `writeFile`, `glob`, `grep`, `webFetch`, `webSearch`, `todo`, `loadSkill`).
|
|
53
|
+
A few non-`define*` helpers round out the set: `disableTool` and `ExperimentalWorkflow` from `eve/tools` (see [Default harness](../concepts/default-harness)), the route verbs `GET`/`POST`/`PUT`/`PATCH`/`DELETE`/`WS` from `eve/channels`, the approval predicates `always`/`once`/`never` from `eve/tools/approval`, and the channel auth helpers `localDev`/`vercelOidc`/`placeholderAuth` from `eve/channels/auth`. To wrap a built-in tool, import its default value from `eve/tools/defaults` (`bash`, `readFile`, `writeFile`, `glob`, `grep`, `webFetch`, `webSearch`, `todo`, `loadSkill`). `AgentWorkflowDefinition` and `AgentWorkflowWorldDefinition` are exported from `eve` for the `defineAgent({ experimental: { workflow } })` config shape.
|
|
54
54
|
|
|
55
55
|
## Runtime context (`ctx`)
|
|
56
56
|
|
|
@@ -68,7 +68,7 @@ A few non-`define*` helpers round out the set: `disableTool` and `ExperimentalWo
|
|
|
68
68
|
|
|
69
69
|
| Import | Holds |
|
|
70
70
|
| ----------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
71
|
-
| `eve` | `defineAgent`, `defineRemoteAgent
|
|
71
|
+
| `eve` | `defineAgent`, `defineRemoteAgent`, agent config types |
|
|
72
72
|
| `eve/tools` | `defineTool`, `defineDynamic`, `disableTool`, `ExperimentalWorkflow` |
|
|
73
73
|
| `eve/tools/defaults` | the built-in tools as plain values |
|
|
74
74
|
| `eve/tools/approval` | `always`, `once`, `never` |
|