@securecoms/agent-bridge 0.26.2 → 0.26.4
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/bin.js +84 -3
- package/dist/bridge-agent.d.ts +22 -0
- package/dist/index.js +84 -3
- package/dist/service-log.d.ts +14 -1
- package/dist/version.d.ts +1 -1
- package/dist/voice/fast-prompt.d.ts +9 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -7597,6 +7597,15 @@ function systemPrompt(p, settings, agentName) {
|
|
|
7597
7597
|
}
|
|
7598
7598
|
|
|
7599
7599
|
// src/service-log.ts
|
|
7600
|
+
var ENUM_VALUE = /^[a-z][a-z_]{0,31}$/;
|
|
7601
|
+
var ServiceLogEnum = class _ServiceLogEnum {
|
|
7602
|
+
constructor(value) {
|
|
7603
|
+
this.value = value;
|
|
7604
|
+
}
|
|
7605
|
+
static of(value, allowed) {
|
|
7606
|
+
return new _ServiceLogEnum(allowed.includes(value) && ENUM_VALUE.test(value) ? value : "invalid");
|
|
7607
|
+
}
|
|
7608
|
+
};
|
|
7600
7609
|
var TOKEN = /^[a-z][a-z0-9_.-]{0,63}$/;
|
|
7601
7610
|
var CLASS = /^[A-Za-z_$][A-Za-z0-9_$]{0,63}$/;
|
|
7602
7611
|
function errorClass(error) {
|
|
@@ -7626,6 +7635,7 @@ function formatServiceLog(level3, event, fields = {}, error) {
|
|
|
7626
7635
|
if (!TOKEN.test(key)) continue;
|
|
7627
7636
|
if (typeof value === "boolean") parts.push(`${key}=${value}`);
|
|
7628
7637
|
else if (typeof value === "number" && Number.isFinite(value)) parts.push(`${key}=${value}`);
|
|
7638
|
+
else if (value instanceof ServiceLogEnum && ENUM_VALUE.test(value.value)) parts.push(`${key}=${value.value}`);
|
|
7629
7639
|
}
|
|
7630
7640
|
if (error !== void 0) parts.push(`error_class=${errorClass(error)}`);
|
|
7631
7641
|
return parts.join(" ");
|
|
@@ -28502,11 +28512,26 @@ function clampContent(content, max = MAX_MESSAGE_CHARS) {
|
|
|
28502
28512
|
const marker = "\u2026";
|
|
28503
28513
|
return content.slice(0, Math.max(0, max - marker.length)) + marker;
|
|
28504
28514
|
}
|
|
28515
|
+
var FAST_DEFER_RULE = `Whenever you cannot answer completely from what is shown here, never say you can't, that you lack a tool, access or real-time data: reply with one short sentence like "Let me check that." and end with the exact token [[DEFER]] so the full agent answers next.`;
|
|
28516
|
+
var REFUSAL_PATTERNS = [
|
|
28517
|
+
// "I can't / cannot / am unable to / am not able to <check|access|...>", optional adverb.
|
|
28518
|
+
/\b(?:can't|cannot|can\s+not|unable\s+to|not\s+able\s+to)\s+(?:\w+ly\s+)?(?:check|access|look|see|browse|search|get|provide|give\s+you|find|retrieve|fetch|pull|know|help\s+with\s+(?:that|this)|do\s+that)\b/,
|
|
28519
|
+
// "I don't / do not / doesn't have (a|any|the)? <tool|access|ability|real-time|live ...>".
|
|
28520
|
+
/\b(?:don't|do\s+not|doesn't|does\s+not|lack)\s+(?:have\s+)?(?:a\s+|any\s+|the\s+)?(?:tool|tools|access|ability|capability|real[-\s]?time|live|current|internet|web|way\s+to)\b/,
|
|
28521
|
+
/\bno\s+(?:access|tools?|internet|web\s+access|real[-\s]?time|live\s+data|way\s+to\s+(?:check|access|know|see))\b/,
|
|
28522
|
+
/\bas\s+an\s+(?:ai|a\.i\.|language\s+model)\b/,
|
|
28523
|
+
/\bbeyond\s+my\s+(?:capabilities|abilities|capability)\b/,
|
|
28524
|
+
/\b(?:knowledge\s+cutoff|training\s+data)\b/
|
|
28525
|
+
];
|
|
28526
|
+
function isRefusal(reply) {
|
|
28527
|
+
const t = reply.toLowerCase().replace(/[‘’]/g, "'");
|
|
28528
|
+
return REFUSAL_PATTERNS.some((re) => re.test(t));
|
|
28529
|
+
}
|
|
28505
28530
|
function buildFastMessages(args2) {
|
|
28506
28531
|
const cap = args2.charCap ?? FAST_RECENT_CHARS_CAP;
|
|
28507
28532
|
const sp = systemPrompt(args2.persona, args2.settings, args2.agentName);
|
|
28508
28533
|
const head = sp.slice(0, sp.length - CONTINUITY_CLAUSE.length).trimEnd();
|
|
28509
|
-
const system = `${head} ${args2.guidance.trim()} ${CONTINUITY_CLAUSE}`;
|
|
28534
|
+
const system = `${head} ${args2.guidance.trim()} ${FAST_DEFER_RULE} ${CONTINUITY_CLAUSE}`;
|
|
28510
28535
|
const recent = args2.memory.recent.map((t) => ({
|
|
28511
28536
|
role: t.role === "agent" ? "assistant" : "user",
|
|
28512
28537
|
content: t.text
|
|
@@ -29694,6 +29719,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
29694
29719
|
fastPathDeferred = 0;
|
|
29695
29720
|
/** Of those, `full`-mode turns the deterministic pre-check deferred without calling the fast model. */
|
|
29696
29721
|
fastPathIntentDeferred = 0;
|
|
29722
|
+
/** Of those, fast-model replies that voiced inability ("I can't check...") — never spoken; the ack is spoken instead. */
|
|
29723
|
+
fastPathRefusalDeferred = 0;
|
|
29697
29724
|
/**
|
|
29698
29725
|
* Task 8: count of live AUTHENTICATED /v1/ws sessions, so a later `status`
|
|
29699
29726
|
* verb can tell "bridge process up" (the app's green dot, which only
|
|
@@ -31971,11 +31998,15 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
31971
31998
|
});
|
|
31972
31999
|
const maxSentences = this.voiceSettings?.brevity === "one" ? 1 : 2;
|
|
31973
32000
|
const raw = await this.latency.time("voice.fast_llm", () => fastLlm.complete(messages));
|
|
31974
|
-
const
|
|
31975
|
-
if (!reply) return "failed";
|
|
32001
|
+
const split3 = splitFastReply(raw, 200, maxSentences);
|
|
32002
|
+
if (!split3.reply) return "failed";
|
|
32003
|
+
const refusal = !isGreeting && isRefusal(split3.reply);
|
|
32004
|
+
const defer = split3.defer || refusal;
|
|
32005
|
+
const reply = refusal ? deferAck(m.messageId) : split3.reply;
|
|
31976
32006
|
await this.sendSpoken(m.channelId, reply, fastReplyId(this.replyIdKey, this.cfg.identityId, m.channelId, m.messageId));
|
|
31977
32007
|
this.fastPathSent++;
|
|
31978
32008
|
if (defer) this.fastPathDeferred++;
|
|
32009
|
+
if (refusal) this.fastPathRefusalDeferred++;
|
|
31979
32010
|
return defer ? "deferred" : "answered";
|
|
31980
32011
|
} catch (e) {
|
|
31981
32012
|
this.fastPathFailed++;
|
|
@@ -32344,6 +32375,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
32344
32375
|
if (ownerDirect || ownerRelayed) tool_grants = OWNER_DM_DEFAULT_READ_GRANTS;
|
|
32345
32376
|
}
|
|
32346
32377
|
}
|
|
32378
|
+
this.logAuthzDecision(channelId, senderId, relaySpeakerId, audience, tool_grants.length);
|
|
32347
32379
|
return Object.freeze({
|
|
32348
32380
|
version: 1,
|
|
32349
32381
|
audience,
|
|
@@ -32351,6 +32383,54 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
32351
32383
|
tool_grants
|
|
32352
32384
|
});
|
|
32353
32385
|
}
|
|
32386
|
+
/**
|
|
32387
|
+
* One content-free INFO line per authorization decision (every call site of
|
|
32388
|
+
* authorizationFor stages exactly one runtime turn). It says WHY a turn got
|
|
32389
|
+
* the grants it got, without any identifier, name or text: every field is a
|
|
32390
|
+
* boolean, a count, or a closed-set label.
|
|
32391
|
+
*
|
|
32392
|
+
* owner_pin pinned | anomalous | missing (channelOwner cache)
|
|
32393
|
+
* owner_row_is_agent_owner the pinned owner's roster row; `absent` when
|
|
32394
|
+
* there is no pin or the row is not in the roster
|
|
32395
|
+
* sender_is_owner sender identity == pinned owner identity
|
|
32396
|
+
* relay sender's roster row has is_relay
|
|
32397
|
+
* relay_speaker_is_owner `na` unless a relay speaker was passed
|
|
32398
|
+
* kill_switch SECURECOMS_OWNER_DM_DEFAULT_GRANTS as read at
|
|
32399
|
+
* startup: `on` = default grants enabled
|
|
32400
|
+
* meta_fresh / roster_fresh whether the channel meta / roster caches were
|
|
32401
|
+
* fresh (either stale forces audience=unknown)
|
|
32402
|
+
*/
|
|
32403
|
+
logAuthzDecision(channelId, senderId, relaySpeakerId, audience, grants) {
|
|
32404
|
+
const meta = this.freshMeta(channelId);
|
|
32405
|
+
const roster = this.freshRoster(channelId);
|
|
32406
|
+
const sender = roster?.get(senderId);
|
|
32407
|
+
const pinned = this.channelOwner.get(channelId);
|
|
32408
|
+
const ownerId = pinned && pinned !== "anomalous" ? pinned.identityId : void 0;
|
|
32409
|
+
const ownerRow = ownerId !== void 0 ? roster?.get(ownerId) : void 0;
|
|
32410
|
+
const ownerPin = pinned === void 0 ? "missing" : pinned === "anomalous" ? "anomalous" : "pinned";
|
|
32411
|
+
const ownerRowIsAgentOwner = ownerRow === void 0 ? "absent" : ownerRow.is_agent_owner ? "true" : "false";
|
|
32412
|
+
const speakerIsOwner = relaySpeakerId === void 0 ? "na" : ownerId !== void 0 && relaySpeakerId === ownerId ? "true" : "false";
|
|
32413
|
+
writeServiceLog("info", "authz.decision", {
|
|
32414
|
+
audience: ServiceLogEnum.of(audience, [
|
|
32415
|
+
"owner_dm",
|
|
32416
|
+
"other_human_dm",
|
|
32417
|
+
"team_room",
|
|
32418
|
+
"partner_room",
|
|
32419
|
+
"agent_to_agent",
|
|
32420
|
+
"unknown"
|
|
32421
|
+
]),
|
|
32422
|
+
grants,
|
|
32423
|
+
owner_pin: ServiceLogEnum.of(ownerPin, ["pinned", "anomalous", "missing"]),
|
|
32424
|
+
owner_row_is_agent_owner: ServiceLogEnum.of(ownerRowIsAgentOwner, ["true", "false", "absent"]),
|
|
32425
|
+
sender_is_owner: ownerId !== void 0 && senderId === ownerId,
|
|
32426
|
+
relay: sender?.is_relay === true,
|
|
32427
|
+
relay_speaker_is_owner: ServiceLogEnum.of(speakerIsOwner, ["true", "false", "na"]),
|
|
32428
|
+
is_dm: meta?.is_dm === true,
|
|
32429
|
+
kill_switch: ServiceLogEnum.of(this.ownerDmDefaultGrants ? "on" : "off", ["on", "off"]),
|
|
32430
|
+
meta_fresh: meta !== void 0,
|
|
32431
|
+
roster_fresh: roster !== void 0
|
|
32432
|
+
});
|
|
32433
|
+
}
|
|
32354
32434
|
/**
|
|
32355
32435
|
* Task 8 (owner-attested Reset Secure Session): STRUCTURAL owner selection —
|
|
32356
32436
|
* NEVER read the server's `role` field to pick the owner. In a DM the owner
|
|
@@ -33878,6 +33958,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
33878
33958
|
fast_path_skipped: this.fastPathSkipped,
|
|
33879
33959
|
fast_path_deferred: this.fastPathDeferred,
|
|
33880
33960
|
fast_path_intent_deferred: this.fastPathIntentDeferred,
|
|
33961
|
+
fast_path_refusal_deferred: this.fastPathRefusalDeferred,
|
|
33881
33962
|
fast_path_mode: this.cfg.voiceFastPathMode ?? "full",
|
|
33882
33963
|
voice_settings_version: this.voiceSettingsVersion,
|
|
33883
33964
|
voice_settings_source: this.voiceSettingsSource,
|
package/dist/bridge-agent.d.ts
CHANGED
|
@@ -800,6 +800,8 @@ export declare class BridgeAgent {
|
|
|
800
800
|
private fastPathDeferred;
|
|
801
801
|
/** Of those, `full`-mode turns the deterministic pre-check deferred without calling the fast model. */
|
|
802
802
|
private fastPathIntentDeferred;
|
|
803
|
+
/** Of those, fast-model replies that voiced inability ("I can't check...") — never spoken; the ack is spoken instead. */
|
|
804
|
+
private fastPathRefusalDeferred;
|
|
803
805
|
/**
|
|
804
806
|
* Task 8: count of live AUTHENTICATED /v1/ws sessions, so a later `status`
|
|
805
807
|
* verb can tell "bridge process up" (the app's green dot, which only
|
|
@@ -1967,6 +1969,24 @@ export declare class BridgeAgent {
|
|
|
1967
1969
|
* Everything else gets the frozen empty list.
|
|
1968
1970
|
*/
|
|
1969
1971
|
private authorizationFor;
|
|
1972
|
+
/**
|
|
1973
|
+
* One content-free INFO line per authorization decision (every call site of
|
|
1974
|
+
* authorizationFor stages exactly one runtime turn). It says WHY a turn got
|
|
1975
|
+
* the grants it got, without any identifier, name or text: every field is a
|
|
1976
|
+
* boolean, a count, or a closed-set label.
|
|
1977
|
+
*
|
|
1978
|
+
* owner_pin pinned | anomalous | missing (channelOwner cache)
|
|
1979
|
+
* owner_row_is_agent_owner the pinned owner's roster row; `absent` when
|
|
1980
|
+
* there is no pin or the row is not in the roster
|
|
1981
|
+
* sender_is_owner sender identity == pinned owner identity
|
|
1982
|
+
* relay sender's roster row has is_relay
|
|
1983
|
+
* relay_speaker_is_owner `na` unless a relay speaker was passed
|
|
1984
|
+
* kill_switch SECURECOMS_OWNER_DM_DEFAULT_GRANTS as read at
|
|
1985
|
+
* startup: `on` = default grants enabled
|
|
1986
|
+
* meta_fresh / roster_fresh whether the channel meta / roster caches were
|
|
1987
|
+
* fresh (either stale forces audience=unknown)
|
|
1988
|
+
*/
|
|
1989
|
+
private logAuthzDecision;
|
|
1970
1990
|
/**
|
|
1971
1991
|
* Task 8 (owner-attested Reset Secure Session): STRUCTURAL owner selection —
|
|
1972
1992
|
* NEVER read the server's `role` field to pick the owner. In a DM the owner
|
|
@@ -2326,6 +2346,8 @@ export declare class BridgeAgent {
|
|
|
2326
2346
|
fast_path_deferred: number;
|
|
2327
2347
|
/** `full`-mode turns deferred by the deterministic pre-check (no fast model call). */
|
|
2328
2348
|
fast_path_intent_deferred: number;
|
|
2349
|
+
/** Fast-model replies that voiced inability/lack of tools: never spoken, deferred behind the canned ack instead. */
|
|
2350
|
+
fast_path_refusal_deferred: number;
|
|
2329
2351
|
/** Credential `voice_fast_path_mode`: "full" (default), "ack_only" or "off". */
|
|
2330
2352
|
fast_path_mode: "full" | "ack_only" | "off";
|
|
2331
2353
|
/** Plan 2026-09-06 (agent voice settings): version of the loaded backend
|
package/dist/index.js
CHANGED
|
@@ -27308,6 +27308,15 @@ var TokenBucketFloor = class {
|
|
|
27308
27308
|
};
|
|
27309
27309
|
|
|
27310
27310
|
// src/service-log.ts
|
|
27311
|
+
var ENUM_VALUE = /^[a-z][a-z_]{0,31}$/;
|
|
27312
|
+
var ServiceLogEnum = class _ServiceLogEnum {
|
|
27313
|
+
constructor(value) {
|
|
27314
|
+
this.value = value;
|
|
27315
|
+
}
|
|
27316
|
+
static of(value, allowed) {
|
|
27317
|
+
return new _ServiceLogEnum(allowed.includes(value) && ENUM_VALUE.test(value) ? value : "invalid");
|
|
27318
|
+
}
|
|
27319
|
+
};
|
|
27311
27320
|
var TOKEN2 = /^[a-z][a-z0-9_.-]{0,63}$/;
|
|
27312
27321
|
var CLASS2 = /^[A-Za-z_$][A-Za-z0-9_$]{0,63}$/;
|
|
27313
27322
|
function errorClass2(error) {
|
|
@@ -27337,6 +27346,7 @@ function formatServiceLog2(level3, event, fields = {}, error) {
|
|
|
27337
27346
|
if (!TOKEN2.test(key)) continue;
|
|
27338
27347
|
if (typeof value === "boolean") parts.push(`${key}=${value}`);
|
|
27339
27348
|
else if (typeof value === "number" && Number.isFinite(value)) parts.push(`${key}=${value}`);
|
|
27349
|
+
else if (value instanceof ServiceLogEnum && ENUM_VALUE.test(value.value)) parts.push(`${key}=${value.value}`);
|
|
27340
27350
|
}
|
|
27341
27351
|
if (error !== void 0) parts.push(`error_class=${errorClass2(error)}`);
|
|
27342
27352
|
return parts.join(" ");
|
|
@@ -28278,11 +28288,26 @@ function clampContent(content, max = MAX_MESSAGE_CHARS) {
|
|
|
28278
28288
|
const marker = "\u2026";
|
|
28279
28289
|
return content.slice(0, Math.max(0, max - marker.length)) + marker;
|
|
28280
28290
|
}
|
|
28291
|
+
var FAST_DEFER_RULE = `Whenever you cannot answer completely from what is shown here, never say you can't, that you lack a tool, access or real-time data: reply with one short sentence like "Let me check that." and end with the exact token [[DEFER]] so the full agent answers next.`;
|
|
28292
|
+
var REFUSAL_PATTERNS = [
|
|
28293
|
+
// "I can't / cannot / am unable to / am not able to <check|access|...>", optional adverb.
|
|
28294
|
+
/\b(?:can't|cannot|can\s+not|unable\s+to|not\s+able\s+to)\s+(?:\w+ly\s+)?(?:check|access|look|see|browse|search|get|provide|give\s+you|find|retrieve|fetch|pull|know|help\s+with\s+(?:that|this)|do\s+that)\b/,
|
|
28295
|
+
// "I don't / do not / doesn't have (a|any|the)? <tool|access|ability|real-time|live ...>".
|
|
28296
|
+
/\b(?:don't|do\s+not|doesn't|does\s+not|lack)\s+(?:have\s+)?(?:a\s+|any\s+|the\s+)?(?:tool|tools|access|ability|capability|real[-\s]?time|live|current|internet|web|way\s+to)\b/,
|
|
28297
|
+
/\bno\s+(?:access|tools?|internet|web\s+access|real[-\s]?time|live\s+data|way\s+to\s+(?:check|access|know|see))\b/,
|
|
28298
|
+
/\bas\s+an\s+(?:ai|a\.i\.|language\s+model)\b/,
|
|
28299
|
+
/\bbeyond\s+my\s+(?:capabilities|abilities|capability)\b/,
|
|
28300
|
+
/\b(?:knowledge\s+cutoff|training\s+data)\b/
|
|
28301
|
+
];
|
|
28302
|
+
function isRefusal(reply) {
|
|
28303
|
+
const t = reply.toLowerCase().replace(/[‘’]/g, "'");
|
|
28304
|
+
return REFUSAL_PATTERNS.some((re) => re.test(t));
|
|
28305
|
+
}
|
|
28281
28306
|
function buildFastMessages(args2) {
|
|
28282
28307
|
const cap = args2.charCap ?? FAST_RECENT_CHARS_CAP;
|
|
28283
28308
|
const sp = systemPrompt(args2.persona, args2.settings, args2.agentName);
|
|
28284
28309
|
const head = sp.slice(0, sp.length - CONTINUITY_CLAUSE.length).trimEnd();
|
|
28285
|
-
const system = `${head} ${args2.guidance.trim()} ${CONTINUITY_CLAUSE}`;
|
|
28310
|
+
const system = `${head} ${args2.guidance.trim()} ${FAST_DEFER_RULE} ${CONTINUITY_CLAUSE}`;
|
|
28286
28311
|
const recent = args2.memory.recent.map((t) => ({
|
|
28287
28312
|
role: t.role === "agent" ? "assistant" : "user",
|
|
28288
28313
|
content: t.text
|
|
@@ -29542,6 +29567,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
29542
29567
|
fastPathDeferred = 0;
|
|
29543
29568
|
/** Of those, `full`-mode turns the deterministic pre-check deferred without calling the fast model. */
|
|
29544
29569
|
fastPathIntentDeferred = 0;
|
|
29570
|
+
/** Of those, fast-model replies that voiced inability ("I can't check...") — never spoken; the ack is spoken instead. */
|
|
29571
|
+
fastPathRefusalDeferred = 0;
|
|
29545
29572
|
/**
|
|
29546
29573
|
* Task 8: count of live AUTHENTICATED /v1/ws sessions, so a later `status`
|
|
29547
29574
|
* verb can tell "bridge process up" (the app's green dot, which only
|
|
@@ -31819,11 +31846,15 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
31819
31846
|
});
|
|
31820
31847
|
const maxSentences = this.voiceSettings?.brevity === "one" ? 1 : 2;
|
|
31821
31848
|
const raw = await this.latency.time("voice.fast_llm", () => fastLlm.complete(messages));
|
|
31822
|
-
const
|
|
31823
|
-
if (!reply) return "failed";
|
|
31849
|
+
const split3 = splitFastReply(raw, 200, maxSentences);
|
|
31850
|
+
if (!split3.reply) return "failed";
|
|
31851
|
+
const refusal = !isGreeting && isRefusal(split3.reply);
|
|
31852
|
+
const defer = split3.defer || refusal;
|
|
31853
|
+
const reply = refusal ? deferAck(m.messageId) : split3.reply;
|
|
31824
31854
|
await this.sendSpoken(m.channelId, reply, fastReplyId(this.replyIdKey, this.cfg.identityId, m.channelId, m.messageId));
|
|
31825
31855
|
this.fastPathSent++;
|
|
31826
31856
|
if (defer) this.fastPathDeferred++;
|
|
31857
|
+
if (refusal) this.fastPathRefusalDeferred++;
|
|
31827
31858
|
return defer ? "deferred" : "answered";
|
|
31828
31859
|
} catch (e) {
|
|
31829
31860
|
this.fastPathFailed++;
|
|
@@ -32192,6 +32223,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
32192
32223
|
if (ownerDirect || ownerRelayed) tool_grants = OWNER_DM_DEFAULT_READ_GRANTS;
|
|
32193
32224
|
}
|
|
32194
32225
|
}
|
|
32226
|
+
this.logAuthzDecision(channelId, senderId, relaySpeakerId, audience, tool_grants.length);
|
|
32195
32227
|
return Object.freeze({
|
|
32196
32228
|
version: 1,
|
|
32197
32229
|
audience,
|
|
@@ -32199,6 +32231,54 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
32199
32231
|
tool_grants
|
|
32200
32232
|
});
|
|
32201
32233
|
}
|
|
32234
|
+
/**
|
|
32235
|
+
* One content-free INFO line per authorization decision (every call site of
|
|
32236
|
+
* authorizationFor stages exactly one runtime turn). It says WHY a turn got
|
|
32237
|
+
* the grants it got, without any identifier, name or text: every field is a
|
|
32238
|
+
* boolean, a count, or a closed-set label.
|
|
32239
|
+
*
|
|
32240
|
+
* owner_pin pinned | anomalous | missing (channelOwner cache)
|
|
32241
|
+
* owner_row_is_agent_owner the pinned owner's roster row; `absent` when
|
|
32242
|
+
* there is no pin or the row is not in the roster
|
|
32243
|
+
* sender_is_owner sender identity == pinned owner identity
|
|
32244
|
+
* relay sender's roster row has is_relay
|
|
32245
|
+
* relay_speaker_is_owner `na` unless a relay speaker was passed
|
|
32246
|
+
* kill_switch SECURECOMS_OWNER_DM_DEFAULT_GRANTS as read at
|
|
32247
|
+
* startup: `on` = default grants enabled
|
|
32248
|
+
* meta_fresh / roster_fresh whether the channel meta / roster caches were
|
|
32249
|
+
* fresh (either stale forces audience=unknown)
|
|
32250
|
+
*/
|
|
32251
|
+
logAuthzDecision(channelId, senderId, relaySpeakerId, audience, grants) {
|
|
32252
|
+
const meta = this.freshMeta(channelId);
|
|
32253
|
+
const roster = this.freshRoster(channelId);
|
|
32254
|
+
const sender = roster?.get(senderId);
|
|
32255
|
+
const pinned = this.channelOwner.get(channelId);
|
|
32256
|
+
const ownerId = pinned && pinned !== "anomalous" ? pinned.identityId : void 0;
|
|
32257
|
+
const ownerRow = ownerId !== void 0 ? roster?.get(ownerId) : void 0;
|
|
32258
|
+
const ownerPin = pinned === void 0 ? "missing" : pinned === "anomalous" ? "anomalous" : "pinned";
|
|
32259
|
+
const ownerRowIsAgentOwner = ownerRow === void 0 ? "absent" : ownerRow.is_agent_owner ? "true" : "false";
|
|
32260
|
+
const speakerIsOwner = relaySpeakerId === void 0 ? "na" : ownerId !== void 0 && relaySpeakerId === ownerId ? "true" : "false";
|
|
32261
|
+
writeServiceLog2("info", "authz.decision", {
|
|
32262
|
+
audience: ServiceLogEnum.of(audience, [
|
|
32263
|
+
"owner_dm",
|
|
32264
|
+
"other_human_dm",
|
|
32265
|
+
"team_room",
|
|
32266
|
+
"partner_room",
|
|
32267
|
+
"agent_to_agent",
|
|
32268
|
+
"unknown"
|
|
32269
|
+
]),
|
|
32270
|
+
grants,
|
|
32271
|
+
owner_pin: ServiceLogEnum.of(ownerPin, ["pinned", "anomalous", "missing"]),
|
|
32272
|
+
owner_row_is_agent_owner: ServiceLogEnum.of(ownerRowIsAgentOwner, ["true", "false", "absent"]),
|
|
32273
|
+
sender_is_owner: ownerId !== void 0 && senderId === ownerId,
|
|
32274
|
+
relay: sender?.is_relay === true,
|
|
32275
|
+
relay_speaker_is_owner: ServiceLogEnum.of(speakerIsOwner, ["true", "false", "na"]),
|
|
32276
|
+
is_dm: meta?.is_dm === true,
|
|
32277
|
+
kill_switch: ServiceLogEnum.of(this.ownerDmDefaultGrants ? "on" : "off", ["on", "off"]),
|
|
32278
|
+
meta_fresh: meta !== void 0,
|
|
32279
|
+
roster_fresh: roster !== void 0
|
|
32280
|
+
});
|
|
32281
|
+
}
|
|
32202
32282
|
/**
|
|
32203
32283
|
* Task 8 (owner-attested Reset Secure Session): STRUCTURAL owner selection —
|
|
32204
32284
|
* NEVER read the server's `role` field to pick the owner. In a DM the owner
|
|
@@ -33726,6 +33806,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
33726
33806
|
fast_path_skipped: this.fastPathSkipped,
|
|
33727
33807
|
fast_path_deferred: this.fastPathDeferred,
|
|
33728
33808
|
fast_path_intent_deferred: this.fastPathIntentDeferred,
|
|
33809
|
+
fast_path_refusal_deferred: this.fastPathRefusalDeferred,
|
|
33729
33810
|
fast_path_mode: this.cfg.voiceFastPathMode ?? "full",
|
|
33730
33811
|
voice_settings_version: this.voiceSettingsVersion,
|
|
33731
33812
|
voice_settings_source: this.voiceSettingsSource,
|
package/dist/service-log.d.ts
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
export type ServiceLogLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* A closed-set label for a service-log field. The only way to build one is
|
|
4
|
+
* `ServiceLogEnum.of(value, allowed)`: a value outside `allowed`, or one that
|
|
5
|
+
* is not a short lowercase word, renders as `invalid`. Raw strings are still
|
|
6
|
+
* dropped by `formatServiceLog`, so an identifier, name or message text can
|
|
7
|
+
* never reach the log through a field. The privacy test requires `allowed` to
|
|
8
|
+
* be an inline array of string literals at every call site.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ServiceLogEnum {
|
|
11
|
+
readonly value: string;
|
|
12
|
+
private constructor();
|
|
13
|
+
static of<T extends string>(value: T, allowed: readonly T[]): ServiceLogEnum;
|
|
14
|
+
}
|
|
15
|
+
export type ServiceLogFields = Readonly<Record<string, number | boolean | ServiceLogEnum>>;
|
|
3
16
|
export type ServiceLog = (level: ServiceLogLevel, event: string, fields?: ServiceLogFields, error?: unknown) => void;
|
|
4
17
|
/** Reduce a thrown value without reading its message, stack, or string form. */
|
|
5
18
|
export declare function errorClass(error: unknown): string;
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const BRIDGE_VERSION = "0.26.
|
|
1
|
+
export declare const BRIDGE_VERSION = "0.26.4";
|
|
@@ -29,6 +29,15 @@ export declare const MAX_MESSAGE_CHARS = 8000;
|
|
|
29
29
|
export declare const FAST_PATH_MEMORY_TURNS = 10;
|
|
30
30
|
export declare const FAST_RECENT_CHARS_CAP = 1500;
|
|
31
31
|
export declare const FAST_FACTS_CHARS_CAP = 2000;
|
|
32
|
+
/**
|
|
33
|
+
* Live bug 2026-10-02 (bridge 0.26.3): asked for Sydney's weather, the fast
|
|
34
|
+
* model said "I can't check the weather right now since I don't have a
|
|
35
|
+
* tool..." with no [[DEFER]], so the runtime (which has web_search) never got
|
|
36
|
+
* the turn. Placed last before the continuity clause so it is the final rule
|
|
37
|
+
* the model reads. Compiled in, never interpolated with peer data.
|
|
38
|
+
*/
|
|
39
|
+
export declare const FAST_DEFER_RULE: string;
|
|
40
|
+
export declare function isRefusal(reply: string): boolean;
|
|
32
41
|
export declare function buildFastMessages(args: {
|
|
33
42
|
persona: VoicePersona;
|
|
34
43
|
guidance: string;
|