@mlpal/yodex-ui 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,9 +34,14 @@ function Chat({ url, token }: { url: string; token?: string }) {
34
34
  prompts, run status, usage, session and model lists.
35
35
  - The reducer and client are framework-free (`YodexClient`, `reduce`, `applyEvent`) for hosts that
36
36
  are not React.
37
- - Styling: Tailwind utilities compiled against the Obsidian Intelligence token names
37
+ - Hosts that virtualize the transcript render `TranscriptRow` per item; `ApprovalPrompt` shows
38
+ `state.pendingApproval`; `PermissionPrompt` takes `onAllowSession` (the hook's `allowForSession`)
39
+ and opt-in `hotkeys` for the one prompt that should own `a`/`d`; the Composer steers a live run
40
+ through `onSteer` (the hook's `steer`) and takes `value`/`onChange`, `initialValue`, `hideAttach`,
41
+ `submitOnEnter` (defaults to keyboards only).
42
+ - Styling: `styles.css` carries only utilities (no preflight, no Tailwind theme on `:root`), compiled against the Obsidian Intelligence token names
38
43
  (`--background`, `--primary`, `--muted`, `--success`, `--warning`, `--info`, …). Every component
39
44
  works at 390 px width with 44 px touch targets.
40
- - Forward-compatible with serve `yodex/2`: `run{hop, cwd}`, `question_request` / `question_response`,
41
- `approval_response` and the extra `list_sessions` fields are rendered when the server sends them
42
- and ignored by a `yodex/1` server.
45
+ - `state.capabilities` mirrors `ready.capabilities` ("questions", "approval", "attachments",
46
+ "sessions_v2", …): the hook refuses attachments the server cannot take, `QuestionForm` and the
47
+ approval controls only ever show when the server asks, and a `yodex/1` server still works.
@@ -1,4 +1,4 @@
1
- import type { ClientMessage, ServerMessage } from "@mlpal/harness-protocol";
1
+ import type { ClientMessage, ContentBlock, ServerMessage } from "@mlpal/harness-protocol";
2
2
  import type { AskOutcome, ConnectionState, IncomingMessage } from "./types";
3
3
  /** The subset of the WebSocket API the client uses, so tests can inject a fake. */
4
4
  export interface SocketLike {
@@ -17,26 +17,14 @@ export interface RunOptions {
17
17
  sessionId?: string;
18
18
  model?: string;
19
19
  mode?: string;
20
- /** yodex/2: per-run HOP and working directory. Ignored by a yodex/1 server. */
20
+ /** yodex/2: the HOP for this run (must be the server's own unless it claims "hop_per_run"),
21
+ * the working directory under the workspace root, and attachments ("attachments"). */
21
22
  hop?: string;
22
23
  cwd?: string;
24
+ blocks?: ContentBlock[];
23
25
  }
24
- /** Outgoing messages: yodex/1 plus the yodex/2 additions. */
25
- export type OutgoingMessage = ClientMessage | (Extract<ClientMessage, {
26
- type: "run";
27
- }> & {
28
- hop?: string;
29
- cwd?: string;
30
- }) | {
31
- type: "question_response";
32
- id: string;
33
- outcome: AskOutcome;
34
- } | {
35
- type: "approval_response";
36
- sessionId: string;
37
- decision: "allow" | "deny";
38
- reason?: string;
39
- };
26
+ /** Outgoing messages: the protocol's ClientMessage (yodex/2, additive over yodex/1). */
27
+ export type OutgoingMessage = ClientMessage;
40
28
  export interface YodexClientOptions {
41
29
  /** ws:// or wss:// URL of `yodex serve`'s /ws endpoint. */
42
30
  url: string;
@@ -121,5 +109,11 @@ export declare class YodexClient {
121
109
  requestId?: string;
122
110
  sessionId: string;
123
111
  messages: import("@mlpal/harness-protocol").SDKMessage[];
112
+ pendingQuestion?: {
113
+ id: string;
114
+ questions: import("@mlpal/harness-protocol").AskQuestion[];
115
+ };
116
+ pendingApproval?: import("@mlpal/harness-protocol").PendingApproval;
117
+ live?: boolean;
124
118
  }>;
125
119
  }
@@ -1,4 +1,5 @@
1
- import type { Author, ContentBlock, ResultEvent, ServerMessage, SessionSummary, ToolResultBlocks, Usage } from "@mlpal/harness-protocol";
1
+ import type { AskQuestion, Author, ContentBlock, PendingApproval, ResultEvent, ServerCapability, ServerMessage, SessionSummary, ToolResultBlocks, Usage } from "@mlpal/harness-protocol";
2
+ export type { AskAnswer, AskOption, AskOutcome, AskQuestion, PendingApproval, ServerCapability } from "@mlpal/harness-protocol";
2
3
  /** One rendered row of a session's transcript. Built by the reducer from the wire events. */
3
4
  export type TranscriptItem = {
4
5
  kind: "user";
@@ -58,28 +59,9 @@ export interface PendingPermission {
58
59
  sessionId: string;
59
60
  toolName: string;
60
61
  input: Record<string, unknown>;
61
- }
62
- /** The AskUserQuestion schema (harness tools/builtin/ask.ts), mirrored here until the wire
63
- * protocol carries it. `question_request` / `question_response` land with serve yodex/2. */
64
- export interface AskOption {
65
- label: string;
66
- description?: string;
67
- preview?: string;
68
- }
69
- export interface AskQuestion {
70
- question: string;
71
- header?: string;
72
- multiSelect?: boolean;
73
- options?: AskOption[];
74
- }
75
- export interface AskAnswer {
76
- question: string;
77
- answers: string[];
78
- note?: string;
79
- }
80
- export interface AskOutcome {
81
- declined: boolean;
82
- answers: AskAnswer[];
62
+ /** Why the policy asked (yodex/2): a mode rule, or a HOP safety reason code. */
63
+ reason?: string;
64
+ safetyReason?: string;
83
65
  }
84
66
  export interface PendingQuestion {
85
67
  id: string;
@@ -90,6 +72,15 @@ export type ConnectionState = "connecting" | "connected" | "reconnecting" | "clo
90
72
  export interface SessionState {
91
73
  connection: ConnectionState;
92
74
  protocol?: string;
75
+ /** What the server serves beyond yodex/1 (ready.capabilities); empty on a yodex/1 server. */
76
+ capabilities: ServerCapability[];
77
+ /** The server's HOP and workspace root, from ready. */
78
+ hop?: string;
79
+ rootCwd?: string;
80
+ /** A run is streaming on this session on another connection (load_session.live). */
81
+ liveElsewhere?: boolean;
82
+ /** The last run parked at the safety edge; `approve` resumes it. */
83
+ pendingApproval?: PendingApproval;
93
84
  sessionId?: string;
94
85
  model?: string;
95
86
  cwd?: string;
@@ -114,17 +105,8 @@ export interface SessionState {
114
105
  /** The last transport or server error, cleared on the next run. */
115
106
  error?: string;
116
107
  }
117
- /** Forward-compatible view of the server stream: yodex/1 messages plus the yodex/2 additions
118
- * this package already renders (question round-trips). Unknown messages are ignored. */
119
- export type IncomingMessage = ServerMessage | {
120
- type: "question_request";
121
- id: string;
122
- sessionId: string;
123
- questions: AskQuestion[];
124
- } | {
125
- type: "question_cleared";
126
- id: string;
127
- };
108
+ /** The server stream (yodex/2, additive over yodex/1). Unknown messages are ignored. */
109
+ export type IncomingMessage = ServerMessage;
128
110
  export type SessionAction = {
129
111
  type: "connection";
130
112
  state: ConnectionState;
@@ -26,6 +26,12 @@ export interface YodexSession {
26
26
  deny: (permissionId: string, reason?: string) => void;
27
27
  answer: (questionId: string, outcome: AskOutcome) => void;
28
28
  approve: (decision: "allow" | "deny", reason?: string) => void;
29
+ /** Allow this tool (and, for Bash, this exact command) without asking again while this hook lives. */
30
+ allowForSession: (permissionId: string) => void;
31
+ /** Steer a running session: the text reaches the agent at its next turn boundary. */
32
+ steer: (text: string) => void;
33
+ /** What the run is waiting on, for the status row. */
34
+ waitingOn?: "permission" | "question";
29
35
  loadSession: (sessionId: string) => Promise<void>;
30
36
  refreshSessions: () => Promise<void>;
31
37
  refreshModels: () => Promise<void>;
@@ -0,0 +1,7 @@
1
+ import type { PendingApproval } from "@mlpal/harness-protocol";
2
+ /** The last run stopped at the HOP's safety edge (needs_approval). Approve to resume, or decline. */
3
+ export declare function ApprovalPrompt({ approval, onDecide, className }: {
4
+ approval: PendingApproval;
5
+ onDecide: (decision: "allow" | "deny", reason?: string) => void;
6
+ className?: string;
7
+ }): import("react").JSX.Element;
@@ -13,9 +13,18 @@ export interface HopOption {
13
13
  }
14
14
  export interface ComposerProps {
15
15
  onSubmit: (text: string, attachments: ComposerAttachment[]) => void;
16
- /** While a run is live the send button becomes Stop. */
16
+ /** While a run is live the send button becomes Stop; with `onSteer`, typed text sends as a steer instead. */
17
17
  running?: boolean;
18
18
  onStop?: () => void;
19
+ onSteer?: (text: string) => void;
20
+ /** Controlled text (with onChange), or `initialValue` to prefill once. */
21
+ value?: string;
22
+ onChange?: (text: string) => void;
23
+ initialValue?: string;
24
+ /** Enter sends on keyboards; on touch devices the send button does (Shift+Enter always breaks a line).
25
+ * Force one behaviour with true / false. */
26
+ submitOnEnter?: boolean;
27
+ hideAttach?: boolean;
19
28
  disabled?: boolean;
20
29
  /** Shown as a placeholder reason while disabled, e.g. "Workspace is waking up". */
21
30
  disabledReason?: string;
@@ -29,9 +38,4 @@ export interface ComposerProps {
29
38
  className?: string;
30
39
  autoFocus?: boolean;
31
40
  }
32
- /**
33
- * The input: a growing textarea, Enter sends and Shift+Enter breaks a line, images paste or
34
- * drop in as attachments, a HOP picker slot, Stop while running, and an outside-controlled
35
- * disabled state for a sleeping workspace.
36
- */
37
- export declare function Composer({ onSubmit, running, onStop, disabled, disabledReason, placeholder, hopOptions, hop, onHopChange, leading, maxAttachmentBytes, className, autoFocus }: ComposerProps): import("react").JSX.Element;
41
+ export declare function Composer({ onSubmit, running, onStop, onSteer, value, onChange, initialValue, submitOnEnter, hideAttach, disabled, disabledReason, placeholder, hopOptions, hop, onHopChange, leading, maxAttachmentBytes, className, autoFocus }: ComposerProps): import("react").JSX.Element;
@@ -1,9 +1,14 @@
1
1
  import type { PendingPermission } from "../client/types";
2
2
  export declare function permissionSummary(p: PendingPermission): string;
3
- /** The engine asks before a tool runs. Allow / Deny, with `a` / `d` as keys while focused. */
4
- export declare function PermissionPrompt({ request, onAllow, onDeny, className }: {
3
+ export interface PermissionPromptProps {
5
4
  request: PendingPermission;
6
5
  onAllow: (id: string) => void;
7
6
  onDeny: (id: string, reason?: string) => void;
7
+ /** Shown as a third choice when given (the hook's allowForSession). */
8
+ onAllowSession?: (id: string) => void;
9
+ /** `a` / `d` answer this prompt while focus is outside an input. Enable on one prompt at a time. */
10
+ hotkeys?: boolean;
8
11
  className?: string;
9
- }): import("react").JSX.Element;
12
+ }
13
+ /** The engine asks before a tool runs: Allow, Allow for this session, or Deny with an optional reason. */
14
+ export declare function PermissionPrompt({ request, onAllow, onDeny, onAllowSession, hotkeys, className }: PermissionPromptProps): import("react").JSX.Element;
@@ -4,6 +4,8 @@ export interface RunStatusProps {
4
4
  running: boolean;
5
5
  startedAt?: number;
6
6
  activity?: string;
7
+ /** The run is waiting on the person; shown instead of the activity. */
8
+ waitingOn?: "permission" | "question";
7
9
  lastResult?: ResultEvent;
8
10
  usageTotal?: Usage;
9
11
  connection?: ConnectionState;
@@ -14,4 +16,4 @@ export interface RunStatusProps {
14
16
  now?: () => number;
15
17
  }
16
18
  /** One row: what the agent is doing with elapsed time while running; the result line after. */
17
- export declare function RunStatus({ running, startedAt, activity, lastResult, usageTotal, connection, onStop, onApprove, className, now }: RunStatusProps): import("react").JSX.Element | null;
19
+ export declare function RunStatus({ running, startedAt, activity, waitingOn, lastResult, usageTotal, connection, onStop, onApprove, className, now }: RunStatusProps): import("react").JSX.Element | null;
@@ -1,14 +1,7 @@
1
1
  import type { SessionSummary } from "@mlpal/harness-protocol";
2
- /** A yodex/1 SessionSummary plus the yodex/2 fields when the server sends them. */
3
- export interface SessionRow extends SessionSummary {
4
- name?: string;
5
- hop?: string;
6
- cwd?: string;
7
- relCwd?: string;
8
- projectName?: string;
9
- lastMessagePreview?: string;
10
- pendingQuestion?: boolean;
11
- }
2
+ /** A SessionSummary; the yodex/2 fields (hop, relCwd, projectName, …) are present when the
3
+ * server claims "sessions_v2". */
4
+ export type SessionRow = SessionSummary;
12
5
  export declare function groupSessions(rows: SessionRow[]): Array<{
13
6
  project: string;
14
7
  rows: SessionRow[];
@@ -9,4 +9,11 @@ export interface TranscriptProps {
9
9
  emptyState?: React.ReactNode;
10
10
  className?: string;
11
11
  }
12
+ /** One transcript row. Exported so a host that virtualizes the list renders the same markup. */
13
+ export declare function TranscriptRow({ item, onOpenFile }: {
14
+ item: TranscriptItem;
15
+ onOpenFile?: (path: string) => void;
16
+ }): import("react").JSX.Element;
17
+ /** The whole transcript in a scrolling column. For long sessions a host may virtualize with
18
+ * TranscriptRow instead; announcements belong to RunStatus, not to the log itself. */
12
19
  export declare function Transcript({ items, autoScroll, onOpenFile, footer, emptyState, className }: TranscriptProps): import("react").JSX.Element;
package/dist/index.d.ts CHANGED
@@ -2,11 +2,12 @@ export * from "./client/types";
2
2
  export { YodexClient, type YodexClientOptions, type RunOptions, type SocketLike, type SocketFactory, type OutgoingMessage } from "./client/client";
3
3
  export { reduce, applyEvent, initialState, elapsedLabel, resultLabel, formatTokens } from "./client/reducer";
4
4
  export { useYodexSession, type UseYodexSessionOptions, type YodexSession, type Attachment } from "./client/useYodexSession";
5
- export { Transcript, type TranscriptProps } from "./components/Transcript";
5
+ export { Transcript, TranscriptRow, type TranscriptProps } from "./components/Transcript";
6
6
  export { AssistantMessage } from "./components/AssistantMessage";
7
7
  export { ToolCall, toolSummary } from "./components/ToolCall";
8
8
  export { DiffView, diffCounts } from "./components/DiffView";
9
- export { PermissionPrompt, permissionSummary } from "./components/PermissionPrompt";
9
+ export { PermissionPrompt, permissionSummary, type PermissionPromptProps } from "./components/PermissionPrompt";
10
+ export { ApprovalPrompt } from "./components/ApprovalPrompt";
10
11
  export { QuestionForm, buildOutcome, type QuestionDraft } from "./components/QuestionForm";
11
12
  export { PlanChecklist } from "./components/PlanChecklist";
12
13
  export { RunStatus } from "./components/RunStatus";