@uxnan/shared 0.0.7-alpha.20260719 → 0.0.9-alpha.20260721

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -52,7 +52,7 @@ flowchart TB
52
52
  | JSON-RPC | envelope types + constructors (`makeRequest`, `makeNotification`, `makeResponse`, `makeErrorResponse`), error codes (`JsonRpcErrorCode` + Uxnan-specific `-32000..-32008`), `RpcError`, typed method registry (`JsonRpcMethodRegistry` + `METHOD_NAMES`), `isKnownMethod` |
53
53
  | Streaming | `StreamNotification` enum + param types (`TurnStartedParams`, `MessageDeltaParams`, `ThinkingDeltaParams`, `ContentBlockParams`, `TurnCompletedParams`, `TurnUsage`, `TurnErrorParams`, `TurnAbortedParams`, `ModelResolvedParams`) |
54
54
  | E2EE | handshake messages (`clientHello` / `serverHello` / `clientAuth` / `ready`), `buildHandshakeTranscript`, `SecureEnvelope`, `PairingPayload` v2 (`relay` optional + `hosts: string[]`) with `Base64(utf8(JSON))` QR encoding |
55
- | Models | thread / turn / message (with `MessageContent` polymorphic blocks), git, workspace (incl. `browseDirs` + `exists`), project, auth, session/trust (`BridgeStatus` incl. `latestVersion?`/`updateAvailable?`), approval, question (interactive multiple-choice) |
55
+ | Models | thread / turn / message (with `MessageContent` polymorphic blocks), git, workspace (incl. `browseDirs` + `exists`), project, auth, session/trust (`BridgeStatus` incl. `latestVersion?`/`updateAvailable?`), bridge-owned profile metrics (complete-ledger export/import), approval, question (interactive multiple-choice) |
56
56
  | Agents | `IAgentAdapter` (with `respondApproval`, `listModels`, `nativeSessionId`, `SendTurnOptions { threadId, turnId, text, service?, effort?, options?, attachments?, cwd?, accessMode? }`), `AgentModel` (incl. `version?`, `isDefault?`, `options?`, `contextWindow?`, `isLatestAlias?`), `AgentCapabilities` (incl. `images`, `approvals`, `reportsContextUsage`), `AgentConfig` (cwd, agentId, model, plus optional `binaryPath`/`extraArgs`) |
