@sigrun/tui 0.4.1 → 0.5.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/CHANGELOG.md +10 -1
- package/dist/actions.d.ts +119 -0
- package/dist/actions.js +12 -0
- package/dist/api.d.ts +209 -0
- package/dist/api.js +282 -0
- package/dist/blocks/braille.d.ts +41 -0
- package/dist/blocks/braille.js +131 -0
- package/dist/blocks/chart.d.ts +20 -0
- package/dist/blocks/chart.js +397 -0
- package/dist/blocks/chat-transport.d.ts +44 -0
- package/dist/blocks/chat-transport.js +116 -0
- package/dist/blocks/collection/calendar.d.ts +28 -0
- package/dist/blocks/collection/calendar.js +232 -0
- package/dist/blocks/collection/card.d.ts +36 -0
- package/dist/blocks/collection/card.js +128 -0
- package/dist/blocks/collection/collection.d.ts +9 -0
- package/dist/blocks/collection/collection.js +26 -0
- package/dist/blocks/collection/grid.d.ts +18 -0
- package/dist/blocks/collection/grid.js +72 -0
- package/dist/blocks/collection/lanes.d.ts +32 -0
- package/dist/blocks/collection/lanes.js +320 -0
- package/dist/blocks/collection/list.d.ts +30 -0
- package/dist/blocks/collection/list.js +273 -0
- package/dist/blocks/collection/move.d.ts +67 -0
- package/dist/blocks/collection/move.js +96 -0
- package/dist/blocks/collection/shared.d.ts +87 -0
- package/dist/blocks/collection/shared.js +177 -0
- package/dist/blocks/collection/table.d.ts +26 -0
- package/dist/blocks/collection/table.js +221 -0
- package/dist/blocks/content.d.ts +48 -0
- package/dist/blocks/content.js +253 -0
- package/dist/blocks/context.d.ts +117 -0
- package/dist/blocks/context.js +65 -0
- package/dist/blocks/diff.d.ts +21 -0
- package/dist/blocks/diff.js +25 -0
- package/dist/blocks/ext-transport.d.ts +70 -0
- package/dist/blocks/ext-transport.js +165 -0
- package/dist/blocks/form/choice.d.ts +54 -0
- package/dist/blocks/form/choice.js +175 -0
- package/dist/blocks/form/fields.d.ts +56 -0
- package/dist/blocks/form/fields.js +136 -0
- package/dist/blocks/form/file.d.ts +61 -0
- package/dist/blocks/form/file.js +121 -0
- package/dist/blocks/form/form.d.ts +66 -0
- package/dist/blocks/form/form.js +764 -0
- package/dist/blocks/form/list.d.ts +37 -0
- package/dist/blocks/form/list.js +70 -0
- package/dist/blocks/form/values.d.ts +20 -0
- package/dist/blocks/form/values.js +106 -0
- package/dist/blocks/icons.d.ts +14 -0
- package/dist/blocks/icons.js +74 -0
- package/dist/blocks/image.d.ts +39 -0
- package/dist/blocks/image.js +82 -0
- package/dist/blocks/interaction-transport.d.ts +42 -0
- package/dist/blocks/interaction-transport.js +79 -0
- package/dist/blocks/layout.d.ts +31 -0
- package/dist/blocks/layout.js +155 -0
- package/dist/blocks/log.d.ts +24 -0
- package/dist/blocks/log.js +111 -0
- package/dist/blocks/meter.d.ts +16 -0
- package/dist/blocks/meter.js +50 -0
- package/dist/blocks/page.d.ts +54 -0
- package/dist/blocks/page.js +360 -0
- package/dist/blocks/render.d.ts +31 -0
- package/dist/blocks/render.js +163 -0
- package/dist/blocks/surface-view.d.ts +41 -0
- package/dist/blocks/surface-view.js +137 -0
- package/dist/blocks/surface.d.ts +80 -0
- package/dist/blocks/surface.js +506 -0
- package/dist/blocks/tones.d.ts +23 -0
- package/dist/blocks/tones.js +63 -0
- package/dist/blocks/treeview.d.ts +22 -0
- package/dist/blocks/treeview.js +121 -0
- package/dist/blocks/width.d.ts +32 -0
- package/dist/blocks/width.js +70 -0
- package/dist/clipboard.d.ts +20 -0
- package/dist/clipboard.js +19 -0
- package/dist/editor-launch.d.ts +34 -0
- package/dist/editor-launch.js +70 -0
- package/dist/keymaps/emacs.d.ts +18 -0
- package/dist/keymaps/emacs.js +172 -0
- package/dist/keymaps/standard.d.ts +17 -0
- package/dist/keymaps/standard.js +175 -0
- package/dist/keymaps/types.d.ts +43 -0
- package/dist/keymaps/types.js +28 -0
- package/dist/keymaps/vim.d.ts +48 -0
- package/dist/keymaps/vim.js +291 -0
- package/dist/launch.d.ts +159 -0
- package/dist/launch.js +478 -0
- package/dist/sse.d.ts +112 -0
- package/dist/sse.js +418 -0
- package/dist/theme.d.ts +51 -0
- package/dist/theme.js +119 -0
- package/dist/ui/ambient.d.ts +78 -0
- package/dist/ui/ambient.js +264 -0
- package/dist/ui/app.d.ts +319 -0
- package/dist/ui/app.js +1539 -0
- package/dist/ui/composer.d.ts +66 -0
- package/dist/ui/composer.js +133 -0
- package/dist/ui/conversation-pane.d.ts +143 -0
- package/dist/ui/conversation-pane.js +615 -0
- package/dist/ui/help.d.ts +15 -0
- package/dist/ui/help.js +80 -0
- package/dist/ui/interactions.d.ts +91 -0
- package/dist/ui/interactions.js +477 -0
- package/dist/ui/picker.d.ts +26 -0
- package/dist/ui/picker.js +130 -0
- package/dist/ui/settings.d.ts +68 -0
- package/dist/ui/settings.js +254 -0
- package/dist/ui/sidebar.d.ts +99 -0
- package/dist/ui/sidebar.js +269 -0
- package/dist/ui/status-bar.d.ts +77 -0
- package/dist/ui/status-bar.js +146 -0
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.5.0] - 2026-11-02
|
|
9
|
+
|
|
10
|
+
Version-companion release for sigrun 0.5.0 (no tui changes).
|
|
11
|
+
## [0.4.2] - 2026-10-31
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- The published tarball carries `dist/` again (`prepublishOnly` builds
|
|
15
|
+
before `npm publish` — releases 0.2.0–0.4.1 shipped without it, so
|
|
16
|
+
the `sigrun-tui` bin did not exist for installing users).
|
|
9
17
|
|
|
10
18
|
## [0.4.1] - 2026-10-31
|
|
11
19
|
|
|
@@ -49,3 +57,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
49
57
|
alt-screen UI with mouse support, per-keymap standard/vim/emacs
|
|
50
58
|
keymaps, themes from the shared tokens, turn-grouped transcript,
|
|
51
59
|
composer, help overlay, and OSC 52 clipboard.
|
|
60
|
+
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TUI action layer (Task C3, UI refresh): a channel-neutral union of
|
|
3
|
+
* everything the keymaps, pickers, mouse regions, and slash commands can
|
|
4
|
+
* ask the shell to do. {@link TuiApp} implements {@link ActionHost} and
|
|
5
|
+
* owns the focus state the focus/move/open actions drive.
|
|
6
|
+
*
|
|
7
|
+
* Focus targets: `composer` (the pi-tui Editor), `list` (the sidebar's
|
|
8
|
+
* conversation tree), `transcript` (the pane's focusable turns). The
|
|
9
|
+
* keymap decides WHICH action a key means per focus; the app decides
|
|
10
|
+
* what the action DOES per focus.
|
|
11
|
+
*/
|
|
12
|
+
/** The three focusable regions. `surface` (Task 4, UI-blocks U2) is a
|
|
13
|
+
* block surface pane (lanes/calendar/collection) — the blocks.* keys
|
|
14
|
+
* route ONLY while it has focus. While a surface is focused the
|
|
15
|
+
* transcript/list nav keys (y/f/e//?) are inert (the keymaps pass
|
|
16
|
+
* them); Task 6's surface host decides any surface-side meaning for
|
|
17
|
+
* them. */
|
|
18
|
+
export type Focus = "composer" | "list" | "transcript" | "surface";
|
|
19
|
+
/** Every dispatchable intent. */
|
|
20
|
+
export type Action = {
|
|
21
|
+
readonly type: "focus";
|
|
22
|
+
readonly target: Focus;
|
|
23
|
+
} | {
|
|
24
|
+
readonly type: "focus-cycle";
|
|
25
|
+
readonly direction: 1 | -1;
|
|
26
|
+
} | {
|
|
27
|
+
readonly type: "move";
|
|
28
|
+
readonly delta: number;
|
|
29
|
+
} | {
|
|
30
|
+
readonly type: "page";
|
|
31
|
+
readonly direction: 1 | -1;
|
|
32
|
+
readonly half: boolean;
|
|
33
|
+
} | {
|
|
34
|
+
readonly type: "edge";
|
|
35
|
+
readonly to: "top" | "bottom";
|
|
36
|
+
} | {
|
|
37
|
+
readonly type: "prompt-jump";
|
|
38
|
+
readonly delta: 1 | -1;
|
|
39
|
+
} | {
|
|
40
|
+
readonly type: "toggle-fold";
|
|
41
|
+
} | {
|
|
42
|
+
readonly type: "open";
|
|
43
|
+
} | {
|
|
44
|
+
readonly type: "copy";
|
|
45
|
+
} | {
|
|
46
|
+
readonly type: "fork-here";
|
|
47
|
+
} | {
|
|
48
|
+
readonly type: "edit-here";
|
|
49
|
+
} | {
|
|
50
|
+
readonly type: "edit-in-vim";
|
|
51
|
+
} | {
|
|
52
|
+
readonly type: "picker";
|
|
53
|
+
readonly kind: "conversation" | "model" | "command";
|
|
54
|
+
} | {
|
|
55
|
+
readonly type: "new-conversation";
|
|
56
|
+
} | {
|
|
57
|
+
readonly type: "cycle-thinking";
|
|
58
|
+
} | {
|
|
59
|
+
readonly type: "search";
|
|
60
|
+
readonly direction: "forward" | "backward";
|
|
61
|
+
} | {
|
|
62
|
+
readonly type: "search-next";
|
|
63
|
+
readonly reverse: boolean;
|
|
64
|
+
} | {
|
|
65
|
+
readonly type: "answer-interaction";
|
|
66
|
+
} | {
|
|
67
|
+
readonly type: "cancel-run";
|
|
68
|
+
} | {
|
|
69
|
+
readonly type: "redraw";
|
|
70
|
+
} | {
|
|
71
|
+
readonly type: "quit";
|
|
72
|
+
} | {
|
|
73
|
+
readonly type: "help";
|
|
74
|
+
} | {
|
|
75
|
+
readonly type: "command-line";
|
|
76
|
+
} | {
|
|
77
|
+
readonly type: "run-command";
|
|
78
|
+
readonly line: string;
|
|
79
|
+
}
|
|
80
|
+
/** Pick up the item under the cursor (m / M-m). */
|
|
81
|
+
| {
|
|
82
|
+
readonly type: "blocks.pickUp";
|
|
83
|
+
}
|
|
84
|
+
/** Drop the picked-up item at its placement (Enter). */
|
|
85
|
+
| {
|
|
86
|
+
readonly type: "blocks.drop";
|
|
87
|
+
}
|
|
88
|
+
/** Cancel the pick-up (Esc). */
|
|
89
|
+
| {
|
|
90
|
+
readonly type: "blocks.cancelMove";
|
|
91
|
+
}
|
|
92
|
+
/** Toggle the selection of the item under the cursor (Space). */
|
|
93
|
+
| {
|
|
94
|
+
readonly type: "blocks.select";
|
|
95
|
+
}
|
|
96
|
+
/** Open the action menu (`.` — bulk actions, "Move to…"). */
|
|
97
|
+
| {
|
|
98
|
+
readonly type: "blocks.actionMenu";
|
|
99
|
+
}
|
|
100
|
+
/** Toggle the checkable field of the item under the cursor (x). */
|
|
101
|
+
| {
|
|
102
|
+
readonly type: "blocks.check";
|
|
103
|
+
}
|
|
104
|
+
/** Previous calendar range ([). */
|
|
105
|
+
| {
|
|
106
|
+
readonly type: "blocks.rangePrev";
|
|
107
|
+
}
|
|
108
|
+
/** Next calendar range (]). */
|
|
109
|
+
| {
|
|
110
|
+
readonly type: "blocks.rangeNext";
|
|
111
|
+
}
|
|
112
|
+
/** Return the calendar range to today (t). */
|
|
113
|
+
| {
|
|
114
|
+
readonly type: "blocks.today";
|
|
115
|
+
};
|
|
116
|
+
/** The dispatch surface {@link TuiApp} implements. */
|
|
117
|
+
export interface ActionHost {
|
|
118
|
+
dispatch(action: Action): Promise<void>;
|
|
119
|
+
}
|
package/dist/actions.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TUI action layer (Task C3, UI refresh): a channel-neutral union of
|
|
3
|
+
* everything the keymaps, pickers, mouse regions, and slash commands can
|
|
4
|
+
* ask the shell to do. {@link TuiApp} implements {@link ActionHost} and
|
|
5
|
+
* owns the focus state the focus/move/open actions drive.
|
|
6
|
+
*
|
|
7
|
+
* Focus targets: `composer` (the pi-tui Editor), `list` (the sidebar's
|
|
8
|
+
* conversation tree), `transcript` (the pane's focusable turns). The
|
|
9
|
+
* keymap decides WHICH action a key means per focus; the app decides
|
|
10
|
+
* what the action DOES per focus.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TUI's typed §5 HTTP client (Task 4).
|
|
3
|
+
*
|
|
4
|
+
* Wire shapes are the channel contract — imported from the
|
|
5
|
+
* `@sigrun/shared` package (`views`, plus `SessionMeta`/`SlashCommand`
|
|
6
|
+
* siblings), the single source of truth shared with the server and the
|
|
7
|
+
* webui (build-and-webui Task 1: the tui is a pure client of the wire,
|
|
8
|
+
* never a relative importer of server source).
|
|
9
|
+
*
|
|
10
|
+
* Identity seam (Task 2): the configured `user` rides EVERY request as the
|
|
11
|
+
* `X-Sigrun-User` header; absent/empty user omits it entirely — the
|
|
12
|
+
* server's naive mode. The bearer token (Task 7) rides every request as
|
|
13
|
+
* `authorization: Bearer <token>` when configured.
|
|
14
|
+
*
|
|
15
|
+
* Fail-loud: a non-2xx rejects with a {@link SigrunApiError} carrying the
|
|
16
|
+
* status and the server's parsed error body (`{error: ...}`), never a
|
|
17
|
+
* silent `undefined` the UI could misrender as empty state.
|
|
18
|
+
*/
|
|
19
|
+
import type { CommandRunResponse, CommandsResponse } from "@sigrun/shared/commands";
|
|
20
|
+
import type { JobsResponse, JobRunResponse } from "@sigrun/shared/views";
|
|
21
|
+
import type { JobRevisionWire, JobRunWire } from "@sigrun/shared/jobs";
|
|
22
|
+
import type { SessionMeta, SessionMetaPatch } from "@sigrun/shared/session-meta";
|
|
23
|
+
import type { AbortResponse, AmbientList, CompactResponse, ConfigureResponse, ConversationCreateResponse, ConversationForkResponse, ConversationList, ConversationViewWire, InteractionAnswered, InteractionList, InteractionWire, MetaResponse, ModelsResponse, SettingsResponse, SettingsSectionsResponse, SubmissionResponse, WhoamiResponse } from "@sigrun/shared/views";
|
|
24
|
+
export type { AbortResponse, AmbientList, AmbientWire, CompactResponse, ConfigureResponse, ConversationCreateResponse, ConversationForkResponse, ConversationList, ConversationSummary, ConversationViewWire, InteractionAnswered, InteractionList, InteractionWire, MetaResponse, ModelEntry, ModelsResponse, SettingsResponse, SettingsSectionsResponse, SettingsSectionEntry, SettingsSectionIndexEntry, SubmissionResponse, WhoamiResponse, } from "@sigrun/shared/views";
|
|
25
|
+
export type { CommandRunResponse, CommandsResponse, SlashCommand } from "@sigrun/shared/commands";
|
|
26
|
+
export type { JobSpec, JobSchedule, JobAction, JobWire, JobRunWire, JobRevisionWire, } from "@sigrun/shared/jobs";
|
|
27
|
+
export type { JobsResponse, JobRunResponse } from "@sigrun/shared/views";
|
|
28
|
+
export type { ExtensionsResponse, ExtensionInfo, ExtensionSurfaceInfo } from "@sigrun/shared/views";
|
|
29
|
+
export type { SessionMeta, SessionMetaPatch } from "@sigrun/shared/session-meta";
|
|
30
|
+
/**
|
|
31
|
+
* One answer to a pending interaction (the ONE generic route's body).
|
|
32
|
+
* U6 Task 2 (ruling 20): `{values}` ONLY — every field's value under
|
|
33
|
+
* its name; the legacy `{ids, text}` shapes are a 400 server-side.
|
|
34
|
+
*/
|
|
35
|
+
export interface InteractionAnswerInput {
|
|
36
|
+
readonly values: Readonly<Record<string, import("@sigrun/shared/blocks").JsonValue>>;
|
|
37
|
+
}
|
|
38
|
+
/** `GET /v1/jobs/:id/runs`: the job's runs, newest first. */
|
|
39
|
+
export interface JobRunsResponse {
|
|
40
|
+
readonly runs: readonly JobRunWire[];
|
|
41
|
+
}
|
|
42
|
+
/** `GET /v1/jobs/:id/revisions`: the job's revisions, newest first. */
|
|
43
|
+
export interface JobRevisionsResponse {
|
|
44
|
+
readonly revisions: readonly JobRevisionWire[];
|
|
45
|
+
}
|
|
46
|
+
/** The `whenBusy` admission modes the server accepts (spec §7 item 3). */
|
|
47
|
+
export type WhenBusyMode = "steer" | "followUp" | "reject";
|
|
48
|
+
/** A non-2xx response: status + the server's parsed error body. */
|
|
49
|
+
export declare class SigrunApiError extends Error {
|
|
50
|
+
readonly status: number;
|
|
51
|
+
readonly body: unknown;
|
|
52
|
+
constructor(method: string, path: string, status: number, body: unknown);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The user-facing name of an auth failure (Task 5): a 401
|
|
56
|
+
* {@link SigrunApiError} maps to {@link AUTH_FAILURE_MESSAGE} — the one
|
|
57
|
+
* string that tells the operator WHAT is wrong and WHERE to fix it (the
|
|
58
|
+
* token is missing, expired, or revoked; a new one is minted in the
|
|
59
|
+
* webui's Settings → API tokens section). Anything else (a different
|
|
60
|
+
* status, a non-api error) maps to `undefined`: the caller keeps its
|
|
61
|
+
* generic error path. Shared by the startup exit (`tuiStartupMessage`),
|
|
62
|
+
* the pane's error lines (`describeCommandError`), and the SSE
|
|
63
|
+
* reconnect policy (a 401 never retries — a credential cannot recover
|
|
64
|
+
* by reconnecting).
|
|
65
|
+
*/
|
|
66
|
+
export declare const AUTH_FAILURE_MESSAGE = "authentication failed (401): SIGRUN_TOKEN is missing, expired, or revoked \u2014 create one in Settings \u2192 API tokens";
|
|
67
|
+
/** @see {@link AUTH_FAILURE_MESSAGE} */
|
|
68
|
+
export declare function authFailureMessage(error: unknown): string | undefined;
|
|
69
|
+
/** The fetch surface the client needs (the real thing or a test stub). */
|
|
70
|
+
export type FetchLike = (input: string, init?: {
|
|
71
|
+
readonly method?: string;
|
|
72
|
+
readonly headers?: Record<string, string>;
|
|
73
|
+
readonly body?: string;
|
|
74
|
+
}) => Promise<{
|
|
75
|
+
readonly ok: boolean;
|
|
76
|
+
readonly status: number;
|
|
77
|
+
json(): Promise<unknown>;
|
|
78
|
+
text(): Promise<string>;
|
|
79
|
+
}>;
|
|
80
|
+
export interface SigrunClientOptions {
|
|
81
|
+
/**
|
|
82
|
+
* The server's origin (e.g. `http://127.0.0.1:PORT`) — the TUI is a
|
|
83
|
+
* remote client, so unlike the webui there is no same-origin default.
|
|
84
|
+
*/
|
|
85
|
+
readonly baseUrl: string;
|
|
86
|
+
/**
|
|
87
|
+
* The `X-Sigrun-User` identity (Task 2). Absent/empty/whitespace → the
|
|
88
|
+
* header is omitted entirely (the server's naive mode).
|
|
89
|
+
*/
|
|
90
|
+
readonly user?: string;
|
|
91
|
+
/**
|
|
92
|
+
* A bearer token (Task 7): rides EVERY request as
|
|
93
|
+
* `authorization: Bearer <token>`. Absent/empty/whitespace → omitted.
|
|
94
|
+
* The launcher threads `SIGRUN_TOKEN` here — the env beats tui.json.
|
|
95
|
+
*/
|
|
96
|
+
readonly token?: string;
|
|
97
|
+
/** Injected for tests; defaults to the ambient `fetch`. */
|
|
98
|
+
readonly fetch?: FetchLike;
|
|
99
|
+
}
|
|
100
|
+
/** The typed §5 client. See {@link SigrunClientOptions} for the seams. */
|
|
101
|
+
import type { ExtensionsResponse } from "@sigrun/shared/views";
|
|
102
|
+
export declare class SigrunClient {
|
|
103
|
+
#private;
|
|
104
|
+
/** The server's origin (the settings view's surface transports). */
|
|
105
|
+
get baseUrl(): string;
|
|
106
|
+
/** The auth token (the surface transports' bearer). */
|
|
107
|
+
get token(): string | undefined;
|
|
108
|
+
/** The `X-Sigrun-User` identity (the surface transports' header). */
|
|
109
|
+
get user(): string | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* The client's fetch (the settings view's surface transports — a
|
|
112
|
+
* test-injected fetch must carry the section loads too). The
|
|
113
|
+
* parameter/return types widen to the ambient `fetch` signature;
|
|
114
|
+
* the declared {@link FetchLike} is the consumed subset.
|
|
115
|
+
*/
|
|
116
|
+
get fetch(): typeof globalThis.fetch;
|
|
117
|
+
constructor(options: SigrunClientOptions);
|
|
118
|
+
/** `GET /v1/conversations` — all durable conversations (Task 3). */
|
|
119
|
+
listConversations(): Promise<ConversationList>;
|
|
120
|
+
/** `GET /v1/conversations/:id` — the full wire view (one frame). */
|
|
121
|
+
getConversation(id: number): Promise<ConversationViewWire>;
|
|
122
|
+
/** `POST /v1/conversations` — the new id plus its first view frame. */
|
|
123
|
+
createConversation(): Promise<ConversationCreateResponse>;
|
|
124
|
+
/** `POST /v1/conversations/:id/fork` (absent `at` = the newest entry). */
|
|
125
|
+
forkConversation(id: number, at?: number): Promise<ConversationForkResponse>;
|
|
126
|
+
/**
|
|
127
|
+
* `POST /v1/conversations/:id/submissions` — plain text input; 202 at
|
|
128
|
+
* ADMISSION (§7.1) with the record as it stands. The TUI is
|
|
129
|
+
* fire-and-forget: the reply arrives as view revisions on the stream,
|
|
130
|
+
* not from this response.
|
|
131
|
+
*/
|
|
132
|
+
submit(id: number, text: string, whenBusy?: WhenBusyMode): Promise<SubmissionResponse>;
|
|
133
|
+
/** `POST /v1/conversations/:id/abort`. */
|
|
134
|
+
abort(id: number): Promise<AbortResponse>;
|
|
135
|
+
/**
|
|
136
|
+
* `POST /v1/conversations/:id/compact` — admits a manual compaction;
|
|
137
|
+
* the summary entry arrives later as a view revision on the stream.
|
|
138
|
+
*/
|
|
139
|
+
compact(id: number, instructions?: string): Promise<CompactResponse>;
|
|
140
|
+
/** `GET /v1/conversations/:id/meta`. */
|
|
141
|
+
getMeta(id: number): Promise<SessionMeta>;
|
|
142
|
+
/** `PATCH /v1/conversations/:id/meta` — merges; resolves to the new meta. */
|
|
143
|
+
patchMeta(id: number, patch: SessionMetaPatch): Promise<SessionMeta>;
|
|
144
|
+
/** `GET /v1/conversations/:id/interactions` — pending interactions. */
|
|
145
|
+
listInteractions(id: number): Promise<InteractionList>;
|
|
146
|
+
/** `GET .../interactions/:callId` — one record in ANY status (the
|
|
147
|
+
* settle-refetch path: an answer that lands on an already-settled
|
|
148
|
+
* record (404 not_pending) refetches here and renders it read-only). */
|
|
149
|
+
getInteraction(id: number, callId: string): Promise<InteractionWire>;
|
|
150
|
+
/** `POST .../interactions/:callId/answer` — the ONE generic answer route. */
|
|
151
|
+
answerInteraction(id: number, callId: string, answer: InteractionAnswerInput): Promise<InteractionAnswered>;
|
|
152
|
+
/** `GET /v1/conversations/:id/ambient`. */
|
|
153
|
+
getAmbient(id: number): Promise<AmbientList>;
|
|
154
|
+
/** `GET /v1/commands` — the slash-command catalog (a boot constant). */
|
|
155
|
+
getCommands(): Promise<CommandsResponse>;
|
|
156
|
+
/** `GET /v1/extensions` — the loaded-extension inventory (U3 Task 5:
|
|
157
|
+
* the sidebar's Views group + panels read the declared surfaces). */
|
|
158
|
+
listExtensions(): Promise<ExtensionsResponse>;
|
|
159
|
+
/**
|
|
160
|
+
* `POST /v1/conversations/:id/commands/:name` — invoke an extension
|
|
161
|
+
* slash command (W5 Task 3): the server runs the winning declaration
|
|
162
|
+
* and resolves with its optional `{notice}`.
|
|
163
|
+
*/
|
|
164
|
+
runCommand(id: number, name: string, args?: string): Promise<CommandRunResponse>;
|
|
165
|
+
/** `GET /v1/settings` — the effective settings (absent file = `{}`). */
|
|
166
|
+
getSettings(): Promise<SettingsResponse>;
|
|
167
|
+
/**
|
|
168
|
+
* `GET /v1/settings/sections` (UI-blocks U4 Task 4): the settings
|
|
169
|
+
* panel's nav list + search index in one payload.
|
|
170
|
+
*/
|
|
171
|
+
listSettingsSections(): Promise<SettingsSectionsResponse>;
|
|
172
|
+
/** `GET /v1/auth/whoami` — the resolved principal (the settings
|
|
173
|
+
* callout's admin gate). */
|
|
174
|
+
whoami(): Promise<WhoamiResponse>;
|
|
175
|
+
/** `GET /v1/meta` — the boot facts (the settings callout's
|
|
176
|
+
* `supervised` read). */
|
|
177
|
+
getBootMeta(): Promise<MetaResponse>;
|
|
178
|
+
/**
|
|
179
|
+
* `POST /v1/admin/restart` (admin; U4 Task 2, spec §13.3): resolves
|
|
180
|
+
* once the server answered 202 (the restart begins after the reply);
|
|
181
|
+
* rejects on 403/409 (the restart never began).
|
|
182
|
+
*/
|
|
183
|
+
requestRestart(): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* `GET /readyz` readiness probe (U4 Task 2): `true` only on a 200.
|
|
186
|
+
* The restart-recovery poll rides this; a connection-level failure
|
|
187
|
+
* (the process mid-restart) resolves `false`, never throws.
|
|
188
|
+
*/
|
|
189
|
+
checkReady(): Promise<boolean>;
|
|
190
|
+
/** `GET /v1/models` — the chat-model catalog (the model picker's items). */
|
|
191
|
+
listModels(): Promise<ModelsResponse>;
|
|
192
|
+
/**
|
|
193
|
+
* `POST /v1/conversations/:id/configure` — change the model and/or
|
|
194
|
+
* thinking level (T9c-i). At least one field is required (the server
|
|
195
|
+
* rejects an empty change as `invalid_body`).
|
|
196
|
+
*/
|
|
197
|
+
configure(id: number, body: {
|
|
198
|
+
readonly model?: string;
|
|
199
|
+
readonly thinking?: string;
|
|
200
|
+
}): Promise<ConfigureResponse>;
|
|
201
|
+
/** `GET /v1/jobs` — the caller-visible jobs (`/jobs`, W4 Task 5). */
|
|
202
|
+
listJobs(): Promise<JobsResponse>;
|
|
203
|
+
/** `POST /v1/jobs/:id/run` — fire a job now (write access). */
|
|
204
|
+
runJob(id: string): Promise<JobRunResponse>;
|
|
205
|
+
/** `GET /v1/jobs/:id/runs` — the job's runs, newest first. */
|
|
206
|
+
listJobRuns(id: string, limit?: number): Promise<JobRunsResponse>;
|
|
207
|
+
/** `GET /v1/jobs/:id/revisions` — the job's revisions, newest first. */
|
|
208
|
+
listJobRevisions(id: string): Promise<JobRevisionsResponse>;
|
|
209
|
+
}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TUI's typed §5 HTTP client (Task 4).
|
|
3
|
+
*
|
|
4
|
+
* Wire shapes are the channel contract — imported from the
|
|
5
|
+
* `@sigrun/shared` package (`views`, plus `SessionMeta`/`SlashCommand`
|
|
6
|
+
* siblings), the single source of truth shared with the server and the
|
|
7
|
+
* webui (build-and-webui Task 1: the tui is a pure client of the wire,
|
|
8
|
+
* never a relative importer of server source).
|
|
9
|
+
*
|
|
10
|
+
* Identity seam (Task 2): the configured `user` rides EVERY request as the
|
|
11
|
+
* `X-Sigrun-User` header; absent/empty user omits it entirely — the
|
|
12
|
+
* server's naive mode. The bearer token (Task 7) rides every request as
|
|
13
|
+
* `authorization: Bearer <token>` when configured.
|
|
14
|
+
*
|
|
15
|
+
* Fail-loud: a non-2xx rejects with a {@link SigrunApiError} carrying the
|
|
16
|
+
* status and the server's parsed error body (`{error: ...}`), never a
|
|
17
|
+
* silent `undefined` the UI could misrender as empty state.
|
|
18
|
+
*/
|
|
19
|
+
/** A non-2xx response: status + the server's parsed error body. */
|
|
20
|
+
export class SigrunApiError extends Error {
|
|
21
|
+
status;
|
|
22
|
+
body;
|
|
23
|
+
constructor(method, path, status, body) {
|
|
24
|
+
super(`sigrun api: ${method} ${path} failed with status ${status}`);
|
|
25
|
+
this.name = "SigrunApiError";
|
|
26
|
+
this.status = status;
|
|
27
|
+
this.body = body;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The user-facing name of an auth failure (Task 5): a 401
|
|
32
|
+
* {@link SigrunApiError} maps to {@link AUTH_FAILURE_MESSAGE} — the one
|
|
33
|
+
* string that tells the operator WHAT is wrong and WHERE to fix it (the
|
|
34
|
+
* token is missing, expired, or revoked; a new one is minted in the
|
|
35
|
+
* webui's Settings → API tokens section). Anything else (a different
|
|
36
|
+
* status, a non-api error) maps to `undefined`: the caller keeps its
|
|
37
|
+
* generic error path. Shared by the startup exit (`tuiStartupMessage`),
|
|
38
|
+
* the pane's error lines (`describeCommandError`), and the SSE
|
|
39
|
+
* reconnect policy (a 401 never retries — a credential cannot recover
|
|
40
|
+
* by reconnecting).
|
|
41
|
+
*/
|
|
42
|
+
export const AUTH_FAILURE_MESSAGE = "authentication failed (401): SIGRUN_TOKEN is missing, expired, or revoked — create one in Settings → API tokens";
|
|
43
|
+
/** @see {@link AUTH_FAILURE_MESSAGE} */
|
|
44
|
+
export function authFailureMessage(error) {
|
|
45
|
+
if (error instanceof SigrunApiError && error.status === 401) {
|
|
46
|
+
return AUTH_FAILURE_MESSAGE;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
export class SigrunClient {
|
|
51
|
+
#baseUrl;
|
|
52
|
+
#fetch;
|
|
53
|
+
#headers;
|
|
54
|
+
#token;
|
|
55
|
+
#user;
|
|
56
|
+
/** The server's origin (the settings view's surface transports). */
|
|
57
|
+
get baseUrl() {
|
|
58
|
+
return this.#baseUrl;
|
|
59
|
+
}
|
|
60
|
+
/** The auth token (the surface transports' bearer). */
|
|
61
|
+
get token() {
|
|
62
|
+
return this.#token;
|
|
63
|
+
}
|
|
64
|
+
/** The `X-Sigrun-User` identity (the surface transports' header). */
|
|
65
|
+
get user() {
|
|
66
|
+
return this.#user;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The client's fetch (the settings view's surface transports — a
|
|
70
|
+
* test-injected fetch must carry the section loads too). The
|
|
71
|
+
* parameter/return types widen to the ambient `fetch` signature;
|
|
72
|
+
* the declared {@link FetchLike} is the consumed subset.
|
|
73
|
+
*/
|
|
74
|
+
get fetch() {
|
|
75
|
+
return this.#fetch;
|
|
76
|
+
}
|
|
77
|
+
constructor(options) {
|
|
78
|
+
// A trailing slash would double every route's leading one
|
|
79
|
+
// (`--server http://host/` → `//v1/...`) — normalize once.
|
|
80
|
+
this.#baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
81
|
+
this.#fetch =
|
|
82
|
+
options.fetch ??
|
|
83
|
+
((input, init) => globalThis.fetch(input, init));
|
|
84
|
+
const user = options.user?.trim();
|
|
85
|
+
const token = options.token?.trim();
|
|
86
|
+
this.#token = token === "" ? undefined : token;
|
|
87
|
+
this.#user = user === "" ? undefined : user;
|
|
88
|
+
this.#headers = {
|
|
89
|
+
accept: "application/json",
|
|
90
|
+
...(user === undefined || user === "" ? {} : { "x-sigrun-user": user }),
|
|
91
|
+
...(token === undefined || token === ""
|
|
92
|
+
? {}
|
|
93
|
+
: { authorization: `Bearer ${token}` }),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async #get(path) {
|
|
97
|
+
const response = await this.#fetch(`${this.#baseUrl}${path}`, {
|
|
98
|
+
method: "GET",
|
|
99
|
+
headers: this.#headers,
|
|
100
|
+
});
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
throw new SigrunApiError("GET", path, response.status, await response.json().catch(() => undefined));
|
|
103
|
+
}
|
|
104
|
+
return (await response.json());
|
|
105
|
+
}
|
|
106
|
+
async #send(method, path, body) {
|
|
107
|
+
const response = await this.#fetch(`${this.#baseUrl}${path}`, {
|
|
108
|
+
method,
|
|
109
|
+
headers: { ...this.#headers, "content-type": "application/json" },
|
|
110
|
+
body: JSON.stringify(body),
|
|
111
|
+
});
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new SigrunApiError(method, path, response.status, await response.json().catch(() => undefined));
|
|
114
|
+
}
|
|
115
|
+
return (await response.json());
|
|
116
|
+
}
|
|
117
|
+
/** `GET /v1/conversations` — all durable conversations (Task 3). */
|
|
118
|
+
listConversations() {
|
|
119
|
+
return this.#get("/v1/conversations");
|
|
120
|
+
}
|
|
121
|
+
/** `GET /v1/conversations/:id` — the full wire view (one frame). */
|
|
122
|
+
getConversation(id) {
|
|
123
|
+
return this.#get(`/v1/conversations/${id}`);
|
|
124
|
+
}
|
|
125
|
+
/** `POST /v1/conversations` — the new id plus its first view frame. */
|
|
126
|
+
createConversation() {
|
|
127
|
+
return this.#send("POST", "/v1/conversations", {});
|
|
128
|
+
}
|
|
129
|
+
/** `POST /v1/conversations/:id/fork` (absent `at` = the newest entry). */
|
|
130
|
+
forkConversation(id, at) {
|
|
131
|
+
return this.#send("POST", `/v1/conversations/${id}/fork`, {
|
|
132
|
+
...(at === undefined ? {} : { at }),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* `POST /v1/conversations/:id/submissions` — plain text input; 202 at
|
|
137
|
+
* ADMISSION (§7.1) with the record as it stands. The TUI is
|
|
138
|
+
* fire-and-forget: the reply arrives as view revisions on the stream,
|
|
139
|
+
* not from this response.
|
|
140
|
+
*/
|
|
141
|
+
submit(id, text, whenBusy) {
|
|
142
|
+
return this.#send("POST", `/v1/conversations/${id}/submissions`, {
|
|
143
|
+
input: [{ type: "text", text }],
|
|
144
|
+
...(whenBusy === undefined ? {} : { whenBusy }),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/** `POST /v1/conversations/:id/abort`. */
|
|
148
|
+
abort(id) {
|
|
149
|
+
return this.#send("POST", `/v1/conversations/${id}/abort`, {});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* `POST /v1/conversations/:id/compact` — admits a manual compaction;
|
|
153
|
+
* the summary entry arrives later as a view revision on the stream.
|
|
154
|
+
*/
|
|
155
|
+
compact(id, instructions) {
|
|
156
|
+
return this.#send("POST", `/v1/conversations/${id}/compact`, {
|
|
157
|
+
...(instructions === undefined || instructions === ""
|
|
158
|
+
? {}
|
|
159
|
+
: { instructions }),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/** `GET /v1/conversations/:id/meta`. */
|
|
163
|
+
getMeta(id) {
|
|
164
|
+
return this.#get(`/v1/conversations/${id}/meta`);
|
|
165
|
+
}
|
|
166
|
+
/** `PATCH /v1/conversations/:id/meta` — merges; resolves to the new meta. */
|
|
167
|
+
patchMeta(id, patch) {
|
|
168
|
+
return this.#send("PATCH", `/v1/conversations/${id}/meta`, patch);
|
|
169
|
+
}
|
|
170
|
+
/** `GET /v1/conversations/:id/interactions` — pending interactions. */
|
|
171
|
+
listInteractions(id) {
|
|
172
|
+
return this.#get(`/v1/conversations/${id}/interactions`);
|
|
173
|
+
}
|
|
174
|
+
/** `GET .../interactions/:callId` — one record in ANY status (the
|
|
175
|
+
* settle-refetch path: an answer that lands on an already-settled
|
|
176
|
+
* record (404 not_pending) refetches here and renders it read-only). */
|
|
177
|
+
getInteraction(id, callId) {
|
|
178
|
+
return this.#get(`/v1/conversations/${id}/interactions/${encodeURIComponent(callId)}`);
|
|
179
|
+
}
|
|
180
|
+
/** `POST .../interactions/:callId/answer` — the ONE generic answer route. */
|
|
181
|
+
answerInteraction(id, callId, answer) {
|
|
182
|
+
return this.#send("POST", `/v1/conversations/${id}/interactions/${encodeURIComponent(callId)}/answer`, answer);
|
|
183
|
+
}
|
|
184
|
+
/** `GET /v1/conversations/:id/ambient`. */
|
|
185
|
+
getAmbient(id) {
|
|
186
|
+
return this.#get(`/v1/conversations/${id}/ambient`);
|
|
187
|
+
}
|
|
188
|
+
/** `GET /v1/commands` — the slash-command catalog (a boot constant). */
|
|
189
|
+
getCommands() {
|
|
190
|
+
return this.#get("/v1/commands");
|
|
191
|
+
}
|
|
192
|
+
/** `GET /v1/extensions` — the loaded-extension inventory (U3 Task 5:
|
|
193
|
+
* the sidebar's Views group + panels read the declared surfaces). */
|
|
194
|
+
listExtensions() {
|
|
195
|
+
return this.#get("/v1/extensions");
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* `POST /v1/conversations/:id/commands/:name` — invoke an extension
|
|
199
|
+
* slash command (W5 Task 3): the server runs the winning declaration
|
|
200
|
+
* and resolves with its optional `{notice}`.
|
|
201
|
+
*/
|
|
202
|
+
runCommand(id, name, args) {
|
|
203
|
+
return this.#send("POST", `/v1/conversations/${id}/commands/${encodeURIComponent(name)}`, {
|
|
204
|
+
args: args ?? "",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** `GET /v1/settings` — the effective settings (absent file = `{}`). */
|
|
208
|
+
getSettings() {
|
|
209
|
+
return this.#get("/v1/settings");
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* `GET /v1/settings/sections` (UI-blocks U4 Task 4): the settings
|
|
213
|
+
* panel's nav list + search index in one payload.
|
|
214
|
+
*/
|
|
215
|
+
listSettingsSections() {
|
|
216
|
+
return this.#get("/v1/settings/sections");
|
|
217
|
+
}
|
|
218
|
+
/** `GET /v1/auth/whoami` — the resolved principal (the settings
|
|
219
|
+
* callout's admin gate). */
|
|
220
|
+
whoami() {
|
|
221
|
+
return this.#get("/v1/auth/whoami");
|
|
222
|
+
}
|
|
223
|
+
/** `GET /v1/meta` — the boot facts (the settings callout's
|
|
224
|
+
* `supervised` read). */
|
|
225
|
+
getBootMeta() {
|
|
226
|
+
return this.#get("/v1/meta");
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* `POST /v1/admin/restart` (admin; U4 Task 2, spec §13.3): resolves
|
|
230
|
+
* once the server answered 202 (the restart begins after the reply);
|
|
231
|
+
* rejects on 403/409 (the restart never began).
|
|
232
|
+
*/
|
|
233
|
+
async requestRestart() {
|
|
234
|
+
await this.#send("POST", "/v1/admin/restart", {});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* `GET /readyz` readiness probe (U4 Task 2): `true` only on a 200.
|
|
238
|
+
* The restart-recovery poll rides this; a connection-level failure
|
|
239
|
+
* (the process mid-restart) resolves `false`, never throws.
|
|
240
|
+
*/
|
|
241
|
+
async checkReady() {
|
|
242
|
+
try {
|
|
243
|
+
const response = await this.#fetch(`${this.#baseUrl}/readyz`, {
|
|
244
|
+
method: "GET",
|
|
245
|
+
headers: this.#headers,
|
|
246
|
+
});
|
|
247
|
+
return response.ok;
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** `GET /v1/models` — the chat-model catalog (the model picker's items). */
|
|
254
|
+
listModels() {
|
|
255
|
+
return this.#get("/v1/models");
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* `POST /v1/conversations/:id/configure` — change the model and/or
|
|
259
|
+
* thinking level (T9c-i). At least one field is required (the server
|
|
260
|
+
* rejects an empty change as `invalid_body`).
|
|
261
|
+
*/
|
|
262
|
+
configure(id, body) {
|
|
263
|
+
return this.#send("POST", `/v1/conversations/${id}/configure`, body);
|
|
264
|
+
}
|
|
265
|
+
/** `GET /v1/jobs` — the caller-visible jobs (`/jobs`, W4 Task 5). */
|
|
266
|
+
listJobs() {
|
|
267
|
+
return this.#get("/v1/jobs");
|
|
268
|
+
}
|
|
269
|
+
/** `POST /v1/jobs/:id/run` — fire a job now (write access). */
|
|
270
|
+
runJob(id) {
|
|
271
|
+
return this.#send("POST", `/v1/jobs/${encodeURIComponent(id)}/run`, {});
|
|
272
|
+
}
|
|
273
|
+
/** `GET /v1/jobs/:id/runs` — the job's runs, newest first. */
|
|
274
|
+
listJobRuns(id, limit) {
|
|
275
|
+
return this.#get(`/v1/jobs/${encodeURIComponent(id)}/runs` +
|
|
276
|
+
(limit === undefined ? "" : `?limit=${limit}`));
|
|
277
|
+
}
|
|
278
|
+
/** `GET /v1/jobs/:id/revisions` — the job's revisions, newest first. */
|
|
279
|
+
listJobRevisions(id) {
|
|
280
|
+
return this.#get(`/v1/jobs/${encodeURIComponent(id)}/revisions`);
|
|
281
|
+
}
|
|
282
|
+
}
|