tandem-editor 0.21.0 → 0.22.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/.claude-plugin/plugin.json +5 -5
- package/CHANGELOG.md +20 -0
- package/README.md +6 -6
- package/dist/cli/index.js +73 -27
- package/dist/cli/index.js.map +1 -1
- package/dist/client/assets/{CoworkSettings-Cw79n4Xb.js → CoworkSettings-CiDA5o27.js} +1 -1
- package/dist/client/assets/{dist-PiTIn2Ai.js → dist-DChmSjqi.js} +1 -1
- package/dist/client/assets/index-D1G1tZo6.js +309 -0
- package/dist/client/assets/{prod-CGRV0K-S.js → prod-BOSHNjiT.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/server/index.js +375 -175
- package/dist/server/index.js.map +1 -1
- package/package.json +2 -1
- package/skills/tandem/SKILL.md +8 -7
- package/dist/client/assets/index-D47GfCBV.js +0 -307
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tandem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Edit and iterate on documents with any MCP-capable AI. Claude is the default and best-supported client today.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Tandem"
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
"mcpServers": {
|
|
12
12
|
"tandem": {
|
|
13
13
|
"command": "npx",
|
|
14
|
-
"args": ["-y", "tandem-editor@0.
|
|
14
|
+
"args": ["-y", "tandem-editor@0.22.0", "mcp-stdio"],
|
|
15
15
|
"env": {
|
|
16
16
|
"TANDEM_URL": "http://127.0.0.1:3479"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"tandem-channel": {
|
|
20
20
|
"command": "npx",
|
|
21
|
-
"args": ["-y", "tandem-editor@0.
|
|
21
|
+
"args": ["-y", "tandem-editor@0.22.0", "channel"],
|
|
22
22
|
"env": {
|
|
23
23
|
"TANDEM_URL": "http://127.0.0.1:3479"
|
|
24
24
|
}
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"monitors": [
|
|
29
29
|
{
|
|
30
30
|
"name": "tandem-events",
|
|
31
|
-
"command": "npx -y tandem-editor@0.
|
|
31
|
+
"command": "npx -y tandem-editor@0.22.0 monitor",
|
|
32
32
|
"description": "Tandem real-time document events (annotations, chat, selections)",
|
|
33
33
|
"when": "on-skill-invoke:tandem:tandem"
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"name": "tandem-events-user-skill",
|
|
37
|
-
"command": "npx -y tandem-editor@0.
|
|
37
|
+
"command": "npx -y tandem-editor@0.22.0 monitor",
|
|
38
38
|
"description": "Tandem real-time document events (annotations, chat, selections)",
|
|
39
39
|
"when": "on-skill-invoke:tandem"
|
|
40
40
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.22.0] - 2026-08-11
|
|
11
|
+
|
|
12
|
+
This release is about your AI noticing that you said something.
|
|
13
|
+
|
|
14
|
+
Tandem has always been able to tell your AI that you commented on a paragraph or sent a chat message, but for sessions you started yourself, something had to be listening first — and nothing arranged that for you. A session you opened by typing `claude` would sit there while your comments piled up unread. Now it sets that up itself, on its own, the first time it touches Tandem, and Tandem says how at the moment the connection opens rather than hoping the subject comes up later. Measured across ten isolated sessions, the number that arranged it went from three in six to six in six.
|
|
15
|
+
|
|
16
|
+
The rest is a smaller thing that will save you a round trip: bug reports now arrive with the details we would otherwise have to ask you for, and the Report a bug link fills them in.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **Hand-started Claude Code sessions now try to watch Tandem automatically on first use.** After the first successful read-only `tandem_status`, the bundled skill makes one persistent built-in Monitor attempt using the server-reported wake address; asking Claude to watch is now recovery rather than setup. Existing setup-managed skill copies refresh before Tandem reports ready, including when the launcher is disabled or deferred. A missing standalone skill does not install during generic startup, so plugin-only users do not silently acquire a second integration route. Plugin-only sessions receive the updated instructions when Claude Code refreshes its plugin cache, on a timeline Tandem does not control. If a plugin-backed session starts both its packaged monitor and the built-in watch automatically, wakes remain payload-free and inbox reads de-duplicate; `TaskStop` stops the built-in watch when doubled wakes make the overlap visible.
|
|
21
|
+
|
|
22
|
+
- **Tandem now tells your AI how to be woken the moment the connection opens, not only once it reaches for the Tandem skill (#1397).** Tandem's connection has always been able to carry a short standing instruction, and Tandem sent nothing. That text is the only thing besides tool *names* that reaches your AI before it calls anything, so guidance about staying reachable arrived — when it arrived at all — after the moment a session decides how to behave. It now says that the inbox is the authority on your activity, that a client able to hold a persistent watch should open one on the address Tandem reports, and that annotations are held in solo mode. Sessions Tandem launches for you are told to skip the watch, because they are already woken directly and a second watch would deliver everything twice. It deliberately makes no claim about whether *this* session is covered and quotes no subscriber count — Tandem cannot see either, and a standing instruction is not a diagnosis. AI clients other than Claude Code receive the same text; nothing in it names a capability as a requirement, so it reads as inapplicable rather than wrong where no such watch exists. Measured across ten isolated authenticated sessions, first-use arming went from three of six to six of six with this and the two skill changes above in place — the three together, not this one alone.
|
|
23
|
+
|
|
24
|
+
- **Copy Diagnostics now carries the facts a bug report needs, and the Report-a-bug link fills them in for you (#1394).** Diagnostics used to describe Tandem and almost nothing about the machine it ran on, so most reports opened with a round trip asking for the operating system version, the browser, and how much memory the machine had. Both the copied text and the `tandem_diagnostics` tool now include platform, architecture, OS release and version, CPU model and core count, total memory, Node version, and — in the browser distribution — a short browser descriptor. The Report a bug link in Settings opens a GitHub issue with that block already in the body instead of an empty form. **What is deliberately absent stays absent**, because this text lands in a public issue: your machine name, your username, your home directory path, your network interfaces, your locale and timezone. Paths that do survive are collapsed — your home directory becomes `~`, Tandem's data directory becomes `<app-data>` — including paths that arrive inside raw filesystem error messages, which no prefix list can be trusted to cover. The link gathers diagnostics only after your pointer or keyboard focus rests on it briefly, and not merely passes through: collecting them is not an inert read — it opens and drops a real connection to Tandem's own update stream and runs a global package listing — so a pointer crossing the sidebar on its way elsewhere must not trigger it. If gathering fails, the link still works, unfilled.
|
|
25
|
+
|
|
26
|
+
### Tests
|
|
27
|
+
|
|
28
|
+
- **A live-evidence gate for first-use session-monitor arming (#1393).** That a hand-started session arms a watch on first use could previously only be argued from source. This adds a harness that builds an isolated Claude Code and Tandem fixture — its own home, config directory and authentication — and drives real bounded sessions across a ten-trial matrix, emitting signed pass/fail evidence per trial. Two properties are load-bearing: a recorded decline must be shown to be the model's own choice rather than a broken rig, and a re-attempt supersedes prior evidence without erasing it. The suite is not wired into CI; #1399 records why and what it would cost. Harness only — no product code.
|
|
29
|
+
|
|
10
30
|
## [0.21.0] - 2026-08-10
|
|
11
31
|
|
|
12
32
|
Most of this release is Tandem telling you the truth about itself.
|
package/README.md
CHANGED
|
@@ -107,9 +107,9 @@ You do not have to configure any of this by hand. Tandem opens a setup wizard th
|
|
|
107
107
|
|
|
108
108
|
Prefer a different AI? Any MCP-capable client can connect to the same endpoint — see the [compatibility table](#the-mcp-integration-policy) for what is supported and what is untested, and [Cowork](#cowork) for connecting Claude Desktop on Windows.
|
|
109
109
|
|
|
110
|
-
**How Claude starts matters.** Tandem talks to Claude over two independent connections: one lets Claude read and edit your document, the other tells it the moment you comment or send a chat message. Sessions Tandem launches for you — including the desktop app's **Relaunch Claude** button — get both, and need no setup from you. A session you start yourself by typing `claude`
|
|
110
|
+
**How Claude starts matters.** Tandem talks to Claude over two independent connections: one lets Claude read and edit your document, the other tells it the moment you comment or send a chat message. Sessions Tandem launches for you — including the desktop app's **Relaunch Claude** button — get both, and need no setup from you. A session you start yourself by typing `claude` begins with the first connection; on its first successful read-mode `tandem_status`, Tandem's bundled skill automatically tells it to attempt the second with the built-in Monitor when that tool is available.
|
|
111
111
|
|
|
112
|
-
The simplest needs no installation at all: **
|
|
112
|
+
The simplest needs no installation at all: **the built-in Monitor watch starts automatically on first Tandem use.** The bundled skill reads the live wake-stream address from that first successful `tandem_status` and makes one persistent attempt for the session. If that attempt was skipped, asking Claude to watch is a recovery step, not normal setup. It does need a Claude Code that offers a built-in Monitor tool. That is enabled per account rather than per version, so upgrading will not add it, and on Windows it also needs Git Bash. The plugin monitor shares the same per-account feature gate, so it cannot help when that gate is off. But the plugin monitor does not require Git Bash on Windows and can fall back to PowerShell, so it may help when Git Bash is the missing precondition. The third option below, the channel shim, avoids both requirements.
|
|
113
113
|
|
|
114
114
|
The second is to install the Tandem plugin, which also needs no flag — see [Real-time updates](#real-time-updates) below. It starts watching the first time Claude uses Tandem's skill in a session, so ask for Tandem by name ("let's work on this in Tandem") rather than expecting it to be listening before you have mentioned it. Start `claude` from a terminal window if you install it: the plugin's monitor uses whatever program path that shell has, and a Claude Code started from a desktop icon may not have one it can use.
|
|
115
115
|
|
|
@@ -120,7 +120,7 @@ tandem setup --apply --with-channel-shim # once
|
|
|
120
120
|
claude --dangerously-load-development-channels server:tandem-channel # every session
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
Choose one setup route where possible. A plugin-backed session whose Claude Code also offers the built-in Monitor may start both automatically when it first uses Tandem's skill. Those wakes contain no message content; host rate limiting can hide the overlap, and the inbox de-duplicates what Claude reads. If wakes visibly double, ask Claude to stop its built-in watch with `TaskStop`.
|
|
124
124
|
|
|
125
125
|
Without any of them nothing breaks and nothing is lost: your messages and comments are saved, and Claude sees them the next time it checks its inbox. But it will not react on its own, which reads as Claude ignoring you. If that is what you are seeing, [this troubleshooting entry](docs/troubleshooting.md#i-sent-a-chat-message-or-left-a-comment-and-nothing-happened) walks through it, and **Settings → About → Copy Diagnostics** (or `tandem doctor`, on npm installs) reports whether anything is listening. More detail in [Real-time updates](#real-time-updates).
|
|
126
126
|
|
|
@@ -255,11 +255,11 @@ Tandem's MCP tools span six capability areas. Full reference: [docs/mcp-tools.md
|
|
|
255
255
|
|
|
256
256
|
### Real-time updates
|
|
257
257
|
|
|
258
|
-
Real-time delivery gets events (annotation actions, chat messages) to the AI the moment they happen, so it does not have to wait until it next polls. There are three
|
|
258
|
+
Real-time delivery gets events (annotation actions, chat messages) to the AI the moment they happen, so it does not have to wait until it next polls. There are three setup routes; they are not layers you need to configure together. When the plugin and built-in Monitor are both available, however, first skill use can start both automatically — a safe but potentially wasteful overlap described below.
|
|
259
259
|
|
|
260
260
|
None of them is needed for a session Tandem launches for you. Those are woken directly by Tandem over the session's own input and use none of the three; everything below is about sessions you start by hand.
|
|
261
261
|
|
|
262
|
-
**A self-armed watch** needs nothing installed and no flag.
|
|
262
|
+
**A self-armed watch** needs nothing installed and no flag. On first Tandem use, the bundled skill automatically reads the wake-stream address from the first successful `tandem_status` and makes one persistent built-in Monitor attempt. It lasts for that session and disappears with it, which is also the point — nothing is left configured on your machine. Asking Claude to watch is recovery if the automatic attempt was skipped. Two limits: it needs a Claude Code that offers a built-in Monitor tool, and the wake stream is **loopback-only**, so it does not reach a session running anywhere but this machine — Cowork included.
|
|
263
263
|
|
|
264
264
|
**The plugin monitor** needs no flag either, and once installed it applies to every session — but it starts watching only when Claude first uses Tandem's skill in that session, not the moment the session opens. In practice that means asking for Tandem by name; a Claude that has never been told about Tandem is not listening yet. That is deliberate: it used to start in every session including ones with nothing to do with Tandem, so when it could not run it reported a failure in all of them. Two further conditions: it requires **Claude Code 2.1.212 or newer** — on older versions the install still succeeds and the monitor simply never runs, with nothing to tell you so — and you should start `claude` from a terminal, because the monitor runs with whatever program path that session was given and a desktop-icon launch may not include Node. If it reports `exit 127`, that is this.
|
|
265
265
|
|
|
@@ -272,7 +272,7 @@ claude --dangerously-load-development-channels server:tandem-channel # every s
|
|
|
272
272
|
|
|
273
273
|
The flag is Claude Code's marker for unstable APIs. It works only in an interactive session, and it is required rather than optional: `tandem-channel` is a plain MCP server, and Claude Code's channel allowlist covers only plugins, so there is no list Tandem could join that would remove the need for it. If you already have it configured, your configuration is untouched and keeps working — the change is to what a *new* setup writes. To remove it, re-run `tandem setup --apply` without the flag.
|
|
274
274
|
|
|
275
|
-
|
|
275
|
+
Choose one setup route where possible. A plugin-backed session may still start its packaged monitor and built-in watch automatically. Wakes carry no message content, the inbox de-duplicates what Claude reads, and host rate limiting can hide the overlap; if wakes visibly double, ask Claude to stop its built-in watch with `TaskStop`.
|
|
276
276
|
|
|
277
277
|
Note that Solo mode suppresses annotation events on all three by design — chat still comes through. If you are testing real-time delivery and only chat arrives, check the mode toggle before debugging the transport.
|
|
278
278
|
|
package/dist/cli/index.js
CHANGED
|
@@ -101,19 +101,27 @@ var init_platform = __esm({
|
|
|
101
101
|
import { execFile } from "child_process";
|
|
102
102
|
import { join } from "path";
|
|
103
103
|
import { promisify } from "util";
|
|
104
|
+
function throwIfAborted(signal) {
|
|
105
|
+
if (signal?.aborted) {
|
|
106
|
+
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
104
109
|
function systemBin(name) {
|
|
105
110
|
return join(process.env.SystemRoot ?? "C:\\Windows", "System32", name);
|
|
106
111
|
}
|
|
107
|
-
async function runPowerShell(script, env) {
|
|
112
|
+
async function runPowerShell(script, env, signal) {
|
|
108
113
|
const args2 = ["-NoProfile", "-NonInteractive", "-Command", script];
|
|
114
|
+
throwIfAborted(signal);
|
|
109
115
|
try {
|
|
110
|
-
return await execFileAsync("pwsh.exe", args2, { env });
|
|
116
|
+
return await execFileAsync("pwsh.exe", args2, { env, signal });
|
|
111
117
|
} catch (err) {
|
|
118
|
+
if (signal?.aborted) throw signal.reason ?? err;
|
|
112
119
|
const code = err.code;
|
|
113
120
|
if (code !== "ENOENT") throw err;
|
|
114
121
|
try {
|
|
115
|
-
return await execFileAsync("powershell.exe", args2, { env });
|
|
122
|
+
return await execFileAsync("powershell.exe", args2, { env, signal });
|
|
116
123
|
} catch (fallbackErr) {
|
|
124
|
+
if (signal?.aborted) throw signal.reason ?? fallbackErr;
|
|
117
125
|
throw new Error(
|
|
118
126
|
`runPowerShell: both pwsh.exe and powershell.exe failed (pwsh: ${err.message})`,
|
|
119
127
|
{ cause: fallbackErr }
|
|
@@ -121,9 +129,13 @@ async function runPowerShell(script, env) {
|
|
|
121
129
|
}
|
|
122
130
|
}
|
|
123
131
|
}
|
|
124
|
-
async function getCurrentUserSid() {
|
|
132
|
+
async function getCurrentUserSid(signal) {
|
|
125
133
|
if (cachedCurrentUserSid !== null) return cachedCurrentUserSid;
|
|
126
|
-
|
|
134
|
+
throwIfAborted(signal);
|
|
135
|
+
const { stdout } = await execFileAsync(systemBin("whoami.exe"), ["/user", "/fo", "csv", "/nh"], {
|
|
136
|
+
signal
|
|
137
|
+
});
|
|
138
|
+
throwIfAborted(signal);
|
|
127
139
|
const match = stdout.match(/"(S-[\d-]+)"\s*$/m);
|
|
128
140
|
if (!match) {
|
|
129
141
|
throw new Error(`getCurrentUserSid: could not parse SID from whoami output: ${stdout.trim()}`);
|
|
@@ -133,29 +145,39 @@ async function getCurrentUserSid() {
|
|
|
133
145
|
}
|
|
134
146
|
async function setRestrictiveAcl(path9, opts = {}) {
|
|
135
147
|
if (process.platform !== "win32") return;
|
|
136
|
-
const
|
|
148
|
+
const { signal } = opts;
|
|
149
|
+
throwIfAborted(signal);
|
|
150
|
+
const sid = await getCurrentUserSid(signal);
|
|
137
151
|
const rights = opts.inheritable ? "(OI)(CI)F" : "F";
|
|
138
152
|
try {
|
|
139
|
-
await execFileAsync(
|
|
140
|
-
|
|
141
|
-
"/inheritance:r",
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
]);
|
|
153
|
+
await execFileAsync(
|
|
154
|
+
systemBin("icacls.exe"),
|
|
155
|
+
[path9, "/inheritance:r", "/grant:r", `*${sid}:${rights}`],
|
|
156
|
+
{ signal }
|
|
157
|
+
);
|
|
145
158
|
} catch (err) {
|
|
159
|
+
if (signal?.aborted) throw signal.reason ?? err;
|
|
146
160
|
throw new Error(`setRestrictiveAcl: icacls failed on ${path9}: ${err.message}`, {
|
|
147
161
|
cause: err
|
|
148
162
|
});
|
|
149
163
|
}
|
|
150
|
-
|
|
164
|
+
throwIfAborted(signal);
|
|
165
|
+
await assertNoBroadAce(path9, { signal });
|
|
151
166
|
}
|
|
152
|
-
async function assertNoBroadAce(path9) {
|
|
167
|
+
async function assertNoBroadAce(path9, opts = {}) {
|
|
153
168
|
if (process.platform !== "win32") return;
|
|
169
|
+
const { signal } = opts;
|
|
170
|
+
throwIfAborted(signal);
|
|
154
171
|
const script = "Import-Module Microsoft.PowerShell.Security; (Get-Acl -LiteralPath $env:TANDEM_ACL_PATH).Sddl";
|
|
155
|
-
const { stdout } = await runPowerShell(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
172
|
+
const { stdout } = await runPowerShell(
|
|
173
|
+
script,
|
|
174
|
+
{
|
|
175
|
+
...process.env,
|
|
176
|
+
TANDEM_ACL_PATH: path9
|
|
177
|
+
},
|
|
178
|
+
signal
|
|
179
|
+
);
|
|
180
|
+
throwIfAborted(signal);
|
|
159
181
|
const sddl = stdout.trim();
|
|
160
182
|
for (const fragment of BROAD_SDDL_FRAGMENTS) {
|
|
161
183
|
if (sddl.includes(fragment)) {
|
|
@@ -477,7 +499,7 @@ function resolveChannelDist(env = process.env, exists = existsSync) {
|
|
|
477
499
|
return resolve3(PACKAGE_ROOT, "dist/channel/index.js");
|
|
478
500
|
}
|
|
479
501
|
function resolveCliVersion() {
|
|
480
|
-
if (true) return "0.
|
|
502
|
+
if (true) return "0.22.0";
|
|
481
503
|
if (typeof __APP_VERSION__ !== "undefined") return __APP_VERSION__;
|
|
482
504
|
for (const rel of ["../../package.json", "../../../package.json"]) {
|
|
483
505
|
try {
|
|
@@ -634,19 +656,42 @@ function detectTargets(opts = {}) {
|
|
|
634
656
|
}
|
|
635
657
|
return targets;
|
|
636
658
|
}
|
|
637
|
-
|
|
659
|
+
function throwIfAborted2(signal) {
|
|
660
|
+
if (signal?.aborted) {
|
|
661
|
+
throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
async function atomicWrite(content, dest, opts = {}) {
|
|
665
|
+
const { signal } = opts;
|
|
666
|
+
throwIfAborted2(signal);
|
|
638
667
|
const tmp = join4(dirname2(dest), `.tandem-setup-${randomUUID2()}.tmp`);
|
|
639
|
-
|
|
668
|
+
try {
|
|
669
|
+
await writeFile(tmp, content, { encoding: "utf-8", mode: 384, signal });
|
|
670
|
+
} catch (writeErr) {
|
|
671
|
+
await unlinkOrLeak(tmp, writeErr);
|
|
672
|
+
throw writeErr;
|
|
673
|
+
}
|
|
640
674
|
try {
|
|
641
675
|
if (process.platform === "win32") {
|
|
642
|
-
await setRestrictiveAcl(tmp);
|
|
676
|
+
await setRestrictiveAcl(tmp, { signal });
|
|
643
677
|
} else {
|
|
678
|
+
throwIfAborted2(signal);
|
|
644
679
|
await chmod(tmp, 384);
|
|
645
680
|
}
|
|
681
|
+
throwIfAborted2(signal);
|
|
646
682
|
} catch (tightenErr) {
|
|
647
683
|
await unlinkOrLeak(tmp, tightenErr);
|
|
648
684
|
throw tightenErr;
|
|
649
685
|
}
|
|
686
|
+
try {
|
|
687
|
+
if (process.env.VITEST === "true") await opts.beforeCommitForTests?.();
|
|
688
|
+
throwIfAborted2(signal);
|
|
689
|
+
if (opts.requireExistingDest) await stat(dest);
|
|
690
|
+
throwIfAborted2(signal);
|
|
691
|
+
} catch (commitGuardErr) {
|
|
692
|
+
await unlinkOrLeak(tmp, commitGuardErr);
|
|
693
|
+
throw commitGuardErr;
|
|
694
|
+
}
|
|
650
695
|
try {
|
|
651
696
|
await rename(tmp, dest);
|
|
652
697
|
} catch (err) {
|
|
@@ -665,6 +710,7 @@ async function unlinkOrLeak(path9, originalErr) {
|
|
|
665
710
|
try {
|
|
666
711
|
await unlink(path9);
|
|
667
712
|
} catch (cleanupErr) {
|
|
713
|
+
if (cleanupErr.code === "ENOENT") return;
|
|
668
714
|
if (originalErr instanceof Error && originalErr.cause === void 0) {
|
|
669
715
|
originalErr.cause = cleanupErr;
|
|
670
716
|
}
|
|
@@ -3531,21 +3577,21 @@ function evaluatePushPath(push) {
|
|
|
3531
3577
|
return {
|
|
3532
3578
|
status: "warn",
|
|
3533
3579
|
message: "No real-time push consumer attached \u2014 Claude is not notified when you comment or send a chat message, and only sees them when it polls its inbox",
|
|
3534
|
-
fix: "This affects sessions you start yourself; sessions Tandem launches are woken directly and do not use this path. Simplest first: ask Claude to watch for updates \u2014 it can arm a watch on Tandem's wake stream itself, with nothing to install and no flag (the bundled skill tells it how; `tandem_status` reports the address). That one needs a Monitor tool, which not every Claude Code has \u2014 it is enabled per account rather than per version, so upgrading will not add it, and on Windows it also needs Git Bash. If Claude says it has none,
|
|
3580
|
+
fix: "This affects sessions you start yourself; sessions Tandem launches are woken directly and do not use this path. Simplest first: ask Claude to watch for updates \u2014 it can arm a watch on Tandem's wake stream itself, with nothing to install and no flag (the bundled skill tells it how; `tandem_status` reports the address). That one needs a Monitor tool, which not every Claude Code has \u2014 it is enabled per account rather than per version, so upgrading will not add it, and on Windows it also needs Git Bash. If Claude says it has none, the channel shim below is the option that always avoids Monitor. The plugin monitor shares that per-account feature gate, so it will not help if the gate is off; however, the plugin monitor does not require Git Bash on Windows and can fall back to PowerShell, so it can help when Git Bash is the missing precondition. You can install the Tandem plugin, which registers a monitor needing no flag (`claude plugin list` to check) \u2014 start `claude` from a terminal if you do, since the monitor inherits that shell's PATH and cannot find Node without it \u2014 or register the channel shim with `tandem setup --apply --with-channel-shim` and start Claude Code with `--dangerously-load-development-channels server:tandem-channel`. Choose one setup route where possible. When the plugin and built-in Monitor are both available, invoking Tandem's skill can start both automatically; doubled wakes carry no content and the inbox de-duplicates, but they can waste a turn. Ask Claude to stop its built-in watch with `TaskStop` if that happens.",
|
|
3535
3581
|
data
|
|
3536
3582
|
};
|
|
3537
3583
|
}
|
|
3538
3584
|
if (eventCount === 0) {
|
|
3539
3585
|
return {
|
|
3540
3586
|
status: "pass",
|
|
3541
|
-
message: `${subscribers} push consumer(s) attached; no events delivered yet this run (expected if you haven't edited, or if you're in Solo mode)`,
|
|
3587
|
+
message: `${subscribers} push consumer(s) attached; no events delivered yet this run (expected if you haven't edited, or if you're in Solo mode; this is a process-global count, NOT that this Claude session is covered)`,
|
|
3542
3588
|
data
|
|
3543
3589
|
};
|
|
3544
3590
|
}
|
|
3545
3591
|
const agoS = p.lastEventAt ? Math.round((Date.now() - p.lastEventAt) / 1e3) : null;
|
|
3546
3592
|
return {
|
|
3547
3593
|
status: "pass",
|
|
3548
|
-
message: `${subscribers} push consumer(s) attached; ${eventCount} event(s) delivered this run, most recently ${agoS === null ? "?" : `${agoS}s`} ago (confirms events reach a consumer, NOT that Claude
|
|
3594
|
+
message: `${subscribers} push consumer(s) attached; ${eventCount} event(s) delivered this run, most recently ${agoS === null ? "?" : `${agoS}s`} ago (confirms events reach a process-global consumer, NOT that this Claude session is covered or saw them)`,
|
|
3549
3595
|
data
|
|
3550
3596
|
};
|
|
3551
3597
|
}
|
|
@@ -3755,7 +3801,7 @@ function evaluateStaleGlobal(bundled, globalVersion) {
|
|
|
3755
3801
|
};
|
|
3756
3802
|
}
|
|
3757
3803
|
async function checkStaleGlobal(r) {
|
|
3758
|
-
const bundled = true ? "0.
|
|
3804
|
+
const bundled = true ? "0.22.0" : null;
|
|
3759
3805
|
if (!bundled) return;
|
|
3760
3806
|
let globalVersion;
|
|
3761
3807
|
try {
|
|
@@ -4862,7 +4908,7 @@ process.once("unhandledRejection", (reason) => {
|
|
|
4862
4908
|
`);
|
|
4863
4909
|
process.exit(1);
|
|
4864
4910
|
});
|
|
4865
|
-
var version = true ? "0.
|
|
4911
|
+
var version = true ? "0.22.0" : "0.0.0-dev";
|
|
4866
4912
|
var args = process.argv.slice(2);
|
|
4867
4913
|
if (args.includes("--help") || args.includes("-h")) {
|
|
4868
4914
|
console.log(`tandem v${version}
|