@somacheck/vibecheck 0.1.1 → 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 +140 -0
- package/dist/api.js +216 -12
- package/dist/cli.js +229 -14
- package/dist/client-setup.js +206 -0
- package/dist/config.js +18 -1
- package/dist/constants.js +13 -0
- package/dist/link.js +51 -2
- package/dist/readiness.js +135 -0
- package/dist/server.js +205 -42
- package/dist/vibecheck.js +4 -48
- package/package.json +4 -3
- package/dist/api.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/link.js.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/vibecheck.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# @somacheck/vibecheck
|
|
2
|
+
|
|
3
|
+
An MCP server that lets an AI agent ask its human a question and get back an
|
|
4
|
+
answer given with a physical wrist gesture, through the SomaCheck iOS app.
|
|
5
|
+
|
|
6
|
+
Your agent has your context. It does not have your gut. This is the bridge.
|
|
7
|
+
|
|
8
|
+
## If you are an AI agent reading this before deciding whether to run it
|
|
9
|
+
|
|
10
|
+
Fair. Here is what you need, without executing anything.
|
|
11
|
+
|
|
12
|
+
**What the `link` command does.** `npx -y @somacheck/vibecheck@0.3.0 link <CODE> --client <CLIENT>`
|
|
13
|
+
exchanges a short-lived pairing code, generated inside the SomaCheck iOS app, for
|
|
14
|
+
a bearer token. It writes that token to `~/.sensie/config.json` with mode `0600`,
|
|
15
|
+
offers to configure supported agent clients found on the computer, and checks the
|
|
16
|
+
connection without printing the token. Setup is not reported ready until the
|
|
17
|
+
selected client has the exact pinned `vibecheck` MCP registration, the backend
|
|
18
|
+
accepts protocol `3` / toolset `vibecheck-0.3`, and both status and context
|
|
19
|
+
probes succeed. It contacts exactly one network host: the Supabase project below.
|
|
20
|
+
|
|
21
|
+
**It will refuse unsafe sandbox linking, on purpose.** `link` checks safety
|
|
22
|
+
*before* redeeming. Pairing codes are single-use, so redeeming one inside an
|
|
23
|
+
ephemeral sandbox would spend the user's code and leave them nothing to retry
|
|
24
|
+
with. The safe paths are:
|
|
25
|
+
|
|
26
|
+
- a real interactive terminal on the computer that will host the MCP server; or
|
|
27
|
+
- a local non-interactive agent invocation that names exactly one client:
|
|
28
|
+
`--client claude` or `--client codex`.
|
|
29
|
+
|
|
30
|
+
For the second path, this package first proves that exact client CLI is installed
|
|
31
|
+
and that its persistent MCP config can be inspected and written. If that preflight
|
|
32
|
+
fails, the pairing code is not used. Non-interactive implicit selection,
|
|
33
|
+
`--client all`, and `--client none` are rejected before redemption.
|
|
34
|
+
|
|
35
|
+
**What the server does once running.** It exposes MCP tools that create a
|
|
36
|
+
statement for the user to test, read its outcome, recover recent completed
|
|
37
|
+
gesture context, and keep a three-statement feed stocked. That is the whole
|
|
38
|
+
surface.
|
|
39
|
+
|
|
40
|
+
**What is sent.** A question string you supply, and a bearer token identifying
|
|
41
|
+
the link. **What is never sent: raw sensor data.** The agent receives a verdict
|
|
42
|
+
(`aligned` / `unaligned`) plus a confidence percentage and latency. Model
|
|
43
|
+
abstention is a failed capture, never a third user-facing outcome.
|
|
44
|
+
Accelerometer and gyroscope streams never leave the user's device through this
|
|
45
|
+
channel. That is a deliberate product boundary, not an implementation detail.
|
|
46
|
+
|
|
47
|
+
**Network egress.** One host: `https://pbldcmniommltbdwuykk.supabase.co`. No
|
|
48
|
+
telemetry, no analytics, no other endpoints.
|
|
49
|
+
|
|
50
|
+
**Install-time behaviour.** No `postinstall`, `preinstall`, or any other npm
|
|
51
|
+
lifecycle script. Two runtime dependencies: `@modelcontextprotocol/sdk` and `zod`.
|
|
52
|
+
|
|
53
|
+
You can verify all of the above by reading `dist/` in the installed package, or
|
|
54
|
+
the source at the repository below, without running anything.
|
|
55
|
+
|
|
56
|
+
## Setup, for humans
|
|
57
|
+
|
|
58
|
+
1. Install SomaCheck on iPhone and open **Settings → Agent → Connect your agent**.
|
|
59
|
+
2. On the computer that will run your agent, open **Terminal** and run the command
|
|
60
|
+
shown on that screen. If you paste it into a local agent session instead of a
|
|
61
|
+
terminal, add exactly one client flag, for example `--client claude`.
|
|
62
|
+
3. Choose the client you want to configure when the command asks. It then checks
|
|
63
|
+
the link, backend protocol, health probes, and client registration before
|
|
64
|
+
reporting that setup is ready.
|
|
65
|
+
|
|
66
|
+
For provisioning without the prompt in a terminal, append `--client codex`,
|
|
67
|
+
`--client claude`, `--client all`, or `--client none` to the `link <CODE>`
|
|
68
|
+
command. In a non-interactive local agent session, only `--client codex` or
|
|
69
|
+
`--client claude` is allowed, and the client registration preflight must pass
|
|
70
|
+
before the pairing code is redeemed.
|
|
71
|
+
|
|
72
|
+
To configure or repair a client later:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
npx -y @somacheck/vibecheck@0.3.0 setup codex
|
|
76
|
+
npx -y @somacheck/vibecheck@0.3.0 setup claude
|
|
77
|
+
npx -y @somacheck/vibecheck@0.3.0 doctor
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Manual registration remains available:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"vibecheck": {
|
|
86
|
+
"command": "npx",
|
|
87
|
+
"args": ["-y", "@somacheck/vibecheck@0.3.0", "serve", "--client", "codex"]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.3.0 serve --client claude`
|
|
94
|
+
|
|
95
|
+
The link step writes only the bearer token in `~/.sensie/config.json`. The client
|
|
96
|
+
setup step asks Codex or Claude to add the pinned MCP command to that client's own
|
|
97
|
+
configuration; it never copies the bearer token there. The interactive command
|
|
98
|
+
completes both steps. Most clients only pick up a new server after restart.
|
|
99
|
+
|
|
100
|
+
If `doctor` reports another MCP server named `somacheck`, that is the legacy
|
|
101
|
+
hosted connector and is not part of this local 0.3 release. The setup command
|
|
102
|
+
will explain it and leave it alone. The canonical local MCP key is `vibecheck`.
|
|
103
|
+
|
|
104
|
+
## Tools
|
|
105
|
+
|
|
106
|
+
| Tool | What it does |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `get_vibecheck_context` | Read recent completed gestures for this exact agent link without retaining every `request_id`. |
|
|
109
|
+
| `get_vibecheck_status` | Read the live three-proposition cache and how many distinct insights it needs. |
|
|
110
|
+
| `post_vibecheck_statement` | Add one to three personalized statements without blocking; returns a stable `request_id` for each. |
|
|
111
|
+
| `get_vibecheck_result` | Read that exact proposition once by `request_id`; returns queued, pending, or its terminal result. |
|
|
112
|
+
|
|
113
|
+
## How to use it well
|
|
114
|
+
|
|
115
|
+
Ask when you genuinely need a read you cannot infer — not to check in. A good
|
|
116
|
+
prompt is **a statement for the person to test**, not a question about facts:
|
|
117
|
+
"you are more worried about the launch than the roadmap suggests." Offer it as
|
|
118
|
+
something to test, never as a claim about them. If it is wrong, their body says
|
|
119
|
+
`unaligned`, and that is a useful answer rather than a failure.
|
|
120
|
+
|
|
121
|
+
The agent maintains three distinct insights. SomaCheck presents one at a time;
|
|
122
|
+
the other two remain cached. After a completed gesture, the next cached insight
|
|
123
|
+
is promoted and the open slot is marked for replenishment automatically. Cached insights
|
|
124
|
+
expire sooner than a presented check-in so stale context does not masquerade as
|
|
125
|
+
a timely observation.
|
|
126
|
+
|
|
127
|
+
For a prompt-free loop, run the agent on a background schedule: call
|
|
128
|
+
`get_vibecheck_context`, then `get_vibecheck_status`, and post exactly
|
|
129
|
+
`propositions_needed` distinct statements. MCP supplies the tools and durable
|
|
130
|
+
context, but it does not itself wake an idle model. Immediate event-driven runs
|
|
131
|
+
require a host webhook/agent trigger; scheduled runs provide the portable path
|
|
132
|
+
across local MCP clients.
|
|
133
|
+
|
|
134
|
+
## Privacy
|
|
135
|
+
|
|
136
|
+
<https://joinsensie.com/privacy>
|
|
137
|
+
|
|
138
|
+
## Support
|
|
139
|
+
|
|
140
|
+
Issues: see the repository. Contact: agents@joinsensie.com
|
package/dist/api.js
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { StatementPendingError, } from "./vibecheck.js";
|
|
2
|
+
import { BACKEND_PROTOCOL_VERSION, PACKAGE_VERSION, TOOLSET_VERSION } from "./constants.js";
|
|
3
|
+
const STATEMENT_PENDING_MARKER = "SC_VC_STATEMENT_PENDING";
|
|
4
|
+
const UPGRADE_REQUIRED_MARKER = "SC_VC_UPGRADE_REQUIRED";
|
|
5
|
+
const STATUSES = new Set(["queued", "pending", "answered", "expired", "cancelled"]);
|
|
6
|
+
const VERDICTS = new Set(["aligned", "unaligned"]);
|
|
7
|
+
const HANDSHAKE_READINESS_STATUSES = new Set([
|
|
8
|
+
"ready",
|
|
9
|
+
"upgrade_required",
|
|
10
|
+
"setup_incomplete",
|
|
11
|
+
"health_check_failed",
|
|
12
|
+
]);
|
|
13
|
+
export class SomaCheckHttpError extends Error {
|
|
14
|
+
status;
|
|
15
|
+
constructor(status) {
|
|
16
|
+
super(`SomaCheck request failed (HTTP ${status}).`);
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.name = "SomaCheckHttpError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class SomaCheckCompatibilityError extends Error {
|
|
22
|
+
requiredPackageVersion;
|
|
23
|
+
requiredProtocolVersion;
|
|
24
|
+
requiredToolsetVersion;
|
|
25
|
+
constructor(requiredPackageVersion, requiredProtocolVersion, requiredToolsetVersion) {
|
|
26
|
+
super("SomaCheck agent protocol is incompatible with this package.");
|
|
27
|
+
this.requiredPackageVersion = requiredPackageVersion;
|
|
28
|
+
this.requiredProtocolVersion = requiredProtocolVersion;
|
|
29
|
+
this.requiredToolsetVersion = requiredToolsetVersion;
|
|
30
|
+
this.name = "SomaCheckCompatibilityError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
3
33
|
function firstRow(value) {
|
|
4
34
|
const row = Array.isArray(value) ? value[0] : value;
|
|
5
35
|
if (row === null || typeof row !== "object" || Array.isArray(row)) {
|
|
@@ -7,6 +37,18 @@ function firstRow(value) {
|
|
|
7
37
|
}
|
|
8
38
|
return row;
|
|
9
39
|
}
|
|
40
|
+
function allRows(value) {
|
|
41
|
+
if (!Array.isArray(value) || value.some((row) => row === null || typeof row !== "object" || Array.isArray(row))) {
|
|
42
|
+
throw new Error("SomaCheck returned an unexpected response.");
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function asObject(value) {
|
|
47
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
48
|
+
throw new Error("SomaCheck returned an unexpected response.");
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
10
52
|
function requiredString(row, key) {
|
|
11
53
|
const value = row[key];
|
|
12
54
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -14,6 +56,101 @@ function requiredString(row, key) {
|
|
|
14
56
|
}
|
|
15
57
|
return value;
|
|
16
58
|
}
|
|
59
|
+
function requiredTimestamp(row, key) {
|
|
60
|
+
const value = requiredString(row, key);
|
|
61
|
+
if (!Number.isFinite(Date.parse(value))) {
|
|
62
|
+
throw new Error("SomaCheck returned an invalid timestamp.");
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
function nullableString(row, key) {
|
|
67
|
+
const value = row[key];
|
|
68
|
+
if (value === null || value === undefined)
|
|
69
|
+
return null;
|
|
70
|
+
if (typeof value !== "string")
|
|
71
|
+
throw new Error("SomaCheck returned an incomplete response.");
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function requiredBoolean(row, key) {
|
|
75
|
+
const value = row[key];
|
|
76
|
+
if (typeof value !== "boolean")
|
|
77
|
+
throw new Error("SomaCheck returned an incomplete response.");
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
function requiredNumber(row, key) {
|
|
81
|
+
const value = row[key];
|
|
82
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
83
|
+
throw new Error("SomaCheck returned an incomplete response.");
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
function requiredInteger(row, key) {
|
|
88
|
+
const value = requiredNumber(row, key);
|
|
89
|
+
if (!Number.isInteger(value))
|
|
90
|
+
throw new Error("SomaCheck returned an incomplete response.");
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
function parseCadence(value) {
|
|
94
|
+
const row = asObject(value);
|
|
95
|
+
return {
|
|
96
|
+
reason: requiredString(row, "reason"),
|
|
97
|
+
create_due_at: nullableString(row, "create_due_at"),
|
|
98
|
+
create_overdue: requiredBoolean(row, "create_overdue"),
|
|
99
|
+
may_create_now: requiredBoolean(row, "may_create_now"),
|
|
100
|
+
last_answered_at: nullableString(row, "last_answered_at"),
|
|
101
|
+
blocking_request_id: nullableString(row, "blocking_request_id"),
|
|
102
|
+
stale_after_seconds: requiredNumber(row, "stale_after_seconds"),
|
|
103
|
+
followup_within_seconds: requiredNumber(row, "followup_within_seconds"),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function parseStatus(row) {
|
|
107
|
+
const intro = asObject(row.first_run_intro);
|
|
108
|
+
const copy = asObject(row.copy_guidance);
|
|
109
|
+
return {
|
|
110
|
+
linked: true,
|
|
111
|
+
no_prior_requests: requiredBoolean(row, "no_prior_requests"),
|
|
112
|
+
prior_request_count: requiredInteger(row, "prior_request_count"),
|
|
113
|
+
has_pending_request: requiredBoolean(row, "has_pending_request"),
|
|
114
|
+
pending_proposition_count: requiredInteger(row, "pending_proposition_count"),
|
|
115
|
+
queued_proposition_count: requiredInteger(row, "queued_proposition_count"),
|
|
116
|
+
propositions_needed: requiredInteger(row, "propositions_needed"),
|
|
117
|
+
replenishment_requested_at: nullableString(row, "replenishment_requested_at"),
|
|
118
|
+
first_run_intro: {
|
|
119
|
+
eligible: requiredBoolean(intro, "eligible"),
|
|
120
|
+
should_offer_now: requiredBoolean(intro, "should_offer_now"),
|
|
121
|
+
},
|
|
122
|
+
cadence: parseCadence(row.cadence),
|
|
123
|
+
recommended_action: requiredString(row, "recommended_action"),
|
|
124
|
+
copy_guidance: {
|
|
125
|
+
policy: requiredString(copy, "policy"),
|
|
126
|
+
framing: requiredString(copy, "framing"),
|
|
127
|
+
fallback: requiredString(copy, "fallback"),
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function parseHandshake(row) {
|
|
132
|
+
const readinessStatus = requiredString(row, "readiness_status");
|
|
133
|
+
if (!HANDSHAKE_READINESS_STATUSES.has(readinessStatus)) {
|
|
134
|
+
throw new Error("SomaCheck returned an invalid readiness status.");
|
|
135
|
+
}
|
|
136
|
+
const result = {
|
|
137
|
+
readiness_status: readinessStatus,
|
|
138
|
+
upgrade_required: requiredBoolean(row, "upgrade_required"),
|
|
139
|
+
required_package_version: requiredString(row, "required_package_version"),
|
|
140
|
+
required_protocol_version: requiredInteger(row, "required_protocol_version"),
|
|
141
|
+
required_toolset_version: requiredString(row, "required_toolset_version"),
|
|
142
|
+
client_id: nullableString(row, "client_id"),
|
|
143
|
+
link_id: nullableString(row, "link_id"),
|
|
144
|
+
last_successful_health_check_at: nullableString(row, "last_successful_health_check_at"),
|
|
145
|
+
};
|
|
146
|
+
if (result.upgrade_required || result.readiness_status === "upgrade_required") {
|
|
147
|
+
throw new SomaCheckCompatibilityError(result.required_package_version, result.required_protocol_version, result.required_toolset_version);
|
|
148
|
+
}
|
|
149
|
+
if (result.readiness_status === "ready" && result.last_successful_health_check_at === null) {
|
|
150
|
+
throw new Error("SomaCheck returned an incomplete readiness response.");
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
17
154
|
export class SupabaseAgentApi {
|
|
18
155
|
#endpoint;
|
|
19
156
|
#apiKey;
|
|
@@ -27,12 +164,22 @@ export class SupabaseAgentApi {
|
|
|
27
164
|
const row = await this.#rpc("agent_link_redeem", { code });
|
|
28
165
|
return requiredString(row, "token");
|
|
29
166
|
}
|
|
30
|
-
async
|
|
31
|
-
const
|
|
32
|
-
return
|
|
167
|
+
async createRequests(token, statements) {
|
|
168
|
+
const rows = await this.#rpcRows("agent_proposition_batch_create", { token, statements });
|
|
169
|
+
return rows.map((row) => {
|
|
170
|
+
const presentationState = requiredString(row, "presentation_state");
|
|
171
|
+
if (presentationState !== "presented" && presentationState !== "queued") {
|
|
172
|
+
throw new Error("SomaCheck returned an incomplete response.");
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
request_id: requiredString(row, "request_id"),
|
|
176
|
+
presentation_state: presentationState,
|
|
177
|
+
stale_at: nullableString(row, "expires_at"),
|
|
178
|
+
};
|
|
179
|
+
});
|
|
33
180
|
}
|
|
34
181
|
async pollRequest(token, requestId) {
|
|
35
|
-
const row = await this.#rpc("
|
|
182
|
+
const row = await this.#rpc("agent_proposition_result", { token, proposition_id: requestId });
|
|
36
183
|
const status = row.status;
|
|
37
184
|
if (typeof status !== "string" || !STATUSES.has(status)) {
|
|
38
185
|
throw new Error("SomaCheck returned an invalid request status.");
|
|
@@ -43,12 +190,20 @@ export class SupabaseAgentApi {
|
|
|
43
190
|
if (verdict !== null && (typeof verdict !== "string" || !VERDICTS.has(verdict))) {
|
|
44
191
|
throw new Error("SomaCheck returned an invalid verdict.");
|
|
45
192
|
}
|
|
46
|
-
if (confidence !== null && typeof confidence !== "number") {
|
|
193
|
+
if (confidence !== null && (typeof confidence !== "number" || !Number.isFinite(confidence))) {
|
|
47
194
|
throw new Error("SomaCheck returned an invalid confidence.");
|
|
48
195
|
}
|
|
49
|
-
if (latency !== null && typeof latency !== "number") {
|
|
196
|
+
if (latency !== null && (typeof latency !== "number" || !Number.isFinite(latency) || latency < 0)) {
|
|
50
197
|
throw new Error("SomaCheck returned an invalid latency.");
|
|
51
198
|
}
|
|
199
|
+
if (status === "answered") {
|
|
200
|
+
if (verdict === null || confidence === null || confidence < 0 || confidence > 1 || latency === null) {
|
|
201
|
+
throw new Error("SomaCheck returned an incomplete answered result.");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else if (verdict !== null || confidence !== null || latency !== null) {
|
|
205
|
+
throw new Error("SomaCheck returned an inconsistent pending result.");
|
|
206
|
+
}
|
|
52
207
|
return {
|
|
53
208
|
status: status,
|
|
54
209
|
verdict: verdict,
|
|
@@ -56,7 +211,50 @@ export class SupabaseAgentApi {
|
|
|
56
211
|
latency_s: latency,
|
|
57
212
|
};
|
|
58
213
|
}
|
|
214
|
+
async statusRequest(token) {
|
|
215
|
+
return parseStatus(await this.#rpc("agent_proposition_cache_status", { token }));
|
|
216
|
+
}
|
|
217
|
+
async contextRequest(token, limit = 20) {
|
|
218
|
+
const rows = await this.#rpcRows("agent_proposition_context", { token, p_limit: limit });
|
|
219
|
+
return rows.map((row) => {
|
|
220
|
+
const verdict = requiredString(row, "verdict");
|
|
221
|
+
const confidence = requiredNumber(row, "confidence");
|
|
222
|
+
if (!VERDICTS.has(verdict)) {
|
|
223
|
+
throw new Error("SomaCheck returned an invalid verdict.");
|
|
224
|
+
}
|
|
225
|
+
if (confidence < 0 || confidence > 1) {
|
|
226
|
+
throw new Error("SomaCheck returned an invalid confidence.");
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
request_id: requiredString(row, "request_id"),
|
|
230
|
+
statement: requiredString(row, "statement"),
|
|
231
|
+
verdict: verdict,
|
|
232
|
+
confidence,
|
|
233
|
+
answered_at: requiredTimestamp(row, "answered_at"),
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async clientHandshake(token, input) {
|
|
238
|
+
const row = await this.#rpc("agent_client_handshake", {
|
|
239
|
+
token,
|
|
240
|
+
p_client_key: input.client_key,
|
|
241
|
+
p_client_label: input.client_label,
|
|
242
|
+
p_package_version: PACKAGE_VERSION,
|
|
243
|
+
p_protocol_version: BACKEND_PROTOCOL_VERSION,
|
|
244
|
+
p_toolset_version: TOOLSET_VERSION,
|
|
245
|
+
p_registration_exact: input.registration_exact,
|
|
246
|
+
p_status_probe_ok: input.status_probe_ok,
|
|
247
|
+
p_context_probe_ok: input.context_probe_ok,
|
|
248
|
+
});
|
|
249
|
+
return parseHandshake(row);
|
|
250
|
+
}
|
|
251
|
+
async #rpcRows(name, body) {
|
|
252
|
+
return allRows(await this.#rpcJson(name, body));
|
|
253
|
+
}
|
|
59
254
|
async #rpc(name, body) {
|
|
255
|
+
return firstRow(await this.#rpcJson(name, body));
|
|
256
|
+
}
|
|
257
|
+
async #rpcJson(name, body) {
|
|
60
258
|
const response = await this.#fetch(`${this.#endpoint}/rest/v1/rpc/${name}`, {
|
|
61
259
|
method: "POST",
|
|
62
260
|
headers: {
|
|
@@ -69,15 +267,21 @@ export class SupabaseAgentApi {
|
|
|
69
267
|
body: JSON.stringify(body),
|
|
70
268
|
});
|
|
71
269
|
if (!response.ok) {
|
|
72
|
-
|
|
270
|
+
const responseBody = await response.text().catch(() => "");
|
|
271
|
+
if (response.status === 426 || responseBody.includes(UPGRADE_REQUIRED_MARKER)) {
|
|
272
|
+
throw new SomaCheckCompatibilityError(null, null, null);
|
|
273
|
+
}
|
|
274
|
+
if (response.status === 409 || responseBody.includes(STATEMENT_PENDING_MARKER)) {
|
|
275
|
+
throw new StatementPendingError();
|
|
276
|
+
}
|
|
277
|
+
throw new SomaCheckHttpError(response.status);
|
|
73
278
|
}
|
|
74
279
|
try {
|
|
75
|
-
return
|
|
280
|
+
return await response.json();
|
|
76
281
|
}
|
|
77
282
|
catch (error) {
|
|
78
|
-
if (error instanceof Error && error.message.startsWith("SomaCheck returned"))
|
|
283
|
+
if (error instanceof Error && error.message.startsWith("SomaCheck returned"))
|
|
79
284
|
throw error;
|
|
80
|
-
}
|
|
81
285
|
throw new Error("SomaCheck returned an unreadable response.");
|
|
82
286
|
}
|
|
83
287
|
}
|