experimental-ash 0.55.0 → 0.55.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/docs/public/advanced/cli-build-and-debugging.md +1 -1
  3. package/dist/docs/public/advanced/dev-tui.md +202 -0
  4. package/dist/docs/public/advanced/meta.json +1 -0
  5. package/dist/docs/public/advanced/vercel-deployment.md +3 -3
  6. package/dist/docs/public/getting-started.mdx +1 -1
  7. package/dist/docs/public/human-in-the-loop.mdx +4 -4
  8. package/dist/docs/public/onboarding.md +2 -2
  9. package/dist/src/cli/dev/{repl.d.ts → repl/repl.d.ts} +1 -1
  10. package/dist/src/cli/dev/{repl.js → repl/repl.js} +1 -1
  11. package/dist/src/cli/dev/tui/layout.d.ts +24 -0
  12. package/dist/src/cli/dev/tui/layout.js +3 -0
  13. package/dist/src/cli/dev/tui/markdown.d.ts +14 -0
  14. package/dist/src/cli/dev/tui/markdown.js +3 -0
  15. package/dist/src/cli/dev/tui/runner.d.ts +205 -0
  16. package/dist/src/cli/dev/tui/runner.js +1 -0
  17. package/dist/src/cli/dev/tui/terminal-frame-buffer.d.ts +21 -0
  18. package/dist/src/cli/dev/tui/terminal-frame-buffer.js +2 -0
  19. package/dist/src/cli/dev/tui/terminal-renderer.d.ts +111 -0
  20. package/dist/src/cli/dev/tui/terminal-renderer.js +12 -0
  21. package/dist/src/cli/dev/tui/terminal-text.d.ts +6 -0
  22. package/dist/src/cli/dev/tui/terminal-text.js +1 -0
  23. package/dist/src/cli/dev/tui/test/index.d.ts +10 -0
  24. package/dist/src/cli/dev/tui/test/index.js +1 -0
  25. package/dist/src/cli/dev/tui/test/mock-terminal.d.ts +28 -0
  26. package/dist/src/cli/dev/tui/test/mock-terminal.js +3 -0
  27. package/dist/src/cli/dev/tui/tui.d.ts +32 -0
  28. package/dist/src/cli/dev/tui/tui.js +1 -0
  29. package/dist/src/cli/dev/tui/types.d.ts +68 -0
  30. package/dist/src/cli/dev/tui/types.js +1 -0
  31. package/dist/src/cli/run.d.ts +66 -0
  32. package/dist/src/cli/run.js +2 -2
  33. package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
  34. package/dist/src/evals/scorers/autoevals.js +1 -1
  35. package/dist/src/execution/node-step.js +1 -1
  36. package/dist/src/harness/code-mode.js +1 -1
  37. package/dist/src/harness/tool-loop.js +1 -1
  38. package/dist/src/internal/application/package.js +1 -1
  39. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  40. package/dist/src/public/channels/ash.js +1 -1
  41. package/package.json +6 -1
  42. /package/dist/src/cli/dev/{input-requests.d.ts → repl/input-requests.d.ts} +0 -0
  43. /package/dist/src/cli/dev/{input-requests.js → repl/input-requests.js} +0 -0
  44. /package/dist/src/cli/dev/{input.d.ts → repl/input.d.ts} +0 -0
  45. /package/dist/src/cli/dev/{input.js → repl/input.js} +0 -0
  46. /package/dist/src/cli/dev/{terminal.d.ts → repl/terminal.d.ts} +0 -0
  47. /package/dist/src/cli/dev/{terminal.js → repl/terminal.js} +0 -0
