@voxagent/bridge 0.3.5 → 0.4.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.
- package/README.md +27 -0
- package/dist/cli.js +22 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +26 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +46 -1
- package/dist/config.js.map +1 -1
- package/dist/drivers/claude.d.ts.map +1 -1
- package/dist/drivers/claude.js +6 -1
- package/dist/drivers/claude.js.map +1 -1
- package/dist/drivers/codex.d.ts.map +1 -1
- package/dist/drivers/codex.js +30 -1
- package/dist/drivers/codex.js.map +1 -1
- package/dist/drivers/types.d.ts +9 -0
- package/dist/drivers/types.d.ts.map +1 -1
- package/dist/protocol.d.ts +103 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/room.d.ts +20 -0
- package/dist/room.d.ts.map +1 -1
- package/dist/room.js +220 -8
- package/dist/room.js.map +1 -1
- package/dist/trigger/addressing.d.ts +40 -0
- package/dist/trigger/addressing.d.ts.map +1 -0
- package/dist/trigger/addressing.js +90 -0
- package/dist/trigger/addressing.js.map +1 -0
- package/dist/trigger/decide.d.ts +33 -0
- package/dist/trigger/decide.d.ts.map +1 -0
- package/dist/trigger/decide.js +133 -0
- package/dist/trigger/decide.js.map +1 -0
- package/dist/trigger/effort.d.ts +26 -0
- package/dist/trigger/effort.d.ts.map +1 -0
- package/dist/trigger/effort.js +66 -0
- package/dist/trigger/effort.js.map +1 -0
- package/dist/trigger/engine.d.ts +120 -0
- package/dist/trigger/engine.d.ts.map +1 -0
- package/dist/trigger/engine.js +515 -0
- package/dist/trigger/engine.js.map +1 -0
- package/dist/trigger/jev.d.ts +52 -0
- package/dist/trigger/jev.d.ts.map +1 -0
- package/dist/trigger/jev.js +204 -0
- package/dist/trigger/jev.js.map +1 -0
- package/dist/trigger/questions.d.ts +43 -0
- package/dist/trigger/questions.d.ts.map +1 -0
- package/dist/trigger/questions.js +243 -0
- package/dist/trigger/questions.js.map +1 -0
- package/dist/trigger/types.d.ts +175 -0
- package/dist/trigger/types.d.ts.map +1 -0
- package/dist/trigger/types.js +29 -0
- package/dist/trigger/types.js.map +1 -0
- package/package.json +11 -10
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Deterministic addressing rules.
|
|
3
|
+
*
|
|
4
|
+
* Two jobs:
|
|
5
|
+
* 1. Hard rules that must never be probabilistic — a spoken "stop", and a
|
|
6
|
+
* "yes" to a pending nudge. These run BEFORE the classifier.
|
|
7
|
+
* 2. The wake-phrase fallback used when jev is unavailable (no key, timeout,
|
|
8
|
+
* outage). Ported from `apps/web/lib/bridge/addressing.ts` with the bare
|
|
9
|
+
* "agent" patterns dropped: "agent" is a common noun for our users, which
|
|
10
|
+
* is the whole reason the classifier exists. "Vox" / "VoxAgent" stay as
|
|
11
|
+
* the guaranteed summon.
|
|
12
|
+
*/
|
|
13
|
+
export declare function mentionsAgentName(text: string): boolean;
|
|
14
|
+
export declare function hasDirectAgentAddress(text: string): boolean;
|
|
15
|
+
export declare function isDisengagePhrase(text: string): boolean;
|
|
16
|
+
export declare function isHardStop(text: string): boolean;
|
|
17
|
+
export declare function isAffirmative(text: string): boolean;
|
|
18
|
+
export declare function isNegative(text: string): boolean;
|
|
19
|
+
export interface AddressingState {
|
|
20
|
+
/** Speaker who last addressed the agent. */
|
|
21
|
+
engagedSpeakerId: string | null;
|
|
22
|
+
/** Wall-clock when the agent's last run ended. */
|
|
23
|
+
lastAgentRunEndMs: number | null;
|
|
24
|
+
}
|
|
25
|
+
export type AddressingReason = 'wake' | 'follow_up' | 'ignore' | 'disengage';
|
|
26
|
+
export interface AddressingDecision {
|
|
27
|
+
addressed: boolean;
|
|
28
|
+
reason: AddressingReason;
|
|
29
|
+
nextState: AddressingState;
|
|
30
|
+
}
|
|
31
|
+
export declare function createInitialAddressingState(): AddressingState;
|
|
32
|
+
export declare function noteAgentRunEnded(state: AddressingState, nowMs: number): AddressingState;
|
|
33
|
+
/** Fallback-only. With jev available, follow-ups are a judgment, not a timer. */
|
|
34
|
+
export declare function evaluateAddressing(options: {
|
|
35
|
+
text: string;
|
|
36
|
+
speakerId: string;
|
|
37
|
+
state: AddressingState;
|
|
38
|
+
nowMs: number;
|
|
39
|
+
}): AddressingDecision;
|
|
40
|
+
//# sourceMappingURL=addressing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addressing.d.ts","sourceRoot":"","sources":["../../src/trigger/addressing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA4CH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,MAAM,WAAW,eAAe;IAC9B,4CAA4C;IAC5C,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,kDAAkD;IAClD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE7E,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,eAAe,CAAC;CAC5B;AAED,wBAAgB,4BAA4B,IAAI,eAAe,CAE9D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,CAGxF;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,kBAAkB,CA8BrB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Deterministic addressing rules.
|
|
3
|
+
*
|
|
4
|
+
* Two jobs:
|
|
5
|
+
* 1. Hard rules that must never be probabilistic — a spoken "stop", and a
|
|
6
|
+
* "yes" to a pending nudge. These run BEFORE the classifier.
|
|
7
|
+
* 2. The wake-phrase fallback used when jev is unavailable (no key, timeout,
|
|
8
|
+
* outage). Ported from `apps/web/lib/bridge/addressing.ts` with the bare
|
|
9
|
+
* "agent" patterns dropped: "agent" is a common noun for our users, which
|
|
10
|
+
* is the whole reason the classifier exists. "Vox" / "VoxAgent" stay as
|
|
11
|
+
* the guaranteed summon.
|
|
12
|
+
*/
|
|
13
|
+
const NAME = '(?:vox\\s?agent|vox|voice\\s+agent)';
|
|
14
|
+
const DIRECT_ADDRESS_PATTERNS = [
|
|
15
|
+
new RegExp(`\\b(?:hey|hi|hello|ok|okay)\\s+${NAME}\\b`, 'i'),
|
|
16
|
+
new RegExp(`^\\s*${NAME}\\s*[,!?.]`, 'i'),
|
|
17
|
+
new RegExp(`^\\s*${NAME}\\s+(?:can|could|would|will|please|what|when|where|who|why|how|is|are|do|does)\\b`, 'i'),
|
|
18
|
+
new RegExp(`\\b(?:can|could|would|please)\\s+(?:you\\s+)?${NAME}\\b`, 'i'),
|
|
19
|
+
new RegExp(`\\bask\\s+${NAME}\\s+to\\b`, 'i'),
|
|
20
|
+
new RegExp(`[,]\\s*${NAME}\\s*[.!?]?\\s*$`, 'i'),
|
|
21
|
+
];
|
|
22
|
+
const NAME_MENTION = new RegExp(`\\b${NAME}\\b`, 'i');
|
|
23
|
+
const DISENGAGE_PATTERNS = [
|
|
24
|
+
/^\s*(thanks|thank you)\b/i,
|
|
25
|
+
/\b(that'?s|that is)\s+all\b/i,
|
|
26
|
+
/\b(nevermind|never mind)\b/i,
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* The WHOLE utterance is a stop command, optionally with the agent's name.
|
|
30
|
+
* Deliberately anchored at both ends: "we should stop by the office" must not
|
|
31
|
+
* cancel a ten-minute run.
|
|
32
|
+
*/
|
|
33
|
+
const STOP_CORE = "(?:stop|stop it|stop that|stop talking|cancel|cancel that|cancel it|abort|shut up|be quiet|quiet|enough|that'?s enough|never ?mind)";
|
|
34
|
+
const HARD_STOP = new RegExp(`^\\s*(?:(?:hey|ok|okay)\\s+)?(?:${NAME}\\s*[,.!]?\\s*)?(?:please\\s+)?${STOP_CORE}(?:\\s+please)?(?:\\s*[,.!]?\\s*${NAME})?\\s*[.!]*\\s*$`, 'i');
|
|
35
|
+
const AFFIRMATIVE = /^\s*(?:yes|yeah|yep|yup|sure|please|please do|go ahead|go for it|do it|that was for you|yes please|ok|okay)\b[\s,.!]*(?:vox\s?agent|vox)?[\s,.!]*(?:please|go ahead|do it)?[\s.!]*$/i;
|
|
36
|
+
const NEGATIVE = /^\s*(?:no|nope|nah|not you|no thanks|not now|never ?mind)\b[\s,.!]*(?:vox\s?agent|vox)?[\s.!]*$/i;
|
|
37
|
+
const FOLLOW_UP_WINDOW_MS = 15_000;
|
|
38
|
+
export function mentionsAgentName(text) {
|
|
39
|
+
return NAME_MENTION.test(text);
|
|
40
|
+
}
|
|
41
|
+
export function hasDirectAgentAddress(text) {
|
|
42
|
+
return DIRECT_ADDRESS_PATTERNS.some((pattern) => pattern.test(text));
|
|
43
|
+
}
|
|
44
|
+
export function isDisengagePhrase(text) {
|
|
45
|
+
return DISENGAGE_PATTERNS.some((pattern) => pattern.test(text));
|
|
46
|
+
}
|
|
47
|
+
export function isHardStop(text) {
|
|
48
|
+
return HARD_STOP.test(text);
|
|
49
|
+
}
|
|
50
|
+
export function isAffirmative(text) {
|
|
51
|
+
return AFFIRMATIVE.test(text);
|
|
52
|
+
}
|
|
53
|
+
export function isNegative(text) {
|
|
54
|
+
return NEGATIVE.test(text);
|
|
55
|
+
}
|
|
56
|
+
export function createInitialAddressingState() {
|
|
57
|
+
return { engagedSpeakerId: null, lastAgentRunEndMs: null };
|
|
58
|
+
}
|
|
59
|
+
export function noteAgentRunEnded(state, nowMs) {
|
|
60
|
+
if (!state.engagedSpeakerId)
|
|
61
|
+
return state;
|
|
62
|
+
return { ...state, lastAgentRunEndMs: nowMs };
|
|
63
|
+
}
|
|
64
|
+
/** Fallback-only. With jev available, follow-ups are a judgment, not a timer. */
|
|
65
|
+
export function evaluateAddressing(options) {
|
|
66
|
+
const text = options.text.trim();
|
|
67
|
+
if (text.length === 0) {
|
|
68
|
+
return { addressed: false, reason: 'ignore', nextState: options.state };
|
|
69
|
+
}
|
|
70
|
+
if (isDisengagePhrase(text)) {
|
|
71
|
+
return { addressed: false, reason: 'disengage', nextState: createInitialAddressingState() };
|
|
72
|
+
}
|
|
73
|
+
if (hasDirectAgentAddress(text)) {
|
|
74
|
+
return {
|
|
75
|
+
addressed: true,
|
|
76
|
+
reason: 'wake',
|
|
77
|
+
nextState: {
|
|
78
|
+
engagedSpeakerId: options.speakerId,
|
|
79
|
+
lastAgentRunEndMs: options.state.lastAgentRunEndMs,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (options.state.engagedSpeakerId === options.speakerId &&
|
|
84
|
+
options.state.lastAgentRunEndMs !== null &&
|
|
85
|
+
options.nowMs - options.state.lastAgentRunEndMs <= FOLLOW_UP_WINDOW_MS) {
|
|
86
|
+
return { addressed: true, reason: 'follow_up', nextState: options.state };
|
|
87
|
+
}
|
|
88
|
+
return { addressed: false, reason: 'ignore', nextState: options.state };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=addressing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addressing.js","sourceRoot":"","sources":["../../src/trigger/addressing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Pure mapping from jev answers + run state → what the agent does.
|
|
3
|
+
*
|
|
4
|
+
* No I/O, no timers, no clocks. jev gives calibrated probabilities; every
|
|
5
|
+
* product judgment about what those probabilities MEAN lives here, where it
|
|
6
|
+
* can be table-tested (`decide.test.ts`) and retuned without touching the
|
|
7
|
+
* question wording.
|
|
8
|
+
*
|
|
9
|
+
* Asymmetry to keep in mind when changing thresholds: a false wake makes the
|
|
10
|
+
* agent talk over people and may run tools on the host's machine; a false
|
|
11
|
+
* ignore costs the speaker one "Vox, ..." retry. So waking is strict, and
|
|
12
|
+
* stopping is lenient.
|
|
13
|
+
*/
|
|
14
|
+
import type { RunSnapshot, TriggerAction, TriggerAnswers, TriggerSettings } from './types.js';
|
|
15
|
+
export interface DecideInput {
|
|
16
|
+
answers: TriggerAnswers;
|
|
17
|
+
run: RunSnapshot;
|
|
18
|
+
/** The utterance contains "Vox" / "VoxAgent". Evidence, not a gate. */
|
|
19
|
+
nameMentioned: boolean;
|
|
20
|
+
humanCount: number;
|
|
21
|
+
/** The hold for this burst already timed out — don't hold again. */
|
|
22
|
+
forceComplete: boolean;
|
|
23
|
+
/** False while a nudge is pending or cooling down. */
|
|
24
|
+
canNudge: boolean;
|
|
25
|
+
settings: TriggerSettings;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* `probabilities.agent` needed before the agent acts. A name or a solo room
|
|
29
|
+
* is strong prior evidence, so they lower the bar; risk raises it.
|
|
30
|
+
*/
|
|
31
|
+
export declare function wakeThresholdFor(input: DecideInput): number;
|
|
32
|
+
export declare function decide(input: DecideInput): TriggerAction;
|
|
33
|
+
//# sourceMappingURL=decide.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decide.d.ts","sourceRoot":"","sources":["../../src/trigger/decide.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAkB,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9G,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAC;IACxB,GAAG,EAAE,WAAW,CAAC;IACjB,uEAAuE;IACvE,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,aAAa,EAAE,OAAO,CAAC;IACvB,sDAAsD;IACtD,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;CAC3B;AASD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAQ3D;AAgED,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAoDxD"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Pure mapping from jev answers + run state → what the agent does.
|
|
3
|
+
*
|
|
4
|
+
* No I/O, no timers, no clocks. jev gives calibrated probabilities; every
|
|
5
|
+
* product judgment about what those probabilities MEAN lives here, where it
|
|
6
|
+
* can be table-tested (`decide.test.ts`) and retuned without touching the
|
|
7
|
+
* question wording.
|
|
8
|
+
*
|
|
9
|
+
* Asymmetry to keep in mind when changing thresholds: a false wake makes the
|
|
10
|
+
* agent talk over people and may run tools on the host's machine; a false
|
|
11
|
+
* ignore costs the speaker one "Vox, ..." retry. So waking is strict, and
|
|
12
|
+
* stopping is lenient.
|
|
13
|
+
*/
|
|
14
|
+
const MIN_HOLD_MS = 600;
|
|
15
|
+
const MAX_WAKE_THRESHOLD = 0.95;
|
|
16
|
+
function fmt(value) {
|
|
17
|
+
return value.toFixed(2);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* `probabilities.agent` needed before the agent acts. A name or a solo room
|
|
21
|
+
* is strong prior evidence, so they lower the bar; risk raises it.
|
|
22
|
+
*/
|
|
23
|
+
export function wakeThresholdFor(input) {
|
|
24
|
+
const { settings, answers } = input;
|
|
25
|
+
let threshold = settings.wakeThreshold;
|
|
26
|
+
if (input.nameMentioned)
|
|
27
|
+
threshold = settings.wakeThresholdWithName;
|
|
28
|
+
else if (input.humanCount <= 1)
|
|
29
|
+
threshold = settings.wakeThresholdSolo;
|
|
30
|
+
const riskLevel = Math.round(answers.risk.score);
|
|
31
|
+
threshold += settings.riskBump * Math.max(0, riskLevel);
|
|
32
|
+
return Math.min(MAX_WAKE_THRESHOLD, threshold);
|
|
33
|
+
}
|
|
34
|
+
function holdMs(thoughtComplete, settings) {
|
|
35
|
+
// Less sure the thought is finished → wait longer.
|
|
36
|
+
const openness = 1 - Math.min(1, thoughtComplete / settings.holdBelow);
|
|
37
|
+
return Math.round(Math.max(MIN_HOLD_MS, settings.maxHoldMs * openness));
|
|
38
|
+
}
|
|
39
|
+
function decideWhileBusy(input, pAgent, wake) {
|
|
40
|
+
const { answers, run, settings } = input;
|
|
41
|
+
const relation = answers.relation;
|
|
42
|
+
if (!relation) {
|
|
43
|
+
// Relation wasn't asked (run started between classify and decide).
|
|
44
|
+
return pAgent >= wake
|
|
45
|
+
? { kind: 'supersede', reason: `addressee agent ${fmt(pAgent)}, no relation answer` }
|
|
46
|
+
: { kind: 'ignore', reason: `busy, addressee agent ${fmt(pAgent)} < ${fmt(wake)}` };
|
|
47
|
+
}
|
|
48
|
+
const p = relation.probabilities;
|
|
49
|
+
// Stopping is lenient: it's cheap to undo and the alternative is the agent
|
|
50
|
+
// talking over someone. It needs far less "this was for the agent" evidence
|
|
51
|
+
// than starting work does.
|
|
52
|
+
const stopFloor = Math.min(wake, settings.stopAddressFloor);
|
|
53
|
+
if (pAgent >= stopFloor) {
|
|
54
|
+
if (p.abort_work >= settings.abortThreshold) {
|
|
55
|
+
return { kind: 'abort', reason: `abort_work ${fmt(p.abort_work)}` };
|
|
56
|
+
}
|
|
57
|
+
const stopMass = p.stop_talking + p.abort_work;
|
|
58
|
+
if (run.status === 'speaking' && stopMass >= settings.stopTalkingThreshold) {
|
|
59
|
+
return { kind: 'stop_talking', reason: `stop_talking+abort ${fmt(stopMass)} while speaking` };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (pAgent < wake) {
|
|
63
|
+
return { kind: 'ignore', reason: `busy, addressee agent ${fmt(pAgent)} < ${fmt(wake)}` };
|
|
64
|
+
}
|
|
65
|
+
if (answers.hypotheticalOrQuoted >= settings.hypotheticalVeto && !input.nameMentioned) {
|
|
66
|
+
return { kind: 'ignore', reason: `hypothetical ${fmt(answers.hypotheticalOrQuoted)}` };
|
|
67
|
+
}
|
|
68
|
+
// Pick among the three "new input for the agent" relations only; the stop
|
|
69
|
+
// and backchannel mass was handled above / means "nothing to do".
|
|
70
|
+
const work = [
|
|
71
|
+
['append', p.adds_detail],
|
|
72
|
+
['supersede', p.redirects],
|
|
73
|
+
['queue', p.unrelated],
|
|
74
|
+
];
|
|
75
|
+
const workMass = work.reduce((sum, [, value]) => sum + value, 0);
|
|
76
|
+
if (p.backchannel >= workMass) {
|
|
77
|
+
return { kind: 'ignore', reason: `backchannel ${fmt(p.backchannel)}` };
|
|
78
|
+
}
|
|
79
|
+
if (!input.forceComplete && answers.thoughtComplete < settings.holdBelow) {
|
|
80
|
+
return {
|
|
81
|
+
kind: 'hold',
|
|
82
|
+
waitMs: holdMs(answers.thoughtComplete, settings),
|
|
83
|
+
reason: `thought_complete ${fmt(answers.thoughtComplete)}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const [kind, value] = work.reduce((best, next) => (next[1] > best[1] ? next : best));
|
|
87
|
+
return { kind, reason: `addressee agent ${fmt(pAgent)}, relation ${kind} ${fmt(value)}` };
|
|
88
|
+
}
|
|
89
|
+
export function decide(input) {
|
|
90
|
+
const { answers, settings } = input;
|
|
91
|
+
const pAgent = answers.addressee.probabilities.agent ?? 0;
|
|
92
|
+
const wake = wakeThresholdFor(input);
|
|
93
|
+
if (input.run.status !== 'idle')
|
|
94
|
+
return decideWhileBusy(input, pAgent, wake);
|
|
95
|
+
const vetoed = answers.hypotheticalOrQuoted >= settings.hypotheticalVeto && !input.nameMentioned;
|
|
96
|
+
if (pAgent >= wake && !vetoed) {
|
|
97
|
+
if (!input.forceComplete && answers.thoughtComplete < settings.holdBelow) {
|
|
98
|
+
return {
|
|
99
|
+
kind: 'hold',
|
|
100
|
+
waitMs: holdMs(answers.thoughtComplete, settings),
|
|
101
|
+
reason: `addressee agent ${fmt(pAgent)}, thought_complete ${fmt(answers.thoughtComplete)}`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return { kind: 'run', reason: `addressee agent ${fmt(pAgent)} >= ${fmt(wake)}` };
|
|
105
|
+
}
|
|
106
|
+
if (vetoed) {
|
|
107
|
+
return { kind: 'ignore', reason: `hypothetical ${fmt(answers.hypotheticalOrQuoted)}` };
|
|
108
|
+
}
|
|
109
|
+
// Nothing below speaks. A nudge is a pill on the agent tile.
|
|
110
|
+
const thoughtDone = input.forceComplete || answers.thoughtComplete >= settings.holdBelow;
|
|
111
|
+
if (input.canNudge && thoughtDone) {
|
|
112
|
+
if (pAgent >= settings.nudgeFloor) {
|
|
113
|
+
return {
|
|
114
|
+
kind: 'nudge',
|
|
115
|
+
nudgeKind: 'unsure',
|
|
116
|
+
reason: `addressee agent ${fmt(pAgent)} in [${fmt(settings.nudgeFloor)}, ${fmt(wake)})`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const openFloor = answers.addressee.choice === 'group' || answers.addressee.choice === 'nobody';
|
|
120
|
+
if (settings.proactive === 'nudge' &&
|
|
121
|
+
openFloor &&
|
|
122
|
+
answers.isRequest >= settings.proactiveThreshold &&
|
|
123
|
+
answers.agentCapable >= settings.proactiveThreshold) {
|
|
124
|
+
return {
|
|
125
|
+
kind: 'nudge',
|
|
126
|
+
nudgeKind: 'offer',
|
|
127
|
+
reason: `open request ${fmt(answers.isRequest)}, agent_capable ${fmt(answers.agentCapable)}`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { kind: 'ignore', reason: `addressee agent ${fmt(pAgent)} < ${fmt(wake)}` };
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=decide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decide.js","sourceRoot":"","sources":["../../src/trigger/decide.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAiBH,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC,SAAS,GAAG,CAAC,KAAa;IACxB,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAkB;IACjD,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IACpC,IAAI,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC;IACvC,IAAI,KAAK,CAAC,aAAa;QAAE,SAAS,GAAG,QAAQ,CAAC,qBAAqB,CAAC;SAC/D,IAAI,KAAK,CAAC,UAAU,IAAI,CAAC;QAAE,SAAS,GAAG,QAAQ,CAAC,iBAAiB,CAAC;IACvE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjD,SAAS,IAAI,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IACxD,OAAO,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,MAAM,CAAC,eAAuB,EAAE,QAAyB;IAChE,mDAAmD;IACnD,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IACvE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAC,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,eAAe,CAAC,KAAkB,EAAE,MAAc,EAAE,IAAY;IACvE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACzC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,mEAAmE;QACnE,OAAO,MAAM,IAAI,IAAI;YACnB,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,mBAAmB,GAAG,CAAC,MAAM,CAAC,sBAAsB,EAAE;YACrF,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,yBAAyB,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IACxF,CAAC;IACD,MAAM,CAAC,GAAG,QAAQ,CAAC,aAA+C,CAAC;IAEnE,2EAA2E;IAC3E,4EAA4E;IAC5E,2BAA2B;IAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC5D,IAAI,MAAM,IAAI,SAAS,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,UAAU,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;YAC5C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;QACtE,CAAC;QACD,MAAM,QAAQ,GAAG,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,UAAU,CAAC;QAC/C,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,IAAI,QAAQ,IAAI,QAAQ,CAAC,oBAAoB,EAAE,CAAC;YAC3E,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,sBAAsB,GAAG,CAAC,QAAQ,CAAC,iBAAiB,EAAE,CAAC;QAChG,CAAC;IACH,CAAC;IAED,IAAI,MAAM,GAAG,IAAI,EAAE,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,yBAAyB,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IAC3F,CAAC;IACD,IAAI,OAAO,CAAC,oBAAoB,IAAI,QAAQ,CAAC,gBAAgB,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;QACtF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,GAAG,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,EAAE,CAAC;IACzF,CAAC;IAED,0EAA0E;IAC1E,kEAAkE;IAClE,MAAM,IAAI,GAAsF;QAC9F,CAAC,QAAQ,EAAE,CAAC,CAAC,WAAW,CAAC;QACzB,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC;QAC1B,CAAC,OAAO,EAAE,CAAC,CAAC,SAAS,CAAC;KACvB,CAAC;IACF,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;IACjE,IAAI,CAAC,CAAC,WAAW,IAAI,QAAQ,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC;IACzE,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,aAAa,IAAI,OAAO,CAAC,eAAe,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;QACzE,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,QAAQ,CAAC;YACjD,MAAM,EAAE,oBAAoB,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE;SAC3D,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrF,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,GAAG,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;AAC5F,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAkB;IACvC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACpC,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAErC,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAE7E,MAAM,MAAM,GACV,OAAO,CAAC,oBAAoB,IAAI,QAAQ,CAAC,gBAAgB,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC;IAEpF,IAAI,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,aAAa,IAAI,OAAO,CAAC,eAAe,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC;YACzE,OAAO;gBACL,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,QAAQ,CAAC;gBACjD,MAAM,EAAE,mBAAmB,GAAG,CAAC,MAAM,CAAC,sBAAsB,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE;aAC3F,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,GAAG,CAAC,MAAM,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IACnF,CAAC;IAED,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,GAAG,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,EAAE,CAAC;IACzF,CAAC;IAED,6DAA6D;IAC7D,MAAM,WAAW,GAAG,KAAK,CAAC,aAAa,IAAI,OAAO,CAAC,eAAe,IAAI,QAAQ,CAAC,SAAS,CAAC;IACzF,IAAI,KAAK,CAAC,QAAQ,IAAI,WAAW,EAAE,CAAC;QAClC,IAAI,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;YAClC,OAAO;gBACL,IAAI,EAAE,OAAO;gBACb,SAAS,EAAE,QAAQ;gBACnB,MAAM,EAAE,mBAAmB,GAAG,CAAC,MAAM,CAAC,QAAQ,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG;aACxF,CAAC;QACJ,CAAC;QACD,MAAM,SAAS,GACb,OAAO,CAAC,SAAS,CAAC,MAAM,KAAK,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,KAAK,QAAQ,CAAC;QAChF,IACE,QAAQ,CAAC,SAAS,KAAK,OAAO;YAC9B,SAAS;YACT,OAAO,CAAC,SAAS,IAAI,QAAQ,CAAC,kBAAkB;YAChD,OAAO,CAAC,YAAY,IAAI,QAAQ,CAAC,kBAAkB,EACnD,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,OAAO;gBACb,SAAS,EAAE,OAAO;gBAClB,MAAM,EAAE,gBAAgB,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,mBAAmB,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE;aAC7F,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,mBAAmB,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;AACrF,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Effort score → per-run model routing ("Auto" in the model picker).
|
|
3
|
+
*
|
|
4
|
+
* jev judges effort from the utterance alone — it can't see the repo, so
|
|
5
|
+
* "fix the login bug" is genuinely uncertain. We therefore err UPWARD: take
|
|
6
|
+
* the highest level that still carries real probability rather than the mean.
|
|
7
|
+
* An over-provisioned turn costs a little; an under-provisioned one fails in
|
|
8
|
+
* front of the room.
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentBackend } from '../protocol.js';
|
|
11
|
+
import type { RunPlan, ScoreAnswer } from './types.js';
|
|
12
|
+
export declare function pickEffortLevel(effort: ScoreAnswer): number;
|
|
13
|
+
/**
|
|
14
|
+
* Parses `VOXAGENT_AUTO_MODELS=a,b,c,d` — one model per effort level, low to
|
|
15
|
+
* high. Returns `null` for anything that isn't exactly four entries.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseAutoModels(raw: string | undefined): string[] | null;
|
|
18
|
+
export declare function planRun(options: {
|
|
19
|
+
effort: ScoreAnswer | null;
|
|
20
|
+
backend: AgentBackend;
|
|
21
|
+
/** True when the host picked "Auto" (`--llm-model auto`). */
|
|
22
|
+
autoModel: boolean;
|
|
23
|
+
modelOverrides?: string[] | null;
|
|
24
|
+
ack?: boolean;
|
|
25
|
+
}): RunPlan;
|
|
26
|
+
//# sourceMappingURL=effort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effort.d.ts","sourceRoot":"","sources":["../../src/trigger/effort.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAgCvD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAK3D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,GAAG,IAAI,CAIxE;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE;IAC/B,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,YAAY,CAAC;IACtB,6DAA6D;IAC7D,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACjC,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAiBV"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Effort score → per-run model routing ("Auto" in the model picker).
|
|
3
|
+
*
|
|
4
|
+
* jev judges effort from the utterance alone — it can't see the repo, so
|
|
5
|
+
* "fix the login bug" is genuinely uncertain. We therefore err UPWARD: take
|
|
6
|
+
* the highest level that still carries real probability rather than the mean.
|
|
7
|
+
* An over-provisioned turn costs a little; an under-provisioned one fails in
|
|
8
|
+
* front of the room.
|
|
9
|
+
*/
|
|
10
|
+
/** A level needs at least this much probability to be picked. */
|
|
11
|
+
const LEVEL_FLOOR = 0.25;
|
|
12
|
+
/** At/above this level the agent says something before the LLM's first token. */
|
|
13
|
+
const ACK_FROM_LEVEL = 2;
|
|
14
|
+
const ACK_LINE = 'On it.';
|
|
15
|
+
/**
|
|
16
|
+
* Claude rungs are CLI aliases, not dated IDs, so Auto follows whatever the
|
|
17
|
+
* host's Claude Code considers current.
|
|
18
|
+
*/
|
|
19
|
+
const CLAUDE_LADDER = [
|
|
20
|
+
{ model: 'haiku' },
|
|
21
|
+
{ model: 'sonnet' },
|
|
22
|
+
{ model: 'sonnet', maxThinkingTokens: 8_000 },
|
|
23
|
+
{ model: 'opus', maxThinkingTokens: 16_000 },
|
|
24
|
+
];
|
|
25
|
+
const CODEX_LADDER = [
|
|
26
|
+
{ model: 'gpt-5.4-mini', reasoningEffort: 'low' },
|
|
27
|
+
{ model: 'gpt-5.4', reasoningEffort: 'low' },
|
|
28
|
+
{ model: 'gpt-5.4', reasoningEffort: 'medium' },
|
|
29
|
+
{ model: 'gpt-5.4', reasoningEffort: 'high' },
|
|
30
|
+
];
|
|
31
|
+
export function pickEffortLevel(effort) {
|
|
32
|
+
for (let level = effort.probabilities.length - 1; level >= 0; level -= 1) {
|
|
33
|
+
if ((effort.probabilities[level] ?? 0) >= LEVEL_FLOOR)
|
|
34
|
+
return level;
|
|
35
|
+
}
|
|
36
|
+
return Math.round(effort.score);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parses `VOXAGENT_AUTO_MODELS=a,b,c,d` — one model per effort level, low to
|
|
40
|
+
* high. Returns `null` for anything that isn't exactly four entries.
|
|
41
|
+
*/
|
|
42
|
+
export function parseAutoModels(raw) {
|
|
43
|
+
if (!raw)
|
|
44
|
+
return null;
|
|
45
|
+
const models = raw.split(',').map((value) => value.trim()).filter(Boolean);
|
|
46
|
+
return models.length === 4 ? models : null;
|
|
47
|
+
}
|
|
48
|
+
export function planRun(options) {
|
|
49
|
+
// No classifier answer (regex fallback): assume a real task.
|
|
50
|
+
const effortLevel = options.effort ? pickEffortLevel(options.effort) : ACK_FROM_LEVEL;
|
|
51
|
+
const plan = { effortLevel };
|
|
52
|
+
if (options.autoModel) {
|
|
53
|
+
const ladder = options.backend === 'codex' ? CODEX_LADDER : CLAUDE_LADDER;
|
|
54
|
+
const rung = ladder[Math.min(effortLevel, ladder.length - 1)] ?? ladder[ladder.length - 1];
|
|
55
|
+
plan.model = options.modelOverrides?.[effortLevel] ?? rung.model;
|
|
56
|
+
if (rung.maxThinkingTokens !== undefined)
|
|
57
|
+
plan.maxThinkingTokens = rung.maxThinkingTokens;
|
|
58
|
+
if (rung.reasoningEffort !== undefined)
|
|
59
|
+
plan.reasoningEffort = rung.reasoningEffort;
|
|
60
|
+
}
|
|
61
|
+
if (options.ack !== false && options.effort && effortLevel >= ACK_FROM_LEVEL) {
|
|
62
|
+
plan.ack = ACK_LINE;
|
|
63
|
+
}
|
|
64
|
+
return plan;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=effort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effort.js","sourceRoot":"","sources":["../../src/trigger/effort.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,iEAAiE;AACjE,MAAM,WAAW,GAAG,IAAI,CAAC;AACzB,iFAAiF;AACjF,MAAM,cAAc,GAAG,CAAC,CAAC;AACzB,MAAM,QAAQ,GAAG,QAAQ,CAAC;AAQ1B;;;GAGG;AACH,MAAM,aAAa,GAAW;IAC5B,EAAE,KAAK,EAAE,OAAO,EAAE;IAClB,EAAE,KAAK,EAAE,QAAQ,EAAE;IACnB,EAAE,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,KAAK,EAAE;IAC7C,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE;CAC7C,CAAC;AAEF,MAAM,YAAY,GAAW;IAC3B,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,EAAE;IACjD,EAAE,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,KAAK,EAAE;IAC5C,EAAE,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE;IAC/C,EAAE,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE;CAC9C,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,MAAmB;IACjD,KAAK,IAAI,KAAK,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACzE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,WAAW;YAAE,OAAO,KAAK,CAAC;IACtE,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,GAAuB;IACrD,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC3E,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAOvB;IACC,6DAA6D;IAC7D,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IACtF,MAAM,IAAI,GAAY,EAAE,WAAW,EAAE,CAAC;IAEtC,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC;QAC1E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;QAC5F,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC;QACjE,IAAI,IAAI,CAAC,iBAAiB,KAAK,SAAS;YAAE,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,CAAC;QAC1F,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS;YAAE,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;IACtF,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,IAAI,WAAW,IAAI,cAAc,EAAE,CAAC;QAC7E,IAAI,CAAC,GAAG,GAAG,QAAQ,CAAC;IACtB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Trigger engine — turns a stream of finalized transcript lines into
|
|
3
|
+
* agent actions.
|
|
4
|
+
*
|
|
5
|
+
* Owns the state a pure function can't: the transcript window, the utterance
|
|
6
|
+
* being held while a speaker finishes a thought, the pending nudge, and the
|
|
7
|
+
* classifier circuit breaker. All judgment lives in `decide.ts`; all I/O goes
|
|
8
|
+
* through the injected `Classifier` and `TriggerHost`, so this file has no
|
|
9
|
+
* transport or network imports and runs under fake timers in tests.
|
|
10
|
+
*
|
|
11
|
+
* Ordering: lines are processed one at a time through a promise chain. A
|
|
12
|
+
* speaker's second fragment must see the hold created by their first, and a
|
|
13
|
+
* classifier call takes 100-500 ms — without serialization "Hey Vox, can you"
|
|
14
|
+
* + "check the logs" would race into two runs. Hard stops bypass the chain.
|
|
15
|
+
*/
|
|
16
|
+
import type { AgentAttentionEvent, AgentBackend, AgentTriggerDecisionEvent, PromptDisposition } from '../protocol.js';
|
|
17
|
+
import { type AgentDescriptor, type Classifier, type RoomInfo, type RunPlan, type RunSnapshot, type TranscriptLine, type TriggerSettings } from './types.js';
|
|
18
|
+
export interface TriggerPrompt {
|
|
19
|
+
promptId: string;
|
|
20
|
+
speaker: string;
|
|
21
|
+
speakerIdentity: string;
|
|
22
|
+
/** The merged burst. */
|
|
23
|
+
text: string;
|
|
24
|
+
window: TranscriptLine[];
|
|
25
|
+
room: RoomInfo;
|
|
26
|
+
disposition: PromptDisposition;
|
|
27
|
+
plan: RunPlan;
|
|
28
|
+
}
|
|
29
|
+
/** What the engine needs from the runtime that hosts it. */
|
|
30
|
+
export interface TriggerHost {
|
|
31
|
+
getRunSnapshot(): RunSnapshot;
|
|
32
|
+
startRun(prompt: TriggerPrompt): void;
|
|
33
|
+
/** Silence TTS, keep the run going. */
|
|
34
|
+
stopTalking(): void;
|
|
35
|
+
/** Cancel the active run and anything queued. */
|
|
36
|
+
abort(): void;
|
|
37
|
+
publishAttention(event: Omit<AgentAttentionEvent, 'type'>): void;
|
|
38
|
+
publishDecision(event: Omit<AgentTriggerDecisionEvent, 'type'>): void;
|
|
39
|
+
}
|
|
40
|
+
export interface TimerApi {
|
|
41
|
+
set(callback: () => void, ms: number): unknown;
|
|
42
|
+
clear(handle: unknown): void;
|
|
43
|
+
}
|
|
44
|
+
export interface TriggerEngineOptions {
|
|
45
|
+
/** `shadow`: the regex decides, jev's verdict is only reported. */
|
|
46
|
+
mode: 'jev' | 'regex' | 'shadow';
|
|
47
|
+
classifier: Classifier | null;
|
|
48
|
+
host: TriggerHost;
|
|
49
|
+
agent: AgentDescriptor;
|
|
50
|
+
/** Participant identity of the bridge host, for the host-authority rule. */
|
|
51
|
+
hostIdentity: string | null;
|
|
52
|
+
backend: AgentBackend;
|
|
53
|
+
autoModel: boolean;
|
|
54
|
+
modelOverrides?: string[] | null;
|
|
55
|
+
ack?: boolean;
|
|
56
|
+
settings?: Partial<TriggerSettings>;
|
|
57
|
+
now?: () => number;
|
|
58
|
+
timers?: TimerApi;
|
|
59
|
+
newId?: () => string;
|
|
60
|
+
}
|
|
61
|
+
export interface FinalLineInput {
|
|
62
|
+
entryId: string;
|
|
63
|
+
speaker: string;
|
|
64
|
+
speakerIdentity: string;
|
|
65
|
+
text: string;
|
|
66
|
+
timestampMs: number;
|
|
67
|
+
confidence?: number;
|
|
68
|
+
room: RoomInfo;
|
|
69
|
+
}
|
|
70
|
+
export declare class TriggerEngine {
|
|
71
|
+
private readonly options;
|
|
72
|
+
private readonly settings;
|
|
73
|
+
private readonly now;
|
|
74
|
+
private readonly timers;
|
|
75
|
+
private readonly newId;
|
|
76
|
+
private window;
|
|
77
|
+
private seenEntryIds;
|
|
78
|
+
private chain;
|
|
79
|
+
private hold;
|
|
80
|
+
private pendingNudge;
|
|
81
|
+
private lastNudgeAtMs;
|
|
82
|
+
private lastActivityAtMs;
|
|
83
|
+
private lastAgentText;
|
|
84
|
+
private lastAgentSpokeAtMs;
|
|
85
|
+
private addressingState;
|
|
86
|
+
private consecutiveFailures;
|
|
87
|
+
private breakerOpenUntilMs;
|
|
88
|
+
private disposed;
|
|
89
|
+
constructor(options: TriggerEngineOptions);
|
|
90
|
+
/** Resolves once this line (and everything queued before it) is handled. */
|
|
91
|
+
onFinal(input: FinalLineInput): Promise<void>;
|
|
92
|
+
/** Interim speech from a speaker — keeps their hold open while they talk. */
|
|
93
|
+
onSpeechActivity(speakerIdentity: string): void;
|
|
94
|
+
onNudgeResponse(nudgeId: string, decision: 'accept' | 'dismiss'): void;
|
|
95
|
+
/** The agent finished saying something; keeps it in the classifier's view. */
|
|
96
|
+
noteAgentSaid(text: string): void;
|
|
97
|
+
noteRunEnded(): void;
|
|
98
|
+
dispose(): void;
|
|
99
|
+
private rememberEntry;
|
|
100
|
+
private pushWindow;
|
|
101
|
+
private process;
|
|
102
|
+
private classifierUsable;
|
|
103
|
+
private classify;
|
|
104
|
+
private chooseAction;
|
|
105
|
+
private decideByRegex;
|
|
106
|
+
private canNudge;
|
|
107
|
+
private apply;
|
|
108
|
+
private onHoldTimer;
|
|
109
|
+
private resolveHold;
|
|
110
|
+
private clearHold;
|
|
111
|
+
/** Drops the hold and retires the Listening state on every browser. */
|
|
112
|
+
private releaseHold;
|
|
113
|
+
private buildPlan;
|
|
114
|
+
private startRun;
|
|
115
|
+
private startNudge;
|
|
116
|
+
private acceptNudge;
|
|
117
|
+
private endNudge;
|
|
118
|
+
private publishDecision;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/trigger/engine.ts"],"names":[],"mappings":"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"}
|