switchroom 0.17.3 → 0.17.5
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/dist/cli/switchroom.js +2 -2
- package/dist/host-control/main.js +2 -2
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +14 -0
- package/telegram-plugin/dist/gateway/gateway.js +7 -6
- package/telegram-plugin/gateway/gateway.ts +13 -2
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +26 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -63737,8 +63737,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
63737
63737
|
import { dirname, join } from "node:path";
|
|
63738
63738
|
|
|
63739
63739
|
// src/build-info.ts
|
|
63740
|
-
var VERSION = "0.17.
|
|
63741
|
-
var COMMIT_SHA = "
|
|
63740
|
+
var VERSION = "0.17.5";
|
|
63741
|
+
var COMMIT_SHA = "aae03307";
|
|
63742
63742
|
|
|
63743
63743
|
// src/cli/resolve-version.ts
|
|
63744
63744
|
function readPackageVersion() {
|
|
@@ -22670,7 +22670,7 @@ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:f
|
|
|
22670
22670
|
import { dirname as dirname4, join as join2 } from "node:path";
|
|
22671
22671
|
|
|
22672
22672
|
// src/build-info.ts
|
|
22673
|
-
var VERSION = "0.17.
|
|
22673
|
+
var VERSION = "0.17.5";
|
|
22674
22674
|
|
|
22675
22675
|
// src/cli/resolve-version.ts
|
|
22676
22676
|
function readPackageVersion() {
|
|
@@ -24819,7 +24819,7 @@ class HostdServer {
|
|
|
24819
24819
|
});
|
|
24820
24820
|
return this.reconcileFailedRolledBack(`write failed: ${e.message}`, req, caller, started);
|
|
24821
24821
|
}
|
|
24822
|
-
const runner = this.opts.runReconcile ?? (async () => this.runSwitchroom(["apply"]));
|
|
24822
|
+
const runner = this.opts.runReconcile ?? (async () => this.runSwitchroom(caller.kind === "agent" ? ["apply", "--only", callerName, "--non-interactive"] : ["apply", "--non-interactive"]));
|
|
24823
24823
|
const recRes = await runner({ requestId: approvalId });
|
|
24824
24824
|
if (recRes.exit_code === 0) {
|
|
24825
24825
|
const blast = classifyBlastRadius(snapshot, postApply);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
3
|
"//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
|
|
4
|
-
"version": "0.17.
|
|
4
|
+
"version": "0.17.5",
|
|
5
5
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -303,12 +303,26 @@ for _stray_claude in \
|
|
|
303
303
|
"$HOME/.local/bin/claude" \
|
|
304
304
|
"$HOME/bin/claude"; do
|
|
305
305
|
if [ -e "$_stray_claude" ] || [ -L "$_stray_claude" ]; then
|
|
306
|
+
# Skip our own canonical symlink re-seated at end of this block — it points
|
|
307
|
+
# back to the image binary and is not a shadow install. Any other file or
|
|
308
|
+
# symlink at this path (e.g. npm self-install, manual copy) is pruned.
|
|
309
|
+
if [ -L "$_stray_claude" ] && [ "$(readlink "$_stray_claude" 2>/dev/null)" = "/usr/local/bin/claude" ]; then
|
|
310
|
+
continue
|
|
311
|
+
fi
|
|
306
312
|
echo "start.sh: pruning user-local claude shadow at $_stray_claude (image binary is authoritative)" >&2
|
|
307
313
|
rm -f "$_stray_claude"
|
|
308
314
|
fi
|
|
309
315
|
done
|
|
310
316
|
rm -rf "$HOME/.npm-global/lib/node_modules/@anthropic-ai/claude-code" 2>/dev/null || true
|
|
311
317
|
unset _stray_claude
|
|
318
|
+
# Re-seat the canonical symlink so claude v2.1.x+ self-check ("claude command
|
|
319
|
+
# at $HOME/.local/bin/claude missing or broken") stays silent. The prune loop
|
|
320
|
+
# above skips this canonical pointer (via the readlink guard) and removes only
|
|
321
|
+
# genuine shadow installs; the ln -sf below creates the symlink on first boot
|
|
322
|
+
# and is a no-op on subsequent boots. `mkdir -p` is safe: the dir already exists
|
|
323
|
+
# or is about to be created.
|
|
324
|
+
mkdir -p "$HOME/.local/bin"
|
|
325
|
+
ln -sf /usr/local/bin/claude "$HOME/.local/bin/claude"
|
|
312
326
|
|
|
313
327
|
# ── Root-tier agent: provision the docker CLI ────────────────────────
|
|
314
328
|
# The root debugging agent (`root: true`) has /var/run/docker.sock
|
|
@@ -58590,10 +58590,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
58590
58590
|
}
|
|
58591
58591
|
|
|
58592
58592
|
// ../src/build-info.ts
|
|
58593
|
-
var VERSION = "0.17.
|
|
58594
|
-
var COMMIT_SHA = "
|
|
58595
|
-
var COMMIT_DATE = "2026-07-
|
|
58596
|
-
var LATEST_PR =
|
|
58593
|
+
var VERSION = "0.17.5";
|
|
58594
|
+
var COMMIT_SHA = "aae03307";
|
|
58595
|
+
var COMMIT_DATE = "2026-07-05T22:09:14+10:00";
|
|
58596
|
+
var LATEST_PR = null;
|
|
58597
58597
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
58598
58598
|
|
|
58599
58599
|
// gateway/boot-version.ts
|
|
@@ -60650,9 +60650,10 @@ var FEED_LIVENESS_OPEN_MS = (() => {
|
|
|
60650
60650
|
})();
|
|
60651
60651
|
var POST_ANSWER_LIVENESS_STALE_MS = parsePostAnswerLivenessMs(process.env.SWITCHROOM_POST_ANSWER_LIVENESS_STALE_MS) || 30000;
|
|
60652
60652
|
function turnInFlightForGate() {
|
|
60653
|
+
const hasPendingApproval = pendingPermissions.size > 0;
|
|
60653
60654
|
if (!isDeliveryCutoverEnabled())
|
|
60654
|
-
return claudeBusyKeys.size > 0;
|
|
60655
|
-
return probeGateParity(isMachineInTurn(), claudeBusyKeys.size);
|
|
60655
|
+
return claudeBusyKeys.size > 0 || hasPendingApproval;
|
|
60656
|
+
return probeGateParity(isMachineInTurn(), claudeBusyKeys.size) || hasPendingApproval;
|
|
60656
60657
|
}
|
|
60657
60658
|
function deliverResumeSyntheticOrBuffer(agent, inbound) {
|
|
60658
60659
|
const decision = decideInboundDelivery({
|
|
@@ -2039,14 +2039,25 @@ const POST_ANSWER_LIVENESS_STALE_MS = parsePostAnswerLivenessMs(
|
|
|
2039
2039
|
* message self-blocks. See the snapshot at the inbound handler.
|
|
2040
2040
|
*/
|
|
2041
2041
|
function turnInFlightForGate(): boolean {
|
|
2042
|
-
|
|
2042
|
+
// Include pendingPermissions in the "turn busy" signal (#2841): after the
|
|
2043
|
+
// first interim reply, `releaseTurnBufferGate` clears claudeBusyKeys/machine
|
|
2044
|
+
// even though a permission card is still outstanding and claude is still
|
|
2045
|
+
// blocked mid-turn waiting for the tap verdict. A new inbound that arrives
|
|
2046
|
+
// in this window would see turnInFlightAtReceipt=false and deliver directly,
|
|
2047
|
+
// landing while claude's bridge is suspended in the permission-notification
|
|
2048
|
+
// handler — displacing the approval and orphaning the pending brevo/MCP call.
|
|
2049
|
+
// Keeping the gate closed for as long as there is an outstanding approval
|
|
2050
|
+
// card prevents that race. The gate reopens when the card is tapped or times
|
|
2051
|
+
// out (pendingPermissions.delete in finalizeCallback / TTL sweep).
|
|
2052
|
+
const hasPendingApproval = pendingPermissions.size > 0
|
|
2053
|
+
if (!isDeliveryCutoverEnabled()) return claudeBusyKeys.size > 0 || hasPendingApproval
|
|
2043
2054
|
// Machine is authoritative. Run the log-only drift canary (#2794): the
|
|
2044
2055
|
// imperative `claudeBusyKeys` shadow is still live in parallel, so a
|
|
2045
2056
|
// dangerous over-hold divergence (machine holds the gate while the
|
|
2046
2057
|
// imperative view is idle) is surfaced without changing behaviour. The
|
|
2047
2058
|
// benign orphan-dangle direction — the wedge the machine self-heals — is
|
|
2048
2059
|
// NOT flagged. `probeGateParity` returns the machine value unchanged.
|
|
2049
|
-
return probeGateParity(isMachineInTurn(), claudeBusyKeys.size)
|
|
2060
|
+
return probeGateParity(isMachineInTurn(), claudeBusyKeys.size) || hasPendingApproval
|
|
2050
2061
|
}
|
|
2051
2062
|
|
|
2052
2063
|
/**
|
|
@@ -84,3 +84,29 @@ describe('no-repeat-on-timeout wiring', () => {
|
|
|
84
84
|
expect(GATEWAY).toMatch(/permissionTimeoutSignatures\.delete\(sig\)/)
|
|
85
85
|
})
|
|
86
86
|
})
|
|
87
|
+
|
|
88
|
+
describe('inbound gate holds while approval card is outstanding (#2841)', () => {
|
|
89
|
+
// turnInFlightForGate() must return true when pendingPermissions is non-empty,
|
|
90
|
+
// even after releaseTurnBufferGate has cleared claudeBusyKeys/machine on the
|
|
91
|
+
// first interim reply. Without this, a new inbound delivered in that window
|
|
92
|
+
// displaces the approval context and orphans the pending MCP call.
|
|
93
|
+
it('turnInFlightForGate includes pendingPermissions.size > 0 on the legacy path', () => {
|
|
94
|
+
// span 1500: the function has a ~800-char comment block before the code lines.
|
|
95
|
+
const fn = slice(GATEWAY, 'function turnInFlightForGate()', 1500)
|
|
96
|
+
// Both paths (legacy claudeBusyKeys + machine-authoritative) must include the check.
|
|
97
|
+
expect(fn).toMatch(/claudeBusyKeys\.size\s*>\s*0\s*\|\|\s*hasPendingApproval/)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('turnInFlightForGate includes pendingPermissions.size > 0 on the machine path', () => {
|
|
101
|
+
const fn = slice(GATEWAY, 'function turnInFlightForGate()', 1600)
|
|
102
|
+
// probeGateParity(...) || hasPendingApproval — the inner arg contains its own
|
|
103
|
+
// parens (isMachineInTurn()), so match the two tokens independently.
|
|
104
|
+
expect(fn).toContain('probeGateParity(')
|
|
105
|
+
expect(fn).toMatch(/probeGateParity[\s\S]+?\|\|\s*hasPendingApproval/)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('hasPendingApproval reads pendingPermissions.size', () => {
|
|
109
|
+
const fn = slice(GATEWAY, 'function turnInFlightForGate()', 1500)
|
|
110
|
+
expect(fn).toMatch(/pendingPermissions\.size\s*>\s*0/)
|
|
111
|
+
})
|
|
112
|
+
})
|