@@ -0,0 +1 @@
1
+ import{TerminalRenderer}from"./terminal-renderer.js";import{isCurrentTurnBoundaryEvent}from"#client/index.js";import{getToolName,isToolUIPart}from"ai";var AshTUIRunner=class{#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u;#d=new Map;#f=new Map;#p=new Map;#m=new Map;#h=new Set;constructor(e){this.#e=e.session,e.client!==void 0&&(this.#t=e.client),this.#n=createRenderer(e)??createDefaultRenderer(e),this.#r=e.name??`Ash`,this.#i=e.tools??`full`,this.#a=e.reasoning??`full`,this.#o=e.subagents??`full`,this.#s=e.connectionAuth??`full`,this.#c=e.assistantResponseStats??`tokensPerSecond`,this.#l=e.contextSize,this.#u=e.formatTransportError??formatErrorMessage}async run(){let e=this.#r,t=[],n=0,generateMessageId=()=>`message-${++n}`,r,i,a=!1,o=!1;for(;;){if(!o){if(r==null){if(!this.#n.readPrompt){if(a)return;throw Error(`No prompt was provided and the renderer does not support prompt input.`)}try{r=await this.#n.readPrompt({title:e})}catch(e){if(isInterruptedError(e))return;throw e}if(r==null)return}let s=parsePromptCommand(r);if(s===`exit`){this.#n.shutdown?.();return}if(s===`new`){this.#g(),t.length=0,n=0,i=void 0,o=!1,r=void 0,this.#n.reset?.();continue}t.push(createUserMessage(generateMessageId(),r)),a=!0}let s=await this.#_({prompt:o?void 0:r,inputResponses:i,messages:t,generateMessageId});try{let n=await this.#n.renderStream(s,{title:e,submittedPrompt:r,continueSession:!!this.#n.readPrompt,tools:this.#i,reasoning:this.#a,subagents:this.#o,connectionAuth:this.#s,assistantResponseStats:this.#c,contextSize:this.#l,waitForExit:!1});if(n&&n.parts.length>0){let a=findPendingToolApprovalRequests(n),s=findPendingQuestionRequests(this.#d);if(a.length>0||s.length>0){let c=[];if(a.length>0){if(!this.#n.readToolApproval)throw Error(`Tool approval was requested, but the renderer does not support tool approval input.`);for(let t of a){let r=await this.#n.readToolApproval(t,{title:e});applyToolApprovalResponse(n,t,r);let i=this.#d.get(t.approvalId);i&&(c.push({requestId:i.requestId,optionId:r.approved?`approve`:`deny`}),this.#d.delete(t.approvalId))}}if(s.length>0){if(!this.#n.readInputQuestion)throw Error(`An interactive question was requested, but the renderer does not support input questions.`);for(let t of s){let n=toAgentTUIInputQuestion(t),r=await this.#n.readInputQuestion(n,{title:e});if(r===void 0)continue;let i={requestId:t.requestId};r.optionId!==void 0&&(i.optionId=r.optionId),r.text!==void 0&&(i.text=r.text),c.push(i),this.#d.delete(t.requestId)}}upsertResponseMessage(t,n,o),o=!0,i=c,r=void 0;continue}upsertResponseMessage(t,n,o)}}catch(e){if(isInterruptedError(e))return;throw e}o=!1,i=void 0,r=void 0}}#g(){for(let e of this.#p.values())e.abort();this.#p.clear(),this.#f.clear(),this.#d.clear(),this.#m.clear(),this.#h.clear(),this.#t&&(this.#e=this.#t.session())}async#_(e){let t={};e.prompt!==void 0&&(t.message=e.prompt),e.inputResponses!==void 0&&e.inputResponses.length>0&&(t.inputResponses=e.inputResponses);let n;try{n=await this.#e.send(t)}catch(t){if(isInterruptedError(t))throw t;return{uiMessageStream:errorOnlyUIMessageStream({errorText:this.#u(t),generateMessageId:e.generateMessageId,originalMessages:e.messages}),message:lastAssistantMessage(e.messages)}}return{uiMessageStream:ashEventsToUIMessageStream({events:n,generateMessageId:e.generateMessageId,originalMessages:e.messages,pendingInputRequests:this.#d,subagentRuns:this.#f,onSubagentCalled:e=>this.#x(e),onSubagentCompleted:e=>this.#C(e),onConnectionAuthRequired:e=>this.#v(e),onConnectionAuthCompleted:e=>this.#y(e)}),message:lastAssistantMessage(e.messages)}}#v(e){let t={name:e.data.name,description:e.data.description,state:`required`};e.data.authorization!==void 0&&(t.challenge=e.data.authorization),e.data.webhookUrl!==void 0&&(t.webhookUrl=e.data.webhookUrl),this.#m.set(e.data.name,t),this.#b(t)}#y(e){let t=this.#m.get(e.data.name)??{name:e.data.name,description:``,state:e.data.outcome};t.state=e.data.outcome,e.data.reason!==void 0&&(t.reason=e.data.reason),this.#m.set(e.data.name,t),this.#h.delete(e.data.name),this.#b(t),this.#n.setConnectionAuthPendingCount?.(this.#h.size)}#b(e){let t={name:e.name,description:e.description,state:e.state};e.challenge!==void 0&&(t.challenge=e.challenge),e.reason!==void 0&&(t.reason=e.reason),this.#n.upsertConnectionAuth?.(t)}#x(e){let n=e.data.callId;if(this.#p.has(n))return;let r=this.#t;if(!r)return;let i=new AbortController;this.#p.set(n,i),(async()=>{try{let a=r.session({sessionId:e.data.childSessionId,streamIndex:0}).openStream({signal:i.signal});for await(let e of a)if(i.signal.aborted||(this.#w(n,e),isCurrentTurnBoundaryEvent(e)))break}catch(e){if(!isAbortLikeError(e)){let t=formatErrorMessage(e),r=this.#f.get(n);if(r){let{key:e,step:i}=openCurrentSubagentSection(r);i.message=i.message?`${i.message}\n\nstream error: ${t}`:`stream error: ${t}`,i.finalized=!0,r.currentSectionKey=null,this.#n.upsertSubagentStep?.({callId:n,subagentName:r.name,sectionKey:e,reasoning:i.reasoning,message:i.message,finalized:!0})}}}finally{this.#p.delete(n)}})()}#S(e,t,n){let r=t.tools.get(n.childCallId),i=r??{toolName:n.toolName,input:n.input,status:n.status};if(r){let e={"approval-requested":0,executing:1,done:2,failed:2};e[n.status]>e[r.status]&&(r.status=n.status),r.input=n.input}else t.tools.set(n.childCallId,i);this.#n.markChildToolCallId?.(n.childCallId),this.#n.upsertSubagentTool?.({callId:e,subagentName:t.name,childCallId:n.childCallId,toolName:i.toolName,input:i.input,status:i.status})}#C(e){let t=this.#f.get(e);if(t){for(let[n,r]of t.steps)r.finalized||(r.finalized=!0,this.#n.upsertSubagentStep?.({callId:e,subagentName:t.name,sectionKey:n,reasoning:r.reasoning,message:r.message,finalized:!0}));t.currentSectionKey=null}}#w(e,t){let n=this.#f.get(e);if(!n)return;let r=this.#n,emit=(t,i)=>{r.upsertSubagentStep?.({callId:e,subagentName:n.name,sectionKey:t,reasoning:i.reasoning,message:i.message,finalized:i.finalized})},finalizeCurrent=()=>{if(n.currentSectionKey===null)return;let e=n.steps.get(n.currentSectionKey);e&&(e.finalized=!0,emit(n.currentSectionKey,e)),n.currentSectionKey=null};switch(t.type){case`reasoning.appended`:{let{key:e,step:r}=openCurrentSubagentSection(n);r.reasoning+=t.data.reasoningDelta,emit(e,r);break}case`reasoning.completed`:break;case`message.appended`:{let{key:e,step:r}=openCurrentSubagentSection(n);r.message+=t.data.messageDelta,emit(e,r);break}case`message.completed`:{let{key:e,step:r}=openCurrentSubagentSection(n);t.data.message!==null&&r.message.length===0&&(r.message=t.data.message),r.finalized=!0,emit(e,r),n.currentSectionKey=null;break}case`step.completed`:finalizeCurrent();break;case`actions.requested`:finalizeCurrent();for(let r of t.data.actions)r.kind===`tool-call`&&this.#S(e,n,{childCallId:r.callId,toolName:r.toolName,input:r.input,status:`executing`});break;case`input.requested`:finalizeCurrent();for(let r of t.data.requests)r.action.kind===`tool-call`&&this.#S(e,n,{childCallId:r.action.callId,toolName:r.action.toolName,input:r.action.input,status:`approval-requested`});break;case`action.result`:{let i=t.data.result;if(i.kind!==`tool-result`)break;let a=n.tools.get(i.callId);if(!a)break;t.data.status===`failed`?(a.status=`failed`,a.errorText=formatActionResultError(t)):(a.status=`done`,a.output=i.output);let o={callId:e,subagentName:n.name,childCallId:i.callId,toolName:a.toolName,input:a.input,status:a.status};a.output!==void 0&&(o.output=a.output),a.errorText!==void 0&&(o.errorText=a.errorText),r.upsertSubagentTool?.(o);break}default:break}}};function createDefaultRenderer(t){return t.tools===void 0&&t.reasoning===void 0&&t.subagents===void 0&&t.connectionAuth===void 0&&t.assistantResponseStats===void 0&&t.contextSize===void 0&&t.logs===void 0?new TerminalRenderer:new TerminalRenderer({tools:t.tools,reasoning:t.reasoning,subagents:t.subagents,connectionAuth:t.connectionAuth,assistantResponseStats:t.assistantResponseStats,contextSize:t.contextSize,logs:t.logs})}function createRenderer(t){if(t.renderer)return t.renderer;if(!(!t.screen&&!t.userInput))return new TerminalRenderer({tools:t.tools,reasoning:t.reasoning,subagents:t.subagents,connectionAuth:t.connectionAuth,assistantResponseStats:t.assistantResponseStats,contextSize:t.contextSize,logs:t.logs,input:t.userInput,output:t.screen})}async function*ashEventsToUIMessageStream(e){let{events:t,generateMessageId:n,originalMessages:r,pendingInputRequests:i,subagentRuns:a,onSubagentCalled:o,onSubagentCompleted:s,onConnectionAuthRequired:c,onConnectionAuthCompleted:l}=e,u=new Set,d=new Set,f=new Set,p=!1,m=!1,h,ensureStart=()=>{if(!p)return p=!0,{type:`start`,messageId:lastAssistantMessage(r)?.id??n()}};for await(let e of t){let t=ensureStart();switch(t&&(yield t),e.type){case`session.started`:case`turn.started`:case`message.received`:break;case`step.started`:yield{type:`start-step`};break;case`step.completed`:h=e.data.usage,yield*closeOpenTextParts(u),yield*closeOpenReasoningParts(d),yield{type:`finish-step`};break;case`message.appended`:{let t=e,n=textPartId(t.data.turnId,t.data.stepIndex);u.has(n)||(u.add(n),yield{type:`text-start`,id:n}),yield{type:`text-delta`,id:n,delta:t.data.messageDelta};break}case`message.completed`:{let t=textPartId(e.data.turnId,e.data.stepIndex);u.delete(t)&&(yield{type:`text-end`,id:t});break}case`reasoning.appended`:{let t=e,n=reasoningPartId(t.data.turnId,t.data.stepIndex);d.has(n)||(d.add(n),yield{type:`reasoning-start`,id:n}),yield{type:`reasoning-delta`,id:n,delta:t.data.reasoningDelta};break}case`reasoning.completed`:{let t=reasoningPartId(e.data.turnId,e.data.stepIndex);d.delete(t)&&(yield{type:`reasoning-end`,id:t});break}case`actions.requested`:{let t=e.data.actions;for(let e of t)e.kind===`tool-call`&&(f.add(e.callId),yield{type:`tool-input-available`,toolCallId:e.callId,toolName:e.toolName,input:e.input});break}case`input.requested`:{let t=e.data.requests;for(let e of t){if(e.action.kind!==`tool-call`)continue;let t=e.action.callId;f.has(t)||(f.add(t),yield{type:`tool-input-available`,toolCallId:t,toolName:e.action.toolName,input:e.action.input}),i.set(e.requestId,e),!isQuestionRequest(e)&&(yield{type:`tool-approval-request`,approvalId:e.requestId,toolCallId:t})}break}case`action.result`:{let t=e,n=t.data.result.callId;if(!f.has(n))break;t.data.status===`failed`?yield{type:`tool-output-error`,toolCallId:n,errorText:formatActionResultError(t)}:yield{type:`tool-output-available`,toolCallId:n,output:t.data.result.output};break}case`step.failed`:case`turn.failed`:case`session.failed`:yield{type:`error`,errorText:formatFailureMessage(e)};break;case`session.waiting`:case`session.completed`:case`turn.completed`:yield*closeOpenTextParts(u),yield*closeOpenReasoningParts(d),yield{type:`finish`,messageMetadata:createResponseMetadata(h)},m=!0;break;case`subagent.called`:{let t=e;if(!a.has(t.data.callId))a.set(t.data.callId,{name:t.data.name,steps:new Map,currentSectionKey:null,nextSectionKey:0,tools:new Map});else{let e=a.get(t.data.callId);e&&(e.name=t.data.name)}o?.(t);break}case`subagent.started`:case`subagent.event`:break;case`subagent.completed`:s?.(e.data.callId);break;case`authorization.required`:c?.(e);break;case`authorization.completed`:l?.(e);break;default:break}}p||(yield{type:`start`,messageId:n()}),m||(yield*closeOpenTextParts(u),yield*closeOpenReasoningParts(d),yield{type:`finish`,messageMetadata:createResponseMetadata(h)})}async function*errorOnlyUIMessageStream(e){yield{type:`start`,messageId:lastAssistantMessage(e.originalMessages)?.id??e.generateMessageId()},yield{type:`error`,errorText:e.errorText},yield{type:`finish`}}function textPartId(e,t){return`text:${e}:${t}`}function reasoningPartId(e,t){return`reasoning:${e}:${t}`}function*closeOpenTextParts(e){for(let t of e)yield{type:`text-end`,id:t};e.clear()}function*closeOpenReasoningParts(e){for(let t of e)yield{type:`reasoning-end`,id:t};e.clear()}function createResponseMetadata(e){if(!e)return;let{inputTokens:t,outputTokens:n}=e,r=t!=null||n!=null?(t??0)+(n??0):void 0;if(r==null&&n==null)return;let i={};return r!=null&&(i.totalTokens=r),n!=null&&(i.outputTokens=n),{usage:i}}function formatActionResultError(e){if(e.data.error?.message)return e.data.error.message;let t=e.data.result.output;if(typeof t==`string`)return t;try{return JSON.stringify(t)}catch{return`Tool execution failed.`}}function formatFailureMessage(e){let{code:t,message:n}=e.data;return t?`${t}: ${n}`:n}function createUserMessage(e,t){return{id:e,role:`user`,parts:[{type:`text`,text:t}]}}function upsertResponseMessage(e,t,n){if(n&&e.at(-1)?.role===`assistant`){e[e.length-1]=t;return}e.push(t)}function lastAssistantMessage(e){let t=e.at(-1);return t?.role===`assistant`?t:void 0}function isQuestionRequest(e){return e.display===`select`||e.display===`text`?!0:e.display===`confirmation`?!1:e.options!==void 0&&e.options.length>0}function findPendingQuestionRequests(e){let t=[];for(let n of e.values())isQuestionRequest(n)&&t.push(n);return t}function toAgentTUIInputQuestion(e){let t=e.display===`text`?`text`:e.display===`select`||e.options!==void 0&&e.options.length>0?`select`:`text`,n={requestId:e.requestId,prompt:e.prompt,display:t};return e.options!==void 0&&(n.options=e.options.map(e=>{let t={id:e.id,label:e.label};return e.description!==void 0&&(t.description=e.description),e.style!==void 0&&(t.style=e.style),t})),e.allowFreeform!==void 0&&(n.allowFreeform=e.allowFreeform),n}function findPendingToolApprovalRequests(e){let t=[];for(let[i,a]of e.parts.entries()){if(!isToolUIPart(a)||a.state!==`approval-requested`||a.approval.isAutomatic===!0)continue;let o={approvalId:a.approval.id,toolCallId:a.toolCallId,toolName:getToolName(a),input:a.input,messageId:e.id,partIndex:i};a.title!==void 0&&(o.title=a.title),a.providerExecuted!==void 0&&(o.providerExecuted=a.providerExecuted),t.push(o)}return t}function applyToolApprovalResponse(e,t,n){let i=e.parts[t.partIndex];if(!i||!isToolUIPart(i)||i.toolCallId!==t.toolCallId)throw Error(`Could not find tool approval request ${t.approvalId}.`);i.state=`approval-responded`;let a={id:t.approvalId,approved:n.approved};n.reason&&(a.reason=n.reason),i.approval=a}function isInterruptedError(e){return e instanceof Error&&e.message===`Interrupted`}function parsePromptCommand(e){let t=e.trim();return t===`/new`?`new`:t===`/exit`||t===`/quit`?`exit`:null}function isAbortLikeError(e){return!!(e instanceof Error&&(e.name===`AbortError`||e.message.toLowerCase().includes(`abort`)))}function formatErrorMessage(e){if(e instanceof Error)return e.message;if(typeof e==`string`)return e;try{return JSON.stringify(e)}catch{return String(e)}}function openCurrentSubagentSection(e){e.currentSectionKey===null&&(e.currentSectionKey=e.nextSectionKey++,e.steps.set(e.currentSectionKey,{reasoning:``,message:``,finalized:!1}));let t=e.steps.get(e.currentSectionKey);if(!t)throw Error(`invariant: subagent section state missing for current key`);return{key:e.currentSectionKey,step:t}}export{AshTUIRunner};
@@ -0,0 +1,21 @@
1
+ export type TerminalFrameOutput = {
2
+ write(chunk: string | Uint8Array, encodingOrCallback?: BufferEncoding | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean;
3
+ };
4
+ export type TerminalFrameBufferOptions = {
5
+ useSynchronizedUpdates?: boolean;
6
+ };
7
+ export declare class TerminalFrameBuffer {
8
+ #private;
9
+ constructor(output: TerminalFrameOutput, options?: TerminalFrameBufferOptions);
10
+ present(frame: string): void;
11
+ reset(): void;
12
+ /**
13
+ * Writes a control sequence (e.g. alt-screen enter/exit, cursor
14
+ * show/hide) straight to the underlying terminal via the original
15
+ * write captured at construction. This bypasses any later monkey-patch
16
+ * of the output's `write` — notably the renderer's foreign-output log
17
+ * capture — so the renderer's own terminal control writes are never
18
+ * mistaken for captured log output.
19
+ */
20
+ writeControl(sequence: string): void;
21
+ }
@@ -0,0 +1,2 @@
1
+ var TerminalFrameBuffer=class{#e;#t;#n;#r=!1;#i=!1;constructor(e,t){this.#t=e.write.bind(e),this.#e=t?.useSynchronizedUpdates??!0,e.write=((e,t,n)=>(this.#r||(this.#i=!0),this.#t(e,t,n)))}present(e){let t=snapshotFrame(e),n=this.#n&&!this.#i?diffFrame(this.#n,t):`${e}`;this.#n=t,this.#i=!1,n.length!==0&&this.#a(n)}reset(){this.#n=void 0}writeControl(e){this.#t(e)}#a(e){this.#r=!0;try{if(!this.#e){this.#t(e);return}this.#t(`[?2026h${e}[?2026l`)}finally{this.#r=!1}}};function snapshotFrame(e){return{lines:e.split(`
2
+ `)}}function diffFrame(e,t){let n=``,r=Math.max(e.lines.length,t.lines.length);for(let i=0;i<r;i++){let r=t.lines[i];e.lines[i]!==r&&(n+=`[${i+1};1H${r??``}`)}return n}export{TerminalFrameBuffer};
@@ -0,0 +1,111 @@
1
+ import type { AgentTUIInputQuestion, AgentTUIInputQuestionResponse, AgentTUIStreamResult, AgentTUIToolApprovalRequest, AgentTUIToolApprovalResponse, ConnectionAuthUpdate, SubagentStepUpdate, SubagentToolUpdate } from "./runner.js";
2
+ import type { AssistantResponseStatsMode, LogDisplayMode, TerminalPartDisplayMode } from "./types.js";
3
+ import { TerminalFrameBuffer } from "./terminal-frame-buffer.js";
4
+ import { type UIMessage } from "ai";
5
+ export type TerminalInput = {
6
+ isTTY?: boolean;
7
+ on(event: "data", listener: (chunk: Buffer) => void): TerminalInput;
8
+ off(event: "data", listener: (chunk: Buffer) => void): TerminalInput;
9
+ resume(): TerminalInput;
10
+ pause(): TerminalInput;
11
+ setRawMode?: (mode: boolean) => TerminalInput;
12
+ };
13
+ export type TerminalOutput = {
14
+ columns?: number;
15
+ rows?: number;
16
+ write(chunk: string | Uint8Array, encodingOrCallback?: BufferEncoding | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean;
17
+ on(event: "resize", listener: () => void): TerminalOutput;
18
+ off(event: "resize", listener: () => void): TerminalOutput;
19
+ };
20
+ export type TerminalRendererOptions = {
21
+ input?: TerminalInput;
22
+ output?: TerminalOutput;
23
+ frameBuffer?: TerminalFrameBuffer;
24
+ tools?: TerminalPartDisplayMode;
25
+ reasoning?: TerminalPartDisplayMode;
26
+ subagents?: TerminalPartDisplayMode;
27
+ connectionAuth?: TerminalPartDisplayMode;
28
+ assistantResponseStats?: AssistantResponseStatsMode;
29
+ contextSize?: number;
30
+ /**
31
+ * Whether to capture foreign `process.stdout` / `process.stderr` writes
32
+ * (from the in-process dev server and agent code) and render them as
33
+ * inline log regions. Defaults to `true` only when the renderer is bound
34
+ * to the real `process.stdout`; tests can force it on with an injected
35
+ * screen.
36
+ */
37
+ captureForeignOutput?: boolean;
38
+ logs?: LogDisplayMode;
39
+ };
40
+ export type TerminalSessionOptions = {
41
+ title?: string;
42
+ initialPrompt?: string;
43
+ submittedPrompt?: string;
44
+ waitForExit?: boolean;
45
+ continueSession?: boolean;
46
+ tools?: TerminalPartDisplayMode;
47
+ reasoning?: TerminalPartDisplayMode;
48
+ subagents?: TerminalPartDisplayMode;
49
+ connectionAuth?: TerminalPartDisplayMode;
50
+ assistantResponseStats?: AssistantResponseStatsMode;
51
+ contextSize?: number;
52
+ };
53
+ export type TerminalKey = {
54
+ type: "character";
55
+ value: string;
56
+ } | {
57
+ type: "backspace";
58
+ } | {
59
+ type: "enter";
60
+ } | {
61
+ type: "up";
62
+ } | {
63
+ type: "down";
64
+ } | {
65
+ type: "left";
66
+ } | {
67
+ type: "right";
68
+ } | {
69
+ type: "tab";
70
+ } | {
71
+ type: "escape";
72
+ } | {
73
+ type: "ctrl-r";
74
+ } | {
75
+ type: "ctrl-c";
76
+ } | {
77
+ type: "ignore";
78
+ };
79
+ export declare class TerminalRenderer {
80
+ #private;
81
+ constructor(options?: TerminalRendererOptions);
82
+ readPrompt(options?: TerminalSessionOptions): Promise<string>;
83
+ renderStream(result: AgentTUIStreamResult, options?: TerminalSessionOptions): Promise<UIMessage | undefined>;
84
+ readToolApproval(request: AgentTUIToolApprovalRequest, options?: TerminalSessionOptions): Promise<AgentTUIToolApprovalResponse>;
85
+ readInputQuestion(question: AgentTUIInputQuestion, options?: TerminalSessionOptions): Promise<AgentTUIInputQuestionResponse | undefined>;
86
+ upsertSubagentStep(update: SubagentStepUpdate): void;
87
+ upsertSubagentTool(update: SubagentToolUpdate): void;
88
+ markChildToolCallId(callId: string): void;
89
+ isChildToolCallId(callId: string): boolean;
90
+ upsertConnectionAuth(update: ConnectionAuthUpdate): void;
91
+ /**
92
+ * Sets the count of connections currently awaiting an OAuth
93
+ * callback. While the count is > 0, the bottom status bar shows a
94
+ * "waiting for connection authorization" hint that overrides the
95
+ * default streaming / processing message. The runner calls this on
96
+ * `authorization.required` (incrementing) and
97
+ * `authorization.completed` (decrementing) transitions.
98
+ */
99
+ setConnectionAuthPendingCount(count: number): void;
100
+ /**
101
+ * Clears the rendered transcript and per-conversation display state,
102
+ * leaving the UI interactive on a fresh screen. Backs the `/new` command.
103
+ */
104
+ reset(): void;
105
+ /**
106
+ * Tears down interactive mode and restores the terminal. Backs the
107
+ * `/exit` command, matching the Ctrl+C teardown.
108
+ */
109
+ shutdown(): void;
110
+ }
111
+ export declare function parseKey(chunk: Buffer): TerminalKey;
@@ -0,0 +1,12 @@
1
+ import{sliceVisible,stripAnsi,visibleLength}from"./terminal-text.js";import{renderMarkdown}from"./markdown.js";import{renderScreenViewport}from"./layout.js";import{TerminalFrameBuffer}from"./terminal-frame-buffer.js";import{getToolName,isToolUIPart,readUIMessageStream}from"ai";const colors={reset:`\x1B[0m`,dim:`\x1B[2m`,user:`\x1B[96m`,assistant:`\x1B[92m`,reasoning:`\x1B[94m`,tool:`\x1B[95m`,error:`\x1B[91m`,question:`\x1B[93m`,subagent:`\x1B[38;5;208m`,connectionAuth:`\x1B[35m`,log:`\x1B[90m`},sectionStyles={user:{color:colors.user,border:`─`},assistant:{color:colors.assistant,border:`─`},reasoning:{color:colors.reasoning,border:`·`},tool:{color:colors.tool,border:`─`},error:{color:colors.error,border:`─`},question:{color:colors.question,border:`─`},"subagent-step":{color:colors.subagent,border:`─`},"subagent-tool":{color:colors.subagent,border:`─`},"connection-auth":{color:colors.connectionAuth,border:`─`},log:{color:colors.log,border:`·`}},processingStatus=`Processing input... ↑/↓ scroll · Ctrl+C quit`,processingToolResultsStatus=`Processing tool results... ↑/↓ scroll · Ctrl+C quit`,streamingStatus=`Streaming... ↑/↓ scroll · Ctrl+C quit`,executingToolsStatus=`Executing tools... ↑/↓ scroll · Ctrl+C quit`;var TerminalRenderer=class{#e;#t;#n;#r;#i;#a;#o;#s;#c;#l=[];#u=new Set;#d=new Map;#f=0;#p=``;#m=!1;#h=0;#g=`Agent TUI`;#_=streamingStatus;#v=!1;#y=!1;#b;#x;#S;#C;#w=!0;#T;#E;#D;#O;#k;#A=``;#j=``;#M;#N;#P;constructor(e){this.#e=e?.input??process.stdin,this.#t=e?.output??process.stdout,this.#n=e?.frameBuffer??new TerminalFrameBuffer(this.#t),this.#r=e?.tools??`full`,this.#i=e?.reasoning??`full`,this.#a=e?.subagents??`full`,this.#o=e?.connectionAuth??`full`,this.#s=e?.assistantResponseStats??`tokensPerSecond`,this.#c=e?.contextSize,this.#x=e?.contextSize,this.#M=e?.captureForeignOutput??this.#t===process.stdout,this.#N=e?.logs??`all`}async readPrompt(e){return this.#F(e),this.#m=!0,this.#p=e?.initialPrompt??``,this.#_=`Type a prompt and press Enter · ↑/↓ scroll · Ctrl+C quit`,this.#V(),this.#ne(),await new Promise((e,t)=>{this.#E=n=>{let r=parseKey(n);switch(r.type){case`character`:this.#p+=r.value,this.#U(),this.#ne();break;case`backspace`:this.#p=this.#p.slice(0,-1),this.#U(),this.#ne();break;case`enter`:{let t=this.#p;this.#m=!1,this.#H(),this.#_=processingStatus,this.#G(t),this.#P=t,this.#p=``,this.#ne(),this.#R(),e(t);break}case`up`:case`down`:this.#B(r.type);break;case`ctrl-r`:this.#re();break;case`ctrl-c`:this.#H(),this.#I(),t(interruptedError());break;case`ignore`:break}},this.#L()})}async renderStream(e,t){this.#F(t),this.#m=!1,this.#_=processingStatus,this.#W(t?.submittedPrompt),this.#y=!1,this.#b=void 0,this.#S=void 0,this.#C=void 0;let n={tools:t?.tools??this.#r,reasoning:t?.reasoning??this.#i,assistantResponseStats:t?.assistantResponseStats??this.#s};this.#ne();let r=new Promise(e=>{this.#O=e});this.#E=e=>this.#z(e),this.#L();let i,a=toReadableStream(this.#Y(e.uiMessageStream));try{let t=readUIMessageStream({message:e.message,stream:a,onError:e=>this.#X(`Error`,formatStreamError(e))});for await(let e of takeUntil(t,r)){if(this.#y)break;i=e,this.#K(e,n)}!this.#y&&i&&this.#S!=null&&this.#K(i,n)}finally{this.#O=void 0,this.#y&&e.abort?.(),this.#R(),this.#_=this.#y?`Interrupted`:t?.continueSession?`Done · Enter another prompt · ↑/↓ scroll · Ctrl+C quit`:`Done · ↑/↓ scroll · q/Ctrl+C quit`,this.#ne(),await this.#ue(t),(this.#y||!t?.continueSession)&&this.#I()}if(this.#y)throw interruptedError();return i}async readToolApproval(e,t){return this.#F(t),this.#m=!1,this.#_=`Approve ${formatToolApprovalTitle(e)}? y/n · ↑/↓ scroll · Ctrl+C quit`,this.#y=!1,this.#ne(),await new Promise((e,t)=>{this.#E=n=>{let r=parseKey(n);switch(r.type){case`character`:{let t=r.value.toLowerCase();t===`y`?(this.#_=`Approved · Processing input... ↑/↓ scroll · Ctrl+C quit`,this.#R(),this.#ne(),e({approved:!0})):t===`n`&&(this.#_=`Denied · Processing input... ↑/↓ scroll · Ctrl+C quit`,this.#R(),this.#ne(),e({approved:!1,reason:`Denied by user.`}));break}case`up`:case`down`:this.#B(r.type);break;case`ctrl-r`:this.#re();break;case`ctrl-c`:this.#y=!0,this.#I(),t(interruptedError());break;default:break}},this.#L()})}async readInputQuestion(e,t){this.#F(t),this.#m=!1,this.#y=!1;let n=e.options??[],r=n.length>0,i=(e.allowFreeform===!0||!r)&&r,a=n.length+ +!!i,o=questionSectionId(e.requestId),s=r?`select`:`text`,c=r?0:n.length,l=``,isOnFreeformRow=()=>i&&c===n.length,renderSection=()=>{let t=this.#oe();this.#q({id:o,kind:`question`,title:`Question`,rightTitle:`pending`,content:formatQuestionSectionContent(e,c)}),this.#te(t)};renderSection();let repaintStatus=()=>{if(s===`select`){let e=[`↑/↓ select`];e.push(isOnFreeformRow()?`enter type`:`enter confirm`),e.push(`Ctrl+C quit`),this.#_=e.join(` · `)}else{let e=this.#w?`_`:` `,t=[`enter send`];t.push(r?`esc back`:`esc clear`),t.push(`Ctrl+C quit`),this.#_=`> ${l}${e} ${t.join(` · `)}`}this.#ne()};s===`text`&&this.#V(),repaintStatus();let finalize=t=>{let n=this.#oe();this.#q({id:o,kind:`question`,title:`Question`,rightTitle:`answered`,content:formatAnsweredQuestionContent(e,t.label)}),this.#_=`Answered · Processing input... ↑/↓ scroll · Ctrl+C quit`,this.#H(),this.#R(),this.#te(n);let r={};return t.optionId!==void 0&&(r.optionId=t.optionId),t.text!==void 0&&(r.text=t.text),r};return await new Promise((t,i)=>{this.#E=o=>{let u=parseKey(o);if(u.type===`ctrl-c`){this.#y=!0,this.#H(),this.#I(),i(interruptedError());return}if(u.type===`ctrl-r`){this.#re();return}if(s===`select`){switch(u.type){case`up`:a>0&&(c=(c-1+a)%a,renderSection(),repaintStatus());break;case`down`:a>0&&(c=(c+1)%a,renderSection(),repaintStatus());break;case`enter`:{if(isOnFreeformRow()){s=`text`,l=``,this.#V(),repaintStatus();break}let e=n[c];e&&t(finalize({optionId:e.id,label:e.label}));break}default:break}return}switch(u.type){case`character`:l+=u.value,this.#U(),repaintStatus();break;case`backspace`:l=l.slice(0,-1),this.#U(),repaintStatus();break;case`enter`:{let n=resolveQuestionText(l,e);if(n===void 0)break;t(finalize(n));break}case`escape`:if(r){if(l.length>0){l=``,this.#U(),repaintStatus();break}s=`select`,l=``,this.#H(),repaintStatus();break}l=``,this.#U(),repaintStatus();break;default:break}},this.#L()})}#F(e){this.#g=e?.title??this.#g,this.#x=e?.contextSize??this.#c,!this.#v&&(this.#v=!0,this.#n.reset(),this.#n.writeControl(`\x1B[?1049h\x1B[?25l`),this.#Z(),this.#e.isTTY&&(this.#e.setRawMode?.(!0),this.#e.resume()),this.#D=()=>this.#re(),this.#t.on(`resize`,this.#D))}#I(){this.#R(),this.#H(),this.#v&&=(this.#e.isTTY&&(this.#e.setRawMode?.(!1),this.#e.pause()),this.#D&&=(this.#t.off(`resize`,this.#D),void 0),this.#Q(),this.#n.writeControl(`\x1B[?25h\x1B[?1049l`),this.#n.reset(),!1)}#L(){this.#E&&this.#e.on(`data`,this.#E)}#R(){this.#E&&=(this.#e.off(`data`,this.#E),void 0)}#z(e){let t=parseKey(e);switch(t.type){case`up`:case`down`:this.#B(t.type);break;case`ctrl-r`:this.#re();break;case`ctrl-c`:this.#y=!0,this.#O?.();break;default:break}}#B(e){let t=e===`up`?1:-1;this.#h=this.#ae(this.#h+t),this.#ne()}#V(){this.#H(),this.#U(),this.#T=setInterval(()=>{this.#w=!this.#w,this.#ne()},500),this.#T.unref?.()}#H(){this.#T&&=(clearInterval(this.#T),void 0),this.#w=!0}#U(){this.#w=!0}#W(e){if(e!=null){if(this.#P===e){this.#P=void 0;return}this.#G(e)}}#G(e){let t=this.#oe();this.#l.push({kind:`user`,title:`User`,content:e}),this.#te(t)}#K(e,t){let n=this.#oe(),r=new Set,i=extractAssistantResponseStatsFromMetadata(e.metadata);this.#b=i.totalTokens??this.#b,this.#S=i.outputTokens??this.#S,this.#C=i.tokensPerSecond??this.#C;for(let[n,i]of e.parts.entries()){let a=sectionId(e.id,n);switch(i.type){case`text`:{let e=i.text.trim();if(e.length===0)break;r.add(a),this.#q({id:a,kind:`assistant`,title:`Assistant`,rightTitle:formatAssistantResponseStats({totalTokens:this.#b,outputTokens:this.#S,tokensPerSecond:this.#C},t.assistantResponseStats),content:e});break}case`reasoning`:{let o=i.text.trim();if(t.reasoning===`hidden`||o.length===0)break;r.add(a),this.#q({id:a,kind:`reasoning`,title:`Reasoning`,content:o,collapsed:shouldCollapsePart(e,n,t.reasoning,t)});break}default:if(isToolUIPart(i)){if(t.tools===`hidden`||this.#u.has(i.toolCallId))break;r.add(a),this.#d.set(i.toolCallId,a),this.#q({id:a,...renderToolInvocation(i,{mode:t.tools,collapsed:shouldCollapsePart(e,n,t.tools,t)})})}break}}this.#J(e.id,r),this.#te(n)}#q(e){let t=e.id?this.#l.find(t=>t.id===e.id):void 0;if(t){let n=t.cache;t.kind=e.kind,t.title=e.title,t.rightTitle=e.rightTitle,t.content=e.content,t.collapsed=e.collapsed,t.cache=n&&sectionMatchesCache(e,n)?n:void 0;return}this.#l.push(e)}#J(e,t){let n=`${e}:`;this.#l=this.#l.filter(e=>e.id==null||!e.id.startsWith(n)||t.has(e.id))}async*#Y(e){let t=!1;for await(let n of iterateUIMessageStream(e)){let e=statusForStreamChunk(n,{hasPendingToolResults:t});if(n.type===`start-step`?t=!1:finishesToolExecution(n)&&(t=!0),e&&this.#_!==e?(this.#_=e,this.#ne()):startsVisibleAssistantStream(n)&&this.#_!==streamingStatus&&(this.#_=streamingStatus,this.#ne()),n.type===`finish`){let e=extractAssistantResponseStats(n);this.#b=e.totalTokens,this.#S=e.outputTokens,this.#C=e.tokensPerSecond}yield n}}upsertSubagentStep(e){if(this.#a===`hidden`)return;let t=this.#oe(),n=e.reasoning?.trim()??``,r=e.message?.trim()??``;if(n.length===0&&r.length===0){this.#te(t);return}let i=this.#a===`collapsed`||this.#a===`auto-collapsed`&&e.finalized,a=[];if(n.length>0){let e=n.split(`
2
+ `).map(e=>`${colors.dim} ${e}${colors.reset}`).join(`
3
+ `);a.push(e)}r.length>0&&a.push(r);let o={id:subagentStepSectionId(e.callId,e.sectionKey),kind:`subagent-step`,title:`Subagent · ${e.subagentName}`,content:a.join(`
4
+
5
+ `)};e.finalized||(o.rightTitle=`streaming`),i&&(o.collapsed=!0),this.#q(o),this.#te(t)}upsertSubagentTool(e){if(this.#a===`hidden`)return;let t=this.#oe(),n=this.#a===`collapsed`||this.#a===`auto-collapsed`&&e.status!==`executing`,r=[];e.input!==void 0&&r.push(`Input:\n${formatValue(e.input)}`),e.output===void 0?e.errorText!==void 0&&r.push(`Error:\n${e.errorText}`):r.push(`Output:\n${formatValue(e.output)}`);let i={id:subagentToolSectionId(e.callId,e.childCallId),kind:`subagent-tool`,title:`Subagent · ${e.subagentName} · Tool · ${e.toolName}`,rightTitle:e.status,content:r.join(`
6
+
7
+ `)};n&&(i.collapsed=!0),this.#q(i),this.#te(t)}markChildToolCallId(e){this.#u.add(e);let t=this.#d.get(e);if(t===void 0)return;let n=this.#oe();this.#l=this.#l.filter(e=>e.id!==t),this.#d.delete(e),this.#te(n)}isChildToolCallId(e){return this.#u.has(e)}upsertConnectionAuth(e){if(this.#o===`hidden`)return;let t=this.#oe(),n=e.state===`authorized`||e.state===`declined`||e.state===`failed`||e.state===`timed-out`,r=this.#o===`collapsed`||this.#o===`auto-collapsed`&&n,i={id:connectionAuthSectionId(e.name),kind:`connection-auth`,title:`Connection · ${e.name} · Auth`,rightTitle:e.state,content:formatConnectionAuthSectionContent(e)};r&&(i.collapsed=!0),this.#q(i),this.#te(t)}setConnectionAuthPendingCount(e){let t=Math.max(0,e);if(t===this.#f)return;let n=this.#f>0;this.#f=t,t>0?(this.#_=`Waiting for connection authorization — visit the URL above · Ctrl+C quit`,this.#ne()):n&&(this.#_=processingStatus,this.#ne())}reset(){this.#l=[],this.#u.clear(),this.#d.clear(),this.#P=void 0,this.#f=0,this.#h=0,this.#b=void 0,this.#S=void 0,this.#C=void 0,this.#v&&this.#re()}shutdown(){this.#I()}#X(e,t){let n=this.#oe();this.#l.push({kind:`error`,title:e,content:t}),this.#te(n)}#Z(){if(this.#k!==void 0||!this.#M)return;this.#A=``,this.#j=``;let capture=(e,t)=>{let n=e.write.bind(e);return e.write=((e,n,r)=>{let i=typeof n==`string`?n:void 0,a=typeof n==`function`?n:r;return this.#$(t,chunkToString(e,i)),a?.(),!0}),()=>{e.write=n}},e=capture(process.stdout,`stdout`),t=capture(process.stderr,`stderr`);this.#k=()=>{e(),t()}}#Q(){let e=this.#k;e!==void 0&&(this.#k=void 0,e(),this.#A.length>0&&(this.#ee(`stdout`)&&process.stdout.write(`${this.#A}\n`),this.#A=``),this.#j.length>0&&(this.#ee(`stderr`)&&process.stderr.write(`${this.#j}\n`),this.#j=``))}#$(e,n){let r=(e===`stdout`?this.#A:this.#j)+n,i=r.lastIndexOf(`
8
+ `),a=i===-1?r:r.slice(i+1);if(e===`stdout`?this.#A=a:this.#j=a,i===-1||!this.#ee(e))return;let o=stripAnsi(r.slice(0,i)).replace(/\s+$/u,``);if(o.trim().length===0)return;let s=this.#oe();this.#l.push({kind:`log`,title:`Log · ${e}`,content:o}),this.#te(s)}#ee(e){switch(this.#N){case`none`:return!1;case`stderr`:return e===`stderr`;case`all`:return!0}}#te(e){if(this.#h!==0){let t=this.#oe()-e;this.#h=this.#ae(this.#h+t)}this.#ne()}#ne(){let e=renderScreenViewport({width:this.#ce(),height:this.#le(),title:this.#g,rightTitle:formatTokenCount(this.#b,this.#x),visibleBodyLines:this.#ie(),input:this.#p,inputActive:this.#m,inputCursorVisible:this.#w,status:this.#_});this.#n.present(e)}#re(){this.#n.reset(),this.#ne()}#ie(){if(this.#l.length===0)return[`Waiting for input...`];let e=this.#se(),t=this.#oe(),n=Math.max(0,t-e-this.#h),r=n+e,i=[],a=0;for(let t of this.#l){let o=renderSectionLines(t,this.#ce()-4),s=a+o.length;if(s>n&&a<r&&i.push(...o.slice(Math.max(0,n-a),r-a)),i.length>=e)break;a=s}return i}#ae(e){let t=Math.max(0,this.#oe()-this.#se());return Math.min(Math.max(0,e),t)}#oe(){if(this.#l.length===0)return 1;let e=0;for(let t of this.#l)e+=renderSectionLines(t,this.#ce()-4).length;return e}#se(){return Math.max(1,this.#le()-5)}#ce(){return Math.max(20,this.#t.columns??80)}#le(){return Math.max(8,this.#t.rows??24)}async#ue(e){e?.waitForExit===!1||!this.#e.isTTY||this.#y||await new Promise(e=>{this.#E=t=>{let n=parseKey(t);switch(n.type){case`up`:case`down`:this.#B(n.type);break;case`ctrl-r`:this.#re();break;case`character`:n.value===`q`&&(this.#R(),e());break;case`ctrl-c`:this.#R(),process.exitCode=130,e();break;default:break}},this.#L()})}};function interruptedError(){return Error(`Interrupted`)}function chunkToString(e,t){return typeof e==`string`?e:Buffer.from(e).toString(t)}async function*takeUntil(e,t){let n=e[Symbol.asyncIterator](),r=t.then(()=>({done:!0,value:void 0}));for(;;){let e=await Promise.race([n.next(),r]);if(e.done)break;yield e.value}}function formatStreamError(e){return e instanceof Error?e.message:typeof e==`string`?e:JSON.stringify(e)}function renderToolInvocation(e,t){let n=getToolName(e),r=`Tool · ${e.title??n}`,i=`input`in e?e.input:void 0,a=i===void 0?`Input: (streaming...)`:`Input:\n${formatValue(i)}`,s=toolStatus(e);if(t.collapsed)return{kind:`tool`,title:r,rightTitle:s,content:``,collapsed:!0};switch(e.state){case`input-streaming`:return{kind:`tool`,title:r,rightTitle:s,content:a};case`input-available`:return{kind:`tool`,title:r,rightTitle:s,content:a};case`approval-requested`:return{kind:`tool`,title:r,rightTitle:s,content:a};case`approval-responded`:return{kind:`tool`,title:r,rightTitle:s,content:a};case`output-available`:return{kind:`tool`,title:r,rightTitle:s,content:`${a}\n\nOutput:\n${formatValue(e.output)}`};case`output-error`:return{kind:`error`,title:`Tool Error · ${e.title??n}`,rightTitle:s,content:`${a}\n\nError:\n${e.errorText}`};case`output-denied`:return{kind:`error`,title:`Tool Denied · ${e.title??n}`,rightTitle:s,content:`${a}\n\nReason: ${e.approval.reason??`denied`}`}}}function shouldCollapsePart(e,t,n,r){switch(n){case`collapsed`:return!0;case`auto-collapsed`:return e.parts.slice(t+1).some(e=>isVisibleAssistantPart(e,r));case`full`:case`hidden`:return!1}}function isVisibleAssistantPart(e,t){switch(e.type){case`text`:return e.text.trim().length>0;case`reasoning`:return t.reasoning!==`hidden`&&e.text.trim().length>0;default:return isToolUIPart(e)&&t.tools!==`hidden`}}function startsVisibleAssistantStream(e){switch(e.type){case`text-start`:case`text-delta`:case`reasoning-start`:case`reasoning-delta`:case`tool-input-start`:case`tool-input-delta`:return!0;default:return!1}}function statusForStreamChunk(e,{hasPendingToolResults:t}){switch(e.type){case`start-step`:return t?processingToolResultsStatus:processingStatus;case`tool-output-available`:case`tool-output-error`:case`tool-output-denied`:return processingToolResultsStatus;case`tool-input-available`:return executingToolsStatus;case`tool-approval-response`:return e.approved?executingToolsStatus:void 0;default:return}}function finishesToolExecution(e){switch(e.type){case`tool-output-available`:case`tool-output-error`:case`tool-output-denied`:return!0;default:return!1}}function toolStatus(e){switch(e.state){case`input-streaming`:return`waiting`;case`approval-requested`:return`approval requested`;case`input-available`:return`executing`;case`approval-responded`:return e.approval.approved?`executing`:`denied`;case`output-available`:case`output-error`:return`done`;case`output-denied`:return`denied`}}function formatValue(e){return typeof e==`string`?e:JSON.stringify(e,null,2)}function formatToolApprovalTitle(e){return`tool ${e.title??e.toolName}`}function sectionId(e,t){return`${e}:${t}`}function questionSectionId(e){return`question:${e}`}function subagentStepSectionId(e,t){return`subagent:${e}:step:${t}`}function subagentToolSectionId(e,t){return`subagent:${e}:tool:${t}`}function connectionAuthSectionId(e){return`connection-auth:${e}`}function formatConnectionAuthSectionContent(e){let t=[];e.description.length>0&&t.push(e.description);let n=e.challenge;return n?.url&&t.push(`URL: ${n.url}`),n?.userCode&&t.push(`Code: ${n.userCode}`),n?.expiresAt&&t.push(`Expires: ${n.expiresAt}`),n?.instructions&&t.push(n.instructions),e.reason!==void 0&&e.reason.length>0&&t.push(`Reason: ${e.reason}`),t.join(`
9
+ `)}function formatQuestionSectionContent(e,t){let n=[];n.push(e.prompt),n.push(``);let r=e.options??[];if(r.length>0){for(let[e,i]of r.entries()){let r=i.description?` — ${i.description}`:``,a=t===e,o=a?`▸ `:` `,s=`${i.label}${r}`,c=a?`${colors.question}${o}${s}${colors.reset}`:`${o}${s}`;n.push(c)}if(e.allowFreeform===!0){let e=t===r.length,i=e?`▸ `:` `,a=`Type your own answer`;n.push(e?`${colors.question}${i}${a}${colors.reset}`:`${colors.dim}${i}${a}${colors.reset}`)}}else n.push(`${colors.dim} (type your answer)${colors.reset}`);return n.join(`
10
+ `)}function formatAnsweredQuestionContent(e,t){return`${e.prompt}\n\n ✓ ${t}`}function resolveQuestionText(e,t){let n=e.trim();if(n.length===0)return;let r=n.toLowerCase(),i=t.options??[];if(i.length>0){let e=matchQuestionOption(r,i);if(e!==void 0)return{optionId:e.id,label:e.label}}if(t.allowFreeform===!0||i.length===0)return{text:n,label:n}}function matchQuestionOption(e,t){let n=t.find(t=>t.id.toLowerCase()===e);if(n!==void 0)return n;let r=t.find(t=>t.label.toLowerCase()===e);if(r!==void 0)return r;let i=Number(e);if(Number.isInteger(i)&&i>0&&i<=t.length)return t[i-1]}function toReadableStream(e){return e instanceof ReadableStream?e:new ReadableStream({async start(t){try{for await(let n of e)t.enqueue(n);t.close()}catch(e){t.error(e)}}})}async function*iterateUIMessageStream(e){if(e instanceof ReadableStream){let t=e.getReader();try{for(;;){let{done:e,value:n}=await t.read();if(e)return;yield n}}finally{t.releaseLock()}return}yield*e}function renderSectionLines(e,t){if(e.cache&&sectionMatchesCache(e,e.cache,t))return e.cache.lines;let n=createSectionLines(e,t);return e.cache={width:t,kind:e.kind,title:e.title,rightTitle:e.rightTitle,content:e.content,collapsed:e.collapsed===!0,lines:n},n}function sectionMatchesCache(e,t,n=t.width){return t.width===n&&t.kind===e.kind&&t.title===e.title&&t.rightTitle===e.rightTitle&&t.content===e.content&&t.collapsed===(e.collapsed===!0)}function createSectionLines(e,t){let n=sectionStyles[e.kind],i=Math.max(1,t-4),a=` ${e.title} `,o=e.rightTitle?` ${e.rightTitle} `:``;if(e.collapsed){let e=Math.max(0,t-2-a.length-o.length);return[`${n.color}╭${a}${n.border.repeat(e)}${o}╮${colors.reset}`,`${n.color}╰${n.border.repeat(Math.max(0,t-2))}╯${colors.reset}`]}let s=Math.max(0,t-2-a.length-o.length),c=`${n.color}╭${a}${n.border.repeat(s)}${o}╮${colors.reset}`,d=`${n.color}╰${n.border.repeat(Math.max(0,t-2))}╯${colors.reset}`;return[c,...(e.content.length===0?colors.dim+`(streaming...)`+colors.reset:e.kind===`log`?e.content:renderMarkdown(e.content)).split(`
11
+ `).flatMap(e=>wrapVisibleLine(e,i)).map(e=>sectionLine(e,i,n.color)),d]}function sectionLine(t,r,i){let a=sliceVisible(t,r),o=` `.repeat(Math.max(0,r-visibleLength(a)));return`${i}│${colors.reset} ${a}${o} ${i}│${colors.reset}`}function wrapVisibleLine(e,t){if(e.length===0)return[``];let r=[],i=e;for(;visibleLength(i)>t;){let e=findVisibleBreakPoint(i,t);r.push(i.slice(0,e).trimEnd()),i=i.slice(e).trimStart()}return r.push(i),r}function findVisibleBreakPoint(t,n){let r=sliceVisible(t,n+1).lastIndexOf(` `);return r>0?r:sliceVisible(t,n).length}function extractAssistantResponseStats(e){let t=`usage`in e?e.usage:void 0,n=`messageMetadata`in e?e.messageMetadata?.usage:void 0,r=`messageMetadata`in e?e.messageMetadata?.performance:void 0;return{totalTokens:extractTotalTokenCountFromUsage(t??n),outputTokens:extractOutputTokenCountFromUsage(t??n),tokensPerSecond:r?.outputTokensPerSecond}}function extractAssistantResponseStatsFromMetadata(e){let t=e;return{totalTokens:extractTotalTokenCountFromUsage(t?.usage),outputTokens:extractOutputTokenCountFromUsage(t?.usage),tokensPerSecond:t?.performance?.outputTokensPerSecond}}function extractTotalTokenCountFromUsage(e){let t=e?.totalTokens;if(typeof t==`number`)return t;if(typeof t?.total==`number`)return t.total;let n=extractInputTokenCountFromUsage(e),r=extractOutputTokenCountFromUsage(e);if(n!=null&&r!=null)return n+r}function extractInputTokenCountFromUsage(e){let t=e?.inputTokens;return typeof t==`number`?t:typeof t?.total==`number`?t.total:e?.promptTokens}function extractOutputTokenCountFromUsage(e){let t=e?.outputTokens;return typeof t==`number`?t:typeof t?.total==`number`?t.total:e?.completionTokens}function formatTokenCount(e,t){if(e==null)return;let n=`${e.toLocaleString()} ${e===1?`token`:`tokens`}`,r=formatContextPercentage(e,t);return r==null?n:`${n} ${r}`}function formatContextPercentage(e,t){if(!(t==null||t<=0||!Number.isFinite(t)))return`${Math.round(e/t*100).toLocaleString()}%`}function formatAssistantResponseStats(e,t){return t===`tokensPerSecond`?formatTokensPerSecond(e.tokensPerSecond):formatTokenCount(e.outputTokens)}function formatTokensPerSecond(e){if(e!=null)return`${formatNumber(e)} tok/s`}function formatNumber(e){return Number.isInteger(e)?e.toLocaleString():e.toLocaleString(void 0,{maximumFractionDigits:1})}function parseKey(e){let t=e.toString(`utf8`);switch(t){case``:return{type:`ctrl-r`};case``:return{type:`ctrl-c`};case`\r`:case`
12
+ `:return{type:`enter`};case``:case`\b`:return{type:`backspace`};case`\x1B[A`:return{type:`up`};case`\x1B[B`:return{type:`down`};case`\x1B[C`:return{type:`right`};case`\x1B[D`:return{type:`left`};case` `:return{type:`tab`};case`\x1B`:return{type:`escape`};default:return t>=` `&&t!==``?{type:`character`,value:t}:{type:`ignore`}}}export{TerminalRenderer,parseKey};
@@ -0,0 +1,6 @@
1
+ export declare const ansiPattern: RegExp;
2
+ export declare const ansiPrefixPattern: RegExp;
3
+ export declare function stripAnsi(input: string): string;
4
+ export declare function visibleLength(input: string): number;
5
+ export declare function sliceVisible(input: string, width: number): string;
6
+ export declare function codePointWidth(codePoint: number): number;
@@ -0,0 +1 @@
1
+ const ansiPattern=RegExp(`\x1B\\[[0-?]*[ -/]*[@-~]`,`g`),ansiPrefixPattern=RegExp(`^\x1B\\[[0-?]*[ -/]*[@-~]`);function stripAnsi(t){return t.replaceAll(ansiPattern,``)}function visibleLength(e){let n=0,r=0;for(;r<e.length;){let i=e.slice(r).match(ansiPrefixPattern);if(i){r+=i[0].length;continue}let a=e.codePointAt(r);if(a==null)break;let o=String.fromCodePoint(a);n+=codePointWidth(a),r+=o.length}return n}function sliceVisible(e,n){if(n<=0)return``;let r=``,i=0,a=0;for(;a<e.length&&i<n;){let o=e.slice(a).match(ansiPrefixPattern);if(o){r+=o[0],a+=o[0].length;continue}let s=e.codePointAt(a);if(s==null)break;let c=String.fromCodePoint(s),l=codePointWidth(s);if(l>0&&i+l>n)break;r+=c,a+=c.length,i+=l}for(;a<e.length;){let n=e.slice(a).match(ansiPrefixPattern);if(!n)break;r+=n[0],a+=n[0].length}return r}function codePointWidth(e){return e===9?4:e<32||e>=127&&e<160||isZeroWidthCodePoint(e)?0:isWideCodePoint(e)?2:1}function isZeroWidthCodePoint(e){return e>=768&&e<=879||e>=1155&&e<=1161||e>=1425&&e<=1469||e===1471||e>=1473&&e<=1474||e>=1476&&e<=1477||e===1479||e>=1552&&e<=1562||e>=1611&&e<=1631||e===1648||e>=1750&&e<=1756||e>=1759&&e<=1764||e>=1767&&e<=1768||e>=1770&&e<=1773||e===1809||e>=1840&&e<=1866||e>=1958&&e<=1968||e>=2027&&e<=2035||e>=2070&&e<=2073||e>=2075&&e<=2083||e>=2085&&e<=2087||e>=2089&&e<=2093||e>=2137&&e<=2139||e>=2259&&e<=2306||e===2362||e===2364||e>=2369&&e<=2376||e===2381||e>=2385&&e<=2391||e===8205||e>=65024&&e<=65039||e>=917760&&e<=917999}function isWideCodePoint(e){return e>=4352&&(e<=4447||e===9001||e===9002||e>=11904&&e<=42191&&e!==12351||e>=44032&&e<=55203||e>=63744&&e<=64255||e>=65040&&e<=65049||e>=65072&&e<=65135||e>=65280&&e<=65376||e>=65504&&e<=65510||e>=127744&&e<=128591||e>=129280&&e<=129535||e>=131072&&e<=262141)}export{ansiPattern,ansiPrefixPattern,codePointWidth,sliceVisible,stripAnsi,visibleLength};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Test-only entrypoint for driving the `ash dev` terminal UI from
3
+ * end-to-end smoke tests. Exposed via the `experimental-ash/dev-tui-test`
4
+ * subpath export. Not part of the supported public API — production code
5
+ * reaches the TUI through the internal `#cli/dev/tui/*` modules instead.
6
+ */
7
+ export { AshTUIRunner } from "../runner.js";
8
+ export type { AshTUIRunnerOptions } from "../runner.js";
9
+ export { TerminalRenderer } from "../terminal-renderer.js";
10
+ export { MockScreen, MockUserInput } from "./mock-terminal.js";
@@ -0,0 +1 @@
1
+ import{TerminalRenderer}from"../terminal-renderer.js";import{AshTUIRunner}from"../runner.js";import{MockScreen,MockUserInput}from"./mock-terminal.js";export{AshTUIRunner,MockScreen,MockUserInput,TerminalRenderer};
@@ -0,0 +1,28 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { TerminalInput, TerminalOutput } from "../terminal-renderer.js";
3
+ export declare class MockUserInput extends EventEmitter implements TerminalInput {
4
+ isTTY: boolean;
5
+ rawModes: boolean[];
6
+ resumeCalls: number;
7
+ pauseCalls: number;
8
+ setRawMode(mode: boolean): this;
9
+ resume(): this;
10
+ pause(): this;
11
+ type(text: string): void;
12
+ enter(): void;
13
+ ctrlC(): void;
14
+ }
15
+ export declare class MockScreen extends EventEmitter implements TerminalOutput {
16
+ #private;
17
+ columns: number;
18
+ rows: number;
19
+ constructor({ columns, rows }: {
20
+ columns: number;
21
+ rows: number;
22
+ });
23
+ write(chunk: string | Uint8Array, encodingOrCallback?: BufferEncoding | ((error?: Error | null) => void), callback?: (error?: Error | null) => void): boolean;
24
+ resize(columns: number, rows: number): void;
25
+ snapshot(): string;
26
+ rawOutput(): string;
27
+ waitForText(text: string, timeoutMs?: number, getDebugOutput?: () => string): Promise<void>;
28
+ }
@@ -0,0 +1,3 @@
1
+ import{EventEmitter}from"node:events";const ansiControlSequencePattern=RegExp(`^\x1B\\[([0-9?;]*)([ -/]*)([@-~])`);var MockUserInput=class extends EventEmitter{isTTY=!0;rawModes=[];resumeCalls=0;pauseCalls=0;setRawMode(e){return this.rawModes.push(e),this}resume(){return this.resumeCalls+=1,this}pause(){return this.pauseCalls+=1,this}type(e){this.emit(`data`,Buffer.from(e))}enter(){this.emit(`data`,Buffer.from(`\r`))}ctrlC(){this.emit(`data`,Buffer.from(``))}},MockScreen=class extends EventEmitter{columns;rows;#e=``;#t=[];#n=0;#r=0;#i=[];constructor({columns:e,rows:t}){super(),this.columns=e,this.rows=t}write(e,t,n){let r=String(e);return this.#e+=r,this.#o(r),typeof t==`function`&&t(),n?.(),this.#a(),!0}resize(e,t){this.columns=e,this.rows=t,this.emit(`resize`)}snapshot(){return this.#t.join(`
2
+ `)}rawOutput(){return this.#e}async waitForText(e,t=1e3,n=()=>this.snapshot()){this.snapshot().includes(e)||await new Promise((r,i)=>{let a={text:e,resolve:r,reject:i,timeout:setTimeout(()=>{this.#i=this.#i.filter(e=>e!==a),i(Error(`Timed out waiting for screen text: ${e}\n\nScreen:\n${n()}`))},t)};this.#i.push(a)})}#a(){let e=this.snapshot();for(let t of this.#i.slice())e.includes(t.text)&&(clearTimeout(t.timeout),this.#i=this.#i.filter(e=>e!==t),t.resolve())}#o(e){let t=0;for(;t<e.length;){if(e[t]===`\x1B`){let n=this.#s(e,t);if(n>t){t=n;continue}}let n=e[t];if(t+=1,n!==void 0){if(n===`
3
+ `){this.#n+=1,this.#r=0;continue}if(n===`\r`){this.#r=0;continue}this.#c(n)}}}#s(e,n){let r=e.slice(n).match(ansiControlSequencePattern);if(!r)return n;let[i,a,,o]=r,s=a?a.split(`;`):[];return o===`H`&&s.length===0?(this.#n=0,this.#r=0):o===`J`&&s[0]===`2`?this.#t=[]:o===`K`&&s[0]===`2`?(this.#t[this.#n]=``,this.#r=0):o===`H`&&(this.#n=Number(s[0]??1)-1,this.#r=Number(s[1]??1)-1),n+i.length}#c(e){let t=this.#t[this.#n]??``,n=t.slice(0,this.#r)+e+t.slice(this.#r+e.length);this.#t[this.#n]=n,this.#r+=e.length}};export{MockScreen,MockUserInput};
@@ -0,0 +1,32 @@
1
+ import type { ClientOptions } from "#client/index.js";
2
+ import type { TuiDisplayOptions } from "./types.js";
3
+ /**
4
+ * Options for running the `ash dev` terminal UI against a server URL.
5
+ */
6
+ export interface RunDevelopmentTuiInput extends TuiDisplayOptions {
7
+ /**
8
+ * The Ash server URL the TUI connects to — either the in-process dev
9
+ * server started by `ash dev`, or a remote `--url` target.
10
+ */
11
+ readonly serverUrl: string;
12
+ }
13
+ /**
14
+ * Runs the `ash dev` terminal UI against the given server URL until the
15
+ * user exits.
16
+ *
17
+ * Builds the client with the same development auth/header resolution the
18
+ * REPL's dev client uses (`#services/dev-client`): local hosts skip the
19
+ * Vercel OIDC bearer, remote hosts attach it alongside any protection
20
+ * bypass headers. The configured client is handed to the runner so its
21
+ * subagent child-session streams inherit the same auth. Turn-dispatch
22
+ * failures — including the Vercel Deployment Protection challenge — are
23
+ * formatted into the inline error region rather than crashing the command.
24
+ */
25
+ /**
26
+ * Builds the client options the `ash dev` TUI connects with, matching the
27
+ * REPL's dev client: local hosts skip the Vercel OIDC bearer (the framework's
28
+ * `localDev()` channel auth accepts unauthenticated calls); remote hosts
29
+ * attach it alongside any protection-bypass headers resolved per request.
30
+ */
31
+ export declare function resolveDevTuiClientOptions(serverUrl: string): ClientOptions;
32
+ export declare function runDevelopmentTui(input: RunDevelopmentTuiInput): Promise<void>;
@@ -0,0 +1 @@
1
+ import{AshTUIRunner}from"./runner.js";import{toErrorMessage}from"#shared/errors.js";import{isLocalDevelopmentServerUrl,resolveDevelopmentClientHeaders,resolveDevelopmentOidcToken}from"#services/dev-client/request-headers.js";import{formatVercelAuthChallengeMessage,isVercelAuthChallenge}from"#services/dev-client/vercel-auth-error.js";import{Client}from"#client/index.js";function resolveDevTuiClientOptions(e){let t={headers:()=>resolveDevelopmentClientHeaders({serverUrl:e}),host:e};return isLocalDevelopmentServerUrl(e)?t:{...t,auth:{bearer:resolveDevelopmentOidcToken}}}async function runDevelopmentTui(n){let{serverUrl:r,...i}=n,a=new Client(resolveDevTuiClientOptions(r));await new AshTUIRunner({...i,session:a.session(),client:a,formatTransportError:e=>isVercelAuthChallenge(e)?formatVercelAuthChallengeMessage({serverUrl:r}):toErrorMessage(e)}).run()}export{resolveDevTuiClientOptions,runDevelopmentTui};
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Controls how terminal UI sections for stream parts are displayed.
3
+ */
4
+ export type TerminalPartDisplayMode = "full" | "collapsed" | "auto-collapsed" | "hidden";
5
+ /**
6
+ * Controls which usage statistic is shown for assistant responses.
7
+ */
8
+ export type AssistantResponseStatsMode = "tokens" | "tokensPerSecond";
9
+ /**
10
+ * Controls which in-process server/agent logs the TUI surfaces as inline
11
+ * regions. `all` shows both stdout and stderr; `stderr` shows only stderr;
12
+ * `none` suppresses log regions entirely.
13
+ */
14
+ export type LogDisplayMode = "all" | "stderr" | "none";
15
+ /**
16
+ * Display options shared by the terminal UI runner and renderer.
17
+ */
18
+ export type TuiDisplayOptions = {
19
+ /**
20
+ * The title shown in the terminal UI.
21
+ */
22
+ name?: string;
23
+ /**
24
+ * How tool calls should render.
25
+ */
26
+ tools?: TerminalPartDisplayMode;
27
+ /**
28
+ * How reasoning parts should render.
29
+ */
30
+ reasoning?: TerminalPartDisplayMode;
31
+ /**
32
+ * How subagent sections should render. `full` shows every nested child
33
+ * event line and the subagent's output; `auto-collapsed` collapses the
34
+ * section once the subagent reaches `done`; `collapsed` always shows
35
+ * only the header; `hidden` skips the section entirely.
36
+ */
37
+ subagents?: TerminalPartDisplayMode;
38
+ /**
39
+ * How MCP connection authorization sections should render. `full`
40
+ * shows the challenge URL and any user code; `auto-collapsed`
41
+ * collapses the section once authorization reaches a terminal
42
+ * outcome (`authorized`, `declined`, `failed`, `timed-out`);
43
+ * `collapsed` always shows only the header. `hidden` is supported
44
+ * but strongly discouraged: a hidden auth challenge looks identical
45
+ * to a hung turn from the user's perspective.
46
+ */
47
+ connectionAuth?: TerminalPartDisplayMode;
48
+ /**
49
+ * Which statistic to show in assistant response headers.
50
+ *
51
+ * @default "tokensPerSecond"
52
+ */
53
+ assistantResponseStats?: AssistantResponseStatsMode;
54
+ /**
55
+ * The model context window size in tokens.
56
+ *
57
+ * When provided, the terminal UI shows the current total token usage as a
58
+ * percentage of this context window.
59
+ */
60
+ contextSize?: number;
61
+ /**
62
+ * Which in-process server/agent logs to surface as inline regions.
63
+ * Output is always captured so it cannot corrupt the frame; this only
64
+ * controls what is rendered. Defaults to `all` — the TUI is primarily a
65
+ * development tool.
66
+ */
67
+ logs?: LogDisplayMode;
68
+ };
@@ -0,0 +1 @@
1
+ export{};
@@ -1,7 +1,23 @@
1
+ import type { AssistantResponseStatsMode, LogDisplayMode, TerminalPartDisplayMode, TuiDisplayOptions } from "#cli/dev/tui/types.js";
1
2
  interface CliLogger {
2
3
  error(message: string): void;
3
4
  log(message: string): void;
4
5
  }
6
+ interface DevelopmentCliOptions {
7
+ assistantResponseStats?: AssistantResponseStatsMode;
8
+ connectionAuth?: TerminalPartDisplayMode;
9
+ contextSize?: number;
10
+ host?: string;
11
+ logs?: LogDisplayMode;
12
+ name?: string;
13
+ port?: number;
14
+ reasoning?: TerminalPartDisplayMode;
15
+ repl?: boolean;
16
+ subagents?: TerminalPartDisplayMode;
17
+ tools?: TerminalPartDisplayMode;
18
+ ui?: boolean;
19
+ url?: string;
20
+ }
5
21
  interface DevelopmentServerHandle {
6
22
  readonly url: string;
7
23
  close(): Promise<void>;
@@ -19,6 +35,9 @@ interface CliRuntimeDependencies {
19
35
  runDevelopmentRepl(input: {
20
36
  serverUrl: string;
21
37
  }): Promise<void>;
38
+ runDevelopmentTui(input: {
39
+ serverUrl: string;
40
+ } & TuiDisplayOptions): Promise<void>;
22
41
  runEvalCommand(options: EvalCliOptions, logger: CliLogger): Promise<void>;
23
42
  startHost(appRoot: string, options?: {
24
43
  host?: string;
@@ -39,6 +58,53 @@ interface EvalCliOptions {
39
58
  timeout?: string;
40
59
  url?: string;
41
60
  }
61
+ /**
62
+ * The interactive UI `ash dev` runs against a server.
63
+ *
64
+ * - `tui` — the default terminal UI.
65
+ * - `repl` — the classic line-based REPL (`--repl`).
66
+ * - `headless` — no UI: just keep the server running (`--no-ui`, or a
67
+ * non-interactive terminal).
68
+ *
69
+ * Exported for unit coverage of the flag-routing contract.
70
+ */
71
+ export type DevUiMode = "tui" | "repl" | "headless";
72
+ /**
73
+ * Resolves which UI `ash dev` should run from the parsed flags and whether
74
+ * the terminal is interactive. `--no-ui` and non-TTY terminals force
75
+ * `headless`; `--repl` selects the classic REPL; otherwise the terminal UI
76
+ * is the default.
77
+ */
78
+ export declare function resolveDevUiMode(input: {
79
+ options: Pick<DevelopmentCliOptions, "repl" | "ui">;
80
+ interactive: boolean;
81
+ }): DevUiMode;
82
+ /**
83
+ * Resolves the terminal UI's header title: an explicit `--name`, else the
84
+ * remote server's host (for `--url`), else the humanized app-folder name
85
+ * (e.g. `apps/weather-agent` → "Weather Agent"). Returns `undefined` when
86
+ * nothing meaningful can be derived, so the runner falls back to its own
87
+ * default.
88
+ */
89
+ export declare function resolveTuiTitle(input: {
90
+ name: string | undefined;
91
+ remoteServerUrl: string | undefined;
92
+ appRoot: string;
93
+ }): string | undefined;
94
+ /**
95
+ * Builds the terminal-UI display options for `ash dev`. Tools default to
96
+ * `auto-collapsed` and reasoning to `full` — the combination the example
97
+ * apps' former `tui` scripts shipped — while the other dimensions fall
98
+ * back to the runner's own defaults when unset.
99
+ */
100
+ export declare function resolveTuiDisplayOptions(options: DevelopmentCliOptions): TuiDisplayOptions;
101
+ /**
102
+ * Rewrites the deprecated `ash dev --no-repl` flag to its replacement
103
+ * `--no-ui`. The TUI is now the default UI, so "no REPL" no longer
104
+ * describes "no interactive UI"; `--no-repl` is kept as a silent alias so
105
+ * existing scripts and programmatic spawns (e.g. `withAsh`) keep working.
106
+ */
107
+ export declare function rewriteDeprecatedReplFlag(argv: readonly string[]): string[];
42
108
  /**
43
109
  * Runs the Ash CLI entrypoint.
44
110
  */
@@ -1,3 +1,3 @@
1
- import{createCliTheme,renderCliTaggedLine}from"#cli/ui/output.js";import{Command,CommanderError,InvalidArgumentError}from"#compiled/commander/index.js";import{resolveApplicationRoot}from"#internal/application/paths.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{parseDevelopmentServerUrl}from"#cli/dev/url.js";async function loadBuildHost(){return(await import(`#internal/nitro/host.js`)).buildApplication}async function loadPrintApplicationInfo(){return(await import(`#cli/commands/info.js`)).printApplicationInfo}async function loadRunDevelopmentRepl(){return(await import(`#cli/dev/repl.js`)).runDevelopmentRepl}async function loadRunEvalCommand(){return(await import(`#evals/cli/eval.js`)).runEvalCommand}async function loadStartHost(){return(await import(`#internal/nitro/host.js`)).startDevelopmentServer}async function loadStartProductionHost(){return(await import(`#internal/nitro/host.js`)).startProductionServer}function shouldPrintCliBootBanner(e){return e.name()===`info`||e.name()===`dev`}async function waitForShutdownSignal(e){await new Promise((t,n)=>{let r=!1,cleanup=()=>{process.off(`SIGINT`,handleSignal),process.off(`SIGTERM`,handleSignal)},handleSignal=()=>{r||(r=!0,cleanup(),e.close().then(t,n))};process.once(`SIGINT`,handleSignal),process.once(`SIGTERM`,handleSignal)})}async function waitForProductionServer(e){await Promise.race([e.wait(),waitForShutdownSignal({close:()=>e.close()})])}function parsePortOption(e){if(!/^-?\d+$/.test(e))throw new InvalidArgumentError(`Expected a numeric port, received "${e}".`);let t=Number(e);if(t<0||t>65535)throw new InvalidArgumentError(`Expected a port between 0 and 65535, received "${e}".`);return t}function hasInteractiveTerminal(){return!!(process.stdin.isTTY&&process.stdout.isTTY)}function rewriteDevelopmentUrlShorthand(e){let t=e[1];return e[0]!==`dev`||e.length!==2||t===void 0||t.startsWith(`-`)?[...e]:[`dev`,`--url`,t]}function resolveRemoteDevelopmentServerUrl(e){if(e.url){if(e.host!==void 0)throw new InvalidArgumentError(`The --host option cannot be used with --url.`);if(e.port!==void 0)throw new InvalidArgumentError(`The --port option cannot be used with --url.`);if(e.repl===!1)throw new InvalidArgumentError(`The --no-repl option cannot be used with --url.`);return e.url}}function createCliProgram(r,i){let c=resolveApplicationRoot(),l=resolveInstalledPackageInfo().version,u=new Command,d=createCliTheme();u.name(`ash`).description(`Build and run an Ash application.`).version(l).showHelpAfterError().exitOverride().hook(`preAction`,(e,t)=>{shouldPrintCliBootBanner(t)&&r.log(`Ash (v${l})`)}).configureOutput({writeErr:e=>{r.error(e.trimEnd())},writeOut:e=>{r.log(e.trimEnd())}});let f=u.command(`channels`).description(`Manage user-authored channels in the current project.`);return f.command(`add [kind]`).description(`Add channels interactively, or scaffold a channel kind (slack | web).`).option(`-f, --force`,`Overwrite existing channel files`).option(`-y, --yes`,`Assume yes for confirmations; requires an explicit channel kind`).action(async(e,t)=>{let{runChannelsAddCommand:n}=await import(`#cli/commands/channels.js`);await n(r,c,{kind:e,options:t})}),f.command(`list`).description(`List user-authored channels in the current project.`).option(`--json`,`Output as JSON`).action(async e=>{let{runChannelsListCommand:t}=await import(`#cli/commands/channels.js`);await t(r,c,e)}),u.command(`build`).description(`Build the current Ash application.`).action(async()=>{let{loadDevelopmentEnvironmentFiles:e}=await import(`#cli/dev/environment.js`);e(c);let n=await(i.buildHost??await loadBuildHost())(c);r.log(renderCliTaggedLine(d,{message:`built output at ${n}`,tag:`build`,tone:`success`}))}),u.command(`start`).description(`Start a built Ash application.`).option(`--host <host>`,`Host interface to bind`).option(`--port <port>`,`Port to listen on (defaults to $PORT, then 3000)`,parsePortOption).action(async e=>{let{loadDevelopmentEnvironmentFiles:n}=await import(`#cli/dev/environment.js`);n(c);let a=await(i.startProductionHost??await loadStartProductionHost())(c,{host:e.host,port:e.port});r.log(renderCliTaggedLine(d,{message:`server listening at ${a.url}`,tag:`start`,tone:`success`})),await waitForProductionServer(a)}),u.command(`dev`).description(`Start the Ash development server or connect the REPL to an existing URL.`).option(`--host <host>`,`Host interface to bind`).option(`--no-repl`,`Start the server without the interactive REPL`).option(`--port <port>`,`Port to listen on (defaults to $PORT, then 3000)`,parsePortOption).option(`-u, --url <url>`,`Connect the REPL to an existing server URL`,parseDevelopmentServerUrl).addHelpText(`after`,`
1
+ import{basename}from"node:path";import{createCliTheme,renderCliTaggedLine}from"#cli/ui/output.js";import{Command,CommanderError,InvalidArgumentError}from"#compiled/commander/index.js";import{resolveApplicationRoot}from"#internal/application/paths.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{parseDevelopmentServerUrl}from"#cli/dev/url.js";async function loadBuildHost(){return(await import(`#internal/nitro/host.js`)).buildApplication}async function loadPrintApplicationInfo(){return(await import(`#cli/commands/info.js`)).printApplicationInfo}async function loadRunDevelopmentRepl(){return(await import(`#cli/dev/repl/repl.js`)).runDevelopmentRepl}async function loadRunDevelopmentTui(){return(await import(`#cli/dev/tui/tui.js`)).runDevelopmentTui}async function loadRunEvalCommand(){return(await import(`#evals/cli/eval.js`)).runEvalCommand}async function loadStartHost(){return(await import(`#internal/nitro/host.js`)).startDevelopmentServer}async function loadStartProductionHost(){return(await import(`#internal/nitro/host.js`)).startProductionServer}function shouldPrintCliBootBanner(e){return e.name()===`info`||e.name()===`dev`}async function waitForShutdownSignal(e){await new Promise((t,n)=>{let r=!1,cleanup=()=>{process.off(`SIGINT`,handleSignal),process.off(`SIGTERM`,handleSignal)},handleSignal=()=>{r||(r=!0,cleanup(),e.close().then(t,n))};process.once(`SIGINT`,handleSignal),process.once(`SIGTERM`,handleSignal)})}async function waitForProductionServer(e){await Promise.race([e.wait(),waitForShutdownSignal({close:()=>e.close()})])}function parsePortOption(e){if(!/^-?\d+$/.test(e))throw new InvalidArgumentError(`Expected a numeric port, received "${e}".`);let t=Number(e);if(t<0||t>65535)throw new InvalidArgumentError(`Expected a port between 0 and 65535, received "${e}".`);return t}const DISPLAY_MODES=new Set([`full`,`collapsed`,`auto-collapsed`,`hidden`]),STATS_MODES=new Set([`tokens`,`tokensPerSecond`]),LOG_MODES=new Set([`all`,`stderr`,`none`]);function parseDisplayMode(e){if(!DISPLAY_MODES.has(e))throw new InvalidArgumentError(`Expected one of ${[...DISPLAY_MODES].join(`, `)}, received "${e}".`);return e}function parseStatsMode(e){if(!STATS_MODES.has(e))throw new InvalidArgumentError(`Expected one of ${[...STATS_MODES].join(`, `)}, received "${e}".`);return e}function parseLogsMode(e){if(!LOG_MODES.has(e))throw new InvalidArgumentError(`Expected one of ${[...LOG_MODES].join(`, `)}, received "${e}".`);return e}function parseContextSizeOption(e){let t=Number(e);if(!Number.isFinite(t)||t<=0)throw new InvalidArgumentError(`Expected a positive number, received "${e}".`);return t}function resolveDevUiMode(e){return e.options.ui===!1||!e.interactive?`headless`:e.options.repl===!0?`repl`:`tui`}function resolveTuiTitle(t){if(t.name!==void 0&&t.name.length>0)return t.name;if(t.remoteServerUrl!==void 0)try{return new URL(t.remoteServerUrl).host}catch{return}let n=humanizeProjectName(basename(t.appRoot));return n.length>0?n:void 0}function humanizeProjectName(e){return e.replace(/[-_.]+/gu,` `).trim().split(/\s+/u).filter(e=>e.length>0).map(e=>e[0].toUpperCase()+e.slice(1)).join(` `)}function resolveTuiDisplayOptions(e){let t={reasoning:e.reasoning??`full`,tools:e.tools??`auto-collapsed`};return e.subagents!==void 0&&(t.subagents=e.subagents),e.connectionAuth!==void 0&&(t.connectionAuth=e.connectionAuth),e.assistantResponseStats!==void 0&&(t.assistantResponseStats=e.assistantResponseStats),e.contextSize!==void 0&&(t.contextSize=e.contextSize),e.logs!==void 0&&(t.logs=e.logs),t}function hasInteractiveTerminal(){return!!(process.stdin.isTTY&&process.stdout.isTTY)}function rewriteDeprecatedReplFlag(e){return e[0]===`dev`?e.map(e=>e===`--no-repl`?`--no-ui`:e):[...e]}function rewriteDevelopmentUrlShorthand(e){let t=e[1];return e[0]!==`dev`||e.length!==2||t===void 0||t.startsWith(`-`)?[...e]:[`dev`,`--url`,t]}function resolveRemoteDevelopmentServerUrl(e){if(e.url){if(e.host!==void 0)throw new InvalidArgumentError(`The --host option cannot be used with --url.`);if(e.port!==void 0)throw new InvalidArgumentError(`The --port option cannot be used with --url.`);if(e.ui===!1)throw new InvalidArgumentError(`The --no-ui option cannot be used with --url.`);return e.url}}function createCliProgram(e,i){let a=resolveApplicationRoot(),l=resolveInstalledPackageInfo().version,u=new Command,d=createCliTheme();u.name(`ash`).description(`Build and run an Ash application.`).version(l).showHelpAfterError().exitOverride().hook(`preAction`,(t,n)=>{shouldPrintCliBootBanner(n)&&e.log(`Ash (v${l})`)}).configureOutput({writeErr:t=>{e.error(t.trimEnd())},writeOut:t=>{e.log(t.trimEnd())}});let f=u.command(`channels`).description(`Manage user-authored channels in the current project.`);return f.command(`add [kind]`).description(`Add channels interactively, or scaffold a channel kind (slack | web).`).option(`-f, --force`,`Overwrite existing channel files`).option(`-y, --yes`,`Assume yes for confirmations; requires an explicit channel kind`).action(async(t,n)=>{let{runChannelsAddCommand:r}=await import(`#cli/commands/channels.js`);await r(e,a,{kind:t,options:n})}),f.command(`list`).description(`List user-authored channels in the current project.`).option(`--json`,`Output as JSON`).action(async t=>{let{runChannelsListCommand:n}=await import(`#cli/commands/channels.js`);await n(e,a,t)}),u.command(`build`).description(`Build the current Ash application.`).action(async()=>{let{loadDevelopmentEnvironmentFiles:t}=await import(`#cli/dev/environment.js`);t(a);let r=await(i.buildHost??await loadBuildHost())(a);e.log(renderCliTaggedLine(d,{message:`built output at ${r}`,tag:`build`,tone:`success`}))}),u.command(`start`).description(`Start a built Ash application.`).option(`--host <host>`,`Host interface to bind`).option(`--port <port>`,`Port to listen on (defaults to $PORT, then 3000)`,parsePortOption).action(async t=>{let{loadDevelopmentEnvironmentFiles:r}=await import(`#cli/dev/environment.js`);r(a);let o=await(i.startProductionHost??await loadStartProductionHost())(a,{host:t.host,port:t.port});e.log(renderCliTaggedLine(d,{message:`server listening at ${o.url}`,tag:`start`,tone:`success`})),await waitForProductionServer(o)}),u.command(`dev`).description(`Start the Ash development server or connect to an existing URL.`).option(`--host <host>`,`Host interface to bind`).option(`--port <port>`,`Port to listen on (defaults to $PORT, then 3000)`,parsePortOption).option(`-u, --url <url>`,`Connect to an existing server URL`,parseDevelopmentServerUrl).option(`--repl`,`Use the classic line-based REPL instead of the default terminal UI`).option(`--no-ui`,`Start the server without an interactive UI`).option(`--name <name>`,`Title shown in the terminal UI (defaults to the app folder name)`).option(`--tools <mode>`,`How tool calls render: full | collapsed | auto-collapsed | hidden`,parseDisplayMode).option(`--reasoning <mode>`,`How reasoning renders: full | collapsed | auto-collapsed | hidden`,parseDisplayMode).option(`--subagents <mode>`,`How subagent sections render: full | collapsed | auto-collapsed | hidden`,parseDisplayMode).option(`--connection-auth <mode>`,`How connection authorization renders: full | collapsed | auto-collapsed | hidden`,parseDisplayMode).option(`--assistant-response-stats <mode>`,`Assistant header statistic: tokens | tokensPerSecond`,parseStatsMode).option(`--context-size <tokens>`,`Model context window size, shown as a usage percentage`,parseContextSizeOption).option(`--logs <mode>`,`Which server/agent logs to show: all | stderr | none`,parseLogsMode).addHelpText(`after`,`
2
2
  You can also pass a bare URL as the only argument, for example: ash dev https://example.com
3
- `).action(async e=>{let n=resolveRemoteDevelopmentServerUrl(e),{loadDevelopmentEnvironmentFiles:a}=await import(`#cli/dev/environment.js`);if(a(c),n){if(r.log(renderCliTaggedLine(d,{message:`REPL connecting to ${n}`,tag:`dev`,tone:`info`})),!hasInteractiveTerminal()){r.log(renderCliTaggedLine(d,{message:`Interactive REPL disabled because the current terminal is not a TTY.`,tag:`dev`,tone:`warning`}));return}r.log(``),await(i.runDevelopmentRepl??await loadRunDevelopmentRepl())({serverUrl:n});return}let o=await(i.startHost??await loadStartHost())(c,{host:e.host,port:e.port}),s=!1,closeServer=async()=>{s||(s=!0,await o.close())};try{if(r.log(renderCliTaggedLine(d,{message:`server listening at ${o.url}`,tag:`dev`,tone:`success`})),e.repl===!1)return await waitForShutdownSignal({close:closeServer});if(!hasInteractiveTerminal())return r.log(renderCliTaggedLine(d,{message:`Interactive REPL disabled because the current terminal is not a TTY.`,tag:`dev`,tone:`warning`})),await waitForShutdownSignal({close:closeServer});r.log(``),await(i.runDevelopmentRepl??await loadRunDevelopmentRepl())({serverUrl:o.url})}finally{await closeServer()}}),u.command(`info`).description(`Print resolved application information.`).option(`--json`,`Output as JSON`).action(async e=>{await(i.printApplicationInfo??await loadPrintApplicationInfo())(r,c,e)}),u.command(`eval`).description(`Run eval suites against an Ash agent.`).option(`--suite <id...>`,`Suite IDs to run (repeatable)`).option(`--all`,`Run all discovered suites`).option(`--url <url>`,`Remote agent URL (skip local host startup)`).option(`--timeout <ms>`,`Per-case timeout in milliseconds`).option(`--max-concurrency <n>`,`Max concurrent case executions per suite`).option(`--json`,`Output results as JSON`).option(`--skip-report`,`Skip suite-defined reporters (e.g. Braintrust)`).action(async e=>{await(i.runEvalCommand??await loadRunEvalCommand())(e,r)}),u}async function runCli(e=process.argv.slice(2),t=console,n={}){let i=createCliProgram(t,n),a=e.length===0?[`info`]:rewriteDevelopmentUrlShorthand(e);try{await i.parseAsync(a,{from:`user`})}catch(e){if(e instanceof CommanderError){if(e.exitCode===0)return;throw Error(e.message)}throw e}}export{runCli};
3
+ `).action(async t=>{let r=resolveRemoteDevelopmentServerUrl(t),{loadDevelopmentEnvironmentFiles:o}=await import(`#cli/dev/environment.js`);o(a);let runInteractiveUi=async(n,o)=>{if(e.log(``),o===`repl`){await(i.runDevelopmentRepl??await loadRunDevelopmentRepl())({serverUrl:n});return}let s=i.runDevelopmentTui??await loadRunDevelopmentTui(),c=resolveTuiDisplayOptions(t),l=resolveTuiTitle({name:t.name,remoteServerUrl:r,appRoot:a});l!==void 0&&(c.name=l),await s({serverUrl:n,...c})};if(r){e.log(renderCliTaggedLine(d,{message:`connecting to ${r}`,tag:`dev`,tone:`info`}));let i=resolveDevUiMode({options:t,interactive:hasInteractiveTerminal()});if(i===`headless`){e.log(renderCliTaggedLine(d,{message:`Interactive UI disabled because the current terminal is not a TTY.`,tag:`dev`,tone:`warning`}));return}await runInteractiveUi(r,i);return}let s=await(i.startHost??await loadStartHost())(a,{host:t.host,port:t.port}),c=!1,closeServer=async()=>{c||(c=!0,await s.close())};try{e.log(renderCliTaggedLine(d,{message:`server listening at ${s.url}`,tag:`dev`,tone:`success`}));let r=hasInteractiveTerminal(),i=resolveDevUiMode({options:t,interactive:r});if(i===`headless`)return t.ui!==!1&&!r&&e.log(renderCliTaggedLine(d,{message:`Interactive UI disabled because the current terminal is not a TTY.`,tag:`dev`,tone:`warning`})),await waitForShutdownSignal({close:closeServer});await runInteractiveUi(s.url,i)}finally{await closeServer()}}),u.command(`info`).description(`Print resolved application information.`).option(`--json`,`Output as JSON`).action(async t=>{await(i.printApplicationInfo??await loadPrintApplicationInfo())(e,a,t)}),u.command(`eval`).description(`Run eval suites against an Ash agent.`).option(`--suite <id...>`,`Suite IDs to run (repeatable)`).option(`--all`,`Run all discovered suites`).option(`--url <url>`,`Remote agent URL (skip local host startup)`).option(`--timeout <ms>`,`Per-case timeout in milliseconds`).option(`--max-concurrency <n>`,`Max concurrent case executions per suite`).option(`--json`,`Output results as JSON`).option(`--skip-report`,`Skip suite-defined reporters (e.g. Braintrust)`).action(async t=>{await(i.runEvalCommand??await loadRunEvalCommand())(t,e)}),u}async function runCli(e=process.argv.slice(2),t=console,n={}){let r=createCliProgram(t,n),a=e.length===0?[`info`]:rewriteDevelopmentUrlShorthand(rewriteDeprecatedReplFlag(e));try{await r.parseAsync(a,{from:`user`})}catch(e){if(e instanceof CommanderError){if(e.exitCode===0)return;throw Error(e.message)}throw e}}export{resolveDevUiMode,resolveTuiDisplayOptions,resolveTuiTitle,rewriteDeprecatedReplFlag,runCli};
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{LiveStepToolsKey,SessionDynamicToolMetadataKey,TurnDynamicToolMetadataKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{ALLOWED_DYNAMIC_TOOL_EVENTS,isBrandedToolEntry}from"#shared/dynamic-tool-definition.js";import{jsonSchema,zodSchema}from"ai";import{buildCallbackContext}from"#context/build-callback-context.js";import{buildResolveContext}from"#context/dynamic-resolve-context.js";import{normalizeJsonSchemaDefinition}from"#internal/json-schema.js";const log=createLogger(`dynamic-tools`);function toHarnessToolDefinition(e,t){return{description:t.description,execute:e=>t.execute(e,buildCallbackContext()),inputSchema:convertInputSchema(t.inputSchema),name:e,needsApproval:t.needsApproval,...t.toModelOutput===void 0?{}:{toModelOutput:t.toModelOutput}}}function convertInputSchema(e){return typeof e==`object`&&e&&`~standard`in e?zodSchema(e):jsonSchema(e)}function qualifyDynamicToolNames(e,t,n){let r=Object.keys(n),i=[];if(r.length===0)return i;if(t)return i.push({name:e,entryKey:r[0],entry:n[r[0]]}),i;for(let t of r)i.push({name:`${e}__${t}`,entryKey:t,entry:n[t]});return i}function replayDynamicSessionTools(e,t){let n=[];for(let t of e){if(!t.executeStepFnName||!t.closureVars){log.warn(`Dynamic tool "${t.name}" has no registered step function — skipping on this step. The bundler transform may not have processed this tool file.`);continue}let e=lookupStepFunction(t.executeStepFnName);if(!e){log.warn(`Dynamic tool "${t.name}" references step function "${t.executeStepFnName}" which is not registered — skipping on this step.`);continue}n.push({description:t.description,execute:n=>e(t.closureVars,n,buildCallbackContext()),inputSchema:jsonSchema(t.inputSchema),name:t.name})}return n}function getStepRegistry(){let e=Symbol.for(`@workflow/core//registeredSteps`),t=globalThis,n=t[e];return n===void 0&&(n=new Map,t[e]=n),n}function lookupStepFunction(e){try{return getStepRegistry().get(e)||null}catch{return null}}function registerStepFunction(e,t){getStepRegistry().set(e,t)}function safeSerialize(e){try{return JSON.parse(JSON.stringify(e))}catch{return{}}}function durableKeyForEvent(e){switch(e){case`session.started`:return SessionDynamicToolMetadataKey;case`turn.started`:return TurnDynamicToolMetadataKey;default:return}}async function resolveToolsFromEvent(e,t,n,r){let a=await Promise.allSettled(t.map(async t=>{let i=t.events[n.type];if(i===void 0)return null;let a=await i(n,buildResolveContext(e,r));if(a==null)return null;let s,c;return isBrandedToolEntry(a)?(s={_single:a},c=!0):(s=a,c=!1),{resolver:t,entries:s,isSingle:c}})),s=[],c=[];for(let e of a){if(e.status===`rejected`){log.error(`Dynamic tool resolver (${n.type}) threw — skipping.`,{error:toErrorMessage(e.reason)});continue}if(e.value===null)continue;let{resolver:t,entries:r,isSingle:a}=e.value,o=qualifyDynamicToolNames(t.slug,a,r);for(let{name:e,entryKey:n,entry:r}of o){c.push(toHarnessToolDefinition(e,r));let i=`__executeStepFn`in r?r.__executeStepFn:void 0,a=`__closureVars`in r?r.__closureVars:void 0,o=i?.stepId,l=a===void 0?void 0:safeSerialize(a);if(o===void 0){let e=`ash:framework-dynamic:${t.slug}:${n}`,i=r.execute.bind(r);registerStepFunction(e,(e,t,n)=>i(t,n)),o=e,l={}}s.push({name:e,description:r.description,inputSchema:normalizeJsonSchemaDefinition(r.inputSchema),resolverSlug:t.slug,entryKey:n,executeStepFnName:o,closureVars:l})}}return{metadata:s,liveTools:c}}async function dispatchDynamicToolEvent(e){let{ctx:n,resolvers:r,event:i,messages:o}=e;if(!ALLOWED_DYNAMIC_TOOL_EVENTS.has(i.type))return;let s=r.filter(e=>e.eventNames.includes(i.type));if(s.length===0)return;let{metadata:c,liveTools:l}=await resolveToolsFromEvent(n,s,i,o);if(i.type===`step.started`){n.setVirtualContext(LiveStepToolsKey,l);return}let u=durableKeyForEvent(i.type);if(u===void 0)return;let d=new Set(s.map(e=>e.slug)),f=(n.get(u)??[]).filter(e=>!d.has(e.resolverSlug));n.set(u,[...f,...c])}export{dispatchDynamicToolEvent,replayDynamicSessionTools};
1
+ import{createLogger}from"#internal/logging.js";import{LiveStepToolsKey,SessionDynamicToolMetadataKey,TurnDynamicToolMetadataKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{jsonSchema,zodSchema}from"ai";import{ALLOWED_DYNAMIC_TOOL_EVENTS,isBrandedToolEntry}from"#shared/dynamic-tool-definition.js";import{buildCallbackContext}from"#context/build-callback-context.js";import{buildResolveContext}from"#context/dynamic-resolve-context.js";import{normalizeJsonSchemaDefinition}from"#internal/json-schema.js";const log=createLogger(`dynamic-tools`);function toHarnessToolDefinition(e,t){return{description:t.description,execute:e=>t.execute(e,buildCallbackContext()),inputSchema:convertInputSchema(t.inputSchema),name:e,needsApproval:t.needsApproval,...t.toModelOutput===void 0?{}:{toModelOutput:t.toModelOutput}}}function convertInputSchema(e){return typeof e==`object`&&e&&`~standard`in e?zodSchema(e):jsonSchema(e)}function qualifyDynamicToolNames(e,t,n){let r=Object.keys(n),i=[];if(r.length===0)return i;if(t)return i.push({name:e,entryKey:r[0],entry:n[r[0]]}),i;for(let t of r)i.push({name:`${e}__${t}`,entryKey:t,entry:n[t]});return i}function replayDynamicSessionTools(e,t){let n=[];for(let t of e){if(!t.executeStepFnName||!t.closureVars){log.warn(`Dynamic tool "${t.name}" has no registered step function — skipping on this step. The bundler transform may not have processed this tool file.`);continue}let e=lookupStepFunction(t.executeStepFnName);if(!e){log.warn(`Dynamic tool "${t.name}" references step function "${t.executeStepFnName}" which is not registered — skipping on this step.`);continue}n.push({description:t.description,execute:n=>e(t.closureVars,n,buildCallbackContext()),inputSchema:jsonSchema(t.inputSchema),name:t.name})}return n}function getStepRegistry(){let e=Symbol.for(`@workflow/core//registeredSteps`),t=globalThis,n=t[e];return n===void 0&&(n=new Map,t[e]=n),n}function lookupStepFunction(e){try{return getStepRegistry().get(e)||null}catch{return null}}function registerStepFunction(e,t){getStepRegistry().set(e,t)}function safeSerialize(e){try{return JSON.parse(JSON.stringify(e))}catch{return{}}}function durableKeyForEvent(e){switch(e){case`session.started`:return SessionDynamicToolMetadataKey;case`turn.started`:return TurnDynamicToolMetadataKey;default:return}}async function resolveToolsFromEvent(e,t,n,r){let a=await Promise.allSettled(t.map(async t=>{let i=t.events[n.type];if(i===void 0)return null;let a=await i(n,buildResolveContext(e,r));if(a==null)return null;let o,s;return isBrandedToolEntry(a)?(o={_single:a},s=!0):(o=a,s=!1),{resolver:t,entries:o,isSingle:s}})),o=[],s=[];for(let e of a){if(e.status===`rejected`){log.error(`Dynamic tool resolver (${n.type}) threw — skipping.`,{error:toErrorMessage(e.reason)});continue}if(e.value===null)continue;let{resolver:t,entries:r,isSingle:a}=e.value,c=qualifyDynamicToolNames(t.slug,a,r);for(let{name:e,entryKey:n,entry:r}of c){s.push(toHarnessToolDefinition(e,r));let i=`__executeStepFn`in r?r.__executeStepFn:void 0,a=`__closureVars`in r?r.__closureVars:void 0,c=i?.stepId,l=a===void 0?void 0:safeSerialize(a);if(c===void 0){let e=`ash:framework-dynamic:${t.slug}:${n}`,i=r.execute.bind(r);registerStepFunction(e,(e,t,n)=>i(t,n)),c=e,l={}}o.push({name:e,description:r.description,inputSchema:normalizeJsonSchemaDefinition(r.inputSchema),resolverSlug:t.slug,entryKey:n,executeStepFnName:c,closureVars:l})}}return{metadata:o,liveTools:s}}async function dispatchDynamicToolEvent(e){let{ctx:n,resolvers:r,event:i,messages:a}=e;if(!ALLOWED_DYNAMIC_TOOL_EVENTS.has(i.type))return;let o=r.filter(e=>e.eventNames.includes(i.type));if(o.length===0)return;let{metadata:c,liveTools:l}=await resolveToolsFromEvent(n,o,i,a);if(i.type===`step.started`){n.setVirtualContext(LiveStepToolsKey,l);return}let u=durableKeyForEvent(i.type);if(u===void 0)return;let d=new Set(o.map(e=>e.slug)),f=(n.get(u)??[]).filter(e=>!d.has(e.resolverSlug));n.set(u,[...f,...c])}export{dispatchDynamicToolEvent,replayDynamicSessionTools};
@@ -1 +1 @@
1
- import{ClosedQA,Factuality,Sql,Summary}from"../../node_modules/.pnpm/autoevals@0.0.132_ws@8.21.0/node_modules/autoevals/jsdist/index.js";import{formatLanguageModelGatewayId}from"#internal/runtime-model.js";import"ai";import{createAutoevalsClient}from"#evals/scorers/autoevals-client.js";function factuality(e={}){return languageScorer(Factuality,e)}function summary(e={}){return languageScorer(Summary,e)}function sql(e={}){return languageScorer(Sql,e)}function closedQA(t){return async n=>{let r=t.model??n.model;return toScorerResult(await ClosedQA({input:n.input,output:n.output,criteria:t.criteria,model:formatLanguageModelGatewayId(r),client:createAutoevalsClient({languageModel:r,providerOptions:mergeProviderOptions(n,t.modelOptions)})}))}}function languageScorer(e,t){return async n=>{let r=t.model??n.model;return toScorerResult(await e({input:n.input,output:n.output,expected:n.expected,model:formatLanguageModelGatewayId(r),client:createAutoevalsClient({languageModel:r,providerOptions:mergeProviderOptions(n,t.modelOptions)})}))}}function mergeProviderOptions(e,t){let n=e.modelOptions?.providerOptions,r=t?.providerOptions;if(!(n===void 0&&r===void 0))return{...n,...r}}function toScorerResult(e){return{name:e.name,score:e.score,metadata:e.metadata}}export{closedQA,factuality,sql,summary};
1
+ import{ClosedQA,Factuality,Sql,Summary}from"../../node_modules/.pnpm/autoevals@0.0.132_ws@8.21.0/node_modules/autoevals/jsdist/index.js";import"ai";import{formatLanguageModelGatewayId}from"#internal/runtime-model.js";import{createAutoevalsClient}from"#evals/scorers/autoevals-client.js";function factuality(e={}){return languageScorer(Factuality,e)}function summary(e={}){return languageScorer(Summary,e)}function sql(e={}){return languageScorer(Sql,e)}function closedQA(t){return async n=>{let r=t.model??n.model;return toScorerResult(await ClosedQA({input:n.input,output:n.output,criteria:t.criteria,model:formatLanguageModelGatewayId(r),client:createAutoevalsClient({languageModel:r,providerOptions:mergeProviderOptions(n,t.modelOptions)})}))}}function languageScorer(e,t){return async n=>{let r=t.model??n.model;return toScorerResult(await e({input:n.input,output:n.output,expected:n.expected,model:formatLanguageModelGatewayId(r),client:createAutoevalsClient({languageModel:r,providerOptions:mergeProviderOptions(n,t.modelOptions)})}))}}function mergeProviderOptions(e,t){let n=e.modelOptions?.providerOptions,r=t?.providerOptions;if(!(n===void 0&&r===void 0))return{...n,...r}}function toScorerResult(e){return{name:e.name,score:e.score,metadata:e.metadata}}export{closedQA,factuality,sql,summary};
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{jsonSchema}from"ai";import{buildCallbackContext}from"#context/build-callback-context.js";import{createToolLoopHarness}from"#harness/tool-loop.js";import{resolveCodeModeEnabled}from"#shared/code-mode.js";import{resolveRuntimeModelReference}from"#runtime/agent/resolve-model.js";import{findRegisteredRuntimeTool}from"#runtime/tools/registry.js";import{createToolCompactionHandler}from"#execution/tool-compaction.js";const log=createLogger(`execution.node-step`);function createExecutionNodeStep(e){let t=createRuntimeModelResolver(e.compiledArtifactsSource),n=createNodeHarnessTools({node:e.node}),r=createToolCompactionHandler(collectResolvedTools(e.node));return createToolLoopHarness({capabilities:e.capabilities,codeMode:resolveCodeModeEnabled(e.node.agent.config?.experimental?.codeMode),handleEvent:e.handleEvent,mode:e.mode,onCompaction:r,resolveModel:t,runtimeIdentity:buildRuntimeIdentity(e.node),tools:n})}function buildRuntimeIdentity(e){let n=resolveInstalledPackageInfo(),r={agentId:e.turnAgent.id,agentName:e.agent.config?.name,ashVersion:n.version,modelId:e.turnAgent.model.id},i=process.env.VERCEL_GIT_COMMIT_SHA?.trim(),a=process.env.VERCEL_GIT_COMMIT_REF?.trim(),o=process.env.VERCEL_DEPLOYMENT_CREATED_AT?.trim();return i||a||o?{...r,build:{deployedAt:o||void 0,gitBranch:a||void 0,gitSha:i||void 0}}:r}function createRuntimeModelResolver(e){return t=>resolveRuntimeModelReference(t,{compiledArtifactsSource:e})}function collectResolvedTools(e){return[...e.toolRegistry.toolsByName.values()].map(e=>e.definition)}function createNodeHarnessTools(e){let t=new Map;for(let n of e.node.turnAgent.tools){let r=resolveHarnessToolDefinition({node:e.node,tool:n});r!==null&&t.set(n.name,r)}return t}function resolveHarnessToolDefinition(e){if(e.tool.kind===`subagent`)return{description:e.tool.description??``,inputSchema:jsonSchema(e.tool.inputSchema??{}),name:e.tool.name,runtimeAction:{kind:`subagent-call`,nodeId:e.tool.nodeId,subagentName:e.tool.name}};if(e.tool.kind===`remote`)return{description:e.tool.description??``,inputSchema:jsonSchema(e.tool.inputSchema??{}),name:e.tool.name,runtimeAction:{kind:`remote-agent-call`,nodeId:e.tool.nodeId,remoteAgentName:e.tool.name,subagentName:e.tool.name}};let t=findRegisteredRuntimeTool(e.node.toolRegistry,e.tool.name);if(t===null)return log.warn(`declared tool is not registered — omitting from toolset`,{toolName:e.tool.name,nodeId:e.node.nodeId}),null;let i=t.definition,a=i.sourceId.startsWith(`ash:`),o=i.execute;return{approvalKey:i.approvalKey,description:i.description,execute:o===void 0?void 0:a?o:e=>o(e,buildCallbackContext()),inputSchema:i.inputStandardSchema??jsonSchema(i.inputSchema??{}),name:i.name,needsApproval:i.needsApproval,toModelOutput:i.toModelOutput}}export{createExecutionNodeStep,createNodeHarnessTools};
1
+ import{createLogger}from"#internal/logging.js";import{jsonSchema}from"ai";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{buildCallbackContext}from"#context/build-callback-context.js";import{createToolLoopHarness}from"#harness/tool-loop.js";import{resolveCodeModeEnabled}from"#shared/code-mode.js";import{resolveRuntimeModelReference}from"#runtime/agent/resolve-model.js";import{findRegisteredRuntimeTool}from"#runtime/tools/registry.js";import{createToolCompactionHandler}from"#execution/tool-compaction.js";const log=createLogger(`execution.node-step`);function createExecutionNodeStep(e){let t=createRuntimeModelResolver(e.compiledArtifactsSource),n=createNodeHarnessTools({node:e.node}),r=createToolCompactionHandler(collectResolvedTools(e.node));return createToolLoopHarness({capabilities:e.capabilities,codeMode:resolveCodeModeEnabled(e.node.agent.config?.experimental?.codeMode),handleEvent:e.handleEvent,mode:e.mode,onCompaction:r,resolveModel:t,runtimeIdentity:buildRuntimeIdentity(e.node),tools:n})}function buildRuntimeIdentity(e){let t=resolveInstalledPackageInfo(),r={agentId:e.turnAgent.id,agentName:e.agent.config?.name,ashVersion:t.version,modelId:e.turnAgent.model.id},i=process.env.VERCEL_GIT_COMMIT_SHA?.trim(),a=process.env.VERCEL_GIT_COMMIT_REF?.trim(),o=process.env.VERCEL_DEPLOYMENT_CREATED_AT?.trim();return i||a||o?{...r,build:{deployedAt:o||void 0,gitBranch:a||void 0,gitSha:i||void 0}}:r}function createRuntimeModelResolver(e){return t=>resolveRuntimeModelReference(t,{compiledArtifactsSource:e})}function collectResolvedTools(e){return[...e.toolRegistry.toolsByName.values()].map(e=>e.definition)}function createNodeHarnessTools(e){let t=new Map;for(let n of e.node.turnAgent.tools){let r=resolveHarnessToolDefinition({node:e.node,tool:n});r!==null&&t.set(n.name,r)}return t}function resolveHarnessToolDefinition(e){if(e.tool.kind===`subagent`)return{description:e.tool.description??``,inputSchema:jsonSchema(e.tool.inputSchema??{}),name:e.tool.name,runtimeAction:{kind:`subagent-call`,nodeId:e.tool.nodeId,subagentName:e.tool.name}};if(e.tool.kind===`remote`)return{description:e.tool.description??``,inputSchema:jsonSchema(e.tool.inputSchema??{}),name:e.tool.name,runtimeAction:{kind:`remote-agent-call`,nodeId:e.tool.nodeId,remoteAgentName:e.tool.name,subagentName:e.tool.name}};let n=findRegisteredRuntimeTool(e.node.toolRegistry,e.tool.name);if(n===null)return log.warn(`declared tool is not registered — omitting from toolset`,{toolName:e.tool.name,nodeId:e.node.nodeId}),null;let i=n.definition,a=i.sourceId.startsWith(`ash:`),o=i.execute;return{approvalKey:i.approvalKey,description:i.description,execute:o===void 0?void 0:a?o:e=>o(e,buildCallbackContext()),inputSchema:i.inputStandardSchema??jsonSchema(i.inputSchema??{}),name:i.name,needsApproval:i.needsApproval,toModelOutput:i.toModelOutput}}export{createExecutionNodeStep,createNodeHarnessTools};
@@ -1 +1 @@
1
- import{contextStorage}from"#context/container.js";import"ai";import{CODE_MODE_TOOL_NAME,loadCodeModeModule}from"#shared/code-mode.js";import{isAuthorizationSignal}from"#harness/authorization.js";import{CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND,markCodeModeToolExecutionOptions,toCodeModeConnectionAuthArgs}from"#runtime/framework-tools/code-mode-connection-auth.js";import{buildDynamicTools}from"#context/build-dynamic-tools.js";import{buildToolSet}from"#harness/tools.js";function createAshCodeModeOptions(e={}){let t={approval:{mode:`interrupt`}};return e.lifecycle!==void 0&&(t.lifecycle=e.lifecycle),t}async function applyCodeModeToToolSet(e){let r={},i={};for(let[t,n]of Object.entries(e.tools)){if(isDirectTool(n,e.harnessTools.get(t))){i[t]=n;continue}r[t]=wrapHostToolForCodeMode(n)}if(Object.keys(r).length>0){let{createCodeModeTool:a}=await loadCodeModeModule();i[CODE_MODE_TOOL_NAME]=a(r,createAshCodeModeOptions({lifecycle:e.lifecycle}))}return{hostTools:r,modelTools:i}}async function buildCodeModeHostTools(t){let n=buildToolSet({approvedTools:t.approvedTools,capabilities:t.capabilities,tools:t.tools}),r=contextStorage.getStore();if(r!==void 0){let e=buildDynamicTools(r);for(let t of e)n[t.name]??={description:t.description,inputSchema:t.inputSchema,execute:t.execute}}return(await applyCodeModeToToolSet({harnessTools:t.tools,tools:n})).hostTools}function isDirectTool(e,t){return e.execute===void 0||t?.runtimeAction!==void 0}function wrapHostToolForCodeMode(e){let t=e.execute,o=e.toModelOutput;return t===void 0?e:{...e,execute:async(e,s)=>{let c=await resolveExecuteOutput(t(e,markCodeModeToolExecutionOptions(s)));if(isAuthorizationSignal(c)){let{requestCodeModeInterrupt:t}=await loadCodeModeModule(),r=c.challenges[0]?.name;r&&t({args:toCodeModeConnectionAuthArgs(e),challenges:c.challenges,connectionName:r,kind:CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND,toolName:``})}if(o===void 0)return c;let l=await o({output:c});return isModelOutput(l)?l.value:l}}}async function resolveExecuteOutput(e){if(isAsyncIterable(e)){let t;for await(let n of e)t=n;return t}return await e}function isAsyncIterable(e){return typeof e==`object`&&!!e&&Symbol.asyncIterator in e&&typeof e[Symbol.asyncIterator]==`function`}function isModelOutput(e){if(typeof e!=`object`||!e)return!1;let t=e;return(t.type===`json`||t.type===`text`)&&Object.hasOwn(t,`value`)}export{applyCodeModeToToolSet,buildCodeModeHostTools,createAshCodeModeOptions};
1
+ import"ai";import{contextStorage}from"#context/container.js";import{CODE_MODE_TOOL_NAME,loadCodeModeModule}from"#shared/code-mode.js";import{isAuthorizationSignal}from"#harness/authorization.js";import{CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND,markCodeModeToolExecutionOptions,toCodeModeConnectionAuthArgs}from"#runtime/framework-tools/code-mode-connection-auth.js";import{buildDynamicTools}from"#context/build-dynamic-tools.js";import{buildToolSet}from"#harness/tools.js";function createAshCodeModeOptions(e={}){let t={approval:{mode:`interrupt`}};return e.lifecycle!==void 0&&(t.lifecycle=e.lifecycle),t}async function applyCodeModeToToolSet(e){let r={},i={};for(let[t,n]of Object.entries(e.tools)){if(isDirectTool(n,e.harnessTools.get(t))){i[t]=n;continue}r[t]=wrapHostToolForCodeMode(n)}if(Object.keys(r).length>0){let{createCodeModeTool:a}=await loadCodeModeModule();i[CODE_MODE_TOOL_NAME]=a(r,createAshCodeModeOptions({lifecycle:e.lifecycle}))}return{hostTools:r,modelTools:i}}async function buildCodeModeHostTools(t){let n=buildToolSet({approvedTools:t.approvedTools,capabilities:t.capabilities,tools:t.tools}),r=contextStorage.getStore();if(r!==void 0){let e=buildDynamicTools(r);for(let t of e)n[t.name]??={description:t.description,inputSchema:t.inputSchema,execute:t.execute}}return(await applyCodeModeToToolSet({harnessTools:t.tools,tools:n})).hostTools}function isDirectTool(e,t){return e.execute===void 0||t?.runtimeAction!==void 0}function wrapHostToolForCodeMode(e){let t=e.execute;return t===void 0?e:{...e,execute:async(e,o)=>{let s=await resolveExecuteOutput(t(e,markCodeModeToolExecutionOptions(o)));if(isAuthorizationSignal(s)){let{requestCodeModeInterrupt:t}=await loadCodeModeModule(),r=s.challenges[0]?.name;r&&t({args:toCodeModeConnectionAuthArgs(e),challenges:s.challenges,connectionName:r,kind:CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND,toolName:``})}return s}}}async function resolveExecuteOutput(e){if(isAsyncIterable(e)){let t;for await(let n of e)t=n;return t}return await e}function isAsyncIterable(e){return typeof e==`object`&&!!e&&Symbol.asyncIterator in e&&typeof e[Symbol.asyncIterator]==`function`}export{applyCodeModeToToolSet,buildCodeModeHostTools,createAshCodeModeOptions};