@lloyal-labs/desktop 0.1.0-alpha.10

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/LICENSE ADDED
@@ -0,0 +1,107 @@
1
+ # Functional Source License, Version 1.1, Apache 2.0 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-Apache-2.0
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Lloyal Labs
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publish, and distribute the
28
+ Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A "Permitted Purpose" is any purpose other than a Competing Use. A
33
+ "Competing Use" means making the Software available to others in a
34
+ commercial product or service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a Licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives
65
+ of the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND,
74
+ INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE,
75
+ MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO
78
+ THE SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
79
+ DAMAGES, EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software
90
+ under the Apache License, Version 2.0 that is effective on the second
91
+ anniversary of the date we make the Software available. On or after that
92
+ date, you may use the Software under the Apache License, Version 2.0, in
93
+ which case the following will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
96
+ use this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software
103
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
104
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
105
+
106
+ See the License for the specific language governing permissions and
107
+ limitations under the License.
@@ -0,0 +1,20 @@
1
+ import type { Descriptor } from '@lloyal-labs/media';
2
+ /** The scheme's authority: `standard` schemes require one. Every route lives under the path, as on HTTP. */
3
+ export declare const CONTENT_ORIGIN = "attachment://store";
4
+ /**
5
+ * Must run BEFORE app ready — afterwards the registration is ignored SILENTLY.
6
+ * All four privileges are load-bearing: `standard` gives the scheme an origin
7
+ * so CSP can name it, `secure` keeps it out of the mixed-content rules,
8
+ * `supportFetchAPI` lets `fetch` address it, and without `corsEnabled` the
9
+ * scheme takes no part in CORS at all, so the handler's allow-origin header
10
+ * is never consulted and every fetch fails before reaching it.
11
+ */
12
+ export declare function registerContentScheme(): void;
13
+ /**
14
+ * Serve reads from the project store; hand an upload to `ingest`. Reading needs
15
+ * no lock: OCI blobs are content-addressed and immutable. Writing is not ours —
16
+ * `ingest` belongs to whichever process owns the store, and takes a signal so a
17
+ * deadline here can actually stop it.
18
+ */
19
+ export declare function serveContentScheme(projectRoot: string, ingest: (bytes: Uint8Array, signal: AbortSignal) => Promise<Descriptor>): void;
20
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrD,4GAA4G;AAC5G,eAAO,MAAM,cAAc,uBAAuB,CAAC;AAEnD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,UAAU,CAAC,GACtE,IAAI,CAuCN"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONTENT_ORIGIN = void 0;
4
+ exports.registerContentScheme = registerContentScheme;
5
+ exports.serveContentScheme = serveContentScheme;
6
+ /**
7
+ * The content plane on a desktop: a custom scheme, not a port.
8
+ *
9
+ * A loopback server would be reachable by every process on the machine, and
10
+ * these routes authenticate nothing by design — a digest is identity, not
11
+ * authorization. `attachment://` is reachable only from this app's own
12
+ * renderers and opens no socket at all. The routes are rig's `resolveContent`,
13
+ * the same table the served host answers over HTTP, so a route added there
14
+ * arrives here for free and the two targets cannot answer one URL differently.
15
+ *
16
+ * @category Desktop
17
+ */
18
+ const electron_1 = require("electron");
19
+ const node_1 = require("@lloyal-labs/rig/node");
20
+ const node_2 = require("@lloyal-labs/media/node");
21
+ const read_bounded_1 = require("./read-bounded");
22
+ /** The scheme's authority: `standard` schemes require one. Every route lives under the path, as on HTTP. */
23
+ exports.CONTENT_ORIGIN = 'attachment://store';
24
+ /**
25
+ * Must run BEFORE app ready — afterwards the registration is ignored SILENTLY.
26
+ * All four privileges are load-bearing: `standard` gives the scheme an origin
27
+ * so CSP can name it, `secure` keeps it out of the mixed-content rules,
28
+ * `supportFetchAPI` lets `fetch` address it, and without `corsEnabled` the
29
+ * scheme takes no part in CORS at all, so the handler's allow-origin header
30
+ * is never consulted and every fetch fails before reaching it.
31
+ */
32
+ function registerContentScheme() {
33
+ electron_1.protocol.registerSchemesAsPrivileged([
34
+ { scheme: 'attachment', privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true, stream: true } },
35
+ ]);
36
+ }
37
+ /**
38
+ * Serve reads from the project store; hand an upload to `ingest`. Reading needs
39
+ * no lock: OCI blobs are content-addressed and immutable. Writing is not ours —
40
+ * `ingest` belongs to whichever process owns the store, and takes a signal so a
41
+ * deadline here can actually stop it.
42
+ */
43
+ function serveContentScheme(projectRoot, ingest) {
44
+ const media = (0, node_1.createProjectMediaStore)(projectRoot);
45
+ // The renderer is cross-origin to this scheme — a dev server in development,
46
+ // `file://` in a build — so it has no origin to echo. `*` grants nothing
47
+ // further: only this app's own renderers can address the scheme.
48
+ const cors = { 'Access-Control-Allow-Origin': '*' };
49
+ electron_1.protocol.handle('attachment', async (request) => {
50
+ const path = new URL(request.url).pathname;
51
+ // The plane's one WRITE, and the only route not answerable from the store.
52
+ if (request.method === 'POST' && path === '/v1/media/ingress') {
53
+ // ONE deadline over BOTH halves — the transfer and the ingest — and the same bounds the served host
54
+ // passes its route, so both targets admit the same thing.
55
+ const ctrl = new AbortController();
56
+ const timer = setTimeout(() => ctrl.abort(), node_2.DOCUMENT_UPLOAD_TIMEOUT_MS);
57
+ try {
58
+ const declared = Number(request.headers.get('content-length') ?? NaN);
59
+ if (Number.isFinite(declared) && declared > node_2.MAX_DOCUMENT_BYTES)
60
+ throw new read_bounded_1.TooLarge(`upload exceeds ${node_2.MAX_DOCUMENT_BYTES} bytes`);
61
+ const bytes = await (0, read_bounded_1.readBounded)(request.body, node_2.MAX_DOCUMENT_BYTES, ctrl.signal);
62
+ const root = await ingest(bytes, ctrl.signal);
63
+ return Response.json(root, { status: 201, headers: cors });
64
+ }
65
+ catch (err) {
66
+ // The same statuses the HTTP route answers with: 413 too large, 408 too slow, else refused.
67
+ const late = err instanceof read_bounded_1.TooSlow || ctrl.signal.aborted;
68
+ const status = err instanceof read_bounded_1.TooLarge ? 413 : late ? 408 : 400;
69
+ const error = late
70
+ ? `upload exceeded ${node_2.DOCUMENT_UPLOAD_TIMEOUT_MS}ms end to end`
71
+ : err instanceof Error ? err.message : 'ingest failed';
72
+ return Response.json({ error }, { status, headers: cors });
73
+ }
74
+ finally {
75
+ clearTimeout(timer);
76
+ }
77
+ }
78
+ const reply = (0, node_1.resolveContent)({ method: request.method, path, ifNoneMatch: request.headers.get('if-none-match') ?? undefined }, media);
79
+ if (!reply)
80
+ return new Response(null, { status: 404 });
81
+ return new Response(reply.body ?? null, { status: reply.status, headers: { ...reply.headers, ...cors } });
82
+ });
83
+ }
84
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { Descriptor } from '@lloyal-labs/media';
2
+ import type { Frame, SessionState, Snapshot } from '@lloyal-labs/binding';
3
+ /** The slice of Electron's `UtilityProcess` the engine uses, structurally — a test hands in a fake. */
4
+ export interface EngineProcess {
5
+ postMessage(message: unknown): void;
6
+ on(event: 'message', listener: (message: unknown) => void): this;
7
+ on(event: 'exit', listener: (code: number) => void): this;
8
+ kill(): boolean;
9
+ stdout?: {
10
+ on(event: 'data', listener: (chunk: {
11
+ toString(): string;
12
+ }) => void): unknown;
13
+ } | null;
14
+ stderr?: {
15
+ on(event: 'data', listener: (chunk: {
16
+ toString(): string;
17
+ }) => void): unknown;
18
+ } | null;
19
+ }
20
+ export interface CreateEngineOpts<E, S> {
21
+ /** The engine binary — this project's own built cli boot, e.g. `join(process.cwd(), 'bin', 'run.js')`. */
22
+ bin: string;
23
+ /** Added to the engine's environment. `RR_BRIDGE` is set for you: it is what makes the cli boot mount the ipc binding. */
24
+ env?: Record<string, string>;
25
+ initialState: S;
26
+ reduce: (state: S, ev: E) => S;
27
+ /** Where a frame goes: the renderer, when its window is alive. */
28
+ forward: (frame: Frame<E>) => void;
29
+ /** The engine's own stdout/stderr lines. */
30
+ log?: (stream: 'stdout' | 'stderr' | 'exit', text: string) => void;
31
+ /** Fork it yourself. The default forks `bin` as an Electron utility process; a test hands in a fake. */
32
+ fork?: (bin: string, env: NodeJS.ProcessEnv) => EngineProcess;
33
+ }
34
+ export interface Engine<C, S> {
35
+ /** Post a command to the engine. False when the engine is not running. */
36
+ send(command: C): boolean;
37
+ /** The shell's fold as of the last frame forwarded: the renderer's seed on (re)load. */
38
+ snapshot(): Snapshot<S>;
39
+ /** Hand bytes to the engine and wait for the root descriptor it commits. `signal` carries the deadline across. */
40
+ ingest(bytes: Uint8Array, signal: AbortSignal): Promise<Descriptor>;
41
+ /** Stop the engine. */
42
+ kill(): void;
43
+ /** This session's life, as the renderer's view reads it. */
44
+ session(): SessionState;
45
+ /** Subscribe to it: the current state at once, then every change. Returns the unsubscribe. */
46
+ onSession(cb: (state: SessionState) => void): () => void;
47
+ /**
48
+ * Replace the engine with a fresh one — the reader's way back from a session
49
+ * that ended.
50
+ *
51
+ * The replacement is not forked until the old process has actually gone:
52
+ * `draining` says work cannot be taken, never that the model has been freed.
53
+ * Settles when the replacement is USABLE or has failed to start, and a request
54
+ * made in the meantime reuses the one in flight — what the reader asked for is
55
+ * a working harness, and between the fork and `ready` there is not one yet, so
56
+ * a second press would otherwise kill an engine still loading its model.
57
+ */
58
+ restart(): Promise<void>;
59
+ readonly running: boolean;
60
+ }
61
+ export declare function createEngine<E, C, S>(opts: CreateEngineOpts<E, S>): Engine<C, S>;
62
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAE1E,uGAAuG;AACvG,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;IACjE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC1D,IAAI,IAAI,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,KAAK,IAAI,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAClG,MAAM,CAAC,EAAE;QAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE;YAAE,QAAQ,IAAI,MAAM,CAAA;SAAE,KAAK,IAAI,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACnG;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,EAAE,CAAC;IACpC,0GAA0G;IAC1G,GAAG,EAAE,MAAM,CAAC;IACZ,0HAA0H;IAC1H,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,YAAY,EAAE,CAAC,CAAC;IAChB,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;IAC/B,kEAAkE;IAClE,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACnC,4CAA4C;IAC5C,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnE,wGAAwG;IACxG,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,aAAa,CAAC;CAC/D;AASD,MAAM,WAAW,MAAM,CAAC,CAAC,EAAE,CAAC;IAC1B,0EAA0E;IAC1E,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC;IAC1B,wFAAwF;IACxF,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;IACxB,kHAAkH;IAClH,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACpE,uBAAuB;IACvB,IAAI,IAAI,IAAI,CAAC;IACb,4DAA4D;IAC5D,OAAO,IAAI,YAAY,CAAC;IACxB,8FAA8F;IAC9F,SAAS,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACzD;;;;;;;;;;OAUG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CA4KhF"}
package/dist/engine.js ADDED
@@ -0,0 +1,233 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createEngine = createEngine;
4
+ /**
5
+ * The engine: the project's own cli bin, forked as an Electron utility process
6
+ * with the `ipc` binding mounted, and the shell's fold over what it says.
7
+ *
8
+ * Streaming model, the same as every target: the engine emits raw events; the
9
+ * shell numbers each one (`seq` within this engine's `epoch`) and forwards the
10
+ * frame to the renderer, which folds it through the SAME pure reduce — only
11
+ * the small event crosses IPC, never the growing transcript. The shell also
12
+ * folds every event itself, purely to answer ONE snapshot per (re)load: the
13
+ * renderer seeds from it and applies only frames after the cut. An engine
14
+ * respawn is a new epoch, so a renderer that outlives one cannot mix two.
15
+ *
16
+ * The shell relays uploads to the engine and waits for the root descriptor it
17
+ * commits: the engine is the store's single writer.
18
+ *
19
+ * **It also owns the session's life, in the platform's own words.** A desktop
20
+ * shell has a host in every sense that matters to a reader — something that
21
+ * starts the harness, knows when it is usable and knows when it is gone — so it
22
+ * reports {@link SessionState} rather than inventing a second vocabulary for
23
+ * the same facts. `warming` at the fork, `live` at the child's own `ready`
24
+ * (which means the command channel is attached, and is the earliest honest
25
+ * boundary — a child exists while it is still loading a model), `draining`
26
+ * once a stop is asked for, then `reaped` or `died`. The renderer's IPC link
27
+ * stays up throughout, which is exactly why the session's state and the
28
+ * transport's are two facts and not one.
29
+ *
30
+ * @category Desktop
31
+ */
32
+ const node_fs_1 = require("node:fs");
33
+ const electron_1 = require("electron");
34
+ /** The default: this project's cli boot as a utility process, with the bridge flag set and its output piped. */
35
+ function forkUtilityProcess(bin, env) {
36
+ // A missing binary is the one failure worth naming: the window would otherwise open onto an engine that never speaks.
37
+ if (!(0, node_fs_1.existsSync)(bin))
38
+ throw new Error(`engine not built: ${bin} not found — build the cli target first.`);
39
+ return electron_1.utilityProcess.fork(bin, [], { serviceName: 'harness-engine', stdio: 'pipe', env });
40
+ }
41
+ function createEngine(opts) {
42
+ const fork = opts.fork ?? forkUtilityProcess;
43
+ let epoch = Date.now();
44
+ let seq = 0;
45
+ let state = opts.initialState;
46
+ let child = null;
47
+ /** Which fork we own. A dead child's listeners stay attached to its own process object. */
48
+ let generation = 0;
49
+ let stopping = false; // we asked it to go
50
+ let replacing = false; // …and something is taking its place, so its end is not the session's
51
+ let awaitExit = null;
52
+ /** Armed across a replacement's startup: settled when it is usable, or when it failed to start. */
53
+ let awaitStartup = null;
54
+ let restarting = null;
55
+ let session = { phase: 'warming' };
56
+ const sessionListeners = new Set();
57
+ const pending = new Map();
58
+ let ingestId = 0;
59
+ const announce = (next) => {
60
+ session = next;
61
+ // A startup is over at its first outcome, either way. `warming` is not one.
62
+ if (next.phase === 'live' || next.phase === 'died') {
63
+ const up = awaitStartup;
64
+ awaitStartup = null;
65
+ up?.();
66
+ }
67
+ for (const cb of sessionListeners)
68
+ cb(next);
69
+ };
70
+ const post = (message) => {
71
+ if (!child)
72
+ return false;
73
+ try {
74
+ child.postMessage(message);
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ };
81
+ const failPending = (why) => {
82
+ for (const [, waiting] of pending)
83
+ waiting.reject(new Error(why));
84
+ pending.clear();
85
+ };
86
+ function start() {
87
+ const mine = ++generation;
88
+ stopping = false;
89
+ replacing = false;
90
+ announce({ phase: 'warming' });
91
+ let proc;
92
+ try {
93
+ proc = fork(opts.bin, { ...process.env, ...opts.env, RR_BRIDGE: '1' });
94
+ }
95
+ catch (err) {
96
+ // A shell whose engine will not start must still open and say so; throwing here would take
97
+ // the main process down before the window that could report it exists.
98
+ child = null;
99
+ opts.log?.('exit', err instanceof Error ? err.message : String(err));
100
+ announce({ phase: 'died' });
101
+ return;
102
+ }
103
+ child = proc;
104
+ proc.stdout?.on('data', (d) => opts.log?.('stdout', d.toString().trimEnd()));
105
+ proc.stderr?.on('data', (d) => opts.log?.('stderr', d.toString().trimEnd()));
106
+ proc.on('message', (raw) => {
107
+ if (mine !== generation)
108
+ return; // a child we no longer own, still talking
109
+ const msg = raw;
110
+ if (msg?.t === 'ready') {
111
+ // The command channel is attached. Not a promise that the harness has finished booting —
112
+ // it is simply the first moment anything the reader does can reach it.
113
+ announce({ phase: 'live' });
114
+ return;
115
+ }
116
+ if (msg?.t === 'event' && msg.payload !== undefined) {
117
+ seq += 1;
118
+ state = opts.reduce(state, msg.payload);
119
+ opts.forward({ epoch, seq, ev: msg.payload });
120
+ return;
121
+ }
122
+ if (typeof msg?.id !== 'number')
123
+ return;
124
+ const waiting = pending.get(msg.id);
125
+ if (!waiting)
126
+ return;
127
+ pending.delete(msg.id);
128
+ if (msg.t === 'ingested' && msg.root)
129
+ waiting.resolve(msg.root);
130
+ else if (msg.t === 'ingestFailed')
131
+ waiting.reject(new Error(msg.error ?? 'ingest failed'));
132
+ });
133
+ proc.on('exit', (code) => {
134
+ if (mine !== generation)
135
+ return;
136
+ opts.log?.('exit', String(code));
137
+ // Drop the handle FIRST: a stale one passes `if (!child)`, and a message to a dead process is discarded
138
+ // silently. An ingest in flight must FAIL, not hang.
139
+ child = null;
140
+ failPending('the engine stopped before the file was ingested');
141
+ const wake = awaitExit;
142
+ awaitExit = null;
143
+ // A replacement is coming: the reader is between engines, not without one, so nothing terminal
144
+ // is said and the banner does not flash "ended" on the way to a working session.
145
+ if (replacing)
146
+ return wake?.();
147
+ announce(stopping ? { phase: 'reaped' } : { phase: 'died', ...(code != null ? { code } : {}) });
148
+ wake?.();
149
+ });
150
+ }
151
+ start();
152
+ return {
153
+ send: (command) => post({ t: 'command', payload: command }),
154
+ snapshot: () => ({ state, epoch, seq }),
155
+ session: () => session,
156
+ onSession(cb) {
157
+ sessionListeners.add(cb);
158
+ cb(session); // a renderer that loads mid-session is told where things stand
159
+ return () => { sessionListeners.delete(cb); };
160
+ },
161
+ ingest(bytes, signal) {
162
+ // An already-aborted signal fires no `abort` event, so the entry would sit in `pending` for the life of the process.
163
+ if (signal.aborted)
164
+ return Promise.reject(new Error('the ingest was cancelled'));
165
+ if (!child)
166
+ return Promise.reject(new Error('the engine is not running'));
167
+ const id = ++ingestId;
168
+ const answer = new Promise((resolve, reject) => pending.set(id, { resolve, reject }));
169
+ const settle = (fail) => {
170
+ const waiting = pending.get(id);
171
+ if (!waiting)
172
+ return;
173
+ pending.delete(id);
174
+ waiting.reject(fail);
175
+ };
176
+ signal.addEventListener('abort', () => {
177
+ post({ t: 'ingestCancel', id });
178
+ settle(new Error('the ingest was cancelled'));
179
+ }, { once: true });
180
+ if (!post({ t: 'ingest', id, bytes }))
181
+ settle(new Error('the engine is not running'));
182
+ return answer;
183
+ },
184
+ kill() {
185
+ if (!child)
186
+ return;
187
+ stopping = true;
188
+ announce({ phase: 'draining' });
189
+ try {
190
+ child.kill();
191
+ }
192
+ catch { /* already gone; its exit still arrives */ }
193
+ },
194
+ restart() {
195
+ if (restarting)
196
+ return restarting; // the reader pressed it twice; one engine, one promise
197
+ const done = (async () => {
198
+ if (child) {
199
+ stopping = true;
200
+ replacing = true;
201
+ announce({ phase: 'draining' });
202
+ const gone = new Promise((resolve) => { awaitExit = resolve; });
203
+ try {
204
+ child.kill();
205
+ }
206
+ catch { /* already gone; its exit still arrives */ }
207
+ await gone; // the model is resident until the process is gone: never two at once
208
+ }
209
+ // A fresh stream. The renderer compares frames only within an epoch, so the replacement
210
+ // must not reuse one — `Date.now()` alone can repeat inside a millisecond.
211
+ epoch = Math.max(Date.now(), epoch + 1);
212
+ seq = 0;
213
+ state = opts.initialState;
214
+ // Stay in flight until the replacement is usable or has failed, so a reader who presses
215
+ // again a moment later REUSES this one. Clearing at the fork would let the second press
216
+ // kill an engine that is still loading its model, throwing away the wait and starting it
217
+ // over — and the second press is exactly what an unresponsive-looking startup invites.
218
+ // It ends at `died` too, so a startup that failed leaves recovery available again.
219
+ const up = new Promise((resolve) => { awaitStartup = resolve; });
220
+ start();
221
+ await up;
222
+ })();
223
+ restarting = done;
224
+ void done.finally(() => { if (restarting === done)
225
+ restarting = null; });
226
+ return done;
227
+ },
228
+ get running() {
229
+ return child !== null;
230
+ },
231
+ };
232
+ }
233
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `@lloyal-labs/desktop` — a harness in a native window. The Electron main
3
+ * process is a thin host: it owns the window and forks the project's own cli
4
+ * bin as the engine; heavy work (native inference, the harness) lives there,
5
+ * so the UI thread never blocks.
6
+ *
7
+ * @packageDocumentation
8
+ * @category Desktop
9
+ */
10
+ export { createEngine } from './engine';
11
+ export type { Engine, EngineProcess, CreateEngineOpts } from './engine';
12
+ export { registerContentScheme, serveContentScheme, CONTENT_ORIGIN } from './content';
13
+ export { readBounded, TooLarge, TooSlow } from './read-bounded';
14
+ export { createWindow } from './window';
15
+ export type { CreateWindowOpts } from './window';
16
+ export { CHANNELS } from './preload-channels';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACtF,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CHANNELS = exports.createWindow = exports.TooSlow = exports.TooLarge = exports.readBounded = exports.CONTENT_ORIGIN = exports.serveContentScheme = exports.registerContentScheme = exports.createEngine = void 0;
4
+ /**
5
+ * `@lloyal-labs/desktop` — a harness in a native window. The Electron main
6
+ * process is a thin host: it owns the window and forks the project's own cli
7
+ * bin as the engine; heavy work (native inference, the harness) lives there,
8
+ * so the UI thread never blocks.
9
+ *
10
+ * @packageDocumentation
11
+ * @category Desktop
12
+ */
13
+ var engine_1 = require("./engine");
14
+ Object.defineProperty(exports, "createEngine", { enumerable: true, get: function () { return engine_1.createEngine; } });
15
+ var content_1 = require("./content");
16
+ Object.defineProperty(exports, "registerContentScheme", { enumerable: true, get: function () { return content_1.registerContentScheme; } });
17
+ Object.defineProperty(exports, "serveContentScheme", { enumerable: true, get: function () { return content_1.serveContentScheme; } });
18
+ Object.defineProperty(exports, "CONTENT_ORIGIN", { enumerable: true, get: function () { return content_1.CONTENT_ORIGIN; } });
19
+ var read_bounded_1 = require("./read-bounded");
20
+ Object.defineProperty(exports, "readBounded", { enumerable: true, get: function () { return read_bounded_1.readBounded; } });
21
+ Object.defineProperty(exports, "TooLarge", { enumerable: true, get: function () { return read_bounded_1.TooLarge; } });
22
+ Object.defineProperty(exports, "TooSlow", { enumerable: true, get: function () { return read_bounded_1.TooSlow; } });
23
+ var window_1 = require("./window");
24
+ Object.defineProperty(exports, "createWindow", { enumerable: true, get: function () { return window_1.createWindow; } });
25
+ var preload_channels_1 = require("./preload-channels");
26
+ Object.defineProperty(exports, "CHANNELS", { enumerable: true, get: function () { return preload_channels_1.CHANNELS; } });
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,mCAAwC;AAA/B,sGAAA,YAAY,OAAA;AAErB,qCAAsF;AAA7E,gHAAA,qBAAqB,OAAA;AAAE,6GAAA,kBAAkB,OAAA;AAAE,yGAAA,cAAc,OAAA;AAClE,+CAAgE;AAAvD,2GAAA,WAAW,OAAA;AAAE,wGAAA,QAAQ,OAAA;AAAE,uGAAA,OAAO,OAAA;AACvC,mCAAwC;AAA/B,sGAAA,YAAY,OAAA;AAErB,uDAA8C;AAArC,4GAAA,QAAQ,OAAA"}
@@ -0,0 +1,13 @@
1
+ /** The IPC channel names the shell and the preload agree on — importable without `electron`. */
2
+ export declare const CHANNELS: {
3
+ readonly event: "harness:event";
4
+ readonly command: "harness:command";
5
+ readonly snapshot: "harness:snapshot";
6
+ /** Main pushes each {@link SessionState} change here. */
7
+ readonly session: "harness:session";
8
+ /** …and answers with the current one here, so a renderer that loads mid-session is not left guessing. */
9
+ readonly sessionNow: "harness:session-now";
10
+ /** The reader asking for a working engine. Main owns what that means. */
11
+ readonly recover: "harness:recover";
12
+ };
13
+ //# sourceMappingURL=preload-channels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preload-channels.d.ts","sourceRoot":"","sources":["../src/preload-channels.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,eAAO,MAAM,QAAQ;;;;IAInB,yDAAyD;;IAEzD,yGAAyG;;IAEzG,yEAAyE;;CAEjE,CAAC"}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CHANNELS = void 0;
4
+ /** The IPC channel names the shell and the preload agree on — importable without `electron`. */
5
+ exports.CHANNELS = {
6
+ event: 'harness:event',
7
+ command: 'harness:command',
8
+ snapshot: 'harness:snapshot',
9
+ /** Main pushes each {@link SessionState} change here. */
10
+ session: 'harness:session',
11
+ /** …and answers with the current one here, so a renderer that loads mid-session is not left guessing. */
12
+ sessionNow: 'harness:session-now',
13
+ /** The reader asking for a working engine. Main owns what that means. */
14
+ recover: 'harness:recover',
15
+ };
16
+ //# sourceMappingURL=preload-channels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preload-channels.js","sourceRoot":"","sources":["../src/preload-channels.ts"],"names":[],"mappings":";;;AAAA,gGAAgG;AACnF,QAAA,QAAQ,GAAG;IACtB,KAAK,EAAE,eAAe;IACtB,OAAO,EAAE,iBAAiB;IAC1B,QAAQ,EAAE,kBAAkB;IAC5B,yDAAyD;IACzD,OAAO,EAAE,iBAAiB;IAC1B,yGAAyG;IACzG,UAAU,EAAE,qBAAqB;IACjC,yEAAyE;IACzE,OAAO,EAAE,iBAAiB;CAClB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { CHANNELS } from './preload-channels';
2
+ export { CHANNELS };
3
+ export declare function preloadBridge<E, C, S>(contentOrigin?: string): void;
4
+ //# sourceMappingURL=preload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../src/preload.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,CAAC;AAEpB,wBAAgB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,SAAuB,GAAG,IAAI,CAwCjF"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CHANNELS = void 0;
4
+ exports.preloadBridge = preloadBridge;
5
+ /**
6
+ * The preload bridge: `window.harness` for the renderer over Electron's
7
+ * contextBridge, the same {@link Bridge} shape the web target backs with
8
+ * `createBridge`, so the view is transport-agnostic. Frames come down on
9
+ * `harness:event`, commands go up on `harness:command`, the snapshot is one
10
+ * `harness:snapshot` round trip, and the content plane is the custom scheme.
11
+ *
12
+ * Import from `@lloyal-labs/desktop/preload` in the preload script only.
13
+ *
14
+ * @category Desktop
15
+ */
16
+ const electron_1 = require("electron");
17
+ const preload_channels_1 = require("./preload-channels");
18
+ Object.defineProperty(exports, "CHANNELS", { enumerable: true, get: function () { return preload_channels_1.CHANNELS; } });
19
+ function preloadBridge(contentOrigin = 'attachment://store') {
20
+ const api = {
21
+ onEvent(cb) {
22
+ const h = (_e, frame) => cb(frame);
23
+ electron_1.ipcRenderer.on(preload_channels_1.CHANNELS.event, h);
24
+ return () => {
25
+ electron_1.ipcRenderer.removeListener(preload_channels_1.CHANNELS.event, h);
26
+ };
27
+ },
28
+ send(command) {
29
+ electron_1.ipcRenderer.send(preload_channels_1.CHANNELS.command, command);
30
+ },
31
+ requestSnapshot() {
32
+ return electron_1.ipcRenderer.invoke(preload_channels_1.CHANNELS.snapshot);
33
+ },
34
+ onSession(cb) {
35
+ // A renderer that has just loaded needs where things stand, not only what changes next: a
36
+ // session can sit at `live` for hours. The push wins a race with the answer, because the
37
+ // answer was true when it was asked and the push is true now.
38
+ let heard = false;
39
+ const h = (_e, state) => { heard = true; cb(state); };
40
+ electron_1.ipcRenderer.on(preload_channels_1.CHANNELS.session, h);
41
+ void electron_1.ipcRenderer.invoke(preload_channels_1.CHANNELS.sessionNow).then((now) => {
42
+ if (heard)
43
+ return;
44
+ heard = true;
45
+ cb(now);
46
+ });
47
+ return () => {
48
+ electron_1.ipcRenderer.removeListener(preload_channels_1.CHANNELS.session, h);
49
+ };
50
+ },
51
+ recover() {
52
+ // What a working session costs is the placement's business: here, a new engine process.
53
+ void electron_1.ipcRenderer.invoke(preload_channels_1.CHANNELS.recover);
54
+ },
55
+ contentOrigin() {
56
+ return contentOrigin;
57
+ },
58
+ };
59
+ electron_1.contextBridge.exposeInMainWorld('harness', api);
60
+ }
61
+ //# sourceMappingURL=preload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preload.js","sourceRoot":"","sources":["../src/preload.ts"],"names":[],"mappings":";;;AAiBA,sCAwCC;AAzDD;;;;;;;;;;GAUG;AACH,uCAAsD;AAEtD,yDAA8C;AAErC,yFAFA,2BAAQ,OAEA;AAEjB,SAAgB,aAAa,CAAU,aAAa,GAAG,oBAAoB;IACzE,MAAM,GAAG,GAAoB;QAC3B,OAAO,CAAC,EAA6B;YACnC,MAAM,CAAC,GAAG,CAAC,EAAW,EAAE,KAAe,EAAQ,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;YAC5D,sBAAW,CAAC,EAAE,CAAC,2BAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAClC,OAAO,GAAG,EAAE;gBACV,sBAAW,CAAC,cAAc,CAAC,2BAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAChD,CAAC,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,OAAU;YACb,sBAAW,CAAC,IAAI,CAAC,2BAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;QACD,eAAe;YACb,OAAO,sBAAW,CAAC,MAAM,CAAC,2BAAQ,CAAC,QAAQ,CAAyB,CAAC;QACvE,CAAC;QACD,SAAS,CAAC,EAAiC;YACzC,0FAA0F;YAC1F,yFAAyF;YACzF,8DAA8D;YAC9D,IAAI,KAAK,GAAG,KAAK,CAAC;YAClB,MAAM,CAAC,GAAG,CAAC,EAAW,EAAE,KAAmB,EAAQ,EAAE,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACnF,sBAAW,CAAC,EAAE,CAAC,2BAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACpC,KAAM,sBAAW,CAAC,MAAM,CAAC,2BAAQ,CAAC,UAAU,CAA2B,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;gBACnF,IAAI,KAAK;oBAAE,OAAO;gBAClB,KAAK,GAAG,IAAI,CAAC;gBACb,EAAE,CAAC,GAAG,CAAC,CAAC;YACV,CAAC,CAAC,CAAC;YACH,OAAO,GAAG,EAAE;gBACV,sBAAW,CAAC,cAAc,CAAC,2BAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAClD,CAAC,CAAC;QACJ,CAAC;QACD,OAAO;YACL,wFAAwF;YACxF,KAAK,sBAAW,CAAC,MAAM,CAAC,2BAAQ,CAAC,OAAO,CAAC,CAAC;QAC5C,CAAC;QACD,aAAa;YACX,OAAO,aAAa,CAAC;QACvB,CAAC;KACF,CAAC;IACF,wBAAa,CAAC,iBAAiB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Reading an upload under a ceiling and a deadline.
3
+ *
4
+ * Nothing here knows about Electron or the content plane — it is a stream, a
5
+ * cap and a signal — which is what lets it be tested against a stalled stream
6
+ * instead of a three-minute wait.
7
+ *
8
+ * @category Desktop
9
+ */
10
+ /** Over the cap. Separate from a refusal so the client is told which limit. */
11
+ export declare class TooLarge extends Error {
12
+ }
13
+ /** Past the deadline — the transfer, the ingest, or both together. */
14
+ export declare class TooSlow extends Error {
15
+ }
16
+ /**
17
+ * Read the body, refusing AT the cap rather than after it, and giving up the
18
+ * moment the signal says so. The abort is RACED against each read rather than
19
+ * checked between them: a stalled stream is precisely the case where no read
20
+ * resolves. Cancellation is started, never awaited: on a stream whose `read()`
21
+ * is stuck, `cancel()` can be stuck too.
22
+ */
23
+ export declare function readBounded(body: ReadableStream<Uint8Array> | null, cap: number, signal: AbortSignal): Promise<Uint8Array>;
24
+ //# sourceMappingURL=read-bounded.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-bounded.d.ts","sourceRoot":"","sources":["../src/read-bounded.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,+EAA+E;AAC/E,qBAAa,QAAS,SAAQ,KAAK;CAAG;AACtC,sEAAsE;AACtE,qBAAa,OAAQ,SAAQ,KAAK;CAAG;AAErC;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,EACvC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,UAAU,CAAC,CAoCrB"}
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * Reading an upload under a ceiling and a deadline.
4
+ *
5
+ * Nothing here knows about Electron or the content plane — it is a stream, a
6
+ * cap and a signal — which is what lets it be tested against a stalled stream
7
+ * instead of a three-minute wait.
8
+ *
9
+ * @category Desktop
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.TooSlow = exports.TooLarge = void 0;
13
+ exports.readBounded = readBounded;
14
+ /** Over the cap. Separate from a refusal so the client is told which limit. */
15
+ class TooLarge extends Error {
16
+ }
17
+ exports.TooLarge = TooLarge;
18
+ /** Past the deadline — the transfer, the ingest, or both together. */
19
+ class TooSlow extends Error {
20
+ }
21
+ exports.TooSlow = TooSlow;
22
+ /**
23
+ * Read the body, refusing AT the cap rather than after it, and giving up the
24
+ * moment the signal says so. The abort is RACED against each read rather than
25
+ * checked between them: a stalled stream is precisely the case where no read
26
+ * resolves. Cancellation is started, never awaited: on a stream whose `read()`
27
+ * is stuck, `cancel()` can be stuck too.
28
+ */
29
+ async function readBounded(body, cap, signal) {
30
+ if (signal.aborted)
31
+ throw new TooSlow('the upload was cancelled');
32
+ if (!body)
33
+ return new Uint8Array();
34
+ const reader = body.getReader();
35
+ let reject;
36
+ const cancelled = new Promise((_, rej) => { reject = rej; });
37
+ const abort = () => reject(new TooSlow('the upload was cancelled'));
38
+ signal.addEventListener('abort', abort, { once: true });
39
+ const chunks = [];
40
+ let seen = 0;
41
+ try {
42
+ for (;;) {
43
+ const { done, value } = await Promise.race([reader.read(), cancelled]);
44
+ if (done)
45
+ break;
46
+ seen += value.byteLength;
47
+ if (seen > cap)
48
+ throw new TooLarge(`upload exceeds ${cap} bytes`);
49
+ chunks.push(value);
50
+ }
51
+ // Reaching EOF is not safety: the deadline can pass as the last chunk lands.
52
+ if (signal.aborted)
53
+ throw new TooSlow('the upload was cancelled');
54
+ }
55
+ catch (err) {
56
+ void reader.cancel().catch(() => { });
57
+ throw err;
58
+ }
59
+ finally {
60
+ signal.removeEventListener('abort', abort);
61
+ }
62
+ const out = new Uint8Array(seen);
63
+ let at = 0;
64
+ for (const c of chunks) {
65
+ out.set(c, at);
66
+ at += c.byteLength;
67
+ }
68
+ return out;
69
+ }
70
+ //# sourceMappingURL=read-bounded.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-bounded.js","sourceRoot":"","sources":["../src/read-bounded.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAcH,kCAwCC;AApDD,+EAA+E;AAC/E,MAAa,QAAS,SAAQ,KAAK;CAAG;AAAtC,4BAAsC;AACtC,sEAAsE;AACtE,MAAa,OAAQ,SAAQ,KAAK;CAAG;AAArC,0BAAqC;AAErC;;;;;;GAMG;AACI,KAAK,UAAU,WAAW,CAC/B,IAAuC,EACvC,GAAW,EACX,MAAmB;IAEnB,IAAI,MAAM,CAAC,OAAO;QAAE,MAAM,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAClE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,UAAU,EAAE,CAAC;IAEnC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,IAAI,MAA2B,CAAC;IAChC,MAAM,SAAS,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,MAAM,KAAK,GAAG,GAAS,EAAE,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAC1E,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAExD,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,CAAC;QACH,SAAS,CAAC;YACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;YACvE,IAAI,IAAI;gBAAE,MAAM;YAChB,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC;YACzB,IAAI,IAAI,GAAG,GAAG;gBAAE,MAAM,IAAI,QAAQ,CAAC,kBAAkB,GAAG,QAAQ,CAAC,CAAC;YAClE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QACD,6EAA6E;QAC7E,IAAI,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACrC,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACf,EAAE,IAAI,CAAC,CAAC,UAAU,CAAC;IACrB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The window: one `BrowserWindow` over the preload bridge, links kept out of
3
+ * the renderer. The renderer loads the dev server's URL when electron-vite
4
+ * provides one, else the built page.
5
+ *
6
+ * @category Desktop
7
+ */
8
+ import { BrowserWindow } from 'electron';
9
+ import type { BrowserWindowConstructorOptions } from 'electron';
10
+ export interface CreateWindowOpts {
11
+ /** The preload bundle. electron-vite names it after its entry and emits `.mjs`. */
12
+ preload: string;
13
+ /** The built page, loaded when no dev server URL is set (`ELECTRON_RENDERER_URL`). */
14
+ page: string;
15
+ title: string;
16
+ /** Window options over the defaults (size, colours). */
17
+ window?: Partial<BrowserWindowConstructorOptions>;
18
+ }
19
+ export declare function createWindow(opts: CreateWindowOpts): BrowserWindow;
20
+ //# sourceMappingURL=window.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"window.d.ts","sourceRoot":"","sources":["../src/window.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,aAAa,EAAS,MAAM,UAAU,CAAC;AAChD,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,UAAU,CAAC;AAYhE,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,MAAM,CAAC,EAAE,OAAO,CAAC,+BAA+B,CAAC,CAAC;CACnD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,aAAa,CAsBlE"}
package/dist/window.js ADDED
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createWindow = createWindow;
4
+ /**
5
+ * The window: one `BrowserWindow` over the preload bridge, links kept out of
6
+ * the renderer. The renderer loads the dev server's URL when electron-vite
7
+ * provides one, else the built page.
8
+ *
9
+ * @category Desktop
10
+ */
11
+ const electron_1 = require("electron");
12
+ /** Only http(s) links may leave the app. */
13
+ function isExternalUrl(url) {
14
+ try {
15
+ const p = new URL(url).protocol;
16
+ return p === 'http:' || p === 'https:';
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ function createWindow(opts) {
23
+ const win = new electron_1.BrowserWindow({
24
+ width: 1100, height: 760, minWidth: 800, minHeight: 520, show: false, title: opts.title,
25
+ ...opts.window,
26
+ webPreferences: { preload: opts.preload, contextIsolation: true, sandbox: false, ...(opts.window?.webPreferences ?? {}) },
27
+ });
28
+ win.once('ready-to-show', () => win.show());
29
+ // Links open in the system browser; the renderer never navigates away.
30
+ win.webContents.setWindowOpenHandler(({ url }) => {
31
+ if (isExternalUrl(url))
32
+ void electron_1.shell.openExternal(url);
33
+ return { action: 'deny' };
34
+ });
35
+ win.webContents.on('will-navigate', (e, url) => {
36
+ if (url !== win.webContents.getURL()) {
37
+ e.preventDefault();
38
+ if (isExternalUrl(url))
39
+ void electron_1.shell.openExternal(url);
40
+ }
41
+ });
42
+ const dev = process.env.ELECTRON_RENDERER_URL;
43
+ if (dev)
44
+ void win.loadURL(dev);
45
+ else
46
+ void win.loadFile(opts.page);
47
+ return win;
48
+ }
49
+ //# sourceMappingURL=window.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"window.js","sourceRoot":"","sources":["../src/window.ts"],"names":[],"mappings":";;AA8BA,oCAsBC;AApDD;;;;;;GAMG;AACH,uCAAgD;AAGhD,4CAA4C;AAC5C,SAAS,aAAa,CAAC,GAAW;IAChC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;QAChC,OAAO,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,QAAQ,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAYD,SAAgB,YAAY,CAAC,IAAsB;IACjD,MAAM,GAAG,GAAG,IAAI,wBAAa,CAAC;QAC5B,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK;QACvF,GAAG,IAAI,CAAC,MAAM;QACd,cAAc,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,IAAI,EAAE,CAAC,EAAE;KAC1H,CAAC,CAAC;IACH,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5C,uEAAuE;IACvE,GAAG,CAAC,WAAW,CAAC,oBAAoB,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QAC/C,IAAI,aAAa,CAAC,GAAG,CAAC;YAAE,KAAK,gBAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACrD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE;QAC7C,IAAI,GAAG,KAAK,GAAG,CAAC,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YACrC,CAAC,CAAC,cAAc,EAAE,CAAC;YACnB,IAAI,aAAa,CAAC,GAAG,CAAC;gBAAE,KAAK,gBAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACvD,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAC9C,IAAI,GAAG;QAAE,KAAK,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;;QAC1B,KAAK,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,OAAO,GAAG,CAAC;AACb,CAAC"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@lloyal-labs/desktop",
3
+ "version": "0.1.0-alpha.10",
4
+ "description": "A harness in a native window: the Electron shell's main-process pieces — the engine over the project's own cli bin, the content scheme, the preload bridge and the window",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./preload": {
13
+ "types": "./dist/preload.d.ts",
14
+ "default": "./dist/preload.js"
15
+ }
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/lloyal-ai/hdk.git",
23
+ "directory": "packages/desktop"
24
+ },
25
+ "homepage": "https://github.com/lloyal-ai/hdk/tree/main/packages/desktop#readme",
26
+ "bugs": {
27
+ "url": "https://github.com/lloyal-ai/hdk/issues"
28
+ },
29
+ "keywords": [
30
+ "desktop",
31
+ "electron",
32
+ "harness",
33
+ "shell"
34
+ ],
35
+ "license": "SEE LICENSE IN LICENSE",
36
+ "type": "commonjs",
37
+ "scripts": {
38
+ "build": "tsc -b"
39
+ },
40
+ "dependencies": {
41
+ "@lloyal-labs/binding": "0.2.0-alpha.6",
42
+ "@lloyal-labs/media": "0.2.0-alpha.6",
43
+ "@lloyal-labs/rig": "5.6.0-alpha.10"
44
+ },
45
+ "peerDependencies": {
46
+ "electron": ">=38"
47
+ },
48
+ "devDependencies": {
49
+ "electron": "^44.0.0"
50
+ },
51
+ "files": [
52
+ "dist/",
53
+ "LICENSE"
54
+ ]
55
+ }