@fungi.computer/knapsack 0.0.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,136 @@
1
+ import { defineIdFamily } from "@fungi.computer/cairn";
2
+ import * as z from "zod";
3
+ export const uiWireIds = defineIdFamily({
4
+ family: "knapsack-ui",
5
+ version: 1,
6
+ kinds: {
7
+ request: { prefix: "ui_request" },
8
+ connection: { prefix: "ui_connection" },
9
+ claim: { prefix: "ui_claim" },
10
+ attempt: { prefix: "ui_attempt" },
11
+ },
12
+ });
13
+ const identifier = z.string().min(1).max(256);
14
+ const requestId = z
15
+ .string()
16
+ .refine((value) => uiWireIds.request.parse(value).ok);
17
+ const json = z.json();
18
+ const schemaDocument = z.record(z.string(), json);
19
+ export const BrowserWireObjectSchema = z.record(z.string(), z.unknown());
20
+ const WhistleAvailabilitySchema = z.discriminatedUnion("status", [
21
+ z.object({ status: z.literal("available") }),
22
+ z.object({ status: z.literal("unavailable"), reason: z.string() }),
23
+ ]);
24
+ /** The same descriptor is decoded at browser ingress and in model tool results. */
25
+ const browserCommandRowFields = {
26
+ commandId: identifier,
27
+ sourceId: identifier,
28
+ title: z.string(),
29
+ category: z.string(),
30
+ description: z.string().nullable(),
31
+ order: z.number(),
32
+ keybind: z.string().nullable(),
33
+ availability: WhistleAvailabilitySchema,
34
+ };
35
+ const browserActionSchema = z.union([
36
+ z
37
+ .object({ inputSchema: schemaDocument, outputSchema: schemaDocument })
38
+ .strict(),
39
+ z.object({ inputSchema: schemaDocument }).strict(),
40
+ z.object({ outputSchema: schemaDocument }).strict(),
41
+ ]);
42
+ const browserCommandRowWithActionSchema = z
43
+ .object({ ...browserCommandRowFields, action: browserActionSchema })
44
+ .strict();
45
+ const browserCommandRowWithoutActionSchema = z
46
+ .object(browserCommandRowFields)
47
+ .strict();
48
+ export const BrowserCommandRowSchema = z.union([
49
+ browserCommandRowWithActionSchema,
50
+ browserCommandRowWithoutActionSchema,
51
+ ]);
52
+ const browserExecuteCommandSchema = z.object({
53
+ commandId: z.string().max(256),
54
+ });
55
+ const browserExecuteInputWireSchema = browserExecuteCommandSchema
56
+ .extend({ arguments: json })
57
+ .strict();
58
+ const browserExecuteInputWithoutArgumentsSchema = browserExecuteCommandSchema.strict();
59
+ export const BrowserExecuteInputSchema = z.union([
60
+ browserExecuteInputWireSchema,
61
+ browserExecuteInputWithoutArgumentsSchema,
62
+ ]);
63
+ const BrowserRequestSchema = z.union([
64
+ z.object({ type: z.literal("discover") }).strict(),
65
+ browserExecuteInputWireSchema.extend({ type: z.literal("execute") }),
66
+ browserExecuteInputWithoutArgumentsSchema.extend({
67
+ type: z.literal("execute"),
68
+ }),
69
+ ]);
70
+ const BrowserRequestEnvelopeSchema = z.object({
71
+ requestId,
72
+ request: BrowserRequestSchema,
73
+ });
74
+ export const BrowserRequestDeliverySchema = BrowserRequestEnvelopeSchema.extend({
75
+ connectionId: z
76
+ .string()
77
+ .refine((value) => uiWireIds.connection.parse(value).ok),
78
+ });
79
+ export const BrowserClaimSchema = z.object({
80
+ requestId,
81
+ connectionId: z
82
+ .string()
83
+ .refine((value) => uiWireIds.connection.parse(value).ok),
84
+ attemptId: z.string().refine((value) => uiWireIds.attempt.parse(value).ok),
85
+ });
86
+ export const BrowserClaimResultSchema = z.discriminatedUnion("status", [
87
+ z.object({ status: z.literal("claimed"), claimId: z.string() }),
88
+ z.object({
89
+ status: z.literal("waiting"),
90
+ retryAfterMs: z.number().int().min(10).max(1_000),
91
+ }),
92
+ z.object({ status: z.literal("skipped") }),
93
+ ]);
94
+ export const BrowserExecutionOutcomeSchema = z.union([
95
+ z.object({ status: z.literal("handled"), result: json }).strict(),
96
+ z.object({ status: z.literal("handled") }).strict(),
97
+ z.object({ status: z.literal("closed") }).strict(),
98
+ z.object({ status: z.literal("missing") }).strict(),
99
+ z.object({ status: z.literal("invalid") }).strict(),
100
+ z.object({ status: z.literal("unavailable"), reason: z.string() }).strict(),
101
+ z
102
+ .object({
103
+ status: z.literal("failed"),
104
+ error: z.object({
105
+ code: z.string(),
106
+ message: z.string(),
107
+ details: json,
108
+ }),
109
+ })
110
+ .strict(),
111
+ z
112
+ .object({
113
+ status: z.literal("failed"),
114
+ error: z.object({ code: z.string(), message: z.string() }).strict(),
115
+ })
116
+ .strict(),
117
+ ]);
118
+ const BrowserWireResultSchema = z.discriminatedUnion("type", [
119
+ z.object({
120
+ type: z.literal("discover"),
121
+ commands: z.array(BrowserCommandRowSchema),
122
+ }),
123
+ z.object({
124
+ type: z.literal("execute"),
125
+ outcome: BrowserExecutionOutcomeSchema,
126
+ }),
127
+ ]);
128
+ export const BrowserResultEnvelopeSchema = z.object({
129
+ requestId,
130
+ claimId: z.string().refine((value) => uiWireIds.claim.parse(value).ok),
131
+ result: BrowserWireResultSchema,
132
+ });
133
+ export const BrowserSettlementResultSchema = z.discriminatedUnion("status", [
134
+ z.object({ status: z.literal("accepted") }),
135
+ z.object({ status: z.literal("ignored") }),
136
+ ]);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Browser side of Knapsack.ui: connect the App's Whistle runtime to one Agent
3
+ * Session's `/browser/*` transport so the Agent can discover and run its
4
+ * live commands.
5
+ */
6
+ export { connectBrowser, UiBrowserError, type BrowserConnection, type BrowserConnectionOptions, } from "./ui/browser.js";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Browser side of Knapsack.ui: connect the App's Whistle runtime to one Agent
3
+ * Session's `/browser/*` transport so the Agent can discover and run its
4
+ * live commands.
5
+ */
6
+ export { connectBrowser, UiBrowserError, } from "./ui/browser.js";
package/dist/ui.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { CapabilitySource } from "@fungi.computer/mycelium";
2
+ /** How one Agent host connects its Sessions' open Apps to Knapsack.ui. */
3
+ export type UiConnectionOptions = Readonly<{
4
+ /** Stable, secret-free capability source id, such as `"hub-ui"`. */
5
+ id: string;
6
+ /** Whether the Session is stored; only a stored Session's browser may connect. */
7
+ hasSession: (sessionId: string) => Promise<boolean>;
8
+ }>;
9
+ /**
10
+ * Live commands of the App open in each Agent Session's browser. The Agent
11
+ * reaches them through the `ui` module (`ui.discover`, `ui.execute`); the
12
+ * browser runs them through Whistle over the transport this connection serves.
13
+ */
14
+ export type UiConnection = Readonly<{
15
+ /**
16
+ * Each Session run borrows its Session's `ui` module and releases it when
17
+ * the run ends. A Session-free acquisition contributes no module.
18
+ */
19
+ source: CapabilitySource;
20
+ /**
21
+ * Serve `/browser/sessions/<sessionId>/browser/{events,claims,results}`.
22
+ * It authenticates nobody: mount it behind the host's own authorization.
23
+ */
24
+ fetch: (request: Request) => Promise<Response>;
25
+ /** End every browser stream; later requests answer 410 and later borrows fail. */
26
+ close: () => Promise<void>;
27
+ }>;
28
+ /** Connect the open App's live Whistle commands to each Agent Session. */
29
+ export declare function ui(options: UiConnectionOptions): UiConnection;
package/dist/ui.js ADDED
@@ -0,0 +1,35 @@
1
+ import { createUiHost } from "./ui/host.js";
2
+ import { BrowserSessions } from "./ui/sessions.js";
3
+ /** Borrow one Session's `ui` module for the acquiring run. */
4
+ function borrow(sessions, context) {
5
+ context.signal.throwIfAborted();
6
+ if (context.session === undefined)
7
+ return {
8
+ snapshot: { version: "no-session", modules: [] },
9
+ release: () => Promise.resolve(),
10
+ };
11
+ const sessionId = context.session.sessionId;
12
+ const lease = sessions.acquire(sessionId);
13
+ return {
14
+ snapshot: { version: `session:${sessionId}`, modules: lease.modules },
15
+ release: () => {
16
+ lease.release();
17
+ return Promise.resolve();
18
+ },
19
+ };
20
+ }
21
+ /** Connect the open App's live Whistle commands to each Agent Session. */
22
+ export function ui(options) {
23
+ const sessions = new BrowserSessions(() => createUiHost(), options.hasSession);
24
+ return Object.freeze({
25
+ source: {
26
+ id: options.id,
27
+ // The executor turns a refused borrow (aborted, or closed) into a rejection.
28
+ acquire: (context) => new Promise((resolve) => {
29
+ resolve(borrow(sessions, context));
30
+ }),
31
+ },
32
+ fetch: (request) => sessions.fetch(request),
33
+ close: () => sessions.close(),
34
+ });
35
+ }
@@ -15,3 +15,4 @@
15
15
  | [files-read](files-read.md) | - |
