@uxnan/shared 0.0.4-alpha.20260703 → 0.0.6-alpha.20260716

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
@@ -13,7 +13,7 @@ equivalents (see
13
13
  [`architecture/02b-contracts-and-requirements.md`](../architecture/02b-contracts-and-requirements.md)
14
14
  §1 for the canonical contract list).
15
15
 
16
- > **Status:** implemented and stable — **61 JSON-RPC methods** + **8 streaming
16
+ > **Status:** implemented and stable — **66 JSON-RPC methods** + **8 streaming
17
17
  > notifications**, kept lock-step at build time with the `METHOD_NAMES` array and
18
18
  > the `StreamNotification` enum (a compile-time assertion in
19
19
  > `src/jsonrpc/method-registry.ts` fails the build on any drift). Changes are
@@ -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 |
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) |
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 |
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Source: architecture/02a-system-architecture.md §5.8.2 (adapters/base-adapter).
5
5
  */
6
- import type { AgentCapabilities, AgentId, AgentModel } from './agent-capabilities.js';
6
+ import type { AgentCapabilities, AgentCommand, AgentCommandInvocation, AgentId, AgentModel } from './agent-capabilities.js';
7
7
  import type { AgentConfig } from './agent-config.js';
8
8
  import type { TurnAttachment } from '../models/workspace.js';
9
9
  import type { ApprovalDecision } from '../models/approval.js';
@@ -52,6 +52,15 @@ export interface SendTurnOptions {
52
52
  * configured default posture (no behaviour change).
53
53
  */
54
54
  accessMode?: AccessMode;
55
+ /**
56
+ * Invoke an advertised agent command (from `agent/commands`) instead of
57
+ * free-form {@link text}. The {@link AgentManager} resolves it before the
58
+ * adapter runs — expanding a custom prompt-template via {@link
59
+ * IAgentAdapter.expandCommand}, or composing the CLI's native `/name args`
60
+ * form — and sets {@link text} to the result, so most adapters need no
61
+ * per-command handling. Absent for an ordinary text turn.
62
+ */
63
+ command?: AgentCommandInvocation;
55
64
  }
56
65
  export interface IAgentAdapter {
57
66
  readonly agentId: AgentId;
@@ -75,4 +84,23 @@ export interface IAgentAdapter {
75
84
  onEvent(listener: (event: AgentStreamEvent) => void): () => void;
76
85
  /** List the models this agent's CLI reports as available (optional). */
77
86
  listModels?(): Promise<AgentModel[]>;
87
+ /**
88
+ * List the special ("slash") commands this agent exposes — control commands
89
+ * reachable headless plus user-defined prompt-template commands scanned from
90
+ * disk (optional; adapters with none simply don't implement it). `cwd` is the
91
+ * thread/project directory so project-scoped custom commands (`<cwd>/.claude/
92
+ * commands`, `<cwd>/.gemini/commands`, …) are discovered alongside user-level
93
+ * ones. Discovery only; invocation flows through {@link sendTurn} with {@link
94
+ * SendTurnOptions.command}.
95
+ */
96
+ listCommands?(cwd?: string): Promise<AgentCommand[]>;
97
+ /**
98
+ * Resolve a custom prompt-template command to the final prompt text (reads the
99
+ * template file from `cwd`/user config, substitutes arguments). Implemented
100
+ * only by adapters whose commands are prompt templates the bridge expands
101
+ * itself (Codex/Gemini/OpenCode); adapters whose commands run natively (Claude
102
+ * Code, ACP agents) leave it unset and receive the composed `/name args` form
103
+ * as {@link SendTurnOptions.text}. Throw if `name` is not a known custom command.
104
+ */
105
+ expandCommand?(name: string, args?: string, cwd?: string): Promise<string>;
78
106
  }
@@ -3,7 +3,11 @@
3
3
  *
4
4
  * Source: architecture/02a-system-architecture.md §5.8.2 (adapters).
5
5
  */
6
- export type AgentId = 'codex' | 'opencode' | 'claude-code' | 'gemini-cli' | 'pi-agent' | 'aider'
6
+ export type AgentId = 'codex' | 'opencode' | 'claude-code' | 'gemini-cli' | 'pi-agent'
7
+ /** Zero — open-source Go coding agent, driven over the Agent Client Protocol. */
8
+ | 'zero'
9
+ /** Grok — xAI's coding CLI, driven over the Agent Client Protocol (`grok agent stdio`). */
10
+ | 'grok'
7
11
  /** Built-in reference/dev agent that echoes the prompt (no external CLI). */
8
12
  | 'echo';
9
13
  export interface AgentCapabilities {
@@ -24,6 +28,21 @@ export interface AgentCapabilities {
24
28
  * OpenCode) and the meter stays hidden.
25
29
  */
26
30
  reportsContextUsage?: boolean;
31
+ /**
32
+ * Agent runs in autonomous ("YOLO") mode by default — it acts and edits
33
+ * without per-action approval prompts because its headless CLI exposes no
34
+ * pre-tool approval channel. The phone surfaces this so the user knows Pi
35
+ * (and any such agent) will not ask before running tools. Optional; absent/
36
+ * false means the agent either gates tools or is pending approval wiring.
37
+ */
38
+ autonomous?: boolean;
39
+ /**
40
+ * Agent exposes special ("slash") commands the phone can discover via
41
+ * `agent/commands` and invoke through `turn/send` `command`. Optional for
42
+ * back-compat; absent/false means the agent advertises none (the phone shows
43
+ * only its client-side `/` palette). See {@link AgentCommand}.
44
+ */
45
+ commands?: boolean;
27
46
  }
28
47
  /**
29
48
  * A registered agent the phone can pick for a thread, returned by `agent/list`.
@@ -112,3 +131,52 @@ export interface AgentModel {
112
131
  */
113
132
  isLatestAlias?: boolean;
114
133
  }
