@snowyroad/braid 0.83.0 → 0.84.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.
|
@@ -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-SXNTO4ON.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-SXNTO4ON.js");
|
|
482
482
|
const r = await boundedJsonFetch2(`${relayHttpUrl.replace(/\/$/, "")}/agents/token`, {
|
|
483
483
|
method: "POST",
|
|
484
484
|
headers: {
|
|
@@ -1361,16 +1361,26 @@ function pickAllowOption(req) {
|
|
|
1361
1361
|
"ACP request_permission had no allow option (allow_once/allow_always); refusing to auto-select a non-allow option"
|
|
1362
1362
|
);
|
|
1363
1363
|
}
|
|
1364
|
+
function safeAcpToolName(tc) {
|
|
1365
|
+
const title = tc?.title;
|
|
1366
|
+
if (typeof title === "string") {
|
|
1367
|
+
const m = /^mcp__[A-Za-z0-9_.-]+__([A-Za-z0-9_.-]+)$/.exec(title);
|
|
1368
|
+
if (m) return m[1];
|
|
1369
|
+
}
|
|
1370
|
+
return tc?.kind ?? "unknown";
|
|
1371
|
+
}
|
|
1364
1372
|
var AcpClient = class {
|
|
1365
|
-
constructor(launch, scopeWrap, afterChildExitHook) {
|
|
1373
|
+
constructor(launch, scopeWrap, afterChildExitHook, telemetry) {
|
|
1366
1374
|
this.launch = launch;
|
|
1367
1375
|
this.scopeWrap = scopeWrap;
|
|
1368
1376
|
this.afterChildExitHook = afterChildExitHook;
|
|
1377
|
+
this.telemetry = telemetry;
|
|
1369
1378
|
this.policy = launch.policy ?? { mode: "readonly", configDirAbs: configDir(process.env) };
|
|
1370
1379
|
}
|
|
1371
1380
|
launch;
|
|
1372
1381
|
scopeWrap;
|
|
1373
1382
|
afterChildExitHook;
|
|
1383
|
+
telemetry;
|
|
1374
1384
|
/** The tool permission policy; defaults FAIL-SAFE to readonly (never to approval). */
|
|
1375
1385
|
policy;
|
|
1376
1386
|
child = null;
|
|
@@ -1544,6 +1554,12 @@ var AcpClient = class {
|
|
|
1544
1554
|
};
|
|
1545
1555
|
}
|
|
1546
1556
|
console.warn(`[braid] denied agent tool permission: ${sanitizeForTty(verdict.reason)}`);
|
|
1557
|
+
this.telemetry?.captureBridgeEvent("tool_call_blocked", {
|
|
1558
|
+
tool: safeAcpToolName(req.toolCall),
|
|
1559
|
+
mode,
|
|
1560
|
+
path: "acp",
|
|
1561
|
+
reason: verdict.deniedByMode ? "mode" : "tripwire"
|
|
1562
|
+
});
|
|
1547
1563
|
if (verdict.deniedByMode) {
|
|
1548
1564
|
const cause = this.currentTurnOverrideMode === "readonly" && this.policy.mode !== "readonly" ? "channel" : "operator";
|
|
1549
1565
|
const hint = takeDenialHint(this.policy, cause);
|
|
@@ -3894,8 +3910,26 @@ var RelayClient = class {
|
|
|
3894
3910
|
this.bumpCursor(m.channelId, m.seq);
|
|
3895
3911
|
this.inboundCbs.forEach((cb) => cb(m));
|
|
3896
3912
|
}
|
|
3913
|
+
/** Returns whether the frame was actually written (WS open) — callers that
|
|
3914
|
+
* don't care (the vast majority) simply ignore the return value. The
|
|
3915
|
+
* telemetry seam's pre-connect latch (BL-177 whole-branch review) relies on
|
|
3916
|
+
* this signal via sendTelemetry below. */
|
|
3897
3917
|
send(obj) {
|
|
3898
|
-
if (this.ws && this.ws.readyState === 1)
|
|
3918
|
+
if (this.ws && this.ws.readyState === 1) {
|
|
3919
|
+
this.ws.send(JSON.stringify(obj));
|
|
3920
|
+
return true;
|
|
3921
|
+
}
|
|
3922
|
+
return false;
|
|
3923
|
+
}
|
|
3924
|
+
/** Public wrapper for the telemetry seam (BL-177 Task 4): forwards an already-
|
|
3925
|
+
* built, already-allowlisted frame (src/telemetry.ts owns consent/allowlist/
|
|
3926
|
+
* debounce) to the guarded protected send() — so a telemetry frame goes out
|
|
3927
|
+
* under the SAME WS-open discipline as everything else and is silently
|
|
3928
|
+
* dropped when the socket isn't open (no queue, per design). Deliberately
|
|
3929
|
+
* dumb: no validation here. Returns delivered=true/false so the seam can
|
|
3930
|
+
* latch an undelivered pre-connect frame (single-slot, D5). */
|
|
3931
|
+
sendTelemetry(frame) {
|
|
3932
|
+
return this.send(frame);
|
|
3899
3933
|
}
|
|
3900
3934
|
/** Emit a typing/thinking activity for a channel. `activityOverride` (start only)
|
|
3901
3935
|
* forces the broadcast label — used by the warm-up path to show "coming_online".
|
|
@@ -5783,10 +5817,12 @@ function makeInputQueue() {
|
|
|
5783
5817
|
};
|
|
5784
5818
|
}
|
|
5785
5819
|
var ClaudeAdapter = class {
|
|
5786
|
-
constructor(policy = defaultToolPolicy()) {
|
|
5820
|
+
constructor(policy = defaultToolPolicy(), telemetry) {
|
|
5787
5821
|
this.policy = policy;
|
|
5822
|
+
this.telemetry = telemetry;
|
|
5788
5823
|
}
|
|
5789
5824
|
policy;
|
|
5825
|
+
telemetry;
|
|
5790
5826
|
// mcpServers/env from AgentStartOptions are ignored here: the generic (bundled Claude
|
|
5791
5827
|
// SDK) path does not advertise stdio MCP servers to a subprocess. Default no-MCP path.
|
|
5792
5828
|
async start(opts) {
|
|
@@ -5811,6 +5847,12 @@ var ClaudeAdapter = class {
|
|
|
5811
5847
|
const verdict = evaluateSdkTool(mode, policy.configDirAbs, toolName, toolInput);
|
|
5812
5848
|
if (verdict.allow) return { behavior: "allow", updatedInput: toolInput };
|
|
5813
5849
|
console.warn(`[braid] denied agent tool use: ${sanitizeForTty(verdict.reason)}`);
|
|
5850
|
+
this.telemetry?.captureBridgeEvent("tool_call_blocked", {
|
|
5851
|
+
tool: toolName,
|
|
5852
|
+
mode,
|
|
5853
|
+
path: "sdk",
|
|
5854
|
+
reason: verdict.deniedByMode ? "mode" : "tripwire"
|
|
5855
|
+
});
|
|
5814
5856
|
if (verdict.deniedByMode) {
|
|
5815
5857
|
const hint = takeDenialHint(policy);
|
|
5816
5858
|
if (hint) console.warn(sanitizeForTty(hint));
|
|
@@ -5859,14 +5901,14 @@ var ClaudeAdapter = class {
|
|
|
5859
5901
|
};
|
|
5860
5902
|
}
|
|
5861
5903
|
};
|
|
5862
|
-
function createAdapter(cfg, channelId) {
|
|
5904
|
+
function createAdapter(cfg, channelId, telemetry) {
|
|
5863
5905
|
const policy = {
|
|
5864
5906
|
mode: cfg.toolMode,
|
|
5865
5907
|
configDirAbs: resolve2(configDir(process.env)),
|
|
5866
5908
|
agentName: cfg.agentName
|
|
5867
5909
|
// for the once-per-process "braid tools full <name>" denial hint
|
|
5868
5910
|
};
|
|
5869
|
-
if (cfg.agentMode !== "acp") return new ClaudeAdapter(policy);
|
|
5911
|
+
if (cfg.agentMode !== "acp") return new ClaudeAdapter(policy, telemetry);
|
|
5870
5912
|
if (channelId) {
|
|
5871
5913
|
const prev = detectProviderSwitch(configDir(process.env), cfg.relayWsUrl, cfg.agentName, channelId, cfg.agent);
|
|
5872
5914
|
if (prev) {
|
|
@@ -5888,7 +5930,8 @@ function createAdapter(cfg, channelId) {
|
|
|
5888
5930
|
const makeClient = (launch) => new AcpClient(
|
|
5889
5931
|
launch,
|
|
5890
5932
|
(l) => scopedLaunch(l, cfg, cfg.agent),
|
|
5891
|
-
afterChildExit
|
|
5933
|
+
afterChildExit,
|
|
5934
|
+
telemetry
|
|
5892
5935
|
);
|
|
5893
5936
|
return new AcpAdapter(cfg.agent, makeClient, void 0, policy, session, usageSource, cfg.agentName, cfg.stripEnvKeys, cfg.fallbackModel);
|
|
5894
5937
|
}
|
|
@@ -6163,7 +6206,7 @@ function loadConfig(env) {
|
|
|
6163
6206
|
function wsToHttp(relayWsUrl) {
|
|
6164
6207
|
return relayWsUrl.replace(/^wss:\/\//i, "https://").replace(/^ws:\/\//i, "http://");
|
|
6165
6208
|
}
|
|
6166
|
-
async function buildFromStoredAgent(dir, stored, env, flags) {
|
|
6209
|
+
async function buildFromStoredAgent(dir, stored, env, flags, telemetry) {
|
|
6167
6210
|
if (stored.provider !== void 0 && !ACP_AGENT_IDS.includes(stored.provider)) {
|
|
6168
6211
|
throw new Error(
|
|
6169
6212
|
`Invalid provider in saved agent config: ${stored.provider}. Expected one of: ${ACP_AGENT_IDS.join(", ")}`
|
|
@@ -6187,8 +6230,10 @@ async function buildFromStoredAgent(dir, stored, env, flags) {
|
|
|
6187
6230
|
let toolMode;
|
|
6188
6231
|
if (flags?.tools) {
|
|
6189
6232
|
toolMode = flags.tools;
|
|
6233
|
+
telemetry?.captureBridgeEvent("tool_mode_selected", { mode: toolMode, surface: "flag", persisted: false });
|
|
6190
6234
|
} else if (envToolMode) {
|
|
6191
6235
|
toolMode = parseToolMode(env);
|
|
6236
|
+
telemetry?.captureBridgeEvent("tool_mode_selected", { mode: toolMode, surface: "env", persisted: false });
|
|
6192
6237
|
} else if (current.toolMode) {
|
|
6193
6238
|
toolMode = current.toolMode;
|
|
6194
6239
|
} else {
|
|
@@ -6198,6 +6243,7 @@ async function buildFromStoredAgent(dir, stored, env, flags) {
|
|
|
6198
6243
|
current = { ...current, toolMode };
|
|
6199
6244
|
saveAgent(dir, current);
|
|
6200
6245
|
}
|
|
6246
|
+
telemetry?.captureBridgeEvent("tool_mode_selected", { mode: toolMode, surface: "prompt", persisted: choice.persist });
|
|
6201
6247
|
}
|
|
6202
6248
|
let signingPrivateJwk = current.signingPrivateJwk;
|
|
6203
6249
|
if (!signingPrivateJwk) {
|
|
@@ -6323,7 +6369,7 @@ function parseToolModeForJoin(env) {
|
|
|
6323
6369
|
);
|
|
6324
6370
|
return void 0;
|
|
6325
6371
|
}
|
|
6326
|
-
async function loadConfigFromInvite(code, env, flags) {
|
|
6372
|
+
async function loadConfigFromInvite(code, env, flags, telemetry) {
|
|
6327
6373
|
resolveAgentSelection(env, flags?.provider);
|
|
6328
6374
|
const joinToolMode = flags?.tools ?? parseToolModeForJoin(env);
|
|
6329
6375
|
const envForBuild = env.BRAID_TOOL_MODE && joinToolMode === void 0 ? { ...env, BRAID_TOOL_MODE: void 0 } : env;
|
|
@@ -6365,7 +6411,7 @@ async function loadConfigFromInvite(code, env, flags) {
|
|
|
6365
6411
|
const file = saveAgent(dir, stored);
|
|
6366
6412
|
const cmdName = isShellSafeName(bundle.agentName) ? bundle.agentName : "<agent-name>";
|
|
6367
6413
|
console.log(`[braid] credential saved to ${file} (restart later with: npx @snowyroad/braid start ${cmdName})`);
|
|
6368
|
-
return buildFromStoredAgent(dir, stored, envForBuild, flags);
|
|
6414
|
+
return buildFromStoredAgent(dir, stored, envForBuild, flags, telemetry);
|
|
6369
6415
|
}
|
|
6370
6416
|
function buildJoinScopeBlock(flags, env) {
|
|
6371
6417
|
const split = (v) => v ? v.split(/[,:]/).map((s) => s.trim()).filter((s) => s.length > 0) : [];
|
|
@@ -6384,10 +6430,10 @@ function buildJoinScopeBlock(flags, env) {
|
|
|
6384
6430
|
...disabled ? { disabled: true } : {}
|
|
6385
6431
|
};
|
|
6386
6432
|
}
|
|
6387
|
-
async function loadConfigFromStore(agentName, env, flags, io) {
|
|
6433
|
+
async function loadConfigFromStore(agentName, env, flags, io, telemetry) {
|
|
6388
6434
|
const dir = configDir(env);
|
|
6389
6435
|
const entry = await resolveStoredAgent(dir, agentName, io);
|
|
6390
|
-
return buildFromStoredAgent(dir, entry.agent, env, flags);
|
|
6436
|
+
return buildFromStoredAgent(dir, entry.agent, env, flags, telemetry);
|
|
6391
6437
|
}
|
|
6392
6438
|
async function resolveStoredAgent(dir, agentName, io) {
|
|
6393
6439
|
const name = agentName?.trim();
|
|
@@ -6408,7 +6454,7 @@ function getFlag(argv, name) {
|
|
|
6408
6454
|
}
|
|
6409
6455
|
return void 0;
|
|
6410
6456
|
}
|
|
6411
|
-
async function resolveConfig(argv, env) {
|
|
6457
|
+
async function resolveConfig(argv, env, telemetry) {
|
|
6412
6458
|
const parsed = parseRunFlags(argv);
|
|
6413
6459
|
if (!parsed.ok) throw new Error(parsed.error);
|
|
6414
6460
|
const { flags, rest } = parsed;
|
|
@@ -6416,12 +6462,12 @@ async function resolveConfig(argv, env) {
|
|
|
6416
6462
|
if (positional[0] === "join") {
|
|
6417
6463
|
const code = positional[1];
|
|
6418
6464
|
if (!code || code.trim() === "") throw new Error("Missing value for join");
|
|
6419
|
-
return loadConfigFromInvite(code.trim(), env, flags);
|
|
6465
|
+
return loadConfigFromInvite(code.trim(), env, flags, telemetry);
|
|
6420
6466
|
}
|
|
6421
6467
|
if (positional[0] === "start") {
|
|
6422
6468
|
const restAfter = positional.slice(1);
|
|
6423
6469
|
const name = restAfter[0] && !restAfter[0].startsWith("-") ? restAfter[0].trim() : void 0;
|
|
6424
|
-
if (name) return loadConfigFromStore(name, env, flags);
|
|
6470
|
+
if (name) return loadConfigFromStore(name, env, flags, void 0, telemetry);
|
|
6425
6471
|
positional = restAfter;
|
|
6426
6472
|
}
|
|
6427
6473
|
const argInvite = getFlag(positional, "--invite");
|
|
@@ -6429,9 +6475,9 @@ async function resolveConfig(argv, env) {
|
|
|
6429
6475
|
throw new Error("Missing value for --invite");
|
|
6430
6476
|
}
|
|
6431
6477
|
const invite = (argInvite ?? env.BRAID_INVITE)?.trim();
|
|
6432
|
-
if (invite) return loadConfigFromInvite(invite, env, flags);
|
|
6478
|
+
if (invite) return loadConfigFromInvite(invite, env, flags, telemetry);
|
|
6433
6479
|
if (env.BRAID_TOKEN && env.BRAID_TOKEN.trim() !== "") return loadConfig(env);
|
|
6434
|
-
return loadConfigFromStore(void 0, env, flags);
|
|
6480
|
+
return loadConfigFromStore(void 0, env, flags, void 0, telemetry);
|
|
6435
6481
|
}
|
|
6436
6482
|
function parseClaudeConfigModel(jsonText) {
|
|
6437
6483
|
try {
|
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-PQGSJM2V.js";
|
|
74
74
|
import {
|
|
75
75
|
externalSourceLabel,
|
|
76
76
|
externalUntrustedPreamble,
|
|
@@ -2758,6 +2758,7 @@ async function createAndStartBridge(cfg, deps = {}) {
|
|
|
2758
2758
|
wsFactory,
|
|
2759
2759
|
fetchFn: deps.fetchFn ?? fetch
|
|
2760
2760
|
});
|
|
2761
|
+
deps.onRelayCreated?.(relay);
|
|
2761
2762
|
let handle = null;
|
|
2762
2763
|
relay.onFatal(
|
|
2763
2764
|
deps.onFatal ?? ((codeOrError, reason) => {
|
|
@@ -2777,7 +2778,8 @@ async function createAndStartBridge(cfg, deps = {}) {
|
|
|
2777
2778
|
void drainAndExit(handle ? handle.sessions.values() : [], exitCode, void 0, handle?.brokers.values());
|
|
2778
2779
|
})
|
|
2779
2780
|
);
|
|
2780
|
-
const
|
|
2781
|
+
const rawMakeAdapter = deps.makeAdapter ?? createAdapter;
|
|
2782
|
+
const makeAdapter = (c, channelId) => rawMakeAdapter(c, channelId, deps.telemetry);
|
|
2781
2783
|
handle = await startBridge(cfg, relay, { makeAdapter, onReady: deps.onReady, cardCache: deps.cardCache, controlSocket: deps.controlSocket });
|
|
2782
2784
|
return { relay, sessions: handle.sessions, channelNames: handle.channelNames, brokers: handle.brokers, hub: handle.hub, ensureSession: handle.ensureSession, controlSocketClose: handle.controlSocketClose };
|
|
2783
2785
|
}
|
|
@@ -2829,7 +2831,7 @@ function startUpdateNotifier(opts) {
|
|
|
2829
2831
|
}
|
|
2830
2832
|
|
|
2831
2833
|
// src/cli.ts
|
|
2832
|
-
import { readFileSync as
|
|
2834
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
2833
2835
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2834
2836
|
|
|
2835
2837
|
// src/cliArgs.ts
|
|
@@ -2859,6 +2861,10 @@ Commands:
|
|
|
2859
2861
|
(no network; local inspection only)
|
|
2860
2862
|
scope allow <agent> <host>
|
|
2861
2863
|
Approve a custom model endpoint host for a saved agent (BL-148)
|
|
2864
|
+
telemetry [status|enable|disable]
|
|
2865
|
+
Show or change anonymous usage telemetry (tool-mode +
|
|
2866
|
+
blocked-tool-call signals only; never content, paths, or
|
|
2867
|
+
names). Machine-level, no saved agent required. Default: status
|
|
2862
2868
|
service <install|uninstall|start|stop|restart|status|logs> [name]
|
|
2863
2869
|
Run a saved agent as a background service that starts at login
|
|
2864
2870
|
and restarts on crashes (launchd on macOS, systemd user unit on
|
|
@@ -2911,6 +2917,7 @@ var SERVICE_VERBS = /* @__PURE__ */ new Set([
|
|
|
2911
2917
|
"status",
|
|
2912
2918
|
"logs"
|
|
2913
2919
|
]);
|
|
2920
|
+
var TELEMETRY_VERBS = /* @__PURE__ */ new Set(["status", "enable", "disable"]);
|
|
2914
2921
|
function parseCliArgs(argv) {
|
|
2915
2922
|
const first = argv[0];
|
|
2916
2923
|
if (first === "list") return { kind: "list" };
|
|
@@ -2987,6 +2994,20 @@ function parseCliArgs(argv) {
|
|
|
2987
2994
|
if (first === "update") {
|
|
2988
2995
|
return { kind: "update" };
|
|
2989
2996
|
}
|
|
2997
|
+
if (first === "telemetry") {
|
|
2998
|
+
const verbArg = argv[1];
|
|
2999
|
+
const verb = verbArg ?? "status";
|
|
3000
|
+
if (!TELEMETRY_VERBS.has(verb)) {
|
|
3001
|
+
return {
|
|
3002
|
+
kind: "usage",
|
|
3003
|
+
error: `Unknown telemetry subcommand: ${verbArg}. Expected status|enable|disable`
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
if (argv.length > 2) {
|
|
3007
|
+
return { kind: "usage", error: `Unexpected argument for telemetry: ${argv[2]}` };
|
|
3008
|
+
}
|
|
3009
|
+
return { kind: "telemetry", verb };
|
|
3010
|
+
}
|
|
2990
3011
|
if (first === "attach") {
|
|
2991
3012
|
const agent = argv[1] && !argv[1].startsWith("-") ? argv[1].trim() : void 0;
|
|
2992
3013
|
return { kind: "attach", agent };
|
|
@@ -3322,17 +3343,25 @@ async function runGuidedStart(flags, deps) {
|
|
|
3322
3343
|
if (chosen.persist) patch.toolMode = chosen.mode;
|
|
3323
3344
|
}
|
|
3324
3345
|
let ephemeralToolMode = toolsPending && patch.toolMode === void 0;
|
|
3346
|
+
let toolModePersistedOnFile = false;
|
|
3325
3347
|
if (patch.provider !== void 0 || patch.toolMode !== void 0) {
|
|
3326
3348
|
const persisted = deps.persist(patch);
|
|
3327
3349
|
if (persisted === false) {
|
|
3328
3350
|
deps.err(
|
|
3329
3351
|
`[braid] ${displayName}'s setup answers could not be saved \u2014 it may already be running on this machine. Stop it first, then run: braid start ${cmdName}`
|
|
3330
3352
|
);
|
|
3353
|
+
if (toolsPending) {
|
|
3354
|
+
deps.telemetry.captureBridgeEvent("tool_mode_selected", { mode: toolMode, surface: "guided", persisted: false });
|
|
3355
|
+
}
|
|
3331
3356
|
return { action: "continue" };
|
|
3332
3357
|
}
|
|
3333
3358
|
provider = persisted.provider ?? provider;
|
|
3334
3359
|
toolMode = persisted.toolMode ?? toolMode;
|
|
3335
3360
|
ephemeralToolMode = toolsPending && patch.toolMode === void 0 && persisted.toolMode === void 0;
|
|
3361
|
+
toolModePersistedOnFile = persisted.toolMode !== void 0;
|
|
3362
|
+
}
|
|
3363
|
+
if (toolsPending) {
|
|
3364
|
+
deps.telemetry.captureBridgeEvent("tool_mode_selected", { mode: toolMode, surface: "guided", persisted: toolModePersistedOnFile });
|
|
3336
3365
|
}
|
|
3337
3366
|
const cont = () => ephemeralToolMode ? { action: "continue", runToolMode: toolMode } : { action: "continue" };
|
|
3338
3367
|
const wantBackground = await deps.askBackground(buildBackgroundQuestion(displayName));
|
|
@@ -4528,13 +4557,125 @@ function renderStatusJson(input) {
|
|
|
4528
4557
|
return JSON.stringify({ agents }, null, 2);
|
|
4529
4558
|
}
|
|
4530
4559
|
|
|
4560
|
+
// src/telemetry.ts
|
|
4561
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, renameSync as renameSync3, chmodSync as chmodSync4 } from "fs";
|
|
4562
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
4563
|
+
var EVENT_PROP_ALLOWLIST = {
|
|
4564
|
+
tool_mode_selected: ["mode", "surface", "persisted"],
|
|
4565
|
+
tool_call_blocked: ["tool", "mode", "path", "reason"]
|
|
4566
|
+
};
|
|
4567
|
+
var DEBOUNCE_KEY_PROPS = ["tool", "mode", "path", "reason"];
|
|
4568
|
+
var DEBOUNCE_MS = 6e4;
|
|
4569
|
+
var DISCLOSURE_LINE = "[braid] anonymous usage telemetry is on (tool-mode + blocked-tool-call signals; never content, paths, or names). Disable: braid telemetry disable";
|
|
4570
|
+
function isDoNotTrackSet(env) {
|
|
4571
|
+
const v = env.DO_NOT_TRACK;
|
|
4572
|
+
if (v === void 0) return false;
|
|
4573
|
+
return /^(1|true|yes)$/i.test(v.trim());
|
|
4574
|
+
}
|
|
4575
|
+
function telemetryConfigPath(dir) {
|
|
4576
|
+
return join7(dir, "telemetry.json");
|
|
4577
|
+
}
|
|
4578
|
+
function readTelemetryConfig(dir) {
|
|
4579
|
+
try {
|
|
4580
|
+
const raw = readFileSync6(telemetryConfigPath(dir), "utf8");
|
|
4581
|
+
const parsed = JSON.parse(raw);
|
|
4582
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4583
|
+
return parsed;
|
|
4584
|
+
}
|
|
4585
|
+
return {};
|
|
4586
|
+
} catch {
|
|
4587
|
+
return {};
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
function writeTelemetryConfig(dir, cfg) {
|
|
4591
|
+
const file = telemetryConfigPath(dir);
|
|
4592
|
+
mkdirSync5(dirname5(file), { recursive: true, mode: 448 });
|
|
4593
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
4594
|
+
writeFileSync5(tmp, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
4595
|
+
renameSync3(tmp, file);
|
|
4596
|
+
try {
|
|
4597
|
+
chmodSync4(file, 384);
|
|
4598
|
+
} catch {
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
function createBridgeTelemetry(deps) {
|
|
4602
|
+
let cachedConfig;
|
|
4603
|
+
const debounceLast = /* @__PURE__ */ new Map();
|
|
4604
|
+
let pendingFrame = null;
|
|
4605
|
+
function loadConfigCached() {
|
|
4606
|
+
if (cachedConfig === void 0) cachedConfig = readTelemetryConfig(deps.configDir);
|
|
4607
|
+
return cachedConfig;
|
|
4608
|
+
}
|
|
4609
|
+
function captureBridgeEvent(name, props) {
|
|
4610
|
+
try {
|
|
4611
|
+
if (isDoNotTrackSet(deps.env)) return;
|
|
4612
|
+
const cfg = loadConfigCached();
|
|
4613
|
+
if (cfg.disabled === true) return;
|
|
4614
|
+
const allowedKeys = EVENT_PROP_ALLOWLIST[name];
|
|
4615
|
+
if (!allowedKeys) return;
|
|
4616
|
+
const filteredProps = {};
|
|
4617
|
+
for (const key of allowedKeys) {
|
|
4618
|
+
if (Object.prototype.hasOwnProperty.call(props, key)) filteredProps[key] = props[key];
|
|
4619
|
+
}
|
|
4620
|
+
if (name === "tool_call_blocked") {
|
|
4621
|
+
const debounceKey = DEBOUNCE_KEY_PROPS.map((k) => String(filteredProps[k])).join("|");
|
|
4622
|
+
const nowMs = deps.now();
|
|
4623
|
+
const last = debounceLast.get(debounceKey);
|
|
4624
|
+
if (last !== void 0 && nowMs - last < DEBOUNCE_MS) return;
|
|
4625
|
+
debounceLast.set(debounceKey, nowMs);
|
|
4626
|
+
}
|
|
4627
|
+
if (!cfg.noticeShown) {
|
|
4628
|
+
deps.out(DISCLOSURE_LINE);
|
|
4629
|
+
cachedConfig = { ...cfg, noticeShown: true };
|
|
4630
|
+
try {
|
|
4631
|
+
writeTelemetryConfig(deps.configDir, cachedConfig);
|
|
4632
|
+
} catch {
|
|
4633
|
+
}
|
|
4634
|
+
}
|
|
4635
|
+
const frame = { type: "telemetry", event: name, props: filteredProps };
|
|
4636
|
+
const delivered = deps.send(frame);
|
|
4637
|
+
if (delivered !== true) pendingFrame = frame;
|
|
4638
|
+
} catch {
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
function flushPending() {
|
|
4642
|
+
try {
|
|
4643
|
+
if (pendingFrame === null) return;
|
|
4644
|
+
const frame = pendingFrame;
|
|
4645
|
+
pendingFrame = null;
|
|
4646
|
+
deps.send(frame);
|
|
4647
|
+
} catch {
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
return { captureBridgeEvent, flushPending };
|
|
4651
|
+
}
|
|
4652
|
+
function telemetryStatus(dir, env) {
|
|
4653
|
+
const cfg = readTelemetryConfig(dir);
|
|
4654
|
+
const forcedOffByDoNotTrack = isDoNotTrackSet(env);
|
|
4655
|
+
return {
|
|
4656
|
+
enabled: !forcedOffByDoNotTrack && cfg.disabled !== true,
|
|
4657
|
+
forcedOffByDoNotTrack,
|
|
4658
|
+
configPath: telemetryConfigPath(dir)
|
|
4659
|
+
};
|
|
4660
|
+
}
|
|
4661
|
+
function renderTelemetryStatusLines(view) {
|
|
4662
|
+
const lines = [];
|
|
4663
|
+
const state = view.enabled ? "ON" : "OFF";
|
|
4664
|
+
lines.push(`[braid] telemetry: ${state}`);
|
|
4665
|
+
if (view.forcedOffByDoNotTrack) {
|
|
4666
|
+
lines.push(" forced off by DO_NOT_TRACK (unset it to allow telemetry)");
|
|
4667
|
+
}
|
|
4668
|
+
lines.push(` config: ${view.configPath}`);
|
|
4669
|
+
return lines;
|
|
4670
|
+
}
|
|
4671
|
+
|
|
4531
4672
|
// src/service/cli.ts
|
|
4532
4673
|
import { execFile } from "child_process";
|
|
4533
4674
|
import { homedir as homedir5 } from "os";
|
|
4534
4675
|
|
|
4535
4676
|
// src/service/manager.ts
|
|
4536
|
-
import { mkdirSync as
|
|
4537
|
-
import { join as
|
|
4677
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, rmSync as rmSync2, existsSync as existsSync5 } from "fs";
|
|
4678
|
+
import { join as join9 } from "path";
|
|
4538
4679
|
|
|
4539
4680
|
// src/service/templates.ts
|
|
4540
4681
|
function escapeXml(s) {
|
|
@@ -4705,18 +4846,18 @@ function schtasksXml(p) {
|
|
|
4705
4846
|
}
|
|
4706
4847
|
|
|
4707
4848
|
// src/service/win32.ts
|
|
4708
|
-
import { mkdirSync as
|
|
4709
|
-
import { join as
|
|
4849
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, readdirSync as readdirSync4, readFileSync as readFileSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
4850
|
+
import { join as join8 } from "path";
|
|
4710
4851
|
function win32LogDir(homeDir) {
|
|
4711
|
-
return
|
|
4852
|
+
return join8(homeDir, "AppData", "Local", "braid", "logs");
|
|
4712
4853
|
}
|
|
4713
4854
|
function win32Manager(ctx) {
|
|
4714
|
-
const serviceDir =
|
|
4855
|
+
const serviceDir = join8(ctx.braidDir, "service");
|
|
4715
4856
|
const task = (name) => schtasksLabel(name);
|
|
4716
4857
|
const legacyTask = (name) => legacySchtasksLabel(name);
|
|
4717
|
-
const xmlPath = (name) =>
|
|
4718
|
-
const wrapperPath = (name) =>
|
|
4719
|
-
const launcherPath = (name) =>
|
|
4858
|
+
const xmlPath = (name) => join8(serviceDir, `${task(name)}.xml`);
|
|
4859
|
+
const wrapperPath = (name) => join8(serviceDir, `${task(name)}.cmd`);
|
|
4860
|
+
const launcherPath = (name) => join8(serviceDir, `${task(name)}.vbs`);
|
|
4720
4861
|
const st = (...args) => ctx.exec("schtasks", args);
|
|
4721
4862
|
const stx = async (...args) => {
|
|
4722
4863
|
const res = await st(...args);
|
|
@@ -4727,18 +4868,18 @@ function win32Manager(ctx) {
|
|
|
4727
4868
|
};
|
|
4728
4869
|
const killBridgeProcess = async (name) => {
|
|
4729
4870
|
const legacyAgents = legacyTwinPath(ctx.braidDir, ["agents"]);
|
|
4730
|
-
const agentDirs = [
|
|
4871
|
+
const agentDirs = [join8(ctx.braidDir, "agents"), ...legacyAgents ? [legacyAgents] : []];
|
|
4731
4872
|
const hostDirs = [];
|
|
4732
4873
|
for (const agentsDir of agentDirs) {
|
|
4733
4874
|
try {
|
|
4734
|
-
for (const host of readdirSync4(agentsDir)) hostDirs.push(
|
|
4875
|
+
for (const host of readdirSync4(agentsDir)) hostDirs.push(join8(agentsDir, host));
|
|
4735
4876
|
} catch {
|
|
4736
4877
|
}
|
|
4737
4878
|
}
|
|
4738
4879
|
for (const hostDir of hostDirs) {
|
|
4739
4880
|
let pid = NaN;
|
|
4740
4881
|
try {
|
|
4741
|
-
pid = Number(
|
|
4882
|
+
pid = Number(readFileSync7(join8(hostDir, `${name}.json.lock`), "utf8").trim());
|
|
4742
4883
|
} catch {
|
|
4743
4884
|
continue;
|
|
4744
4885
|
}
|
|
@@ -4754,15 +4895,15 @@ function win32Manager(ctx) {
|
|
|
4754
4895
|
}
|
|
4755
4896
|
};
|
|
4756
4897
|
const write = (name) => {
|
|
4757
|
-
|
|
4898
|
+
mkdirSync6(serviceDir, { recursive: true });
|
|
4758
4899
|
const entryPath = pinnedEntryPath({ braidDir: ctx.braidDir, platform: "win32" });
|
|
4759
4900
|
const cmd = schtasksWrapperCmd({ name, nodePath: ctx.nodePath, entryPath });
|
|
4760
|
-
|
|
4901
|
+
writeFileSync6(wrapperPath(name), cmd);
|
|
4761
4902
|
const vbs = schtasksLauncherVbs({ wrapperPath: wrapperPath(name) });
|
|
4762
|
-
|
|
4903
|
+
writeFileSync6(launcherPath(name), vbs);
|
|
4763
4904
|
const xml = schtasksXml({ name, launcherPath: launcherPath(name) });
|
|
4764
4905
|
const path2 = xmlPath(name);
|
|
4765
|
-
|
|
4906
|
+
writeFileSync6(path2, Buffer.concat([Buffer.from([255, 254]), Buffer.from(xml, "utf16le")]));
|
|
4766
4907
|
return path2;
|
|
4767
4908
|
};
|
|
4768
4909
|
return {
|
|
@@ -4782,7 +4923,7 @@ function win32Manager(ctx) {
|
|
|
4782
4923
|
await st("/delete", "/tn", task(name), "/f");
|
|
4783
4924
|
await st("/delete", "/tn", legacyTask(name), "/f");
|
|
4784
4925
|
try {
|
|
4785
|
-
unlinkSync3(
|
|
4926
|
+
unlinkSync3(join8(runDir(ctx.braidDir), `${name}.token`));
|
|
4786
4927
|
} catch {
|
|
4787
4928
|
}
|
|
4788
4929
|
const legacyToken = legacyTwinPath(ctx.braidDir, ["run", `${name}.token`]);
|
|
@@ -4820,7 +4961,7 @@ function win32Manager(ctx) {
|
|
|
4820
4961
|
return { installed, loaded, running };
|
|
4821
4962
|
},
|
|
4822
4963
|
logsPath(name) {
|
|
4823
|
-
return
|
|
4964
|
+
return join8(win32LogDir(ctx.homeDir), `${name}.err.log`);
|
|
4824
4965
|
}
|
|
4825
4966
|
};
|
|
4826
4967
|
}
|
|
@@ -4834,7 +4975,7 @@ var NotSupportedError = class extends Error {
|
|
|
4834
4975
|
};
|
|
4835
4976
|
function makeServiceManager(platform, exec, deps = {}) {
|
|
4836
4977
|
const homeDir = deps.homeDir ?? process.env.HOME ?? "";
|
|
4837
|
-
const braidDir = deps.braidDir ??
|
|
4978
|
+
const braidDir = deps.braidDir ?? join9(homeDir, ".braid");
|
|
4838
4979
|
const uid = deps.uid ?? (typeof process.getuid === "function" ? process.getuid() : 0);
|
|
4839
4980
|
const nodePath = deps.nodePath ?? process.execPath;
|
|
4840
4981
|
const entryPath = pinnedEntryPath({ braidDir });
|
|
@@ -4859,18 +5000,18 @@ function notSupportedManager(platform) {
|
|
|
4859
5000
|
};
|
|
4860
5001
|
}
|
|
4861
5002
|
function darwinManager(ctx) {
|
|
4862
|
-
const launchAgentsDir =
|
|
4863
|
-
const logDir =
|
|
4864
|
-
const legacyLogDir =
|
|
4865
|
-
const plistPath = (name) =>
|
|
4866
|
-
const legacyPlistPath = (name) =>
|
|
5003
|
+
const launchAgentsDir = join9(ctx.homeDir, "Library", "LaunchAgents");
|
|
5004
|
+
const logDir = join9(ctx.homeDir, "Library", "Logs", "braid");
|
|
5005
|
+
const legacyLogDir = join9(ctx.homeDir, "Library", "Logs", "arp-bridge");
|
|
5006
|
+
const plistPath = (name) => join9(launchAgentsDir, `${launchdLabel(name)}.plist`);
|
|
5007
|
+
const legacyPlistPath = (name) => join9(launchAgentsDir, `${legacyLaunchdLabel(name)}.plist`);
|
|
4867
5008
|
const legacyInstalled = (name) => !existsSync5(plistPath(name)) && existsSync5(legacyPlistPath(name));
|
|
4868
5009
|
const domainTarget = `gui/${ctx.uid}`;
|
|
4869
5010
|
const serviceTarget = (name) => `${domainTarget}/${legacyInstalled(name) ? legacyLaunchdLabel(name) : launchdLabel(name)}`;
|
|
4870
5011
|
const write = (name) => {
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
5012
|
+
mkdirSync7(launchAgentsDir, { recursive: true });
|
|
5013
|
+
mkdirSync7(logDir, { recursive: true });
|
|
5014
|
+
writeFileSync7(
|
|
4874
5015
|
plistPath(name),
|
|
4875
5016
|
launchdPlist({ name, nodePath: ctx.nodePath, entryPath: ctx.entryPath, logDir }),
|
|
4876
5017
|
{ mode: 420 }
|
|
@@ -4928,23 +5069,23 @@ function darwinManager(ctx) {
|
|
|
4928
5069
|
};
|
|
4929
5070
|
},
|
|
4930
5071
|
logsPath(name) {
|
|
4931
|
-
return
|
|
5072
|
+
return join9(legacyInstalled(name) ? legacyLogDir : logDir, `${name}.err.log`);
|
|
4932
5073
|
}
|
|
4933
5074
|
};
|
|
4934
5075
|
}
|
|
4935
5076
|
function linuxManager(ctx) {
|
|
4936
|
-
const unitDir =
|
|
5077
|
+
const unitDir = join9(ctx.homeDir, ".config", "systemd", "user");
|
|
4937
5078
|
const newUnitName = (name) => `braid-${name}.service`;
|
|
4938
5079
|
const legacyUnitName = (name) => `arp-bridge-${name}.service`;
|
|
4939
|
-
const newUnitPath = (name) =>
|
|
4940
|
-
const legacyUnitPath = (name) =>
|
|
5080
|
+
const newUnitPath = (name) => join9(unitDir, newUnitName(name));
|
|
5081
|
+
const legacyUnitPath = (name) => join9(unitDir, legacyUnitName(name));
|
|
4941
5082
|
const legacyInstalled = (name) => !existsSync5(newUnitPath(name)) && existsSync5(legacyUnitPath(name));
|
|
4942
5083
|
const unitName = (name) => legacyInstalled(name) ? legacyUnitName(name) : newUnitName(name);
|
|
4943
5084
|
const unitPath = (name) => legacyInstalled(name) ? legacyUnitPath(name) : newUnitPath(name);
|
|
4944
5085
|
const sc = (...args) => ctx.exec("systemctl", ["--user", ...args]);
|
|
4945
5086
|
const write = (name) => {
|
|
4946
|
-
|
|
4947
|
-
|
|
5087
|
+
mkdirSync7(unitDir, { recursive: true });
|
|
5088
|
+
writeFileSync7(
|
|
4948
5089
|
newUnitPath(name),
|
|
4949
5090
|
systemdUnit({ name, nodePath: ctx.nodePath, entryPath: ctx.entryPath }),
|
|
4950
5091
|
{ mode: 420 }
|
|
@@ -5218,17 +5359,17 @@ function printStatusHuman(name, st, logsPath) {
|
|
|
5218
5359
|
}
|
|
5219
5360
|
|
|
5220
5361
|
// src/service/serviceLog.ts
|
|
5221
|
-
import { createWriteStream, mkdirSync as
|
|
5222
|
-
import { dirname as
|
|
5362
|
+
import { createWriteStream, mkdirSync as mkdirSync8 } from "fs";
|
|
5363
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
5223
5364
|
|
|
5224
5365
|
// src/service/logRotation.ts
|
|
5225
5366
|
import {
|
|
5226
5367
|
existsSync as existsSync6,
|
|
5227
5368
|
statSync as statSync2,
|
|
5228
|
-
renameSync as
|
|
5369
|
+
renameSync as renameSync4,
|
|
5229
5370
|
rmSync as rmSync3,
|
|
5230
|
-
readFileSync as
|
|
5231
|
-
writeFileSync as
|
|
5371
|
+
readFileSync as readFileSync8,
|
|
5372
|
+
writeFileSync as writeFileSync8
|
|
5232
5373
|
} from "fs";
|
|
5233
5374
|
import { gzipSync } from "zlib";
|
|
5234
5375
|
var DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -5252,18 +5393,18 @@ function rotateLogFileIfNeeded(logPath, opts = {}) {
|
|
|
5252
5393
|
}
|
|
5253
5394
|
for (let n = keepCount - 1; n >= 1; n--) {
|
|
5254
5395
|
try {
|
|
5255
|
-
if (existsSync6(gz(n)))
|
|
5396
|
+
if (existsSync6(gz(n))) renameSync4(gz(n), gz(n + 1));
|
|
5256
5397
|
} catch {
|
|
5257
5398
|
}
|
|
5258
5399
|
}
|
|
5259
5400
|
try {
|
|
5260
|
-
const raw =
|
|
5261
|
-
|
|
5401
|
+
const raw = readFileSync8(logPath);
|
|
5402
|
+
writeFileSync8(gz(1), gzipSync(raw));
|
|
5262
5403
|
} catch {
|
|
5263
5404
|
return;
|
|
5264
5405
|
}
|
|
5265
5406
|
try {
|
|
5266
|
-
|
|
5407
|
+
writeFileSync8(logPath, "");
|
|
5267
5408
|
} catch {
|
|
5268
5409
|
}
|
|
5269
5410
|
} catch {
|
|
@@ -5272,16 +5413,16 @@ function rotateLogFileIfNeeded(logPath, opts = {}) {
|
|
|
5272
5413
|
|
|
5273
5414
|
// src/service/serviceLog.ts
|
|
5274
5415
|
function darwinLogDir(homeDir) {
|
|
5275
|
-
return
|
|
5416
|
+
return join10(homeDir, "Library", "Logs", "braid");
|
|
5276
5417
|
}
|
|
5277
5418
|
function serviceLogTargets(name, platform, homeDir) {
|
|
5278
5419
|
if (platform === "darwin") {
|
|
5279
5420
|
const d = darwinLogDir(homeDir);
|
|
5280
|
-
return [
|
|
5421
|
+
return [join10(d, `${name}.out.log`), join10(d, `${name}.err.log`)];
|
|
5281
5422
|
}
|
|
5282
5423
|
if (platform === "win32") {
|
|
5283
5424
|
const d = win32LogDir(homeDir);
|
|
5284
|
-
return [
|
|
5425
|
+
return [join10(d, `${name}.out.log`), join10(d, `${name}.err.log`)];
|
|
5285
5426
|
}
|
|
5286
5427
|
return [];
|
|
5287
5428
|
}
|
|
@@ -5331,7 +5472,7 @@ function initServiceLogging(opts) {
|
|
|
5331
5472
|
};
|
|
5332
5473
|
}
|
|
5333
5474
|
function defaultTeeStream(path2) {
|
|
5334
|
-
|
|
5475
|
+
mkdirSync8(dirname6(path2), { recursive: true });
|
|
5335
5476
|
const stream = createWriteStream(path2, { flags: "a" });
|
|
5336
5477
|
stream.on("error", () => {
|
|
5337
5478
|
});
|
|
@@ -5373,7 +5514,7 @@ function printStatus(agentName, json) {
|
|
|
5373
5514
|
}
|
|
5374
5515
|
const readTextFile = (p) => {
|
|
5375
5516
|
try {
|
|
5376
|
-
return
|
|
5517
|
+
return readFileSync9(p, "utf8");
|
|
5377
5518
|
} catch {
|
|
5378
5519
|
return null;
|
|
5379
5520
|
}
|
|
@@ -5464,7 +5605,7 @@ function printScope(agentName) {
|
|
|
5464
5605
|
}
|
|
5465
5606
|
const readTextFile = (p) => {
|
|
5466
5607
|
try {
|
|
5467
|
-
return
|
|
5608
|
+
return readFileSync9(p, "utf8");
|
|
5468
5609
|
} catch {
|
|
5469
5610
|
return null;
|
|
5470
5611
|
}
|
|
@@ -5504,7 +5645,7 @@ function scopeAllow(agentName, host) {
|
|
|
5504
5645
|
const { agent: provider } = resolveAgentSelection(process.env);
|
|
5505
5646
|
const readTextFile = (p) => {
|
|
5506
5647
|
try {
|
|
5507
|
-
return
|
|
5648
|
+
return readFileSync9(p, "utf8");
|
|
5508
5649
|
} catch {
|
|
5509
5650
|
return null;
|
|
5510
5651
|
}
|
|
@@ -5538,6 +5679,19 @@ Set the provider's config to use ${normalizedHost} and try again, or check BRAID
|
|
|
5538
5679
|
console.log(`[braid] pinned: ${normalizedHost} (source: ${match.source})`);
|
|
5539
5680
|
console.log(`[braid] restart the bridge for the change to take effect.`);
|
|
5540
5681
|
}
|
|
5682
|
+
function runTelemetryVerb(verb) {
|
|
5683
|
+
const dir = configDir(process.env);
|
|
5684
|
+
if (verb === "disable") {
|
|
5685
|
+
const cfg = readTelemetryConfig(dir);
|
|
5686
|
+
writeTelemetryConfig(dir, { ...cfg, disabled: true });
|
|
5687
|
+
} else if (verb === "enable") {
|
|
5688
|
+
const cfg = readTelemetryConfig(dir);
|
|
5689
|
+
const { disabled, ...rest } = cfg;
|
|
5690
|
+
writeTelemetryConfig(dir, rest);
|
|
5691
|
+
}
|
|
5692
|
+
const view = telemetryStatus(dir, process.env);
|
|
5693
|
+
for (const line of renderTelemetryStatusLines(view)) console.log(line);
|
|
5694
|
+
}
|
|
5541
5695
|
var resolvedAgentName = "";
|
|
5542
5696
|
async function main() {
|
|
5543
5697
|
applyEnvAliases();
|
|
@@ -5580,6 +5734,10 @@ async function main() {
|
|
|
5580
5734
|
scopeAllow(invocation.agent, invocation.host);
|
|
5581
5735
|
return;
|
|
5582
5736
|
}
|
|
5737
|
+
if (invocation.kind === "telemetry") {
|
|
5738
|
+
runTelemetryVerb(invocation.verb);
|
|
5739
|
+
return;
|
|
5740
|
+
}
|
|
5583
5741
|
if (invocation.kind === "status") {
|
|
5584
5742
|
printStatus(invocation.agent, invocation.json);
|
|
5585
5743
|
return;
|
|
@@ -5609,6 +5767,14 @@ async function main() {
|
|
|
5609
5767
|
recover: invocation.recover
|
|
5610
5768
|
}));
|
|
5611
5769
|
}
|
|
5770
|
+
const relayRef = { current: null };
|
|
5771
|
+
const telemetry = createBridgeTelemetry({
|
|
5772
|
+
env: process.env,
|
|
5773
|
+
configDir: configDir(process.env),
|
|
5774
|
+
now: Date.now,
|
|
5775
|
+
out: (line) => console.log(line),
|
|
5776
|
+
send: (frame) => relayRef.current ? relayRef.current.sendTelemetry(frame) : false
|
|
5777
|
+
});
|
|
5612
5778
|
{
|
|
5613
5779
|
const parsed = parseRunFlags(invocation.argv);
|
|
5614
5780
|
const gate = parsed.ok ? shouldOfferGuidedStart(parsed.rest, process.env) : { offer: false };
|
|
@@ -5680,7 +5846,8 @@ async function main() {
|
|
|
5680
5846
|
printStatus: (name) => printStatus(name, false),
|
|
5681
5847
|
verbose: invocation.argv.includes("--verbose"),
|
|
5682
5848
|
out: (line) => console.log(line),
|
|
5683
|
-
err: (line) => console.error(line)
|
|
5849
|
+
err: (line) => console.error(line),
|
|
5850
|
+
telemetry
|
|
5684
5851
|
});
|
|
5685
5852
|
if (outcome.action === "exit") process.exit(outcome.code);
|
|
5686
5853
|
if (outcome.runToolMode !== void 0) {
|
|
@@ -5688,7 +5855,7 @@ async function main() {
|
|
|
5688
5855
|
}
|
|
5689
5856
|
}
|
|
5690
5857
|
}
|
|
5691
|
-
const cfg = await resolveConfig(invocation.argv, process.env);
|
|
5858
|
+
const cfg = await resolveConfig(invocation.argv, process.env, telemetry);
|
|
5692
5859
|
resolvedAgentName = cfg.agentName ?? "";
|
|
5693
5860
|
{
|
|
5694
5861
|
const decision = shouldSuppressStart(
|
|
@@ -5740,7 +5907,7 @@ async function main() {
|
|
|
5740
5907
|
const epScopeOff = process.env.BRAID_SCOPE === "off" || cfg.scope?.disabled === true;
|
|
5741
5908
|
const readTextFile = (p) => {
|
|
5742
5909
|
try {
|
|
5743
|
-
return
|
|
5910
|
+
return readFileSync9(p, "utf8");
|
|
5744
5911
|
} catch {
|
|
5745
5912
|
return null;
|
|
5746
5913
|
}
|
|
@@ -5900,11 +6067,14 @@ async function main() {
|
|
|
5900
6067
|
// Honest status: declare "connected" only after the relay confirms the agent
|
|
5901
6068
|
// (auth succeeded), never optimistically on factory return. Rendered as the
|
|
5902
6069
|
// operator-guidance block: mode + keys, distinct from stream noise.
|
|
5903
|
-
onReady: () =>
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
6070
|
+
onReady: () => {
|
|
6071
|
+
telemetry.flushPending();
|
|
6072
|
+
console.log(banner("braid", "CONNECTED", connectedBannerLines({
|
|
6073
|
+
keysLive,
|
|
6074
|
+
serviceEligible,
|
|
6075
|
+
agentName: cfg.agentName ? sanitizeForTty(cfg.agentName) : void 0
|
|
6076
|
+
})));
|
|
6077
|
+
},
|
|
5908
6078
|
// Disk-backed card cache: skips the expensive elicit subprocess on reconnects
|
|
5909
6079
|
// when the agent's skill inventory hasn't changed (BL-003).
|
|
5910
6080
|
cardCache: {
|
|
@@ -5913,6 +6083,13 @@ async function main() {
|
|
|
5913
6083
|
return r && r.fingerprint === fp ? r : null;
|
|
5914
6084
|
},
|
|
5915
6085
|
save: (c, fp, card) => saveCachedCard(c, fp, card)
|
|
6086
|
+
},
|
|
6087
|
+
// BL-177 Task 4: the SAME telemetry instance constructed above, now wired to
|
|
6088
|
+
// the live RelayClient the instant it exists (onRelayCreated) so the two
|
|
6089
|
+
// denial-path capture sites (ACP/SDK, wired via makeAdapter) reach the wire.
|
|
6090
|
+
telemetry,
|
|
6091
|
+
onRelayCreated: (relay) => {
|
|
6092
|
+
relayRef.current = relay;
|
|
5916
6093
|
}
|
|
5917
6094
|
});
|
|
5918
6095
|
if (cfg.runtimeVersion) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowyroad/braid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.84.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",
|
|
@@ -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.22",
|
|
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.84.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.84.1",
|
|
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.22",
|
|
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.22",
|
|
1187
|
+
"resolved": "https://registry.npmjs.org/@snowyroad/braid-tui/-/braid-tui-0.0.22.tgz",
|
|
1188
|
+
"integrity": "sha512-Sgsn8Dl8v6qLwZjRnlPUKJEXM6nOLFu0B/d2bOAJycSu1AI35LyzWjrOoui6TIwDB7KKBYVkbdOKqfwUtHFbyA==",
|
|
1189
1189
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
1190
1190
|
"dependencies": {
|
|
1191
1191
|
"ink": "^5.2.0",
|