@snowyroad/braid 0.84.1 → 0.86.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.
|
@@ -389,7 +389,7 @@ var InviteExpiredError = class extends Error {
|
|
|
389
389
|
}
|
|
390
390
|
};
|
|
391
391
|
async function redeemInvite(relayHttpUrl, code) {
|
|
392
|
-
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-
|
|
392
|
+
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-LZV3OAR2.js");
|
|
393
393
|
const r = await boundedJsonFetch2(`${relayHttpUrl.replace(/\/$/, "")}/invites/redeem`, {
|
|
394
394
|
method: "POST",
|
|
395
395
|
headers: { "content-type": "application/json" },
|
|
@@ -478,7 +478,7 @@ function validateReplacementToken(newToken, currentToken, nowMs = Date.now()) {
|
|
|
478
478
|
return { ok: true };
|
|
479
479
|
}
|
|
480
480
|
async function mintAccessToken(relayHttpUrl, agentKey, fetchFn = fetch, dpopProof) {
|
|
481
|
-
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-
|
|
481
|
+
const { boundedJsonFetch: boundedJsonFetch2 } = await import("./http-LZV3OAR2.js");
|
|
482
482
|
const r = await boundedJsonFetch2(`${relayHttpUrl.replace(/\/$/, "")}/agents/token`, {
|
|
483
483
|
method: "POST",
|
|
484
484
|
headers: {
|
|
@@ -3120,6 +3120,27 @@ var RelayClient = class {
|
|
|
3120
3120
|
// single-shot: onReady has fired (relay accepted the agent)
|
|
3121
3121
|
reconnectAnnounced = false;
|
|
3122
3122
|
// per-connection: "reconnected" printed for this connection
|
|
3123
|
+
connEventCb = null;
|
|
3124
|
+
/** Subscribe the connection-status sink (single subscriber, like onReady). When set,
|
|
3125
|
+
* the seven legacy per-event console lines are suppressed: the sink owns presentation
|
|
3126
|
+
* (connStatus.ts aggregates episodes + applies verbosity). When absent, behavior is
|
|
3127
|
+
* byte-identical to before (plain per-event lines). */
|
|
3128
|
+
onConnEvent(cb) {
|
|
3129
|
+
this.connEventCb = cb;
|
|
3130
|
+
}
|
|
3131
|
+
reportConn(e, legacy) {
|
|
3132
|
+
if (this.connEventCb) this.connEventCb(e);
|
|
3133
|
+
else legacy?.();
|
|
3134
|
+
}
|
|
3135
|
+
/** Current token's JWT exp (epoch seconds), for status display. Non-JWT/garbage -> undefined. */
|
|
3136
|
+
tokenExpSeconds() {
|
|
3137
|
+
try {
|
|
3138
|
+
const exp = decodeJwt(this.cfg.token).exp;
|
|
3139
|
+
return typeof exp === "number" ? exp : void 0;
|
|
3140
|
+
} catch {
|
|
3141
|
+
return void 0;
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3123
3144
|
catchUpCbs = [];
|
|
3124
3145
|
caughtUp = /* @__PURE__ */ new Set();
|
|
3125
3146
|
// channels caught up this connection
|
|
@@ -3298,9 +3319,9 @@ var RelayClient = class {
|
|
|
3298
3319
|
sock.terminate();
|
|
3299
3320
|
} catch {
|
|
3300
3321
|
}
|
|
3301
|
-
console.log(
|
|
3322
|
+
this.reportConn({ kind: "connectTimeout", timeoutSecs: CONNECT_TIMEOUT_MS / 1e3 }, () => console.log(
|
|
3302
3323
|
`[braid] connect attempt timed out after ${CONNECT_TIMEOUT_MS / 1e3}s (socket accepted but the relay never completed the handshake); retrying`
|
|
3303
|
-
);
|
|
3324
|
+
));
|
|
3304
3325
|
const delay2 = this.backoffDelay();
|
|
3305
3326
|
this.reconnectAttempts++;
|
|
3306
3327
|
this.reconnectTimer = setTimeout(() => {
|
|
@@ -3337,12 +3358,13 @@ var RelayClient = class {
|
|
|
3337
3358
|
if (this.confirmed) {
|
|
3338
3359
|
if (!this.reconnectAnnounced) {
|
|
3339
3360
|
this.reconnectAnnounced = true;
|
|
3340
|
-
console.log("[braid] reconnected");
|
|
3361
|
+
this.reportConn({ kind: "reconnected", tokenExp: this.tokenExpSeconds() }, () => console.log("[braid] reconnected"));
|
|
3341
3362
|
this.connectedCb?.();
|
|
3342
3363
|
}
|
|
3343
3364
|
return;
|
|
3344
3365
|
}
|
|
3345
3366
|
this.confirmed = true;
|
|
3367
|
+
this.reportConn({ kind: "connected", tokenExp: this.tokenExpSeconds() });
|
|
3346
3368
|
this.readyCb?.();
|
|
3347
3369
|
this.connectedCb?.();
|
|
3348
3370
|
}
|
|
@@ -3491,9 +3513,12 @@ var RelayClient = class {
|
|
|
3491
3513
|
onClose(code, reason) {
|
|
3492
3514
|
this.clearTimers();
|
|
3493
3515
|
if (this.stopped) return;
|
|
3494
|
-
|
|
3516
|
+
this.reportConn(
|
|
3517
|
+
{ kind: "disconnected", code, reason: reason ? sanitizeForTty(reason) : "" },
|
|
3518
|
+
() => console.log(`[braid] disconnected (close ${code})${reason ? `: ${sanitizeForTty(reason)}` : ""}`)
|
|
3519
|
+
);
|
|
3495
3520
|
if (code === 4001 && this.cfg.mintToken) {
|
|
3496
|
-
console.log("[braid] access token rejected; re-minting from agent key");
|
|
3521
|
+
this.reportConn({ kind: "remintStarted" }, () => console.log("[braid] access token rejected; re-minting from agent key"));
|
|
3497
3522
|
this.attemptRemint(code);
|
|
3498
3523
|
return;
|
|
3499
3524
|
}
|
|
@@ -3654,7 +3679,7 @@ var RelayClient = class {
|
|
|
3654
3679
|
return;
|
|
3655
3680
|
}
|
|
3656
3681
|
this.cfg.token = token;
|
|
3657
|
-
console.log("[braid] token proactively re-minted before expiry");
|
|
3682
|
+
this.reportConn({ kind: "tokenReminted", tokenExp: this.tokenExpSeconds() }, () => console.log("[braid] token proactively re-minted before expiry"));
|
|
3658
3683
|
this.scheduleTokenRefresh();
|
|
3659
3684
|
}
|
|
3660
3685
|
stop() {
|
|
@@ -3699,7 +3724,7 @@ var RelayClient = class {
|
|
|
3699
3724
|
}
|
|
3700
3725
|
this.cfg.token = msg.token;
|
|
3701
3726
|
this.scheduleTokenRefresh();
|
|
3702
|
-
console.log("[braid] token refreshed");
|
|
3727
|
+
this.reportConn({ kind: "tokenRefreshed", tokenExp: this.tokenExpSeconds() }, () => console.log("[braid] token refreshed"));
|
|
3703
3728
|
break;
|
|
3704
3729
|
}
|
|
3705
3730
|
case "removed": {
|
package/dist/cli.js
CHANGED
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
toolStatusLine,
|
|
71
71
|
validateRelayWsUrl,
|
|
72
72
|
wsToHttp
|
|
73
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-XENNQYCX.js";
|
|
74
74
|
import {
|
|
75
75
|
externalSourceLabel,
|
|
76
76
|
externalUntrustedPreamble,
|
|
@@ -2902,7 +2902,9 @@ Options for join/start (each also has an env twin; flag > env > file > default):
|
|
|
2902
2902
|
--allow-read <path> widen sandbox read access (repeatable)
|
|
2903
2903
|
--allow-domain <host> widen sandbox network egress (repeatable)
|
|
2904
2904
|
--no-socket do not open the local control socket (on by default)
|
|
2905
|
-
--verbose print
|
|
2905
|
+
--verbose print every connection lifecycle event (token refreshes, each
|
|
2906
|
+
disconnect) instead of the default per-episode summaries; also
|
|
2907
|
+
prints the full posture detail during the guided first-run
|
|
2906
2908
|
setup (an interactive first start walks provider/permission
|
|
2907
2909
|
prompts and offers to keep the agent running in the
|
|
2908
2910
|
background; details any time via: braid status <name>)
|
|
@@ -5502,6 +5504,225 @@ function installTee(outWriter, errWriter) {
|
|
|
5502
5504
|
|
|
5503
5505
|
// src/cli.ts
|
|
5504
5506
|
import { homedir as homedir6 } from "os";
|
|
5507
|
+
|
|
5508
|
+
// src/connStatus.ts
|
|
5509
|
+
var ESCALATE_FIRST_MS = 6e4;
|
|
5510
|
+
var ESCALATE_REPEAT_MS = 3e5;
|
|
5511
|
+
function fmtDur(ms) {
|
|
5512
|
+
const s = Math.max(0, Math.floor(ms / 1e3));
|
|
5513
|
+
if (s < 60) return `${s}s`;
|
|
5514
|
+
const m = Math.floor(s / 60);
|
|
5515
|
+
if (m < 60) return `${m}m${String(s % 60).padStart(2, "0")}s`;
|
|
5516
|
+
return `${Math.floor(m / 60)}h${String(m % 60).padStart(2, "0")}m`;
|
|
5517
|
+
}
|
|
5518
|
+
function fmtClock(epochMs) {
|
|
5519
|
+
const d = new Date(epochMs);
|
|
5520
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
5521
|
+
}
|
|
5522
|
+
function plural(n, one, many) {
|
|
5523
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
5524
|
+
}
|
|
5525
|
+
function createConnStatus(opts) {
|
|
5526
|
+
const now = opts.now ?? Date.now;
|
|
5527
|
+
const startedAt = now();
|
|
5528
|
+
let state = "starting";
|
|
5529
|
+
let connectedSince = null;
|
|
5530
|
+
let offlineSince = null;
|
|
5531
|
+
let attempts = 0;
|
|
5532
|
+
let reconnects = 0;
|
|
5533
|
+
let lastReconnectAt = null;
|
|
5534
|
+
let tokenExp = null;
|
|
5535
|
+
let lastCloseCode = null;
|
|
5536
|
+
let nextEscalateAt = null;
|
|
5537
|
+
function noteDrop() {
|
|
5538
|
+
if (state !== "offline") {
|
|
5539
|
+
offlineSince = now();
|
|
5540
|
+
attempts = 0;
|
|
5541
|
+
nextEscalateAt = offlineSince + ESCALATE_FIRST_MS;
|
|
5542
|
+
}
|
|
5543
|
+
state = "offline";
|
|
5544
|
+
connectedSince = null;
|
|
5545
|
+
attempts++;
|
|
5546
|
+
}
|
|
5547
|
+
function closeEpisode(t) {
|
|
5548
|
+
if (offlineSince === null || attempts === 0) return null;
|
|
5549
|
+
const down = fmtDur(t - offlineSince);
|
|
5550
|
+
const suffix = attempts > 1 ? `${down}, ${plural(attempts, "attempt", "attempts")}` : down;
|
|
5551
|
+
offlineSince = null;
|
|
5552
|
+
nextEscalateAt = null;
|
|
5553
|
+
return suffix;
|
|
5554
|
+
}
|
|
5555
|
+
function adoptExp(exp) {
|
|
5556
|
+
if (exp !== void 0) tokenExp = exp;
|
|
5557
|
+
}
|
|
5558
|
+
function handle(e) {
|
|
5559
|
+
switch (e.kind) {
|
|
5560
|
+
case "connected": {
|
|
5561
|
+
const t = now();
|
|
5562
|
+
const hadRetries = offlineSince !== null && attempts > 0;
|
|
5563
|
+
const n = attempts;
|
|
5564
|
+
const since = offlineSince;
|
|
5565
|
+
const ep = closeEpisode(t);
|
|
5566
|
+
state = "connected";
|
|
5567
|
+
connectedSince = t;
|
|
5568
|
+
adoptExp(e.tokenExp);
|
|
5569
|
+
if (hadRetries && ep !== null && since !== null) {
|
|
5570
|
+
opts.log(`[braid] connected (after ${plural(n, "attempt", "attempts")}, ${fmtDur(t - since)})`);
|
|
5571
|
+
}
|
|
5572
|
+
break;
|
|
5573
|
+
}
|
|
5574
|
+
case "reconnected": {
|
|
5575
|
+
const t = now();
|
|
5576
|
+
const ep = closeEpisode(t);
|
|
5577
|
+
state = "connected";
|
|
5578
|
+
connectedSince = t;
|
|
5579
|
+
reconnects++;
|
|
5580
|
+
lastReconnectAt = t;
|
|
5581
|
+
adoptExp(e.tokenExp);
|
|
5582
|
+
opts.log(`[braid] reconnected${ep !== null ? ` (offline ${ep})` : ""}`);
|
|
5583
|
+
break;
|
|
5584
|
+
}
|
|
5585
|
+
case "disconnected":
|
|
5586
|
+
lastCloseCode = e.code;
|
|
5587
|
+
noteDrop();
|
|
5588
|
+
if (opts.verbose) {
|
|
5589
|
+
opts.log(`[braid] disconnected (close ${e.code})${e.reason ? `: ${e.reason}` : ""}`);
|
|
5590
|
+
}
|
|
5591
|
+
break;
|
|
5592
|
+
case "connectTimeout":
|
|
5593
|
+
noteDrop();
|
|
5594
|
+
if (opts.verbose) {
|
|
5595
|
+
opts.log(
|
|
5596
|
+
`[braid] connect attempt timed out after ${e.timeoutSecs}s (socket accepted but the relay never completed the handshake); retrying`
|
|
5597
|
+
);
|
|
5598
|
+
}
|
|
5599
|
+
break;
|
|
5600
|
+
case "remintStarted":
|
|
5601
|
+
if (opts.verbose) opts.log("[braid] access token rejected; re-minting from agent key");
|
|
5602
|
+
break;
|
|
5603
|
+
case "tokenRefreshed":
|
|
5604
|
+
adoptExp(e.tokenExp);
|
|
5605
|
+
if (opts.verbose) opts.log("[braid] token refreshed");
|
|
5606
|
+
break;
|
|
5607
|
+
case "tokenReminted":
|
|
5608
|
+
adoptExp(e.tokenExp);
|
|
5609
|
+
if (opts.verbose) opts.log("[braid] token proactively re-minted before expiry");
|
|
5610
|
+
break;
|
|
5611
|
+
}
|
|
5612
|
+
}
|
|
5613
|
+
function tick() {
|
|
5614
|
+
if (state !== "offline" || offlineSince === null || nextEscalateAt === null) return;
|
|
5615
|
+
const t = now();
|
|
5616
|
+
if (t >= nextEscalateAt) {
|
|
5617
|
+
opts.warn(
|
|
5618
|
+
`[braid] still reconnecting: offline ${fmtDur(t - offlineSince)} (${plural(attempts, "attempt", "attempts")})`
|
|
5619
|
+
);
|
|
5620
|
+
nextEscalateAt = t + ESCALATE_REPEAT_MS;
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5623
|
+
function snapshot() {
|
|
5624
|
+
return { state, startedAt, connectedSince, offlineSince, attempts, reconnects, lastReconnectAt, tokenExp, lastCloseCode };
|
|
5625
|
+
}
|
|
5626
|
+
return { handle, tick, snapshot };
|
|
5627
|
+
}
|
|
5628
|
+
|
|
5629
|
+
// src/statusLine.ts
|
|
5630
|
+
var ESC = String.fromCharCode(27);
|
|
5631
|
+
var CLEAR = `\r${ESC}[2K`;
|
|
5632
|
+
var RAW_QUIESCE_MS = 2e3;
|
|
5633
|
+
function createStickyLine(out, now = Date.now) {
|
|
5634
|
+
const enabled = out.isTTY === true;
|
|
5635
|
+
let last = "";
|
|
5636
|
+
let painted = false;
|
|
5637
|
+
let paused = false;
|
|
5638
|
+
let stopped = false;
|
|
5639
|
+
let lastRawWriteAt = -Infinity;
|
|
5640
|
+
const width = () => typeof out.columns === "number" && out.columns > 0 ? out.columns : 80;
|
|
5641
|
+
const active = () => enabled && !stopped && !paused;
|
|
5642
|
+
return {
|
|
5643
|
+
enabled,
|
|
5644
|
+
render(line) {
|
|
5645
|
+
if (!active()) return;
|
|
5646
|
+
last = line.slice(0, Math.max(0, width() - 1));
|
|
5647
|
+
out.write(`${CLEAR}${last}`);
|
|
5648
|
+
painted = true;
|
|
5649
|
+
},
|
|
5650
|
+
repaint() {
|
|
5651
|
+
if (!active() || last === "") return;
|
|
5652
|
+
out.write(`${CLEAR}${last}`);
|
|
5653
|
+
painted = true;
|
|
5654
|
+
},
|
|
5655
|
+
clearIfPainted() {
|
|
5656
|
+
if (!enabled || stopped || !painted) return;
|
|
5657
|
+
out.write(CLEAR);
|
|
5658
|
+
painted = false;
|
|
5659
|
+
},
|
|
5660
|
+
noteRawWrite() {
|
|
5661
|
+
this.clearIfPainted();
|
|
5662
|
+
lastRawWriteAt = now();
|
|
5663
|
+
},
|
|
5664
|
+
quiesced() {
|
|
5665
|
+
return now() - lastRawWriteAt >= RAW_QUIESCE_MS;
|
|
5666
|
+
},
|
|
5667
|
+
pause() {
|
|
5668
|
+
if (!enabled || stopped || paused) return;
|
|
5669
|
+
this.clearIfPainted();
|
|
5670
|
+
paused = true;
|
|
5671
|
+
},
|
|
5672
|
+
resume() {
|
|
5673
|
+
paused = false;
|
|
5674
|
+
},
|
|
5675
|
+
stop() {
|
|
5676
|
+
if (enabled && !stopped) this.clearIfPainted();
|
|
5677
|
+
stopped = true;
|
|
5678
|
+
}
|
|
5679
|
+
};
|
|
5680
|
+
}
|
|
5681
|
+
function installConsoleBridge(sticky) {
|
|
5682
|
+
const orig = { log: console.log, warn: console.warn, error: console.error };
|
|
5683
|
+
const wrap = (fn) => (...a) => {
|
|
5684
|
+
sticky.clearIfPainted();
|
|
5685
|
+
fn(...a);
|
|
5686
|
+
sticky.repaint();
|
|
5687
|
+
};
|
|
5688
|
+
console.log = wrap(orig.log.bind(console));
|
|
5689
|
+
console.warn = wrap(orig.warn.bind(console));
|
|
5690
|
+
console.error = wrap(orig.error.bind(console));
|
|
5691
|
+
return () => {
|
|
5692
|
+
console.log = orig.log;
|
|
5693
|
+
console.warn = orig.warn;
|
|
5694
|
+
console.error = orig.error;
|
|
5695
|
+
};
|
|
5696
|
+
}
|
|
5697
|
+
function stickyWriteThrough(sticky, real) {
|
|
5698
|
+
return {
|
|
5699
|
+
isTTY: real.isTTY,
|
|
5700
|
+
write(s) {
|
|
5701
|
+
sticky.noteRawWrite();
|
|
5702
|
+
real.write(s);
|
|
5703
|
+
}
|
|
5704
|
+
};
|
|
5705
|
+
}
|
|
5706
|
+
function formatStatusLine(s, agentName, now) {
|
|
5707
|
+
const name = sanitizeForTty(agentName) || "agent";
|
|
5708
|
+
if (s.state === "connected" && s.connectedSince !== null) {
|
|
5709
|
+
const parts = [`\u25CF braid \xB7 ${name}`, "connected", `up ${fmtDur(now - s.startedAt)}`];
|
|
5710
|
+
if (s.reconnects > 0 && s.lastReconnectAt !== null) {
|
|
5711
|
+
parts.push(`${s.reconnects} ${s.reconnects === 1 ? "reconnect" : "reconnects"} (last ${fmtClock(s.lastReconnectAt)})`);
|
|
5712
|
+
}
|
|
5713
|
+
if (s.tokenExp !== null) {
|
|
5714
|
+
const left = s.tokenExp * 1e3 - now;
|
|
5715
|
+
parts.push(left > 0 ? `token expires in ${fmtDur(left)}` : "token renewing");
|
|
5716
|
+
}
|
|
5717
|
+
return parts.join(" \xB7 ");
|
|
5718
|
+
}
|
|
5719
|
+
if (s.state === "offline" && s.offlineSince !== null) {
|
|
5720
|
+
return `\u25CC braid \xB7 ${name} \xB7 reconnecting: offline ${fmtDur(now - s.offlineSince)} (attempt ${s.attempts})`;
|
|
5721
|
+
}
|
|
5722
|
+
return `\u25CC braid \xB7 ${name} \xB7 connecting\u2026`;
|
|
5723
|
+
}
|
|
5724
|
+
|
|
5725
|
+
// src/cli.ts
|
|
5505
5726
|
function printList() {
|
|
5506
5727
|
console.log(formatAgentList(listAgents(configDir(process.env))));
|
|
5507
5728
|
}
|
|
@@ -6039,6 +6260,22 @@ async function main() {
|
|
|
6039
6260
|
await initScopeRuntime(cfg, cfg.agent);
|
|
6040
6261
|
console.log(banner("braid", "SCOPE", resolveScope(cfg, cfg.agent, process.env).describe()));
|
|
6041
6262
|
}
|
|
6263
|
+
const runVerbose = invocation.argv.includes("--verbose");
|
|
6264
|
+
const sticky = createStickyLine(process.stdout);
|
|
6265
|
+
const connStatus = createConnStatus({
|
|
6266
|
+
verbose: runVerbose,
|
|
6267
|
+
log: (l) => console.log(l),
|
|
6268
|
+
warn: (l) => console.warn(l)
|
|
6269
|
+
});
|
|
6270
|
+
const restoreConsole = sticky.enabled ? installConsoleBridge(sticky) : null;
|
|
6271
|
+
const statusTimer = setInterval(() => {
|
|
6272
|
+
connStatus.tick();
|
|
6273
|
+
if (sticky.enabled && sticky.quiesced()) {
|
|
6274
|
+
sticky.render(formatStatusLine(connStatus.snapshot(), cfg.agentName ?? "", Date.now()));
|
|
6275
|
+
}
|
|
6276
|
+
}, 1e3);
|
|
6277
|
+
statusTimer.unref?.();
|
|
6278
|
+
process.on("exit", () => sticky.stop());
|
|
6042
6279
|
console.log("[braid] connecting to the relay...");
|
|
6043
6280
|
const keysLive = attachEligible(process.stdin, process.stdout, process.env);
|
|
6044
6281
|
const serviceEligible = process.env.BRAID_SERVICE !== "1" && !!cfg.agentName;
|
|
@@ -6090,6 +6327,7 @@ async function main() {
|
|
|
6090
6327
|
telemetry,
|
|
6091
6328
|
onRelayCreated: (relay) => {
|
|
6092
6329
|
relayRef.current = relay;
|
|
6330
|
+
relay.onConnEvent((e) => connStatus.handle(e));
|
|
6093
6331
|
}
|
|
6094
6332
|
});
|
|
6095
6333
|
if (cfg.runtimeVersion) {
|
|
@@ -6099,6 +6337,9 @@ async function main() {
|
|
|
6099
6337
|
installGracefulShutdown(bridge, {
|
|
6100
6338
|
agentName: cfg.agentName,
|
|
6101
6339
|
beforeDrain: () => {
|
|
6340
|
+
clearInterval(statusTimer);
|
|
6341
|
+
restoreConsole?.();
|
|
6342
|
+
sticky.stop();
|
|
6102
6343
|
attach?.stop();
|
|
6103
6344
|
void bridge.controlSocketClose?.();
|
|
6104
6345
|
resetScope();
|
|
@@ -6114,7 +6355,7 @@ async function main() {
|
|
|
6114
6355
|
listChannels: () => [...bridge.sessions.keys()],
|
|
6115
6356
|
channelName: (channelId) => bridge.channelNames.get(channelId),
|
|
6116
6357
|
stdin: process.stdin,
|
|
6117
|
-
stdout: process.stdout,
|
|
6358
|
+
stdout: stickyWriteThrough(sticky, process.stdout),
|
|
6118
6359
|
onInterrupt: () => {
|
|
6119
6360
|
try {
|
|
6120
6361
|
process.kill(process.pid, "SIGINT");
|
|
@@ -6125,21 +6366,26 @@ async function main() {
|
|
|
6125
6366
|
// Pause the headless driver for this channel, open the real `claude --resume <id>` on the
|
|
6126
6367
|
// owner's terminal (same conversation), and resume driving when they quit it.
|
|
6127
6368
|
handoff: async (channelId, out) => {
|
|
6128
|
-
|
|
6129
|
-
if (!session) {
|
|
6130
|
-
out.write("\n[handoff] that session is no longer active\n");
|
|
6131
|
-
return;
|
|
6132
|
-
}
|
|
6133
|
-
if (!handoffGate.tryAcquire(channelId, "terminal")) {
|
|
6134
|
-
const g = handoffGate.snapshot();
|
|
6135
|
-
out.write(g.state === "orphaned" ? `
|
|
6136
|
-
[handoff] a previous handoff was orphaned - run: braid handoff ${cfg.agentName ?? ""} --recover`.trimEnd() + "\n" : "\n[handoff] another handoff is already in progress\n");
|
|
6137
|
-
return;
|
|
6138
|
-
}
|
|
6369
|
+
sticky.pause();
|
|
6139
6370
|
try {
|
|
6140
|
-
|
|
6371
|
+
const session = bridge.sessions.get(channelId);
|
|
6372
|
+
if (!session) {
|
|
6373
|
+
out.write("\n[handoff] that session is no longer active\n");
|
|
6374
|
+
return;
|
|
6375
|
+
}
|
|
6376
|
+
if (!handoffGate.tryAcquire(channelId, "terminal")) {
|
|
6377
|
+
const g = handoffGate.snapshot();
|
|
6378
|
+
out.write(g.state === "orphaned" ? `
|
|
6379
|
+
[handoff] a previous handoff was orphaned - run: braid handoff ${cfg.agentName ?? ""} --recover`.trimEnd() + "\n" : "\n[handoff] another handoff is already in progress\n");
|
|
6380
|
+
return;
|
|
6381
|
+
}
|
|
6382
|
+
try {
|
|
6383
|
+
await runHandoff({ channelId, provider: cfg.agent, session, cwd: handoffCwdFor(cfg, channelId), out, channelLabel: channelLabel(channelId), scope: cfg.scope });
|
|
6384
|
+
} finally {
|
|
6385
|
+
handoffGate.release();
|
|
6386
|
+
}
|
|
6141
6387
|
} finally {
|
|
6142
|
-
|
|
6388
|
+
sticky.resume();
|
|
6143
6389
|
}
|
|
6144
6390
|
}
|
|
6145
6391
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowyroad/braid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.0",
|
|
4
4
|
"description": "Braid: connect your own coding agent (Claude Code, Codex, Gemini, Grok) to a Braid channel and collaborate with other agents and humans.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "SnowyRoad",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@anthropic-ai/sandbox-runtime": "0.0.63",
|
|
52
52
|
"@anthropic-ai/sdk": "0.104.1",
|
|
53
53
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
54
|
-
"@snowyroad/braid-tui": "0.0.
|
|
54
|
+
"@snowyroad/braid-tui": "0.0.25",
|
|
55
55
|
"canonicalize": "^3.0.0",
|
|
56
56
|
"jose": "^6.2.3",
|
|
57
57
|
"ws": "8.21.0",
|
package/service-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowyroad/braid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@snowyroad/braid",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.86.0",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@agentclientprotocol/sdk": "0.25.1",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"@anthropic-ai/sandbox-runtime": "0.0.63",
|
|
15
15
|
"@anthropic-ai/sdk": "0.104.1",
|
|
16
16
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
17
|
-
"@snowyroad/braid-tui": "0.0.
|
|
17
|
+
"@snowyroad/braid-tui": "0.0.25",
|
|
18
18
|
"canonicalize": "^3.0.0",
|
|
19
19
|
"jose": "^6.2.3",
|
|
20
20
|
"ws": "8.21.0",
|
|
@@ -1183,9 +1183,9 @@
|
|
|
1183
1183
|
]
|
|
1184
1184
|
},
|
|
1185
1185
|
"node_modules/@snowyroad/braid-tui": {
|
|
1186
|
-
"version": "0.0.
|
|
1187
|
-
"resolved": "https://registry.npmjs.org/@snowyroad/braid-tui/-/braid-tui-0.0.
|
|
1188
|
-
"integrity": "sha512-
|
|
1186
|
+
"version": "0.0.25",
|
|
1187
|
+
"resolved": "https://registry.npmjs.org/@snowyroad/braid-tui/-/braid-tui-0.0.25.tgz",
|
|
1188
|
+
"integrity": "sha512-HXtqJ0FBPAOTdR5bZ9zFb3Rcew+1ZlDU44AeOz/PxrDW5a1D+TAdrfVSby32mUk9Z5dVUgWZ2wrnGdbyIX725g==",
|
|
1189
1189
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
1190
1190
|
"dependencies": {
|
|
1191
1191
|
"ink": "^5.2.0",
|
|
@@ -2505,9 +2505,9 @@
|
|
|
2505
2505
|
"license": "ISC"
|
|
2506
2506
|
},
|
|
2507
2507
|
"node_modules/jose": {
|
|
2508
|
-
"version": "6.2.
|
|
2509
|
-
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.
|
|
2510
|
-
"integrity": "sha512-
|
|
2508
|
+
"version": "6.2.4",
|
|
2509
|
+
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.4.tgz",
|
|
2510
|
+
"integrity": "sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==",
|
|
2511
2511
|
"license": "MIT",
|
|
2512
2512
|
"funding": {
|
|
2513
2513
|
"url": "https://github.com/sponsors/panva"
|
|
@@ -2908,9 +2908,9 @@
|
|
|
2908
2908
|
}
|
|
2909
2909
|
},
|
|
2910
2910
|
"node_modules/postcss": {
|
|
2911
|
-
"version": "8.5.
|
|
2912
|
-
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.
|
|
2913
|
-
"integrity": "sha512-
|
|
2911
|
+
"version": "8.5.22",
|
|
2912
|
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz",
|
|
2913
|
+
"integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==",
|
|
2914
2914
|
"dev": true,
|
|
2915
2915
|
"funding": [
|
|
2916
2916
|
{
|