@somacheck/vibecheck 0.6.5 → 0.6.6
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 +21 -46
- package/SKILL.md +10 -142
- package/claude-marketplace/.claude-plugin/marketplace.json +1 -1
- package/claude-marketplace/plugins/vibecheck/.claude-plugin/plugin.json +1 -1
- package/claude-marketplace/plugins/vibecheck/hooks/hooks.json +1 -1
- package/dist/api.js +19 -2
- package/dist/claude-hook.js +19 -4
- package/dist/client-setup.js +2 -1
- package/dist/constants.js +1 -1
- package/dist/server.js +27 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ 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.6.
|
|
12
|
+
**What the `link` command does.** `npx -y @somacheck/vibecheck@0.6.6 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
|
|
@@ -81,9 +81,9 @@ before the pairing code is redeemed.
|
|
|
81
81
|
To configure or repair a client later:
|
|
82
82
|
|
|
83
83
|
```text
|
|
84
|
-
npx -y @somacheck/vibecheck@0.6.
|
|
85
|
-
npx -y @somacheck/vibecheck@0.6.
|
|
86
|
-
npx -y @somacheck/vibecheck@0.6.
|
|
84
|
+
npx -y @somacheck/vibecheck@0.6.6 setup codex
|
|
85
|
+
npx -y @somacheck/vibecheck@0.6.6 setup claude
|
|
86
|
+
npx -y @somacheck/vibecheck@0.6.6 doctor
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
Manual registration remains available:
|
|
@@ -93,13 +93,13 @@ Manual registration remains available:
|
|
|
93
93
|
"mcpServers": {
|
|
94
94
|
"vibecheck": {
|
|
95
95
|
"command": "npx",
|
|
96
|
-
"args": ["-y", "@somacheck/vibecheck@0.6.
|
|
96
|
+
"args": ["-y", "@somacheck/vibecheck@0.6.6", "serve", "--client", "codex"]
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.6.
|
|
102
|
+
For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.6.6 serve --client claude`
|
|
103
103
|
|
|
104
104
|
The link step writes only the bearer token in `~/.sensie/config.json`. The client
|
|
105
105
|
setup step asks Codex or Claude to add the pinned MCP command to that client's own
|
|
@@ -110,15 +110,15 @@ If `doctor` reports another MCP server named `somacheck`, that is the legacy
|
|
|
110
110
|
hosted connector and is separate from this local package. The setup command
|
|
111
111
|
will explain it and leave it alone. The canonical local MCP key is `vibecheck`.
|
|
112
112
|
|
|
113
|
-
## Teach your agent when to
|
|
113
|
+
## Teach your agent when to use SomaCheck
|
|
114
114
|
|
|
115
|
-
[`SKILL.md`](SKILL.md) teaches compatible agents
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
[`SKILL.md`](SKILL.md) teaches compatible agents one simple affordance:
|
|
116
|
+
SomaCheck lets them ask their person for a vibecheck. The agent can offer one
|
|
117
|
+
when it believes the read would help, or send one when the person asks. The
|
|
118
|
+
skill keeps only the essential boundaries: ask the person, choose a useful
|
|
119
|
+
first-person statement from available context, treat the returned gesture and
|
|
120
|
+
optional feedback as context rather than authorization, and never include
|
|
121
|
+
secrets or assess anyone else.
|
|
122
122
|
|
|
123
123
|
For Claude Code, add this repository as a marketplace and install the skill in
|
|
124
124
|
one terminal command:
|
|
@@ -156,31 +156,11 @@ The skill expects the `@somacheck/vibecheck` MCP server to already be linked as
|
|
|
156
156
|
|
|
157
157
|
## How to use it well
|
|
158
158
|
|
|
159
|
-
Ask when
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
Setup and linking are not permission for future asks. If the person's current
|
|
166
|
-
message explicitly asks for a SomaCheck vibecheck or check-in, that is consent for one ask.
|
|
167
|
-
Use their exact statement when supplied. If they delegated the choice, select
|
|
168
|
-
the most useful first-person statement from the current conversation, goal,
|
|
169
|
-
decision, or tension and send it immediately—do not list candidates, ask them
|
|
170
|
-
for wording, or seek a second confirmation. Remembered context may identify a general tension, but an
|
|
171
|
-
agent-selected statement must not name a project, build, branch, wave, ticket,
|
|
172
|
-
task status, or supposedly open/closed work. Abstract it into a present-tense
|
|
173
|
-
first-person experience or choice, such as “I want to keep consolidating what is
|
|
174
|
-
already in motion rather than start something new.” Preserve exact wording the
|
|
175
|
-
person supplied. If meaningful context is unavailable, use the neutral fallback
|
|
176
|
-
“I feel clear about what deserves my attention right now.”
|
|
177
|
-
|
|
178
|
-
If their message did not explicitly request a vibecheck, first show the exact
|
|
179
|
-
statement and invite the person plainly:
|
|
180
|
-
|
|
181
|
-
> Want a quick vibecheck before I continue? I'll send this statement to your
|
|
182
|
-
> phone; you answer with your wrist gesture, and I'll use the result as a
|
|
183
|
-
> signal—not a verdict.
|
|
159
|
+
Ask when the person's felt response would be useful context. A good prompt is a
|
|
160
|
+
first-person statement for the person to test, not a claim about them. When the
|
|
161
|
+
person asks for a vibecheck, choose and send a useful statement from the context
|
|
162
|
+
available to you. For proactive offers, get the person's acceptance before
|
|
163
|
+
sending.
|
|
184
164
|
|
|
185
165
|
Call `request_vibecheck` with `consent_basis: "user_requested_vibecheck"` for a
|
|
186
166
|
direct request or `consent_basis: "user_approved_statement"` after approval of
|
|
@@ -191,8 +171,7 @@ On timeout or a result-read failure it returns the same pending `live:<uuid>`
|
|
|
191
171
|
handle; pass that handle to `get_vibecheck_result` once later. Caller
|
|
192
172
|
cancellation stops result reads and preserves the backend request and
|
|
193
173
|
idempotency key, but the cancelled MCP transport may suppress that pending
|
|
194
|
-
response.
|
|
195
|
-
preferred result.
|
|
174
|
+
response.
|
|
196
175
|
|
|
197
176
|
Standard MCP cannot start a new agent turn after a tool call has ended. The
|
|
198
177
|
Claude setup therefore installs a normal user-scoped plugin with an async
|
|
@@ -209,11 +188,7 @@ Claude-specific continuation must never be presented as provider-neutral.
|
|
|
209
188
|
|
|
210
189
|
The 10-second whole-call deadline also covers the one create request. If that
|
|
211
190
|
network request is aborted after an ambiguous commit, retry the exact same
|
|
212
|
-
statement with the same `idempotency_key
|
|
213
|
-
|
|
214
|
-
After a completed result, use it as context and respond naturally in the
|
|
215
|
-
current conversation. The result is not authorization and does not establish a
|
|
216
|
-
diagnosis or certainty. Never say that the person's body made the decision.
|
|
191
|
+
statement with the same `idempotency_key`.
|
|
217
192
|
|
|
218
193
|
An immediate vibecheck is not proposition caching. It sends one person-approved statement
|
|
219
194
|
for the current choice, expires, and remains bound to its originating client.
|
package/SKILL.md
CHANGED
|
@@ -1,157 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vibecheck
|
|
3
|
-
description:
|
|
3
|
+
description: Ask your person for a SomaCheck vibecheck when it would help.
|
|
4
4
|
user-invocable: true
|
|
5
5
|
allowed-tools: mcp__vibecheck__get_vibecheck_context mcp__vibecheck__get_vibecheck_status mcp__vibecheck__get_vibecheck_result mcp__vibecheck__request_vibecheck
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Vibecheck
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
SomaCheck lets you ask your person for a vibecheck.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
2. **You notice one of the three trigger events below.** Offer one exact statement and wait for acceptance before sending it.
|
|
12
|
+
Use `request_vibecheck` in either case:
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
1. You proactively offer one because you believe it would be useful.
|
|
15
|
+
2. The person asks for a vibecheck.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
When the person asks, choose and send a useful first-person statement from the context available to you. Do not make them supply wording unless they want to. Help the person get an insight about themselves from the context you have.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
For proactive offers, get the person's acceptance before sending.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
- Otherwise choose one present-tense, first-person statement about a current experience, choice, or tension.
|
|
23
|
-
- First use reliable context already available to the host: the current conversation, current goal, and user-approved memory or project context when those capabilities exist. Do not ask the person to restate context you can retrieve.
|
|
24
|
-
- Treat remembered project status, task lists, and summaries as potentially stale. They may identify a general tension, but the statement must not name a project, build, branch, wave, ticket, task status, or supposedly open or closed work.
|
|
25
|
-
- Corroborate remembered status against the current conversation. Do not reuse a stock template or recent SomaCheck statement. If context remains thin, create an original exploratory statement that can reveal something useful without pretending to know the person.
|
|
26
|
-
- Generate one fresh UUID for `idempotency_key`. Reuse it only to retry the same exact statement.
|
|
27
|
-
- Do not list candidates, present a draft, ask what they want to test, or ask "Want me to send it?" The request already supplied one-ask consent.
|
|
21
|
+
The returned gesture and optional feedback are context, not authorization. Use your judgment in how you interpret and respond.
|
|
28
22
|
|
|
29
|
-
|
|
23
|
+
Never include secrets, raw private content, diagnostic claims, or use SomaCheck to assess anyone other than the person you are interacting with.
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
### Trigger 1: Drift
|
|
34
|
-
|
|
35
|
-
Offer when at least one of these transcript-grounded conditions is true:
|
|
36
|
-
|
|
37
|
-
- Transcript timestamps show 90 minutes or more on the same task or thread without reaching the stated outcome.
|
|
38
|
-
- The same or materially equivalent fix has been attempted three or more times.
|
|
39
|
-
- The current work no longer advances the human's stated goal, and the human has not explicitly changed that goal.
|
|
40
|
-
|
|
41
|
-
Stay quiet during productive deep work, after an explicit goal change, or when duration cannot be established. A long message by itself is not drift.
|
|
42
|
-
|
|
43
|
-
### Trigger 2: Major or irreversible decision ahead
|
|
44
|
-
|
|
45
|
-
Offer immediately before a consequential action when the transcript shows that action is imminent. Examples include deploying to production, sending an important email or offer, signing an agreement, deleting data, making a material purchase, hiring or firing, or saying "just do it" about a consequential step.
|
|
46
|
-
|
|
47
|
-
Planning or discussing a possible decision is not enough. The action must be close enough that a pause now could still affect it. This trigger may bypass a decline cooldown from triggers 1 or 3, but it still must pass the live status gate.
|
|
48
|
-
|
|
49
|
-
### Trigger 3: Stress signals in messages
|
|
50
|
-
|
|
51
|
-
Offer when the recent messages contain either two or more signals in a short span, or one strong pattern repeated across retries:
|
|
52
|
-
|
|
53
|
-
- Cursing about the task or situation.
|
|
54
|
-
- Fragmented, rushed, or unusually incoherent instructions.
|
|
55
|
-
- All-caps urgency that is not an acronym, log, or quoted text.
|
|
56
|
-
- Repeated retry commands with no meaningful change in approach.
|
|
57
|
-
- Phrases such as "whatever, just make it work."
|
|
58
|
-
|
|
59
|
-
Describe only the observable message pattern. Do not claim to know how the person feels. One casual swear, terse normal writing, pasted logs, or quoted all-caps text is not enough.
|
|
60
|
-
|
|
61
|
-
### Multiple triggers
|
|
62
|
-
|
|
63
|
-
Choose one primary trigger per event. Use trigger 2 first when an imminent consequential action exists, then trigger 3, then trigger 1. Do not stack or repeat offers.
|
|
64
|
-
|
|
65
|
-
## Proactive offer sequence
|
|
66
|
-
|
|
67
|
-
1. If no trigger fired, do nothing.
|
|
68
|
-
2. Check the local event and cooldown record. If this event was already offered, stay quiet. If a trigger 1 or 3 offer was declined within the last two hours, stay quiet. A new trigger 2 event may bypass that cooldown.
|
|
69
|
-
3. Call `get_vibecheck_status` before showing an offer.
|
|
70
|
-
4. Stay quiet when `has_pending_request` is true. Reflection-feed cadence and capacity, including `cadence.may_create_now` and `propositions_needed`, do not authorize or block a live vibecheck.
|
|
71
|
-
5. Make one short offer containing one statement to test. Do not post yet.
|
|
72
|
-
6. If the human declines or does not accept, record the event as offered. For a decline, start a two-hour cooldown for triggers 1 and 3.
|
|
73
|
-
7. If the human accepts, call `request_vibecheck` once with the exact offered statement, a fresh UUID in `idempotency_key`, and `consent_basis: "user_approved_statement"`.
|
|
74
|
-
8. If the call must be retried, preserve both the exact statement and the same idempotency key.
|
|
75
|
-
|
|
76
|
-
`get_vibecheck_context` is optional before phrasing an offer. Use it only to avoid repeating a recent statement. Do not call `share_somacheck_context` or `post_vibecheck_statement` in this workflow. Live vibechecks and the asynchronous reflection feed are separate product behaviors.
|
|
77
|
-
|
|
78
|
-
## How to phrase the offer
|
|
79
|
-
|
|
80
|
-
Use a statement to test, never a claim about the person. Keep it first-person, present-tense, neutral, and focused on one decision or next step. Do not include raw transcript text, names, secrets, identifiers, customer data, or private details.
|
|
81
|
-
|
|
82
|
-
Good offer forms:
|
|
83
|
-
|
|
84
|
-
- "Before you ship, want to test: 'I am ready to deploy this to production'?"
|
|
85
|
-
- "Quick 3-second check: 'I trust this decision'?"
|
|
86
|
-
- "Before another retry, want to test: 'This next attempt is meaningfully different from the last one'?"
|
|
87
|
-
|
|
88
|
-
Do not say that the person is distracted, stressed, dysregulated, afraid, or certain. Do not use medical language. Do not say "vibecheck confirms" or "vibecheck proves." Do not use an em dash in any human-facing string.
|
|
89
|
-
|
|
90
|
-
### Drift statement templates
|
|
91
|
-
|
|
92
|
-
1. "I am still working on the right problem."
|
|
93
|
-
2. "This next attempt is meaningfully different from the last one."
|
|
94
|
-
3. "I know what outcome I am trying to reach."
|
|
95
|
-
4. "Continuing this task now is the best use of my attention."
|
|
96
|
-
5. "I am ready to pause and restate the goal."
|
|
97
|
-
6. "The current approach is moving me closer to done."
|
|
98
|
-
7. "I have enough context to keep going."
|
|
99
|
-
8. "I want to keep working on this thread."
|
|
100
|
-
9. "A short reset would help me choose the next step."
|
|
101
|
-
|
|
102
|
-
### Major or irreversible decision statement templates
|
|
103
|
-
|
|
104
|
-
1. "I am ready to deploy this to production."
|
|
105
|
-
2. "I trust this decision."
|
|
106
|
-
3. "I am ready to send this message as written."
|
|
107
|
-
4. "I understand the consequences of this action."
|
|
108
|
-
5. "I have the information I need to commit."
|
|
109
|
-
6. "This purchase is the right choice right now."
|
|
110
|
-
7. "I am ready to make this offer."
|
|
111
|
-
8. "I am comfortable making this change irreversible."
|
|
112
|
-
9. "I want to act on this now."
|
|
113
|
-
|
|
114
|
-
### Stress-signal statement templates
|
|
115
|
-
|
|
116
|
-
1. "I want to keep pushing on this right now."
|
|
117
|
-
2. "I trust the next step I am about to take."
|
|
118
|
-
3. "I have enough clarity to choose the next step."
|
|
119
|
-
4. "I am ready to slow down for one minute."
|
|
120
|
-
5. "This approach still feels right to me."
|
|
121
|
-
6. "I want to send this message in this tone."
|
|
122
|
-
7. "I am comfortable continuing without another review."
|
|
123
|
-
8. "I know what I need from this task."
|
|
124
|
-
9. "I want to make this decision now."
|
|
125
|
-
|
|
126
|
-
Adapt a template only enough to make it relevant. Keep the statement free of sensitive specifics.
|
|
127
|
-
|
|
128
|
-
## Cooldown rules
|
|
129
|
-
|
|
130
|
-
- One offer per trigger event, whether accepted, declined, or ignored.
|
|
131
|
-
- After a decline, suppress trigger 1 and trigger 3 offers for two hours.
|
|
132
|
-
- A new imminent trigger 2 event may bypass that two-hour cooldown.
|
|
133
|
-
- The one-pending gate always wins. A completed, expired, or cancelled request has no server cooldown; a new direct user request may be sent immediately.
|
|
134
|
-
- A continued conversation about the same task or decision is not a new event. A materially new goal, a different consequential action, or a new message-pattern episode can be.
|
|
135
|
-
|
|
136
|
-
## What to do with the result
|
|
137
|
-
|
|
138
|
-
`request_vibecheck` waits boundedly for the exact result. If it returns `completed`, use the result as context and respond naturally in the current conversation. The result is not authorization and does not establish a diagnosis or certainty. If the tool returns `pending`, retain the exact `live:<uuid>` handle. Do not promise that you will check back automatically unless the current host supports automatic result delivery. Use `get_vibecheck_result` once when the person asks or at a natural follow-up. Never create a replacement request and never poll in a tight loop.
|
|
139
|
-
|
|
140
|
-
For `expired` or `cancelled`, do not infer an answer and do not immediately repost.
|
|
141
|
-
|
|
142
|
-
A result never grants permission to deploy, send, sign, delete, spend, hire, fire, publish, or take any other consequential action.
|
|
143
|
-
|
|
144
|
-
## Do not
|
|
145
|
-
|
|
146
|
-
- Do not offer without one of the three transcript-grounded triggers.
|
|
147
|
-
- Do not ignore or reconfirm an explicit request for one vibecheck.
|
|
148
|
-
- Do not claim to know the person's internal state.
|
|
149
|
-
- Do not nag, stack offers, or re-offer for the same event.
|
|
150
|
-
- Do not proactively offer when a request is pending.
|
|
151
|
-
- Do not send an unsolicited offer before the human accepts the exact statement.
|
|
152
|
-
- Do not use `post_vibecheck_statement` for a live ask.
|
|
153
|
-
- Do not send raw conversation text, secrets, identifiers, or private details to the server.
|
|
154
|
-
- Do not call `share_somacheck_context` in this workflow.
|
|
155
|
-
- Do not treat any outcome as fact, diagnosis, proof, or authorization.
|
|
156
|
-
- Do not continuously poll for a result.
|
|
157
|
-
- Do not use medical language or an em dash in a human-facing string.
|
|
25
|
+
If a request is pending, keep the returned `live:<uuid>` handle instead of creating a replacement.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "vibecheck",
|
|
11
11
|
"source": "./plugins/vibecheck",
|
|
12
12
|
"description": "Let Claude offer a vibecheck and continue when the phone result arrives.",
|
|
13
|
-
"version": "0.6.
|
|
13
|
+
"version": "0.6.6",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Sensie",
|
|
16
16
|
"email": "agents@joinsensie.com"
|
package/dist/api.js
CHANGED
|
@@ -8,6 +8,7 @@ const LIVE_ASK_CLIENT_REQUIRED_MARKER = "SC_VC_VIBECHECK_CLIENT_REQUIRED";
|
|
|
8
8
|
const LIVE_ASK_CLIENT_NOT_READY_MARKER = "SC_VC_VIBECHECK_CLIENT_NOT_READY";
|
|
9
9
|
const STATUSES = new Set(["queued", "pending", "answered", "expired", "cancelled"]);
|
|
10
10
|
const VERDICTS = new Set(["aligned", "unaligned"]);
|
|
11
|
+
const USER_FEEDBACKS = new Set(["agreed", "disagreed", "not_provided"]);
|
|
11
12
|
const HANDSHAKE_READINESS_STATUSES = new Set([
|
|
12
13
|
"ready",
|
|
13
14
|
"upgrade_required",
|
|
@@ -222,6 +223,7 @@ export function decodePollResponse(value) {
|
|
|
222
223
|
const verdict = row.verdict;
|
|
223
224
|
const confidence = row.confidence;
|
|
224
225
|
const latency = row.latency_s;
|
|
226
|
+
const userFeedback = row.user_feedback ?? null;
|
|
225
227
|
if (verdict !== null && (typeof verdict !== "string" || !VERDICTS.has(verdict))) {
|
|
226
228
|
throw new Error("SomaCheck returned an invalid verdict.");
|
|
227
229
|
}
|
|
@@ -231,6 +233,9 @@ export function decodePollResponse(value) {
|
|
|
231
233
|
if (latency !== null && (typeof latency !== "number" || !Number.isFinite(latency) || latency < 0)) {
|
|
232
234
|
throw new Error("SomaCheck returned an invalid latency.");
|
|
233
235
|
}
|
|
236
|
+
if (userFeedback !== null && (typeof userFeedback !== "string" || !USER_FEEDBACKS.has(userFeedback))) {
|
|
237
|
+
throw new Error("SomaCheck returned invalid user feedback.");
|
|
238
|
+
}
|
|
234
239
|
if (status === "answered") {
|
|
235
240
|
if (verdict === null || confidence === null || confidence < 0 || confidence > 1 || latency === null) {
|
|
236
241
|
throw new Error("SomaCheck returned an incomplete answered result.");
|
|
@@ -239,12 +244,21 @@ export function decodePollResponse(value) {
|
|
|
239
244
|
else if (verdict !== null || confidence !== null || latency !== null) {
|
|
240
245
|
throw new Error("SomaCheck returned an inconsistent pending result.");
|
|
241
246
|
}
|
|
242
|
-
|
|
247
|
+
if (status !== "answered" && userFeedback !== null) {
|
|
248
|
+
throw new Error("SomaCheck returned inconsistent feedback for an unanswered result.");
|
|
249
|
+
}
|
|
250
|
+
const decoded = {
|
|
243
251
|
status: status,
|
|
244
252
|
verdict: verdict,
|
|
245
253
|
confidence: confidence,
|
|
246
254
|
latency_s: latency,
|
|
247
255
|
};
|
|
256
|
+
if (userFeedback === null)
|
|
257
|
+
return decoded;
|
|
258
|
+
return {
|
|
259
|
+
...decoded,
|
|
260
|
+
user_feedback: userFeedback,
|
|
261
|
+
};
|
|
248
262
|
}
|
|
249
263
|
export function decodeLiveVibecheckResult(value, expectedRequestId) {
|
|
250
264
|
const row = firstRow(value);
|
|
@@ -347,7 +361,7 @@ export class SupabaseAgentApi {
|
|
|
347
361
|
const lifecycle = created.status === "answered"
|
|
348
362
|
? await this.liveVibecheckResult(token, identity, created.request_id, signal)
|
|
349
363
|
: { status: created.status, verdict: null, confidence: null, latency_s: null };
|
|
350
|
-
|
|
364
|
+
const liveRequest = {
|
|
351
365
|
request_id: `live:${created.request_id}`,
|
|
352
366
|
state: lifecycle.status === "answered" ? "completed" : lifecycle.status,
|
|
353
367
|
verdict: lifecycle.verdict,
|
|
@@ -357,6 +371,9 @@ export class SupabaseAgentApi {
|
|
|
357
371
|
delivery_state: created.push_status,
|
|
358
372
|
cooldown_until: created.cooldown_until,
|
|
359
373
|
};
|
|
374
|
+
if (lifecycle.user_feedback === undefined || lifecycle.user_feedback === null)
|
|
375
|
+
return liveRequest;
|
|
376
|
+
return { ...liveRequest, user_feedback: lifecycle.user_feedback };
|
|
360
377
|
}
|
|
361
378
|
async createRequests(token, statements) {
|
|
362
379
|
return decodeCreatedRequests(await this.#rpcJson("agent_proposition_batch_create", { token, statements }));
|
package/dist/claude-hook.js
CHANGED
|
@@ -17,8 +17,8 @@ export function parsePendingClaudeAsk(input) {
|
|
|
17
17
|
if (typeof envelope.tool_name !== "string"
|
|
18
18
|
|| !envelope.tool_name.endsWith("__request_vibecheck"))
|
|
19
19
|
return null;
|
|
20
|
-
const response =
|
|
21
|
-
const structured = record(response.structuredContent ?? response.structured_content);
|
|
20
|
+
const response = parseToolResponse(envelope.tool_response);
|
|
21
|
+
const structured = record(response.structuredContent ?? response.structured_content ?? response);
|
|
22
22
|
if (structured.state !== "pending" || typeof structured.request_id !== "string")
|
|
23
23
|
return null;
|
|
24
24
|
if (structured.delivery_state === "failed" || structured.delivery_state === "skipped")
|
|
@@ -78,8 +78,8 @@ export async function awaitClaudeVibecheckResult(input, dependencies) {
|
|
|
78
78
|
continue;
|
|
79
79
|
const handle = `live:${pending.requestId}`;
|
|
80
80
|
const message = result.status === "answered"
|
|
81
|
-
? `SomaCheck
|
|
82
|
-
: `SomaCheck
|
|
81
|
+
? `SomaCheck vibecheck ${handle} completed: ${result.verdict}, confidence ${formatConfidence(result.confidence)}${formatUserFeedback(result.user_feedback)}.`
|
|
82
|
+
: `SomaCheck vibecheck ${handle} is ${result.status}.`;
|
|
83
83
|
await writeTerminalReceipt(donePath, result);
|
|
84
84
|
return { exitCode: 2, message };
|
|
85
85
|
}
|
|
@@ -122,9 +122,21 @@ async function writeTerminalReceipt(path, result) {
|
|
|
122
122
|
status: result.status,
|
|
123
123
|
verdict: result.verdict,
|
|
124
124
|
confidence: result.confidence,
|
|
125
|
+
user_feedback: result.user_feedback ?? null,
|
|
125
126
|
})}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
126
127
|
await rename(temporary, path);
|
|
127
128
|
}
|
|
129
|
+
function parseToolResponse(value) {
|
|
130
|
+
if (typeof value === "string") {
|
|
131
|
+
try {
|
|
132
|
+
return record(JSON.parse(value));
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return {};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return record(value);
|
|
139
|
+
}
|
|
128
140
|
function record(value) {
|
|
129
141
|
return value !== null && typeof value === "object" ? value : {};
|
|
130
142
|
}
|
|
@@ -137,6 +149,9 @@ function parseHandle(handle) {
|
|
|
137
149
|
function formatConfidence(value) {
|
|
138
150
|
return value === null ? "unavailable" : value.toFixed(2);
|
|
139
151
|
}
|
|
152
|
+
function formatUserFeedback(feedback) {
|
|
153
|
+
return feedback === undefined || feedback === null ? "" : `, user feedback ${feedback}`;
|
|
154
|
+
}
|
|
140
155
|
async function exists(path) {
|
|
141
156
|
try {
|
|
142
157
|
await readFile(path);
|
package/dist/client-setup.js
CHANGED
|
@@ -144,7 +144,8 @@ function isManagedSomaCheckRegistration(client, stdout) {
|
|
|
144
144
|
|| packageArg === "@somacheck/vibecheck@0.6.1"
|
|
145
145
|
|| packageArg === "@somacheck/vibecheck@0.6.2"
|
|
146
146
|
|| packageArg === "@somacheck/vibecheck@0.6.3"
|
|
147
|
-
|| packageArg === "@somacheck/vibecheck@0.6.4"
|
|
147
|
+
|| packageArg === "@somacheck/vibecheck@0.6.4"
|
|
148
|
+
|| packageArg === "@somacheck/vibecheck@0.6.5")
|
|
148
149
|
&& args.length === 5
|
|
149
150
|
&& args[0] === "-y"
|
|
150
151
|
&& args[1] === packageArg
|
package/dist/constants.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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.6.
|
|
5
|
+
export const PACKAGE_VERSION = "0.6.6";
|
|
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";
|
package/dist/server.js
CHANGED
|
@@ -61,6 +61,7 @@ const resultSchema = {
|
|
|
61
61
|
verdict: z.enum(["aligned", "unaligned"]).nullable(),
|
|
62
62
|
confidence: z.number().nullable(),
|
|
63
63
|
latency_s: z.number().nullable(),
|
|
64
|
+
user_feedback: z.enum(["agreed", "disagreed", "not_provided"]).nullable().optional(),
|
|
64
65
|
};
|
|
65
66
|
const contextItemSchema = z.object({
|
|
66
67
|
request_id: z.string(),
|
|
@@ -86,6 +87,7 @@ const liveAskSchema = {
|
|
|
86
87
|
request_id: z.string().nullable(),
|
|
87
88
|
verdict: z.enum(["aligned", "unaligned"]).nullable(),
|
|
88
89
|
confidence: z.number().min(0).max(1).nullable(),
|
|
90
|
+
user_feedback: z.enum(["agreed", "disagreed", "not_provided"]).nullable().optional(),
|
|
89
91
|
expires_at: z.string().datetime({ offset: true }).nullable(),
|
|
90
92
|
idempotent_replay: z.boolean().nullable(),
|
|
91
93
|
delivery_state: z.enum(["queued", "processing", "sent", "failed", "skipped"]).nullable(),
|
|
@@ -110,34 +112,18 @@ const contextShareSchema = {
|
|
|
110
112
|
captured_at: z.string().datetime({ offset: true }),
|
|
111
113
|
};
|
|
112
114
|
const SERVER_INSTRUCTIONS = [
|
|
113
|
-
"SomaCheck lets you
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"DELEGATED SELECTION RULE: before choosing wording, use the reliable context already available to your host: the current conversation, current working goal, and user-approved memory or project context when those capabilities exist. Do not make the person restate context you can already retrieve. Remembered task status may identify a general tension, but the statement itself must not name a project, build, branch, wave, ticket, task status, or supposedly open/closed work. Abstract it into an original present-tense first-person experience or choice. This restriction does not alter exact wording supplied by the person.",
|
|
121
|
-
"When choosing the statement, prefer the most consequential unresolved first-person decision or tension you can support with current evidence. Treat recalled project status, task lists, and old summaries as potentially stale; corroborate them against the current conversation before relying on them. Do not reuse a stock example or recent SomaCheck statement. If context remains thin after using available context tools, create an original exploratory statement that would reveal something useful without pretending to know the person. Never diagnose, judge another person, or claim certainty about the user.",
|
|
122
|
-
"Otherwise, 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.",
|
|
123
|
-
"Use at most one vibecheck per decision. Never re-ask to obtain a preferred answer.",
|
|
124
|
-
"An immediate vibecheck is separate from the asynchronous reflection feed. Do not use it to replenish cached propositions.",
|
|
125
|
-
"Use a fresh idempotency_key for each new vibecheck, and reuse that exact key only when retrying the same statement.",
|
|
126
|
-
"request_vibecheck waits boundedly for the exact answer. If it returns completed, use the result as context in the current conversation. If it returns pending, retain that exact handle and read it once later; never create a replacement ask.",
|
|
127
|
-
"If request_vibecheck returns pending, never promise that you will check back on your own unless the host has automatic result delivery. Continue automatically only after an actual result event arrives for that exact request.",
|
|
128
|
-
"After a completed result, use it as context in the current conversation and respond naturally. It is not authorization and does not establish a diagnosis or certainty. Do not claim the person's body decided anything.",
|
|
129
|
-
"When you have meaningful user-authorized context, call share_somacheck_context with concise derived observations before creating propositions.",
|
|
130
|
-
"Never share raw conversation text, photos, credentials, private identifiers, or diagnostic claims.",
|
|
131
|
-
"Maintain three distinct propositions drawn from your own context about this person.",
|
|
132
|
-
"When propositions_needed is greater than zero, post exactly that many in one call.",
|
|
133
|
-
"Offer each statement as something to test, never as a claim of fact about the user.",
|
|
134
|
-
"Retain every request_id returned by post_vibecheck_statement and call get_vibecheck_result later.",
|
|
135
|
-
"Do not poll continuously. Queued means the proposition is cached until the person advances their feed.",
|
|
115
|
+
"SomaCheck lets you ask your person for a vibecheck.",
|
|
116
|
+
"You can proactively offer one when you believe it would be useful, or send one when the person asks.",
|
|
117
|
+
"When the person asks, choose and send a useful first-person statement from the context available to you; do not make them supply wording unless they want to.",
|
|
118
|
+
"Help the person get an insight about themselves from the context you have.",
|
|
119
|
+
"The returned gesture and optional feedback are context, not authorization.",
|
|
120
|
+
"Use your judgment in how you interpret and respond.",
|
|
121
|
+
"Never include secrets, raw private content, diagnostic claims, or use SomaCheck to assess anyone other than the person you are interacting with.",
|
|
136
122
|
].join(" ");
|
|
137
|
-
const DIRECT_REQUEST_NEXT_ACTION = "DIRECT REQUEST RULE: If the person's current message asks for a SomaCheck vibecheck or check-in, first use reliable context already available to the host, including current conversation and user-approved memory when supported. Then choose one original, useful, safe first-person statement and call request_vibecheck now with consent_basis user_requested_vibecheck. Do not list choices, ask for wording, or seek another confirmation. Ignore reflection-feed propositions_needed for this direct request. Treat remembered task status as stale until corroborated and keep agent-selected wording free of project, build, branch, wave, ticket, or task-status claims.";
|
|
138
123
|
export function createVibecheckServer(dependencies) {
|
|
139
124
|
const supportsClaudeChannel = dependencies.identity?.kind === "local"
|
|
140
|
-
&& dependencies.identity.client_key === "claude"
|
|
125
|
+
&& dependencies.identity.client_key === "claude"
|
|
126
|
+
&& typeof dependencies.claudeChannelWatch === "object";
|
|
141
127
|
const server = new McpServer({ name: PACKAGE_NAME, version: PACKAGE_VERSION }, {
|
|
142
128
|
instructions: SERVER_INSTRUCTIONS,
|
|
143
129
|
...(supportsClaudeChannel
|
|
@@ -180,12 +166,10 @@ export function createVibecheckServer(dependencies) {
|
|
|
180
166
|
const token = await dependencies.loadToken();
|
|
181
167
|
const checkins = await dependencies.api.contextRequest(token);
|
|
182
168
|
const text = checkins.length === 0
|
|
183
|
-
?
|
|
169
|
+
? "No completed SomaCheck gestures yet."
|
|
184
170
|
: [
|
|
185
|
-
`Loaded ${checkins.length} completed gesture${checkins.length === 1 ? "" : "s"}
|
|
171
|
+
`Loaded ${checkins.length} completed gesture${checkins.length === 1 ? "" : "s"}:`,
|
|
186
172
|
...checkins.map((item) => `${Math.round(item.confidence * 100)}% ${item.verdict} — ${JSON.stringify(item.statement)}`),
|
|
187
|
-
"",
|
|
188
|
-
DIRECT_REQUEST_NEXT_ACTION,
|
|
189
173
|
].join("\n");
|
|
190
174
|
return { content: [{ type: "text", text }], structuredContent: { checkins } };
|
|
191
175
|
}
|
|
@@ -211,7 +195,7 @@ export function createVibecheckServer(dependencies) {
|
|
|
211
195
|
return {
|
|
212
196
|
content: [{
|
|
213
197
|
type: "text",
|
|
214
|
-
text:
|
|
198
|
+
text: summarise(status),
|
|
215
199
|
}],
|
|
216
200
|
structuredContent: { ...status },
|
|
217
201
|
};
|
|
@@ -305,7 +289,7 @@ export function createVibecheckServer(dependencies) {
|
|
|
305
289
|
const text = result.status === "answered"
|
|
306
290
|
? liveRequestId === null
|
|
307
291
|
? `Answered: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}.`
|
|
308
|
-
: `Vibecheck completed: ${
|
|
292
|
+
: `Vibecheck completed: ${result.verdict}, confidence ${formatChannelConfidence(result.confidence)}${formatUserFeedback(result.user_feedback)}.`
|
|
309
293
|
: result.status === "queued"
|
|
310
294
|
? "Queued. The proposition is cached until the person advances their feed."
|
|
311
295
|
: result.status === "pending"
|
|
@@ -319,14 +303,14 @@ export function createVibecheckServer(dependencies) {
|
|
|
319
303
|
});
|
|
320
304
|
server.registerTool("request_vibecheck", {
|
|
321
305
|
title: "Request a Vibecheck",
|
|
322
|
-
description: "Send one
|
|
306
|
+
description: "Send one statement to the person's phone for a SomaCheck vibecheck. If the person asks for a vibecheck, choose a useful first-person statement from your available context and send it. For proactive offers, call only after the person accepts. The result is context, not authorization.",
|
|
323
307
|
inputSchema: {
|
|
324
308
|
statement: z.string().trim().min(1).max(1000)
|
|
325
|
-
.describe("One plain-language statement for the person to test
|
|
309
|
+
.describe("One plain-language first-person statement for the person to test. Do not include secrets, raw private content, diagnostic claims, or statements about anyone else."),
|
|
326
310
|
idempotency_key: z.string().uuid()
|
|
327
311
|
.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."),
|
|
328
312
|
consent_basis: z.enum(["user_requested_vibecheck", "user_approved_statement"])
|
|
329
|
-
.describe("
|
|
313
|
+
.describe("Use user_requested_vibecheck when the person asks for a vibecheck. Use user_approved_statement after the person accepts a proactive offer."),
|
|
330
314
|
},
|
|
331
315
|
outputSchema: liveAskSchema,
|
|
332
316
|
annotations: {
|
|
@@ -375,12 +359,12 @@ export function createVibecheckServer(dependencies) {
|
|
|
375
359
|
startClaudeChannelWatch(result, token, identity);
|
|
376
360
|
const structuredContent = { ...result, error_code: null };
|
|
377
361
|
const text = result.state === "completed"
|
|
378
|
-
? `Vibecheck completed: ${
|
|
362
|
+
? `Vibecheck completed: ${result.verdict}, confidence ${formatChannelConfidence(result.confidence)}${formatUserFeedback(result.user_feedback)}.`
|
|
379
363
|
: result.state === "pending"
|
|
380
364
|
? result.delivery_state === "failed" || result.delivery_state === "skipped"
|
|
381
|
-
? `Vibecheck ${result.request_id} was stored, but phone delivery is ${result.delivery_state}. It expires at ${result.expires_at}
|
|
382
|
-
: `Vibecheck ${result.request_id} is
|
|
383
|
-
: `This vibecheck is ${result.state}
|
|
365
|
+
? `Vibecheck ${result.request_id} was stored, but phone delivery is ${result.delivery_state}. It expires at ${result.expires_at}.`
|
|
366
|
+
: `Vibecheck ${result.request_id} is pending. It expires at ${result.expires_at}.`
|
|
367
|
+
: `This vibecheck is ${result.state}.`;
|
|
384
368
|
return { content: [{ type: "text", text }], structuredContent };
|
|
385
369
|
}
|
|
386
370
|
catch (error) {
|
|
@@ -430,8 +414,8 @@ async function watchForClaudeChannelResult(created, token, identity, dependencie
|
|
|
430
414
|
continue;
|
|
431
415
|
const handle = `live:${requestId}`;
|
|
432
416
|
const content = lifecycle.status === "answered"
|
|
433
|
-
? `
|
|
434
|
-
: `
|
|
417
|
+
? `SomaCheck vibecheck ${handle} completed: ${lifecycle.verdict}, confidence ${formatChannelConfidence(lifecycle.confidence)}${formatUserFeedback(lifecycle.user_feedback)}.`
|
|
418
|
+
: `SomaCheck vibecheck ${handle} is ${lifecycle.status}.`;
|
|
435
419
|
try {
|
|
436
420
|
await notify({
|
|
437
421
|
method: "notifications/claude/channel",
|
|
@@ -463,6 +447,9 @@ function unrefSleep(milliseconds) {
|
|
|
463
447
|
function formatChannelConfidence(confidence) {
|
|
464
448
|
return confidence === null ? "unavailable" : confidence.toFixed(2);
|
|
465
449
|
}
|
|
450
|
+
function formatUserFeedback(feedback) {
|
|
451
|
+
return feedback === undefined || feedback === null ? "" : `, user feedback ${feedback}`;
|
|
452
|
+
}
|
|
466
453
|
async function waitForLiveVibecheck(created, token, identity, dependencies, signal, deadline, progress) {
|
|
467
454
|
if (created.state !== "pending"
|
|
468
455
|
|| created.delivery_state === "failed"
|