134
+ /**
135
+ * A special ("slash") command an agent exposes, returned by `agent/commands`.
136
+ *
137
+ * Two kinds are unified under this one shape:
138
+ * - **control** commands the CLI understands in its headless/programmatic mode
139
+ * (Claude Code's `/compact` sent as the prompt with `--resume`; the commands
140
+ * ACP agents advertise via `available_commands_update`), and
141
+ * - **custom** user-defined prompt-template commands (`.claude/commands`,
142
+ * `~/.codex/prompts`, `.gemini/commands`, `.opencode/command`) that the bridge
143
+ * expands itself before running a normal turn.
144
+ *
145
+ * The phone is a generic renderer: it lists the advertised commands in its `/`
146
+ * palette and, when one is picked, echoes it back on `turn/send` under
147
+ * {@link AgentCommandInvocation} — the bridge resolves it to the final prompt
148
+ * text (expanded template) or the CLI's native `/name args` form. Consumers MUST
149
+ * tolerate absent optional fields so a newer bridge advertising a richer command
150
+ * never breaks an older app.
151
+ */
152
+ export interface AgentCommand {
153
+ /** Command name WITHOUT the leading slash (e.g. `compact`, `refactor`). */
154
+ name: string;
155
+ /** One-line description for the palette, when the source provides one. */
156
+ description?: string;
157
+ /** Hint for the arguments the command accepts (e.g. `<file> <priority>`). */
158
+ argumentHint?: string;
159
+ /**
160
+ * Where the command comes from: `acp` (advertised by an ACP agent),
161
+ * `builtin` (a CLI control command reachable headless), or `custom` (a
162
+ * user-defined prompt-template file the bridge expands).
163
+ */
164
+ source: 'acp' | 'builtin' | 'custom';
165
+ /**
166
+ * Whether the command actually runs in the agent's headless/programmatic mode.
167
+ * Absent/true means yes; `false` marks a command that only works in the CLI's
168
+ * interactive TUI, which the phone should hide. The bridge only advertises
169
+ * commands it can run, so this is a belt-and-suspenders gate.
170
+ */
171
+ headlessSupported?: boolean;
172
+ }
173
+ /**
174
+ * A picked {@link AgentCommand} carried on `turn/send` under `command`, instead
175
+ * of free-form `text`. The bridge resolves `{ name, args }` to the final prompt.
176
+ */
177
+ export interface AgentCommandInvocation {
178
+ /** The command's {@link AgentCommand.name} (no leading slash). */
179
+ name: string;
180
+ /** Raw argument string the user appended after the command, if any. */
181
+ args?: string;
182
+ }
@@ -22,5 +22,8 @@ export * from './models/workspace.js';
22
22
  export * from './models/project.js';
23
23
  export * from './models/session.js';
24
24
  export * from './models/approval.js';
25
+ export * from './models/question.js';
26
+ export * from './models/usage.js';
27
+ export * from './models/metrics.js';
25
28
  export * from './validators/validate.js';
26
29
  export * from './validators/json-schema/schemas.js';
package/dist/src/index.js CHANGED
@@ -28,6 +28,9 @@ export * from './models/workspace.js';
28
28
  export * from './models/project.js';
29
29
  export * from './models/session.js';
30
30
  export * from './models/approval.js';
31
+ export * from './models/question.js';
32
+ export * from './models/usage.js';
33
+ export * from './models/metrics.js';
31
34
  // Validators
32
35
  export * from './validators/validate.js';
33
36
  export * from './validators/json-schema/schemas.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,gBAAgB,CAAC;AAE/B,WAAW;AACX,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAE3C,OAAO;AACP,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAE1C,SAAS;AACT,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAE1C,UAAU;AACV,cAAc,sBAAsB,CAAC;AAErC,gBAAgB;AAChB,cAAc,iCAAiC,CAAC;AAEhD,SAAS;AACT,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AAErC,aAAa;AACb,cAAc,0BAA0B,CAAC;AACzC,cAAc,qCAAqC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,gBAAgB,CAAC;AAE/B,WAAW;AACX,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAE3C,OAAO;AACP,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAE1C,SAAS;AACT,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAE1C,UAAU;AACV,cAAc,sBAAsB,CAAC;AAErC,gBAAgB;AAChB,cAAc,iCAAiC,CAAC;AAEhD,SAAS;AACT,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AAEpC,aAAa;AACb,cAAc,0BAA0B,CAAC;AACzC,cAAc,qCAAqC,CAAC"}
@@ -3,5 +3,5 @@
3
3
  * compile-time {@link JsonRpcMethodRegistry} via the assertion below.
