@somacheck/vibecheck 0.6.1 → 0.6.2
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 +79 -16
- package/dist/client-setup.js +4 -3
- package/dist/constants.js +1 -1
- package/dist/server.js +112 -7
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @somacheck/vibecheck
|
|
2
2
|
|
|
3
|
-
An MCP server that lets an AI agent
|
|
4
|
-
|
|
3
|
+
An MCP server that lets an AI agent, with a person's consent, send one statement
|
|
4
|
+
for that person to test with a physical wrist gesture in the SomaCheck iOS app.
|
|
5
5
|
|
|
6
6
|
Your agent has your context. It does not have your gut. This is the bridge.
|
|
7
7
|
|
|
@@ -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.2 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
|
|
@@ -78,9 +78,9 @@ before the pairing code is redeemed.
|
|
|
78
78
|
To configure or repair a client later:
|
|
79
79
|
|
|
80
80
|
```text
|
|
81
|
-
npx -y @somacheck/vibecheck@0.6.
|
|
82
|
-
npx -y @somacheck/vibecheck@0.6.
|
|
83
|
-
npx -y @somacheck/vibecheck@0.6.
|
|
81
|
+
npx -y @somacheck/vibecheck@0.6.2 setup codex
|
|
82
|
+
npx -y @somacheck/vibecheck@0.6.2 setup claude
|
|
83
|
+
npx -y @somacheck/vibecheck@0.6.2 doctor
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
Manual registration remains available:
|
|
@@ -90,13 +90,13 @@ Manual registration remains available:
|
|
|
90
90
|
"mcpServers": {
|
|
91
91
|
"vibecheck": {
|
|
92
92
|
"command": "npx",
|
|
93
|
-
"args": ["-y", "@somacheck/vibecheck@0.6.
|
|
93
|
+
"args": ["-y", "@somacheck/vibecheck@0.6.2", "serve", "--client", "codex"]
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.6.
|
|
99
|
+
For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.6.2 serve --client claude`
|
|
100
100
|
|
|
101
101
|
The link step writes only the bearer token in `~/.sensie/config.json`. The client
|
|
102
102
|
setup step asks Codex or Claude to add the pinned MCP command to that client's own
|
|
@@ -107,6 +107,39 @@ If `doctor` reports another MCP server named `somacheck`, that is the legacy
|
|
|
107
107
|
hosted connector and is separate from this local package. The setup command
|
|
108
108
|
will explain it and leave it alone. The canonical local MCP key is `vibecheck`.
|
|
109
109
|
|
|
110
|
+
## Teach your agent when to offer a check-in
|
|
111
|
+
|
|
112
|
+
[`SKILL.md`](SKILL.md) teaches compatible agents two behaviors: act immediately
|
|
113
|
+
when the person explicitly requests a vibecheck, and offer one optional
|
|
114
|
+
vibecheck when work drifts, a consequential decision is imminent, or recent
|
|
115
|
+
messages show multiple stress signals. It uses `request_vibecheck`, not the
|
|
116
|
+
reflection-feed batch tool. It includes transcript-grounded heuristics,
|
|
117
|
+
statement templates, a two-hour decline cooldown, privacy rules, and the
|
|
118
|
+
requirement to treat every result as context rather than authorization.
|
|
119
|
+
|
|
120
|
+
For Claude Code, add this repository as a marketplace and install the skill in
|
|
121
|
+
one terminal command:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
claude plugin marketplace add sensie-app/Somacheck && claude plugin install vibecheck@somacheck
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Inside Claude Code, the equivalent commands are:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
/plugin marketplace add sensie-app/Somacheck
|
|
131
|
+
/plugin install vibecheck@somacheck
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For agents supported by the Skills CLI:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
npx skills add sensie-app/Somacheck --skill vibecheck
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The skill expects the `@somacheck/vibecheck` MCP server to already be linked as
|
|
141
|
+
`vibecheck`. Installing the skill does not copy or replace the bearer token.
|
|
142
|
+
|
|
110
143
|
## Tools
|
|
111
144
|
|
|
112
145
|
| Tool | What it does |
|
|
@@ -122,21 +155,33 @@ will explain it and leave it alone. The canonical local MCP key is `vibecheck`.
|
|
|
122
155
|
|
|
123
156
|
Ask when you genuinely need a read you cannot infer — not to check in. A good
|
|
124
157
|
prompt is **a statement for the person to test**, not a question about facts:
|
|
125
|
-
|
|
126
|
-
something to test, never as a claim about them.
|
|
127
|
-
|
|
158
|
+
“I feel more concerned about the launch than the roadmap suggests.” Offer it as
|
|
159
|
+
something to test, never as a claim about them. An `unaligned` result is another
|
|
160
|
+
signal to consider; the person decides whether it matches what they notice.
|
|
128
161
|
|
|
129
162
|
Setup and linking are not permission for future asks. If the person's current
|
|
130
|
-
message explicitly asks
|
|
131
|
-
|
|
132
|
-
|
|
163
|
+
message explicitly asks for a SomaCheck vibecheck, that is consent for one ask.
|
|
164
|
+
Use their exact statement when supplied. If they delegated the choice, select
|
|
165
|
+
the most useful first-person statement from the current conversation, goal,
|
|
166
|
+
decision, or tension and send it immediately—do not ask them for wording or a
|
|
167
|
+
second confirmation. Remembered context may identify a general tension, but an
|
|
168
|
+
agent-selected statement must not name a project, build, branch, wave, ticket,
|
|
169
|
+
task status, or supposedly open/closed work. Abstract it into a present-tense
|
|
170
|
+
first-person experience or choice, such as “I want to keep consolidating what is
|
|
171
|
+
already in motion rather than start something new.” Preserve exact wording the
|
|
172
|
+
person supplied. If meaningful context is unavailable, use the neutral fallback
|
|
173
|
+
“I feel clear about what deserves my attention right now.”
|
|
174
|
+
|
|
175
|
+
If their message did not explicitly request a vibecheck, first show the exact
|
|
176
|
+
statement and invite the person plainly:
|
|
133
177
|
|
|
134
178
|
> Want a quick vibecheck before I continue? I'll send this statement to your
|
|
135
179
|
> phone; you answer with your wrist gesture, and I'll use the result as a
|
|
136
180
|
> signal—not a verdict.
|
|
137
181
|
|
|
138
|
-
Call `request_vibecheck` with `
|
|
139
|
-
`
|
|
182
|
+
Call `request_vibecheck` with `consent_basis: "user_requested_vibecheck"` for a
|
|
183
|
+
direct request or `consent_basis: "user_approved_statement"` after approval of
|
|
184
|
+
agent-proposed wording, plus a new UUID in `idempotency_key`. Reuse that UUID
|
|
140
185
|
only when retrying the same statement. The tool keeps the one created request
|
|
141
186
|
open for a bounded wait and returns `completed` when the exact answer arrives.
|
|
142
187
|
On timeout or a result-read failure it returns the same pending `live:<uuid>`
|
|
@@ -146,6 +191,24 @@ idempotency key, but the cancelled MCP transport may suppress that pending
|
|
|
146
191
|
response. Do not continuously poll or re-ask the same decision to obtain a
|
|
147
192
|
preferred result.
|
|
148
193
|
|
|
194
|
+
Standard MCP cannot start a new agent turn after that bounded tool call has
|
|
195
|
+
ended. The local Claude Code runtime therefore also advertises Anthropic's
|
|
196
|
+
opt-in `claude/channel` capability. When Claude Code is launched with the
|
|
197
|
+
SomaCheck server enabled as a channel, one local background watcher follows
|
|
198
|
+
only the exact pending request. It emits one hardcoded, secret-free channel
|
|
199
|
+
event when that request becomes answered, expired, or cancelled. The event
|
|
200
|
+
contains the constrained lifecycle result and opaque handle, never the private
|
|
201
|
+
statement or link token, and it cannot create another phone request.
|
|
202
|
+
|
|
203
|
+
Claude Code channels are currently a research preview and only receive events
|
|
204
|
+
while the opted-in session remains open. For local QA of this candidate, launch
|
|
205
|
+
Claude Code with `--dangerously-load-development-channels server:vibecheck`.
|
|
206
|
+
Production distribution requires the SomaCheck channel plugin to be accepted by
|
|
207
|
+
Anthropic's channel allowlist (or explicitly allowed by an organization).
|
|
208
|
+
Claude Desktop, hosted connectors, Codex, and other standard MCP hosts retain
|
|
209
|
+
the bounded tool response and stable-handle fallback; this Claude-specific
|
|
210
|
+
channel must never be presented as provider-neutral behavior.
|
|
211
|
+
|
|
149
212
|
The 45-second whole-call deadline also covers the one create request. If that
|
|
150
213
|
network request is aborted after an ambiguous commit, retry the exact same
|
|
151
214
|
statement with the same `idempotency_key`; never create a replacement ask.
|
package/dist/client-setup.js
CHANGED
|
@@ -135,12 +135,13 @@ function isManagedSomaCheckRegistration(client, stdout) {
|
|
|
135
135
|
if (legacyWithoutClient)
|
|
136
136
|
return true;
|
|
137
137
|
// 0.5 introduced the exact local runtime identity required by Live Ask.
|
|
138
|
-
// It is safe to replace only the canonical command generated by 0.5 or
|
|
139
|
-
// exact 0.6
|
|
138
|
+
// It is safe to replace only the canonical command generated by 0.5 or an
|
|
139
|
+
// earlier exact 0.6 patch release. Custom wrappers, extra arguments,
|
|
140
140
|
// disabled entries, later versions, and registrations naming the other
|
|
141
141
|
// client remain user-owned.
|
|
142
142
|
const previousExactClient = (/^@somacheck\/vibecheck@0\.5\.\d+$/.test(packageArg)
|
|
143
|
-
|| packageArg === "@somacheck/vibecheck@0.6.0"
|
|
143
|
+
|| packageArg === "@somacheck/vibecheck@0.6.0"
|
|
144
|
+
|| packageArg === "@somacheck/vibecheck@0.6.1")
|
|
144
145
|
&& args.length === 5
|
|
145
146
|
&& args[0] === "-y"
|
|
146
147
|
&& 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.2";
|
|
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
|
@@ -8,6 +8,9 @@ const LIVE_ASK_POLL_DELAYS_MS = [
|
|
|
8
8
|
1_000, 1_000, 2_000, 2_000, 3_000, 3_000,
|
|
9
9
|
4_000, 4_000, 5_000, 5_000, 5_000, 5_000,
|
|
10
10
|
];
|
|
11
|
+
const CLAUDE_CHANNEL_POLL_INTERVAL_MS = 2_000;
|
|
12
|
+
const CLAUDE_CHANNEL_MAX_WATCH_MS = 15 * 60 * 1_000;
|
|
13
|
+
const CLAUDE_CHANNEL_RESULT_TIMEOUT_MS = 10_000;
|
|
11
14
|
function abortAwareSleep(milliseconds, signal) {
|
|
12
15
|
if (signal.aborted || milliseconds <= 0)
|
|
13
16
|
return Promise.resolve();
|
|
@@ -109,16 +112,20 @@ const contextShareSchema = {
|
|
|
109
112
|
};
|
|
110
113
|
const SERVER_INSTRUCTIONS = [
|
|
111
114
|
"SomaCheck lets you offer the person a statement to test through a quick phone check-in.",
|
|
115
|
+
"MANDATORY COMMAND RULE: when the current user message says 'Give me a SomaCheck vibecheck', 'Request a SomaCheck vibecheck', or otherwise explicitly asks for one, you must call request_vibecheck in that same turn after any read-only context/status calls. Do not present a draft, ask 'Want me to send this?', or wait for another yes. The request itself is one-ask consent for you to select and send the statement.",
|
|
112
116
|
"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.",
|
|
113
117
|
"Treat gesture outcomes as contextual signals, never fixed facts or blanket authorization.",
|
|
114
118
|
"Use request_vibecheck when a current choice would benefit from the person's own felt response; never use it to evaluate someone else.",
|
|
115
119
|
"A setup or link is not blanket consent for future vibechecks.",
|
|
116
|
-
"If the person's current message explicitly
|
|
120
|
+
"If the person's current message explicitly requests a SomaCheck vibecheck, that message is consent for one ask. If they supplied exact wording, use it. Otherwise, choose one useful statement from the current conversation, goal, decision, or tension and send it immediately; do not ask them to supply wording or confirm twice.",
|
|
121
|
+
"DELEGATED SELECTION RULE: when you choose the wording, remembered context 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 a present-tense first-person experience or choice, such as 'I want to keep consolidating what is already in motion rather than start something new.' This restriction does not alter exact wording supplied by the person.",
|
|
122
|
+
"When choosing the statement, prefer the most consequential unresolved first-person decision or tension you already understand. Treat recalled project status, task lists, and old summaries as potentially stale. Never use them as a factual premise for agent-selected wording, even if they were recalled in the current turn. If context is thin, use the neutral fallback 'I feel clear about what deserves my attention right now.' Never diagnose, judge another person, or claim certainty about the user.",
|
|
117
123
|
"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.",
|
|
118
124
|
"Use at most one vibecheck per decision. Never re-ask to obtain a preferred answer.",
|
|
119
125
|
"An immediate vibecheck is separate from the asynchronous reflection feed. Do not use it to replenish cached propositions.",
|
|
120
126
|
"Use a fresh idempotency_key for each new vibecheck, and reuse that exact key only when retrying the same statement.",
|
|
121
127
|
"request_vibecheck waits boundedly for the exact answer. If it returns completed, continue in the same turn and explain what changes. If it returns pending, retain that exact handle and read it once later; never create a replacement ask.",
|
|
128
|
+
"If request_vibecheck returns pending, never promise that you will check back on your own. Only continue automatically after an actual SomaCheck channel event arrives for that exact request.",
|
|
122
129
|
"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.",
|
|
123
130
|
"When you have meaningful user-authorized context, call share_somacheck_context with concise derived observations before creating propositions.",
|
|
124
131
|
"Never share raw conversation text, photos, credentials, private identifiers, or diagnostic claims.",
|
|
@@ -129,7 +136,34 @@ const SERVER_INSTRUCTIONS = [
|
|
|
129
136
|
"Do not poll continuously. Queued means the proposition is cached until the person advances their feed.",
|
|
130
137
|
].join(" ");
|
|
131
138
|
export function createVibecheckServer(dependencies) {
|
|
132
|
-
const
|
|
139
|
+
const supportsClaudeChannel = dependencies.identity?.kind === "local"
|
|
140
|
+
&& dependencies.identity.client_key === "claude";
|
|
141
|
+
const server = new McpServer({ name: PACKAGE_NAME, version: PACKAGE_VERSION }, {
|
|
142
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
143
|
+
...(supportsClaudeChannel
|
|
144
|
+
? { capabilities: { experimental: { "claude/channel": {} } } }
|
|
145
|
+
: {}),
|
|
146
|
+
});
|
|
147
|
+
const claudeChannelWatches = new Set();
|
|
148
|
+
const claudeChannelNotified = new Set();
|
|
149
|
+
const startClaudeChannelWatch = (created, token, identity) => {
|
|
150
|
+
if (!supportsClaudeChannel || dependencies.claudeChannelWatch === false)
|
|
151
|
+
return;
|
|
152
|
+
if (created.state !== "pending"
|
|
153
|
+
|| created.delivery_state === "failed"
|
|
154
|
+
|| created.delivery_state === "skipped")
|
|
155
|
+
return;
|
|
156
|
+
const requestId = parseLiveVibecheckHandle(created.request_id);
|
|
157
|
+
if (requestId === null
|
|
158
|
+
|| claudeChannelWatches.has(requestId)
|
|
159
|
+
|| claudeChannelNotified.has(requestId))
|
|
160
|
+
return;
|
|
161
|
+
claudeChannelWatches.add(requestId);
|
|
162
|
+
void watchForClaudeChannelResult(created, token, identity, dependencies, async (notification) => {
|
|
163
|
+
claudeChannelNotified.add(requestId);
|
|
164
|
+
await server.server.notification(notification);
|
|
165
|
+
}).finally(() => claudeChannelWatches.delete(requestId));
|
|
166
|
+
};
|
|
133
167
|
server.registerTool("get_vibecheck_context", {
|
|
134
168
|
title: "Vibecheck Context",
|
|
135
169
|
description: "Read recent completed check-ins for this linked agent, newest first. Use the outcomes as contextual signals and replenish the feed when status requests it.",
|
|
@@ -280,14 +314,14 @@ export function createVibecheckServer(dependencies) {
|
|
|
280
314
|
});
|
|
281
315
|
server.registerTool("request_vibecheck", {
|
|
282
316
|
title: "Request a Vibecheck",
|
|
283
|
-
description: "Send one plain-language statement to the person's phone for a vibecheck answered with a wrist gesture. A current user
|
|
317
|
+
description: "Send one plain-language statement to the person's phone for a vibecheck answered with a wrist gesture. A current user message that explicitly requests a SomaCheck vibecheck is confirmation for one ask. Use their exact wording when supplied; otherwise choose and send one present-tense first-person experience or choice immediately without asking for wording or reconfirmation. When you choose the wording, remembered context may identify only the general tension: never put a named project, build, branch, wave, ticket, task status, or supposedly open/closed work into the statement, even when recalled in the current turn. Abstract it, for example: 'I want to keep consolidating what is already in motion rather than start something new.' If context is thin, use the neutral fallback 'I feel clear about what deserves my attention right now.' Otherwise, first show the exact statement and call only after the person accepts. Setup or linking is not blanket consent. Use this only when their own felt response would help with the current choice, not for routine feed content, diagnosis, or judging another person. The call waits boundedly for the exact result and returns completed when available. A timeout returns the same stable pending request_id for one later read. Caller cancellation stops result reads without creating another request; the cancelled transport may suppress the pending response.",
|
|
284
318
|
inputSchema: {
|
|
285
319
|
statement: z.string().trim().min(1).max(1000)
|
|
286
|
-
.describe("One plain-language statement for the person to test; offer a signal, not a fact, diagnosis, or instruction."),
|
|
320
|
+
.describe("One plain-language statement for the person to test; offer a signal, not a fact, diagnosis, or instruction. If the agent selected the wording, it must be a present-tense first-person experience or choice and must not name remembered projects, builds, branches, waves, tickets, task status, or supposedly open/closed work. Preserve exact wording supplied by the person."),
|
|
287
321
|
idempotency_key: z.string().uuid()
|
|
288
322
|
.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."),
|
|
289
|
-
|
|
290
|
-
.describe("
|
|
323
|
+
consent_basis: z.enum(["user_requested_vibecheck", "user_approved_statement"])
|
|
324
|
+
.describe("Required one-ask consent basis. Use user_requested_vibecheck when the current message asks for a SomaCheck vibecheck, including when the agent chooses the statement; call immediately without another confirmation. Use user_approved_statement only after the agent proposed exact wording and the person accepted it."),
|
|
291
325
|
},
|
|
292
326
|
outputSchema: liveAskSchema,
|
|
293
327
|
annotations: {
|
|
@@ -333,13 +367,14 @@ export function createVibecheckServer(dependencies) {
|
|
|
333
367
|
// prevent the exact result from completing this tool call.
|
|
334
368
|
});
|
|
335
369
|
});
|
|
370
|
+
startClaudeChannelWatch(result, token, identity);
|
|
336
371
|
const structuredContent = { ...result, error_code: null };
|
|
337
372
|
const text = result.state === "completed"
|
|
338
373
|
? `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.`
|
|
339
374
|
: result.state === "pending"
|
|
340
375
|
? result.delivery_state === "failed" || result.delivery_state === "skipped"
|
|
341
376
|
? `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.`
|
|
342
|
-
: `Vibecheck ${result.request_id} is still pending after a bounded wait. Keep this exact handle and check it once later with get_vibecheck_result. It expires at ${result.expires_at}; do not poll continuously
|
|
377
|
+
: `Vibecheck ${result.request_id} is still pending after a bounded wait. Keep this exact handle and check it once later with get_vibecheck_result. It expires at ${result.expires_at}; do not poll continuously, re-ask for this decision, or promise an automatic follow-up unless an actual SomaCheck channel event arrives.`
|
|
343
378
|
: `This vibecheck is ${result.state}. Do not create another ask for the same decision.`;
|
|
344
379
|
return { content: [{ type: "text", text }], structuredContent };
|
|
345
380
|
}
|
|
@@ -353,6 +388,76 @@ export function createVibecheckServer(dependencies) {
|
|
|
353
388
|
});
|
|
354
389
|
return server;
|
|
355
390
|
}
|
|
391
|
+
async function watchForClaudeChannelResult(created, token, identity, dependencies, notify) {
|
|
392
|
+
const requestId = parseLiveVibecheckHandle(created.request_id);
|
|
393
|
+
if (requestId === null || identity.kind !== "local" || identity.client_key !== "claude")
|
|
394
|
+
return;
|
|
395
|
+
const options = dependencies.claudeChannelWatch || undefined;
|
|
396
|
+
const now = options?.now ?? Date.now;
|
|
397
|
+
const sleep = options?.sleep ?? unrefSleep;
|
|
398
|
+
const interval = options?.pollIntervalMs ?? CLAUDE_CHANNEL_POLL_INTERVAL_MS;
|
|
399
|
+
const maxWatchMs = options?.maxWatchMs ?? CLAUDE_CHANNEL_MAX_WATCH_MS;
|
|
400
|
+
const parsedExpiry = created.expires_at === null ? Number.NaN : Date.parse(created.expires_at);
|
|
401
|
+
const deadline = Math.min(Number.isFinite(parsedExpiry) ? parsedExpiry : now() + maxWatchMs, now() + maxWatchMs);
|
|
402
|
+
let consecutiveReadFailures = 0;
|
|
403
|
+
while (now() < deadline) {
|
|
404
|
+
await sleep(Math.min(interval, Math.max(0, deadline - now())));
|
|
405
|
+
if (now() >= deadline)
|
|
406
|
+
return;
|
|
407
|
+
const readAbort = new AbortController();
|
|
408
|
+
const readTimeout = setTimeout(() => readAbort.abort(new Error("Claude channel result read timed out.")), CLAUDE_CHANNEL_RESULT_TIMEOUT_MS);
|
|
409
|
+
readTimeout.unref?.();
|
|
410
|
+
let lifecycle;
|
|
411
|
+
try {
|
|
412
|
+
lifecycle = await dependencies.api.liveVibecheckResult(token, identity, requestId, readAbort.signal);
|
|
413
|
+
consecutiveReadFailures = 0;
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
consecutiveReadFailures += 1;
|
|
417
|
+
if (consecutiveReadFailures >= 3)
|
|
418
|
+
return;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
clearTimeout(readTimeout);
|
|
423
|
+
}
|
|
424
|
+
if (lifecycle.status === "pending")
|
|
425
|
+
continue;
|
|
426
|
+
const handle = `live:${requestId}`;
|
|
427
|
+
const content = lifecycle.status === "answered"
|
|
428
|
+
? `A SomaCheck vibecheck requested by this Claude connection has completed. The exact result is ${lifecycle.verdict} with confidence ${formatChannelConfidence(lifecycle.confidence)}. Treat it as a signal, not a verdict. Continue the current conversation now: briefly acknowledge the signal and state what it changes in your next action. The exact opaque request handle is ${handle}. Do not request another vibecheck for this decision.`
|
|
429
|
+
: `A SomaCheck vibecheck requested by this Claude connection is now ${lifecycle.status}. The exact opaque request handle is ${handle}. Do not create a replacement request for the same decision.`;
|
|
430
|
+
try {
|
|
431
|
+
await notify({
|
|
432
|
+
method: "notifications/claude/channel",
|
|
433
|
+
params: {
|
|
434
|
+
content,
|
|
435
|
+
meta: {
|
|
436
|
+
event: "vibecheck_result",
|
|
437
|
+
request_id: handle,
|
|
438
|
+
status: lifecycle.status,
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
// Channel opt-in is client-controlled and experimental. A host that did
|
|
445
|
+
// not enable it must not crash the MCP server or cause duplicate events.
|
|
446
|
+
}
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function unrefSleep(milliseconds) {
|
|
451
|
+
if (milliseconds <= 0)
|
|
452
|
+
return Promise.resolve();
|
|
453
|
+
return new Promise((resolve) => {
|
|
454
|
+
const timer = setTimeout(resolve, milliseconds);
|
|
455
|
+
timer.unref?.();
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
function formatChannelConfidence(confidence) {
|
|
459
|
+
return confidence === null ? "unavailable" : confidence.toFixed(2);
|
|
460
|
+
}
|
|
356
461
|
async function waitForLiveVibecheck(created, token, identity, dependencies, signal, deadline, progress) {
|
|
357
462
|
if (created.state !== "pending"
|
|
358
463
|
|| created.delivery_state === "failed"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@somacheck/vibecheck",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Send a consented vibecheck to SomaCheck and use the result as a signal.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
22
|
-
"test": "npm run build && node --import tsx --test test/*.test.ts && node --test test/*.test.mjs",
|
|
22
|
+
"test": "npm run build && node --import tsx --test test/*.test.ts && node --test test/*.test.mjs && npm run test:skill",
|
|
23
|
+
"test:skill": "python3 test/skill-triggers/verify.py",
|
|
23
24
|
"test:release-artifacts": "bash ./verify-release-artifacts.sh",
|
|
24
25
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
25
26
|
"prepack": "npm run build"
|