16
16
  | [index](index.md) | - |
17
17
  | [patch](patch.md) | - |
18
+ | [ui-browser](ui-browser.md) | - |
@@ -13,11 +13,13 @@
13
13
  ### patchInputSchema
14
14
 
15
15
  ```ts
16
- const patchInputSchema: ZodObject<
17
- {
18
- patch: ZodString;
19
- },
20
- $strict
16
+ const patchInputSchema: ZodLazy<
17
+ ZodObject<
18
+ {
19
+ patch: ZodString;
20
+ },
21
+ $strict
22
+ >
21
23
  >;
22
24
  ```
23
25
 
@@ -28,44 +30,38 @@ Browser-safe patch contracts shared by native Files tools and Computer HTTP.
28
30
  ### patchOutputSchema
29
31
 
30
32
  ```ts
31
- const patchOutputSchema: ZodUnion<
32
- readonly [
33
- ZodObject<
34
- {
35
- status: ZodEnum<{
36
- completed: "completed";
37
- failed: "failed";
38
- cancelled: "cancelled";
39
- }>;
40
- exitCode: ZodNumber;
41
- stdout: ZodString;
42
- stderr: ZodString;
43
- truncated: ZodBoolean;
44
- },
45
- $strict
46
- >,
47
- ZodObject<
48
- {
49
- status: ZodEnum<{
50
- completed: "completed";
51
- failed: "failed";
52
- cancelled: "cancelled";
53
- }>;
54
- exitCode: ZodNumber;
55
- stdout: ZodString;
56
- stderr: ZodString;
57
- truncated: ZodBoolean;
58
- error: ZodObject<
59
- {
60
- kind: ZodLiteral<"mismatch">;
61
- },
62
- $strict
63
- >;
64
- },
65
- $strict
66
- >,
67
- ]
68
- >;
33
+ const patchOutputSchema: ZodLazy<ZodUnion<readonly [ZodObject<{
34
+ status: ZodEnum<{
35
+ completed: "completed";
36
+ failed: "failed";
37
+ cancelled: "cancelled";
38
+ }>;
39
+ exitCode: ZodNumber;
40
+ stdout: ZodString;
41
+ stderr: ZodString;
42
+ truncated: ZodUnion<readonly [ZodLiteral<false>, ZodArray<ZodObject<{
43
+ stream: ZodEnum<...>;
44
+ keptBytes: ZodNumber;
45
+ totalBytes: ZodNumber;
46
+ }, $strict>>]>;
47
+ }, $strict>, ZodObject<{
48
+ status: ZodEnum<{
49
+ completed: "completed";
50
+ failed: "failed";
51
+ cancelled: "cancelled";
52
+ }>;
53
+ exitCode: ZodNumber;
54
+ stdout: ZodString;
55
+ stderr: ZodString;
56
+ truncated: ZodUnion<readonly [ZodLiteral<false>, ZodArray<ZodObject<{
57
+ stream: ZodEnum<...>;
58
+ keptBytes: ZodNumber;
59
+ totalBytes: ZodNumber;
60
+ }, $strict>>]>;
61
+ error: ZodObject<{
62
+ kind: ZodLiteral<"mismatch">;
63
+ }, $strict>;
64
+ }, $strict>]>>;
69
65
  ```