4
4
  */
5
5
  import type { JsonRpcMethodName } from './methods.js';
6
- export declare const METHOD_NAMES: readonly ["thread/list", "thread/read", "thread/start", "thread/resume", "thread/fork", "thread/setModel", "thread/rename", "thread/setAccessMode", "thread/archive", "thread/unarchive", "thread/delete", "turn/list", "turn/read", "turn/send", "turn/cancel", "git/status", "git/diff", "git/commit", "git/push", "git/pull", "git/checkout", "git/createBranch", "git/createWorktree", "git/stage", "git/unstage", "git/discard", "git/createPr", "git/undoCommit", "git/branches", "git/switchBranch", "git/revert", "git/deleteBranch", "git/removeWorktree", "git/log", "git/commitShow", "workspace/readFile", "workspace/readImage", "workspace/list", "workspace/searchFiles", "workspace/browseDirs", "workspace/checkpoint", "workspace/diffCheckpoint", "workspace/applyCheckpoint", "workspace/applyPatch", "workspace/exists", "project/list", "project/resolve", "agent/list", "agent/models", "auth/status", "auth/login", "auth/logout", "notifications/register", "notifications/update", "notifications/unregister", "bridge/status", "bridge/generatePairingQr", "bridge/connectedPhones", "bridge/disconnectPhone", "bridge/trustedDevices", "bridge/removeTrustedDevice"];
6
+ export declare const METHOD_NAMES: readonly ["thread/list", "thread/read", "thread/start", "thread/resume", "thread/fork", "thread/setModel", "thread/rename", "thread/setAccessMode", "thread/archive", "thread/unarchive", "thread/delete", "turn/list", "turn/read", "turn/send", "turn/cancel", "git/status", "git/diff", "git/commit", "git/push", "git/pull", "git/checkout", "git/createBranch", "git/createWorktree", "git/stage", "git/unstage", "git/discard", "git/createPr", "git/undoCommit", "git/branches", "git/switchBranch", "git/revert", "git/deleteBranch", "git/removeWorktree", "git/log", "git/commitShow", "workspace/readFile", "workspace/readImage", "workspace/list", "workspace/searchFiles", "workspace/browseDirs", "workspace/checkpoint", "workspace/diffCheckpoint", "workspace/applyCheckpoint", "workspace/applyPatch", "workspace/exists", "project/list", "project/resolve", "agent/list", "agent/models", "agent/commands", "agent/usageStats", "metrics/get", "metrics/export", "metrics/import", "auth/status", "auth/login", "auth/logout", "notifications/register", "notifications/update", "notifications/unregister", "bridge/status", "bridge/generatePairingQr", "bridge/connectedPhones", "bridge/disconnectPhone", "bridge/trustedDevices", "bridge/removeTrustedDevice"];
7
7
  export declare function isKnownMethod(method: string): method is JsonRpcMethodName;
