@ai-matrx/desktop-protocol 0.6.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +13 -0
- package/dist/client.cjs +272 -42
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +9 -2
- package/dist/client.d.ts +9 -2
- package/dist/client.js +272 -42
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +776 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1048 -5
- package/dist/index.d.ts +1048 -5
- package/dist/index.js +776 -5
- package/dist/index.js.map +1 -1
- package/dist/react.d.cts +305 -1
- package/dist/react.d.ts +305 -1
- package/dist/{registry-jTmq1CsI.d.cts → registry-B3NqARgh.d.cts} +1314 -3
- package/dist/{registry-jTmq1CsI.d.ts → registry-B3NqARgh.d.ts} +1314 -3
- package/generated/desktop-protocol.schema.json +4554 -0
- package/package.json +3 -1
- package/scripts/gen_pydantic.py +335 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
Protocol 1.2 adds the `apps` capability (SPEC §6): the desktop apps a person uses (Claude, Codex,
|
|
6
|
+
Cursor, VS Code, Antigravity), their profiles (one account each, opened side by side) and usage.
|
|
7
|
+
|
|
8
|
+
- Ops `apps.list`, `apps.profiles.list`, `apps.profiles.save`, `apps.profiles.remove`, `apps.open`
|
|
9
|
+
(`src/schema/apps.ts`). A profile is a row of `users.desktop_app_profile`; folders found on disk
|
|
10
|
+
that no row names yet are listed with `id: null`. Each app reports `open`, `profiles` and `usage`
|
|
11
|
+
as supported, partial or unsupported on the device's OS, with a one-line reason.
|
|
12
|
+
- `AppProfileLocator`: a profile folder relative to the app's data root; never absolute, no segment
|
|
13
|
+
starting with a dot.
|
|
14
|
+
|
|
15
|
+
Consumer action: none for existing ops.
|
|
16
|
+
|
|
17
|
+
## 0.9.0
|
|
18
|
+
|
|
19
|
+
Protocol 1.2: the `models` capability (local transcription) and the contract for supervised Python
|
|
20
|
+
services (SPEC §3.10).
|
|
21
|
+
|
|
22
|
+
- Ops `models.status`, `models.install`, `models.transcribe` and the event `models.progress`
|
|
23
|
+
(`src/schema/models.ts`). Install and transcribe report progress to the requesting client;
|
|
24
|
+
transcribe streams each recognised segment as it lands.
|
|
25
|
+
- `SERVICES` (registry.ts) and the service envelope (`src/schema/service.ts`): the length-framed
|
|
26
|
+
JSON a core speaks to a Python service over its local socket — `hello` with a per-launch token and
|
|
27
|
+
`CONTRACT_SHA256`, `request`/`response`/`error`/`progress`/`cancel`, `ping`/`pong`, `shutdown`.
|
|
28
|
+
A forwarded Router op uses the same schemas as its service op.
|
|
29
|
+
- `CONTRACT_SHA256` (`src/contract.ts`, generated): the schema file's hash. The generated Python
|
|
30
|
+
module carries the same value, plus `SERVICES`, `SERVICE_PROGRESS` and a typed handler
|
|
31
|
+
`Protocol` per service (`MlServiceHandlers`), so `pyright --strict` fails a service that drifts.
|
|
32
|
+
- Error reasons `runtime_not_installed`, `model_not_installed`, `unsupported_media`,
|
|
33
|
+
`download_failed`, `service_starting`, `service_restarted`, `service_failed`, listed in
|
|
34
|
+
`REASONS_SINCE`; `forPeer` drops a reason an older peer does not know.
|
|
35
|
+
- The tarball ships `generated/desktop-protocol.schema.json` and `scripts/gen_pydantic.py`, so a
|
|
36
|
+
consumer that runs Python (matrx-desktop's services) generates its Pydantic twin with the one
|
|
37
|
+
generator, from the exact schema it installed.
|
|
38
|
+
|
|
39
|
+
Consumer action: none for existing ops. Python consumers regenerate from the installed package
|
|
40
|
+
(`gen_pydantic.py --schema node_modules/@ai-matrx/desktop-protocol/generated/desktop-protocol.schema.json`).
|
|
41
|
+
|
|
42
|
+
## 0.8.0
|
|
43
|
+
|
|
44
|
+
- Local tool proof records (SPEC §10): `ToolLevel`, `ToolOutcome`, `ToolPlatform`, `ToolTransport`,
|
|
45
|
+
`ToolName`, `ToolLevelResult`, `ToolResultsFile` (`src/schema/tools.ts`), generated into the
|
|
46
|
+
Pydantic twin, so the desktop's vitest harnesses and aidream's live pytest write one shape.
|
|
47
|
+
- `TOOL_CATALOG`, `TOOL_GROUPS`, `LEGACY_ACTIONS`, `LEGACY_DISPATCHERS` (`src/tools/catalog.ts`):
|
|
48
|
+
every tool a platform agent calls on a computer, the ops it reaches, its group, and what became of
|
|
49
|
+
each of matrx-local's 139 handlers.
|
|
50
|
+
- `buildToolStatus(files)` and `renderToolStatusMarkdown(status)` (`src/tools/status.ts`): the
|
|
51
|
+
generated tool × level × platform table. Level 2 reads `pass` only with localhost, the relay and
|
|
52
|
+
the relay from another network; a tool is working only when all three levels pass.
|
|
53
|
+
|
|
54
|
+
Consumer action: cite tool status only from `buildToolStatus`; never hand-write a status.
|
|
55
|
+
|
|
56
|
+
## 0.7.0
|
|
57
|
+
|
|
58
|
+
- `CLOSE_RULES` / `closeRule(code)` / `isTerminalClose(code)` (`src/close-policy.ts`): the one table
|
|
59
|
+
of what a dialer does after each close — `reconnect_now`, `reconnect`, `reauth` (one retry with a
|
|
60
|
+
fresh token; a second refusal before any welcome is terminal, outcome `signed_out`), or `stop`
|
|
61
|
+
with an `outcome` (`not_owned` 4003, `removed` 4004, `replaced` 4009, `revoked` 4010,
|
|
62
|
+
`upgrade_required` 4426). A CloseCode without a rule fails to compile; unknown codes reconnect.
|
|
63
|
+
- The client reads that table (its private terminal list is gone) and reports
|
|
64
|
+
`DesktopClientState.outcome` when it stops; 4001/4008 no longer redial forever.
|
|
65
|
+
|
|
66
|
+
Consumer action: never keep a local list of terminal close codes; branch on `closeRule(code)` and
|
|
67
|
+
show `state.outcome`.
|
|
68
|
+
|
|
3
69
|
## 0.6.0
|
|
4
70
|
|
|
5
71
|
- `RelayDeviceStatusEvent.since_ms` is `null` for a device that has never connected (protocol 1.1;
|
package/README.md
CHANGED
|
@@ -88,6 +88,19 @@ params = validate_wire(FsListParams, raw_json) # same verdict as zod, always
|
|
|
88
88
|
`validate_wire` is the one Python entry point: strict (no `"2"` → 2) and JavaScript-faithful
|
|
89
89
|
(`2.0` is the integer 2). `OPS` maps every op to its params/result models, kind and errors.
|
|
90
90
|
|
|
91
|
+
## Python services (matrx-desktop)
|
|
92
|
+
|
|
93
|
+
The desktop's supervised Python services generate their models from the installed package with
|
|
94
|
+
the same generator, so the core and the service can never drift:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
python3 node_modules/@ai-matrx/desktop-protocol/scripts/gen_pydantic.py \
|
|
98
|
+
--schema node_modules/@ai-matrx/desktop-protocol/generated/desktop-protocol.schema.json --out <module>.py
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The module carries `SERVICES`, a typed handler `Protocol` per service (`MlServiceHandlers`) and
|
|
102
|
+
`CONTRACT_SHA256`, which must equal the core's (`import { CONTRACT_SHA256 }`) in the service hello.
|
|
103
|
+
|
|
91
104
|
## Changing the protocol
|
|
92
105
|
|
|
93
106
|
Edit `src/schema/*.ts`, then `pnpm generate` and commit all three files. Rules (enforced by
|
package/dist/client.cjs
CHANGED
|
@@ -124,7 +124,7 @@ var import_zod2 = require("zod");
|
|
|
124
124
|
// src/schema/primitives.ts
|
|
125
125
|
var import_zod = require("zod");
|
|
126
126
|
var PROTOCOL_MAJOR = 1;
|
|
127
|
-
var PROTOCOL_MINOR =
|
|
127
|
+
var PROTOCOL_MINOR = 2;
|
|
128
128
|
var PROTOCOL_VERSION = `${PROTOCOL_MAJOR}.${PROTOCOL_MINOR}`;
|
|
129
129
|
var ProtocolVersion = import_zod.z.string().regex(/^[1-9][0-9]{0,2}\.[0-9]{1,3}$/);
|
|
130
130
|
var RequestId = import_zod.z.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
|
|
@@ -182,7 +182,22 @@ var ErrorReason = import_zod2.z.enum([
|
|
|
182
182
|
"resource_expired",
|
|
183
183
|
"control_taken",
|
|
184
184
|
// another client of the same user took control
|
|
185
|
-
"window_exhausted"
|
|
185
|
+
"window_exhausted",
|
|
186
|
+
// 1.2 (REASONS_SINCE): the `models` capability and its supervised Python service
|
|
187
|
+
"runtime_not_installed",
|
|
188
|
+
// NOT_FOUND: install the inference runtime first (models.install)
|
|
189
|
+
"model_not_installed",
|
|
190
|
+
// NOT_FOUND: that model's weights are not on the device
|
|
191
|
+
"unsupported_media",
|
|
192
|
+
// INVALID_PARAMS: the file is not audio or video the decoder reads
|
|
193
|
+
"download_failed",
|
|
194
|
+
// install could not fetch a package or model file
|
|
195
|
+
"service_starting",
|
|
196
|
+
// RESOURCE_BUSY (retryable): the service is starting or restarting
|
|
197
|
+
"service_restarted",
|
|
198
|
+
// RESOURCE_GONE: the service died while this request ran
|
|
199
|
+
"service_failed"
|
|
200
|
+
// INTERNAL: the service could not be started (see models.status)
|
|
186
201
|
]);
|
|
187
202
|
var FsPatchEditFailure = import_zod2.z.strictObject({ edit_index: nonNegInt, reason: import_zod2.z.string().max(300) });
|
|
188
203
|
var ProtocolErrorBody = import_zod2.z.strictObject({
|
|
@@ -311,6 +326,29 @@ var CloseCode = {
|
|
|
311
326
|
};
|
|
312
327
|
var CloseCodeValue = import_zod3.z.literal([1e3, 1001, 1006, 1012, 1013, 4001, 4003, 4004, 4008, 4009, 4010, 4400, 4426]);
|
|
313
328
|
|
|
329
|
+
// src/close-policy.ts
|
|
330
|
+
var CLOSE_RULES = {
|
|
331
|
+
[CloseCode.NORMAL]: { action: "reconnect", error: ["DEVICE_OFFLINE", "Connection closed"] },
|
|
332
|
+
[CloseCode.GOING_AWAY]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Connection lost"] },
|
|
333
|
+
[CloseCode.ABNORMAL]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Connection lost"] },
|
|
334
|
+
[CloseCode.DEVICE_RECONNECTED]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Device reconnected"] },
|
|
335
|
+
[CloseCode.TRY_AGAIN_LATER]: { action: "reconnect", error: ["DEVICE_OFFLINE", "Relay busy; retrying"] },
|
|
336
|
+
[CloseCode.AUTH_INVALID]: { action: "reauth", outcome: "signed_out", error: ["AUTH_INVALID", "Sign-in was not accepted"] },
|
|
337
|
+
[CloseCode.AUTH_FORBIDDEN]: { action: "stop", outcome: "not_owned", error: ["AUTH_FORBIDDEN", "Not this device's owner"] },
|
|
338
|
+
[CloseCode.DEVICE_NOT_FOUND]: { action: "stop", outcome: "removed", error: ["NOT_FOUND", "Device not found"] },
|
|
339
|
+
[CloseCode.AUTH_EXPIRED]: { action: "reauth", outcome: "signed_out", error: ["AUTH_EXPIRED", "Sign-in expired"] },
|
|
340
|
+
[CloseCode.REPLACED]: { action: "stop", outcome: "replaced", error: ["CONFLICT", "Replaced by a newer connection"] },
|
|
341
|
+
[CloseCode.DEVICE_REVOKED]: { action: "stop", outcome: "revoked", error: ["AUTH_DEVICE_REVOKED", "This device was revoked"] },
|
|
342
|
+
// A malformed message, or a connection the relay lost track of: a fresh socket starts clean.
|
|
343
|
+
[CloseCode.PROTOCOL_ERROR]: { action: "reconnect", error: ["PROTOCOL_ERROR", "Connection reset by the relay"] },
|
|
344
|
+
[CloseCode.UPGRADE_REQUIRED]: { action: "stop", outcome: "upgrade_required", error: ["PROTOCOL_VERSION_UNSUPPORTED", "Protocol upgrade required"] }
|
|
345
|
+
};
|
|
346
|
+
var UNKNOWN = { action: "reconnect", error: ["DEVICE_OFFLINE", "Connection lost"] };
|
|
347
|
+
function closeRule(code) {
|
|
348
|
+
if (code === null || code === void 0) return CLOSE_RULES[CloseCode.ABNORMAL];
|
|
349
|
+
return CLOSE_RULES[code] ?? UNKNOWN;
|
|
350
|
+
}
|
|
351
|
+
|
|
314
352
|
// src/schema/frame.ts
|
|
315
353
|
var import_zod4 = require("zod");
|
|
316
354
|
var FrameHeader = import_zod4.z.strictObject({
|
|
@@ -642,34 +680,115 @@ var SessionListParams = import_zod8.z.strictObject({});
|
|
|
642
680
|
var SessionListResult = import_zod8.z.strictObject({ resources: import_zod8.z.array(ResourceInfo) });
|
|
643
681
|
var SessionControlChangedEvent = import_zod8.z.strictObject({ controller_client_id: import_zod8.z.string().max(64).nullable(), your_mode: import_zod8.z.enum(["control", "read_only"]) });
|
|
644
682
|
|
|
645
|
-
// src/schema/
|
|
683
|
+
// src/schema/models.ts
|
|
646
684
|
var import_zod9 = require("zod");
|
|
647
|
-
var
|
|
648
|
-
|
|
685
|
+
var TranscriptionModelId = import_zod9.z.enum(["tiny", "base", "small", "large-v3-turbo"]);
|
|
686
|
+
var MlRuntimeState = import_zod9.z.enum(["not_installed", "installing", "installed", "failed"]);
|
|
687
|
+
var MlInstallPhase = import_zod9.z.enum(["packages", "model", "verifying"]);
|
|
688
|
+
var MlInstallProgress = import_zod9.z.strictObject({
|
|
689
|
+
phase: MlInstallPhase,
|
|
690
|
+
/** What is being fetched now: a package or a model file name. */
|
|
691
|
+
item: import_zod9.z.string().max(200),
|
|
692
|
+
bytes_done: nonNegInt,
|
|
693
|
+
/** Null while the total is not known yet. */
|
|
694
|
+
bytes_total: nonNegInt.nullable()
|
|
695
|
+
});
|
|
696
|
+
var TranscriptionModelInfo = import_zod9.z.strictObject({
|
|
697
|
+
id: TranscriptionModelId,
|
|
698
|
+
/** Download size of the weights. */
|
|
699
|
+
size_bytes: nonNegInt,
|
|
700
|
+
multilingual: import_zod9.z.boolean(),
|
|
701
|
+
installed: import_zod9.z.boolean()
|
|
702
|
+
});
|
|
703
|
+
var TranscriptSegment = import_zod9.z.strictObject({
|
|
704
|
+
start_s: import_zod9.z.number().min(0),
|
|
705
|
+
end_s: import_zod9.z.number().min(0),
|
|
706
|
+
text: import_zod9.z.string().max(4e3)
|
|
707
|
+
});
|
|
708
|
+
var MlStatusParams = import_zod9.z.strictObject({});
|
|
709
|
+
var MlStatusResult = import_zod9.z.strictObject({
|
|
710
|
+
runtime: MlRuntimeState,
|
|
711
|
+
/** Non-null while runtime or model files are being installed. */
|
|
712
|
+
install: MlInstallProgress.nullable(),
|
|
713
|
+
/** The last install failure, one human line; null when the last install succeeded or none ran. */
|
|
714
|
+
error: import_zod9.z.string().max(300).nullable(),
|
|
715
|
+
/** Where inference runs once the runtime is installed: `cuda` only when a usable GPU is present. */
|
|
716
|
+
accelerator: import_zod9.z.enum(["cpu", "cuda"]),
|
|
717
|
+
models: import_zod9.z.array(TranscriptionModelInfo).max(16),
|
|
718
|
+
default_model: TranscriptionModelId
|
|
719
|
+
});
|
|
720
|
+
var ModelsInstallParams = import_zod9.z.strictObject({ model: TranscriptionModelId.optional() });
|
|
721
|
+
var ModelsInstallResult = import_zod9.z.strictObject({ model: TranscriptionModelId, elapsed_ms: nonNegInt });
|
|
722
|
+
var ModelsTranscribeParams = import_zod9.z.strictObject({
|
|
723
|
+
path: AbsPath,
|
|
724
|
+
model: TranscriptionModelId.optional(),
|
|
725
|
+
language: import_zod9.z.string().regex(/^[a-z]{2,3}$/).optional(),
|
|
726
|
+
task: import_zod9.z.enum(["transcribe", "translate"]).default("transcribe")
|
|
727
|
+
});
|
|
728
|
+
var ModelsTranscribeResult = import_zod9.z.strictObject({
|
|
729
|
+
text: import_zod9.z.string().max(4e5),
|
|
730
|
+
language: import_zod9.z.string().max(8),
|
|
731
|
+
language_probability: import_zod9.z.number().min(0).max(1),
|
|
732
|
+
duration_s: import_zod9.z.number().min(0),
|
|
733
|
+
model: TranscriptionModelId,
|
|
734
|
+
segments: import_zod9.z.array(TranscriptSegment).max(2e4),
|
|
735
|
+
elapsed_ms: nonNegInt
|
|
736
|
+
});
|
|
737
|
+
var MlProgress = import_zod9.z.strictObject({
|
|
738
|
+
kind: import_zod9.z.enum(["install", "transcribe"]),
|
|
739
|
+
/** 0–1 when known (transcribe: audio covered so far; install: bytes so far). */
|
|
740
|
+
fraction: import_zod9.z.number().min(0).max(1).nullable(),
|
|
741
|
+
install: MlInstallProgress.nullable(),
|
|
742
|
+
/** The segment just transcribed (transcribe only): text appears as it is recognised. */
|
|
743
|
+
segment: TranscriptSegment.nullable()
|
|
744
|
+
});
|
|
745
|
+
var MlServiceState = import_zod9.z.enum(["starting", "ready", "restarting", "failed", "stopped"]);
|
|
746
|
+
var MlServiceStatus = import_zod9.z.strictObject({
|
|
747
|
+
state: MlServiceState,
|
|
748
|
+
pid: nonNegInt.nullable(),
|
|
749
|
+
/** Restarts since the app started (crashes, missed health checks). */
|
|
750
|
+
restarts: nonNegInt,
|
|
751
|
+
/** The bundled interpreter's version once the service has answered, e.g. "3.13.15". */
|
|
752
|
+
python: import_zod9.z.string().max(32).nullable(),
|
|
753
|
+
/** Why the service is restarting or failed, one human line. */
|
|
754
|
+
error: import_zod9.z.string().max(300).nullable()
|
|
755
|
+
});
|
|
756
|
+
var ModelsStatusParams = import_zod9.z.strictObject({});
|
|
757
|
+
var ModelsStatusResult = import_zod9.z.strictObject({
|
|
758
|
+
service: MlServiceStatus,
|
|
759
|
+
/** Null until the service has answered (starting, restarting, failed). */
|
|
760
|
+
transcription: MlStatusResult.nullable()
|
|
761
|
+
});
|
|
762
|
+
var ModelsProgressEvent = import_zod9.z.strictObject({ request_id: RequestId, progress: MlProgress });
|
|
763
|
+
|
|
764
|
+
// src/schema/relay.ts
|
|
765
|
+
var import_zod10 = require("zod");
|
|
766
|
+
var RelayDeviceStatusEvent = import_zod10.z.strictObject({
|
|
767
|
+
online: import_zod10.z.boolean(),
|
|
649
768
|
/**
|
|
650
769
|
* When the device last went online/offline; null = it has never connected (since 1.1 —
|
|
651
770
|
* NULL_SINCE: a 1.0 reader is sent 0, its old meaning).
|
|
652
771
|
*/
|
|
653
772
|
since_ms: epochMs.nullable(),
|
|
654
|
-
app_version:
|
|
773
|
+
app_version: import_zod10.z.string().max(32).nullable(),
|
|
655
774
|
protocol_version: ProtocolVersion.nullable()
|
|
656
775
|
});
|
|
657
|
-
var RelayAuthExpiringEvent =
|
|
658
|
-
var RelayCallRequest =
|
|
659
|
-
cap:
|
|
660
|
-
op:
|
|
661
|
-
params:
|
|
662
|
-
timeout_ms:
|
|
776
|
+
var RelayAuthExpiringEvent = import_zod10.z.strictObject({ expires_at_ms: epochMs });
|
|
777
|
+
var RelayCallRequest = import_zod10.z.strictObject({
|
|
778
|
+
cap: import_zod10.z.string().min(1).max(32),
|
|
779
|
+
op: import_zod10.z.string().min(1).max(64),
|
|
780
|
+
params: import_zod10.z.unknown(),
|
|
781
|
+
timeout_ms: import_zod10.z.int().min(100).max(37e5).default(7e4)
|
|
663
782
|
});
|
|
664
|
-
var RelayCallResponse =
|
|
665
|
-
|
|
666
|
-
ok:
|
|
667
|
-
result:
|
|
783
|
+
var RelayCallResponse = import_zod10.z.discriminatedUnion("ok", [
|
|
784
|
+
import_zod10.z.strictObject({
|
|
785
|
+
ok: import_zod10.z.literal(true),
|
|
786
|
+
result: import_zod10.z.unknown(),
|
|
668
787
|
/** Since 1.1: the version the device negotiated for this call (its welcome). */
|
|
669
788
|
protocol_version: ProtocolVersion.optional()
|
|
670
789
|
}),
|
|
671
|
-
|
|
672
|
-
ok:
|
|
790
|
+
import_zod10.z.strictObject({
|
|
791
|
+
ok: import_zod10.z.literal(false),
|
|
673
792
|
error: ProtocolErrorBody,
|
|
674
793
|
/** Since 1.1, as above (absent when the device never answered hello). */
|
|
675
794
|
protocol_version: ProtocolVersion.optional()
|
|
@@ -677,19 +796,111 @@ var RelayCallResponse = import_zod9.z.discriminatedUnion("ok", [
|
|
|
677
796
|
]);
|
|
678
797
|
var RELAY_CALL_HELLO_ID = "hello";
|
|
679
798
|
var RELAY_CALL_REQUEST_ID = "call";
|
|
680
|
-
var RelayCallHello =
|
|
681
|
-
type:
|
|
682
|
-
id:
|
|
799
|
+
var RelayCallHello = import_zod10.z.strictObject({
|
|
800
|
+
type: import_zod10.z.literal("hello"),
|
|
801
|
+
id: import_zod10.z.literal(RELAY_CALL_HELLO_ID),
|
|
683
802
|
protocolVersion: ProtocolVersion,
|
|
684
|
-
clientType:
|
|
685
|
-
clientVersion:
|
|
803
|
+
clientType: import_zod10.z.literal("server"),
|
|
804
|
+
clientVersion: import_zod10.z.string().min(1).max(32)
|
|
805
|
+
});
|
|
806
|
+
var RelayCallEnvelope = import_zod10.z.strictObject({
|
|
807
|
+
id: import_zod10.z.literal(RELAY_CALL_REQUEST_ID),
|
|
808
|
+
type: import_zod10.z.literal("request"),
|
|
809
|
+
cap: import_zod10.z.string().min(1).max(32),
|
|
810
|
+
op: import_zod10.z.string().min(1).max(64),
|
|
811
|
+
params: import_zod10.z.unknown()
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
// src/schema/apps.ts
|
|
815
|
+
var import_zod11 = require("zod");
|
|
816
|
+
var AppKey = import_zod11.z.string().regex(/^[a-z][a-z0-9_-]{0,39}$/);
|
|
817
|
+
var AppProfileId = import_zod11.z.string().regex(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/);
|
|
818
|
+
var AppProfileLocator = import_zod11.z.string().min(1).max(512).regex(/^[A-Za-z0-9 _()+@,-][A-Za-z0-9 ._()+@,-]{0,127}(\/[A-Za-z0-9 _()+@,-][A-Za-z0-9 ._()+@,-]{0,127}){0,7}$/);
|
|
819
|
+
var AppSupport = import_zod11.z.enum(["supported", "partial", "unsupported"]);
|
|
820
|
+
var AppFeatureStatus = import_zod11.z.strictObject({
|
|
821
|
+
status: AppSupport,
|
|
822
|
+
/** One line, set whenever status is not "supported". */
|
|
823
|
+
reason: import_zod11.z.string().max(140).nullable()
|
|
824
|
+
});
|
|
825
|
+
var AppInfo = import_zod11.z.strictObject({
|
|
826
|
+
app_key: AppKey,
|
|
827
|
+
name: import_zod11.z.string().max(64),
|
|
828
|
+
installed: import_zod11.z.boolean(),
|
|
829
|
+
/** The app's own version string, or null when it is not installed or does not say. */
|
|
830
|
+
version: import_zod11.z.string().max(64).nullable(),
|
|
831
|
+
/** The install found (an .app bundle, an .exe, a binary), or null. */
|
|
832
|
+
location: AbsPath.nullable(),
|
|
833
|
+
/** Open the app at all on this OS. */
|
|
834
|
+
open: AppFeatureStatus,
|
|
835
|
+
/** Separate profiles (one account each) on this OS. */
|
|
836
|
+
profiles: AppFeatureStatus,
|
|
837
|
+
/** Plan usage read from the app's own files on this OS. */
|
|
838
|
+
usage: AppFeatureStatus
|
|
839
|
+
});
|
|
840
|
+
var AppUsageReading = import_zod11.z.strictObject({
|
|
841
|
+
window: import_zod11.z.enum(["five_hour", "weekly", "extra"]),
|
|
842
|
+
/** 0–100 for five_hour and weekly; extra-usage spend as a percent of its cap (may pass 100). */
|
|
843
|
+
percent: import_zod11.z.number().min(0).max(1e4),
|
|
844
|
+
/** When the app recorded it. */
|
|
845
|
+
sampled_at_ms: epochMs,
|
|
846
|
+
/** True when the window has rolled over since the sample, so the figure has reset. */
|
|
847
|
+
reset_since: import_zod11.z.boolean()
|
|
848
|
+
});
|
|
849
|
+
var AppProfile = import_zod11.z.strictObject({
|
|
850
|
+
/** The row's id; null for a folder found on disk that no row names yet. */
|
|
851
|
+
id: AppProfileId.nullable(),
|
|
852
|
+
app_key: AppKey,
|
|
853
|
+
display_name: import_zod11.z.string().min(1).max(120),
|
|
854
|
+
/** The account the person means to be signed in here; null when not stated (never guessed). */
|
|
855
|
+
account_identity: import_zod11.z.string().min(3).max(320).nullable(),
|
|
856
|
+
profile_locator: AppProfileLocator.nullable(),
|
|
857
|
+
/** The device the row is tied to; null when the row names none. */
|
|
858
|
+
device_id: DeviceId.nullable(),
|
|
859
|
+
/** The folder this profile resolves to on this device; null for a row tied to another device. */
|
|
860
|
+
folder: AbsPath.nullable(),
|
|
861
|
+
/** The folder exists on this device. */
|
|
862
|
+
on_disk: import_zod11.z.boolean(),
|
|
863
|
+
/** An instance with this profile is running; null when it could not be determined. */
|
|
864
|
+
running: import_zod11.z.boolean().nullable(),
|
|
865
|
+
/** Usage the app recorded for this profile; null when the app exposes none here. */
|
|
866
|
+
usage: import_zod11.z.array(AppUsageReading).nullable(),
|
|
867
|
+
last_opened_ms: epochMs.nullable()
|
|
686
868
|
});
|
|
687
|
-
var
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
869
|
+
var AppsListParams = import_zod11.z.strictObject({});
|
|
870
|
+
var AppsListResult = import_zod11.z.strictObject({ apps: import_zod11.z.array(AppInfo) });
|
|
871
|
+
var AppsProfilesListParams = import_zod11.z.strictObject({
|
|
872
|
+
/** Only this app's profiles; every app's when absent. */
|
|
873
|
+
app_key: AppKey.optional()
|
|
874
|
+
});
|
|
875
|
+
var AppsProfilesListResult = import_zod11.z.strictObject({
|
|
876
|
+
profiles: import_zod11.z.array(AppProfile),
|
|
877
|
+
/**
|
|
878
|
+
* Whether the saved rows could be read: false while signed out or offline, when only folders
|
|
879
|
+
* found on disk are listed.
|
|
880
|
+
*/
|
|
881
|
+
registry: import_zod11.z.enum(["ok", "signed_out", "unavailable"])
|
|
882
|
+
});
|
|
883
|
+
var AppsProfilesSaveParams = import_zod11.z.strictObject({
|
|
884
|
+
id: AppProfileId.optional(),
|
|
885
|
+
app_key: AppKey,
|
|
886
|
+
display_name: import_zod11.z.string().min(1).max(120),
|
|
887
|
+
account_identity: import_zod11.z.string().min(3).max(320).nullable(),
|
|
888
|
+
/** On create: the folder to use (one found on disk), or null for the app's default profile. Ignored on update. */
|
|
889
|
+
profile_locator: AppProfileLocator.nullable(),
|
|
890
|
+
/** On create with profile_locator null: make a new folder named after display_name instead. */
|
|
891
|
+
new_folder: import_zod11.z.boolean().default(false)
|
|
892
|
+
});
|
|
893
|
+
var AppsProfilesSaveResult = AppProfile;
|
|
894
|
+
var AppsProfilesRemoveParams = import_zod11.z.strictObject({ id: AppProfileId });
|
|
895
|
+
var AppsProfilesRemoveResult = import_zod11.z.strictObject({ removed: import_zod11.z.boolean() });
|
|
896
|
+
var AppsOpenParams = import_zod11.z.strictObject({
|
|
897
|
+
app_key: AppKey,
|
|
898
|
+
profile_id: AppProfileId.optional()
|
|
899
|
+
});
|
|
900
|
+
var AppsOpenResult = import_zod11.z.strictObject({
|
|
901
|
+
/** True when an instance with this profile was already running; it is brought forward. */
|
|
902
|
+
already_running: import_zod11.z.boolean(),
|
|
903
|
+
folder: AbsPath.nullable()
|
|
693
904
|
});
|
|
694
905
|
|
|
695
906
|
// src/schema/registry.ts
|
|
@@ -718,7 +929,17 @@ var OPS = {
|
|
|
718
929
|
"session.attach": { params: SessionAttachParams, result: SessionAttachResult, kind: "stream", since: "1.0", errors: ["RESOURCE_GONE", "AUTH_FORBIDDEN", "RESOURCE_BUSY"] },
|
|
719
930
|
"session.detach": { params: SessionDetachParams, result: SessionDetachResult, kind: "unary", since: "1.0", errors: ["RESOURCE_GONE"] },
|
|
720
931
|
"session.release": { params: SessionReleaseParams, result: SessionReleaseResult, kind: "unary", since: "1.0", errors: ["RESOURCE_GONE", "AUTH_FORBIDDEN"] },
|
|
721
|
-
"session.list": { params: SessionListParams, result: SessionListResult, kind: "unary", since: "1.0", errors: ["INTERNAL"] }
|
|
932
|
+
"session.list": { params: SessionListParams, result: SessionListResult, kind: "unary", since: "1.0", errors: ["INTERNAL"] },
|
|
933
|
+
// Local models (SPEC §6), served by the `ml` Python service through SERVICES below.
|
|
934
|
+
"models.status": { params: ModelsStatusParams, result: ModelsStatusResult, kind: "unary", since: "1.2", errors: ["INTERNAL"] },
|
|
935
|
+
"models.install": { params: ModelsInstallParams, result: ModelsInstallResult, kind: "unary", since: "1.2", errors: ["RESOURCE_BUSY", "RESOURCE_GONE", "TIMEOUT", "CANCELLED", "INTERNAL"] },
|
|
936
|
+
"models.transcribe": { params: ModelsTranscribeParams, result: ModelsTranscribeResult, kind: "unary", since: "1.2", errors: ["NOT_FOUND", "INVALID_PARAMS", "PERMISSION_DENIED_OS", "RESOURCE_BUSY", "RESOURCE_GONE", "CANCELLED", "PAYLOAD_TOO_LARGE", "INTERNAL"] },
|
|
937
|
+
// Desktop apps and their profiles (SPEC §6 apps); profiles are rows in users.desktop_app_profile.
|
|
938
|
+
"apps.list": { params: AppsListParams, result: AppsListResult, kind: "unary", since: "1.2", errors: ["INTERNAL"] },
|
|
939
|
+
"apps.profiles.list": { params: AppsProfilesListParams, result: AppsProfilesListResult, kind: "unary", since: "1.2", errors: ["NOT_FOUND", "INVALID_PARAMS", "INTERNAL"] },
|
|
940
|
+
"apps.profiles.save": { params: AppsProfilesSaveParams, result: AppsProfilesSaveResult, kind: "unary", since: "1.2", errors: ["AUTH_REQUIRED", "AUTH_FORBIDDEN", "NOT_FOUND", "INVALID_PARAMS", "CONFLICT", "PLATFORM_UNSUPPORTED", "DEVICE_OFFLINE", "INTERNAL"] },
|
|
941
|
+
"apps.profiles.remove": { params: AppsProfilesRemoveParams, result: AppsProfilesRemoveResult, kind: "unary", since: "1.2", errors: ["AUTH_REQUIRED", "AUTH_FORBIDDEN", "NOT_FOUND", "DEVICE_OFFLINE", "INTERNAL"] },
|
|
942
|
+
"apps.open": { params: AppsOpenParams, result: AppsOpenResult, kind: "unary", since: "1.2", errors: ["NOT_FOUND", "PLATFORM_UNSUPPORTED", "AUTH_REQUIRED", "DEVICE_OFFLINE", "INTERNAL"] }
|
|
722
943
|
};
|
|
723
944
|
var EVENTS = {
|
|
724
945
|
"fs.changed": FsChangedEvent,
|
|
@@ -726,19 +947,13 @@ var EVENTS = {
|
|
|
726
947
|
"exec.title": ExecTitleEvent,
|
|
727
948
|
"exec.cwd": ExecCwdEvent,
|
|
728
949
|
"session.control_changed": SessionControlChangedEvent,
|
|
950
|
+
"models.progress": ModelsProgressEvent,
|
|
729
951
|
"relay.device_status": RelayDeviceStatusEvent,
|
|
730
952
|
"relay.auth_expiring": RelayAuthExpiringEvent
|
|
731
953
|
};
|
|
732
954
|
|
|
733
955
|
// src/client/client.ts
|
|
734
956
|
var WS_OPEN = 1;
|
|
735
|
-
var TERMINAL_CLOSE = /* @__PURE__ */ new Map([
|
|
736
|
-
[CloseCode.DEVICE_REVOKED, ["AUTH_DEVICE_REVOKED", "This device was revoked"]],
|
|
737
|
-
[CloseCode.AUTH_FORBIDDEN, ["AUTH_FORBIDDEN", "Not this device's owner"]],
|
|
738
|
-
[CloseCode.DEVICE_NOT_FOUND, ["NOT_FOUND", "Device not found"]],
|
|
739
|
-
[CloseCode.REPLACED, ["CONFLICT", "Replaced by a newer connection"]],
|
|
740
|
-
[CloseCode.UPGRADE_REQUIRED, ["PROTOCOL_VERSION_UNSUPPORTED", "Protocol upgrade required"]]
|
|
741
|
-
]);
|
|
742
957
|
var defaultTimers = {
|
|
743
958
|
setTimeout: (fn, ms) => globalThis.setTimeout(fn, ms),
|
|
744
959
|
clearTimeout: (handle) => globalThis.clearTimeout(handle),
|
|
@@ -781,8 +996,10 @@ function createDesktopClient(options) {
|
|
|
781
996
|
attempts: 0,
|
|
782
997
|
retryInMs: null,
|
|
783
998
|
lastError: null,
|
|
784
|
-
lastCloseCode: null
|
|
999
|
+
lastCloseCode: null,
|
|
1000
|
+
outcome: null
|
|
785
1001
|
};
|
|
1002
|
+
let authRefusals = 0;
|
|
786
1003
|
const listeners = /* @__PURE__ */ new Set();
|
|
787
1004
|
const readyWaiters = /* @__PURE__ */ new Set();
|
|
788
1005
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -817,6 +1034,7 @@ function createDesktopClient(options) {
|
|
|
817
1034
|
}
|
|
818
1035
|
function connect() {
|
|
819
1036
|
if (state.status === "connecting" || state.status === "handshaking" || state.status === "open") return;
|
|
1037
|
+
if (state.status === "closed" || state.status === "idle") authRefusals = 0;
|
|
820
1038
|
closedByUser = false;
|
|
821
1039
|
if (reconnectTimer !== null) {
|
|
822
1040
|
timers.clearTimeout(reconnectTimer);
|
|
@@ -898,8 +1116,11 @@ function createDesktopClient(options) {
|
|
|
898
1116
|
timers.clearTimeout(probeTimer);
|
|
899
1117
|
probeTimer = null;
|
|
900
1118
|
}
|
|
901
|
-
const
|
|
902
|
-
|
|
1119
|
+
const rule = code === null ? null : closeRule(code);
|
|
1120
|
+
if (rule?.action === "reauth") authRefusals++;
|
|
1121
|
+
const stopped = rule !== null && (rule.action === "stop" || rule.action === "reauth" && authRefusals >= 2);
|
|
1122
|
+
const terminal = stopped && rule ? rule : void 0;
|
|
1123
|
+
const lost = error ?? (terminal ? new DesktopProtocolError(terminal.error[0], terminal.error[1]) : rule && rule.action === "reauth" ? new DesktopProtocolError(rule.error[0], rule.error[1], { retryable: true }) : new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
|
|
903
1124
|
for (const [id, p] of pending) {
|
|
904
1125
|
pending.delete(id);
|
|
905
1126
|
p.cleanup();
|
|
@@ -916,7 +1137,14 @@ function createDesktopClient(options) {
|
|
|
916
1137
|
endStream(s, null, lost);
|
|
917
1138
|
}
|
|
918
1139
|
if (closedByUser || terminal) {
|
|
919
|
-
setState({
|
|
1140
|
+
setState({
|
|
1141
|
+
status: "closed",
|
|
1142
|
+
welcome: null,
|
|
1143
|
+
lastError: closedByUser ? state.lastError : lost,
|
|
1144
|
+
lastCloseCode: code,
|
|
1145
|
+
retryInMs: null,
|
|
1146
|
+
outcome: closedByUser ? null : terminal?.outcome ?? null
|
|
1147
|
+
});
|
|
920
1148
|
rejectReadyWaiters(lost);
|
|
921
1149
|
return;
|
|
922
1150
|
}
|
|
@@ -1070,7 +1298,8 @@ function createDesktopClient(options) {
|
|
|
1070
1298
|
}
|
|
1071
1299
|
helloId = null;
|
|
1072
1300
|
backoff.markConnected();
|
|
1073
|
-
|
|
1301
|
+
authRefusals = 0;
|
|
1302
|
+
setState({ status: "open", welcome: welcome.data, attempts: 0, retryInMs: null, lastError: null, outcome: null });
|
|
1074
1303
|
for (const w of [...readyWaiters]) {
|
|
1075
1304
|
readyWaiters.delete(w);
|
|
1076
1305
|
w.resolve(welcome.data);
|
|
@@ -1232,6 +1461,7 @@ function createDesktopClient(options) {
|
|
|
1232
1461
|
}
|
|
1233
1462
|
await ready(opts);
|
|
1234
1463
|
const id = newId("r");
|
|
1464
|
+
if (opts.onRequestId) safeCall("onRequestId", () => opts.onRequestId?.(id));
|
|
1235
1465
|
return new Promise((resolve, reject) => {
|
|
1236
1466
|
const timeoutMs = opts.timeoutMs ?? requestTimeoutMs;
|
|
1237
1467
|
const timer = timers.setTimeout(() => {
|