70
66
 
71
67
  Native patch completion, cancellation or failure, including an optional
@@ -20,10 +20,31 @@ Maximum UTF-8 byte length of a Files text page.
20
20
 
21
21
  ---
22
22
 
23
+ ### maximumImageBytes
24
+
25
+ ```ts
26
+ const maximumImageBytes: number;
27
+ ```
28
+
29
+ Maximum byte length of an image the read tool returns; a host refuses an image
30
+ it cannot bring within it.
31
+
32
+ ---
33
+
34
+ ### imageSniffBytes
35
+
36
+ ```ts
37
+ const imageSniffBytes: 12 = 12;
38
+ ```
39
+
40
+ Bytes needed at the start of a file to recognise a supported image.
41
+
42
+ ---
43
+
23
44
  ### pathSchema
24
45
 
25
46
  ```ts
26
- const pathSchema: ZodString;
47
+ const pathSchema: ZodLazy<ZodString>;
27
48
  ```
28
49
 
29
50
  Nonempty file path bounded to 4,096 characters, without null bytes.
@@ -33,13 +54,15 @@ Nonempty file path bounded to 4,096 characters, without null bytes.
33
54
  ### readInputSchema
34
55
 
35
56
  ```ts
36
- const readInputSchema: ZodObject<
37
- {
38
- path: ZodString;
39
- offset: ZodDefault<ZodNumber>;
40
- limit: ZodDefault<ZodNumber>;
41
- },
42
- $strict
57
+ const readInputSchema: ZodLazy<
58
+ ZodObject<
59
+ {
60
+ path: ZodLazy<ZodString>;
61
+ offset: ZodDefault<ZodNumber>;
62
+ limit: ZodDefault<ZodNumber>;
63
+ },
64
+ $strict
65
+ >
43
66
  >;
44
67
  ```
