@snowyroad/braid 0.78.1 → 0.79.1
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.js +151 -70
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -848,6 +848,10 @@ var RelayClient = class {
|
|
|
848
848
|
cardPin = /* @__PURE__ */ new Map();
|
|
849
849
|
// no-downgrade pin: name → pinned jkt
|
|
850
850
|
signWarned = false;
|
|
851
|
+
/** BL-228 task 3: activity-derived member-channel inventory (resume-cursor keys +
|
|
852
|
+
* added - removed). Covers channels GET /channels' RLS may drop (e.g. a cross-org
|
|
853
|
+
* guest channel) since this is fed by the same push frames the bridge already gets. */
|
|
854
|
+
memberChannels = /* @__PURE__ */ new Set();
|
|
851
855
|
/** Fail-open signing: any problem -> unsigned post + one warning per process. */
|
|
852
856
|
trySign(f) {
|
|
853
857
|
if (!this.cfg.signMessage) return void 0;
|
|
@@ -1399,12 +1403,14 @@ var RelayClient = class {
|
|
|
1399
1403
|
this.seenByChannel.delete(ch);
|
|
1400
1404
|
this.caughtUp.delete(ch);
|
|
1401
1405
|
this.lastGapResumeAt.delete(ch);
|
|
1406
|
+
this.memberChannels.delete(ch);
|
|
1402
1407
|
this.removedCb?.(ch);
|
|
1403
1408
|
break;
|
|
1404
1409
|
}
|
|
1405
1410
|
case "added": {
|
|
1406
1411
|
const ch = String(msg.channelId ?? "");
|
|
1407
1412
|
if (!ch) break;
|
|
1413
|
+
this.memberChannels.add(ch);
|
|
1408
1414
|
this.addedCb?.(ch);
|
|
1409
1415
|
break;
|
|
1410
1416
|
}
|
|
@@ -1432,6 +1438,7 @@ var RelayClient = class {
|
|
|
1432
1438
|
const resume = msg?.resume;
|
|
1433
1439
|
if (resume && typeof resume === "object") {
|
|
1434
1440
|
for (const [ch, seqRaw] of Object.entries(resume)) {
|
|
1441
|
+
this.memberChannels.add(ch);
|
|
1435
1442
|
const seq = Number(seqRaw);
|
|
1436
1443
|
if (Number.isFinite(seq) && seq > this.cursorOf(ch)) this.cursors.set(ch, seq);
|
|
1437
1444
|
const floor = this.cursorOf(ch);
|
|
@@ -1723,6 +1730,27 @@ var RelayClient = class {
|
|
|
1723
1730
|
isScribeFor(channelId) {
|
|
1724
1731
|
return this.selfScribeCache.get(channelId) ?? false;
|
|
1725
1732
|
}
|
|
1733
|
+
/** BL-228 task 3: this agent's member channels from the relay (`GET /channels`, RLS-scoped
|
|
1734
|
+
* to the caller). Best-effort like fetchRoster/fetchChannelName: never throws, [] on any
|
|
1735
|
+
* failure (HTTP error, network failure, malformed body). Lets the handoff controller offer
|
|
1736
|
+
* COLD channels (member but no live session) alongside the live-session set. */
|
|
1737
|
+
async fetchMemberChannels() {
|
|
1738
|
+
const r = await this.httpJson(`/channels`, { requestClass: "relay.channels.list" });
|
|
1739
|
+
if (!r.ok) {
|
|
1740
|
+
console.warn("[braid] member channels HTTP", r.status ?? r.category);
|
|
1741
|
+
return [];
|
|
1742
|
+
}
|
|
1743
|
+
const body = r.json;
|
|
1744
|
+
const arr = Array.isArray(body?.channels) ? body.channels : [];
|
|
1745
|
+
return arr.filter((c) => typeof c?.id === "string" && c.id.length > 0).map((c) => ({ id: String(c.id), name: typeof c.name === "string" ? c.name : typeof c.title === "string" ? c.title : null }));
|
|
1746
|
+
}
|
|
1747
|
+
/** BL-228 task 3: synchronous in-memory member-channel set (resume-cursor keys at hello,
|
|
1748
|
+
* plus `added` minus `removed`), activity-derived from push frames — no network. Covers
|
|
1749
|
+
* a channel GET /channels' RLS may drop (e.g. a cross-org guest channel) that the bridge
|
|
1750
|
+
* still holds live membership of via the WS. Returns a snapshot copy, not a live view. */
|
|
1751
|
+
knownMemberChannelIds() {
|
|
1752
|
+
return new Set(this.memberChannels);
|
|
1753
|
+
}
|
|
1726
1754
|
/** Spread optional usage into the outbound body. Omits undefined fields so a no-usage
|
|
1727
1755
|
* call produces a body byte-identical to the pre-billing shape. */
|
|
1728
1756
|
usageBody(u) {
|
|
@@ -2736,8 +2764,14 @@ ${fence(slackTaint ? "slack message" : "messages mentioning you", addressed)}
|
|
|
2736
2764
|
hasPendingWork() {
|
|
2737
2765
|
return this.pendingTurns.length > 0;
|
|
2738
2766
|
}
|
|
2767
|
+
/** Open the inbound hold WITHOUT stopping the client (BL-228: start-held warm
|
|
2768
|
+
* for on-demand handoff). Idempotent: an already-open hold is preserved so
|
|
2769
|
+
* nothing queued during the warm is lost. */
|
|
2770
|
+
openHold() {
|
|
2771
|
+
if (!this.holdQueue) this.holdQueue = [];
|
|
2772
|
+
}
|
|
2739
2773
|
async handoffPause() {
|
|
2740
|
-
this.holdQueue = [];
|
|
2774
|
+
if (!this.holdQueue) this.holdQueue = [];
|
|
2741
2775
|
await this.session?.handoffPause?.();
|
|
2742
2776
|
}
|
|
2743
2777
|
async handoffResume() {
|
|
@@ -4276,33 +4310,18 @@ function resolveHandoffBinary(name, install) {
|
|
|
4276
4310
|
handoffBinaryAbs.set(name, found);
|
|
4277
4311
|
return found;
|
|
4278
4312
|
}
|
|
4313
|
+
var HANDOFF_RESUME_SPECS = {
|
|
4314
|
+
"claude-code": (sessionId, cwd) => ({ command: resolveHandoffBinary("claude", "Claude Code"), args: ["--resume", sessionId], cwd }),
|
|
4315
|
+
codex: (sessionId, cwd) => ({ command: resolveHandoffBinary("codex", "the Codex CLI"), args: ["resume", sessionId], cwd }),
|
|
4316
|
+
grok: (sessionId, cwd) => ({ command: resolveHandoffBinary("grok", "xAI's Grok CLI"), args: ["--resume", sessionId], cwd }),
|
|
4317
|
+
opencode: (sessionId, cwd) => ({ command: resolveHandoffBinary("opencode", "the opencode CLI"), args: ["--session", sessionId], cwd })
|
|
4318
|
+
};
|
|
4319
|
+
function interactiveHandoffSupported(agent) {
|
|
4320
|
+
return Object.prototype.hasOwnProperty.call(HANDOFF_RESUME_SPECS, agent);
|
|
4321
|
+
}
|
|
4279
4322
|
function interactiveResumeSpecFor(agent, sessionId, cwd) {
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
return { command: resolveHandoffBinary("claude", "Claude Code"), args: ["--resume", sessionId], cwd };
|
|
4283
|
-
case "codex":
|
|
4284
|
-
return { command: resolveHandoffBinary("codex", "the Codex CLI"), args: ["resume", sessionId], cwd };
|
|
4285
|
-
// grok (2026-07-02): 3-leg probe over `grok agent stdio` — session/new id ==
|
|
4286
|
-
// `grok --resume <id>` id (cwd-keyed store; the bridge's canonical process.cwd()
|
|
4287
|
-
// matches) AND session/load recalled the marker. NOTE: grok's session store is
|
|
4288
|
-
// keyed by CANONICAL cwd; sessionId alone is not enough if cwd were a symlink.
|
|
4289
|
-
case "grok":
|
|
4290
|
-
return { command: resolveHandoffBinary("grok", "xAI's Grok CLI"), args: ["--resume", sessionId], cwd };
|
|
4291
|
-
// opencode (2026-07-02): 3-leg probe over `opencode acp` — `ses_` id resumable by
|
|
4292
|
-
// BOTH the TUI (`opencode --session <id>`) and headless `run --session` (marker
|
|
4293
|
-
// recalled), AND session/load recalled the marker (shared store).
|
|
4294
|
-
case "opencode":
|
|
4295
|
-
return { command: resolveHandoffBinary("opencode", "the opencode CLI"), args: ["--session", sessionId], cwd };
|
|
4296
|
-
// gemini: REFUTED for the handoff 2026-07-02 — native `--resume <uuid>` works, but
|
|
4297
|
-
// ACP session/load returns success while restoring NO conversation context, so the
|
|
4298
|
-
// hand-back leg would silently fork the thread. Do not enable until gemini-cli's
|
|
4299
|
-
// ACP loadSession actually rehydrates context.
|
|
4300
|
-
// cursor: REFUTED 2026-07-02 — `~/.cursor/acp-sessions/` and the native chat store
|
|
4301
|
-
// are separate namespaces; `cursor-agent --resume <acp-id>` exits 0 and silently
|
|
4302
|
-
// opens a FRESH thread (the exact failure mode this gate exists to prevent).
|
|
4303
|
-
default:
|
|
4304
|
-
return null;
|
|
4305
|
-
}
|
|
4323
|
+
const build = HANDOFF_RESUME_SPECS[agent];
|
|
4324
|
+
return build ? build(sessionId, cwd) : null;
|
|
4306
4325
|
}
|
|
4307
4326
|
var defaultAcpClientFactory = (launch) => new AcpClient(launch);
|
|
4308
4327
|
var MAX_CONSECUTIVE_RESTARTS = 3;
|
|
@@ -5808,7 +5827,7 @@ function startHandoffConversation(conn, ctl, initialChannelId, writeLine, log2)
|
|
|
5808
5827
|
}
|
|
5809
5828
|
channelId = id;
|
|
5810
5829
|
phase = "offered";
|
|
5811
|
-
writeLine({ handoff: "offer", channel: res.channel, provider: res.provider, pendingWork: res.pendingWork });
|
|
5830
|
+
writeLine({ handoff: "offer", channel: res.channel, provider: res.provider, pendingWork: res.pendingWork, warm: res.warm });
|
|
5812
5831
|
};
|
|
5813
5832
|
conn.on("close", () => {
|
|
5814
5833
|
const p = phase;
|
|
@@ -6247,6 +6266,7 @@ function renderEvent(ev) {
|
|
|
6247
6266
|
// src/observe/socketHandoff.ts
|
|
6248
6267
|
function makeSocketHandoffController(deps) {
|
|
6249
6268
|
const specFor = deps.specFor ?? interactiveResumeSpecFor;
|
|
6269
|
+
const handoffSupported = deps.handoffSupported ?? interactiveHandoffSupported;
|
|
6250
6270
|
const scopedLaunchFn = deps.scopedLaunchFn ?? scopedLaunch;
|
|
6251
6271
|
const log2 = deps.log ?? (() => {
|
|
6252
6272
|
});
|
|
@@ -6262,57 +6282,91 @@ function makeSocketHandoffController(deps) {
|
|
|
6262
6282
|
if (g.state === "orphaned") return { ok: false, error: "handoff_orphaned" };
|
|
6263
6283
|
return null;
|
|
6264
6284
|
};
|
|
6265
|
-
const
|
|
6285
|
+
const classify = (channelId) => {
|
|
6266
6286
|
const blocked = gateBlocked();
|
|
6267
6287
|
if (blocked) return blocked;
|
|
6268
6288
|
const session = deps.sessions.get(channelId);
|
|
6269
|
-
if (
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6289
|
+
if (session) {
|
|
6290
|
+
const sessionId = session.liveSessionId();
|
|
6291
|
+
if (!sessionId) return { ok: false, error: "no_live_session" };
|
|
6292
|
+
let raw;
|
|
6293
|
+
try {
|
|
6294
|
+
raw = specFor(deps.provider, sessionId, deps.cwdFor(channelId));
|
|
6295
|
+
} catch (e) {
|
|
6296
|
+
return { ok: false, error: sanitizeForTty(String(e?.message ?? e)) };
|
|
6297
|
+
}
|
|
6298
|
+
if (!raw) return { ok: false, error: "provider_unverified" };
|
|
6299
|
+
return { ok: true, kind: "live", session, spec: raw };
|
|
6300
|
+
}
|
|
6301
|
+
if (deps.memberChannelIds().has(channelId) && deps.warmSession) {
|
|
6302
|
+
if (!handoffSupported(deps.provider)) return { ok: false, error: "provider_unverified" };
|
|
6303
|
+
return { ok: true, kind: "cold" };
|
|
6277
6304
|
}
|
|
6278
|
-
|
|
6279
|
-
return { ok: true, session, sessionId, spec: raw };
|
|
6305
|
+
return { ok: false, error: "unknown_channel" };
|
|
6280
6306
|
};
|
|
6281
6307
|
return {
|
|
6282
6308
|
listChannels() {
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
pendingWork: s.hasPendingWork?.() === true
|
|
6288
|
-
}
|
|
6309
|
+
const rows = [];
|
|
6310
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6311
|
+
for (const [id, s] of deps.sessions.entries()) {
|
|
6312
|
+
seen.add(id);
|
|
6313
|
+
rows.push({ id, label: label(id), provider: deps.provider, pendingWork: s.hasPendingWork?.() === true, live: true });
|
|
6314
|
+
}
|
|
6315
|
+
for (const id of deps.memberChannelIds()) {
|
|
6316
|
+
if (seen.has(id)) continue;
|
|
6317
|
+
rows.push({ id, label: label(id), provider: deps.provider, pendingWork: false, live: false });
|
|
6318
|
+
}
|
|
6319
|
+
return rows;
|
|
6289
6320
|
},
|
|
6290
6321
|
offer(channelId) {
|
|
6291
|
-
const
|
|
6292
|
-
if (!
|
|
6322
|
+
const c = classify(channelId);
|
|
6323
|
+
if (!c.ok) return c;
|
|
6293
6324
|
return {
|
|
6294
6325
|
ok: true,
|
|
6295
6326
|
channel: { id: channelId, label: label(channelId) },
|
|
6296
6327
|
provider: deps.provider,
|
|
6297
|
-
pendingWork:
|
|
6328
|
+
pendingWork: c.kind === "live" ? c.session.hasPendingWork?.() === true : false,
|
|
6329
|
+
warm: c.kind === "cold"
|
|
6298
6330
|
};
|
|
6299
6331
|
},
|
|
6300
6332
|
async begin(channelId) {
|
|
6301
|
-
const
|
|
6302
|
-
if (!
|
|
6333
|
+
const c = classify(channelId);
|
|
6334
|
+
if (!c.ok) return c;
|
|
6303
6335
|
if (!deps.gate.tryAcquire(channelId, "socket")) return gateBlocked() ?? { ok: false, error: "handoff_active" };
|
|
6304
6336
|
let pauseAttempted = false;
|
|
6337
|
+
let warmedCold = false;
|
|
6338
|
+
let session;
|
|
6305
6339
|
try {
|
|
6306
|
-
|
|
6340
|
+
let spec;
|
|
6341
|
+
if (c.kind === "cold") {
|
|
6342
|
+
session = await deps.warmSession(channelId);
|
|
6343
|
+
warmedCold = true;
|
|
6344
|
+
const sessionId = session.liveSessionId();
|
|
6345
|
+
if (!sessionId) throw new Error("warm produced no resumable session");
|
|
6346
|
+
const raw = specFor(deps.provider, sessionId, deps.cwdFor(channelId));
|
|
6347
|
+
if (!raw) throw new Error("provider_unverified");
|
|
6348
|
+
spec = raw;
|
|
6349
|
+
log2(`[braid] handoff: warmed ${label(channelId)} on demand for a socket client`);
|
|
6350
|
+
} else {
|
|
6351
|
+
session = c.session;
|
|
6352
|
+
spec = c.spec;
|
|
6353
|
+
}
|
|
6354
|
+
const scoped = await scopedLaunchFn({ command: spec.command, args: spec.args, cwd: spec.cwd }, { scope: deps.scope }, deps.provider);
|
|
6307
6355
|
pauseAttempted = true;
|
|
6308
|
-
await
|
|
6309
|
-
active = { channelId, session
|
|
6356
|
+
await session.handoffPause();
|
|
6357
|
+
active = { channelId, session };
|
|
6310
6358
|
log2(`[braid] handoff: paused ${label(channelId)} for a socket client`);
|
|
6311
6359
|
return { ok: true, spec: { command: scoped.command, args: scoped.args, cwd: scoped.cwd, env: scoped.env ?? null } };
|
|
6312
6360
|
} catch (e) {
|
|
6313
|
-
if (pauseAttempted) {
|
|
6361
|
+
if (pauseAttempted && session) {
|
|
6362
|
+
try {
|
|
6363
|
+
await session.handoffResume();
|
|
6364
|
+
} catch {
|
|
6365
|
+
}
|
|
6366
|
+
} else if (warmedCold && session) {
|
|
6314
6367
|
try {
|
|
6315
|
-
await
|
|
6368
|
+
await session.handoffPause();
|
|
6369
|
+
await session.handoffResume();
|
|
6316
6370
|
} catch {
|
|
6317
6371
|
}
|
|
6318
6372
|
}
|
|
@@ -6513,11 +6567,20 @@ async function startBridge(cfg, relay, deps) {
|
|
|
6513
6567
|
console.warn("[braid] self card publish on connect failed:", sanitizeForTty(String(e)));
|
|
6514
6568
|
}
|
|
6515
6569
|
}
|
|
6516
|
-
async function ensureSession(channelId) {
|
|
6570
|
+
async function ensureSession(channelId, opts) {
|
|
6517
6571
|
const live = sessions.get(channelId);
|
|
6518
|
-
if (live)
|
|
6572
|
+
if (live) {
|
|
6573
|
+
if (opts?.startHeld) live.openHold();
|
|
6574
|
+
return live;
|
|
6575
|
+
}
|
|
6519
6576
|
const inFlight = pending.get(channelId);
|
|
6520
|
-
if (inFlight)
|
|
6577
|
+
if (inFlight) {
|
|
6578
|
+
if (opts?.startHeld) return inFlight.then((s) => {
|
|
6579
|
+
s.openHold();
|
|
6580
|
+
return s;
|
|
6581
|
+
});
|
|
6582
|
+
return inFlight;
|
|
6583
|
+
}
|
|
6521
6584
|
const p = (async () => {
|
|
6522
6585
|
console.log(`[braid] Warming up #${sanitizeForTty(channelId)}\u2026`);
|
|
6523
6586
|
void (async () => {
|
|
@@ -6608,6 +6671,7 @@ async function startBridge(cfg, relay, deps) {
|
|
|
6608
6671
|
clearInterval(warmupHeartbeat);
|
|
6609
6672
|
relay.sendActivity(channelId, "stop");
|
|
6610
6673
|
}
|
|
6674
|
+
if (opts?.startHeld) session.openHold();
|
|
6611
6675
|
brokers.set(channelId, broker);
|
|
6612
6676
|
sessions.set(channelId, session);
|
|
6613
6677
|
pending.delete(channelId);
|
|
@@ -6703,6 +6767,18 @@ async function startBridge(cfg, relay, deps) {
|
|
|
6703
6767
|
cwdFor: (id) => handoffCwdFor(cfg, id),
|
|
6704
6768
|
scope: cfg.scope,
|
|
6705
6769
|
gate: cs.handoffGate,
|
|
6770
|
+
// BL-228 task 4/8: the in-memory member set RelayClient tracks (Task 3) is
|
|
6771
|
+
// sufficient on its own — no union with sessions.keys() needed. listChannels()
|
|
6772
|
+
// already unions deps.sessions (live) with deps.memberChannelIds() (Task 4), so
|
|
6773
|
+
// any channel with a live session appears via the sessions map regardless of
|
|
6774
|
+
// whether it's also in the member set; unioning sessions.keys() into
|
|
6775
|
+
// memberChannelIds() here would be redundant belt-and-suspenders with no
|
|
6776
|
+
// behavioral effect, not a real gap closed.
|
|
6777
|
+
memberChannelIds: () => relay.knownMemberChannelIds(),
|
|
6778
|
+
// BL-228 task 8: warm a cold member channel on demand — the router's own
|
|
6779
|
+
// ensureSession, with startHeld so the socket handoff's begin() (task 5) never
|
|
6780
|
+
// races a headless turn between warm-complete and handoffPause.
|
|
6781
|
+
warmSession: (id) => ensureSession(id, { startHeld: true }),
|
|
6706
6782
|
log: (line) => console.log(line)
|
|
6707
6783
|
}) : void 0;
|
|
6708
6784
|
try {
|
|
@@ -7770,9 +7846,7 @@ function validateSpec(line) {
|
|
|
7770
7846
|
}
|
|
7771
7847
|
async function pickChannel(channels, filter, promptFn, write) {
|
|
7772
7848
|
if (channels.length === 0) {
|
|
7773
|
-
write(
|
|
7774
|
-
"[handoff] no active session to take over yet.\n[handoff] handoff drops you into a session the agent is ALREADY running - one exists\n[handoff] only once the agent is working in a channel (a message, a reply, a flow turn).\n[handoff] send the agent a message in a channel and let it reply, then run this again.\n"
|
|
7775
|
-
);
|
|
7849
|
+
write("[handoff] this agent is not a member of any channel - no channels to take over yet.\n");
|
|
7776
7850
|
return null;
|
|
7777
7851
|
}
|
|
7778
7852
|
if (filter && filter.trim() !== "") {
|
|
@@ -7799,7 +7873,8 @@ async function pickChannel(channels, filter, promptFn, write) {
|
|
|
7799
7873
|
}
|
|
7800
7874
|
function listChannels(write, channels) {
|
|
7801
7875
|
channels.forEach((c, i) => {
|
|
7802
|
-
|
|
7876
|
+
const mark = c.live === false ? " (cold - will start a session)" : c.pendingWork ? " (working)" : "";
|
|
7877
|
+
write(` [${i + 1}] ${sanitizeForTty(c.label)}${mark}
|
|
7803
7878
|
`);
|
|
7804
7879
|
});
|
|
7805
7880
|
}
|
|
@@ -7807,19 +7882,25 @@ function renderOffer(write, offer) {
|
|
|
7807
7882
|
const channel = offer["channel"] ?? {};
|
|
7808
7883
|
const label = sanitizeForTty(String(channel.label ?? ""));
|
|
7809
7884
|
const pendingWork = offer["pendingWork"] === true;
|
|
7885
|
+
const warm = offer["warm"] === true;
|
|
7810
7886
|
write(
|
|
7811
7887
|
banner("handoff", `TAKING CONTROL of ${label}`, [
|
|
7812
7888
|
"pausing the bridge and opening the agent's REAL interactive session",
|
|
7813
7889
|
"on this terminal. Quit it (Ctrl-C or /exit) to hand control back.",
|
|
7814
7890
|
"",
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
"",
|
|
7820
|
-
"
|
|
7821
|
-
"
|
|
7822
|
-
|
|
7891
|
+
...warm ? [
|
|
7892
|
+
"NOTE: this channel has NO running session - taking control will START",
|
|
7893
|
+
"one now, then drop you into it."
|
|
7894
|
+
] : [
|
|
7895
|
+
"WARNING: changes you make inside PERSIST after you leave -",
|
|
7896
|
+
"switching model or mode (or compacting/clearing) carries over into",
|
|
7897
|
+
"how this agent serves the channel afterwards.",
|
|
7898
|
+
...pendingWork ? [
|
|
7899
|
+
"",
|
|
7900
|
+
"NOTE: a reply is being worked on right now; taking control interrupts",
|
|
7901
|
+
"it. The interrupted message will re-run when you hand back."
|
|
7902
|
+
] : []
|
|
7903
|
+
]
|
|
7823
7904
|
])
|
|
7824
7905
|
);
|
|
7825
7906
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowyroad/braid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.79.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@snowyroad/braid",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.79.1",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@agentclientprotocol/sdk": "0.25.1",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowyroad/braid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.79.1",
|
|
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",
|