eve 0.27.2 → 0.27.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/src/channel/reset-session.d.ts +8 -0
- package/dist/src/channel/reset-session.js +1 -0
- package/dist/src/channel/routes.d.ts +24 -1
- package/dist/src/channel/send.js +1 -1
- package/dist/src/channel/session-callback.js +1 -1
- package/dist/src/channel/types.d.ts +14 -0
- package/dist/src/chunks/{use-eve-agent-CbF0l_Fp.js → use-eve-agent-BtkhbY2I.js} +53 -4
- package/dist/src/chunks/{use-eve-agent-CgxB9WQv.js → use-eve-agent-NUjD28Iu.js} +53 -4
- package/dist/src/cli/commands/build.js +1 -1
- package/dist/src/cli/commands/info.js +1 -1
- package/dist/src/cli/dev/tui/blocks.d.ts +7 -0
- package/dist/src/cli/dev/tui/blocks.js +3 -3
- package/dist/src/cli/dev/tui/message-queue.d.ts +88 -0
- package/dist/src/cli/dev/tui/message-queue.js +1 -0
- package/dist/src/cli/dev/tui/runner.d.ts +33 -0
- package/dist/src/cli/dev/tui/runner.js +1 -1
- package/dist/src/cli/dev/tui/setup-commands.js +1 -1
- package/dist/src/cli/dev/tui/subagent-pump.d.ts +9 -0
- package/dist/src/cli/dev/tui/subagent-pump.js +1 -1
- package/dist/src/cli/dev/tui/terminal-renderer.d.ts +9 -0
- package/dist/src/cli/dev/tui/terminal-renderer.js +13 -11
- package/dist/src/client/index.d.ts +1 -1
- package/dist/src/client/session.d.ts +13 -1
- package/dist/src/client/session.js +1 -1
- package/dist/src/client/types.d.ts +10 -0
- package/dist/src/context/dynamic-tool-lifecycle.d.ts +13 -1
- package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
- package/dist/src/context/keys.d.ts +5 -0
- package/dist/src/context/keys.js +1 -1
- package/dist/src/execution/node-step.d.ts +6 -0
- package/dist/src/execution/node-step.js +1 -1
- package/dist/src/execution/sandbox/bindings/docker.js +1 -1
- package/dist/src/execution/sandbox/bindings/just-bash.js +1 -1
- package/dist/src/execution/sandbox/bindings/microsandbox-lifecycle.js +1 -1
- package/dist/src/execution/sandbox/bindings/vercel.js +1 -1
- package/dist/src/execution/terminal-session-failure-step.js +1 -1
- package/dist/src/execution/workflow-callback-url.d.ts +12 -2
- package/dist/src/execution/workflow-callback-url.js +1 -1
- package/dist/src/execution/workflow-entry.js +1 -1
- package/dist/src/execution/workflow-runtime.js +1 -1
- package/dist/src/execution/workflow-steps.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-module-loader.js +3 -3
- package/dist/src/internal/authored-package-tsconfig-paths.js +1 -1
- package/dist/src/internal/authored-relative-extension-resolver.d.ts +6 -0
- package/dist/src/internal/authored-relative-extension-resolver.js +1 -0
- package/dist/src/internal/nitro/dev-runtime-source-snapshot.js +1 -1
- package/dist/src/internal/nitro/host/build-application.js +1 -1
- package/dist/src/internal/nitro/host/types.d.ts +9 -0
- package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
- package/dist/src/internal/node-esm-compat-banner.js +1 -1
- package/dist/src/internal/workflow-bundle/builder-support.d.ts +5 -0
- package/dist/src/internal/workflow-bundle/builder.js +2 -2
- package/dist/src/internal/workflow-bundle/dynamic-tool-ast-references.d.ts +39 -0
- package/dist/src/internal/workflow-bundle/dynamic-tool-ast-references.js +1 -0
- package/dist/src/internal/workflow-bundle/dynamic-tool-transform.js +4 -4
- package/dist/src/internal/workflow-bundle/vercel-workflow-output.d.ts +10 -1
- package/dist/src/internal/workflow-bundle/vercel-workflow-output.js +1 -1
- package/dist/src/packages/eve-catalog/src/index.js +1 -1
- package/dist/src/protocol/reset-session.d.ts +14 -0
- package/dist/src/protocol/reset-session.js +1 -0
- package/dist/src/protocol/routes.d.ts +5 -0
- package/dist/src/protocol/routes.js +1 -1
- package/dist/src/public/channels/eve.d.ts +2 -2
- package/dist/src/public/channels/eve.js +1 -1
- package/dist/src/public/channels/index.d.ts +1 -1
- package/dist/src/public/channels/linear/inbound-images.d.ts +18 -0
- package/dist/src/public/channels/linear/inbound-images.js +1 -0
- package/dist/src/public/channels/linear/linearChannel.js +1 -1
- package/dist/src/public/channels/slack/limits.js +1 -1
- package/dist/src/public/definitions/channel.d.ts +1 -1
- package/dist/src/public/next/vercel-output-config.js +1 -1
- package/dist/src/runtime/sessions/compiled-agent-cache.js +1 -1
- package/dist/src/setup/flows/install-vercel-cli.d.ts +8 -2
- package/dist/src/setup/flows/install-vercel-cli.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/setup/vercel-project-api.d.ts +1 -1
- package/dist/src/setup/vercel-project-api.js +1 -1
- package/dist/src/shared/public-route-prefix.d.ts +23 -0
- package/dist/src/shared/public-route-prefix.js +1 -0
- package/dist/src/svelte/index.js +1 -1
- package/dist/src/svelte/use-eve-agent.js +1 -1
- package/dist/src/vue/index.js +1 -1
- package/dist/src/vue/use-eve-agent.js +1 -1
- package/docs/channels/custom.mdx +38 -1
- package/docs/channels/linear.mdx +1 -1
- package/docs/extensions.md +100 -20
- package/docs/guides/dev-tui.md +9 -2
- package/docs/guides/frontend/nextjs.mdx +2 -0
- package/docs/reference/cli.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# eve
|
|
2
2
|
|
|
3
|
+
## 0.27.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5d961f3: Request Vercel CLI's maximum 100-team page instead of following its broken default pagination path.
|
|
8
|
+
- 04d5814: `eve dev` no longer fails at boot with `UNRESOLVED_IMPORT` when a mounted extension (or any dependency) resolves through a `node_modules` above the app root — npm/yarn workspace hoisting, intermediate monorepo levels, and bare `withEve` agent directories whose host app owns the install now materialize in the dev-runtime snapshot.
|
|
9
|
+
- bbba073: The dev TUI supports queueing, steering, and cooperative turn cancellation. Pressing Enter while a turn is running queues the message (up to 5) in a pinned panel directly above the input — one line per message — and the queue coalesces into the next turn's message when the turn ends. Esc steers: it pops the oldest queued message, cancels the running turn cooperatively (`turn.cancelled` → `session.waiting`, keeping the session's context), and submits the popped message as the replacement turn. With nothing queued, Esc twice cancels the turn. Cancellation requests retry while the turn is live, so an Esc that lands in the turn-dispatch window (before the server has armed the turn's cancel hook) is no longer silently lost. Cancelling a turn mid-delegation settles its subagent sections and stops their child streams, so stale subagent output cannot paint into the steered turn. Messages still queued when a turn is interrupted or fails restore into the next prompt's input, and a turn cancelled from outside the prompt (a stale cancel or another client's `/cancel`) restores its submitted message the same way. On exit, the parting line names the server session id (`☰eve v0.27.0 · session ses_…`) so the conversation can be found again.
|
|
10
|
+
- dfd360f: Custom channel routes can now call `reset()` and `ClientSession` can reset the session that owns a stable continuation token. The next `send()` starts a fresh workflow session and lazily initializes a new session-scoped sandbox instead of reusing prior history or workspace state, and the `eve dev` TUI's `/new` performs that durable reset before clearing its transcript.
|
|
11
|
+
|
|
12
|
+
## 0.27.3
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 8168518: Fix connector-auth and remote-subagent callback URLs returning 404 in multi-agent mode. Generated per-agent Vercel services now bake the agent's public route prefix (`/eve/agents/<name>`) into their workflow function environment via `EVE_PUBLIC_ROUTE_PREFIX`, framework-minted callback URLs prepend it, and the session-callback validator accepts callback URLs mounted behind a route prefix so OAuth redirects and remote-subagent session callbacks reach the deployed agent.
|
|
17
|
+
- fecdaf3: Attach authenticated `uploads.linear.app` images from inbound Linear Agent Session markdown as multimodal file parts while preserving the text fallback for untrusted, failed, or non-image URLs.
|
|
18
|
+
- 25c12d3: Prevent bundler-suffixed CommonJS compatibility variables from suppressing the Node ESM globals that eve bundles need at startup.
|
|
19
|
+
- 1bd0aa4: Preserve underscores in connection tool names shown by Slack typing status indicators.
|
|
20
|
+
- 4d3748d: Keep raw outer workflow failure details in the private session trace instead of copying them into provider logs and Workflow telemetry.
|
|
21
|
+
- 4803dbe: Scope authored resolver hooks to relevant import specifiers and reuse filesystem probes for one build. Repeated extensionless misses now perform at most 19 stats once per plugin instance instead of on every resolution.
|
|
22
|
+
- d76f76b: Refresh session-scoped dynamic tools when an existing production session moves to a new Vercel deployment, so added, removed, and reordered tools use the current build.
|
|
23
|
+
- 45ad5d0: Analyze dynamic tool closure references from the syntax tree so object keys and string contents cannot become invalid runtime captures.
|
|
24
|
+
- fc53a9f: Show the number of discovered authored tools in the human-readable `eve info` output.
|
|
25
|
+
- 1c3123c: Write authored sandbox workspace seed files before running bootstrap across the Vercel, Docker, microsandbox, and just-bash backends, so bootstrap can consume canonical workspace inputs and its outputs remain in the captured template.
|
|
26
|
+
- 7094b4a: Load up to 100 Vercel teams per page and use the authenticated API continuation that matches Vercel's emitted cursor, avoiding repeated-page loops for accounts with many teams. When an outdated CLI lacks the required list options, the TUI can upgrade it with the native upgrader and now retains a concise error if that upgrade fails.
|
|
27
|
+
|
|
3
28
|
## 0.27.2
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ResetFn } from "#channel/routes.js";
|
|
2
|
+
import type { Runtime } from "#channel/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds a channel-scoped session reset helper. It resolves an owner once and
|
|
5
|
+
* retires that observed session id, so it can never cancel a later owner that
|
|
6
|
+
* claimed the same continuation token.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createResetFn(runtime: Runtime, channelName: string): ResetFn;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function createResetFn(e,t){return async n=>{let r=`${t}:${n.continuationToken}`,i=await e.resolveSession(r);return i===void 0?{status:`no_active_session`}:(await e.terminateSession({reason:n.reason,sessionId:i.sessionId}),{status:`reset`,previousSessionId:i.sessionId})}}export{createResetFn};
|
|
@@ -10,7 +10,8 @@ type WebSocketHeaders = Headers | readonly (readonly [string, string])[] | Recor
|
|
|
10
10
|
/**
|
|
11
11
|
* Second argument passed to every route handler. `send` starts or continues a
|
|
12
12
|
* session on this channel; `cancel` stops the active turn on a session
|
|
13
|
-
* addressed by continuation token; `
|
|
13
|
+
* addressed by continuation token; `reset` retires a session so its
|
|
14
|
+
* token can start a fresh session; `getSession` looks one up by id; `receive`
|
|
14
15
|
* hands inbound work to a different channel; `params` contains the matched
|
|
15
16
|
* path parameters; `waitUntil` keeps background work alive past the response;
|
|
16
17
|
* `requestIp` is the client IP, or `null` when the host cannot provide it.
|
|
@@ -23,6 +24,7 @@ export interface RouteHandlerArgs<TState = undefined> {
|
|
|
23
24
|
*/
|
|
24
25
|
resolveActiveSession: ResolveActiveSessionFn;
|
|
25
26
|
cancel: CancelFn;
|
|
27
|
+
reset: ResetFn;
|
|
26
28
|
getSession: GetSessionFn;
|
|
27
29
|
/**
|
|
28
30
|
* Starts a session on a different channel to hand off inbound work (e.g. an
|
|
@@ -110,6 +112,27 @@ export interface CancelOptions {
|
|
|
110
112
|
* on the event stream.
|
|
111
113
|
*/
|
|
112
114
|
export type CancelFn = (options: CancelOptions) => Promise<CancelTurnResult>;
|
|
115
|
+
/** Options for {@link ResetFn}. */
|
|
116
|
+
export interface ResetOptions {
|
|
117
|
+
/** Channel-local raw token, in the same format accepted by {@link SendFn}. */
|
|
118
|
+
readonly continuationToken: string;
|
|
119
|
+
/** Human-readable terminal reason. Do not include credentials or message contents. */
|
|
120
|
+
readonly reason?: string;
|
|
121
|
+
}
|
|
122
|
+
/** Result of resetting the session that owned a continuation token. */
|
|
123
|
+
export type ResetResult = {
|
|
124
|
+
readonly status: "reset";
|
|
125
|
+
readonly previousSessionId: string;
|
|
126
|
+
} | {
|
|
127
|
+
readonly status: "no_active_session";
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Terminally retires the session currently owning a continuation token.
|
|
131
|
+
* The token is available for the next {@link SendFn} after this resolves;
|
|
132
|
+
* that send starts with fresh history and state, and initializes a new
|
|
133
|
+
* session-scoped sandbox on first sandbox use.
|
|
134
|
+
*/
|
|
135
|
+
export type ResetFn = (options: ResetOptions) => Promise<ResetResult>;
|
|
113
136
|
export type RouteHandler<TState = undefined> = (req: Request, args: RouteHandlerArgs<TState>) => Promise<Response>;
|
|
114
137
|
/**
|
|
115
138
|
* A connected WebSocket peer passed to every {@link WebSocketRouteHooks}
|
package/dist/src/channel/send.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createSession}from"#channel/session.js";import{
|
|
1
|
+
import{createSession}from"#channel/session.js";import{isRuntimeNoActiveSessionError}from"#execution/runtime-errors.js";import{serializeUrlFilePart}from"#internal/attachments/url-refs.js";function createSendFn(n,r,i,a={}){return async(o,s)=>{let c=s.auth,l=s.callback,u=s.mode??`conversation`,d=s.state,f=s.title,p=s.continuationToken,m=`${i}:${p}`,{message:h,inputResponses:g,context:_,outputSchema:v}=normalizeSendInput(o),y=serializeUrlFilePartsInMessage(h);try{let t={auth:c,continuationToken:m,requestId:a.requestId,payload:{inputResponses:g,message:y,context:_,outputSchema:v}},{sessionId:r}=await n.deliver(t);return createSession(r,p,n)}catch(e){if(!isRuntimeNoActiveSessionError(e))throw e}if(g&&g.length>0)throw Error(`Cannot deliver inputResponses — the target session was not found via continuation token.`);let b={adapter:d?{...r,state:{...r.state,...d}}:r,auth:c,capabilities:u===`conversation`?{requestInput:!0}:void 0,channelName:i,callback:l,continuationToken:m,input:{message:y??``,context:_,outputSchema:v},mode:u,requestId:a.requestId,title:f};return createSession((await n.run(b)).sessionId,p,n)}}function serializeUrlFilePartsInMessage(e){if(e===void 0||typeof e==`string`)return e;let t=!1,r=e.map(e=>e.type===`file`&&e.data instanceof URL&&e.data.protocol!==`data:`?(t=!0,{...e,data:serializeUrlFilePart(e.data)}):e);return t?r:e}function normalizeSendInput(e){return typeof e==`string`||Array.isArray(e)?{message:e}:e}export{createSendFn};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z}from"#compiled/zod/index.js";import{createEveCallbackRoutePath}from"#protocol/routes.js";import{isReservedIpAddress}from"#shared/network-address.js";const sessionCallbackSchema=z.object({callId:z.string().min(1),subagentName:z.string().min(1),token:z.string().min(1),url:z.string().min(1)}).strict().superRefine((e,t)=>{let r;try{r=new URL(e.url)}catch{t.addIssue({code:`custom`,message:`Callback url must be absolute.`,path:[`url`]});return}readCallbackUrlToken(r)!==e.token&&t.addIssue({code:`custom`,message:`Callback url token must match callback token.`,path:[`url`]}),isReservedIpAddress(r.hostname)&&t.addIssue({code:`custom`,message:`Callback url host must not be a private or reserved address.`,path:[`url`]})});function parseSessionCallback(e){let t=sessionCallbackSchema.safeParse(e);return t.success?{callback:t.data,ok:!0}:{cause:t.error,message:formatSessionCallbackParseError(t.error),ok:!1}}function readCallbackUrlToken(e){let n=createEveCallbackRoutePath(``)
|
|
1
|
+
import{z}from"#compiled/zod/index.js";import{createEveCallbackRoutePath}from"#protocol/routes.js";import{isReservedIpAddress}from"#shared/network-address.js";const sessionCallbackSchema=z.object({callId:z.string().min(1),subagentName:z.string().min(1),token:z.string().min(1),url:z.string().min(1)}).strict().superRefine((e,t)=>{let r;try{r=new URL(e.url)}catch{t.addIssue({code:`custom`,message:`Callback url must be absolute.`,path:[`url`]});return}readCallbackUrlToken(r)!==e.token&&t.addIssue({code:`custom`,message:`Callback url token must match callback token.`,path:[`url`]}),isReservedIpAddress(r.hostname)&&t.addIssue({code:`custom`,message:`Callback url host must not be a private or reserved address.`,path:[`url`]})});function parseSessionCallback(e){let t=sessionCallbackSchema.safeParse(e);return t.success?{callback:t.data,ok:!0}:{cause:t.error,message:formatSessionCallbackParseError(t.error),ok:!1}}function readCallbackUrlToken(e){let n=createEveCallbackRoutePath(``),r=e.pathname.lastIndexOf(n);if(r===-1)return null;let i=e.pathname.slice(r+n.length);if(i.length===0||i.includes(`/`))return null;try{return decodeURIComponent(i)}catch{return null}}function formatSessionCallbackParseError(e){return`Invalid callback metadata: ${e.issues.map(e=>`${e.path.length===0?`callback`:`callback.${e.path.join(`.`)}`}: ${e.message}`).join(`; `)}`}export{parseSessionCallback};
|
|
@@ -21,6 +21,18 @@ export interface CancelTurnInput {
|
|
|
21
21
|
export interface CancelTurnResult {
|
|
22
22
|
readonly status: CancelTurnStatus;
|
|
23
23
|
}
|
|
24
|
+
/** Identifies a session to transition permanently to a terminal state. */
|
|
25
|
+
export interface TerminateSessionInput {
|
|
26
|
+
/** Human-readable reason recorded on the terminal workflow transition. */
|
|
27
|
+
readonly reason?: string;
|
|
28
|
+
readonly sessionId: string;
|
|
29
|
+
}
|
|
30
|
+
/** Result of attempting to terminally retire a session. */
|
|
31
|
+
export type TerminateSessionResult = {
|
|
32
|
+
readonly status: "terminated";
|
|
33
|
+
} | {
|
|
34
|
+
readonly status: "already_terminal";
|
|
35
|
+
};
|
|
24
36
|
/**
|
|
25
37
|
* Identifies one turn within a session.
|
|
26
38
|
*
|
|
@@ -327,6 +339,8 @@ export interface Runtime {
|
|
|
327
339
|
run(input: RunInput): Promise<RunHandle>;
|
|
328
340
|
/** Requests cancellation of a session's in-flight turn. */
|
|
329
341
|
cancelTurn(input: CancelTurnInput): Promise<CancelTurnResult>;
|
|
342
|
+
/** Terminally retires a session and releases its non-retained continuation hooks. */
|
|
343
|
+
terminateSession(input: TerminateSessionInput): Promise<TerminateSessionResult>;
|
|
330
344
|
/**
|
|
331
345
|
* Delivers a follow-up message to a parked session.
|
|
332
346
|
*/
|
|
@@ -6,6 +6,7 @@ const EVE_ROUTE_PREFIX = "/eve/v1";
|
|
|
6
6
|
const EVE_HEALTH_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/health`;
|
|
7
7
|
const EVE_INFO_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/info`;
|
|
8
8
|
const EVE_CREATE_SESSION_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/session`;
|
|
9
|
+
const EVE_RESET_SESSION_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/session/reset`;
|
|
9
10
|
const EVE_CONTINUE_SESSION_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/session/:sessionId`;
|
|
10
11
|
const EVE_MESSAGE_STREAM_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/session/:sessionId/stream`;
|
|
11
12
|
const EVE_CANCEL_TURN_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/session/:sessionId/cancel`;
|
|
@@ -311,6 +312,17 @@ const CancelTurnResponseSchema = z.object({
|
|
|
311
312
|
status: z.enum(["accepted", "no_active_turn"])
|
|
312
313
|
});
|
|
313
314
|
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/protocol/reset-session.ts
|
|
317
|
+
const ResetResponseSchema = z.discriminatedUnion("status", [z.object({
|
|
318
|
+
ok: z.literal(true),
|
|
319
|
+
previousSessionId: z.string().min(1),
|
|
320
|
+
status: z.literal("reset")
|
|
321
|
+
}), z.object({
|
|
322
|
+
ok: z.literal(true),
|
|
323
|
+
status: z.literal("no_active_session")
|
|
324
|
+
})]);
|
|
325
|
+
|
|
314
326
|
//#endregion
|
|
315
327
|
//#region src/client/output-schema.ts
|
|
316
328
|
function extractCompletedResult(events) {
|
|
@@ -696,10 +708,14 @@ var ClientSession = class {
|
|
|
696
708
|
const payload = normalizeSendTurnInput(input);
|
|
697
709
|
const state = this.#state;
|
|
698
710
|
const { continuationToken, sessionId } = await this.#postTurn(payload, state);
|
|
699
|
-
if (this.#state === state)
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
711
|
+
if (this.#state === state) {
|
|
712
|
+
const nextState = {
|
|
713
|
+
...state,
|
|
714
|
+
sessionId
|
|
715
|
+
};
|
|
716
|
+
if (continuationToken !== void 0) nextState.continuationToken = continuationToken;
|
|
717
|
+
this.#state = nextState;
|
|
718
|
+
}
|
|
703
719
|
return new MessageResponse({
|
|
704
720
|
continuationToken,
|
|
705
721
|
createStream: () => this.#createEventStream(sessionId, continuationToken, state, payload),
|
|
@@ -732,6 +748,39 @@ var ClientSession = class {
|
|
|
732
748
|
status: result.data.status
|
|
733
749
|
};
|
|
734
750
|
}
|
|
751
|
+
async reset() {
|
|
752
|
+
const state = this.#state;
|
|
753
|
+
const continuationToken = state.continuationToken;
|
|
754
|
+
if (continuationToken === void 0) {
|
|
755
|
+
if (state.sessionId !== void 0) throw new Error("Session has no continuation token. Consume its event stream before resetting.");
|
|
756
|
+
this.#state = createInitialSessionState();
|
|
757
|
+
return { status: "no_active_session" };
|
|
758
|
+
}
|
|
759
|
+
const url = createClientUrl(this.#context.host, EVE_RESET_SESSION_ROUTE_PATH);
|
|
760
|
+
const headers = await this.#context.resolveHeaders();
|
|
761
|
+
headers.set("content-type", "application/json");
|
|
762
|
+
const response = await fetch(url, withRedirectPolicy$1({
|
|
763
|
+
body: JSON.stringify({ continuationToken }),
|
|
764
|
+
headers,
|
|
765
|
+
method: "POST"
|
|
766
|
+
}, this.#context.redirect));
|
|
767
|
+
const body = await response.text();
|
|
768
|
+
if (!response.ok) throw new ClientError(response.status, body, response.headers);
|
|
769
|
+
let payload;
|
|
770
|
+
try {
|
|
771
|
+
payload = JSON.parse(body);
|
|
772
|
+
} catch {
|
|
773
|
+
throw new Error(`Reset route returned invalid JSON (${response.status}).`);
|
|
774
|
+
}
|
|
775
|
+
const result = ResetResponseSchema.safeParse(payload);
|
|
776
|
+
if (!result.success) throw new Error(`Reset route returned an invalid response (${response.status}).`);
|
|
777
|
+
if (result.data.status === "reset" && state.sessionId !== void 0 && result.data.previousSessionId !== state.sessionId) throw new Error(`Reset route returned an invalid response (${response.status}).`);
|
|
778
|
+
if (this.#state === state) this.#state = createInitialSessionState();
|
|
779
|
+
return result.data.status === "reset" ? {
|
|
780
|
+
previousSessionId: result.data.previousSessionId,
|
|
781
|
+
status: "reset"
|
|
782
|
+
} : { status: "no_active_session" };
|
|
783
|
+
}
|
|
735
784
|
stream(options) {
|
|
736
785
|
const sessionId = this.#state.sessionId;
|
|
737
786
|
if (!sessionId) throw new Error("Session has no session ID. Send a message first.");
|
|
@@ -6,6 +6,7 @@ const EVE_ROUTE_PREFIX = "/eve/v1";
|
|
|
6
6
|
const EVE_HEALTH_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/health`;
|
|
7
7
|
const EVE_INFO_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/info`;
|
|
8
8
|
const EVE_CREATE_SESSION_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/session`;
|
|
9
|
+
const EVE_RESET_SESSION_ROUTE_PATH = `${EVE_ROUTE_PREFIX}/session/reset`;
|
|
9
10
|
const EVE_CONTINUE_SESSION_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/session/:sessionId`;
|
|
10
11
|
const EVE_MESSAGE_STREAM_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/session/:sessionId/stream`;
|
|
11
12
|
const EVE_CANCEL_TURN_ROUTE_PATTERN = `${EVE_ROUTE_PREFIX}/session/:sessionId/cancel`;
|
|
@@ -311,6 +312,17 @@ const CancelTurnResponseSchema = z.object({
|
|
|
311
312
|
status: z.enum(["accepted", "no_active_turn"])
|
|
312
313
|
});
|
|
313
314
|
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/protocol/reset-session.ts
|
|
317
|
+
const ResetResponseSchema = z.discriminatedUnion("status", [z.object({
|
|
318
|
+
ok: z.literal(true),
|
|
319
|
+
previousSessionId: z.string().min(1),
|
|
320
|
+
status: z.literal("reset")
|
|
321
|
+
}), z.object({
|
|
322
|
+
ok: z.literal(true),
|
|
323
|
+
status: z.literal("no_active_session")
|
|
324
|
+
})]);
|
|
325
|
+
|
|
314
326
|
//#endregion
|
|
315
327
|
//#region src/client/output-schema.ts
|
|
316
328
|
function extractCompletedResult(events) {
|
|
@@ -696,10 +708,14 @@ var ClientSession = class {
|
|
|
696
708
|
const payload = normalizeSendTurnInput(input);
|
|
697
709
|
const state = this.#state;
|
|
698
710
|
const { continuationToken, sessionId } = await this.#postTurn(payload, state);
|
|
699
|
-
if (this.#state === state)
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
711
|
+
if (this.#state === state) {
|
|
712
|
+
const nextState = {
|
|
713
|
+
...state,
|
|
714
|
+
sessionId
|
|
715
|
+
};
|
|
716
|
+
if (continuationToken !== void 0) nextState.continuationToken = continuationToken;
|
|
717
|
+
this.#state = nextState;
|
|
718
|
+
}
|
|
703
719
|
return new MessageResponse({
|
|
704
720
|
continuationToken,
|
|
705
721
|
createStream: () => this.#createEventStream(sessionId, continuationToken, state, payload),
|
|
@@ -732,6 +748,39 @@ var ClientSession = class {
|
|
|
732
748
|
status: result.data.status
|
|
733
749
|
};
|
|
734
750
|
}
|
|
751
|
+
async reset() {
|
|
752
|
+
const state = this.#state;
|
|
753
|
+
const continuationToken = state.continuationToken;
|
|
754
|
+
if (continuationToken === void 0) {
|
|
755
|
+
if (state.sessionId !== void 0) throw new Error("Session has no continuation token. Consume its event stream before resetting.");
|
|
756
|
+
this.#state = createInitialSessionState();
|
|
757
|
+
return { status: "no_active_session" };
|
|
758
|
+
}
|
|
759
|
+
const url = createClientUrl(this.#context.host, EVE_RESET_SESSION_ROUTE_PATH);
|
|
760
|
+
const headers = await this.#context.resolveHeaders();
|
|
761
|
+
headers.set("content-type", "application/json");
|
|
762
|
+
const response = await fetch(url, withRedirectPolicy$1({
|
|
763
|
+
body: JSON.stringify({ continuationToken }),
|
|
764
|
+
headers,
|
|
765
|
+
method: "POST"
|
|
766
|
+
}, this.#context.redirect));
|
|
767
|
+
const body = await response.text();
|
|
768
|
+
if (!response.ok) throw new ClientError(response.status, body, response.headers);
|
|
769
|
+
let payload;
|
|
770
|
+
try {
|
|
771
|
+
payload = JSON.parse(body);
|
|
772
|
+
} catch {
|
|
773
|
+
throw new Error(`Reset route returned invalid JSON (${response.status}).`);
|
|
774
|
+
}
|
|
775
|
+
const result = ResetResponseSchema.safeParse(payload);
|
|
776
|
+
if (!result.success) throw new Error(`Reset route returned an invalid response (${response.status}).`);
|
|
777
|
+
if (result.data.status === "reset" && state.sessionId !== void 0 && result.data.previousSessionId !== state.sessionId) throw new Error(`Reset route returned an invalid response (${response.status}).`);
|
|
778
|
+
if (this.#state === state) this.#state = createInitialSessionState();
|
|
779
|
+
return result.data.status === "reset" ? {
|
|
780
|
+
previousSessionId: result.data.previousSessionId,
|
|
781
|
+
status: "reset"
|
|
782
|
+
} : { status: "no_active_session" };
|
|
783
|
+
}
|
|
735
784
|
stream(options) {
|
|
736
785
|
const sessionId = this.#state.sessionId;
|
|
737
786
|
if (!sessionId) throw new Error("Session has no session ID. Send a message first.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolve}from"node:path";import{resolveInternalVercelServiceOutput}from"#cli/vercel-service-output.js";import{createCliTheme,renderCliTaggedLine}from"#cli/ui/output.js";function registerBuildCommand(t){let n=createCliTheme();t.program.command(`build`).description(`Build the current eve application.`).option(`--profile <path>`,`Write best-effort timing and output-size profile JSON to a file`).option(`--skip-sandbox-prewarm`,`Skip sandbox template prewarm for a Vercel build; output may not be deployable`).action(async e=>{let{loadDevelopmentEnvironmentFiles:r}=await import(`#cli/dev/environment.js`);r(t.appRoot);let i=t.buildHost??(await import(`#internal/nitro/host.js`)).buildApplication,a=e.profile===void 0?void 0:resolve(t.appRoot,e.profile),o={skipVercelSandboxPrewarm:e.skipSandboxPrewarm===!0,vercelServiceOutput:resolveInternalVercelServiceOutput(t.appRoot)};a!==void 0&&(o.profileOutputPath=a);let s=await i(t.appRoot,o);t.logger.log(renderCliTaggedLine(n,{message:`built output at ${s}`,tag:`build`,tone:`success`}))})}export{registerBuildCommand};
|
|
1
|
+
import{resolve}from"node:path";import{resolveInternalVercelServiceOutput}from"#cli/vercel-service-output.js";import{createCliTheme,renderCliTaggedLine}from"#cli/ui/output.js";import{EVE_PUBLIC_ROUTE_PREFIX_ENV,normalizePublicRoutePrefix}from"#shared/public-route-prefix.js";function registerBuildCommand(t){let n=createCliTheme();t.program.command(`build`).description(`Build the current eve application.`).option(`--profile <path>`,`Write best-effort timing and output-size profile JSON to a file`).option(`--skip-sandbox-prewarm`,`Skip sandbox template prewarm for a Vercel build; output may not be deployable`).action(async e=>{let{loadDevelopmentEnvironmentFiles:r}=await import(`#cli/dev/environment.js`);r(t.appRoot);let i=t.buildHost??(await import(`#internal/nitro/host.js`)).buildApplication,a=e.profile===void 0?void 0:resolve(t.appRoot,e.profile),o={publicRoutePrefix:normalizePublicRoutePrefix(process.env[EVE_PUBLIC_ROUTE_PREFIX_ENV]),skipVercelSandboxPrewarm:e.skipSandboxPrewarm===!0,vercelServiceOutput:resolveInternalVercelServiceOutput(t.appRoot)};a!==void 0&&(o.profileOutputPath=a);let s=await i(t.appRoot,o);t.logger.log(renderCliTaggedLine(n,{message:`built output at ${s}`,tag:`build`,tone:`success`}))})}export{registerBuildCommand};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createCliTheme,renderCliBanner,renderCliSection}from"#cli/ui/output.js";import{inspectApplication}from"#services/inspect-application.js";function buildApplicationInfoJson(e){let{application:t,compiledState:n,messaging:r}=e;return{appRoot:t.appRoot,agentRoot:n?.project.agentRoot??null,layout:n?.project.layout??null,status:n?.metadata.status??`unavailable`,diagnostics:n?{errors:n.metadata.discovery.summary.errors,warnings:n.metadata.discovery.summary.warnings}:null,model:n?.manifest.config.model.id??null,instructions:n?.manifest.instructions?.logicalPath??null,skills:(n?.manifest.skills??[]).map(e=>e.name),tools:(n?.manifest.tools??[]).map(e=>e.name),subagents:(n?.manifest.subagents??[]).map(e=>e.name),schedules:(n?.manifest.schedules??[]).map(e=>e.name),channels:(n?.manifest.channels??[]).map(e=>e.kind===`channel`?{name:e.name,kind:e.adapterKind??null,method:e.method,urlPath:e.urlPath}:{name:e.name,kind:`disabled`,method:null,urlPath:null}),messaging:{create:r.createSessionRoutePath,continue:r.continueSessionRoutePattern,stream:r.streamRoutePattern},artifacts:n?{compiledManifest:n.paths.compiledManifestPath,discoveryManifest:n.paths.discoveryManifestPath,diagnostics:n.paths.diagnosticsPath,moduleMap:n.paths.moduleMapPath,metadata:n.paths.compileMetadataPath}:null}}function pluralize(e,t){return`${e} ${t}${e===1?``:`s`}`}function formatDiscoverySummary(e,t){return`${pluralize(e,`error`)}, ${pluralize(t,`warning`)}`}function resolveCompileTone(e){switch(e){case`ready`:return`success`;case`failed`:return`danger`;default:return`warning`}}async function printApplicationInfo(i,a,o={}){let s=await inspectApplication(a);if(o.json){i.log(JSON.stringify(buildApplicationInfoJson(s),null,2));return}let c=s.compiledState,l=s.application,u=createCliTheme(),d=[{label:`App Root`,value:l.appRoot}],f=[{label:`Workflow Build`,value:l.workflowBuildDir},{label:`Output`,value:l.outputDir}],p=[];c===null?d.push({label:`Compile`,tone:`warning`,value:`unavailable`}):(d.push({label:`Agent Root`,value:c.project.agentRoot},{label:`Layout`,value:c.project.layout},{label:`Compile`,tone:resolveCompileTone(c.metadata.status),value:c.metadata.status},{label:`Diagnostics`,tone:c.metadata.discovery.summary.errors>0?`danger`:c.metadata.discovery.summary.warnings>0?`warning`:`success`,value:formatDiscoverySummary(c.metadata.discovery.summary.errors,c.metadata.discovery.summary.warnings)},{label:`Instructions`,value:c.manifest.instructions?.logicalPath??`none`},{label:`Skills`,value:pluralize(c.manifest.skills.length,`skill`)},{label:`Subagents`,value:pluralize(c.manifest.subagents.length,`subagent`)},{label:`Schedules`,value:pluralize(c.manifest.schedules.length,`schedule`)}),f.unshift({label:`Compiled Manifest`,value:c.paths.compiledManifestPath},{label:`Discovery Manifest`,value:c.paths.discoveryManifestPath},{label:`Diagnostics`,value:c.paths.diagnosticsPath},{label:`Module Map`,value:c.paths.moduleMapPath},{label:`Metadata`,value:c.paths.compileMetadataPath}),p.push(c.manifest.instructions===void 0?{label:`Instructions`,value:`No instructions prompt discovered.`}:{label:`Instructions`,value:c.manifest.instructions.logicalPath})),i.log([renderCliBanner(u,{subtitle:`Resolved application paths and the active message contract.`,title:`eve Info`}),``,renderCliSection(u,{rows:d,title:`Application`}),``,renderCliSection(u,{rows:f,title:`Artifacts`}),...c===null?[]:[``,renderCliSection(u,{rows:p,title:`Instructions`})],``,renderCliSection(u,{rows:[{label:`Workflow ID`,value:l.workflowId},{label:`Source Dir`,value:l.workflowSourceDir},{label:`Create`,tone:`info`,value:`POST ${s.messaging.createSessionRoutePath}`},{label:`Continue`,tone:`info`,value:`POST ${s.messaging.continueSessionRoutePattern}`},{label:`Stream`,tone:`info`,value:`GET ${s.messaging.streamRoutePattern}`}],title:`Messaging`})].join(`
|
|
1
|
+
import{createCliTheme,renderCliBanner,renderCliSection}from"#cli/ui/output.js";import{inspectApplication}from"#services/inspect-application.js";function buildApplicationInfoJson(e){let{application:t,compiledState:n,messaging:r}=e;return{appRoot:t.appRoot,agentRoot:n?.project.agentRoot??null,layout:n?.project.layout??null,status:n?.metadata.status??`unavailable`,diagnostics:n?{errors:n.metadata.discovery.summary.errors,warnings:n.metadata.discovery.summary.warnings}:null,model:n?.manifest.config.model.id??null,instructions:n?.manifest.instructions?.logicalPath??null,skills:(n?.manifest.skills??[]).map(e=>e.name),tools:(n?.manifest.tools??[]).map(e=>e.name),subagents:(n?.manifest.subagents??[]).map(e=>e.name),schedules:(n?.manifest.schedules??[]).map(e=>e.name),channels:(n?.manifest.channels??[]).map(e=>e.kind===`channel`?{name:e.name,kind:e.adapterKind??null,method:e.method,urlPath:e.urlPath}:{name:e.name,kind:`disabled`,method:null,urlPath:null}),messaging:{create:r.createSessionRoutePath,continue:r.continueSessionRoutePattern,stream:r.streamRoutePattern},artifacts:n?{compiledManifest:n.paths.compiledManifestPath,discoveryManifest:n.paths.discoveryManifestPath,diagnostics:n.paths.diagnosticsPath,moduleMap:n.paths.moduleMapPath,metadata:n.paths.compileMetadataPath}:null}}function pluralize(e,t){return`${e} ${t}${e===1?``:`s`}`}function formatDiscoverySummary(e,t){return`${pluralize(e,`error`)}, ${pluralize(t,`warning`)}`}function resolveCompileTone(e){switch(e){case`ready`:return`success`;case`failed`:return`danger`;default:return`warning`}}async function printApplicationInfo(i,a,o={}){let s=await inspectApplication(a);if(o.json){i.log(JSON.stringify(buildApplicationInfoJson(s),null,2));return}let c=s.compiledState,l=s.application,u=createCliTheme(),d=[{label:`App Root`,value:l.appRoot}],f=[{label:`Workflow Build`,value:l.workflowBuildDir},{label:`Output`,value:l.outputDir}],p=[];c===null?d.push({label:`Compile`,tone:`warning`,value:`unavailable`}):(d.push({label:`Agent Root`,value:c.project.agentRoot},{label:`Layout`,value:c.project.layout},{label:`Compile`,tone:resolveCompileTone(c.metadata.status),value:c.metadata.status},{label:`Diagnostics`,tone:c.metadata.discovery.summary.errors>0?`danger`:c.metadata.discovery.summary.warnings>0?`warning`:`success`,value:formatDiscoverySummary(c.metadata.discovery.summary.errors,c.metadata.discovery.summary.warnings)},{label:`Instructions`,value:c.manifest.instructions?.logicalPath??`none`},{label:`Skills`,value:pluralize(c.manifest.skills.length,`skill`)},{label:`Tools`,value:pluralize(c.manifest.tools.length,`tool`)},{label:`Subagents`,value:pluralize(c.manifest.subagents.length,`subagent`)},{label:`Schedules`,value:pluralize(c.manifest.schedules.length,`schedule`)}),f.unshift({label:`Compiled Manifest`,value:c.paths.compiledManifestPath},{label:`Discovery Manifest`,value:c.paths.discoveryManifestPath},{label:`Diagnostics`,value:c.paths.diagnosticsPath},{label:`Module Map`,value:c.paths.moduleMapPath},{label:`Metadata`,value:c.paths.compileMetadataPath}),p.push(c.manifest.instructions===void 0?{label:`Instructions`,value:`No instructions prompt discovered.`}:{label:`Instructions`,value:c.manifest.instructions.logicalPath})),i.log([renderCliBanner(u,{subtitle:`Resolved application paths and the active message contract.`,title:`eve Info`}),``,renderCliSection(u,{rows:d,title:`Application`}),``,renderCliSection(u,{rows:f,title:`Artifacts`}),...c===null?[]:[``,renderCliSection(u,{rows:p,title:`Instructions`})],``,renderCliSection(u,{rows:[{label:`Workflow ID`,value:l.workflowId},{label:`Source Dir`,value:l.workflowSourceDir},{label:`Create`,tone:`info`,value:`POST ${s.messaging.createSessionRoutePath}`},{label:`Continue`,tone:`info`,value:`POST ${s.messaging.continueSessionRoutePattern}`},{label:`Stream`,tone:`info`,value:`GET ${s.messaging.streamRoutePattern}`}],title:`Messaging`})].join(`
|
|
2
2
|
`))}export{buildApplicationInfoJson,printApplicationInfo};
|
|
@@ -34,6 +34,13 @@ export interface Block {
|
|
|
34
34
|
subtitle?: string;
|
|
35
35
|
/** Main multi-line content (markdown for prose, plain for logs). */
|
|
36
36
|
body?: string;
|
|
37
|
+
/**
|
|
38
|
+
* User blocks only: how a mid-turn message reached the transcript. A
|
|
39
|
+
* steered message (Esc pop, displacing the running turn) marks itself
|
|
40
|
+
* with the accent arrow above its bar; a queued one (drained at the turn
|
|
41
|
+
* boundary) carries the arrow below. Absent for ordinary typed prompts.
|
|
42
|
+
*/
|
|
43
|
+
promptOrigin?: "steer" | "queue";
|
|
37
44
|
/** Reasoning trace shown above `body` (subagent steps). */
|
|
38
45
|
reasoning?: string;
|
|
39
46
|
/** One-line summarized result shown after a tool resolves. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{isPromptControlCommand}from"./prompt-commands.js";import{renderMarkdown}from"./markdown.js";import{elisionText}from"./rail.js";import{renderTool}from"./tool-rows.js";import{clipVisible,sliceVisible,visibleLength,wrapVisibleLine}from"#cli/ui/terminal-text.js";function renderBlockLines(e,t,n,r){let i=e.depth??0,a=nestingPrefix(i,n),s=renderBody(e,Math.max(8,t-visibleLength(a)),n,r);if(e.closesRail===!0&&i>0){let e=closingPrefix(i,n);return s.map((t,n)=>`${n===s.length-1?e:a}${t}`)}return s.map(e=>`${a}${e}`)}function nestingPrefix(e,t){return e<=0?``:` ${`${t.colors.dim(t.glyph.rule)} `.repeat(e)}`}function closingPrefix(e,t){return` ${`${t.colors.dim(t.glyph.rule)} `.repeat(e-1)}${t.colors.dim(t.glyph.corner)} `}function renderBody(e,t,a,o){if(e.elided!==void 0&&e.kind===`subagent-step`)return[` ${elisionText(e.elided,a)}`];switch(e.kind){case`user`:return renderUser(e,t,a);case`assistant`:case`subagent-step`:return renderProse(e,t,a);case`reasoning`:return renderReasoning(e,t,a);case`tool`:case`subagent-tool`:return renderTool(e,t,a,o);case`error`:return renderError(e,t,a);case`notice`:return renderNotice(e,t,a);case`warning`:return renderWarning(e,t,a);case`result`:return renderResult(e,t,a);case`flow`:return renderFlow(e,t,a);case`command`:return renderCommand(e,a);case`question`:case`connection-auth`:return renderPreformatted(e,t,a);case`sandbox`:return renderSandbox(e,t,a,o);case`log`:return renderLog(e,t,a);case`subagent`:return renderSubagentHeader(e,t,a);case`subagent-close`:{let n=` ${a.colors.dim(a.glyph.corner)}`;return e.body!==void 0&&e.body.length>0?[clipVisible(`${n} ${a.colors.dim(e.body)}`,Math.max(1,t))]:[n]}case`turn-stats`:return renderTurnStats(e,t,a);case`session-boundary`:case`todo-list`:case`agent-header`:return(e.body??``).split(`
|
|
2
|
-
`)}}function renderUser(e,t,n){let r=n.colors.cyan(n.glyph.user)
|
|
1
|
+
import{isPromptControlCommand}from"./prompt-commands.js";import{renderMarkdown}from"./markdown.js";import{elisionText}from"./rail.js";import{renderTool}from"./tool-rows.js";import{clipVisible,sliceVisible,visibleLength,wrapVisibleLine}from"#cli/ui/terminal-text.js";function renderBlockLines(e,t,n,r){let i=e.depth??0,a=nestingPrefix(i,n),s=renderBody(e,Math.max(8,t-visibleLength(a)),n,r);if(e.closesRail===!0&&i>0){let e=closingPrefix(i,n);return s.map((t,n)=>`${n===s.length-1?e:a}${t}`)}return s.map(e=>`${a}${e}`)}function nestingPrefix(e,t){return e<=0?``:` ${`${t.colors.dim(t.glyph.rule)} `.repeat(e)}`}function closingPrefix(e,t){return` ${`${t.colors.dim(t.glyph.rule)} `.repeat(e-1)}${t.colors.dim(t.glyph.corner)} `}function renderBody(e,t,a,o){if(e.elided!==void 0&&e.kind===`subagent-step`)return[` ${elisionText(e.elided,a)}`];switch(e.kind){case`user`:return renderUser(e,t,a);case`assistant`:case`subagent-step`:return renderProse(e,t,a);case`reasoning`:return renderReasoning(e,t,a);case`tool`:case`subagent-tool`:return renderTool(e,t,a,o);case`error`:return renderError(e,t,a);case`notice`:return renderNotice(e,t,a);case`warning`:return renderWarning(e,t,a);case`result`:return renderResult(e,t,a);case`flow`:return renderFlow(e,t,a);case`command`:return renderCommand(e,a);case`question`:case`connection-auth`:return renderPreformatted(e,t,a);case`sandbox`:return renderSandbox(e,t,a,o);case`log`:return renderLog(e,t,a);case`subagent`:return renderSubagentHeader(e,t,a,o);case`subagent-close`:{let n=` ${a.colors.dim(a.glyph.corner)}`;return e.body!==void 0&&e.body.length>0?[clipVisible(`${n} ${a.colors.dim(e.body)}`,Math.max(1,t))]:[n]}case`turn-stats`:return renderTurnStats(e,t,a);case`session-boundary`:case`todo-list`:case`agent-header`:return(e.body??``).split(`
|
|
2
|
+
`)}}function renderUser(e,t,n){let r=n.colors.cyan(n.glyph.user),i=wrap(e.body??``,t-2).map(e=>`${r} ${e}`),a=n.colors.cyan(n.glyph.arrowUp);return e.promptOrigin===`steer`?[a,...i]:e.promptOrigin===`queue`?[...i,a]:i}function renderProse(e,n,r){let i=[],o=e.kind===`subagent-step`;if(o&&e.collapsed===!0){let t=firstNonEmptyLine(e.body)??(e.reasoning===void 0?void 0:firstNonEmptyLine(e.reasoning));return t===void 0?[]:[r.colors.dim(sliceVisible(t,Math.max(1,n)))]}let c=o?``:`${r.colors.bold(r.glyph.brand)} `,l=o?``:` `;e.reasoning&&e.reasoning.trim().length>0&&i.push(...renderReasoningLines(e.reasoning,n,r));let u=(e.body??``).trim();return u.length===0&&i.length===0?[`${c}${r.colors.dim(`thinking${r.glyph.ellipsis}`)}`]:(u.length>0&&renderMarkdown(u,n-l.length).split(`
|
|
3
3
|
`).flatMap(e=>wrapVisibleLine(e,n-l.length)).forEach((e,t)=>{t===0&&!o&&i.length===0?i.push(`${c}${e}`):i.push(`${l}${e}`)}),i.length>0?i:[`${c}`])}function renderReasoning(e,t,n){return e.collapsed?[`${n.colors.gray(n.glyph.reasoning)} ${n.colors.dim(e.title??`thinking`)}`]:renderReasoningLines(e.body??``,t,n,n.glyph.reasoning)}function renderReasoningLines(e,t,n,r){let i=r?2:0,a=wrap(e.trim(),t-i);return a.length===0?[]:a.map((e,t)=>`${r?t===0?`${n.colors.gray(r)} `:` `:``}${n.colors.dim(n.colors.italic(e))}`)}function renderError(e,t,n){let r=n.colors.red(n.colors.bold(n.glyph.error)),i=e.title??`Error`,a=[`${r} ${n.colors.red(n.colors.bold(i))}`];for(let r of wrap(e.body??``,t-2))a.push(` ${colorizeError(r,n)}`);if(e.hint!==void 0&&e.hint.trim().length>0)for(let[r,i]of wrap(e.hint,t-4).entries()){let e=r===0?`${n.glyph.arrow} `:` `;a.push(` ${n.colors.cyan(`${e}${i}`)}`)}return a.push(...renderErrorDetail(e.detail,t,n)),a}function renderErrorDetail(e,t,n){if(e===void 0||e.trim().length===0)return[];let r=e.split(`
|
|
4
4
|
`),i=r.slice(0,12),a=i.map(e=>` ${n.colors.dim(truncatePlain(e,Math.max(1,t-2)))}`),o=r.length-i.length;return o>0&&a.push(` ${n.colors.dim(`${n.glyph.ellipsis} +${o} more line${o===1?``:`s`}`)}`),a}const URL_PATTERN=/(https?:\/\/\S+)/u;function colorizeError(e,t){return URL_PATTERN.test(e)?e.split(URL_PATTERN).map((e,n)=>n%2==1?t.colors.cyan(e):t.colors.red(e)).join(``):t.colors.red(e)}function renderNotice(e,t,n){let r=n.colors.dim(n.glyph.dot),i=wrap(e.body??``,t-2);return i.length===0?[r]:i.map(e=>`${r} ${n.colors.dim(e)}`)}function renderAttentionRows(e,t,n){let r=n.colors.yellow(n.glyph.warning);return wrap(e,t-2).map((e,t)=>`${t===0?r:` `} ${paintCommands(e,n)}`)}function renderWarning(e,t,n){return renderAttentionRows(e.body??``,t,n)}function paintCommands(t,n){return t.replace(/\/[a-z:-]+/g,t=>isPromptControlCommand(t)?n.colors.blue(t):t)}function renderCommand(e,t){let n=t.colors,r=e.status===`error`?`${n.red(t.glyph.error)} `:``;return[`${n.cyan(t.glyph.user)} ${r}${n.blue(e.body??``)}`]}function renderFlow(e,t,n){let r=n.colors,i=e.title??`info`,a=i===`success`?r.green(n.glyph.success):i===`warning`?r.yellow(n.glyph.warning):i===`error`?r.red(n.glyph.error):r.dim(n.glyph.dot),o=wrap(e.body??``,t-2),paint=e=>i===`info`?r.dim(e):e;return o.map((e,t)=>`${t===0?a:` `} ${paint(e)}`)}function renderResult(e,t,n){let r=n.colors.dim(n.glyph.elbow),i=wrap(e.body??``,t-7);if(i.length===0)return[` ${r}`];let dim=e=>n.colors.dim(e.replaceAll(`\x1B[22m`,`\x1B[22m\x1B[2m`));return i.map((e,t)=>t===0?` ${r} ${dim(e)}`:` ${dim(e)}`)}function renderPreformatted(e,t,n){let r=e.kind===`connection-auth`?n.colors.yellow(n.glyph.connection):n.colors.yellow(n.colors.bold(n.glyph.question)),i=e.kind===`question`?` `:` `,a=wrap(e.title??``,t-2),o=a.length===0?[`${r} `]:a.map((e,t)=>t===0?`${r} ${n.colors.bold(e)}`:` ${n.colors.bold(e)}`);for(let n of(e.body??``).split(`
|
|
5
5
|
`))for(let e of wrapVisibleLine(n,Math.max(1,t-i.length)))o.push(`${i}${e}`);return o}function renderSandbox(e,t,n,r){let i=n.colors.cyan(n.glyph.rule),a=n.colors.dim(`sandbox ${n.glyph.dot} `),c=visibleLength(a),l=` `.repeat(c),u=r.previous?.kind===`sandbox`,d=(e.body??``).split(`
|
|
6
6
|
`),f=[];for(let e of d){let r=wrapVisibleLine(e,Math.max(1,t-2-c));for(let e of r){let t=f.length===0&&!u?a:l;f.push(`${i} ${t}${n.colors.gray(e)}`)}}return f.length>0?f:[`${i}`]}function renderLog(e,t,r){let i=e.title===`stderr`,a=i?r.colors.red:r.colors.gray,o=r.colors.dim(r.glyph.rule),c=i?`stderr`:`stdout`,l=[`${r.colors.dim(r.glyph.reasoning)} ${r.colors.dim(c)}`];e.elided!==void 0&&e.elided>0&&l.push(`${o} ${elisionText(e.elided,r)}`);for(let n of(e.body??``).split(`
|
|
7
|
-
`))for(let e of wrapVisibleLine(n,Math.max(1,t-2)))l.push(`${o} ${r.colors.dim(a(e))}`);return l}function renderTurnStats(e,t,n){let r=`${n.glyph.corner} ${e.body??``}`;return[n.colors.dim(truncatePlain(r,Math.max(1,t)))]}function renderSubagentHeader(e,t,n){let
|
|
7
|
+
`))for(let e of wrapVisibleLine(n,Math.max(1,t-2)))l.push(`${o} ${r.colors.dim(a(e))}`);return l}function renderTurnStats(e,t,n){let r=`${n.glyph.corner} ${e.body??``}`;return[n.colors.dim(truncatePlain(r,Math.max(1,t)))]}function renderSubagentHeader(e,t,n,r){let i=truncatePlain(e.title===void 0||e.title===`agent`?`self`:e.title,Math.max(8,t-16)),a=e.subtitle!==void 0&&e.subtitle.startsWith(`#`),o=a?`:${e.subtitle.slice(1)}`:``,s=` ${e.status===`done`?n.colors.green(n.glyph.subagent):r.activityPulse.trim().length>0?n.colors.orange(n.glyph.subagent):n.colors.dim(n.glyph.subagent)} subagent(${i}${o})`;return!a&&e.subtitle!==void 0&&e.subtitle.length>0&&(s+=` ${n.colors.dim(e.subtitle)}`),[s]}function firstNonEmptyLine(e){return e===void 0?void 0:e.split(/\r?\n/u).find(e=>e.trim().length>0)?.trim()}function wrap(e,t){return e.trim().length===0?[]:e.split(`
|
|
8
8
|
`).flatMap(e=>wrapVisibleLine(e,Math.max(1,t)))}function truncatePlain(e,t){return visibleLength(e)<=t?e:sliceVisible(e,t)}export{renderAttentionRows,renderBlockLines};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pinned message-queue panel: client-side state and pure rendering for
|
|
3
|
+
* messages submitted while a turn is still streaming.
|
|
4
|
+
*
|
|
5
|
+
* Enter queues the draft (up to {@link MESSAGE_QUEUE_LIMIT}); each queued
|
|
6
|
+
* message waits for the turn to end, where the whole queue coalesces into
|
|
7
|
+
* the next turn's message. Esc pops the oldest message to steer the
|
|
8
|
+
* conversation instead of waiting: the renderer requests cooperative turn
|
|
9
|
+
* cancellation and the runner submits the popped message as the next turn.
|
|
10
|
+
* Esc on an empty queue arms cancellation; a second Esc cancels the turn
|
|
11
|
+
* without a replacement message.
|
|
12
|
+
*
|
|
13
|
+
* The renderer owns lifecycle (keys, cancel requests, when the runner drains
|
|
14
|
+
* the queue); this module only holds the queue state machine and paints rows.
|
|
15
|
+
*/
|
|
16
|
+
import type { Theme } from "./theme.js";
|
|
17
|
+
/** Most messages the queue holds; Enter on a full queue keeps the draft. */
|
|
18
|
+
export declare const MESSAGE_QUEUE_LIMIT = 5;
|
|
19
|
+
/** What one Esc press did to the queue state. */
|
|
20
|
+
export type MessageQueueEscapeOutcome =
|
|
21
|
+
/**
|
|
22
|
+
* A message is (now) staged for steering — the caller should request
|
|
23
|
+
* cooperative turn cancellation. Repeated presses pop further messages
|
|
24
|
+
* into the same staged steer payload and re-request cancellation.
|
|
25
|
+
*/
|
|
26
|
+
"steer"
|
|
27
|
+
/** Queue empty: cancellation armed; the next Esc cancels. */
|
|
28
|
+
| "armed"
|
|
29
|
+
/** Second Esc on an empty queue — the caller should cancel the turn. */
|
|
30
|
+
| "cancel";
|
|
31
|
+
/** Read-only projection consumed by {@link renderMessageQueueRows}. */
|
|
32
|
+
export interface MessageQueueView {
|
|
33
|
+
readonly messages: readonly string[];
|
|
34
|
+
readonly full: boolean;
|
|
35
|
+
/** A popped message is staged and turn cancellation was requested. */
|
|
36
|
+
readonly steering: boolean;
|
|
37
|
+
/** First Esc on an empty queue landed; the next one cancels. */
|
|
38
|
+
readonly armed: boolean;
|
|
39
|
+
/** Esc Esc on an empty queue landed; cancellation was requested. */
|
|
40
|
+
readonly cancelling: boolean;
|
|
41
|
+
}
|
|
42
|
+
export declare class MessageQueue {
|
|
43
|
+
#private;
|
|
44
|
+
get size(): number;
|
|
45
|
+
get full(): boolean;
|
|
46
|
+
/** True when nothing is queued, staged, armed, or cancelling. */
|
|
47
|
+
get idle(): boolean;
|
|
48
|
+
/** Queues one message; returns false (draft stays put) when full. */
|
|
49
|
+
enqueue(message: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Applies one Esc press. Pops the oldest queued message into the staged
|
|
52
|
+
* steer payload while any remain; with an empty queue, arms and then
|
|
53
|
+
* requests cancellation.
|
|
54
|
+
*/
|
|
55
|
+
handleEscape(): MessageQueueEscapeOutcome;
|
|
56
|
+
/** Any non-Esc activity backs out of the armed press-again state. */
|
|
57
|
+
disarm(): void;
|
|
58
|
+
/** Clears per-turn Esc state when a new stream starts rendering. */
|
|
59
|
+
beginTurn(): void;
|
|
60
|
+
/**
|
|
61
|
+
* The next prompt to submit after a turn boundary: the staged steer
|
|
62
|
+
* message when one exists (remaining queued messages stay queued for the
|
|
63
|
+
* steered turn), otherwise the whole queue coalesced into one message.
|
|
64
|
+
*/
|
|
65
|
+
takePrompt(): string | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Everything still held — staged steer payload plus queued messages —
|
|
68
|
+
* coalesced for restoring into the prompt editor when a turn ends without
|
|
69
|
+
* a clean boundary (interrupt, transport failure). Clears the queue.
|
|
70
|
+
*/
|
|
71
|
+
restoreDraft(): string | undefined;
|
|
72
|
+
reset(): void;
|
|
73
|
+
view(): MessageQueueView;
|
|
74
|
+
}
|
|
75
|
+
export interface MessageQueuePanelRowsInput {
|
|
76
|
+
readonly view: MessageQueueView;
|
|
77
|
+
readonly width: number;
|
|
78
|
+
readonly theme: Theme;
|
|
79
|
+
/** True while a turn streams — the only state in which Esc steers. */
|
|
80
|
+
readonly working: boolean;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Paints the pinned queue panel, indented so its marks share the tool
|
|
84
|
+
* column. Queued messages ride a `│` rail under the header (one clipped
|
|
85
|
+
* line each) and the last closes it with `└`. The header carries the Esc
|
|
86
|
+
* affordance — the panel is where steering and cancellation are taught.
|
|
87
|
+
*/
|
|
88
|
+
export declare function renderMessageQueueRows(input: MessageQueuePanelRowsInput): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./rail.js";import{clipVisible,stripTerminalControls}from"#cli/ui/terminal-text.js";const MESSAGE_QUEUE_LIMIT=5;var MessageQueue=class{#e=[];#t;#n=!1;#r=!1;get size(){return this.#e.length}get full(){return this.#e.length>=5}get idle(){return this.#e.length===0&&this.#t===void 0&&!this.#n&&!this.#r}enqueue(e){return this.full?!1:(this.#e.push(e),this.#n=!1,!0)}handleEscape(){let e=this.#e.shift();return e===void 0?this.#t===void 0?this.#n?(this.#r=!0,`cancel`):(this.#n=!0,`armed`):`steer`:(this.#t=joinMessages(this.#t,e),`steer`)}disarm(){this.#n=!1}beginTurn(){this.#n=!1,this.#r=!1}takePrompt(){this.#n=!1,this.#r=!1;let e=this.#t;return e===void 0?this.#i():(this.#t=void 0,e)}restoreDraft(){this.#n=!1,this.#r=!1;let e=this.#t;return this.#t=void 0,joinOptionalMessages(e,this.#i())}reset(){this.#e=[],this.#t=void 0,this.#n=!1,this.#r=!1}view(){return{messages:[...this.#e],full:this.full,steering:this.#t!==void 0,armed:this.#n,cancelling:this.#r}}#i(){if(this.#e.length===0)return;let e=this.#e.reduce(joinOptionalMessages,void 0);return this.#e=[],e}};function joinMessages(e,t){return e===void 0?t:`${e}\n\n${t}`}function joinOptionalMessages(e,t){return t===void 0?e:joinMessages(e,t)}function renderMessageQueueRows(n){let{view:r,width:i,theme:a,working:o}=n,s=a.colors,c=a.glyph;if(r.messages.length===0&&!r.steering)return o?r.cancelling?[clipVisible(` ${s.yellow(c.dotActive)} ${s.dim(`Cancelling turn…`)}`,i)]:r.armed?[clipVisible(` ${s.yellow(c.dotActive)} ${s.dim(`Press esc again to cancel the turn`)}`,i)]:[]:[];let l=[clipVisible(` ${s.gray(c.arrowUp)} ${headerBody(r,o,a)}`,i)];for(let[n,a]of r.messages.entries()){let o=n===r.messages.length-1?c.corner:c.rule,u=firstLine(stripTerminalControls(a));l.push(clipVisible(` ${s.dim(o)} ${s.dim(u)}`,i))}return l}function headerBody(e,t,n){let r=n.colors,i=` ${n.glyph.dot} `,a=`${String(e.messages.length)}/5`;if(e.steering){let t=e.messages.length>0?`${i}${a} still queued`:``;return r.dim(`Steering — cancelling the running turn…${t}`)}let o=e.full?`${i}queue full`:``,s=t?`${i}esc steers with the next message`:``;return`${r.bold(`Queue`)} ${r.dim(`${a}${o}${s}`)}`}function firstLine(e){return e.split(/\r?\n/u,1)[0]??``}export{MESSAGE_QUEUE_LIMIT,MessageQueue,renderMessageQueueRows};
|
|
@@ -19,6 +19,15 @@ export { parsePromptCommand, type PromptCommand } from "./prompt-commands.js";
|
|
|
19
19
|
export type AgentTUIStreamResult = {
|
|
20
20
|
events: AsyncIterable<AgentTUIStreamEvent> | ReadableStream<AgentTUIStreamEvent>;
|
|
21
21
|
abort?: () => void;
|
|
22
|
+
/**
|
|
23
|
+
* Requests cooperative server-side cancellation of the streaming turn
|
|
24
|
+
* (Esc Esc, or an Esc steer pop). Unlike {@link abort} — which drops the
|
|
25
|
+
* client stream and forces a fresh session — the server settles the turn
|
|
26
|
+
* as `turn.cancelled` → `session.waiting`, so the stream reaches its
|
|
27
|
+
* boundary normally and the session keeps its context. Best-effort and
|
|
28
|
+
* idempotent; scoped to the turn the user observed when its id is known.
|
|
29
|
+
*/
|
|
30
|
+
cancel?: () => void;
|
|
22
31
|
turnState?: AgentTUITurnState;
|
|
23
32
|
};
|
|
24
33
|
export type AgentTUIStreamUsage = {
|
|
@@ -75,6 +84,8 @@ export type AgentTUIStreamEvent = {
|
|
|
75
84
|
errorText: string;
|
|
76
85
|
hint?: string;
|
|
77
86
|
detail?: string;
|
|
87
|
+
} | {
|
|
88
|
+
type: "turn-cancelled";
|
|
78
89
|
} | {
|
|
79
90
|
type: "finish";
|
|
80
91
|
usage?: AgentTUIStreamUsage;
|
|
@@ -85,6 +96,10 @@ export type AgentTUITurnState = {
|
|
|
85
96
|
pendingApprovals: AgentTUIToolApprovalRequest[];
|
|
86
97
|
pendingQuestions: InputRequest[];
|
|
87
98
|
sawSessionFailure: boolean;
|
|
99
|
+
/** Id of the streaming turn, once `turn.started` names it. Scopes cancels. */
|
|
100
|
+
turnId?: string;
|
|
101
|
+
/** True while a cooperative-cancel request loop is running for this turn. */
|
|
102
|
+
cancelInFlight?: boolean;
|
|
88
103
|
};
|
|
89
104
|
export type AgentTUISessionOptions = {
|
|
90
105
|
title?: string;
|
|
@@ -169,6 +184,24 @@ export type AgentTUIRenderer = {
|
|
|
169
184
|
renderCommandResult?(text: string): void;
|
|
170
185
|
readonly setupFlow?: SetupFlowRenderer;
|
|
171
186
|
readPrompt?(options?: AgentTUISessionOptions): Promise<string | undefined>;
|
|
187
|
+
/**
|
|
188
|
+
* Consumes the next prompt produced by mid-turn input: the Esc-popped
|
|
189
|
+
* steering message when one is staged, otherwise every message queued
|
|
190
|
+
* during the turn coalesced into one. The runner calls this at a clean
|
|
191
|
+
* turn boundary and submits the result as the next turn without reading
|
|
192
|
+
* the prompt. Optional — renderers without mid-turn input never queue.
|
|
193
|
+
*/
|
|
194
|
+
takeQueuedPrompt?(): string | undefined;
|
|
195
|
+
/**
|
|
196
|
+
* Reports the server session id backing the conversation — pushed by the
|
|
197
|
+
* runner once a send is accepted, and overwritten when a later session's
|
|
198
|
+
* turn is accepted. Deliberately sticky across `/new` and interrupt
|
|
199
|
+
* recovery: the terminal renderer echoes the LAST session this TUI talked
|
|
200
|
+
* to in the parting line on exit, so an interrupted conversation (whose
|
|
201
|
+
* replacement session never ran a turn) can still be found again
|
|
202
|
+
* (`eve logs`, the session store). Optional.
|
|
203
|
+
*/
|
|
204
|
+
setSessionId?(sessionId: string): void;
|
|
172
205
|
readToolApproval?(request: AgentTUIToolApprovalRequest, options?: AgentTUISessionOptions): Promise<AgentTUIToolApprovalResponse>;
|
|
173
206
|
readInputQuestion?(question: AgentTUIInputQuestion, options?: AgentTUISessionOptions): Promise<AgentTUIInputQuestionResponse | undefined>;
|
|
174
207
|
renderStream(result: AgentTUIStreamResult, options?: AgentTUISessionOptions): Promise<void>;
|