45
68
 
@@ -47,34 +70,214 @@ Canonical bounded Files read request and result, shared with display consumers.
47
70
 
48
71
  ---
49
72
 
50
- ### readOutputSchema
73
+ ### readImageMimeType
74
+
75
+ ```ts
76
+ const readImageMimeType: ZodEnum<{
77
+ image/png: "image/png";
78
+ image/jpeg: "image/jpeg";
79
+ image/gif: "image/gif";
80
+ image/webp: "image/webp";
81
+ }>;
82
+ ```
83
+
84
+ The image types a Files read can return.
85
+
86
+ ---
87
+
88
+ ### readImageOutputSchema
89
+
90
+ ```ts
91
+ const readImageOutputSchema: ZodLazy<ZodObject<{
92
+ path: ZodLazy<ZodString>;
93
+ image: ZodObject<{
94
+ mimeType: ZodEnum<{
95
+ image/png: "image/png";
96
+ image/jpeg: "image/jpeg";
97
+ image/gif: "image/gif";
98
+ image/webp: "image/webp";
99
+ }>;
100
+ data: ZodBase64;
101
+ bytes: ZodNumber;
102
+ }, $strict>;
103
+ file: ZodExactOptional<ZodLazy<ZodObject<{
104
+ computerId: ZodString;
105
+ path: ZodLazy<ZodString>;
106
+ }, $strict>>>;
107
+ }, $strict>>;
108
+ ```
109
+
110
+ Whole png, jpeg, gif or webp file, at most `maximumImageBytes`, as base64.
111
+
112
+ ---
113
+
114
+ ### readTextOutputSchema
51
115
 
