@qilin-agent/client-ui-deliverables 0.0.0-stage → 3.1.3

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/lib/index.js ADDED
@@ -0,0 +1,290 @@
1
+ import { remoteErrorOf } from "@qilin-agent/typert-protocol";
2
+ //#region lib/types/changes.js
3
+ /** Authenticated GET route serving one announced change summary while its Session lives. */
4
+ const CHANGED_FILES_PATH = "/api/changes.summary";
5
+ /** Authenticated GET route serving one listed file's turn-start and turn-end comparison while its Session lives. */
6
+ const CHANGES_DIFF_PATH = "/api/changes.diff";
7
+ /** Authenticated GET route serving the Session's whole changed-file list while its Session lives. */
8
+ const CHANGES_SESSION_PATH = "/api/changes.session";
9
+ /** Authenticated POST route for opening a changed file on the Host desktop. */
10
+ const CHANGES_OPEN_PATH = "/api/changes.open";
11
+ CHANGED_FILES_PATH.slice(1);
12
+ CHANGES_DIFF_PATH.slice(1);
13
+ CHANGES_SESSION_PATH.slice(1);
14
+ CHANGES_OPEN_PATH.slice(1);
15
+ //#endregion
16
+ //#region lib/types/presented.js
17
+ /** Authenticated POST route for opening a workspace file on the Host desktop. */
18
+ const PRESENT_OPEN_PATH = "/api/present.open";
19
+ /** Authenticated desktop availability and destination metadata. */
20
+ const PRESENT_HOST_PATH = "/api/present.host";
21
+ PRESENT_OPEN_PATH.slice(1);
22
+ PRESENT_HOST_PATH.slice(1);
23
+ /**
24
+ * Validate a file declaration read from a Session log.
25
+ * @param value - decoded durable data.
26
+ * @returns whether the declaration contains a path and optional description.
27
+ */
28
+ function isPresentedFile(value) {
29
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
30
+ const { path, description } = value;
31
+ return typeof path === "string" && path.trim().length > 0 && (description === void 0 || typeof description === "string");
32
+ }
33
+ /**
34
+ * Validate a delivery event before reading its turn or file declarations.
35
+ * @param value - decoded durable event data.
36
+ * @returns whether the event identifies a turn, call, and file list.
37
+ */
38
+ function isPresentedData(value) {
39
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
40
+ const { turn, callId, files } = value;
41
+ return typeof turn === "number" && Number.isSafeInteger(turn) && turn >= 1 && typeof callId === "string" && callId.length > 0 && Array.isArray(files);
42
+ }
43
+ //#endregion
44
+ //#region lib/types/present-open.js
45
+ /**
46
+ * Register the deliverables routes inside Connection's authentication fence:
47
+ * desktop metadata, change summaries and comparisons, declared-file actions,
48
+ * and changed-file opening.
49
+ * @param ctx - Session lookup, change summaries, native opener, and route lifetime.
50
+ */
51
+ function registerPresentOpen(ctx) {
52
+ ctx.connection.fetch.register({
53
+ path: PRESENT_HOST_PATH,
54
+ methods: ["GET"],
55
+ requestBody: "buffered",
56
+ fetch: () => Promise.resolve(Response.json(ctx.sessionController.workspaceDesktop(), { headers: { "cache-control": "no-store" } }))
57
+ });
58
+ ctx.connection.fetch.register({
59
+ path: CHANGED_FILES_PATH,
60
+ methods: ["GET"],
61
+ requestBody: "buffered",
62
+ fetch: (request) => Promise.resolve(handleChangesSummary(ctx, request))
63
+ });
64
+ ctx.connection.fetch.register({
65
+ path: CHANGES_SESSION_PATH,
66
+ methods: ["GET"],
67
+ requestBody: "buffered",
68
+ fetch: (request) => Promise.resolve(handleChangesSession(ctx, request))
69
+ });
70
+ const lifetime = new AbortController();
71
+ const pending = /* @__PURE__ */ new Set();
72
+ ctx.effect(() => async () => {
73
+ lifetime.abort();
74
+ await Promise.allSettled(pending);
75
+ });
76
+ const routes = [
77
+ [
78
+ PRESENT_OPEN_PATH,
79
+ "POST",
80
+ handlePresentOpen
81
+ ],
82
+ [
83
+ CHANGES_OPEN_PATH,
84
+ "POST",
85
+ handleChangesOpen
86
+ ],
87
+ [
88
+ CHANGES_DIFF_PATH,
89
+ "GET",
90
+ handleChangesDiff
91
+ ]
92
+ ];
93
+ for (const [path, method, handler] of routes) ctx.connection.fetch.register({
94
+ path,
95
+ methods: [method],
96
+ requestBody: "buffered",
97
+ fetch: (request) => {
98
+ const task = handler(ctx, new Request(request, { signal: AbortSignal.any([request.signal, lifetime.signal]) }));
99
+ pending.add(task);
100
+ task.then(() => {
101
+ pending.delete(task);
102
+ }, () => {
103
+ pending.delete(task);
104
+ });
105
+ return task;
106
+ }
107
+ });
108
+ }
109
+ const NUMERIC = /^\d+$/;
110
+ function coordinate(value) {
111
+ return value !== null && NUMERIC.test(value) && Number.isSafeInteger(Number(value)) ? Number(value) : void 0;
112
+ }
113
+ /** Translate lookup and filesystem failures into the not-found or failure status the browser retries from. */
114
+ function failureStatus(error) {
115
+ const remote = remoteErrorOf(error);
116
+ return remote?.code === "session/not-found" || remote?.code === "workspace-file/not-found" || remote?.code === "workspace-file/not-regular-file" || error instanceof Error && "code" in error && (error.code === "SESSION_QUERY_SESSION_NOT_FOUND" || error.code === "SESSION_QUERY_EVENT_NOT_FOUND" || error.code === "ENOENT" || error.code === "ENOTDIR") ? 404 : 500;
117
+ }
118
+ /**
119
+ * Read the addressed event once the Host desktop is known to be available.
120
+ * @returns the event with its Session header, or the refusal to answer with.
121
+ */
122
+ async function readTarget(ctx, request, id, seq) {
123
+ request.signal.throwIfAborted();
124
+ if (!ctx.sessionController.workspaceDesktop().available) return new Response("Host desktop unavailable.", { status: 409 });
125
+ return ctx.sessionQuery.readEvent({
126
+ sessionId: id,
127
+ seq,
128
+ before: 0,
129
+ after: 0
130
+ }, request.signal);
131
+ }
132
+ /**
133
+ * Open one verified Host path. A file is verified through the Session
134
+ * filesystem; a directory is verified by the Host filesystem mapping alone.
135
+ * @returns the HTTP status to answer with.
136
+ */
137
+ async function openVerified(ctx, request, path, action) {
138
+ const { fs } = ctx;
139
+ const mapped = fs.processPathFromHostPath(path);
140
+ if (mapped === void 0 || fs.processPath(await fs.resolve(mapped, { signal: request.signal })) !== path) return new Response("Path has no verified Host path.", { status: 422 });
141
+ request.signal.throwIfAborted();
142
+ await ctx.sessionController.openWorkspacePath({
143
+ path,
144
+ ...action === "reveal" ? { action } : {}
145
+ }, request.signal);
146
+ return new Response(null, {
147
+ status: 204,
148
+ headers: { "cache-control": "no-store" }
149
+ });
150
+ }
151
+ async function handlePresentOpen(ctx, request) {
152
+ const query = new URL(request.url).searchParams;
153
+ const action = query.get("action") ?? "open";
154
+ if (action !== "open" && action !== "reveal") return new Response("Invalid file action.", { status: 400 });
155
+ const id = query.get("sessionId");
156
+ const seq = coordinate(query.get("seq"));
157
+ const index = coordinate(query.get("index"));
158
+ if (!id || seq === void 0 || index === void 0) return new Response("Invalid Presented file coordinates.", { status: 400 });
159
+ try {
160
+ const read = await readTarget(ctx, request, id, seq);
161
+ if (read instanceof Response) return read;
162
+ const { target, session } = read;
163
+ const file = target.type === "deliverables/presented" && isPresentedData(target.data) ? target.data.files[index] : void 0;
164
+ if (!isPresentedFile(file)) return new Response("Presented file not found in this Session result.", { status: 404 });
165
+ request.signal.throwIfAborted();
166
+ const { absolutePath: path } = await ctx.workspaceFiles.stat({
167
+ sessionId: id,
168
+ workspaceRoot: session.cwd ?? ctx.sandboxPolicy.workspaceRoot
169
+ }, file.path, request.signal);
170
+ return await openVerified(ctx, request, path, action);
171
+ } catch (error) {
172
+ request.signal.throwIfAborted();
173
+ return new Response("Presented file unavailable.", { status: failureStatus(error) });
174
+ }
175
+ }
176
+ /** The summary one `workspace/changes` event announced, without the Host working directory; 404 once the Host no longer serves it. */
177
+ function handleChangesSummary(ctx, request) {
178
+ const query = new URL(request.url).searchParams;
179
+ const id = query.get("sessionId");
180
+ const seq = coordinate(query.get("seq"));
181
+ if (!id || seq === void 0) return new Response("Invalid change summary coordinates.", { status: 400 });
182
+ const summary = ctx.workspaceChanges.summary(id, seq);
183
+ if (summary === void 0) return new Response("Change summary unavailable.", { status: 404 });
184
+ const { turn, files, total, added, deleted } = summary;
185
+ return Response.json({
186
+ turn,
187
+ files,
188
+ total,
189
+ added,
190
+ deleted
191
+ }, { headers: { "cache-control": "no-store" } });
192
+ }
193
+ /** Every change the Session recorded, folded by path; 404 once the Host no longer holds its recorder. */
194
+ function handleChangesSession(ctx, request) {
195
+ const id = new URL(request.url).searchParams.get("sessionId");
196
+ if (!id) return new Response("Invalid change session coordinates.", { status: 400 });
197
+ const changes = ctx.workspaceChanges.session(id);
198
+ if (changes === void 0) return new Response("Session changes unavailable.", { status: 404 });
199
+ const { files, total, added, deleted } = changes;
200
+ return Response.json({
201
+ files,
202
+ total,
203
+ added,
204
+ deleted
205
+ }, { headers: { "cache-control": "no-store" } });
206
+ }
207
+ /** A changed file's coordinates from a route query, or the 400 to answer with. */
208
+ function changedFileCoordinates(request) {
209
+ const query = new URL(request.url).searchParams;
210
+ const id = query.get("sessionId");
211
+ const seq = coordinate(query.get("seq"));
212
+ const index = coordinate(query.get("index"));
213
+ if (!id || seq === void 0 || index === void 0) return new Response("Invalid changed file coordinates.", { status: 400 });
214
+ return {
215
+ id,
216
+ seq,
217
+ index
218
+ };
219
+ }
220
+ /** One listed file's comparison; 404 once the Host no longer serves the summary or the index names no file. */
221
+ async function handleChangesDiff(ctx, request) {
222
+ const coordinates = changedFileCoordinates(request);
223
+ if (coordinates instanceof Response) return coordinates;
224
+ const { id, seq, index } = coordinates;
225
+ try {
226
+ const diff = await ctx.workspaceChanges.diff(id, seq, index, request.signal);
227
+ if (diff === void 0) return new Response("Change comparison unavailable.", { status: 404 });
228
+ return Response.json(diff, { headers: { "cache-control": "no-store" } });
229
+ } catch (error) {
230
+ request.signal.throwIfAborted();
231
+ return new Response("Change comparison unavailable.", { status: failureStatus(error) });
232
+ }
233
+ }
234
+ async function handleChangesOpen(ctx, request) {
235
+ const coordinates = changedFileCoordinates(request);
236
+ if (coordinates instanceof Response) return coordinates;
237
+ const { id, seq, index } = coordinates;
238
+ try {
239
+ request.signal.throwIfAborted();
240
+ if (!ctx.sessionController.workspaceDesktop().available) return new Response("Host desktop unavailable.", { status: 409 });
241
+ const changes = ctx.workspaceChanges.summary(id, seq);
242
+ if (changes === void 0) return new Response("Change summary unavailable.", { status: 404 });
243
+ const workspaceRoot = changes.cwd;
244
+ const file = changes.files[index];
245
+ if (file === void 0) return new Response("Changed file not found in this summary.", { status: 404 });
246
+ const { absolutePath: path } = await ctx.workspaceFiles.stat({
247
+ sessionId: id,
248
+ workspaceRoot
249
+ }, file.path, request.signal);
250
+ return await openVerified(ctx, request, path, "open");
251
+ } catch (error) {
252
+ request.signal.throwIfAborted();
253
+ return new Response("Changed file unavailable.", { status: failureStatus(error) });
254
+ }
255
+ }
256
+ //#endregion
257
+ //#region lib/types/index.js
258
+ /**
259
+ * Deliverables plugin, node half. Registers Web file-reference guidance and
260
+ * serves authenticated native opens of declared files. The browser
261
+ * half ships via exports["./client"], discovered through the package.json
262
+ * qilin.client declaration.
263
+ */
264
+ /** Services required for file-reference guidance, change summaries, and authenticated native opens. */
265
+ const inject = [
266
+ "systemPrompt",
267
+ "connection",
268
+ "sessionQuery",
269
+ "sessionController",
270
+ "workspaceFiles",
271
+ "fs",
272
+ "sandboxPolicy",
273
+ "workspaceChanges"
274
+ ];
275
+ /** Static Web guidance for primary outputs and existing-file references. */
276
+ const FILE_REFERENCE_PROMPT = "When you successfully create or modify files, mention the primary outputs in your final response. Use ![Description](<path/to/image.png>) when an image supports an explanation or comparison. Use [Description](<path/to/image.png>) when referring to an image or listing files. Enclose Markdown file destinations in angle brackets, especially paths containing spaces. Outside commands, configuration expressions, and code blocks, link every mention of an existing file, including repeats and tables, to its full path relative to the working directory or absolute; append #L24 or #L24-L30 to the target for known lines. Use the filename or a clear alias as the label, adding only enough parent directories to distinguish files; keep full paths out of labels. Default to the name alone; when precise locations matter, append :24 or :24–30, with no # or L in the line suffix.";
277
+ /**
278
+ * Register Web file-reference guidance and native opens for declared files.
279
+ * @param ctx - host context carrying the system-prompt registry.
280
+ */
281
+ function apply(ctx) {
282
+ registerPresentOpen(ctx);
283
+ ctx.systemPrompt.section({
284
+ name: "ui:deliverable-file-references",
285
+ order: ctx.systemPrompt.getSectionOrder("DELIVERABLE_FILE_REFERENCES"),
286
+ text: FILE_REFERENCE_PROMPT
287
+ });
288
+ }
289
+ //#endregion
290
+ export { apply, inject };
@@ -0,0 +1,122 @@
1
+ /** Validate workspace-change records that cross the Host routes and address their summary, comparison, and native-open actions. */
2
+ import type { SessionId } from '@qilin-agent/session/types';
3
+ import type { WorkspaceChangedFile, WorkspaceChangesSummary, WorkspaceFileDiff, WorkspaceSessionChangedFile, WorkspaceSessionChanges } from '@qilin-agent/workspace-changes/types';
4
+ /** Authenticated GET route serving one announced change summary while its Session lives. */
5
+ export declare const CHANGED_FILES_PATH = "/api/changes.summary";
6
+ /** Authenticated GET route serving one listed file's turn-start and turn-end comparison while its Session lives. */
7
+ export declare const CHANGES_DIFF_PATH = "/api/changes.diff";
8
+ /** Authenticated GET route serving the Session's whole changed-file list while its Session lives. */
9
+ export declare const CHANGES_SESSION_PATH = "/api/changes.session";
10
+ /** Authenticated POST route for opening a changed file on the Host desktop. */
11
+ export declare const CHANGES_OPEN_PATH = "/api/changes.open";
12
+ /**
13
+ * Browser-relative form of {@link CHANGED_FILES_PATH}; see
14
+ * .agents/notes/implemented/architecture/2026-09-14-web-document-relative-app-routes.md.
15
+ */
16
+ export declare const CHANGED_FILES_ROUTE: string;
17
+ /** Browser-relative form of {@link CHANGES_DIFF_PATH}. */
18
+ export declare const CHANGES_DIFF_ROUTE: string;
19
+ /** Browser-relative form of {@link CHANGES_SESSION_PATH}. */
20
+ export declare const CHANGES_SESSION_ROUTE: string;
21
+ /** Browser-relative form of {@link CHANGES_OPEN_PATH}. */
22
+ export declare const CHANGES_OPEN_ROUTE: string;
23
+ /** Resource-address prefix of a turn's review tab in the right Sidebar. */
24
+ export declare const CHANGES_REVIEW_ADDRESS = "qilin-resource://changes-review/session/";
25
+ /** The summary fields the route serves; the Host keeps the working directory and snapshot ids to itself. */
26
+ export type ChangesSummary = Pick<WorkspaceChangesSummary, 'turn' | 'files' | 'total' | 'added' | 'deleted'>;
27
+ /**
28
+ * The comparison the route serves, as the Host computed it.
29
+ */
30
+ export type ChangesDiff = WorkspaceFileDiff;
31
+ /** One file the Session changed, as the session route serves it. */
32
+ export type ChangesSessionFile = WorkspaceSessionChangedFile;
33
+ /** Every file change the Session recorded, folded by path across its turns. */
34
+ export type ChangesSession = Pick<WorkspaceSessionChanges, 'files' | 'total' | 'added' | 'deleted'>;
35
+ /** Coordinates of one turn's review: the viewed Session, the announcing event, and the turn it summarized. */
36
+ export interface ChangesReviewCoordinates {
37
+ sessionId: SessionId;
38
+ seq: number;
39
+ /** The summarized turn, carried for the tab title. */
40
+ turn: number;
41
+ }
42
+ /**
43
+ * Validate one changed-file record read from the summary route.
44
+ * @param value - decoded JSON.
45
+ * @returns whether the record carries a path, a display path, and line counts.
46
+ */
47
+ export declare function isChangedFile(value: unknown): value is WorkspaceChangedFile;
48
+ /**
49
+ * Validate a summary read from the summary route.
50
+ * @param value - decoded JSON.
51
+ * @returns whether the value identifies a turn, a complete file list, the total count, and the line totals.
52
+ */
53
+ export declare function isChangesSummary(value: unknown): value is ChangesSummary;
54
+ /**
55
+ * Validate a comparison read from the comparison route.
56
+ * @param value - decoded JSON.
57
+ * @returns whether the value is a text comparison with well-formed hunks, or a binary or oversized refusal.
58
+ */
59
+ export declare function isChangesDiff(value: unknown): value is ChangesDiff;
60
+ /**
61
+ * Validate the `workspace/changes` event data read from a Session log.
62
+ * @param value - decoded durable event data.
63
+ * @returns whether the event names a turn.
64
+ */
65
+ export declare function isChangesEvent(value: unknown): value is {
66
+ turn: number;
67
+ };
68
+ /**
69
+ * Build authenticated coordinates for the summary one `workspace/changes` event announced.
70
+ * @param sessionId - owning Session.
71
+ * @param seq - event sequence.
72
+ * @returns document-relative summary route.
73
+ */
74
+ export declare function changesSummaryUrl(sessionId: SessionId, seq: number): string;
75
+ /**
76
+ * Validate one folded session changed-file record read from the session route.
77
+ * @param value - decoded JSON.
78
+ * @returns whether the record carries a path, display, per-file counts, and its latest coordinates.
79
+ */
80
+ export declare function isSessionChangedFile(value: unknown): value is WorkspaceSessionChangedFile;
81
+ /**
82
+ * Validate the Session's whole changed-file list read from the session route.
83
+ * @param value - decoded JSON.
84
+ * @returns whether the list carries consistent totals and every record validates.
85
+ */
86
+ export declare function isChangesSession(value: unknown): value is ChangesSession;
87
+ /**
88
+ * Build authenticated coordinates for the Session's whole changed-file list.
89
+ * @param sessionId - owning Session.
90
+ * @returns document-relative session route.
91
+ */
92
+ export declare function changesSessionUrl(sessionId: SessionId): string;
93
+ /**
94
+ * Build authenticated coordinates for one listed file's comparison.
95
+ * @param sessionId - owning Session.
96
+ * @param seq - workspace/changes event sequence.
97
+ * @param index - original index in the summary's files array.
98
+ * @returns document-relative comparison route.
99
+ */
100
+ export declare function changesDiffUrl(sessionId: SessionId, seq: number, index: number): string;
101
+ /**
102
+ * Build authenticated coordinates for a changed file's native open.
103
+ * @param sessionId - owning Session.
104
+ * @param seq - workspace/changes event sequence.
105
+ * @param index - original index in the summary's files array.
106
+ * @returns document-relative action route.
107
+ */
108
+ export declare function changedFileUrl(sessionId: SessionId, seq: number, index: number): string;
109
+ /**
110
+ * The right-Sidebar address of one turn's review. The Session and the event
111
+ * sequence identify the content; the turn rides along for the tab title.
112
+ * @param coordinates - viewed Session, announcing event, and turn.
113
+ * @returns a `qilin-resource://changes-review/session/…` address.
114
+ */
115
+ export declare function changesReviewAddress(coordinates: ChangesReviewCoordinates): string;
116
+ /**
117
+ * Read the coordinates back out of a review address.
118
+ * @param address - a resource address.
119
+ * @returns the coordinates, or undefined for any other address.
120
+ */
121
+ export declare function parseChangesReviewAddress(address: string): ChangesReviewCoordinates | undefined;
122
+ //# sourceMappingURL=changes.d.ts.map
@@ -0,0 +1,19 @@
1
+ import type { PropsLocale } from '@qilin-agent/client-ui-slots';
2
+ import type { ChangesSummary } from '../changes.ts';
3
+ import type { NS } from './locales.ts';
4
+ /**
5
+ * Render one turn's changed files. The header opens the turn's review in the
6
+ * right Sidebar on its first file; each row opens it on that row's file.
7
+ * @param props - the recorded summary, the review opener, and localized copy.
8
+ * @returns the card.
9
+ */
10
+ export declare function ChangedFiles({ changes, cwd, openReview, t }: {
11
+ /** The served summary with the sequence of the event that announced it. */
12
+ changes: Pick<ChangesSummary, 'files' | 'total' | 'added' | 'deleted'> & {
13
+ seq: number;
14
+ };
15
+ cwd: string | undefined;
16
+ /** Open the turn's review on the file at an original summary index. */
17
+ openReview: (index: number) => void;
18
+ } & PropsLocale<typeof NS>): import("react").JSX.Element;
19
+ //# sourceMappingURL=ChangedFiles.d.ts.map
@@ -0,0 +1,50 @@
1
+ import type { TurnTailOwnerProps } from '@qilin-agent/client-ui-chat/client';
2
+ import type { GlobalStandardProps, InjectFace, PropsLocale, PropsRuntime, SessionStandardProps } from '@qilin-agent/client-ui-slots';
3
+ import type { ObservableSnapshot } from '@qilin-agent/client-store';
4
+ import type { PresentedOpenController } from './present-open.ts';
5
+ import type { ChangesSummaryStore } from './changes-summary.ts';
6
+ import { type ChangesTurnData, type PresentedPath } from './turn-deliverables.ts';
7
+ import type { NS } from './locales.ts';
8
+ import { type ChangesReviewCoordinates } from '../changes.ts';
9
+ interface DeliverablesMatch {
10
+ changes: ChangesTurnData | null;
11
+ presented: readonly PresentedPath[];
12
+ }
13
+ /** Summary reads, native-open callbacks, and shared gesture status supplied by the plugin. */
14
+ export interface DeliverablesInjected {
15
+ hooks: {
16
+ presentedOpen: ObservableSnapshot<ReturnType<PresentedOpenController['state']['getSnapshot']>>;
17
+ presentedHost: ObservableSnapshot<ReturnType<PresentedOpenController['host']['getSnapshot']>>;
18
+ changesSummary: ObservableSnapshot<ReturnType<ChangesSummaryStore['state']['getSnapshot']>>;
19
+ };
20
+ reloadPresentedHost: PresentedOpenController['loadHost'];
21
+ loadChangesSummary: ChangesSummaryStore['load'];
22
+ openPresented: PresentedOpenController['open'];
23
+ openChanged: PresentedOpenController['openChanged'];
24
+ /** Open one turn's review in the right Sidebar on the file at an index. */
25
+ openChangesReview: (coordinates: ChangesReviewCoordinates, index: number) => void;
26
+ }
27
+ /**
28
+ * Claim turns with a change announcement or declared files.
29
+ * @param owner - closing turn.
30
+ * @returns matched announcement and deliveries, or null for a turn with neither.
31
+ */
32
+ export declare function selectDeliverables(owner: TurnTailOwnerProps): DeliverablesMatch | null;
33
+ /**
34
+ * Contribute file deliveries alongside other completed-Turn artifacts.
35
+ * @param props - closing Turn, file actions, and localized copy.
36
+ * @returns file rows, or null when the Turn declares none.
37
+ */
38
+ export declare function DeliverablesTail(props: PropsRuntime<'conversation.chat.turnTail'> & PropsLocale<typeof NS> & InjectFace<DeliverablesInjected>): import("react").JSX.Element | null;
39
+ /**
40
+ * Render the changed-files card, once the Host has served the announced
41
+ * summary and it lists a file, and default-application buttons for declared
42
+ * files. A summary the Host no longer serves leaves no card.
43
+ * @param props - matched announcement and files, workspace opener, and localized copy.
44
+ * @returns the closing turn's file rows.
45
+ */
46
+ export declare function Deliverables({ matched, openFile, t, sessionId, useSessions, openPresented, openChangesReview, usePresentedOpen, usePresentedHost, useChangesSummary, reloadPresentedHost, loadChangesSummary, }: Pick<TurnTailOwnerProps, 'openFile'> & {
47
+ matched: DeliverablesMatch;
48
+ } & PropsLocale<typeof NS> & Pick<SessionStandardProps, 'sessionId'> & Pick<GlobalStandardProps, 'useSessions'> & InjectFace<DeliverablesInjected>): import("react").JSX.Element;
49
+ export {};
50
+ //# sourceMappingURL=Deliverables.d.ts.map
@@ -0,0 +1,12 @@
1
+ import type { ToolCallViewProps } from '@qilin-agent/client-ui-tool/client';
2
+ import type { PropsLocale } from '@qilin-agent/client-ui-slots';
3
+ import type { NS } from './locales.ts';
4
+ type PresentRowProps = ToolCallViewProps & PropsLocale<typeof NS>;
5
+ /**
6
+ * Render a present call using its recorded arguments and result.
7
+ * @param props - tool call and localized status copy.
8
+ * @returns a status row with a result disclosure.
9
+ */
10
+ export declare function PresentRow(props: PresentRowProps): import("react").JSX.Element;
11
+ export {};
12
+ //# sourceMappingURL=PresentRow.d.ts.map
@@ -0,0 +1,19 @@
1
+ import type { PropsLocale } from '@qilin-agent/client-ui-slots';
2
+ import type { PresentedAction, PresentedHost } from '../presented.ts';
3
+ import type { PresentedOpenPhase } from './present-open.ts';
4
+ import { type PresentedPath } from './turn-deliverables.ts';
5
+ import type { NS } from './locales.ts';
6
+ /**
7
+ * Render independent file actions without nesting buttons inside a clickable card.
8
+ * @param props - durable file metadata, Sidebar preview, Host capabilities, gesture status, and localized copy.
9
+ * @returns the file card and its anchored action menu.
10
+ */
11
+ export declare function PresentedFileCard({ file, cwd, phase, host, onPreview, onAction, t }: {
12
+ file: PresentedPath;
13
+ cwd: string | undefined;
14
+ phase: PresentedOpenPhase | undefined;
15
+ host: PresentedHost | null;
16
+ onPreview: () => void;
17
+ onAction: (action: PresentedAction) => void;
18
+ } & PropsLocale<typeof NS>): import("react").JSX.Element;
19
+ //# sourceMappingURL=PresentedFileCard.d.ts.map
@@ -0,0 +1,77 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { InjectFace, PropsLocale, PropsRuntime, PropsStore } from '@qilin-agent/client-ui-slots';
3
+ import type { ObservableSnapshot } from '@qilin-agent/client-store';
4
+ import type { WorkspaceDiffHunk } from '@qilin-agent/workspace-changes/types';
5
+ import type { ChangesDiffStore } from './changes-diff.ts';
6
+ import type { ChangesSummaryStore } from './changes-summary.ts';
7
+ import type { PresentedOpenController } from './present-open.ts';
8
+ import type { createReviewStore } from './review-store.ts';
9
+ import type { NS } from './locales.ts';
10
+ /** Lines drawn before the tab stops; a coarse comparison of a file near the byte cap would otherwise draw every line. */
11
+ export declare const MAX_RENDERED_LINES = 5000;
12
+ /** Summary and comparison reads, desktop metadata, and the native open supplied by the plugin. */
13
+ export interface ReviewInjected {
14
+ hooks: {
15
+ changesSummary: ObservableSnapshot<ReturnType<ChangesSummaryStore['state']['getSnapshot']>>;
16
+ changesDiff: ObservableSnapshot<ReturnType<ChangesDiffStore['state']['getSnapshot']>>;
17
+ presentedOpen: ObservableSnapshot<ReturnType<PresentedOpenController['state']['getSnapshot']>>;
18
+ presentedHost: ObservableSnapshot<ReturnType<PresentedOpenController['host']['getSnapshot']>>;
19
+ };
20
+ loadChangesSummary: ChangesSummaryStore['load'];
21
+ loadChangesDiff: ChangesDiffStore['load'];
22
+ reloadPresentedHost: PresentedOpenController['loadHost'];
23
+ openChanged: PresentedOpenController['openChanged'];
24
+ }
25
+ /** The body's composed props: the tab it draws, its store, its injected face, and its copy. */
26
+ export type ReviewTabProps = PropsRuntime<'sidebar.right.pane.tab'> & PropsStore<ReturnType<typeof createReviewStore>> & InjectFace<ReviewInjected> & PropsLocale<typeof NS>;
27
+ /** One drawn line of a hunk with its line numbers on each side. */
28
+ export interface DiffRow {
29
+ kind: 'add' | 'del' | 'context';
30
+ old: number | undefined;
31
+ new: number | undefined;
32
+ text: string;
33
+ }
34
+ /** One side-by-side row: the old side, the new side, or both. */
35
+ export interface SplitRow {
36
+ left?: {
37
+ no: number;
38
+ text: string;
39
+ kind: 'del' | 'context';
40
+ };
41
+ right?: {
42
+ no: number;
43
+ text: string;
44
+ kind: 'add' | 'context';
45
+ };
46
+ }
47
+ /**
48
+ * Number a hunk's lines: context lines count on both sides, deletions on the
49
+ * old side, additions on the new side.
50
+ * @param hunk - a served hunk.
51
+ * @returns the rows in order.
52
+ */
53
+ export declare function hunkRows(hunk: WorkspaceDiffHunk): DiffRow[];
54
+ /**
55
+ * Pair a hunk's lines for the side-by-side view: each run of deletions is
56
+ * aligned with the run of additions that follows it, row by row, and context
57
+ * lines sit on both sides.
58
+ * @param hunk - a served hunk.
59
+ * @returns the rows in order.
60
+ */
61
+ export declare function splitRows(hunk: WorkspaceDiffHunk): SplitRow[];
62
+ /**
63
+ * The hunks to draw, cut at {@link MAX_RENDERED_LINES} lines in total.
64
+ * @param hunks - served hunks.
65
+ * @returns the hunks with the last one shortened as needed, and whether anything was cut.
66
+ */
67
+ export declare function renderedHunks(hunks: readonly WorkspaceDiffHunk[]): {
68
+ hunks: WorkspaceDiffHunk[];
69
+ truncated: boolean;
70
+ };
71
+ /**
72
+ * The review type's body, registered under `sidebar.right.pane.tab` as `changes-review`.
73
+ * @param props - composed slot props.
74
+ * @returns the selected file's comparison behind the file selector, or the state that stands in for it.
75
+ */
76
+ export declare function ReviewTab({ useTabInfo, sessionId, useSessions, useStore, actions, useChangesSummary, useChangesDiff, usePresentedOpen, usePresentedHost, loadChangesSummary, loadChangesDiff, reloadPresentedHost, openChanged, t, }: ReviewTabProps): ReactNode;
77
+ //# sourceMappingURL=ReviewTab.d.ts.map
@@ -0,0 +1,22 @@
1
+ /** Cache of the file comparisons the Host serves for listed changed files, read once per comparison and again after a failure. */
2
+ import type { SessionId } from '@qilin-agent/session/types';
3
+ import { type ChangesDiff } from '../changes.ts';
4
+ import { HostReadStore } from './host-read-store.ts';
5
+ /**
6
+ * A comparison, `'missing'` once the Host no longer serves it, `'error'` for
7
+ * a failed read a later request retries, or `'loading'` while the request runs.
8
+ */
9
+ export type ChangesDiffState = ChangesDiff | 'missing' | 'error' | 'loading';
10
+ /** One browser plugin's comparison reads; a failed read is the one state a later request replaces. */
11
+ export declare class ChangesDiffStore extends HostReadStore<ChangesDiffState> {
12
+ constructor();
13
+ /**
14
+ * Read one comparison; a cached comparison or a missing one is kept, a failed one is read again.
15
+ * @param sessionId - viewed Session.
16
+ * @param seq - the announcing event's sequence.
17
+ * @param index - the file's index in the summary.
18
+ * @returns after the state is published.
19
+ */
20
+ load(sessionId: SessionId, seq: number, index: number): Promise<void>;
21
+ }
22
+ //# sourceMappingURL=changes-diff.d.ts.map
@@ -0,0 +1,18 @@
1
+ /** Fetch-once cache of the change summaries the Host serves for announced `workspace/changes` events. */
2
+ import type { SessionId } from '@qilin-agent/session/types';
3
+ import { type ChangesSummary } from '../changes.ts';
4
+ import { HostReadStore } from './host-read-store.ts';
5
+ /** A summary, `'missing'` once the Host no longer serves it, or `'loading'` while the request runs. */
6
+ export type ChangesSummaryState = ChangesSummary | 'missing' | 'loading';
7
+ /** One browser plugin's summary reads; a summary or a missing answer is kept until the connection is replaced. */
8
+ export declare class ChangesSummaryStore extends HostReadStore<ChangesSummaryState> {
9
+ constructor();
10
+ /**
11
+ * Read one summary once; a later read of the same coordinates returns the cached state.
12
+ * @param sessionId - viewed Session.
13
+ * @param seq - the announcing event's sequence.
14
+ * @returns after the state is published.
15
+ */
16
+ load(sessionId: SessionId, seq: number): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=changes-summary.d.ts.map