@somacheck/vibecheck 0.4.0 → 0.6.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 +44 -15
- package/dist/api.js +164 -1
- package/dist/cli.js +3 -0
- package/dist/client-setup.js +19 -3
- package/dist/constants.js +5 -3
- package/dist/readiness.js +4 -3
- package/dist/server.js +197 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,13 +9,13 @@ Your agent has your context. It does not have your gut. This is the bridge.
|
|
|
9
9
|
|
|
10
10
|
Fair. Here is what you need, without executing anything.
|
|
11
11
|
|
|
12
|
-
**What the `link` command does.** `npx -y @somacheck/vibecheck@0.
|
|
12
|
+
**What the `link` command does.** `npx -y @somacheck/vibecheck@0.6.0 link <CODE> --client <CLIENT>`
|
|
13
13
|
exchanges a short-lived pairing code, generated inside the SomaCheck iOS app, for
|
|
14
14
|
a bearer token. It writes that token to `~/.sensie/config.json` with mode `0600`,
|
|
15
15
|
offers to configure supported agent clients found on the computer, and checks the
|
|
16
16
|
connection without printing the token. Setup is not reported ready until the
|
|
17
17
|
selected client has the exact pinned `vibecheck` MCP registration, the backend
|
|
18
|
-
accepts protocol `
|
|
18
|
+
accepts protocol `5` / toolset `vibecheck-0.6`, and both status and context
|
|
19
19
|
probes succeed. It contacts exactly one network host: the Supabase project below.
|
|
20
20
|
|
|
21
21
|
**It will refuse unsafe sandbox linking, on purpose.** `link` checks safety
|
|
@@ -32,13 +32,19 @@ and that its persistent MCP config can be inspected and written. If that preflig
|
|
|
32
32
|
fails, the pairing code is not used. Non-interactive implicit selection,
|
|
33
33
|
`--client all`, and `--client none` are rejected before redemption.
|
|
34
34
|
|
|
35
|
-
**What the server does once running.** It exposes MCP tools that
|
|
36
|
-
statement for the user to test,
|
|
37
|
-
gesture context, and keep a
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
**What the server does once running.** It exposes MCP tools that share bounded,
|
|
36
|
+
user-authorized context summaries, create a statement for the user to test,
|
|
37
|
+
read its outcome, recover recent completed gesture context, and keep a
|
|
38
|
+
three-statement feed stocked. Version 0.6 adds `request_vibecheck` for a
|
|
39
|
+
person-approved, immediate phone check-in. That path is deliberately separate
|
|
40
|
+
from the reflection feed.
|
|
41
|
+
|
|
42
|
+
**What is sent.** A statement string, bounded derived context observations the
|
|
43
|
+
agent explicitly shares, and a bearer token identifying the link. Tool
|
|
44
|
+
instructions forbid raw conversations, photos, identifiers, and diagnostic
|
|
45
|
+
claims; the backend enforces the bounded capsule shape and rejects
|
|
46
|
+
secret-shaped content. **What is never sent: raw sensor
|
|
47
|
+
data.** The agent receives a verdict
|
|
42
48
|
(`aligned` / `unaligned`) plus a confidence percentage and latency. Model
|
|
43
49
|
abstention is a failed capture, never a third user-facing outcome.
|
|
44
50
|
Accelerometer and gyroscope streams never leave the user's device through this
|
|
@@ -72,9 +78,9 @@ before the pairing code is redeemed.
|
|
|
72
78
|
To configure or repair a client later:
|
|
73
79
|
|
|
74
80
|
```text
|
|
75
|
-
npx -y @somacheck/vibecheck@0.
|
|
76
|
-
npx -y @somacheck/vibecheck@0.
|
|
77
|
-
npx -y @somacheck/vibecheck@0.
|
|
81
|
+
npx -y @somacheck/vibecheck@0.6.0 setup codex
|
|
82
|
+
npx -y @somacheck/vibecheck@0.6.0 setup claude
|
|
83
|
+
npx -y @somacheck/vibecheck@0.6.0 doctor
|
|
78
84
|
```
|
|
79
85
|
|
|
80
86
|
Manual registration remains available:
|
|
@@ -84,13 +90,13 @@ Manual registration remains available:
|
|
|
84
90
|
"mcpServers": {
|
|
85
91
|
"vibecheck": {
|
|
86
92
|
"command": "npx",
|
|
87
|
-
"args": ["-y", "@somacheck/vibecheck@0.
|
|
93
|
+
"args": ["-y", "@somacheck/vibecheck@0.6.0", "serve", "--client", "codex"]
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
97
|
```
|
|
92
98
|
|
|
93
|
-
For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.
|
|
99
|
+
For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.6.0 serve --client claude`
|
|
94
100
|
|
|
95
101
|
The link step writes only the bearer token in `~/.sensie/config.json`. The client
|
|
96
102
|
setup step asks Codex or Claude to add the pinned MCP command to that client's own
|
|
@@ -98,7 +104,7 @@ configuration; it never copies the bearer token there. The interactive command
|
|
|
98
104
|
completes both steps. Most clients only pick up a new server after restart.
|
|
99
105
|
|
|
100
106
|
If `doctor` reports another MCP server named `somacheck`, that is the legacy
|
|
101
|
-
hosted connector and is
|
|
107
|
+
hosted connector and is separate from this local package. The setup command
|
|
102
108
|
will explain it and leave it alone. The canonical local MCP key is `vibecheck`.
|
|
103
109
|
|
|
104
110
|
## Tools
|
|
@@ -107,8 +113,10 @@ will explain it and leave it alone. The canonical local MCP key is `vibecheck`.
|
|
|
107
113
|
|---|---|
|
|
108
114
|
| `get_vibecheck_context` | Read recent completed gestures for this exact agent link without retaining every `request_id`. |
|
|
109
115
|
| `get_vibecheck_status` | Read the live three-proposition cache and how many distinct insights it needs. |
|
|
116
|
+
| `share_somacheck_context` | Share 1–20 bounded, derived, user-authorized observations; raw conversations and secrets are rejected. |
|
|
110
117
|
| `post_vibecheck_statement` | Add one to three personalized statements without blocking; returns a stable `request_id` for each. |
|
|
111
118
|
| `get_vibecheck_result` | Read that exact proposition once by `request_id`; returns queued, pending, or its terminal result. |
|
|
119
|
+
| `request_vibecheck` | After the person accepts the exact statement, send one immediate phone vibecheck with a retry-safe UUID and return an opaque `live:<uuid>` handle. |
|
|
112
120
|
|
|
113
121
|
## How to use it well
|
|
114
122
|
|
|
@@ -118,6 +126,27 @@ prompt is **a statement for the person to test**, not a question about facts:
|
|
|
118
126
|
something to test, never as a claim about them. If it is wrong, their body says
|
|
119
127
|
`unaligned`, and that is a useful answer rather than a failure.
|
|
120
128
|
|
|
129
|
+
For a Live Ask, first show the exact statement and invite the person plainly:
|
|
130
|
+
|
|
131
|
+
> Want a quick vibecheck before I continue? I'll send this statement to your
|
|
132
|
+
> phone; you answer with your wrist gesture, and I'll use the result as a
|
|
133
|
+
> signal—not a verdict.
|
|
134
|
+
|
|
135
|
+
Call `request_vibecheck` only after they accept, with `human_confirmed: true`
|
|
136
|
+
and a new UUID in `idempotency_key`. Reuse that UUID only when retrying the
|
|
137
|
+
same statement. The returned `live:<uuid>` handle can be passed to
|
|
138
|
+
`get_vibecheck_result` later. Each read is non-blocking; do not continuously
|
|
139
|
+
poll, and do not re-ask the same decision to obtain a preferred result.
|
|
140
|
+
|
|
141
|
+
After a completed result, explain what the signal changes in your next action.
|
|
142
|
+
Never treat it as authorization or say that the person's body made the
|
|
143
|
+
decision. A typed response from the person always overrides the signal.
|
|
144
|
+
|
|
145
|
+
An immediate vibecheck is not proposition caching. It sends one person-approved statement
|
|
146
|
+
for the current choice, expires, and remains bound to its originating client.
|
|
147
|
+
The reflection feed remains asynchronous and keeps its existing three-item
|
|
148
|
+
cache behavior.
|
|
149
|
+
|
|
121
150
|
The agent maintains three distinct insights. SomaCheck presents one at a time;
|
|
122
151
|
the other two remain cached. After a completed gesture, the next cached insight
|
|
123
152
|
is promoted and the open slot is marked for replenishment automatically. Cached insights
|
package/dist/api.js
CHANGED
|
@@ -2,6 +2,10 @@ import { StatementPendingError, } from "./vibecheck.js";
|
|
|
2
2
|
import { BACKEND_PROTOCOL_VERSION, PACKAGE_VERSION, TOOLSET_VERSION } from "./constants.js";
|
|
3
3
|
const STATEMENT_PENDING_MARKER = "SC_VC_STATEMENT_PENDING";
|
|
4
4
|
const UPGRADE_REQUIRED_MARKER = "SC_VC_UPGRADE_REQUIRED";
|
|
5
|
+
const LIVE_ASK_CONFLICT_MARKER = "SC_VC_VIBECHECK_IDEMPOTENCY_CONFLICT";
|
|
6
|
+
const LIVE_ASK_PENDING_MARKER = "SC_VC_VIBECHECK_PENDING";
|
|
7
|
+
const LIVE_ASK_CLIENT_REQUIRED_MARKER = "SC_VC_VIBECHECK_CLIENT_REQUIRED";
|
|
8
|
+
const LIVE_ASK_CLIENT_NOT_READY_MARKER = "SC_VC_VIBECHECK_CLIENT_NOT_READY";
|
|
5
9
|
const STATUSES = new Set(["queued", "pending", "answered", "expired", "cancelled"]);
|
|
6
10
|
const VERDICTS = new Set(["aligned", "unaligned"]);
|
|
7
11
|
const HANDSHAKE_READINESS_STATUSES = new Set([
|
|
@@ -10,6 +14,19 @@ const HANDSHAKE_READINESS_STATUSES = new Set([
|
|
|
10
14
|
"setup_incomplete",
|
|
11
15
|
"health_check_failed",
|
|
12
16
|
]);
|
|
17
|
+
const PUSH_STATUSES = new Set([
|
|
18
|
+
"queued", "processing", "sent", "failed", "skipped",
|
|
19
|
+
]);
|
|
20
|
+
const CONTEXT_CATEGORY = {
|
|
21
|
+
goal: "goals_projects", project: "goals_projects",
|
|
22
|
+
decision: "decisions_commitments", commitment: "decisions_commitments",
|
|
23
|
+
tension: "tensions_questions_blind_spots", question: "tensions_questions_blind_spots",
|
|
24
|
+
blindSpot: "tensions_questions_blind_spots",
|
|
25
|
+
relationship: "relationships_situational", situation: "relationships_situational",
|
|
26
|
+
change: "changes_deadlines_developments", deadline: "changes_deadlines_developments",
|
|
27
|
+
development: "changes_deadlines_developments", moodTheme: "emotional_mood_themes",
|
|
28
|
+
preference: "corrections_dismissals_saves_preferences",
|
|
29
|
+
};
|
|
13
30
|
export class SomaCheckHttpError extends Error {
|
|
14
31
|
status;
|
|
15
32
|
constructor(status) {
|
|
@@ -30,6 +47,24 @@ export class SomaCheckCompatibilityError extends Error {
|
|
|
30
47
|
this.name = "SomaCheckCompatibilityError";
|
|
31
48
|
}
|
|
32
49
|
}
|
|
50
|
+
export class SomaCheckLiveAskConflictError extends Error {
|
|
51
|
+
constructor() {
|
|
52
|
+
super("That Live Ask retry key is already bound to a different statement.");
|
|
53
|
+
this.name = "SomaCheckLiveAskConflictError";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export class SomaCheckLiveAskPendingError extends Error {
|
|
57
|
+
constructor() {
|
|
58
|
+
super("A vibecheck is already waiting for this connection.");
|
|
59
|
+
this.name = "SomaCheckLiveAskPendingError";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export class SomaCheckLiveAskClientError extends Error {
|
|
63
|
+
constructor() {
|
|
64
|
+
super("Live Ask requires an exact ready client connection.");
|
|
65
|
+
this.name = "SomaCheckLiveAskClientError";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
33
68
|
function firstRow(value) {
|
|
34
69
|
const row = Array.isArray(value) ? value[0] : value;
|
|
35
70
|
if (row === null || typeof row !== "object" || Array.isArray(row)) {
|
|
@@ -145,6 +180,39 @@ export function decodeCreatedRequests(value) {
|
|
|
145
180
|
};
|
|
146
181
|
});
|
|
147
182
|
}
|
|
183
|
+
/** Strict decoder for agent_vibecheck_create. PostgREST wraps the
|
|
184
|
+
* set-returning function in an array. The backend intentionally uses its
|
|
185
|
+
* canonical `answered` lifecycle name; the provider-neutral adapter below
|
|
186
|
+
* maps that to the tool's clearer `completed` state. */
|
|
187
|
+
export function decodeLiveVibecheckCreation(value) {
|
|
188
|
+
const row = firstRow(value);
|
|
189
|
+
const status = requiredString(row, "status");
|
|
190
|
+
if (status !== "pending" && status !== "answered" && status !== "expired" && status !== "cancelled") {
|
|
191
|
+
throw new Error("SomaCheck returned an invalid vibecheck status.");
|
|
192
|
+
}
|
|
193
|
+
if (requiredString(row, "request_kind") !== "live_ask") {
|
|
194
|
+
throw new Error("SomaCheck returned an invalid vibecheck request kind.");
|
|
195
|
+
}
|
|
196
|
+
const pushStatus = nullableString(row, "push_status");
|
|
197
|
+
if (pushStatus !== null && !PUSH_STATUSES.has(pushStatus)) {
|
|
198
|
+
throw new Error("SomaCheck returned an invalid vibecheck delivery state.");
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
request_id: requiredString(row, "request_id"),
|
|
202
|
+
status,
|
|
203
|
+
expires_at: requiredTimestamp(row, "expires_at"),
|
|
204
|
+
idempotent_replay: requiredBoolean(row, "idempotent_replay"),
|
|
205
|
+
push_status: pushStatus,
|
|
206
|
+
cooldown_until: nullableTimestamp(row, "cooldown_until"),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function nullableTimestamp(row, key) {
|
|
210
|
+
const value = nullableString(row, key);
|
|
211
|
+
if (value !== null && !Number.isFinite(Date.parse(value))) {
|
|
212
|
+
throw new Error("SomaCheck returned an invalid timestamp.");
|
|
213
|
+
}
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
148
216
|
export function decodePollResponse(value) {
|
|
149
217
|
const row = asObject(value);
|
|
150
218
|
const status = row.status;
|
|
@@ -178,6 +246,21 @@ export function decodePollResponse(value) {
|
|
|
178
246
|
latency_s: latency,
|
|
179
247
|
};
|
|
180
248
|
}
|
|
249
|
+
export function decodeLiveVibecheckResult(value, expectedRequestId) {
|
|
250
|
+
const row = firstRow(value);
|
|
251
|
+
if (requiredString(row, "request_id") !== expectedRequestId) {
|
|
252
|
+
throw new Error("SomaCheck returned a mismatched vibecheck result.");
|
|
253
|
+
}
|
|
254
|
+
if (requiredString(row, "request_kind") !== "live_ask") {
|
|
255
|
+
throw new Error("SomaCheck returned an invalid vibecheck request kind.");
|
|
256
|
+
}
|
|
257
|
+
const result = decodePollResponse(row);
|
|
258
|
+
if (result.status === "queued") {
|
|
259
|
+
throw new Error("SomaCheck returned an invalid vibecheck status.");
|
|
260
|
+
}
|
|
261
|
+
requiredTimestamp(row, "expires_at");
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
181
264
|
export function decodeVibecheckContext(value) {
|
|
182
265
|
return allRows(value).map((row) => {
|
|
183
266
|
const verdict = requiredString(row, "verdict");
|
|
@@ -220,6 +303,24 @@ function parseHandshake(row) {
|
|
|
220
303
|
}
|
|
221
304
|
return result;
|
|
222
305
|
}
|
|
306
|
+
export function decodeAgentContextShare(value) {
|
|
307
|
+
const row = firstRow(value);
|
|
308
|
+
const state = requiredString(row, "state");
|
|
309
|
+
if (state !== "ready")
|
|
310
|
+
throw new Error("SomaCheck returned an invalid context state.");
|
|
311
|
+
const observationCount = requiredInteger(row, "observation_count");
|
|
312
|
+
if (observationCount < 1 || observationCount > 20) {
|
|
313
|
+
throw new Error("SomaCheck returned an invalid observation count.");
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
source_id: requiredString(row, "source_id"),
|
|
317
|
+
entry_id: requiredString(row, "entry_id"),
|
|
318
|
+
accepted: requiredBoolean(row, "accepted"),
|
|
319
|
+
state,
|
|
320
|
+
observation_count: observationCount,
|
|
321
|
+
captured_at: requiredTimestamp(row, "captured_at"),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
223
324
|
export class SupabaseAgentApi {
|
|
224
325
|
#endpoint;
|
|
225
326
|
#apiKey;
|
|
@@ -233,20 +334,72 @@ export class SupabaseAgentApi {
|
|
|
233
334
|
const row = await this.#rpc("agent_link_redeem", { code });
|
|
234
335
|
return requiredString(row, "token");
|
|
235
336
|
}
|
|
337
|
+
async requestVibecheck(token, identity, statement, idempotencyKey) {
|
|
338
|
+
if (identity.kind !== "local")
|
|
339
|
+
throw new SomaCheckLiveAskClientError();
|
|
340
|
+
const clientKey = identity.client_key;
|
|
341
|
+
const created = decodeLiveVibecheckCreation(await this.#rpcJson("agent_vibecheck_create", {
|
|
342
|
+
token,
|
|
343
|
+
statement,
|
|
344
|
+
idempotency_key: idempotencyKey,
|
|
345
|
+
client_key: clientKey,
|
|
346
|
+
}));
|
|
347
|
+
const lifecycle = created.status === "answered"
|
|
348
|
+
? await this.liveVibecheckResult(token, identity, created.request_id)
|
|
349
|
+
: { status: created.status, verdict: null, confidence: null, latency_s: null };
|
|
350
|
+
return {
|
|
351
|
+
request_id: `live:${created.request_id}`,
|
|
352
|
+
state: lifecycle.status === "answered" ? "completed" : lifecycle.status,
|
|
353
|
+
verdict: lifecycle.verdict,
|
|
354
|
+
confidence: lifecycle.confidence,
|
|
355
|
+
expires_at: created.expires_at,
|
|
356
|
+
idempotent_replay: created.idempotent_replay,
|
|
357
|
+
delivery_state: created.push_status,
|
|
358
|
+
cooldown_until: created.cooldown_until,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
236
361
|
async createRequests(token, statements) {
|
|
237
362
|
return decodeCreatedRequests(await this.#rpcJson("agent_proposition_batch_create", { token, statements }));
|
|
238
363
|
}
|
|
239
364
|
async pollRequest(token, requestId) {
|
|
240
365
|
return decodePollResponse(await this.#rpc("agent_proposition_result", { token, proposition_id: requestId }));
|
|
241
366
|
}
|
|
367
|
+
async liveVibecheckResult(token, identity, requestId) {
|
|
368
|
+
if (identity.kind !== "local")
|
|
369
|
+
throw new SomaCheckLiveAskClientError();
|
|
370
|
+
return decodeLiveVibecheckResult(await this.#rpcJson("agent_vibecheck_result", {
|
|
371
|
+
token,
|
|
372
|
+
live_ask_id: requestId,
|
|
373
|
+
client_key: identity.client_key,
|
|
374
|
+
}), requestId);
|
|
375
|
+
}
|
|
242
376
|
async statusRequest(token) {
|
|
243
377
|
return decodeVibecheckStatus(await this.#rpc("agent_proposition_cache_status", { token }));
|
|
244
378
|
}
|
|
245
379
|
async contextRequest(token, limit = 20) {
|
|
246
380
|
return decodeVibecheckContext(await this.#rpcJson("agent_proposition_context", { token, p_limit: limit }));
|
|
247
381
|
}
|
|
382
|
+
async shareContext(token, observations) {
|
|
383
|
+
const capturedAt = new Date().toISOString();
|
|
384
|
+
const payload = {};
|
|
385
|
+
for (const observation of observations) {
|
|
386
|
+
const category = CONTEXT_CATEGORY[observation.type];
|
|
387
|
+
(payload[category] ??= []).push({
|
|
388
|
+
type: observation.type,
|
|
389
|
+
summary: observation.summary.trim(),
|
|
390
|
+
confidence: observation.confidence,
|
|
391
|
+
evidence_count: observation.evidence_count,
|
|
392
|
+
captured_at: capturedAt,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
return decodeAgentContextShare(await this.#rpcJson("agent_context_capsule_share", {
|
|
396
|
+
token,
|
|
397
|
+
p_payload: payload,
|
|
398
|
+
p_confidence: observations.reduce((sum, item) => sum + item.confidence, 0) / observations.length,
|
|
399
|
+
}));
|
|
400
|
+
}
|
|
248
401
|
async clientHandshake(token, input) {
|
|
249
|
-
const row = await this.#rpc("
|
|
402
|
+
const row = await this.#rpc("agent_client_handshake_v2", {
|
|
250
403
|
token,
|
|
251
404
|
p_client_key: input.client_key,
|
|
252
405
|
p_client_label: input.client_label,
|
|
@@ -282,6 +435,16 @@ export class SupabaseAgentApi {
|
|
|
282
435
|
if (response.status === 426 || responseBody.includes(UPGRADE_REQUIRED_MARKER)) {
|
|
283
436
|
throw new SomaCheckCompatibilityError(null, null, null);
|
|
284
437
|
}
|
|
438
|
+
if (response.status === 409 && responseBody.includes(LIVE_ASK_CONFLICT_MARKER)) {
|
|
439
|
+
throw new SomaCheckLiveAskConflictError();
|
|
440
|
+
}
|
|
441
|
+
if (response.status === 409 && responseBody.includes(LIVE_ASK_PENDING_MARKER)) {
|
|
442
|
+
throw new SomaCheckLiveAskPendingError();
|
|
443
|
+
}
|
|
444
|
+
if (responseBody.includes(LIVE_ASK_CLIENT_REQUIRED_MARKER)
|
|
445
|
+
|| responseBody.includes(LIVE_ASK_CLIENT_NOT_READY_MARKER)) {
|
|
446
|
+
throw new SomaCheckLiveAskClientError();
|
|
447
|
+
}
|
|
285
448
|
if (response.status === 409 || responseBody.includes(STATEMENT_PENDING_MARKER)) {
|
|
286
449
|
throw new StatementPendingError();
|
|
287
450
|
}
|
package/dist/cli.js
CHANGED
|
@@ -182,6 +182,9 @@ async function startServer(runtimeClient) {
|
|
|
182
182
|
const server = createVibecheckServer({
|
|
183
183
|
api,
|
|
184
184
|
loadToken: async () => (await readConfig(homedir(), runtimeClient ?? undefined)).token,
|
|
185
|
+
identity: runtimeClient === null
|
|
186
|
+
? { kind: "legacy" }
|
|
187
|
+
: { kind: "local", client_key: runtimeClient },
|
|
185
188
|
});
|
|
186
189
|
await server.connect(new StdioServerTransport());
|
|
187
190
|
if (runtimeClient !== null) {
|
package/dist/client-setup.js
CHANGED
|
@@ -123,9 +123,25 @@ function isManagedSomaCheckRegistration(client, stdout) {
|
|
|
123
123
|
if (command !== "npx")
|
|
124
124
|
return false;
|
|
125
125
|
const packageArgs = args.filter((arg) => arg.startsWith("@somacheck/vibecheck@"));
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
if (packageArgs.length !== 1)
|
|
127
|
+
return false;
|
|
128
|
+
const packageArg = packageArgs[0];
|
|
129
|
+
const legacyWithoutClient = /^@somacheck\/vibecheck@0\.[0-3]\.\d+$/.test(packageArg)
|
|
130
|
+
&& args.every((arg) => arg === "-y" || arg === "--yes" || arg === packageArg);
|
|
131
|
+
if (legacyWithoutClient)
|
|
132
|
+
return true;
|
|
133
|
+
// 0.5 introduced the exact local runtime identity required by Live Ask.
|
|
134
|
+
// It is safe to replace only the canonical command generated by that
|
|
135
|
+
// release. Custom wrappers, extra arguments, disabled entries, and a
|
|
136
|
+
// registration naming the other client remain user-owned.
|
|
137
|
+
const previousExactClient = /^@somacheck\/vibecheck@0\.5\.\d+$/.test(packageArg)
|
|
138
|
+
&& args.length === 5
|
|
139
|
+
&& args[0] === "-y"
|
|
140
|
+
&& args[1] === packageArg
|
|
141
|
+
&& args[2] === "serve"
|
|
142
|
+
&& args[3] === "--client"
|
|
143
|
+
&& args[4] === client;
|
|
144
|
+
return previousExactClient;
|
|
129
145
|
}
|
|
130
146
|
export async function isClientRegistered(client, runner) {
|
|
131
147
|
return (await clientRegistrationState(client, runner)) === "current";
|
package/dist/constants.js
CHANGED
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
export const SUPABASE_URL = "https://pbldcmniommltbdwuykk.supabase.co";
|
|
3
3
|
export const SUPABASE_PUBLISHABLE_KEY = "sb_publishable_af-lUNI2FqEcb-oGy-4uxQ_cnm6kY85";
|
|
4
4
|
export const PACKAGE_NAME = "@somacheck/vibecheck";
|
|
5
|
-
export const PACKAGE_VERSION = "0.
|
|
5
|
+
export const PACKAGE_VERSION = "0.6.0";
|
|
6
6
|
export const PACKAGE_SPEC = `${PACKAGE_NAME}@${PACKAGE_VERSION}`;
|
|
7
7
|
export const MCP_SERVER_NAME = "vibecheck";
|
|
8
8
|
export const LEGACY_HOSTED_MCP_SERVER_NAME = "somacheck";
|
|
9
|
-
export const BACKEND_PROTOCOL_VERSION =
|
|
10
|
-
export const TOOLSET_VERSION = "vibecheck-0.
|
|
9
|
+
export const BACKEND_PROTOCOL_VERSION = 5;
|
|
10
|
+
export const TOOLSET_VERSION = "vibecheck-0.6";
|
|
11
11
|
export const TOOL_NAMES = [
|
|
12
12
|
"get_vibecheck_context",
|
|
13
13
|
"get_vibecheck_status",
|
|
14
|
+
"share_somacheck_context",
|
|
14
15
|
"post_vibecheck_statement",
|
|
15
16
|
"get_vibecheck_result",
|
|
17
|
+
"request_vibecheck",
|
|
16
18
|
];
|
|
17
19
|
//# sourceMappingURL=constants.js.map
|
package/dist/readiness.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readConfig } from "./config.js";
|
|
2
2
|
import { SomaCheckCompatibilityError, SomaCheckHttpError } from "./api.js";
|
|
3
3
|
import { clientDisplayName, clientRegistrationState, detectLegacyHostedRegistration, isClientInstalled, manualLegacyHostedRemoveCommand, manualRemoveCommand, manualSetupCommand, } from "./client-setup.js";
|
|
4
|
+
import { BACKEND_PROTOCOL_VERSION, PACKAGE_VERSION } from "./constants.js";
|
|
4
5
|
export async function checkReadiness(dependencies) {
|
|
5
6
|
let token;
|
|
6
7
|
try {
|
|
@@ -62,7 +63,7 @@ export async function checkReadiness(dependencies) {
|
|
|
62
63
|
? " It currently reports: Needs authentication."
|
|
63
64
|
: "";
|
|
64
65
|
dependencies.output(`○ ${clientDisplayName(client)} also has a separate legacy server named "somacheck".${authNote}`);
|
|
65
|
-
dependencies.output(
|
|
66
|
+
dependencies.output(` SomaCheck MCP ${PACKAGE_VERSION} uses "vibecheck"; the legacy entry was not changed.`);
|
|
66
67
|
dependencies.output(` Optional manual cleanup: ${manualLegacyHostedRemoveCommand(client)}`);
|
|
67
68
|
}
|
|
68
69
|
const registration = await clientRegistrationState(client, dependencies.runner);
|
|
@@ -81,7 +82,7 @@ export async function checkReadiness(dependencies) {
|
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
if (registeredClients.length === 0) {
|
|
84
|
-
dependencies.output(
|
|
85
|
+
dependencies.output(`✗ No supported agent client has the current SomaCheck ${PACKAGE_VERSION} configuration.`);
|
|
85
86
|
if (installedClientCount === 0) {
|
|
86
87
|
dependencies.output(" Install Codex or Claude Code, then run the matching setup command.");
|
|
87
88
|
}
|
|
@@ -101,7 +102,7 @@ export async function checkReadiness(dependencies) {
|
|
|
101
102
|
if (handshake.readiness_status === "ready") {
|
|
102
103
|
backendCompatible = true;
|
|
103
104
|
readyClients.push(client);
|
|
104
|
-
dependencies.output(`✓ ${clientDisplayName(client)} completed the SomaCheck protocol
|
|
105
|
+
dependencies.output(`✓ ${clientDisplayName(client)} completed the SomaCheck protocol-${BACKEND_PROTOCOL_VERSION} health check.`);
|
|
105
106
|
}
|
|
106
107
|
else {
|
|
107
108
|
dependencies.output(`✗ ${clientDisplayName(client)} is not ready yet (${handshake.readiness_status}).`);
|
package/dist/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { StatementPendingError } from "./vibecheck.js";
|
|
4
|
-
import { SomaCheckCompatibilityError, SomaCheckHttpError } from "./api.js";
|
|
3
|
+
import { StatementPendingError, } from "./vibecheck.js";
|
|
4
|
+
import { SomaCheckCompatibilityError, SomaCheckHttpError, SomaCheckLiveAskClientError, SomaCheckLiveAskConflictError, SomaCheckLiveAskPendingError, } from "./api.js";
|
|
5
5
|
import { PACKAGE_NAME, PACKAGE_SPEC, PACKAGE_VERSION } from "./constants.js";
|
|
6
6
|
const cadenceSchema = z.object({
|
|
7
7
|
reason: z.string(),
|
|
@@ -48,10 +48,57 @@ const contextItemSchema = z.object({
|
|
|
48
48
|
answered_at: z.string().datetime({ offset: true }),
|
|
49
49
|
});
|
|
50
50
|
const contextSchema = { checkins: z.array(contextItemSchema) };
|
|
51
|
+
const liveAskErrorCodeSchema = z.enum([
|
|
52
|
+
"link_revoked",
|
|
53
|
+
"upgrade_required",
|
|
54
|
+
"setup_required",
|
|
55
|
+
"request_conflict",
|
|
56
|
+
"vibecheck_pending",
|
|
57
|
+
"rate_limited",
|
|
58
|
+
"live_ask_unavailable",
|
|
59
|
+
"backend_unavailable",
|
|
60
|
+
"connection_failed",
|
|
61
|
+
]);
|
|
62
|
+
const liveAskSchema = {
|
|
63
|
+
state: z.enum(["pending", "completed", "expired", "cancelled", "error"]),
|
|
64
|
+
request_id: z.string().nullable(),
|
|
65
|
+
verdict: z.enum(["aligned", "unaligned"]).nullable(),
|
|
66
|
+
confidence: z.number().min(0).max(1).nullable(),
|
|
67
|
+
expires_at: z.string().datetime({ offset: true }).nullable(),
|
|
68
|
+
idempotent_replay: z.boolean().nullable(),
|
|
69
|
+
delivery_state: z.enum(["queued", "processing", "sent", "failed", "skipped"]).nullable(),
|
|
70
|
+
cooldown_until: z.string().datetime({ offset: true }).nullable(),
|
|
71
|
+
error_code: liveAskErrorCodeSchema.nullable(),
|
|
72
|
+
};
|
|
73
|
+
const agentContextTypeSchema = z.enum([
|
|
74
|
+
"goal", "project", "decision", "commitment", "tension", "question",
|
|
75
|
+
"blindSpot", "relationship", "situation", "change", "deadline",
|
|
76
|
+
"development", "moodTheme", "preference",
|
|
77
|
+
]);
|
|
78
|
+
const agentContextObservationSchema = z.object({
|
|
79
|
+
type: agentContextTypeSchema,
|
|
80
|
+
summary: z.string().trim().min(1).max(500)
|
|
81
|
+
.describe("A concise derived theme or fact the person authorized you to share. Never paste raw conversations."),
|
|
82
|
+
confidence: z.number().min(0).max(1).default(0.8),
|
|
83
|
+
evidence_count: z.number().int().min(1).max(100).default(1),
|
|
84
|
+
});
|
|
85
|
+
const contextShareSchema = {
|
|
86
|
+
source_id: z.string(), entry_id: z.string(), accepted: z.boolean(),
|
|
87
|
+
state: z.literal("ready"), observation_count: z.number().int().min(1).max(20),
|
|
88
|
+
captured_at: z.string().datetime({ offset: true }),
|
|
89
|
+
};
|
|
51
90
|
const SERVER_INSTRUCTIONS = [
|
|
52
91
|
"SomaCheck lets you offer the person a statement to test through a quick phone check-in.",
|
|
53
92
|
"Call get_vibecheck_context and get_vibecheck_status at the start of a session or background run, and call status again after a completed check-in.",
|
|
54
93
|
"Treat gesture outcomes as contextual signals, never fixed facts or blanket authorization.",
|
|
94
|
+
"Use request_vibecheck when a current choice would benefit from the person's own felt response; never use it to evaluate someone else.",
|
|
95
|
+
"First show the exact statement and ask: Want a quick vibecheck before I continue? I'll send this statement to your phone; you answer with your wrist gesture, and I'll use the result as a signal—not a verdict. Call only after the person accepts.",
|
|
96
|
+
"Use at most one vibecheck per decision. Never re-ask to obtain a preferred answer.",
|
|
97
|
+
"An immediate vibecheck is separate from the asynchronous reflection feed. Do not use it to replenish cached propositions.",
|
|
98
|
+
"Use a fresh idempotency_key for each new vibecheck, and reuse that exact key only when retrying the same statement.",
|
|
99
|
+
"After a completed result, say what the signal changes in your next action. Never say 'your body decided'; a typed human response always overrides the signal.",
|
|
100
|
+
"When you have meaningful user-authorized context, call share_somacheck_context with concise derived observations before creating propositions.",
|
|
101
|
+
"Never share raw conversation text, photos, credentials, private identifiers, or diagnostic claims.",
|
|
55
102
|
"Maintain three distinct propositions drawn from your own context about this person.",
|
|
56
103
|
"When propositions_needed is greater than zero, post exactly that many in one call.",
|
|
57
104
|
"Offer each statement as something to test, never as a claim of fact about the user.",
|
|
@@ -111,6 +158,32 @@ export function createVibecheckServer(dependencies) {
|
|
|
111
158
|
return failureMessage("read SomaCheck status", error);
|
|
112
159
|
}
|
|
113
160
|
});
|
|
161
|
+
server.registerTool("share_somacheck_context", {
|
|
162
|
+
title: "Share SomaCheck Context",
|
|
163
|
+
description: "Share 1-20 concise, user-authorized context observations so SomaCheck can prepare richer propositions. Send derived summaries only—never raw conversation text, photos, credentials, identifiers, or diagnostic claims.",
|
|
164
|
+
inputSchema: {
|
|
165
|
+
observations: z.array(agentContextObservationSchema).min(1).max(20),
|
|
166
|
+
},
|
|
167
|
+
outputSchema: contextShareSchema,
|
|
168
|
+
annotations: {
|
|
169
|
+
readOnlyHint: false,
|
|
170
|
+
destructiveHint: false,
|
|
171
|
+
idempotentHint: true,
|
|
172
|
+
openWorldHint: false,
|
|
173
|
+
},
|
|
174
|
+
}, async ({ observations }) => {
|
|
175
|
+
try {
|
|
176
|
+
const token = await dependencies.loadToken();
|
|
177
|
+
const result = await dependencies.api.shareContext(token, observations);
|
|
178
|
+
return {
|
|
179
|
+
content: [{ type: "text", text: `Shared ${result.observation_count} bounded context observation${result.observation_count === 1 ? "" : "s"}. SomaCheck can now prepare contextual propositions.` }],
|
|
180
|
+
structuredContent: { ...result },
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
return failureMessage("share context with SomaCheck", error);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
114
187
|
server.registerTool("post_vibecheck_statement", {
|
|
115
188
|
title: "Post Vibecheck Statement",
|
|
116
189
|
description: "Fill the person's SomaCheck cache with one to three personalized statements and return immediately. Call get_vibecheck_status first and submit exactly propositions_needed statements.",
|
|
@@ -144,9 +217,9 @@ export function createVibecheckServer(dependencies) {
|
|
|
144
217
|
});
|
|
145
218
|
server.registerTool("get_vibecheck_result", {
|
|
146
219
|
title: "Get Vibecheck Result",
|
|
147
|
-
description: "Read one proposition by request_id. This is a single non-blocking read: queued is cached, and pending is
|
|
220
|
+
description: "Read one exact proposition or immediate vibecheck by request_id. This is a single non-blocking read: queued is cached, and pending is not answered. Immediate vibecheck handles begin with live: and remain bound to the originating client.",
|
|
148
221
|
inputSchema: {
|
|
149
|
-
request_id: z.string().min(1).describe("The request_id returned by post_vibecheck_statement."),
|
|
222
|
+
request_id: z.string().min(1).describe("The opaque request_id returned by post_vibecheck_statement or request_vibecheck."),
|
|
150
223
|
},
|
|
151
224
|
outputSchema: resultSchema,
|
|
152
225
|
annotations: {
|
|
@@ -158,10 +231,19 @@ export function createVibecheckServer(dependencies) {
|
|
|
158
231
|
}, async ({ request_id }) => {
|
|
159
232
|
try {
|
|
160
233
|
const token = await dependencies.loadToken();
|
|
161
|
-
const
|
|
234
|
+
const liveRequestId = parseLiveVibecheckHandle(request_id);
|
|
235
|
+
const identity = dependencies.identity ?? { kind: "legacy" };
|
|
236
|
+
if (liveRequestId !== null && identity.kind === "legacy") {
|
|
237
|
+
return failure("Could not read that vibecheck: Live Ask needs the exact ready client connection that created it. Run setup and restart the client.");
|
|
238
|
+
}
|
|
239
|
+
const result = liveRequestId === null
|
|
240
|
+
? await dependencies.api.pollRequest(token, request_id)
|
|
241
|
+
: await dependencies.api.liveVibecheckResult(token, identity, liveRequestId);
|
|
162
242
|
const structuredContent = { request_id, ...result };
|
|
163
243
|
const text = result.status === "answered"
|
|
164
|
-
?
|
|
244
|
+
? liveRequestId === null
|
|
245
|
+
? `Answered: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}.`
|
|
246
|
+
: `Vibecheck completed: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}. Treat this as a signal, say what it changes in your next action, and defer to any typed response from the person.`
|
|
165
247
|
: result.status === "queued"
|
|
166
248
|
? "Queued. The proposition is cached until the person advances their feed."
|
|
167
249
|
: result.status === "pending"
|
|
@@ -173,6 +255,46 @@ export function createVibecheckServer(dependencies) {
|
|
|
173
255
|
return failureMessage("read that SomaCheck result", error);
|
|
174
256
|
}
|
|
175
257
|
});
|
|
258
|
+
server.registerTool("request_vibecheck", {
|
|
259
|
+
title: "Request a Vibecheck",
|
|
260
|
+
description: "Send one plain-language statement to the person's phone for a vibecheck answered with a wrist gesture. First show the exact statement and call only after the person accepts. Use this only when their own felt response would help with the current choice, not for routine feed content or to judge another person. Returns a stable request_id immediately; call get_vibecheck_result later once, without continuous polling.",
|
|
261
|
+
inputSchema: {
|
|
262
|
+
statement: z.string().trim().min(1).max(1000)
|
|
263
|
+
.describe("One plain-language statement for the person to test; offer a signal, not a fact, diagnosis, or instruction."),
|
|
264
|
+
idempotency_key: z.string().uuid()
|
|
265
|
+
.describe("A new UUID for this logical ask. Reuse the same UUID only to retry the exact same statement; retries will not create another phone request."),
|
|
266
|
+
human_confirmed: z.literal(true)
|
|
267
|
+
.describe("Confirms the agent showed the exact statement and the person accepted this one vibecheck."),
|
|
268
|
+
},
|
|
269
|
+
outputSchema: liveAskSchema,
|
|
270
|
+
annotations: {
|
|
271
|
+
readOnlyHint: false,
|
|
272
|
+
destructiveHint: false,
|
|
273
|
+
idempotentHint: true,
|
|
274
|
+
openWorldHint: false,
|
|
275
|
+
},
|
|
276
|
+
}, async ({ statement, idempotency_key }) => {
|
|
277
|
+
try {
|
|
278
|
+
const identity = dependencies.identity ?? { kind: "legacy" };
|
|
279
|
+
if (identity.kind === "legacy") {
|
|
280
|
+
return liveAskFailure(new SomaCheckLiveAskClientError());
|
|
281
|
+
}
|
|
282
|
+
const token = await dependencies.loadToken();
|
|
283
|
+
const result = await dependencies.api.requestVibecheck(token, identity, statement.trim(), idempotency_key);
|
|
284
|
+
const structuredContent = { ...result, error_code: null };
|
|
285
|
+
const text = result.state === "completed"
|
|
286
|
+
? `Vibecheck completed: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}. Treat this as a signal, say what it changes in your next action, and defer to any typed response from the person.`
|
|
287
|
+
: result.state === "pending"
|
|
288
|
+
? result.delivery_state === "failed" || result.delivery_state === "skipped"
|
|
289
|
+
? `Vibecheck ${result.request_id} was stored, but phone delivery is ${result.delivery_state}. It expires at ${result.expires_at}. Do not repost it or change the idempotency_key; check the phone setup before a later decision.`
|
|
290
|
+
: `Vibecheck requested. Keep request_id ${result.request_id} and check it later with get_vibecheck_result. It expires at ${result.expires_at}; do not poll continuously or re-ask for this decision.`
|
|
291
|
+
: `This vibecheck is ${result.state}. Do not create another ask for the same decision.`;
|
|
292
|
+
return { content: [{ type: "text", text }], structuredContent };
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
return liveAskFailure(error);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
176
298
|
return server;
|
|
177
299
|
}
|
|
178
300
|
function summarise(status) {
|
|
@@ -192,6 +314,16 @@ function formatDuration(seconds) {
|
|
|
192
314
|
const hours = seconds / 3600;
|
|
193
315
|
return Number.isInteger(hours) ? `${hours}-hour` : `${Math.round(seconds / 60)}-minute`;
|
|
194
316
|
}
|
|
317
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
318
|
+
function parseLiveVibecheckHandle(handle) {
|
|
319
|
+
if (!handle.startsWith("live:"))
|
|
320
|
+
return null;
|
|
321
|
+
const requestId = handle.slice("live:".length);
|
|
322
|
+
if (!UUID_PATTERN.test(requestId)) {
|
|
323
|
+
throw new Error("The Live Ask request_id is malformed.");
|
|
324
|
+
}
|
|
325
|
+
return requestId;
|
|
326
|
+
}
|
|
195
327
|
function failure(text) {
|
|
196
328
|
return {
|
|
197
329
|
isError: true,
|
|
@@ -201,6 +333,62 @@ function failure(text) {
|
|
|
201
333
|
function failureMessage(operation, error) {
|
|
202
334
|
return failure(operationalFailureText(operation, error));
|
|
203
335
|
}
|
|
336
|
+
function liveAskFailure(error) {
|
|
337
|
+
const doctor = `Run: npx -y ${PACKAGE_SPEC} doctor`;
|
|
338
|
+
let errorCode;
|
|
339
|
+
let text;
|
|
340
|
+
if (error instanceof SomaCheckCompatibilityError) {
|
|
341
|
+
errorCode = "upgrade_required";
|
|
342
|
+
text = `Could not request a vibecheck: this package needs an upgrade before immediate vibechecks can be used. ${doctor}`;
|
|
343
|
+
}
|
|
344
|
+
else if (error instanceof SomaCheckLiveAskClientError) {
|
|
345
|
+
errorCode = "setup_required";
|
|
346
|
+
text = `Could not request a vibecheck: immediate vibechecks need an exact ready agent connection. Run the pinned serve command through setup, then restart the client. ${doctor}`;
|
|
347
|
+
}
|
|
348
|
+
else if (error instanceof SomaCheckLiveAskConflictError) {
|
|
349
|
+
errorCode = "request_conflict";
|
|
350
|
+
text = "Could not request a vibecheck: that retry key already belongs to a different statement. Use the original statement, or use a new UUID for a genuinely new ask.";
|
|
351
|
+
}
|
|
352
|
+
else if (error instanceof SomaCheckLiveAskPendingError) {
|
|
353
|
+
errorCode = "vibecheck_pending";
|
|
354
|
+
text = "Could not request a vibecheck: one is already waiting for this connection. Keep the existing request; do not re-ask or change the retry key.";
|
|
355
|
+
}
|
|
356
|
+
else if (error instanceof SomaCheckHttpError && (error.status === 401 || error.status === 403)) {
|
|
357
|
+
errorCode = "link_revoked";
|
|
358
|
+
text = `Could not request a vibecheck: this SomaCheck link was rejected or revoked. Reconnect in the app. ${doctor}`;
|
|
359
|
+
}
|
|
360
|
+
else if (error instanceof SomaCheckHttpError && error.status === 404) {
|
|
361
|
+
errorCode = "live_ask_unavailable";
|
|
362
|
+
text = `Could not request a vibecheck: immediate vibechecks are not available on this SomaCheck backend yet. ${doctor}`;
|
|
363
|
+
}
|
|
364
|
+
else if (error instanceof SomaCheckHttpError && error.status === 429) {
|
|
365
|
+
errorCode = "rate_limited";
|
|
366
|
+
text = "Could not request a vibecheck: too many vibechecks were requested recently. Wait before making a new ask; do not change the retry key for the same ask.";
|
|
367
|
+
}
|
|
368
|
+
else if (error instanceof SomaCheckHttpError && error.status >= 500) {
|
|
369
|
+
errorCode = "backend_unavailable";
|
|
370
|
+
text = `Could not request a vibecheck: the SomaCheck backend is temporarily unavailable. Retry later with the same idempotency_key. ${doctor}`;
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
errorCode = "connection_failed";
|
|
374
|
+
text = `Could not request a vibecheck: the local link or network check failed. Retry later with the same idempotency_key. ${doctor}`;
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
isError: true,
|
|
378
|
+
content: [{ type: "text", text }],
|
|
379
|
+
structuredContent: {
|
|
380
|
+
state: "error",
|
|
381
|
+
request_id: null,
|
|
382
|
+
verdict: null,
|
|
383
|
+
confidence: null,
|
|
384
|
+
expires_at: null,
|
|
385
|
+
idempotent_replay: null,
|
|
386
|
+
delivery_state: null,
|
|
387
|
+
cooldown_until: null,
|
|
388
|
+
error_code: errorCode,
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
204
392
|
function operationalFailureText(operation, error) {
|
|
205
393
|
const doctor = `Run: npx -y ${PACKAGE_SPEC} doctor`;
|
|
206
394
|
if (error instanceof SomaCheckCompatibilityError) {
|
|
@@ -208,6 +396,9 @@ function operationalFailureText(operation, error) {
|
|
|
208
396
|
}
|
|
209
397
|
if (error instanceof SomaCheckHttpError) {
|
|
210
398
|
if (error.status === 401 || error.status === 403) {
|
|
399
|
+
if (operation === "share context with SomaCheck") {
|
|
400
|
+
return `Could not ${operation}: context is not enabled for this link, or the link was rejected or revoked. Open SomaCheck → Connect context, then retry. ${doctor}`;
|
|
401
|
+
}
|
|
211
402
|
return `Could not ${operation}: this SomaCheck link was rejected or revoked. Reconnect in the app. ${doctor}`;
|
|
212
403
|
}
|
|
213
404
|
if (error.status === 404) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@somacheck/vibecheck",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Send a consented vibecheck to SomaCheck and use the result as a signal.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/sensie-app/Somacheck.git",
|