52
116
  ```ts
53
- const readOutputSchema: ZodObject<
54
- {
55
- path: ZodString;
56
- content: ZodString;
57
- offset: ZodNumber;
58
- nextOffset: ZodNullable<ZodNumber>;
59
- truncated: ZodBoolean;
60
- file: ZodExactOptional<
61
- ZodObject<
62
- {
63
- computerId: ZodString;
64
- path: ZodString;
65
- },
66
- $strict
67
- >
68
- >;
69
- },
70
- $strict
117
+ const readTextOutputSchema: ZodLazy<
118
+ ZodObject<
119
+ {
120
+ path: ZodLazy<ZodString>;
121
+ content: ZodString;
122
+ offset: ZodNumber;
123
+ nextOffset: ZodNullable<ZodNumber>;
124
+ truncated: ZodBoolean;
125
+ file: ZodExactOptional<
126
+ ZodLazy<
127
+ ZodObject<
128
+ {
129
+ computerId: ZodString;
130
+ path: ZodLazy<ZodString>;
131
+ },
132
+ $strict
133
+ >
134
+ >
135
+ >;
136
+ },
137
+ $strict
138
+ >
71
139
  >;
72
140
  ```
73
141
 
74
142
  Bounded text page, continuation offset and optional hosted file identity.
75
143
 
144
+ ---
145
+
146
+ ### readImageCaptionSchema
147
+
148
+ ```ts
149
+ const readImageCaptionSchema: ZodLazy<ZodObject<{
150
+ path: ZodLazy<ZodString>;
151
+ image: ZodObject<{
152
+ ref: ZodString;
153
+ mimeType: ZodEnum<{
154
+ image/png: "image/png";
155
+ image/jpeg: "image/jpeg";
156
+ image/gif: "image/gif";
157
+ image/webp: "image/webp";
158
+ }>;
159
+ bytes: ZodNumber;
160
+ }, $strict>;
161
+ }, $strict>>;
162
+ ```
163
+
164
+ What guest code and MCP callers see: an image never travels as base64 in the
165
+ value. It is pushed through the execution's image collector and the value is
166
+ only this caption.
167
+
168
+ ---
169
+
170
+ ### readGuestOutputSchema
171
+
172
+ ```ts
173
+ const readGuestOutputSchema: ZodLazy<ZodUnion<readonly [ZodLazy<ZodObject<{
174
+ path: ZodLazy<ZodString>;
175
+ content: ZodString;
176
+ offset: ZodNumber;
177
+ nextOffset: ZodNullable<ZodNumber>;
178
+ truncated: ZodBoolean;
179
+ file: ZodExactOptional<ZodLazy<ZodObject<{
180
+ computerId: ZodString;
181
+ path: ZodLazy<...>;
182
+ }, $strict>>>;
183
+ }, $strict>>, ZodLazy<ZodObject<{
184
+ path: ZodLazy<ZodString>;
185
+ image: ZodObject<{
186
+ ref: ZodString;
187
+ mimeType: ZodEnum<{
188
+ image/png: "image/png";
189
+ image/jpeg: "image/jpeg";
190
+ image/gif: "image/gif";
191
+ image/webp: "image/webp";
192
+ }>;
193
+ bytes: ZodNumber;
194
+ }, $strict>;
195
+ }, $strict>>]>>;
196
+ ```
197
+
198
+ Text page or image caption returned to guest code.
199
+
200
+ ---
201
+
202
+ ### readOutputSchema
203
+
204
+ ```ts
205
+ const readOutputSchema: ZodLazy<ZodUnion<readonly [ZodLazy<ZodObject<{
206
+ path: ZodLazy<ZodString>;
207
+ content: ZodString;
208
+ offset: ZodNumber;
209
+ nextOffset: ZodNullable<ZodNumber>;
210
+ truncated: ZodBoolean;
211
+ file: ZodExactOptional<ZodLazy<ZodObject<{
212
+ computerId: ZodString;
213
+ path: ZodLazy<...>;
214
+ }, $strict>>>;
215
+ }, $strict>>, ZodLazy<ZodObject<{
216
+ path: ZodLazy<ZodString>;
217
+ image: ZodObject<{
218
+ mimeType: ZodEnum<{
219
+ image/png: "image/png";
220
+ image/jpeg: "image/jpeg";
221
+ image/gif: "image/gif";
222
+ image/webp: "image/webp";
223
+ }>;
224
+ data: ZodBase64;
225
+ bytes: ZodNumber;
226
+ }, $strict>;
227
+ file: ZodExactOptional<ZodLazy<ZodObject<{
228
+ computerId: ZodString;
229
+ path: ZodLazy<...>;
230
+ }, $strict>>>;
231
+ }, $strict>>]>>;
232
+ ```
233
+
234
+ A bounded text page or a whole image; images are only read from offset 0.
235
+
76
236
  ## Functions
