@mlpal/yodex-ui 0.0.0-stage → 0.2.0
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 +41 -2
- package/dist/client/client.d.ts +119 -0
- package/dist/client/reducer.d.ts +16 -0
- package/dist/client/types.d.ts +137 -0
- package/dist/client/useYodexSession.d.ts +35 -0
- package/dist/components/AssistantMessage.d.ts +9 -0
- package/dist/components/Composer.d.ts +37 -0
- package/dist/components/DiffView.d.ts +9 -0
- package/dist/components/PermissionPrompt.d.ts +9 -0
- package/dist/components/PlanChecklist.d.ts +6 -0
- package/dist/components/QuestionForm.d.ts +19 -0
- package/dist/components/RunStatus.d.ts +17 -0
- package/dist/components/SessionList.d.ts +15 -0
- package/dist/components/ToolCall.d.ts +13 -0
- package/dist/components/Transcript.d.ts +12 -0
- package/dist/components/cn.d.ts +2 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +1598 -0
- package/dist/index.js.map +23 -0
- package/dist/styles.css +2 -0
- package/dist/theme.css +60 -0
- package/package.json +41 -4
package/README.md
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mlpal/yodex-ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React components and a session hook for yodex frontends, over the `yodex serve` wire protocol
|
|
4
|
+
(`@mlpal/harness-protocol`). One engine, many frontends: the workspace UI, the VS Code panel and
|
|
5
|
+
the serve web page all render the same events with these.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { useYodexSession, Transcript, Composer, RunStatus, PermissionPrompt, QuestionForm, SessionList } from "@mlpal/yodex-ui";
|
|
9
|
+
import "@mlpal/yodex-ui/styles.css"; // utilities, mapped onto your tokens
|
|
10
|
+
import "@mlpal/yodex-ui/theme.css"; // the default tokens; skip if your app defines them
|
|
11
|
+
|
|
12
|
+
function Chat({ url, token }: { url: string; token?: string }) {
|
|
13
|
+
const s = useYodexSession({ url, token, run: { mode: "cruise" } });
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex h-full flex-col">
|
|
16
|
+
<Transcript
|
|
17
|
+
items={s.state.items}
|
|
18
|
+
footer={
|
|
19
|
+
<>
|
|
20
|
+
{s.state.pendingPermissions.map((p) => <PermissionPrompt key={p.id} request={p} onAllow={s.allow} onDeny={s.deny} />)}
|
|
21
|
+
{s.state.pendingQuestion && <QuestionForm request={s.state.pendingQuestion} onSubmit={s.answer} onDecline={(id) => s.answer(id, { declined: true, answers: [] })} />}
|
|
22
|
+
<RunStatus running={s.state.running} startedAt={s.state.runStartedAt} activity={s.state.activity} lastResult={s.state.lastResult} usageTotal={s.state.usageTotal} connection={s.state.connection} onStop={s.stop} onApprove={s.approve} />
|
|
23
|
+
</>
|
|
24
|
+
}
|
|
25
|
+
/>
|
|
26
|
+
<Composer onSubmit={(text, attachments) => s.run(text, attachments)} running={s.state.running} onStop={s.stop} />
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- `useYodexSession` owns one socket (reconnect with jittered backoff, request-id correlation) and
|
|
33
|
+
reduces the stream into `SessionState`: transcript rows, plan, pending permission and question
|
|
34
|
+
prompts, run status, usage, session and model lists.
|
|
35
|
+
- The reducer and client are framework-free (`YodexClient`, `reduce`, `applyEvent`) for hosts that
|
|
36
|
+
are not React.
|
|
37
|
+
- Styling: Tailwind utilities compiled against the Obsidian Intelligence token names
|
|
38
|
+
(`--background`, `--primary`, `--muted`, `--success`, `--warning`, `--info`, …). Every component
|
|
39
|
+
works at 390 px width with 44 px touch targets.
|
|
40
|
+
- `state.capabilities` mirrors `ready.capabilities` ("questions", "approval", "attachments",
|
|
41
|
+
"sessions_v2", …): the hook refuses attachments the server cannot take, `QuestionForm` and the
|
|
42
|
+
approval controls only ever show when the server asks, and a `yodex/1` server still works.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ClientMessage, ContentBlock, ServerMessage } from "@mlpal/harness-protocol";
|
|
2
|
+
import type { AskOutcome, ConnectionState, IncomingMessage } from "./types";
|
|
3
|
+
/** The subset of the WebSocket API the client uses, so tests can inject a fake. */
|
|
4
|
+
export interface SocketLike {
|
|
5
|
+
readyState: number;
|
|
6
|
+
send(data: string): void;
|
|
7
|
+
close(): void;
|
|
8
|
+
onopen: ((ev: unknown) => void) | null;
|
|
9
|
+
onclose: ((ev: unknown) => void) | null;
|
|
10
|
+
onerror: ((ev: unknown) => void) | null;
|
|
11
|
+
onmessage: ((ev: {
|
|
12
|
+
data: unknown;
|
|
13
|
+
}) => void) | null;
|
|
14
|
+
}
|
|
15
|
+
export type SocketFactory = (url: string) => SocketLike;
|
|
16
|
+
export interface RunOptions {
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
mode?: string;
|
|
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"). */
|
|
22
|
+
hop?: string;
|
|
23
|
+
cwd?: string;
|
|
24
|
+
blocks?: ContentBlock[];
|
|
25
|
+
}
|
|
26
|
+
/** Outgoing messages: the protocol's ClientMessage (yodex/2, additive over yodex/1). */
|
|
27
|
+
export type OutgoingMessage = ClientMessage;
|
|
28
|
+
export interface YodexClientOptions {
|
|
29
|
+
/** ws:// or wss:// URL of `yodex serve`'s /ws endpoint. */
|
|
30
|
+
url: string;
|
|
31
|
+
/** Appended as ?token= when the proxy does not inject Authorization on the upgrade. */
|
|
32
|
+
token?: string;
|
|
33
|
+
onMessage: (m: IncomingMessage) => void;
|
|
34
|
+
onState?: (s: ConnectionState) => void;
|
|
35
|
+
socketFactory?: SocketFactory;
|
|
36
|
+
/** Reconnect backoff bounds, ms. */
|
|
37
|
+
backoffMs?: {
|
|
38
|
+
min: number;
|
|
39
|
+
max: number;
|
|
40
|
+
};
|
|
41
|
+
requestTimeoutMs?: number;
|
|
42
|
+
/** Injected for deterministic tests. */
|
|
43
|
+
setTimer?: (fn: () => void, ms: number) => unknown;
|
|
44
|
+
clearTimer?: (h: unknown) => void;
|
|
45
|
+
random?: () => number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A yodex serve client: one socket per session view, reconnect with jittered exponential
|
|
49
|
+
* backoff, and request-id correlation for the list_* / load_session round-trips. Everything
|
|
50
|
+
* streamed (events, permission and question requests, done, error) goes to `onMessage`;
|
|
51
|
+
* the reducer in ./reducer turns it into view state.
|
|
52
|
+
*/
|
|
53
|
+
export declare class YodexClient {
|
|
54
|
+
private socket;
|
|
55
|
+
private closedByUser;
|
|
56
|
+
private attempt;
|
|
57
|
+
private reconnectHandle;
|
|
58
|
+
private readonly pending;
|
|
59
|
+
private readonly opts;
|
|
60
|
+
private seq;
|
|
61
|
+
state: ConnectionState;
|
|
62
|
+
constructor(options: YodexClientOptions);
|
|
63
|
+
get url(): string;
|
|
64
|
+
connect(): void;
|
|
65
|
+
close(): void;
|
|
66
|
+
private setState;
|
|
67
|
+
private open;
|
|
68
|
+
private scheduleReconnect;
|
|
69
|
+
private failPending;
|
|
70
|
+
get connected(): boolean;
|
|
71
|
+
send(m: OutgoingMessage): void;
|
|
72
|
+
private requestId;
|
|
73
|
+
/** Send a request carrying a requestId and resolve with the server's correlated reply. */
|
|
74
|
+
request<T extends ServerMessage["type"]>(m: OutgoingMessage & {
|
|
75
|
+
requestId: string;
|
|
76
|
+
}, _expect: T): Promise<Extract<ServerMessage, {
|
|
77
|
+
type: T;
|
|
78
|
+
}>>;
|
|
79
|
+
/** Start a turn. Returns the requestId the matching `done` will carry. */
|
|
80
|
+
run(prompt: string, options?: RunOptions): string;
|
|
81
|
+
interrupt(): void;
|
|
82
|
+
sendPeer(sessionId: string, text: string): void;
|
|
83
|
+
respondPermission(id: string, decision: "allow" | "deny", reason?: string): void;
|
|
84
|
+
respondQuestion(id: string, outcome: AskOutcome): void;
|
|
85
|
+
respondApproval(sessionId: string, decision: "allow" | "deny", reason?: string): void;
|
|
86
|
+
listSessions(): Promise<{
|
|
87
|
+
type: "sessions";
|
|
88
|
+
requestId?: string;
|
|
89
|
+
sessions: import("@mlpal/harness-protocol").SessionSummary[];
|
|
90
|
+
}>;
|
|
91
|
+
listModels(): Promise<{
|
|
92
|
+
type: "models";
|
|
93
|
+
requestId?: string;
|
|
94
|
+
models: Array<{
|
|
95
|
+
tag: string;
|
|
96
|
+
displayName: string;
|
|
97
|
+
}>;
|
|
98
|
+
}>;
|
|
99
|
+
listSkills(): Promise<{
|
|
100
|
+
type: "skills";
|
|
101
|
+
requestId?: string;
|
|
102
|
+
skills: Array<{
|
|
103
|
+
name: string;
|
|
104
|
+
description: string;
|
|
105
|
+
}>;
|
|
106
|
+
}>;
|
|
107
|
+
loadSession(sessionId: string): Promise<{
|
|
108
|
+
type: "session_history";
|
|
109
|
+
requestId?: string;
|
|
110
|
+
sessionId: string;
|
|
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;
|
|
118
|
+
}>;
|
|
119
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EngineEvent, ResultEvent, Usage } from "@mlpal/harness-protocol";
|
|
2
|
+
import type { SessionAction, SessionState } from "./types";
|
|
3
|
+
export declare function initialState(): SessionState;
|
|
4
|
+
/**
|
|
5
|
+
* Apply one engine event. `live` distinguishes the running stream from a history replay:
|
|
6
|
+
* a human turn on the live stream was already echoed locally by `local_user`, so it is
|
|
7
|
+
* skipped there and rendered only from history.
|
|
8
|
+
*/
|
|
9
|
+
export declare function applyEvent(state: SessionState, ev: EngineEvent, live: boolean): SessionState;
|
|
10
|
+
export declare function reduce(state: SessionState, action: SessionAction): SessionState;
|
|
11
|
+
export declare function elapsedLabel(ms: number): string;
|
|
12
|
+
export declare function resultLabel(r: ResultEvent): {
|
|
13
|
+
label: string;
|
|
14
|
+
tone: "ok" | "bad" | "warn" | "muted";
|
|
15
|
+
};
|
|
16
|
+
export declare function formatTokens(u: Usage): string;
|
|
@@ -0,0 +1,137 @@
|
|
|
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";
|
|
3
|
+
/** One rendered row of a session's transcript. Built by the reducer from the wire events. */
|
|
4
|
+
export type TranscriptItem = {
|
|
5
|
+
kind: "user";
|
|
6
|
+
id: string;
|
|
7
|
+
text: string;
|
|
8
|
+
author: Author;
|
|
9
|
+
ts: string;
|
|
10
|
+
blocks?: ContentBlock[];
|
|
11
|
+
} | {
|
|
12
|
+
kind: "assistant";
|
|
13
|
+
id: string;
|
|
14
|
+
text: string;
|
|
15
|
+
ts: string;
|
|
16
|
+
live: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "tool";
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
input: Record<string, unknown>;
|
|
22
|
+
ts: string;
|
|
23
|
+
result?: ToolOutcome;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "plan";
|
|
26
|
+
id: "plan";
|
|
27
|
+
items: PlanItem[];
|
|
28
|
+
} | {
|
|
29
|
+
kind: "result";
|
|
30
|
+
id: string;
|
|
31
|
+
result: ResultEvent;
|
|
32
|
+
} | {
|
|
33
|
+
kind: "error";
|
|
34
|
+
id: string;
|
|
35
|
+
message: string;
|
|
36
|
+
ts: string;
|
|
37
|
+
} | {
|
|
38
|
+
kind: "compact";
|
|
39
|
+
id: string;
|
|
40
|
+
replacedMessages: number;
|
|
41
|
+
ts: string;
|
|
42
|
+
};
|
|
43
|
+
export interface ToolOutcome {
|
|
44
|
+
content: string;
|
|
45
|
+
isError: boolean;
|
|
46
|
+
diff?: string;
|
|
47
|
+
blocks?: ToolResultBlocks;
|
|
48
|
+
}
|
|
49
|
+
export type PlanStatus = "pending" | "in_progress" | "completed";
|
|
50
|
+
export interface PlanItem {
|
|
51
|
+
/** The tracker's `#id` once its TaskCreate result names it; the tool_use id before that. */
|
|
52
|
+
id: string;
|
|
53
|
+
subject: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
status: PlanStatus;
|
|
56
|
+
}
|
|
57
|
+
export interface PendingPermission {
|
|
58
|
+
id: string;
|
|
59
|
+
sessionId: string;
|
|
60
|
+
toolName: string;
|
|
61
|
+
input: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
export interface PendingQuestion {
|
|
64
|
+
id: string;
|
|
65
|
+
sessionId: string;
|
|
66
|
+
questions: AskQuestion[];
|
|
67
|
+
}
|
|
68
|
+
export type ConnectionState = "connecting" | "connected" | "reconnecting" | "closed";
|
|
69
|
+
export interface SessionState {
|
|
70
|
+
connection: ConnectionState;
|
|
71
|
+
protocol?: string;
|
|
72
|
+
/** What the server serves beyond yodex/1 (ready.capabilities); empty on a yodex/1 server. */
|
|
73
|
+
capabilities: ServerCapability[];
|
|
74
|
+
/** The server's HOP and workspace root, from ready. */
|
|
75
|
+
hop?: string;
|
|
76
|
+
rootCwd?: string;
|
|
77
|
+
/** A run is streaming on this session on another connection (load_session.live). */
|
|
78
|
+
liveElsewhere?: boolean;
|
|
79
|
+
/** The last run parked at the safety edge; `approve` resumes it. */
|
|
80
|
+
pendingApproval?: PendingApproval;
|
|
81
|
+
sessionId?: string;
|
|
82
|
+
model?: string;
|
|
83
|
+
cwd?: string;
|
|
84
|
+
items: TranscriptItem[];
|
|
85
|
+
running: boolean;
|
|
86
|
+
runStartedAt?: number;
|
|
87
|
+
/** What the agent is doing right now, for the status row: a tool name or "Thinking". */
|
|
88
|
+
activity?: string;
|
|
89
|
+
lastResult?: ResultEvent;
|
|
90
|
+
usageTotal: Usage;
|
|
91
|
+
pendingPermissions: PendingPermission[];
|
|
92
|
+
pendingQuestion?: PendingQuestion;
|
|
93
|
+
sessions: SessionSummary[];
|
|
94
|
+
models: Array<{
|
|
95
|
+
tag: string;
|
|
96
|
+
displayName: string;
|
|
97
|
+
}>;
|
|
98
|
+
skills: Array<{
|
|
99
|
+
name: string;
|
|
100
|
+
description: string;
|
|
101
|
+
}>;
|
|
102
|
+
/** The last transport or server error, cleared on the next run. */
|
|
103
|
+
error?: string;
|
|
104
|
+
}
|
|
105
|
+
/** The server stream (yodex/2, additive over yodex/1). Unknown messages are ignored. */
|
|
106
|
+
export type IncomingMessage = ServerMessage;
|
|
107
|
+
export type SessionAction = {
|
|
108
|
+
type: "connection";
|
|
109
|
+
state: ConnectionState;
|
|
110
|
+
} | {
|
|
111
|
+
type: "server";
|
|
112
|
+
message: IncomingMessage;
|
|
113
|
+
} | {
|
|
114
|
+
type: "history";
|
|
115
|
+
sessionId: string;
|
|
116
|
+
messages: unknown[];
|
|
117
|
+
} | {
|
|
118
|
+
type: "local_user";
|
|
119
|
+
text: string;
|
|
120
|
+
ts: string;
|
|
121
|
+
blocks?: ContentBlock[];
|
|
122
|
+
} | {
|
|
123
|
+
type: "run_started";
|
|
124
|
+
sessionId?: string;
|
|
125
|
+
at: number;
|
|
126
|
+
} | {
|
|
127
|
+
type: "permission_answered";
|
|
128
|
+
id: string;
|
|
129
|
+
} | {
|
|
130
|
+
type: "question_answered";
|
|
131
|
+
id: string;
|
|
132
|
+
} | {
|
|
133
|
+
type: "error";
|
|
134
|
+
message: string;
|
|
135
|
+
} | {
|
|
136
|
+
type: "clear";
|
|
137
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type RunOptions, type SocketFactory, YodexClient } from "./client";
|
|
2
|
+
import type { AskOutcome, SessionState } from "./types";
|
|
3
|
+
export interface UseYodexSessionOptions {
|
|
4
|
+
url: string;
|
|
5
|
+
token?: string;
|
|
6
|
+
/** Load this session's history on connect and run turns against it. */
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
/** Default run options (model, mode, hop, cwd) applied to every turn. */
|
|
9
|
+
run?: RunOptions;
|
|
10
|
+
autoConnect?: boolean;
|
|
11
|
+
socketFactory?: SocketFactory;
|
|
12
|
+
}
|
|
13
|
+
export interface Attachment {
|
|
14
|
+
name: string;
|
|
15
|
+
mediaType: string;
|
|
16
|
+
/** base64 payload, no data: prefix. */
|
|
17
|
+
data: string;
|
|
18
|
+
}
|
|
19
|
+
export interface YodexSession {
|
|
20
|
+
state: SessionState;
|
|
21
|
+
client: YodexClient;
|
|
22
|
+
/** Send a turn. Attachments ride as image blocks once the server speaks yodex/2. */
|
|
23
|
+
run: (text: string, attachments?: Attachment[], options?: RunOptions) => void;
|
|
24
|
+
stop: () => void;
|
|
25
|
+
allow: (permissionId: string) => void;
|
|
26
|
+
deny: (permissionId: string, reason?: string) => void;
|
|
27
|
+
answer: (questionId: string, outcome: AskOutcome) => void;
|
|
28
|
+
approve: (decision: "allow" | "deny", reason?: string) => void;
|
|
29
|
+
loadSession: (sessionId: string) => Promise<void>;
|
|
30
|
+
refreshSessions: () => Promise<void>;
|
|
31
|
+
refreshModels: () => Promise<void>;
|
|
32
|
+
newSession: () => void;
|
|
33
|
+
}
|
|
34
|
+
/** One session view over one socket: connect, replay, stream, answer. */
|
|
35
|
+
export declare function useYodexSession(options: UseYodexSessionOptions): YodexSession;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assistant prose as sanitised markdown: GFM tables and task lists, raw HTML skipped, links
|
|
3
|
+
* opened in a new tab. `live` marks a still-streaming turn.
|
|
4
|
+
*/
|
|
5
|
+
export declare function AssistantMessage({ text, live, className }: {
|
|
6
|
+
text: string;
|
|
7
|
+
live?: boolean;
|
|
8
|
+
className?: string;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface ComposerAttachment {
|
|
2
|
+
name: string;
|
|
3
|
+
mediaType: string;
|
|
4
|
+
/** base64 payload, no data: prefix. */
|
|
5
|
+
data: string;
|
|
6
|
+
/** object URL for the thumbnail; revoked when removed. */
|
|
7
|
+
previewUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface HopOption {
|
|
10
|
+
value: string;
|
|
11
|
+
label: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ComposerProps {
|
|
15
|
+
onSubmit: (text: string, attachments: ComposerAttachment[]) => void;
|
|
16
|
+
/** While a run is live the send button becomes Stop. */
|
|
17
|
+
running?: boolean;
|
|
18
|
+
onStop?: () => void;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
/** Shown as a placeholder reason while disabled, e.g. "Workspace is waking up". */
|
|
21
|
+
disabledReason?: string;
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
hopOptions?: HopOption[];
|
|
24
|
+
hop?: string;
|
|
25
|
+
onHopChange?: (hop: string) => void;
|
|
26
|
+
/** A slot beside the HOP picker (model picker, mode pill). */
|
|
27
|
+
leading?: React.ReactNode;
|
|
28
|
+
maxAttachmentBytes?: number;
|
|
29
|
+
className?: string;
|
|
30
|
+
autoFocus?: boolean;
|
|
31
|
+
}
|
|
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;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function diffCounts(diff: string): {
|
|
2
|
+
added: number;
|
|
3
|
+
removed: number;
|
|
4
|
+
};
|
|
5
|
+
/** A unified diff, line by line. Render-only: the engine computed it, the model never sees it. */
|
|
6
|
+
export declare function DiffView({ diff, className }: {
|
|
7
|
+
diff: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PendingPermission } from "../client/types";
|
|
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 }: {
|
|
5
|
+
request: PendingPermission;
|
|
6
|
+
onAllow: (id: string) => void;
|
|
7
|
+
onDeny: (id: string, reason?: string) => void;
|
|
8
|
+
className?: string;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PlanItem } from "../client/types";
|
|
2
|
+
/** The agent's task list (TaskCreate / TaskUpdate), re-rendered in place as it changes. */
|
|
3
|
+
export declare function PlanChecklist({ items, className }: {
|
|
4
|
+
items: PlanItem[];
|
|
5
|
+
className?: string;
|
|
6
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AskOutcome, AskQuestion, PendingQuestion } from "../client/types";
|
|
2
|
+
/** What the person has picked or typed so far, per question index. */
|
|
3
|
+
export interface QuestionDraft {
|
|
4
|
+
selected: string[][];
|
|
5
|
+
notes: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function emptyDraft(questions: AskQuestion[]): QuestionDraft;
|
|
8
|
+
/** The ask.ts outcome: labels and free text per question; a closed form is `declined`. */
|
|
9
|
+
export declare function buildOutcome(questions: AskQuestion[], draft: QuestionDraft, declined?: boolean): AskOutcome;
|
|
10
|
+
/**
|
|
11
|
+
* AskUserQuestion as a form: one tab per question, single or multi select with optional
|
|
12
|
+
* previews, free text, Submit or close-to-decline. The outcome shape mirrors harness ask.ts.
|
|
13
|
+
*/
|
|
14
|
+
export declare function QuestionForm({ request, onSubmit, onDecline, className }: {
|
|
15
|
+
request: PendingQuestion;
|
|
16
|
+
onSubmit: (id: string, outcome: AskOutcome) => void;
|
|
17
|
+
onDecline: (id: string) => void;
|
|
18
|
+
className?: string;
|
|
19
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ResultEvent, Usage } from "@mlpal/harness-protocol";
|
|
2
|
+
import type { ConnectionState } from "../client/types";
|
|
3
|
+
export interface RunStatusProps {
|
|
4
|
+
running: boolean;
|
|
5
|
+
startedAt?: number;
|
|
6
|
+
activity?: string;
|
|
7
|
+
lastResult?: ResultEvent;
|
|
8
|
+
usageTotal?: Usage;
|
|
9
|
+
connection?: ConnectionState;
|
|
10
|
+
onStop?: () => void;
|
|
11
|
+
onApprove?: (decision: "allow" | "deny") => void;
|
|
12
|
+
className?: string;
|
|
13
|
+
/** Injected clock for deterministic rendering. */
|
|
14
|
+
now?: () => number;
|
|
15
|
+
}
|
|
16
|
+
/** 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;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SessionSummary } from "@mlpal/harness-protocol";
|
|
2
|
+
/** A SessionSummary; the yodex/2 fields (hop, relCwd, projectName, …) are present when the
|
|
3
|
+
* server claims "sessions_v2". */
|
|
4
|
+
export type SessionRow = SessionSummary;
|
|
5
|
+
export declare function groupSessions(rows: SessionRow[]): Array<{
|
|
6
|
+
project: string;
|
|
7
|
+
rows: SessionRow[];
|
|
8
|
+
}>;
|
|
9
|
+
export declare function SessionList({ sessions, selectedId, onSelect, onNew, className }: {
|
|
10
|
+
sessions: SessionRow[];
|
|
11
|
+
selectedId?: string;
|
|
12
|
+
onSelect: (sessionId: string) => void;
|
|
13
|
+
onNew?: () => void;
|
|
14
|
+
className?: string;
|
|
15
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TranscriptItem } from "../client/types";
|
|
2
|
+
type ToolItem = Extract<TranscriptItem, {
|
|
3
|
+
kind: "tool";
|
|
4
|
+
}>;
|
|
5
|
+
/** The one argument worth showing in the header: a command, a path, a pattern, a prompt. */
|
|
6
|
+
export declare function toolSummary(name: string, input: Record<string, unknown>): string;
|
|
7
|
+
/** One tool call with its result, collapsed to a single row by default. */
|
|
8
|
+
export declare function ToolCall({ item, defaultOpen, onOpenFile }: {
|
|
9
|
+
item: ToolItem;
|
|
10
|
+
defaultOpen?: boolean;
|
|
11
|
+
onOpenFile?: (path: string) => void;
|
|
12
|
+
}): import("react").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TranscriptItem } from "../client/types";
|
|
2
|
+
export interface TranscriptProps {
|
|
3
|
+
items: TranscriptItem[];
|
|
4
|
+
/** Keep the view pinned to the bottom while new rows arrive, unless the reader scrolled up. */
|
|
5
|
+
autoScroll?: boolean;
|
|
6
|
+
onOpenFile?: (path: string) => void;
|
|
7
|
+
/** Rendered after the last row: pending prompts, the status line. */
|
|
8
|
+
footer?: React.ReactNode;
|
|
9
|
+
emptyState?: React.ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function Transcript({ items, autoScroll, onOpenFile, footer, emptyState, className }: TranscriptProps): import("react").JSX.Element;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from "./client/types";
|
|
2
|
+
export { YodexClient, type YodexClientOptions, type RunOptions, type SocketLike, type SocketFactory, type OutgoingMessage } from "./client/client";
|
|
3
|
+
export { reduce, applyEvent, initialState, elapsedLabel, resultLabel, formatTokens } from "./client/reducer";
|
|
4
|
+
export { useYodexSession, type UseYodexSessionOptions, type YodexSession, type Attachment } from "./client/useYodexSession";
|
|
5
|
+
export { Transcript, type TranscriptProps } from "./components/Transcript";
|
|
6
|
+
export { AssistantMessage } from "./components/AssistantMessage";
|
|
7
|
+
export { ToolCall, toolSummary } from "./components/ToolCall";
|
|
8
|
+
export { DiffView, diffCounts } from "./components/DiffView";
|
|
9
|
+
export { PermissionPrompt, permissionSummary } from "./components/PermissionPrompt";
|
|
10
|
+
export { QuestionForm, buildOutcome, type QuestionDraft } from "./components/QuestionForm";
|
|
11
|
+
export { PlanChecklist } from "./components/PlanChecklist";
|
|
12
|
+
export { RunStatus } from "./components/RunStatus";
|
|
13
|
+
export { SessionList, groupSessions, type SessionRow } from "./components/SessionList";
|
|
14
|
+
export { Composer, type ComposerProps, type ComposerAttachment, type HopOption } from "./components/Composer";
|
|
15
|
+
export { cn } from "./components/cn";
|