@achasoft/dsh-advanced-sidebar 0.1.0 → 0.3.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 +279 -128
- package/cordis.patch.yml +31 -3
- package/lib/client.js +2803 -466
- package/lib/client.js.map +1 -1
- package/lib/host.js +2071 -418
- package/lib/index.js +6 -2
- package/lib/preview-content-BVUQ5oOR.js +465 -0
- package/lib/remote.js +330 -25
- package/lib/typert.host.js +330 -25
- package/lib/ui-preview.js +352 -0
- package/package.json +8 -2
- package/types/client/ActionMenu.d.ts +16 -1
- package/types/client/LogDownloadDialog.d.ts +24 -0
- package/types/client/contract.d.ts +57 -1
- package/types/client/index.d.ts +4 -2
- package/types/client/locales.d.ts +100 -0
- package/types/client/log-download.d.ts +179 -0
- package/types/client/panels/PreviewPanel.d.ts +20 -15
- package/types/client/panels/preview-file.d.ts +61 -0
- package/types/client/panels/preview-mode.d.ts +67 -0
- package/types/client/panels/preview-scratchpad.d.ts +53 -0
- package/types/client/panels/preview-url.d.ts +17 -0
- package/types/client/panels/shared.d.ts +15 -2
- package/types/client/preview-driver.d.ts +121 -0
- package/types/client/preview-storage.d.ts +43 -0
- package/types/client/preview-types.d.ts +21 -0
- package/types/client/preview-values.d.ts +43 -0
- package/types/host/deletion.d.ts +32 -23
- package/types/host/git.d.ts +94 -8
- package/types/host/index.d.ts +97 -5
- package/types/host/preview-content.d.ts +179 -0
- package/types/host/preview-serve.d.ts +242 -0
- package/types/host/settings-section.d.ts +49 -0
- package/types/host/types.d.ts +341 -0
- package/types/host/ui-bridge.d.ts +197 -0
- package/types/host/ui-preview-tool.d.ts +60 -0
- package/types/index.d.ts +6 -2
- package/types/ui-preview.d.ts +11 -0
package/types/host/types.d.ts
CHANGED
|
@@ -124,6 +124,25 @@ export interface AdvancedSidebarSettings {
|
|
|
124
124
|
previewScrollback: number;
|
|
125
125
|
/** TERM-to-KILL grace when a preview server is stopped. */
|
|
126
126
|
previewGraceMs: number;
|
|
127
|
+
/**
|
|
128
|
+
* Largest workspace file a preview mode will hand to the browser, in bytes.
|
|
129
|
+
*
|
|
130
|
+
* Separate from `filesMaxPreviewBytes`, which bounds a TEXT preview rendered inside the panel:
|
|
131
|
+
* this one bounds a media response streamed into a frame, and a video is legitimately far larger
|
|
132
|
+
* than any file worth reading as text. Past it the panel reports the size and offers
|
|
133
|
+
* "open with the default application" instead.
|
|
134
|
+
*/
|
|
135
|
+
readonly previewMaxFileBytes: number;
|
|
136
|
+
/** Wall-clock bound on one proxied upstream request, in milliseconds. */
|
|
137
|
+
readonly previewProxyTimeoutMs: number;
|
|
138
|
+
/**
|
|
139
|
+
* How long an agent command waits for the panel to answer before the tool reports a timeout,
|
|
140
|
+
* in milliseconds. Long enough for a frame load plus an `open`, short enough that a model turn is
|
|
141
|
+
* not spent waiting on a browser tab that is not there.
|
|
142
|
+
*/
|
|
143
|
+
readonly previewCommandTimeoutMs: number;
|
|
144
|
+
/** How long a bind is trusted after its last poll before the surface is reported as unmounted. */
|
|
145
|
+
readonly previewBindTtlMs: number;
|
|
127
146
|
}
|
|
128
147
|
/** Availability of one Host-backed panel, with the reason when it is unavailable. */
|
|
129
148
|
export interface CapabilityState {
|
|
@@ -157,6 +176,15 @@ export interface AdvancedSidebarView {
|
|
|
157
176
|
readonly preview: CapabilityState & {
|
|
158
177
|
/** How many preview servers this plugin currently holds open. */
|
|
159
178
|
readonly running: number;
|
|
179
|
+
/**
|
|
180
|
+
* Where the same-origin preview routes are, when the Host serves them.
|
|
181
|
+
*
|
|
182
|
+
* Absent means no `webServer` capability is mounted, which is the headless case: the Server and
|
|
183
|
+
* URL modes still work through the frame's own origin, but a workspace file cannot be framed at
|
|
184
|
+
* all and the agent cannot inspect a cross-origin page. The panel says so rather than offering a
|
|
185
|
+
* mode that cannot work.
|
|
186
|
+
*/
|
|
187
|
+
readonly surface?: PreviewSurfaceInfo;
|
|
160
188
|
};
|
|
161
189
|
/** Whether a job registry is mounted, and what may be done to a record. */
|
|
162
190
|
readonly tasks: CapabilityState & {
|
|
@@ -875,3 +903,316 @@ export interface PreviewLogsSuccess {
|
|
|
875
903
|
}
|
|
876
904
|
/** Log read, or a classified failure. */
|
|
877
905
|
export type PreviewLogsResult = PreviewLogsSuccess | PreviewFailure;
|
|
906
|
+
/**
|
|
907
|
+
* How the browser should present one workspace file.
|
|
908
|
+
*
|
|
909
|
+
* The kinds are the browser's own vocabulary on purpose, not MIME types: `iframe` covers HTML,
|
|
910
|
+
* `markdown` covers a text file this panel renders itself, `image`/`media`/`pdf` are handed
|
|
911
|
+
* straight to a native element, `text` is shown in the monospace reader, and `other` is the honest
|
|
912
|
+
* "there is nothing to render here" state that still offers the file's size and its OS application.
|
|
913
|
+
*/
|
|
914
|
+
export type PreviewFileKind = 'iframe' | 'markdown' | 'image' | 'media' | 'pdf' | 'text' | 'other';
|
|
915
|
+
/** What one workspace file is, and where it can be framed from. */
|
|
916
|
+
export interface PreviewFileInfo {
|
|
917
|
+
/** Absolute Host path that was resolved and contained. */
|
|
918
|
+
readonly path: string;
|
|
919
|
+
/** Basename, for a title. */
|
|
920
|
+
readonly name: string;
|
|
921
|
+
/** How the browser should present it. */
|
|
922
|
+
readonly kind: PreviewFileKind;
|
|
923
|
+
/** MIME type for the frame's response, or `text/plain` for a kind that is never framed. */
|
|
924
|
+
readonly contentType: string;
|
|
925
|
+
/** Size on disk in bytes. */
|
|
926
|
+
readonly bytes: number;
|
|
927
|
+
/**
|
|
928
|
+
* Whether the file is small enough for `previewMaxFileBytes`.
|
|
929
|
+
*
|
|
930
|
+
* A file over the cap is reported, not refused: the panel shows the size and the OS application,
|
|
931
|
+
* which is more useful than an error about a limit nobody can see.
|
|
932
|
+
*/
|
|
933
|
+
readonly withinLimit: boolean;
|
|
934
|
+
/**
|
|
935
|
+
* Same-origin URL the file can be framed from, when the Host serves files.
|
|
936
|
+
*
|
|
937
|
+
* Absent when no `webServer` is mounted; then only the non-framed kinds are offered.
|
|
938
|
+
*/
|
|
939
|
+
readonly url?: string;
|
|
940
|
+
/**
|
|
941
|
+
* Opaque token that changes when the file's content or size does.
|
|
942
|
+
*
|
|
943
|
+
* The panel re-reads it on a quiet poll and reloads the frame when it moves, which is what makes
|
|
944
|
+
* an edit on disk show up without a manual refresh. Deliberately not a timestamp: a backend may
|
|
945
|
+
* not expose one, and a token is the interface `ctx.fs` already offers.
|
|
946
|
+
*/
|
|
947
|
+
readonly token: string;
|
|
948
|
+
/** True when the file is a regular file; a directory or a socket is reported as `other`. */
|
|
949
|
+
readonly regular: boolean;
|
|
950
|
+
}
|
|
951
|
+
/** Why a file could not be described. */
|
|
952
|
+
export type PreviewFileFailureCode =
|
|
953
|
+
/** No filesystem capability is mounted. */
|
|
954
|
+
'no-filesystem'
|
|
955
|
+
/** The workspace or the path left the workspace. */
|
|
956
|
+
| 'path-denied'
|
|
957
|
+
/** Nothing is at the path, or it is not a regular file. */
|
|
958
|
+
| 'not-a-file'
|
|
959
|
+
/** The filesystem refused the metadata read; the message carries its error. */
|
|
960
|
+
| 'read-failed';
|
|
961
|
+
/** A classified file-info failure, carried as a value. */
|
|
962
|
+
export interface PreviewFileFailure {
|
|
963
|
+
readonly ok: false;
|
|
964
|
+
readonly code: PreviewFileFailureCode;
|
|
965
|
+
readonly message: string;
|
|
966
|
+
}
|
|
967
|
+
/** File metadata, or a classified failure. */
|
|
968
|
+
export type PreviewFileInfoResult = ({
|
|
969
|
+
readonly ok: true;
|
|
970
|
+
} & PreviewFileInfo) | PreviewFileFailure;
|
|
971
|
+
/** Describe one workspace file for the Preview panel. */
|
|
972
|
+
export interface PreviewFileInfoRequest {
|
|
973
|
+
/** Absolute Host workspace directory the path must stay inside. */
|
|
974
|
+
readonly workspacePath: string;
|
|
975
|
+
/** Absolute Host path, or a path relative to the workspace — the panel sends what it has. */
|
|
976
|
+
readonly path: string;
|
|
977
|
+
}
|
|
978
|
+
/** Where an agent asked the panel to point itself. */
|
|
979
|
+
export type PreviewRequestedMode = 'server' | 'file' | 'url' | 'scratchpad';
|
|
980
|
+
/** Ask one panel: where are you, and can you take a command? */
|
|
981
|
+
export interface PreviewBindRequest {
|
|
982
|
+
/** Identifies this browser tab, so commands reach the panel that answered last. */
|
|
983
|
+
readonly clientId: string;
|
|
984
|
+
/** The session whose panel this is. */
|
|
985
|
+
readonly sessionId: string;
|
|
986
|
+
/** Which mode the panel is showing. */
|
|
987
|
+
readonly mode: PreviewRequestedMode;
|
|
988
|
+
/** The file being previewed, when the mode is `file`. */
|
|
989
|
+
readonly filePath?: string;
|
|
990
|
+
/** The absolute workspace every path this panel sends must stay inside. */
|
|
991
|
+
readonly workspacePath?: string;
|
|
992
|
+
/** What the mode is currently pointing the frame at, when it points anywhere. */
|
|
993
|
+
readonly url?: string;
|
|
994
|
+
/** True when the frame's document is same-origin with the GUI, so the agent may inspect it. */
|
|
995
|
+
readonly inspectable: boolean;
|
|
996
|
+
/** Frame viewport height in CSS pixels. */
|
|
997
|
+
readonly width: number;
|
|
998
|
+
/** Frame viewport height in CSS pixels. */
|
|
999
|
+
readonly height: number;
|
|
1000
|
+
}
|
|
1001
|
+
/** One class of result an agent command can produce. */
|
|
1002
|
+
export type PreviewResultKind = 'open' | 'dom' | 'eval' | 'console' | 'click' | 'input' | 'reload' | 'resize' | 'close';
|
|
1003
|
+
/** One element of a DOM reading. */
|
|
1004
|
+
export interface PreviewDomNode {
|
|
1005
|
+
/** Lower-case tag name. */
|
|
1006
|
+
readonly tag: string;
|
|
1007
|
+
/** `id` and `class` as one readable selector fragment, empty when the element has neither. */
|
|
1008
|
+
readonly selector: string;
|
|
1009
|
+
/** The element's own direct text, collapsed and cut. */
|
|
1010
|
+
readonly text: string;
|
|
1011
|
+
/** Computed `display`. */
|
|
1012
|
+
readonly display: string;
|
|
1013
|
+
/** Rendered border box in CSS pixels, relative to the frame's viewport. */
|
|
1014
|
+
readonly box: {
|
|
1015
|
+
x: number;
|
|
1016
|
+
y: number;
|
|
1017
|
+
width: number;
|
|
1018
|
+
height: number;
|
|
1019
|
+
};
|
|
1020
|
+
/** Nesting depth below the requested root. */
|
|
1021
|
+
readonly depth: number;
|
|
1022
|
+
}
|
|
1023
|
+
/** What one `dom` command answered. */
|
|
1024
|
+
export interface PreviewDomResult {
|
|
1025
|
+
readonly kind: 'dom';
|
|
1026
|
+
/** The selector the reading was taken from; empty means the document element. */
|
|
1027
|
+
readonly selector: string;
|
|
1028
|
+
/** The frame's inner size in CSS pixels. */
|
|
1029
|
+
readonly viewport: {
|
|
1030
|
+
width: number;
|
|
1031
|
+
height: number;
|
|
1032
|
+
};
|
|
1033
|
+
/** Matching elements, breadth-first, capped by the tool's own ceiling. */
|
|
1034
|
+
readonly nodes: readonly PreviewDomNode[];
|
|
1035
|
+
/** The root element's `innerText`, collapsed and cut. */
|
|
1036
|
+
readonly text: string;
|
|
1037
|
+
/** True when the cap cut the walk short. */
|
|
1038
|
+
readonly truncated: boolean;
|
|
1039
|
+
/** The frame's URL as the browser reports it, so a redirect is visible. */
|
|
1040
|
+
readonly url: string;
|
|
1041
|
+
}
|
|
1042
|
+
/** What one `eval` command answered. */
|
|
1043
|
+
export interface PreviewEvalResult {
|
|
1044
|
+
readonly kind: 'eval';
|
|
1045
|
+
/**
|
|
1046
|
+
* The expression's value, serialized to JSON.
|
|
1047
|
+
*
|
|
1048
|
+
* A value JSON cannot represent arrives as its `String(value)` — plus a `note` — rather than
|
|
1049
|
+
* failing the command: a DOM node or a function is an ordinary thing to evaluate to, and
|
|
1050
|
+
* `"null"` with no explanation is a worse answer than `"[object HTMLDivElement]"`.
|
|
1051
|
+
*/
|
|
1052
|
+
readonly value: string;
|
|
1053
|
+
/** Why the value is not JSON, when it is not. */
|
|
1054
|
+
readonly note?: string;
|
|
1055
|
+
/** True when the value's JSON form was cut at the tool's ceiling. */
|
|
1056
|
+
readonly truncated: boolean;
|
|
1057
|
+
}
|
|
1058
|
+
/** One buffered console line or uncaught error. */
|
|
1059
|
+
export interface PreviewConsoleEntry {
|
|
1060
|
+
/** Which sink produced it. */
|
|
1061
|
+
readonly level: 'log' | 'info' | 'warn' | 'error' | 'uncaught' | 'rejection';
|
|
1062
|
+
/** The text, with its arguments joined. */
|
|
1063
|
+
readonly text: string;
|
|
1064
|
+
/** Epoch ms, in the frame's own clock. */
|
|
1065
|
+
readonly at: number;
|
|
1066
|
+
}
|
|
1067
|
+
/** What one `console` command answered. */
|
|
1068
|
+
export interface PreviewConsoleResult {
|
|
1069
|
+
readonly kind: 'console';
|
|
1070
|
+
/** Entries after the requested cursor, oldest first. */
|
|
1071
|
+
readonly entries: readonly PreviewConsoleEntry[];
|
|
1072
|
+
/** Cursor to pass to the next `console` command. */
|
|
1073
|
+
readonly cursor: number;
|
|
1074
|
+
/** True when the requested cursor had already fallen out of the retained window. */
|
|
1075
|
+
readonly lossy: boolean;
|
|
1076
|
+
}
|
|
1077
|
+
/** What one `reload`/`resize` command answered. */
|
|
1078
|
+
export interface PreviewAckResult {
|
|
1079
|
+
readonly kind: 'ack';
|
|
1080
|
+
/** One line describing what happened, for the tool's text block. */
|
|
1081
|
+
readonly detail: string;
|
|
1082
|
+
/** The viewport after the command, when it changed one. */
|
|
1083
|
+
readonly width?: number;
|
|
1084
|
+
/** The viewport height after the command, when it changed one. */
|
|
1085
|
+
readonly height?: number;
|
|
1086
|
+
}
|
|
1087
|
+
/** Every successful command result. */
|
|
1088
|
+
export type PreviewCommandResult = PreviewDomResult | PreviewEvalResult | PreviewConsoleResult | PreviewAckResult;
|
|
1089
|
+
/** One command the panel must execute against its frame. */
|
|
1090
|
+
export interface PreviewCommand {
|
|
1091
|
+
/** Echo of the request id, which the result must carry back. */
|
|
1092
|
+
readonly id: string;
|
|
1093
|
+
/** The panel this command belongs to; a panel ignores a command addressed elsewhere. */
|
|
1094
|
+
readonly clientId: string;
|
|
1095
|
+
/** What to do. */
|
|
1096
|
+
readonly kind: PreviewResultKind;
|
|
1097
|
+
/** CSS selector for `dom`, `click`, and `type`. */
|
|
1098
|
+
readonly selector?: string;
|
|
1099
|
+
/** JavaScript source for `eval`. */
|
|
1100
|
+
readonly expression?: string;
|
|
1101
|
+
/** Cursor already consumed by a previous `console`. */
|
|
1102
|
+
readonly cursor?: number;
|
|
1103
|
+
/** Text `type` must set before dispatching its events. */
|
|
1104
|
+
readonly text?: string;
|
|
1105
|
+
/** Key `type` must dispatch after setting the value, e.g. `Enter`. */
|
|
1106
|
+
readonly key?: string;
|
|
1107
|
+
/** Viewport `resize` must apply. */
|
|
1108
|
+
readonly width?: number;
|
|
1109
|
+
/** Viewport height `resize` must apply. */
|
|
1110
|
+
readonly height?: number;
|
|
1111
|
+
/** How long the panel may spend on it before giving up, in milliseconds. */
|
|
1112
|
+
readonly timeoutMs: number;
|
|
1113
|
+
}
|
|
1114
|
+
/** Where an agent asked the panel to point itself, as a control message. */
|
|
1115
|
+
export interface PreviewOpenMessage {
|
|
1116
|
+
/** The panel this message belongs to. */
|
|
1117
|
+
readonly clientId: string;
|
|
1118
|
+
/** Which mode to show. */
|
|
1119
|
+
readonly mode: PreviewRequestedMode;
|
|
1120
|
+
/** Which file to preview, when the mode is `file`. */
|
|
1121
|
+
readonly filePath?: string;
|
|
1122
|
+
/** Which URL to point at, when the mode is `url`. */
|
|
1123
|
+
readonly url?: string;
|
|
1124
|
+
/** Absolute workspace the file path is relative to. */
|
|
1125
|
+
readonly workspacePath?: string;
|
|
1126
|
+
}
|
|
1127
|
+
/** Apply one open request to the panel's own state. */
|
|
1128
|
+
export interface PreviewControlMessage {
|
|
1129
|
+
/** Discriminator for the message union. */
|
|
1130
|
+
readonly control: 'open';
|
|
1131
|
+
/** What to open. */
|
|
1132
|
+
readonly open: PreviewOpenMessage;
|
|
1133
|
+
}
|
|
1134
|
+
/** Everything the panel must act on after one poll. */
|
|
1135
|
+
export interface PreviewMessage {
|
|
1136
|
+
/** Commands to execute, oldest first. */
|
|
1137
|
+
readonly commands: readonly PreviewCommand[];
|
|
1138
|
+
/** Mode changes to apply. */
|
|
1139
|
+
readonly controls: readonly PreviewControlMessage[];
|
|
1140
|
+
}
|
|
1141
|
+
/** Ask for the work queued against one panel. */
|
|
1142
|
+
export interface PreviewPollRequest {
|
|
1143
|
+
/** The panel asking. */
|
|
1144
|
+
readonly clientId: string;
|
|
1145
|
+
/** The session that panel belongs to. */
|
|
1146
|
+
readonly sessionId: string;
|
|
1147
|
+
/** True while a preview is actually mounted; a closed panel stops claiming commands. */
|
|
1148
|
+
readonly mounted: boolean;
|
|
1149
|
+
/** What the panel is doing right now. */
|
|
1150
|
+
readonly bind: PreviewBindRequest;
|
|
1151
|
+
}
|
|
1152
|
+
/** The panel's next work, or a classified failure. */
|
|
1153
|
+
export interface PreviewPollSuccess {
|
|
1154
|
+
readonly ok: true;
|
|
1155
|
+
/** The work, possibly empty. */
|
|
1156
|
+
readonly message: PreviewMessage;
|
|
1157
|
+
/**
|
|
1158
|
+
* Epoch ms after which this poll's bind is stale.
|
|
1159
|
+
*
|
|
1160
|
+
* The panel echoes nothing back; it simply keeps polling. The Host uses the interval to decide
|
|
1161
|
+
* that a tab was closed or navigated away, which is what makes the agent's next command fail with
|
|
1162
|
+
* "no preview surface" instead of waiting out its timeout.
|
|
1163
|
+
*/
|
|
1164
|
+
readonly bindTtlMs: number;
|
|
1165
|
+
}
|
|
1166
|
+
/** Why a panel could not be polled. */
|
|
1167
|
+
export type PreviewPollFailureCode =
|
|
1168
|
+
/** No preview server capability is mounted, so there is nothing to report. */
|
|
1169
|
+
'no-subprocess'
|
|
1170
|
+
/** The plugin is unloading. */
|
|
1171
|
+
| 'closed';
|
|
1172
|
+
/** A classified poll failure, carried as a value. */
|
|
1173
|
+
export interface PreviewPollFailure {
|
|
1174
|
+
readonly ok: false;
|
|
1175
|
+
readonly code: PreviewPollFailureCode;
|
|
1176
|
+
readonly message: string;
|
|
1177
|
+
}
|
|
1178
|
+
/** Poll outcome. */
|
|
1179
|
+
export type PreviewPollResult = PreviewPollSuccess | PreviewPollFailure;
|
|
1180
|
+
/** Report what one command did. */
|
|
1181
|
+
export interface PreviewResultRequest {
|
|
1182
|
+
/** The panel reporting. */
|
|
1183
|
+
readonly clientId: string;
|
|
1184
|
+
/** Echo of {@link PreviewCommand.id}. */
|
|
1185
|
+
readonly id: string;
|
|
1186
|
+
/** True when the command was executed. */
|
|
1187
|
+
readonly ok: boolean;
|
|
1188
|
+
/** Why it was not, when it was not. */
|
|
1189
|
+
readonly error?: string;
|
|
1190
|
+
/** What it produced, when it was. */
|
|
1191
|
+
readonly result?: PreviewCommandResult;
|
|
1192
|
+
/** Console entries observed since the panel last reported; appended to the Host's buffer. */
|
|
1193
|
+
readonly console?: readonly PreviewConsoleEntry[];
|
|
1194
|
+
}
|
|
1195
|
+
/** Settlement of a result report. */
|
|
1196
|
+
export type PreviewResultAck = {
|
|
1197
|
+
readonly ok: true;
|
|
1198
|
+
} | PreviewPollFailure;
|
|
1199
|
+
/** Say that a panel is closed, so its queued work is dropped. */
|
|
1200
|
+
export interface PreviewReleaseRequest {
|
|
1201
|
+
/** The panel that closed. */
|
|
1202
|
+
readonly clientId: string;
|
|
1203
|
+
}
|
|
1204
|
+
/** Settlement of a release. */
|
|
1205
|
+
export type PreviewReleaseResult = {
|
|
1206
|
+
readonly ok: true;
|
|
1207
|
+
} | PreviewPollFailure;
|
|
1208
|
+
/** Where the same-origin preview routes live. */
|
|
1209
|
+
export interface PreviewSurfaceInfo {
|
|
1210
|
+
/** Absolute path of the workspace-file route, without a trailing slash. */
|
|
1211
|
+
readonly fileRoute: string;
|
|
1212
|
+
/** Absolute path prefix of the loopback reverse proxy, without a trailing slash. */
|
|
1213
|
+
readonly proxyRoute: string;
|
|
1214
|
+
/** Whether this Host has a `webServer` capability at all. */
|
|
1215
|
+
readonly available: boolean;
|
|
1216
|
+
/** Why it does not, when it does not. */
|
|
1217
|
+
readonly reason?: string;
|
|
1218
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host↔browser channel behind the model-facing `ui_preview` tool.
|
|
3
|
+
*
|
|
4
|
+
* An out-of-tree plugin cannot add a wire frame, so there is no push channel from the Host to a
|
|
5
|
+
* browser tab: the browser half already polls this plugin's Typert endpoints, and the tool uses that
|
|
6
|
+
* same path backwards. A command is a record in a queue; the panel's driver polls for its queue,
|
|
7
|
+
* executes what it finds against the frame, and posts the result back. This module owns the queue,
|
|
8
|
+
* the results, and — most importantly — the deadlines, because a browser tab that is not there must
|
|
9
|
+
* fail a tool call with a sentence rather than hang it until the model's turn times out.
|
|
10
|
+
*
|
|
11
|
+
* Three pieces of state, and each one exists for a distinct failure:
|
|
12
|
+
*
|
|
13
|
+
* - **A per-panel queue** of undelivered work, with a hard cap. A panel that never polls cannot make
|
|
14
|
+
* the Host hold an unbounded backlog.
|
|
15
|
+
* - **The in-flight records**, keyed by command id. This is what `await`s a result.
|
|
16
|
+
* - **A liveness stamp** per panel, refreshed by every poll. Without it, "the tool is waiting" and
|
|
17
|
+
* "nobody is looking at the panel" are indistinguishable, and the operator gets a timeout instead
|
|
18
|
+
* of an explanation.
|
|
19
|
+
*
|
|
20
|
+
* Nothing here is model-facing and nothing here is trusted: the panel that posts a result is the
|
|
21
|
+
* operator's own browser, but the payload is validated by shape before it is handed to a tool.
|
|
22
|
+
* @module @achasoft/dsh-advanced-sidebar/host/ui-bridge
|
|
23
|
+
*/
|
|
24
|
+
import type { PreviewBindRequest, PreviewCommand, PreviewCommandResult, PreviewConsoleEntry, PreviewControlMessage, PreviewMessage } from './types.ts';
|
|
25
|
+
/** Queue and liveness configuration, read from the settings section per call. */
|
|
26
|
+
export interface BridgeOptions {
|
|
27
|
+
/** How long one command waits for its result before the tool reports a timeout. */
|
|
28
|
+
readonly commandTimeoutMs: number;
|
|
29
|
+
/** How long a bind is trusted after its last poll. */
|
|
30
|
+
readonly bindTtlMs: number;
|
|
31
|
+
/** Injected clock, so a test can move time without waiting for it. */
|
|
32
|
+
readonly now?: () => number;
|
|
33
|
+
}
|
|
34
|
+
/** Outcome of queueing a command: either an answer, or why there will not be one. */
|
|
35
|
+
export type QueueOutcome = {
|
|
36
|
+
readonly ok: true;
|
|
37
|
+
readonly result: PreviewCommandResult;
|
|
38
|
+
} | {
|
|
39
|
+
readonly ok: false;
|
|
40
|
+
readonly code: 'no-surface' | 'timeout';
|
|
41
|
+
readonly message: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The command queue, the panel registry, and the console buffer.
|
|
45
|
+
*
|
|
46
|
+
* Held by the Host service and disposed with it, so no timer survives an unload.
|
|
47
|
+
*/
|
|
48
|
+
export declare class PreviewBindings {
|
|
49
|
+
private readonly options;
|
|
50
|
+
private readonly panels;
|
|
51
|
+
/**
|
|
52
|
+
* Panels that said they were done, so a later bind cannot resurrect them.
|
|
53
|
+
*
|
|
54
|
+
* A closed dock's queued work must fail, and "closed" is a fact only the browser knows: without
|
|
55
|
+
* this, a bind request the Host never asked for would put a dead tab back on the roster and the
|
|
56
|
+
* model would wait on it. A fresh mount generates a fresh client id, so a reload is unaffected.
|
|
57
|
+
*/
|
|
58
|
+
private readonly retired;
|
|
59
|
+
private readonly inFlight;
|
|
60
|
+
private closed;
|
|
61
|
+
/**
|
|
62
|
+
* @param options - reads the current deadlines; called per operation so an edited settings
|
|
63
|
+
* section reaches the next command with no registration to rebuild.
|
|
64
|
+
*/
|
|
65
|
+
constructor(options: () => BridgeOptions);
|
|
66
|
+
/** How many panels have polled recently enough to be considered present. */
|
|
67
|
+
get livePanels(): number;
|
|
68
|
+
/**
|
|
69
|
+
* Record a panel's state. Called by every poll, so it doubles as the liveness heartbeat.
|
|
70
|
+
* @param bind - what the panel reported.
|
|
71
|
+
* @returns the panel's id, for a caller that wants to address it later.
|
|
72
|
+
*/
|
|
73
|
+
bind(bind: PreviewBindRequest): string;
|
|
74
|
+
/**
|
|
75
|
+
* Take everything queued for one panel.
|
|
76
|
+
*
|
|
77
|
+
* The poll is also the heartbeat, so a stale panel is refreshed even when it has no work: what the
|
|
78
|
+
* tool needs to know is that the tab is alive, not that it is busy.
|
|
79
|
+
* @param clientId - the panel asking.
|
|
80
|
+
* @param mounted - whether a preview surface is actually rendered; false leaves the queue alone.
|
|
81
|
+
* @param console - entries the panel observed since its last report.
|
|
82
|
+
* @returns the work to execute and the interval after which this poll is stale.
|
|
83
|
+
*/
|
|
84
|
+
poll(clientId: string, mounted: boolean, console?: readonly PreviewConsoleEntry[]): {
|
|
85
|
+
message: PreviewMessage;
|
|
86
|
+
bindTtlMs: number;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* A panel's state, or the panel itself when it has never reported in.
|
|
90
|
+
*
|
|
91
|
+
* This is what lets a tool call wake a dock that has not polled yet — the operator opens the
|
|
92
|
+
* Preview panel and a model asks for a DOM reading in the same second. Only the FIRST bind is
|
|
93
|
+
* stored without a poll behind it: once a panel is known, its liveness rules, so a closed tab
|
|
94
|
+
* cannot be resurrected by a bind request it never sent.
|
|
95
|
+
* @param bind - what the panel reported.
|
|
96
|
+
* @returns nothing; the caller polls afterwards.
|
|
97
|
+
*/
|
|
98
|
+
bindAt(bind: PreviewBindRequest): void;
|
|
99
|
+
/**
|
|
100
|
+
* Record what one command did, and append any console lines it carried.
|
|
101
|
+
*
|
|
102
|
+
* A result whose command already timed out is accepted and dropped: the deadline fired, the tool
|
|
103
|
+
* has its answer, and an operator's panel must not be told its report was invalid. A result of the
|
|
104
|
+
* WRONG KIND is a different matter — it means the two halves disagree about the command, which is
|
|
105
|
+
* a defect worth reporting rather than a late answer worth ignoring.
|
|
106
|
+
* @param clientId - the panel reporting.
|
|
107
|
+
* @param id - the command id.
|
|
108
|
+
* @param outcome - success with a result, or the reason it failed.
|
|
109
|
+
* @param console - entries the panel observed alongside the result.
|
|
110
|
+
* @returns true when the id matched an in-flight command.
|
|
111
|
+
*/
|
|
112
|
+
post(clientId: string, id: string, outcome: {
|
|
113
|
+
ok: true;
|
|
114
|
+
result: PreviewCommandResult;
|
|
115
|
+
} | {
|
|
116
|
+
ok: false;
|
|
117
|
+
error: string;
|
|
118
|
+
}, console?: readonly PreviewConsoleEntry[]): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Forget a panel and fail everything it was holding.
|
|
121
|
+
* @param clientId - the panel that closed.
|
|
122
|
+
*/
|
|
123
|
+
release(clientId: string): void;
|
|
124
|
+
/**
|
|
125
|
+
* Whether one panel is present and able to take a command.
|
|
126
|
+
* @param clientId - the panel id, or undefined to ask about any panel in a session.
|
|
127
|
+
* @param sessionId - the session the panel must belong to.
|
|
128
|
+
* @returns the live panel's id, or undefined.
|
|
129
|
+
*/
|
|
130
|
+
active(sessionId?: string, clientId?: string): string | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* The last thing one panel reported about itself.
|
|
133
|
+
* @param clientId - the panel id.
|
|
134
|
+
* @returns the bind, or undefined for a panel this Host has not heard from.
|
|
135
|
+
*/
|
|
136
|
+
bindOf(clientId: string): PreviewBindRequest | undefined;
|
|
137
|
+
/**
|
|
138
|
+
* Queue one command against a live panel and wait for its answer.
|
|
139
|
+
*
|
|
140
|
+
* Every path out of here is bounded: a missing panel refuses immediately, a queue that is full
|
|
141
|
+
* refuses immediately, and a delivered command that is never answered fails on its own deadline.
|
|
142
|
+
* The one thing this must never do is wait for a browser that is not going to answer.
|
|
143
|
+
* @param sessionId - the session whose panel should execute it.
|
|
144
|
+
* @param command - the command body, without its id or its panel.
|
|
145
|
+
* @returns the result, or the reason there is none.
|
|
146
|
+
*/
|
|
147
|
+
queue(sessionId: string, command: Omit<PreviewCommand, 'id' | 'clientId' | 'timeoutMs'> & {
|
|
148
|
+
timeoutMs?: number;
|
|
149
|
+
}): Promise<QueueOutcome>;
|
|
150
|
+
/**
|
|
151
|
+
* Queue one command against one known panel, without requiring it to be live.
|
|
152
|
+
*
|
|
153
|
+
* Used by `open`, which must be able to hand a mode change to a panel before that panel has had a
|
|
154
|
+
* chance to poll — the very first call against a freshly opened dock.
|
|
155
|
+
* @param clientId - the panel id.
|
|
156
|
+
* @param command - the command body.
|
|
157
|
+
* @returns the result, or the reason there is none.
|
|
158
|
+
*/
|
|
159
|
+
send(clientId: string, command: Omit<PreviewCommand, 'id' | 'clientId' | 'timeoutMs'> & {
|
|
160
|
+
timeoutMs?: number;
|
|
161
|
+
}): Promise<QueueOutcome>;
|
|
162
|
+
/**
|
|
163
|
+
* Queue a mode change that needs no answer.
|
|
164
|
+
* @param clientId - the panel to change.
|
|
165
|
+
* @param control - what to change.
|
|
166
|
+
* @returns false when the panel is unknown.
|
|
167
|
+
*/
|
|
168
|
+
control(clientId: string, control: PreviewControlMessage): boolean;
|
|
169
|
+
/** Forget every panel and fail everything in flight. Called from the plugin's teardown. */
|
|
170
|
+
dispose(): void;
|
|
171
|
+
/**
|
|
172
|
+
* One panel's liveness.
|
|
173
|
+
* @param panel - the panel.
|
|
174
|
+
* @returns true when its last poll is inside the trust window.
|
|
175
|
+
*/
|
|
176
|
+
private isLive;
|
|
177
|
+
/** The configured clock. */
|
|
178
|
+
private now;
|
|
179
|
+
/**
|
|
180
|
+
* Append console entries to a panel's window, dropping the oldest past the cap.
|
|
181
|
+
* @param panel - the panel.
|
|
182
|
+
* @param entries - entries observed since the last report.
|
|
183
|
+
*/
|
|
184
|
+
private appendConsole;
|
|
185
|
+
/**
|
|
186
|
+
* Fail every command one panel is holding and drop its queue.
|
|
187
|
+
* @param clientId - the panel.
|
|
188
|
+
* @param reason - the sentence the tool reports.
|
|
189
|
+
*/
|
|
190
|
+
private dropPanelWork;
|
|
191
|
+
/**
|
|
192
|
+
* Resolve one in-flight command and forget it.
|
|
193
|
+
* @param record - the record.
|
|
194
|
+
* @param outcome - what to resolve with.
|
|
195
|
+
*/
|
|
196
|
+
private finish;
|
|
197
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ui_preview` — the model's hands and eyes on the Preview panel.
|
|
3
|
+
*
|
|
4
|
+
* This is the point of the whole same-origin preview story. A page rendered in the panel is
|
|
5
|
+
* same-origin with the GUI, so a small driver in the panel can read its DOM, run an expression in
|
|
6
|
+
* it, and dispatch a real click — and the model can ask for exactly that through one tool. Without
|
|
7
|
+
* it, "look at your UI" means asking a person to describe a screenshot.
|
|
8
|
+
*
|
|
9
|
+
* One tool with an `action` discriminant is the right shape here, unlike this monorepo's own task
|
|
10
|
+
* tools: every action addresses the same one surface, the arguments genuinely differ per action, and
|
|
11
|
+
* the alternative — nine tools — would spend nine descriptions on one subject. The conditional
|
|
12
|
+
* argument requirements that shape normally costs are paid for with an explicit `require…` check per
|
|
13
|
+
* action, which returns a sentence naming the missing argument instead of a schema failure the model
|
|
14
|
+
* cannot read.
|
|
15
|
+
*
|
|
16
|
+
* **The trust boundary.** Everything this tool accepts is untrusted model input:
|
|
17
|
+
*
|
|
18
|
+
* - `open` with a `path` and every `file` argument are resolved through `resolveWorkspace` /
|
|
19
|
+
* `resolveInside` before anything reads them, so a path cannot leave the session's workspace. A
|
|
20
|
+
* missing workspace is a refusal, never a guess.
|
|
21
|
+
* - `open` with a `url` accepts any `http(s)` URL, which is the same latitude the panel gives a
|
|
22
|
+
* person typing into its address bar. The Host's proxy that actually fetches a loopback target
|
|
23
|
+
* refuses every non-loopback host on its own, so a model cannot use this tool to fetch an intranet
|
|
24
|
+
* service through the GUI.
|
|
25
|
+
* - `eval` runs inside the preview frame's own origin, which the panel has already pointed at either
|
|
26
|
+
* a same-origin route, a loopback dev server through the Host's proxy, or a URL a person typed.
|
|
27
|
+
* It can therefore reach nothing the frame itself could not.
|
|
28
|
+
*
|
|
29
|
+
* Every call is bounded. The command queue refuses immediately when no panel is open, fails a
|
|
30
|
+
* delivered command on its own deadline, and returns the reason — a hung tool call would be worse
|
|
31
|
+
* than a failed one, because the model cannot tell the difference between "slow" and "gone".
|
|
32
|
+
* @module @achasoft/dsh-advanced-sidebar/host/ui-preview-tool
|
|
33
|
+
*/
|
|
34
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
35
|
+
import z from '@deepseek-ai/schemastery';
|
|
36
|
+
import type { PreviewCommand } from './types.ts';
|
|
37
|
+
/** Cordis plugin name for the model-facing tool. */
|
|
38
|
+
export declare const name = "advanced-sidebar-ui-preview";
|
|
39
|
+
/** The tool registry and the sidebar service both have to be present for this tool to mean anything. */
|
|
40
|
+
export declare const inject: string[];
|
|
41
|
+
/** Deployment configuration for the UI preview tool. */
|
|
42
|
+
export interface Config {
|
|
43
|
+
/** How long one command waits for the panel to answer, in milliseconds. */
|
|
44
|
+
commandTimeoutMs: number;
|
|
45
|
+
}
|
|
46
|
+
/** Schemastery configuration for the tool. */
|
|
47
|
+
export declare const Config: z<Config>;
|
|
48
|
+
/**
|
|
49
|
+
* One command body, as the service accepts it: everything but the id, the panel, and the deadline,
|
|
50
|
+
* which the queue owns.
|
|
51
|
+
*/
|
|
52
|
+
export type PreviewCommandBody = Omit<PreviewCommand, 'id' | 'clientId' | 'timeoutMs'> & {
|
|
53
|
+
timeoutMs?: number;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Register the model-facing UI preview tool.
|
|
57
|
+
* @param ctx - registrant context carrying the tool registry and the sidebar service.
|
|
58
|
+
* @param config - the deployment's tool configuration.
|
|
59
|
+
*/
|
|
60
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@achasoft/dsh-advanced-sidebar` root entry —
|
|
3
|
-
* system requires them together.
|
|
2
|
+
* `@achasoft/dsh-advanced-sidebar` root entry — three roles in one module, because the client module
|
|
3
|
+
* system requires two of them together.
|
|
4
4
|
*
|
|
5
5
|
* **As a plugin**, this is the advanced sidebar's node half. The apply is empty: the browser half
|
|
6
6
|
* ships via `exports["./client"]` and is discovered through the package's `dsh.client` declaration.
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
*
|
|
11
11
|
* **As a library**, it re-exports the wire contract, so another package can type against the
|
|
12
12
|
* `advancedSidebar` namespace without depending on the Host endpoint or the browser surface.
|
|
13
|
+
*
|
|
14
|
+
* The agent-facing `ui_preview` tool ships as its own entry (`exports["./ui-preview"]`) and its own
|
|
15
|
+
* composition row, so a deployment that wants the sidebar but not a model-facing verb simply leaves
|
|
16
|
+
* that row out — and the row's own `inject` is what makes the tool wait for the service it calls.
|
|
13
17
|
* @module @achasoft/dsh-advanced-sidebar
|
|
14
18
|
*/
|
|
15
19
|
export type * from './host/types.ts';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@achasoft/dsh-advanced-sidebar/ui-preview` — the row the agent-facing `ui_preview` tool mounts on.
|
|
3
|
+
*
|
|
4
|
+
* It is a separate entry, and a separate composition row, for one reason: the tool calls the
|
|
5
|
+
* `advancedSidebar` service, and a row's `inject` is what makes Cordis wait for a service instead of
|
|
6
|
+
* handing the plugin a context that cannot reach it. Re-exporting here rather than registering from
|
|
7
|
+
* the root entry also keeps the tool out of a deployment that composes the sidebar surface without
|
|
8
|
+
* wanting a model-facing verb on it: leave the row out, and the tool does not exist.
|
|
9
|
+
* @module @achasoft/dsh-advanced-sidebar/ui-preview
|
|
10
|
+
*/
|
|
11
|
+
export { Config, apply, inject, name } from './host/ui-preview-tool.ts';
|