@cozibud/harness 0.2.0 → 0.3.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/README.md +32 -3
- package/dist/cli.js +12 -14
- package/dist/pairing.js +2 -0
- package/dist/process-driver.js +37 -8
- package/dist/relay-client.d.ts +2 -0
- package/dist/relay-client.js +57 -8
- package/dist/relay-polling.d.ts +12 -0
- package/dist/relay-polling.js +34 -0
- package/dist/relay-protocol.d.ts +10 -3
- package/dist/relay-protocol.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
# `@cozibud/harness`
|
|
2
2
|
|
|
3
|
-
Connect a signed-in Codex CLI to your Cozibud household for
|
|
3
|
+
Connect a signed-in Codex CLI to your Cozibud household for baby-care questions
|
|
4
|
+
and actions in the web and phone apps, and watch voice.
|
|
4
5
|
Keep the computer awake and the outbound relay running while using voice.
|
|
5
|
-
|
|
6
|
-
protocol 2.
|
|
6
|
+
This harness requires Codex CLI 0.160.0 or newer. Watch voice needs a server supporting relay
|
|
7
|
+
protocol 2. Web and phone baby-care conversations require harness 0.3.0,
|
|
8
|
+
Codex CLI 0.160.0 or newer, and a server supporting relay protocol 3.
|
|
9
|
+
This source version still needs its package and server release.
|
|
10
|
+
|
|
11
|
+
## Web and phone baby care
|
|
12
|
+
|
|
13
|
+
Enable Voice and text entry in Settings and choose Codex on your computer.
|
|
14
|
+
Ask about the selected baby's last sleep, follow up about its duration, record
|
|
15
|
+
care, manage timers, or ask for summaries, trends, routines and caregiver coverage.
|
|
16
|
+
The server uses the same baby-care MCP handlers and permissions as other clients.
|
|
17
|
+
Availability of medication and symptom recording follows the server's policy.
|
|
18
|
+
Switch the selected child before asking about another baby.
|
|
19
|
+
|
|
20
|
+
Corrections, deletions, restores, bulk changes and care-plan changes show the
|
|
21
|
+
exact proposal before execution. Choose Confirm change to apply it, or Cancel
|
|
22
|
+
to leave it unchanged. Approval expires after five minutes; a record changed
|
|
23
|
+
elsewhere requires a new proposal. A text message alone cannot approve a proposal.
|
|
24
|
+
|
|
25
|
+
The interpreter has no shell, filesystem, browser, image, memory, plugin or
|
|
26
|
+
external MCP access. It proposes operations to a closed server-side baby-care
|
|
27
|
+
allowlist. Mother tools, feedback and unrelated applications are excluded.
|
|
28
|
+
No separate Codex MCP connection is needed for this path. Your signed-in Codex
|
|
29
|
+
client receives the transcript and bounded baby-care context/results.
|
|
7
30
|
|
|
8
31
|
## Watch voice
|
|
9
32
|
|
|
@@ -28,6 +51,12 @@ child context go to your signed-in Codex client and its model service.
|
|
|
28
51
|
Your Codex login stays local. Pairing credentials are protected with your
|
|
29
52
|
Windows account's DPAPI on Windows, or a user-only file on other systems.
|
|
30
53
|
Disconnect the computer in Cozibud's Watch settings to revoke its access.
|
|
54
|
+
Each Cozibud account can pair two computers. The one connected longest answers
|
|
55
|
+
requests from the web app, the phone app and the watch; the other is a backup
|
|
56
|
+
that takes over when the first stops reporting in. Run `pair` again on a computer
|
|
57
|
+
to replace its pairing, or choose the household and account by signing in to the
|
|
58
|
+
one you want when you approve the link. Codex uses whichever account `codex login`
|
|
59
|
+
signed in (set `CODEX_HOME` to keep a separate Codex profile).
|
|
31
60
|
Use `COZIBUD_RELAY_URL` to choose another HTTPS server before pairing; pairings
|
|
32
61
|
are scoped to that origin. The relay requires no inbound computer port.
|
|
33
62
|
The separate local HTTP adapter must remain on loopback.
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { ensureRelayPairing, pairRelay, relayOrigin } from "./pairing.js";
|
|
|
7
7
|
import { createBuiltInDrivers } from "./process-driver.js";
|
|
8
8
|
import { FileRelayExecutionStore, OutboundRelayBridge, RelayTransportError } from "./relay-client.js";
|
|
9
9
|
import { HarnessRuntime } from "./runtime.js";
|
|
10
|
+
import { RelayPollDelay, relayPause as pause } from "./relay-polling.js";
|
|
10
11
|
import { createHarnessHttpServer } from "./server.js";
|
|
11
12
|
import { DEFAULT_COZIBUD_MCP_URL, setupProvider } from "./setup.js";
|
|
12
13
|
function value(args, name, fallback) {
|
|
@@ -65,8 +66,8 @@ async function relay(args) {
|
|
|
65
66
|
if (available.state === "unavailable")
|
|
66
67
|
throw new Error(available.message);
|
|
67
68
|
const version = available.version?.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
68
|
-
if (!version || (Number(version[1]) === 0 && Number(version[2]) <
|
|
69
|
-
throw new Error("
|
|
69
|
+
if (!version || (Number(version[1]) === 0 && Number(version[2]) < 160))
|
|
70
|
+
throw new Error("Baby-care relay requires Codex CLI 0.160.0 or newer. Update Codex, sign in, and retry.");
|
|
70
71
|
const controller = new AbortController();
|
|
71
72
|
process.once("SIGINT", () => controller.abort());
|
|
72
73
|
process.once("SIGTERM", () => controller.abort());
|
|
@@ -75,25 +76,31 @@ async function relay(args) {
|
|
|
75
76
|
const createBridge = () => {
|
|
76
77
|
const namespace = createHash("sha256").update(`${baseUrl}\n${credential}`).digest("hex").slice(0, 20);
|
|
77
78
|
const statePath = process.env.COZIBUD_RELAY_STATE?.trim() || join(homedir(), ".cozibud", `relay-state-${namespace}.json`);
|
|
78
|
-
return new OutboundRelayBridge({ baseUrl, credential, driver, store: new FileRelayExecutionStore(statePath) });
|
|
79
|
+
return new OutboundRelayBridge({ baseUrl, credential, driver, store: new FileRelayExecutionStore(statePath), signal: controller.signal });
|
|
79
80
|
};
|
|
80
81
|
let bridge = createBridge();
|
|
81
82
|
let repaired = false;
|
|
82
83
|
let connected = false;
|
|
83
84
|
let backoffMs = 1_000;
|
|
85
|
+
const pollDelay = new RelayPollDelay();
|
|
84
86
|
while (!controller.signal.aborted) {
|
|
85
87
|
try {
|
|
86
88
|
if (!connected) {
|
|
87
89
|
await bridge.connect();
|
|
88
90
|
connected = true;
|
|
89
91
|
backoffMs = 1_000;
|
|
92
|
+
pollDelay.reset();
|
|
90
93
|
console.log(`Cozibud relay connected with ${driver.label}. No local or LAN listener is open.`);
|
|
91
94
|
}
|
|
92
95
|
const state = await bridge.step();
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
const waitMs = pollDelay.after(state, bridge.hasPendingExecution);
|
|
97
|
+
if (waitMs > 0)
|
|
98
|
+
await pause(waitMs, controller.signal);
|
|
95
99
|
}
|
|
96
100
|
catch (error) {
|
|
101
|
+
if (controller.signal.aborted)
|
|
102
|
+
break;
|
|
103
|
+
pollDelay.reset();
|
|
97
104
|
if (error instanceof RelayTransportError && [401, 403, 404].includes(error.status)) {
|
|
98
105
|
await bridge.close();
|
|
99
106
|
if ([401, 403].includes(error.status) && !process.env.COZIBUD_RELAY_CREDENTIAL?.trim() && !repaired && !controller.signal.aborted) {
|
|
@@ -207,12 +214,3 @@ catch (error) {
|
|
|
207
214
|
console.error(error instanceof Error ? error.message : "The local harness failed.");
|
|
208
215
|
process.exitCode = 1;
|
|
209
216
|
}
|
|
210
|
-
function pause(ms, signal) {
|
|
211
|
-
return new Promise(resolve => {
|
|
212
|
-
const done = () => { clearTimeout(timer); signal.removeEventListener("abort", done); resolve(); };
|
|
213
|
-
const timer = setTimeout(done, ms);
|
|
214
|
-
signal.addEventListener("abort", done, { once: true });
|
|
215
|
-
if (signal.aborted)
|
|
216
|
-
done();
|
|
217
|
-
});
|
|
218
|
-
}
|
package/dist/pairing.js
CHANGED
|
@@ -73,6 +73,8 @@ export async function pairRelay(options) {
|
|
|
73
73
|
interval += 5;
|
|
74
74
|
continue;
|
|
75
75
|
}
|
|
76
|
+
if (result.error?.code === "bridge_limit")
|
|
77
|
+
throw new Error("Your account already has two computers paired. Disconnect one in Cozibud under Settings → Connected devices → Computers, then run pair again.");
|
|
76
78
|
throw new Error("Pairing was denied or expired. Run pair again.");
|
|
77
79
|
}
|
|
78
80
|
throw new Error("Pairing expired. Run pair again.");
|
package/dist/process-driver.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { delimiter, isAbsolute, join } from "node:path";
|
|
5
|
+
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import { StringDecoder } from "node:string_decoder";
|
|
7
7
|
import { ensureCursorWorkspace } from "./cursor-workspace.js";
|
|
8
8
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
@@ -307,17 +307,46 @@ export function executable(configured) {
|
|
|
307
307
|
for (const directory of directories) {
|
|
308
308
|
const base = directory ? join(directory, configured) : configured;
|
|
309
309
|
const script = /\.ps1$/i.test(base) ? base : `${base}.ps1`;
|
|
310
|
-
if (existsSync(script))
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
310
|
+
if (!existsSync(script))
|
|
311
|
+
continue;
|
|
312
|
+
const shimmed = nodeShimTarget(script);
|
|
313
|
+
if (shimmed)
|
|
314
|
+
return shimmed;
|
|
315
|
+
return {
|
|
316
|
+
command: join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
|
|
317
|
+
prefixArgs: ["-NoLogo", "-NoProfile", "-File", script],
|
|
318
|
+
};
|
|
315
319
|
}
|
|
316
320
|
}
|
|
317
321
|
return /\.(?:c|m)?js$/i.test(configured)
|
|
318
322
|
? { command: process.execPath, prefixArgs: [configured] }
|
|
319
323
|
: { command: configured, prefixArgs: [] };
|
|
320
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* npm, pnpm and Yarn install a CLI on Windows as a PowerShell shim that runs a JavaScript entry point
|
|
327
|
+
* with Node. Running that entry point directly keeps arguments intact: `powershell -File` treats a bare
|
|
328
|
+
* `-` (read the prompt from stdin) as an unnamed parameter and fails before the CLI starts.
|
|
329
|
+
*/
|
|
330
|
+
function nodeShimTarget(script) {
|
|
331
|
+
let source;
|
|
332
|
+
try {
|
|
333
|
+
source = readFileSync(script, "utf8");
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
if (source.length > 16_384)
|
|
339
|
+
return null;
|
|
340
|
+
const target = /"\$basedir[\\/]([^"$`]+\.[cm]?js)"/i.exec(source)?.[1];
|
|
341
|
+
if (!target)
|
|
342
|
+
return null;
|
|
343
|
+
const basedir = dirname(script);
|
|
344
|
+
const entry = resolve(basedir, target);
|
|
345
|
+
if (!existsSync(entry))
|
|
346
|
+
return null;
|
|
347
|
+
const bundledNode = join(basedir, "node.exe");
|
|
348
|
+
return { command: existsSync(bundledNode) ? bundledNode : process.execPath, prefixArgs: [entry] };
|
|
349
|
+
}
|
|
321
350
|
const codexExecutable = executable(process.env.COZIBUD_CODEX_PATH || "codex");
|
|
322
351
|
const claudeExecutable = executable(process.env.COZIBUD_CLAUDE_PATH || "claude");
|
|
323
352
|
const geminiExecutable = executable(process.env.COZIBUD_GEMINI_PATH || "gemini");
|
|
@@ -329,7 +358,7 @@ export const builtInProcessHarnesses = [
|
|
|
329
358
|
command: codexExecutable.command,
|
|
330
359
|
versionArgs: [...codexExecutable.prefixArgs, "--version"],
|
|
331
360
|
args: (input) => [...codexExecutable.prefixArgs, "exec", "--json", "--ephemeral", "--skip-git-repo-check", "--sandbox", "read-only",
|
|
332
|
-
...(input?.interpretation ? ["--ignore-user-config", "--ignore-rules", "-c", "features.shell_tool=false", "-c", "features.apps=false", "-c", "features.multi_agent=false", "-c", "features.hooks=false", "-c", "web_search=\"disabled\""] : ["-c", "mcp_servers.cozibud.required=true"]), "-"],
|
|
361
|
+
...(input?.interpretation ? ["--ignore-user-config", "--ignore-rules", "-c", "features.shell_tool=false", "-c", "features.unified_exec=false", "-c", "tools.view_image=false", "-c", "features.view_image=false", "-c", "features.image_generation=false", "-c", "features.memories=false", "-c", "features.plugins=false", "-c", "features.remote_plugin=false", "-c", "features.skill_mcp_dependency_install=false", "-c", "features.apps=false", "-c", "features.multi_agent=false", "-c", "features.hooks=false", "-c", "web_search=\"disabled\""] : ["-c", "mcp_servers.cozibud.required=true"]), "-"],
|
|
333
362
|
},
|
|
334
363
|
{
|
|
335
364
|
id: "claude",
|
package/dist/relay-client.d.ts
CHANGED
package/dist/relay-client.js
CHANGED
|
@@ -65,10 +65,11 @@ export class OutboundRelayBridge {
|
|
|
65
65
|
constructor(options) {
|
|
66
66
|
this.options = options;
|
|
67
67
|
}
|
|
68
|
+
get hasPendingExecution() { return this.scheduled.size > 0; }
|
|
68
69
|
get fetcher() { return this.options.fetchImpl ?? fetch; }
|
|
69
70
|
headers(extra) { return { authorization: `Bearer ${this.options.credential}`, ...extra }; }
|
|
70
71
|
async json(path, init) {
|
|
71
|
-
const response = await this.fetcher(new URL(path, this.options.baseUrl), { ...init, redirect: "error", signal: AbortSignal.timeout(20_000) });
|
|
72
|
+
const response = await this.fetcher(new URL(path, this.options.baseUrl), { ...init, redirect: "error", signal: this.options.signal ? AbortSignal.any([this.options.signal, AbortSignal.timeout(20_000)]) : AbortSignal.timeout(20_000) });
|
|
72
73
|
if (!response.ok) {
|
|
73
74
|
const code = await response.json().then(value => value.error?.code).catch(() => undefined);
|
|
74
75
|
throw new RelayTransportError(response.status, code ?? `relay_http_${response.status}`);
|
|
@@ -145,13 +146,61 @@ export class OutboundRelayBridge {
|
|
|
145
146
|
try {
|
|
146
147
|
const prompt = command.transcript ?? JSON.stringify(command.action);
|
|
147
148
|
const source = command.source === "structured-action" ? "quick-action" : command.source;
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
careRecordIdempotencyKey: `relay:${command.requestId}` }, {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(Math.min(300_000, Math.max(1, Date.parse(command.expiresAt) - Date.now())))]);
|
|
150
|
+
const interpret = (interpretation = command.interpretation) => this.options.driver.run({ prompt, source, client: command.client,
|
|
151
|
+
interpretation, careRecordIdempotencyKey: `relay:${command.requestId}` }, { turnId: command.requestId, signal, emit: () => { } });
|
|
152
|
+
let output;
|
|
153
|
+
if (!command.careTools)
|
|
154
|
+
output = await interpret();
|
|
155
|
+
else {
|
|
156
|
+
if (!command.interpretation || this.options.driver.id !== "codex")
|
|
157
|
+
throw new Error("Baby-care interpretation requires the isolated Codex driver.");
|
|
158
|
+
const results = [];
|
|
159
|
+
let next = command.careTools.confirmed;
|
|
160
|
+
let callId = 0;
|
|
161
|
+
let answerReadback = "";
|
|
162
|
+
for (let round = 0;; round += 1) {
|
|
163
|
+
if (round > 12 || signal.aborted)
|
|
164
|
+
throw new Error("The care request reached its limit. Check the current records before trying again.");
|
|
165
|
+
if (!next) {
|
|
166
|
+
output = await interpret(`${command.interpretation}\n<care_tool_results>${JSON.stringify(results)}</care_tool_results>\n${answerReadback}\nReturn the next care tool request or a final answer as JSON.`);
|
|
167
|
+
let parsed;
|
|
168
|
+
try {
|
|
169
|
+
parsed = JSON.parse(output.text);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
throw new Error("The care interpreter returned invalid JSON.");
|
|
173
|
+
}
|
|
174
|
+
if (!parsed || typeof parsed !== "object")
|
|
175
|
+
throw new Error("The care interpreter returned invalid JSON.");
|
|
176
|
+
const value = parsed;
|
|
177
|
+
if (value.operation !== "tool") {
|
|
178
|
+
if (!["answer", "clarify", "refuse"].includes(value.operation ?? "") || typeof value.text !== "string" || !value.text.trim())
|
|
179
|
+
throw new Error("The care interpreter returned an invalid answer.");
|
|
180
|
+
if (value.operation === "answer" && callId === 0) {
|
|
181
|
+
answerReadback = "Your answer has no fresh server readback for this message. Return a care tool request to check the referenced record now; prior conversation results alone cannot support a final answer.";
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
if (typeof value.name !== "string" || !value.arguments || Array.isArray(value.arguments) || typeof value.arguments !== "object")
|
|
187
|
+
throw new Error("The care interpreter returned an invalid tool request.");
|
|
188
|
+
next = { name: value.name, arguments: value.arguments };
|
|
189
|
+
}
|
|
190
|
+
if (++callId > 12)
|
|
191
|
+
throw new Error("The care request reached its tool limit.");
|
|
192
|
+
const result = await this.json(`/api/watch-relay/v1/deliveries/${command.deliveryId}/care-tools`, { method: "POST", headers: this.headers({ "content-type": "application/json" }),
|
|
193
|
+
body: JSON.stringify({ requestId: command.requestId, connectionId: this.connectionId, callId, ...next }) });
|
|
194
|
+
if (result.retrySameCall)
|
|
195
|
+
callId -= 1;
|
|
196
|
+
results.push({ name: next.name, ...result });
|
|
197
|
+
next = undefined;
|
|
198
|
+
if (result.status === "confirmation_required") {
|
|
199
|
+
output = { text: JSON.stringify({ operation: "clarify", text: "Review this proposed change before applying it." }) };
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
155
204
|
terminal = { state: "completed", modelOutput: output.text, updatedAt: new Date().toISOString() };
|
|
156
205
|
}
|
|
157
206
|
catch (error) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Short polls retain the server's 45-second connection lease. Only a quiet,
|
|
2
|
+
* empty bridge backs off: active provider work keeps the existing one-second
|
|
3
|
+
* cancellation cadence. New commands, cancellation and reconnect reset it.
|
|
4
|
+
* No request is deliberately held open. Idle discovery may wait up to 2.5s. The cap stays below ordinary client keep-alive timeouts to avoid turning idle polls into repeated TLS handshakes. */
|
|
5
|
+
export declare class RelayPollDelay {
|
|
6
|
+
private readonly random;
|
|
7
|
+
private nextIdleMs;
|
|
8
|
+
constructor(random?: () => number);
|
|
9
|
+
reset(): void;
|
|
10
|
+
after(state: "idle" | "command" | "cancel", hasPendingExecution: boolean): number;
|
|
11
|
+
}
|
|
12
|
+
export declare function relayPause(ms: number, signal: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Short polls retain the server's 45-second connection lease. Only a quiet,
|
|
2
|
+
* empty bridge backs off: active provider work keeps the existing one-second
|
|
3
|
+
* cancellation cadence. New commands, cancellation and reconnect reset it.
|
|
4
|
+
* No request is deliberately held open. Idle discovery may wait up to 2.5s. The cap stays below ordinary client keep-alive timeouts to avoid turning idle polls into repeated TLS handshakes. */
|
|
5
|
+
export class RelayPollDelay {
|
|
6
|
+
random;
|
|
7
|
+
nextIdleMs = 1_000;
|
|
8
|
+
constructor(random = Math.random) {
|
|
9
|
+
this.random = random;
|
|
10
|
+
}
|
|
11
|
+
reset() { this.nextIdleMs = 1_000; }
|
|
12
|
+
after(state, hasPendingExecution) {
|
|
13
|
+
if (state !== "idle") {
|
|
14
|
+
this.reset();
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
if (hasPendingExecution) {
|
|
18
|
+
this.reset();
|
|
19
|
+
return 1_000;
|
|
20
|
+
}
|
|
21
|
+
const delay = Math.round(this.nextIdleMs * (0.8 + this.random() * 0.2));
|
|
22
|
+
this.nextIdleMs = Math.min(2_500, this.nextIdleMs * 2);
|
|
23
|
+
return delay;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function relayPause(ms, signal) {
|
|
27
|
+
return new Promise(resolve => {
|
|
28
|
+
const done = () => { clearTimeout(timer); signal.removeEventListener("abort", done); resolve(); };
|
|
29
|
+
const timer = setTimeout(done, ms);
|
|
30
|
+
signal.addEventListener("abort", done, { once: true });
|
|
31
|
+
if (signal.aborted)
|
|
32
|
+
done();
|
|
33
|
+
});
|
|
34
|
+
}
|
package/dist/relay-protocol.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** Versioned wire values consumed by the outbound Cozibud relay client. */
|
|
2
|
-
export declare const WATCH_RELAY_PROTOCOL: "cozibud.watch-relay.
|
|
3
|
-
export declare const WATCH_RELAY_PROTOCOL_VERSION:
|
|
2
|
+
export declare const WATCH_RELAY_PROTOCOL: "cozibud.watch-relay.v3";
|
|
3
|
+
export declare const WATCH_RELAY_PROTOCOL_VERSION: 3;
|
|
4
4
|
export type WatchRelayCommand = Readonly<{
|
|
5
|
-
protocol: typeof WATCH_RELAY_PROTOCOL;
|
|
5
|
+
protocol: typeof WATCH_RELAY_PROTOCOL | "cozibud.watch-relay.v2";
|
|
6
6
|
requestId: string;
|
|
7
7
|
deliveryId: string;
|
|
8
8
|
attempt: number;
|
|
@@ -15,4 +15,11 @@ export type WatchRelayCommand = Readonly<{
|
|
|
15
15
|
}>;
|
|
16
16
|
expiresAt: string;
|
|
17
17
|
interpretation?: string;
|
|
18
|
+
careTools?: Readonly<{
|
|
19
|
+
enabled: true;
|
|
20
|
+
confirmed?: {
|
|
21
|
+
name: string;
|
|
22
|
+
arguments: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
}>;
|
|
18
25
|
}>;
|
package/dist/relay-protocol.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** Versioned wire values consumed by the outbound Cozibud relay client. */
|
|
2
|
-
export const WATCH_RELAY_PROTOCOL = "cozibud.watch-relay.
|
|
3
|
-
export const WATCH_RELAY_PROTOCOL_VERSION =
|
|
2
|
+
export const WATCH_RELAY_PROTOCOL = "cozibud.watch-relay.v3";
|
|
3
|
+
export const WATCH_RELAY_PROTOCOL_VERSION = 3;
|