77
237
 
238
+ ### sniffImageMimeType()
239
+
240
+ ```ts
241
+ function sniffImageMimeType(head): string | undefined;
242
+ ```
243
+
244
+ Identify png, jpeg, gif or webp by their full magic bytes; `undefined` means not
245
+ a supported image.
246
+
247
+ #### Parameters
248
+
249
+ | Parameter | Type |
250
+ | --------- | ------------ |
251
+ | `head` | `Uint8Array` |
252
+
253
+ #### Returns
254
+
255
+ `string` \| `undefined`
256
+
257
+ ---
258
+
259
+ ### imageTooLargeMessage()
260
+
261
+ ```ts
262
+ function imageTooLargeMessage(path, size, limit): string;
263
+ ```
264
+
265
+ Message for an image above a host's read cap, shared by every Files host.
266
+
267
+ #### Parameters
268
+
269
+ | Parameter | Type |
270
+ | --------- | -------- |
271
+ | `path` | `string` |
272
+ | `size` | `number` |
273
+ | `limit` | `number` |
274
+
275
+ #### Returns
276
+
277
+ `string`
278
+
279
+ ---
280
+
78
281
  ### boundedTextSchema()
79
282
 
80
283
  ```ts
@@ -92,3 +295,36 @@ Validate text against a minimum character length and the Files UTF-8 byte limit.
92
295
  #### Returns
93
296
 
94
297
  `ZodString`
298
+
299
+ ---
300
+
301
+ ### imageReadResult()
302
+
303
+ ```ts
304
+ function imageReadResult(path, mimeType, bytes): object;
305
+ ```
306
+
307
+ Build the image read result for a recognised image's complete bytes.
308
+
309
+ #### Parameters
310
+
311
+ | Parameter | Type |
312
+ | ---------- | ------------ |
313
+ | `path` | `string` |
314
+ | `mimeType` | `string` |
315
+ | `bytes` | `Uint8Array` |
316
+
317
+ #### Returns
318
+
319
+ `object`
320
+
321
+ | Name | Type | Default value |
322
+ | ----------------- | ------------------------------------------------------------------ | ------------------- |
323
+ | `path` | `string` | `pathSchema` |
324
+ | `image` | `object` | - |
325
+ | `image.mimeType` | `"image/png"` \| `"image/jpeg"` \| `"image/gif"` \| `"image/webp"` | `readImageMimeType` |
326
+ | `image.data` | `string` | - |
327
+ | `image.bytes` | `number` | - |
328
+ | `file?` | `object` | - |
329
+ | `file.computerId` | `string` | - |
330
+ | `file.path` | `string` | - |