57
57
  | Version | `compareVersions` / `isNewerVersion` — dependency-free SemVer precedence (used by the bridge's npm update check) |
58
58
  | Validation | Ajv validators for requests, responses, envelopes, pairing payload, push payloads |
@@ -6,8 +6,29 @@
6
6
  *
7
7
  * Source: architecture/02a-system-architecture.md §5.9.1.
8
8
  */
9
- /** Secure transport protocol version negotiated in the handshake. */
10
- export declare const SECURE_PROTOCOL_VERSION = 1;
9
+ /**
10
+ * Secure transport protocol version negotiated in the handshake. Both sides
11
+ * reject a mismatch during `clientHello`/`serverHello`, so an incompatible pair
12
+ * fails fast and legibly instead of completing the handshake and then dropping
13
+ * every encrypted frame.
14
+ *
15
+ * **Bump this whenever the encrypted-frame format changes** (envelope shape,
16
+ * AAD layout, key derivation) — not just when the handshake JSON changes. The
17
+ * handshake is the only place the two sides can still talk, so it is the only
18
+ * place a version gap can be reported.
19
+ *
20
+ * - `1` — initial: AES-256-GCM over the envelope's ciphertext only.
21
+ * - `2` — `sessionId`/`seq`/direction bound as GCM AAD (`buildEnvelopeAad`).
22
+ */
23
+ export declare const SECURE_PROTOCOL_VERSION = 2;
24
+ /**
25
+ * AAD direction bytes for {@link SECURE_PROTOCOL_VERSION} ≥ 2. The session key
26
+ * is shared by both directions, so the direction is bound into the AAD to stop
27
+ * a frame being reflected back at its own sender as apparently-inbound traffic.
28
+ * Mirrored in the mobile `ProtocolConstants`.
29
+ */
30
+ export declare const ENVELOPE_DIRECTION_PHONE_TO_BRIDGE = 1;
31
+ export declare const ENVELOPE_DIRECTION_BRIDGE_TO_PHONE = 2;
11
32
  /** Version stamped into the pairing QR payload. */
12
33
  export declare const PAIRING_QR_VERSION = 2;
13
34
  /** HKDF `info` tag used when deriving the session key. */
@@ -6,8 +6,29 @@
6
6
  *
7
7
  * Source: architecture/02a-system-architecture.md §5.9.1.
8
8
  */
9
- /** Secure transport protocol version negotiated in the handshake. */
10
- export const SECURE_PROTOCOL_VERSION = 1;
9
+ /**
10
+ * Secure transport protocol version negotiated in the handshake. Both sides
11
+ * reject a mismatch during `clientHello`/`serverHello`, so an incompatible pair
12
+ * fails fast and legibly instead of completing the handshake and then dropping
13
+ * every encrypted frame.
14
+ *
15
+ * **Bump this whenever the encrypted-frame format changes** (envelope shape,
16
+ * AAD layout, key derivation) — not just when the handshake JSON changes. The
17
+ * handshake is the only place the two sides can still talk, so it is the only
18
+ * place a version gap can be reported.
19
+ *
20
+ * - `1` — initial: AES-256-GCM over the envelope's ciphertext only.
21
+ * - `2` — `sessionId`/`seq`/direction bound as GCM AAD (`buildEnvelopeAad`).
22
+ */
23
+ export const SECURE_PROTOCOL_VERSION = 2;
24
+ /**
25
+ * AAD direction bytes for {@link SECURE_PROTOCOL_VERSION} ≥ 2. The session key
26
+ * is shared by both directions, so the direction is bound into the AAD to stop
27
+ * a frame being reflected back at its own sender as apparently-inbound traffic.
28
+ * Mirrored in the mobile `ProtocolConstants`.
29
+ */
30
+ export const ENVELOPE_DIRECTION_PHONE_TO_BRIDGE = 0x01;
31
+ export const ENVELOPE_DIRECTION_BRIDGE_TO_PHONE = 0x02;
11
32
  /** Version stamped into the pairing QR payload. */
12
33
  export const PAIRING_QR_VERSION = 2;
13
34
  /** HKDF `info` tag used when deriving the session key. */
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AAEzC,mDAAmD;AACnD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC,0DAA0D;AAC1D,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAE7C,0EAA0E;AAC1E,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAE1C,mEAAmE;AACnE,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAE9C,gEAAgE;AAChE,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAEhD,uEAAuE;AACvE,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAEhD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,yBAAyB,GAAG,UAAU,CAAC;AAEpD,8BAA8B;AAC9B,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAExD,yDAAyD;AACzD,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAEtC,wCAAwC;AACxC,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;AACvD,MAAM,CAAC,MAAM,kCAAkC,GAAG,IAAI,CAAC;AAEvD,mDAAmD;AACnD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC,0DAA0D;AAC1D,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAE7C,0EAA0E;AAC1E,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAE1C,mEAAmE;AACnE,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAE9C,gEAAgE;AAChE,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAEhD,uEAAuE;AACvE,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAEhD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,yBAAyB,GAAG,UAAU,CAAC;AAEpD,8BAA8B;AAC9B,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAExD,yDAAyD;AACzD,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAEtC,wCAAwC;AACxC,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC"}
@@ -5,8 +5,24 @@
5
5
  */
6
6
  export interface SecureEnvelope {
7
7
  kind: 'encryptedEnvelope';
8
+ /**
9
+ * Session this envelope belongs to. Travels in the clear (the receiver needs
10
+ * it before it can look up a key) and is therefore bound as GCM AAD — see
11
+ * {@link seq}.
12
+ */
8
13
  sessionId: string;
9
- /** Monotonic sequence number (replay protection). */
14
+ /**
15
+ * Monotonic sequence number (replay protection). Like `sessionId` it is
16
+ * visible on the wire, so from `SECURE_PROTOCOL_VERSION` 2 both are bound —
17
+ * together with a direction byte — as the AES-GCM **AAD**:
18
+ *
19
+ * AAD = utf8(sessionId) || 0x00 || u64_be(seq) || 0x00 || direction
20
+ *
21
+ * so tampering with either field fails the tag instead of silently passing an
22
+ * unauthenticated `seq <= lastApplied` check. Both implementations must build
23
+ * this byte-for-byte identically (`buildEnvelopeAad` on each side); see
24
+ * architecture/02a §5.9.1.
25
+ */
10
26
  seq: number;
11
27
  /** Per-message random nonce (hex, 12 bytes). */
12
28
  nonce: string;
@@ -6,10 +6,10 @@
6
6
  * agents/models used, connection time, git actions, an activity heatmap). Those
7
7
  * were derived on the phone from local storage and so were lost on an app
8
8
  * uninstall (the app has no cloud login). To make them durable, the **bridge**
9
- * becomes the source of truth: it computes them from its own authoritative data
10
- * (the conversation store) plus persisted session + git-action event logs it
11
- * observes itself, and serves them over `metrics/get`. The phone renders one
12
- * snapshot per PC and sums across PCs.
9
+ * becomes the source of truth: it persists a complete activity ledger
10
+ * (conversations, turns/messages, reported tokens, sessions and Git actions)
11
+ * and serves it over `metrics/get`. Deleting mutable conversation history does
12
+ * not subtract activity. The phone renders one snapshot per PC and sums PCs.
13
13
  *
14
14
  * A `metrics/export` produces an opaque, tamper-proof file only the SAME bridge
15
15
  * can later verify + decrypt (AES-256-GCM under a secret held in the PC's OS
@@ -76,10 +76,9 @@ export interface MetricsActivityDay {
76
76
  }
77
77
  /**
78
78
  * Aggregated metrics for ONE PC (the bridge that returns it), built from the
79
- * bridge's own authoritative data: the conversation store (threads/turns) plus
80
- * its persisted session + git-action event logs. The phone renders this per PC
81
- * and sums across PCs for the all-PCs profile. Every field is a count the phone
82
- * cannot inflate — the bridge observes it. Source of `metrics/get`.
79
+ * bridge's durable activity ledger. The phone renders this per PC and sums
80
+ * across PCs for the all-PCs profile. Every field is a count the phone cannot
81
+ * inflate the bridge observes and retains it. Source of `metrics/get`.
83
82
  */
84
83
  export interface MetricsSnapshot {
85
84
  /** Schema version, for forward-compatible parsing. */
@@ -123,7 +122,7 @@ export interface MetricsSnapshot {
123
122
  updatedAt: number;
124
123
  }
125
124
  /**
126
- * `metrics/export` request. The bridge seals its metrics event log into an
125
+ * `metrics/export` request. The bridge seals its complete metrics ledger into an
127
126
  * opaque, tamper-proof blob that only THIS same bridge can later verify +
128
127
  * decrypt (AES-256-GCM under a secret held in the OS keychain). An optional user
129
128
  * [passphrase] adds a second confidentiality layer (scrypt-derived), so a leaked
@@ -145,8 +144,8 @@ export interface MetricsExportResult {
145
144
  * `metrics/import` request. The phone sends back a previously exported [blob].
146
145
  * The bridge verifies it was sealed by THIS PC — a foreign or edited file is
147
146
  * rejected — decrypts it (using [passphrase] when the file was passphrase-locked)
148
- * and merges its events by id (idempotent: re-importing the same file changes
149
- * nothing). Returns the refreshed snapshot.
147
+ * and merges its ledger rows by id (idempotent: re-importing the same file
148
+ * changes nothing). Returns the refreshed snapshot.
150
149
  */
151
150
  export interface MetricsImportParams {
152
151
  /** The sealed blob produced by a prior `metrics/export` on this PC. */
@@ -155,7 +154,7 @@ export interface MetricsImportParams {
155
154
  passphrase?: string;
156
155
  }
157
156
  export interface MetricsImportResult {
158
- /** How many new session + git-action events were merged in. */
157
+ /** How many ledger rows were inserted or advanced. */
159
158
  imported: number;
160
159
  /** The refreshed snapshot after the merge. */
161
160
  snapshot: MetricsSnapshot;
@@ -6,10 +6,10 @@
6
6
  * agents/models used, connection time, git actions, an activity heatmap). Those
7
7
  * were derived on the phone from local storage and so were lost on an app
8
8
  * uninstall (the app has no cloud login). To make them durable, the **bridge**
9
- * becomes the source of truth: it computes them from its own authoritative data
10
- * (the conversation store) plus persisted session + git-action event logs it
11
- * observes itself, and serves them over `metrics/get`. The phone renders one
12
- * snapshot per PC and sums across PCs.
9
+ * becomes the source of truth: it persists a complete activity ledger
10
+ * (conversations, turns/messages, reported tokens, sessions and Git actions)
11
+ * and serves it over `metrics/get`. Deleting mutable conversation history does
12
+ * not subtract activity. The phone renders one snapshot per PC and sums PCs.
13
13
  *
14
14
  * A `metrics/export` produces an opaque, tamper-proof file only the SAME bridge
15
15
  * can later verify + decrypt (AES-256-GCM under a secret held in the PC's OS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxnan/shared",
3
- "version": "0.0.7-alpha.20260719",
3
+ "version": "0.0.9-alpha.20260721",
4
4
  "description": "Shared JSON-RPC and E2EE contracts for the Uxnan ecosystem (bridge, relay, mobile).",
5
5
  "license": "MPL-2.0",
6
6
  "author": "Luis Donaldo Gamas Vazquez",