@@ -53,6 +53,12 @@ export const METHOD_NAMES = [
53
53
  // Agents
54
54
  'agent/list',
55
55
  'agent/models',
56
+ 'agent/commands',
57
+ 'agent/usageStats',
58
+ // Metrics (bridge-owned, survivable profile stats + tamper-proof backup)
59
+ 'metrics/get',
60
+ 'metrics/export',
61
+ 'metrics/import',
56
62
  // Auth
57
63
  'auth/status',
58
64
  'auth/login',
@@ -1 +1 @@
1
- {"version":3,"file":"method-registry.js","sourceRoot":"","sources":["../../../src/jsonrpc/method-registry.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,kBAAkB;IAClB,aAAa;IACb,aAAa;IACb,cAAc;IACd,eAAe;IACf,aAAa;IACb,iBAAiB;IACjB,eAAe;IACf,sBAAsB;IACtB,gBAAgB;IAChB,kBAAkB;IAClB,eAAe;IACf,WAAW;IACX,WAAW;IACX,WAAW;IACX,aAAa;IACb,MAAM;IACN,YAAY;IACZ,UAAU;IACV,YAAY;IACZ,UAAU;IACV,UAAU;IACV,cAAc;IACd,kBAAkB;IAClB,oBAAoB;IACpB,WAAW;IACX,aAAa;IACb,aAAa;IACb,cAAc;IACd,gBAAgB;IAChB,cAAc;IACd,kBAAkB;IAClB,YAAY;IACZ,kBAAkB;IAClB,oBAAoB;IACpB,SAAS;IACT,gBAAgB;IAChB,YAAY;IACZ,oBAAoB;IACpB,qBAAqB;IACrB,gBAAgB;IAChB,uBAAuB;IACvB,sBAAsB;IACtB,sBAAsB;IACtB,0BAA0B;IAC1B,2BAA2B;IAC3B,sBAAsB;IACtB,kBAAkB;IAClB,WAAW;IACX,cAAc;IACd,iBAAiB;IACjB,SAAS;IACT,YAAY;IACZ,cAAc;IACd,OAAO;IACP,aAAa;IACb,YAAY;IACZ,aAAa;IACb,uBAAuB;IACvB,wBAAwB;IACxB,sBAAsB;IACtB,0BAA0B;IAC1B,iBAAiB;IACjB,eAAe;IACf,0BAA0B;IAC1B,wBAAwB;IACxB,wBAAwB;IACxB,uBAAuB;IACvB,4BAA4B;CACpB,CAAC;AAQX,MAAM,sBAAsB,GAAqB,IAAI,CAAC;AACtD,MAAM,sBAAsB,GAAqB,IAAI,CAAC;AACtD,KAAK,sBAAsB,CAAC;AAC5B,KAAK,sBAAsB,CAAC;AAE5B,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;AAEnE,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"method-registry.js","sourceRoot":"","sources":["../../../src/jsonrpc/method-registry.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,kBAAkB;IAClB,aAAa;IACb,aAAa;IACb,cAAc;IACd,eAAe;IACf,aAAa;IACb,iBAAiB;IACjB,eAAe;IACf,sBAAsB;IACtB,gBAAgB;IAChB,kBAAkB;IAClB,eAAe;IACf,WAAW;IACX,WAAW;IACX,WAAW;IACX,aAAa;IACb,MAAM;IACN,YAAY;IACZ,UAAU;IACV,YAAY;IACZ,UAAU;IACV,UAAU;IACV,cAAc;IACd,kBAAkB;IAClB,oBAAoB;IACpB,WAAW;IACX,aAAa;IACb,aAAa;IACb,cAAc;IACd,gBAAgB;IAChB,cAAc;IACd,kBAAkB;IAClB,YAAY;IACZ,kBAAkB;IAClB,oBAAoB;IACpB,SAAS;IACT,gBAAgB;IAChB,YAAY;IACZ,oBAAoB;IACpB,qBAAqB;IACrB,gBAAgB;IAChB,uBAAuB;IACvB,sBAAsB;IACtB,sBAAsB;IACtB,0BAA0B;IAC1B,2BAA2B;IAC3B,sBAAsB;IACtB,kBAAkB;IAClB,WAAW;IACX,cAAc;IACd,iBAAiB;IACjB,SAAS;IACT,YAAY;IACZ,cAAc;IACd,gBAAgB;IAChB,kBAAkB;IAClB,yEAAyE;IACzE,aAAa;IACb,gBAAgB;IAChB,gBAAgB;IAChB,OAAO;IACP,aAAa;IACb,YAAY;IACZ,aAAa;IACb,uBAAuB;IACvB,wBAAwB;IACxB,sBAAsB;IACtB,0BAA0B;IAC1B,iBAAiB;IACjB,eAAe;IACf,0BAA0B;IAC1B,wBAAwB;IACxB,wBAAwB;IACxB,uBAAuB;IACvB,4BAA4B;CACpB,CAAC;AAQX,MAAM,sBAAsB,GAAqB,IAAI,CAAC;AACtD,MAAM,sBAAsB,GAAqB,IAAI,CAAC;AACtD,KAAK,sBAAsB,CAAC;AAC5B,KAAK,sBAAsB,CAAC;AAE5B,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;AAEnE,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"}
@@ -10,9 +10,12 @@ import type { GitBranchList, GitBranchResult, GitCommitDetails, GitCommitResult,
10
10
  import type { ApplyResult, BrowseResult, Checkpoint, CheckpointDiff, FileContent, ImageContent, PatchChange, TurnAttachment, WorkspaceExistsResult, WorkspaceListing, SearchFilesParams, WorkspaceSearchResult } from '../models/workspace.js';
11
11
  import type { AuthStatus, Project } from '../models/project.js';
12
12
  import type { ApprovalResponse } from '../models/approval.js';
13
+ import type { QuestionResponse } from '../models/question.js';
13
14
  import type { BridgeStatus, ConnectedPhone, TrustedDevice } from '../models/session.js';
14
15
  import type { PairingPayload } from '../e2ee/pairing-payload.js';
15
- import type { AgentDescriptor, AgentId, AgentModel } from '../agents/agent-capabilities.js';
16
+ import type { AgentCommand, AgentCommandInvocation, AgentDescriptor, AgentId, AgentModel } from '../agents/agent-capabilities.js';
17
+ import type { UsageStatsParams, UsageStatsResult } from '../models/usage.js';
18
+ import type { MetricsExportParams, MetricsExportResult, MetricsImportParams, MetricsImportResult, MetricsSnapshot } from '../models/metrics.js';
16
19
  import type { PushPlatform } from '../notifications/push-payload.js';
17
20
  export interface ListThreadsParams {
18
21
  projectId?: string;
@@ -74,6 +77,20 @@ export interface TurnSendParams {
74
77
  * is not required.
75
78
  */
76
79
  approvalResponse?: ApprovalResponse;
80
+ /**
81
+ * Reply to a pending question the agent asked (no new turn is created). The
82
+ * bridge routes the chosen answers to the agent adapter. When present, `text`
83
+ * is not required.
84
+ */
85
+ questionResponse?: QuestionResponse;
86
+ /**
87
+ * Invoke an advertised agent command (from `agent/commands`) instead of
88
+ * free-form `text`. The bridge resolves `{ name, args }` to the final prompt —
89
+ * expanding a custom prompt-template file, or composing the CLI's native
90
+ * `/name args` form — then runs a normal turn. When present, `text` is not
91
+ * required.
92
+ */
93
+ command?: AgentCommandInvocation;
77
94
  }
78
95
  export interface ThreadSetModelParams {
79
96
  threadId: string;
@@ -212,6 +229,19 @@ export interface AgentModelsResult {
212
229
  /** Models the agent can use, with presentation metadata, as reported by its CLI. */
213
230
  models: AgentModel[];
214
231
  }
232
+ export interface AgentCommandsParams {
233
+ agentId: AgentId;
234
+ /**
235
+ * Thread/project directory, so project-scoped custom commands (e.g.
236
+ * `<cwd>/.claude/commands`, `<cwd>/.gemini/commands`) are discovered alongside
237
+ * the user-level ones. Omitted → only user-level commands are returned.
238
+ */
239
+ cwd?: string;
240
+ }
241
+ export interface AgentCommandsResult {
242
+ /** Special ("slash") commands the agent exposes, discovered from its CLI/disk. */
243
+ commands: AgentCommand[];
244
+ }
215
245
  /** What the phone wants to be notified about (background push). */
216
246
  export interface NotificationPreferences {
217
247
  /** Push when an agent turn completes. */
@@ -468,6 +498,26 @@ export interface JsonRpcMethodRegistry {
468
498
  params: AgentModelsParams;
469
499
  result: AgentModelsResult;
470
500
  };
501
+ 'agent/commands': {
502
+ params: AgentCommandsParams;
503
+ result: AgentCommandsResult;
504
+ };
505
+ 'agent/usageStats': {
506
+ params: UsageStatsParams;
507
+ result: UsageStatsResult;
508
+ };
509
+ 'metrics/get': {
510
+ params: void;
511
+ result: MetricsSnapshot;
512
+ };
513
+ 'metrics/export': {
514
+ params: MetricsExportParams;
515
+ result: MetricsExportResult;
516
+ };
517
+ 'metrics/import': {
518
+ params: MetricsImportParams;
519
+ result: MetricsImportResult;
520
+ };
471
521
  'auth/status': {
472
522
  params: {
473
523
  agentId: AgentId;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Profile metrics — a bridge-owned, survivable tally of a PC's activity, plus a
3
+ * tamper-proof export/import backup.
4
+ *
5
+ * The mobile profile screen shows aggregated metrics (conversations, messages,
6
+ * agents/models used, connection time, git actions, an activity heatmap). Those
7
+ * were derived on the phone from local storage and so were lost on an app
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.
13
+ *
14
+ * A `metrics/export` produces an opaque, tamper-proof file only the SAME bridge
15
+ * can later verify + decrypt (AES-256-GCM under a secret held in the PC's OS
16
+ * keychain), so users cannot fabricate or edit their stats; `metrics/import`
17
+ * feeds one back and merges its events by id (idempotent).
18
+ *
19
+ * Provider usage/credits are deliberately NOT part of this — those are read live
20
+ * via `agent/usageStats` and never persisted.
21
+ *
22
+ * Source: architecture/02a-system-architecture.md §5.8.11 and
23
+ * 02b-contracts-and-requirements.md.
24
+ */
25
+ /** Transport a phone→PC connection session used. */
26
+ export type MetricsTransport = 'relay' | 'direct';
27
+ /** Per-agent conversation tally (most-used first). */
28
+ export interface MetricsAgentUsage {
29
+ /** Agent wire id (e.g. `claude-code`, `codex`). */
30
+ agentId: string;
31
+ /** Conversations started with this agent on this PC. */
32
+ conversations: number;
33
+ }
34
+ /** One agent's activity on a given day (drives the per-agent bars). */
35
+ export interface MetricsAgentDay {
36
+ /** Agent wire id (e.g. `claude-code`, `codex`). */
37
+ agentId: string;
38
+ /** Conversations (threads) this agent started that day. */
39
+ conversations: number;
40
+ /** Messages exchanged that day in this agent's threads. */
41
+ messages: number;
42
+ /**
43
+ * Tokens processed that day — the sum of each turn's reported usage (input
44
+ * incl. the re-sent context + output). **Throughput, not billed cost**: caching
45
+ * and input/output pricing differ (use `agent/usageStats` for money). 0 for
46
+ * agents that don't report usage (e.g. Zero).
47
+ */
48
+ tokens: number;
49
+ }
50
+ /** One calendar day's activity split per agent. */
51
+ export interface MetricsDayBreakdown {
52
+ /** UTC-midnight epoch ms of the calendar date (same encoding as `activity`). */
53
+ day: number;
54
+ /** Per-agent activity that day (agents with any conversation/message/token). */
55
+ byAgent: MetricsAgentDay[];
56
+ }
57
+ /**
58
+ * One local calendar-day activity bucket. Counts are split by category so a
59
+ * client can render any metric filter (combined / conversations / messages /
60
+ * work) with no extra round-trip. Days with no activity are omitted.
61
+ */
62
+ export interface MetricsActivityDay {
63
+ /**
64
+ * The calendar date this bucket covers, as **UTC-midnight epoch ms** of the
65
+ * bridge host's local date. Encoding the local date at UTC midnight (not the
66
+ * local-midnight instant) makes the key timezone-stable, so a phone in any
67
+ * timezone maps it to the correct heatmap cell.
68
+ */
69
+ day: number;
70
+ /** Conversations started that day. */
71
+ conversations: number;
72
+ /** Messages exchanged that day. */
73
+ messages: number;
74
+ /** Git/work actions performed that day. */
75
+ work: number;
76
+ }
77
+ /**
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`.
83
+ */
84
+ export interface MetricsSnapshot {
85
+ /** Schema version, for forward-compatible parsing. */
86
+ version: number;
87
+ /** The bridge PC's `macDeviceId` this snapshot belongs to. */
88
+ deviceId: string;
89
+ /** Total conversations (threads) started on this PC. */
90
+ conversations: number;
91
+ /** Distinct agents used. */
92
+ agentsUsed: number;
93
+ /** Distinct models used. */
94
+ modelsUsed: number;
95
+ /** Total messages exchanged (both roles). */
96
+ messages: number;
97
+ /** Total git actions performed (mutating operations). */
98
+ gitActions: number;
99
+ /** Connection sessions recorded. */
100
+ sessions: number;
101
+ /** Cumulative connected time across all sessions, ms. */
102
+ totalConnectedMs: number;
103
+ /** The single longest connection session, ms. */
104
+ longestSessionMs: number;
105
+ /** Sessions that ran over the relay. */
106
+ relaySessions: number;
107
+ /** Sessions that ran over a direct LAN/Tailscale host. */
108
+ directSessions: number;
109
+ /** Per-agent conversation tallies, most-used first. */
110
+ byAgent: MetricsAgentUsage[];
111
+ /** Earliest conversation creation (epoch ms); absent when there are none. */
112
+ memberSince?: number;
113
+ /** Per-day activity buckets for the contribution heatmap. */
114
+ activity: MetricsActivityDay[];
115
+ /**
116
+ * Per-day activity split per agent (conversations, messages, tokens), for the
117
+ * unified agent-activity view: the per-agent bars show all-time totals, or a
118
+ * single day's totals when a heatmap cell is selected. See
119
+ * {@link MetricsAgentDay}.
120
+ */
121
+ byAgentDay: MetricsDayBreakdown[];
122
+ /** When the bridge produced this snapshot (epoch ms). */
123
+ updatedAt: number;
124
+ }
125
+ /**
126
+ * `metrics/export` request. The bridge seals its metrics event log into an
127
+ * opaque, tamper-proof blob that only THIS same bridge can later verify +
128
+ * decrypt (AES-256-GCM under a secret held in the OS keychain). An optional user
129
+ * [passphrase] adds a second confidentiality layer (scrypt-derived), so a leaked
130
+ * file also needs the phrase; it must be supplied again at import.
131
+ */
132
+ export interface MetricsExportParams {
133
+ /** Optional extra passphrase lock; required again at import when set. */
134
+ passphrase?: string;
135
+ }
136
+ export interface MetricsExportResult {
137
+ /** The sealed blob to write to a file (a JSON string, opaque to the phone). */
138
+ blob: string;
139
+ /** Suggested filename (e.g. `uxnan-metrics-<host>-<date>.uxmetrics`). */
140
+ filename: string;
141
+ /** Whether the blob carries an extra passphrase lock (import will need it). */
142
+ passphraseProtected: boolean;
143
+ }
144
+ /**
145
+ * `metrics/import` request. The phone sends back a previously exported [blob].
146
+ * The bridge verifies it was sealed by THIS PC — a foreign or edited file is
147
+ * 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.
150
+ */
151
+ export interface MetricsImportParams {
152
+ /** The sealed blob produced by a prior `metrics/export` on this PC. */
153
+ blob: string;
154
+ /** The passphrase, when the file was exported with one. */
155
+ passphrase?: string;
156
+ }
157
+ export interface MetricsImportResult {
158
+ /** How many new session + git-action events were merged in. */
159
+ imported: number;
160
+ /** The refreshed snapshot after the merge. */
161
+ snapshot: MetricsSnapshot;
162
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Profile metrics — a bridge-owned, survivable tally of a PC's activity, plus a
3
+ * tamper-proof export/import backup.
4
+ *
5
+ * The mobile profile screen shows aggregated metrics (conversations, messages,
6
+ * agents/models used, connection time, git actions, an activity heatmap). Those
7
+ * were derived on the phone from local storage and so were lost on an app
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.
13
+ *
14
+ * A `metrics/export` produces an opaque, tamper-proof file only the SAME bridge
15
+ * can later verify + decrypt (AES-256-GCM under a secret held in the PC's OS
16
+ * keychain), so users cannot fabricate or edit their stats; `metrics/import`
17
+ * feeds one back and merges its events by id (idempotent).
18
+ *
19
+ * Provider usage/credits are deliberately NOT part of this — those are read live
20
+ * via `agent/usageStats` and never persisted.
21
+ *
22
+ * Source: architecture/02a-system-architecture.md §5.8.11 and
23
+ * 02b-contracts-and-requirements.md.
24
+ */
25
+ export {};
26
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../../src/models/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Interactive question contracts.
3
+ *
4
+ * An agent that needs the user to CHOOSE among options (not just approve/reject
5
+ * an action) emits a `question` content block (on `stream/content/block`); the
6
+ * phone renders an interactive picker and replies via `turn/send { questionResponse }`.
7
+ * The bridge routes the chosen answers back to the agent adapter. This mirrors
8
+ * the {@link ApprovalResponse} flow but carries multiple-choice answers instead
9
+ * of a binary decision.
10
+ *
11
+ * Source: architecture/02a-system-architecture.md §6.2.
12
+ */
13
+ /** One selectable option of a {@link QuestionItem}. */
14
+ export interface QuestionOption {
15
+ /** Display text the user picks (also the value echoed back as the answer). */
16
+ label: string;
17
+ /** Optional one-line explanation of what choosing this option means. */
18
+ description?: string;
19
+ }
20
+ /** A single question with its options. */
21
+ export interface QuestionItem {
22
+ /** The full question text. */
23
+ question: string;
24
+ /** Optional short label/category for the question. */
25
+ header?: string;
26
+ /** The choices the user may pick from. */
27
+ options: QuestionOption[];
28
+ /** Whether more than one option may be selected (defaults to single-select). */
29
+ multiple?: boolean;
30
+ }
31
+ /**
32
+ * Payload of a `question` content block. The phone decodes this into its
33
+ * interactive question card. `questionId` is the bridge handle the user echoes
34
+ * back in {@link QuestionResponse.questionId}.
35
+ */
36
+ export interface QuestionRequestBlock {
37
+ type: 'question';
38
+ /** Bridge id the phone echoes back in `questionResponse.questionId`. */
39
+ questionId: string;
40
+ /** The questions to ask (usually one; an agent may batch a few). */
41
+ questions: QuestionItem[];
42
+ }
43
+ /**
44
+ * The user's reply to a pending question, carried on `turn/send`. `answers` is
45
+ * one entry per question (in order), each an array of the chosen option
46
+ * `label`s (one for single-select, several for a `multiple` question, empty when
47
+ * the user skipped that question).
48
+ */
49
+ export interface QuestionResponse {
50
+ /** The id from the question request the user is answering. */
51
+ questionId: string;
52
+ /** Chosen option labels, per question, in question order. */
53
+ answers: string[][];
54
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Interactive question contracts.
3
+ *
4
+ * An agent that needs the user to CHOOSE among options (not just approve/reject
5
+ * an action) emits a `question` content block (on `stream/content/block`); the
6
+ * phone renders an interactive picker and replies via `turn/send { questionResponse }`.
7
+ * The bridge routes the chosen answers back to the agent adapter. This mirrors
8
+ * the {@link ApprovalResponse} flow but carries multiple-choice answers instead
9
+ * of a binary decision.
10
+ *
11
+ * Source: architecture/02a-system-architecture.md §6.2.
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=question.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"question.js","sourceRoot":"","sources":["../../../src/models/question.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * AI-provider usage statistics: quota/rate windows, plan/account, credit
3
+ * balance and local token tallies read from a coding CLI's on-disk state — its
4
+ * stored OAuth token (→ the provider's official usage API) and/or its local
5
+ * session logs.
6
+ *
7
+ * Surfaced in the desktop's Settings → Providers section and, over the bridge,
8
+ * on the phone. The access path is per-runtime by design (02a §5.8.10): the
9
+ * desktop reads these files natively in Rust; the bridge reads them in TS so a
10
+ * paired phone — which cannot see the PC's disk directly — gets the same data
11
+ * over `agent/usageStats`. The Dart equivalents live in uxnanmobile and are kept
12
+ * in sync manually (see 02e-bridge-integration.md §4.2).
13
+ *
14
+ * Posture: only the CLI's own stored token is read — never browser cookies or
15
+ * user-pasted API keys.
16
+ */
17
+ /** A coding CLI whose usage we read from its own stored token. */
18
+ export type UsageProvider = 'codex' | 'claude' | 'copilot' | 'gemini' | 'grok';
19
+ /** Outcome of reading one provider's usage. */
20
+ export type UsageStatus =
21
+ /** Fresh quota/credit data was read. */
22
+ 'ok'
23
+ /** CLI is present but not signed in (no usable token). */
24
+ | 'authRequired'
25
+ /** CLI / its config directory is not present on this machine. */
26
+ | 'notInstalled'
27
+ /** Read/network/parse failure — see {@link ProviderUsage.message}. */
28
+ | 'error';
29
+ /** How the data was obtained, for the UI's provenance label. Every wired
30
+ * provider reads its quota from the CLI's own signed-in token. */
31
+ export type UsageSource = 'token';
32
+ /**
33
+ * The kind of billing relationship, so the UI can label an account beyond its
34
+ * plan name (e.g. distinguish a flat subscription from usage/credit billing).
35
+ * Derived per provider from its plan / billing signals.
36
+ */
37
+ export type AccountType =
38
+ /** A flat paid subscription (Pro / Max / Plus / Pro+ …). */
39
+ 'subscription'
40
+ /** Usage-billed: credits, on-demand, or pay-as-you-go on top of / instead of a plan. */
41
+ | 'payAsYouGo'
42
+ /** Free tier. */
43
+ | 'free'
44
+ /** A team / organization seat. */
45
+ | 'team'
46
+ /** An enterprise plan. */
47
+ | 'enterprise';
48
+ /**
49
+ * A single quota/rate window, expressed as a used-percentage with an optional
50
+ * reset time — the atomic unit a provider reports (e.g. a 5-hour session, a
51
+ * weekly cap, or a model-specific window).
52
+ */
53
+ export interface UsageWindow {
54
+ /** Stable id (e.g. `session5h`, `weekly`, `opusWeekly`) — used by the
55
+ * status-bar picker to remember which windows to surface. */
56
+ id: string;
57
+ /** Human label (English; the UI localizes known ids, else shows this). */
58
+ label: string;
59
+ /** Consumed fraction of this window, clamped to 0–100. */
60
+ usedPercent: number;
61
+ /** Window length in minutes (300 = 5h, 10080 = 7d, 1440 = 24h), when known. */
62
+ windowMinutes?: number;
63
+ /** When the window resets (epoch ms), when the provider reports it. */
64
+ resetsAt?: number;
65
+ }
66
+ /** A monetary / credit balance, kept separate from the percentage windows. */
67
+ export interface CreditBalance {
68
+ /** Amount consumed this period, in `currency`. */
69
+ used: number;
70
+ /** Spend/credit cap, when the provider exposes one. */
71
+ limit?: number;
72
+ /** ISO-4217 code (`USD`, `EUR`, …) or `credits` for non-currency units. */
73
+ currency: string;
74
+ /** Period label (English; e.g. `Monthly`, `Credits`). */
75
+ period: string;
76
+ /** When the balance resets (epoch ms), when known. */
77
+ resetsAt?: number;
78
+ /** Amount still available this period, in `currency`, when the provider
79
+ * reports a remaining balance directly (e.g. Grok on-demand / prepaid). */
80
+ available?: number;
81
+ }
82
+ /**
83
+ * "Reset credits" a provider grants to roll a hit rate-limit back early — Codex's
84
+ * rate-limit reset-credit system. Distinct from `credit` (money): these are
85
+ * redeemable reset tokens, not a balance.
86
+ */
87
+ /** One redeemable reset — for the per-credit detail (which one, when it expires). */
88
+ export interface ResetCreditEntry {
89
+ /** Short label the provider gives the reset (e.g. "Full reset"). */
90
+ title?: string;
91
+ /** When this reset lapses (epoch ms). */
92
+ expiresAt?: number;
93
+ }
94
+ export interface ResetCredits {
95
+ /** How many resets can be redeemed right now. */
96
+ available: number;
97
+ /** Total resets ever granted to this account, when reported. */
98
+ totalEarned?: number;
99
+ /** When the soonest still-available reset lapses (epoch ms), when known. */
100
+ nextExpiresAt?: number;
101
+ /** The individual available resets, soonest-expiring first, when the provider
102
+ * details them. */
103
+ entries?: ResetCreditEntry[];
104
+ }
105
+ /** One provider's usage snapshot. */
106
+ export interface ProviderUsage {
107
+ provider: UsageProvider;
108
+ status: UsageStatus;
109
+ /** How `windows`/`credit` were obtained (absent for states with no data). */
110
+ source?: UsageSource;
111
+ account?: {
112
+ email?: string;
113
+ organization?: string;
114
+ plan?: string;
115
+ accountType?: AccountType;
116
+ };
117
+ /** Quota/rate windows (percentage-based). Empty when none apply. */
118
+ windows: UsageWindow[];
119
+ credit?: CreditBalance;
120
+ /** Redeemable rate-limit resets (Codex), when the provider grants them. */
121
+ resetCredits?: ResetCredits;
122
+ /** When this snapshot was produced (epoch ms). */
123
+ updatedAt: number;
124
+ /** Error/hint message for `error` / `authRequired` / `notInstalled` states. */
125
+ message?: string;
126
+ }
127
+ /**
128
+ * `agent/usageStats` request: read usage for exactly these providers — only the
129
+ * ones the user activated. The reader never polls providers not listed here, so
130
+ * inactive providers cost nothing.
131
+ */
132
+ export interface UsageStatsParams {
133
+ providers: UsageProvider[];
134
+ }
135
+ export interface UsageStatsResult {
136
+ usage: ProviderUsage[];
137
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * AI-provider usage statistics: quota/rate windows, plan/account, credit
3
+ * balance and local token tallies read from a coding CLI's on-disk state — its
4
+ * stored OAuth token (→ the provider's official usage API) and/or its local
5
+ * session logs.
6
+ *
7
+ * Surfaced in the desktop's Settings → Providers section and, over the bridge,
8
+ * on the phone. The access path is per-runtime by design (02a §5.8.10): the
9
+ * desktop reads these files natively in Rust; the bridge reads them in TS so a
10
+ * paired phone — which cannot see the PC's disk directly — gets the same data
11
+ * over `agent/usageStats`. The Dart equivalents live in uxnanmobile and are kept
12
+ * in sync manually (see 02e-bridge-integration.md §4.2).
13
+ *
14
+ * Posture: only the CLI's own stored token is read — never browser cookies or
15
+ * user-pasted API keys.
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../../../src/models/usage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxnan/shared",
3
- "version": "0.0.4-alpha.20260703",
3
+ "version": "0.0.6-alpha.20260716",
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",