@pellux/goodvibes-sdk 1.1.0 → 1.3.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/dist/contracts/artifacts/operator-contract.json +1416 -88
- package/dist/platform/companion/companion-chat-manager.d.ts +6 -12
- package/dist/platform/companion/companion-chat-manager.d.ts.map +1 -1
- package/dist/platform/companion/companion-chat-manager.js +8 -2
- package/dist/platform/config/index.d.ts +2 -1
- package/dist/platform/config/index.d.ts.map +1 -1
- package/dist/platform/config/index.js +1 -0
- package/dist/platform/config/manager.d.ts +35 -0
- package/dist/platform/config/manager.d.ts.map +1 -1
- package/dist/platform/config/manager.js +98 -2
- package/dist/platform/config/shared-config-tier.d.ts +51 -0
- package/dist/platform/config/shared-config-tier.d.ts.map +1 -0
- package/dist/platform/config/shared-config-tier.js +127 -0
- package/dist/platform/control-plane/method-catalog-runtime.d.ts.map +1 -1
- package/dist/platform/control-plane/method-catalog-runtime.js +95 -1
- package/dist/platform/control-plane/operator-contract-schemas-knowledge.d.ts.map +1 -1
- package/dist/platform/control-plane/operator-contract-schemas-knowledge.js +6 -1
- package/dist/platform/control-plane/operator-contract-schemas-runtime.d.ts +7 -0
- package/dist/platform/control-plane/operator-contract-schemas-runtime.d.ts.map +1 -1
- package/dist/platform/control-plane/operator-contract-schemas-runtime.js +40 -1
- package/dist/platform/daemon/facade-composition.d.ts.map +1 -1
- package/dist/platform/daemon/facade-composition.js +1 -0
- package/dist/platform/daemon/homeassistant-chat.d.ts +4 -1
- package/dist/platform/daemon/homeassistant-chat.d.ts.map +1 -1
- package/dist/platform/daemon/homeassistant-chat.js +1 -1
- package/dist/platform/daemon/http/homeassistant-routes.d.ts.map +1 -1
- package/dist/platform/daemon/http/homeassistant-routes.js +28 -2
- package/dist/platform/integrations/webhooks.d.ts +15 -0
- package/dist/platform/integrations/webhooks.d.ts.map +1 -1
- package/dist/platform/integrations/webhooks.js +18 -0
- package/dist/platform/knowledge/extraction-policy.d.ts +12 -1
- package/dist/platform/knowledge/extraction-policy.d.ts.map +1 -1
- package/dist/platform/knowledge/extraction-policy.js +19 -1
- package/dist/platform/knowledge/graphql-schema.d.ts +1 -1
- package/dist/platform/knowledge/graphql-schema.d.ts.map +1 -1
- package/dist/platform/knowledge/graphql-schema.js +5 -0
- package/dist/platform/knowledge/home-graph/index.d.ts.map +1 -1
- package/dist/platform/knowledge/home-graph/index.js +4 -0
- package/dist/platform/knowledge/home-graph/link.d.ts +2 -2
- package/dist/platform/knowledge/home-graph/link.d.ts.map +1 -1
- package/dist/platform/knowledge/home-graph/link.js +60 -9
- package/dist/platform/knowledge/home-graph/refinement.d.ts +14 -9
- package/dist/platform/knowledge/home-graph/refinement.d.ts.map +1 -1
- package/dist/platform/knowledge/home-graph/refinement.js +18 -3
- package/dist/platform/knowledge/home-graph/reset.js +21 -4
- package/dist/platform/knowledge/home-graph/service.d.ts +10 -11
- package/dist/platform/knowledge/home-graph/service.d.ts.map +1 -1
- package/dist/platform/knowledge/home-graph/triage.d.ts +103 -0
- package/dist/platform/knowledge/home-graph/triage.d.ts.map +1 -0
- package/dist/platform/knowledge/home-graph/triage.js +362 -0
- package/dist/platform/knowledge/home-graph/types.d.ts +15 -0
- package/dist/platform/knowledge/home-graph/types.d.ts.map +1 -1
- package/dist/platform/knowledge/knowledge-api.d.ts +6 -0
- package/dist/platform/knowledge/knowledge-api.d.ts.map +1 -1
- package/dist/platform/knowledge/knowledge-api.js +5 -0
- package/dist/platform/knowledge/knowledge-history-types.d.ts +56 -0
- package/dist/platform/knowledge/knowledge-history-types.d.ts.map +1 -0
- package/dist/platform/knowledge/knowledge-history-types.js +1 -0
- package/dist/platform/knowledge/packet.d.ts.map +1 -1
- package/dist/platform/knowledge/packet.js +27 -3
- package/dist/platform/knowledge/scope-records.js +4 -2
- package/dist/platform/knowledge/semantic/answer-gaps.d.ts +8 -0
- package/dist/platform/knowledge/semantic/answer-gaps.d.ts.map +1 -1
- package/dist/platform/knowledge/semantic/answer-gaps.js +26 -6
- package/dist/platform/knowledge/semantic/enrichment.d.ts.map +1 -1
- package/dist/platform/knowledge/semantic/enrichment.js +15 -13
- package/dist/platform/knowledge/semantic/graph-index.d.ts.map +1 -1
- package/dist/platform/knowledge/semantic/graph-index.js +3 -1
- package/dist/platform/knowledge/semantic/service.d.ts +7 -0
- package/dist/platform/knowledge/semantic/service.d.ts.map +1 -1
- package/dist/platform/knowledge/semantic/service.js +10 -1
- package/dist/platform/knowledge/service-node-admin.d.ts +46 -0
- package/dist/platform/knowledge/service-node-admin.d.ts.map +1 -0
- package/dist/platform/knowledge/service-node-admin.js +72 -0
- package/dist/platform/knowledge/service.d.ts +24 -20
- package/dist/platform/knowledge/service.d.ts.map +1 -1
- package/dist/platform/knowledge/service.js +23 -47
- package/dist/platform/knowledge/store-config.d.ts +34 -0
- package/dist/platform/knowledge/store-config.d.ts.map +1 -1
- package/dist/platform/knowledge/store-config.js +23 -0
- package/dist/platform/knowledge/store-load.d.ts +3 -1
- package/dist/platform/knowledge/store-load.d.ts.map +1 -1
- package/dist/platform/knowledge/store-load.js +3 -1
- package/dist/platform/knowledge/store-node-history.d.ts +49 -0
- package/dist/platform/knowledge/store-node-history.d.ts.map +1 -0
- package/dist/platform/knowledge/store-node-history.js +331 -0
- package/dist/platform/knowledge/store-record-delete.d.ts +25 -0
- package/dist/platform/knowledge/store-record-delete.d.ts.map +1 -0
- package/dist/platform/knowledge/store-record-delete.js +84 -0
- package/dist/platform/knowledge/store-schema.d.ts +3 -1
- package/dist/platform/knowledge/store-schema.d.ts.map +1 -1
- package/dist/platform/knowledge/store-schema.js +66 -0
- package/dist/platform/knowledge/store.d.ts +19 -1
- package/dist/platform/knowledge/store.d.ts.map +1 -1
- package/dist/platform/knowledge/store.js +69 -108
- package/dist/platform/knowledge/types.d.ts +18 -0
- package/dist/platform/knowledge/types.d.ts.map +1 -1
- package/dist/platform/providers/registry.d.ts +18 -0
- package/dist/platform/providers/registry.d.ts.map +1 -1
- package/dist/platform/providers/registry.js +49 -3
- package/dist/platform/providers/runtime-snapshot.d.ts.map +1 -1
- package/dist/platform/providers/runtime-snapshot.js +14 -2
- package/dist/platform/runtime/memory-spine/client.d.ts +137 -12
- package/dist/platform/runtime/memory-spine/client.d.ts.map +1 -1
- package/dist/platform/runtime/memory-spine/client.js +119 -1
- package/dist/platform/runtime/memory-spine/index.d.ts +8 -1
- package/dist/platform/runtime/memory-spine/index.d.ts.map +1 -1
- package/dist/platform/runtime/memory-spine/index.js +7 -0
- package/dist/platform/runtime/memory-spine/recall-snapshot.d.ts +74 -0
- package/dist/platform/runtime/memory-spine/recall-snapshot.d.ts.map +1 -0
- package/dist/platform/runtime/memory-spine/recall-snapshot.js +87 -0
- package/dist/platform/runtime/memory-spine/wire-verb-availability.d.ts +59 -0
- package/dist/platform/runtime/memory-spine/wire-verb-availability.d.ts.map +1 -0
- package/dist/platform/runtime/memory-spine/wire-verb-availability.js +114 -0
- package/dist/platform/runtime/services.d.ts.map +1 -1
- package/dist/platform/runtime/services.js +3 -12
- package/dist/platform/state/memory-recall-contract.d.ts +7 -0
- package/dist/platform/state/memory-recall-contract.d.ts.map +1 -1
- package/dist/platform/state/memory-recall-contract.js +2 -0
- package/dist/platform/utils/notify.d.ts +25 -1
- package/dist/platform/utils/notify.d.ts.map +1 -1
- package/dist/platform/utils/notify.js +29 -1
- package/dist/platform/version.js +1 -1
- package/dist/platform/voice/index.d.ts +2 -0
- package/dist/platform/voice/index.d.ts.map +1 -1
- package/dist/platform/voice/index.js +3 -0
- package/dist/platform/voice/spoken-turn/audio-sink.d.ts +111 -0
- package/dist/platform/voice/spoken-turn/audio-sink.d.ts.map +1 -0
- package/dist/platform/voice/spoken-turn/audio-sink.js +1 -0
- package/dist/platform/voice/spoken-turn/controller.d.ts +117 -0
- package/dist/platform/voice/spoken-turn/controller.d.ts.map +1 -0
- package/dist/platform/voice/spoken-turn/controller.js +448 -0
- package/dist/platform/voice/spoken-turn/index.d.ts +16 -0
- package/dist/platform/voice/spoken-turn/index.d.ts.map +1 -0
- package/dist/platform/voice/spoken-turn/index.js +12 -0
- package/dist/platform/voice/spoken-turn/text-chunker.d.ts +33 -0
- package/dist/platform/voice/spoken-turn/text-chunker.d.ts.map +1 -0
- package/dist/platform/voice/spoken-turn/text-chunker.js +97 -0
- package/package.json +9 -9
|
@@ -139,6 +139,7 @@ export function runHonestMemorySearch(store, filter = {}, options = {}) {
|
|
|
139
139
|
excludedFlaggedCount: 0,
|
|
140
140
|
excludedBelowFloorCount: 0,
|
|
141
141
|
totalBeforeRecallFilter,
|
|
142
|
+
recallFloor: MIN_PROMPT_MEMORY_CONFIDENCE,
|
|
142
143
|
};
|
|
143
144
|
}
|
|
144
145
|
const kept = [];
|
|
@@ -166,5 +167,6 @@ export function runHonestMemorySearch(store, filter = {}, options = {}) {
|
|
|
166
167
|
excludedFlaggedCount,
|
|
167
168
|
excludedBelowFloorCount,
|
|
168
169
|
totalBeforeRecallFilter,
|
|
170
|
+
recallFloor: MIN_PROMPT_MEMORY_CONFIDENCE,
|
|
169
171
|
};
|
|
170
172
|
}
|
|
@@ -6,8 +6,32 @@
|
|
|
6
6
|
* @param title - Notification title
|
|
7
7
|
* @param message - Notification body
|
|
8
8
|
* @param durationMs - Turn duration in milliseconds
|
|
9
|
+
* @param options - Optional overrides (see {@link NotifyOptions}).
|
|
9
10
|
*/
|
|
10
11
|
/** Escape a string for safe interpolation into an AppleScript string literal. */
|
|
11
12
|
export declare function escapeAppleScript(s: string): string;
|
|
12
|
-
export
|
|
13
|
+
export interface NotifyOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Bypass test suppression for this call. Intended only for tests that
|
|
16
|
+
* exercise the notification shell-out layer itself (e.g. asserting the
|
|
17
|
+
* notify-send/osascript invocation or the terminal bell byte). Every
|
|
18
|
+
* other caller should leave this unset.
|
|
19
|
+
*/
|
|
20
|
+
force?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* True when this call must be a no-op: real desktop notifications (and the
|
|
24
|
+
* terminal bell) must never fire from an automated test run and spam
|
|
25
|
+
* whoever's desktop the tests happen to execute on.
|
|
26
|
+
*
|
|
27
|
+
* Suppressed when either:
|
|
28
|
+
* - `NODE_ENV === 'test'` (set automatically by `bun test`), or
|
|
29
|
+
* - `GOODVIBES_SUPPRESS_NOTIFY` is set to a truthy value (explicit
|
|
30
|
+
* override for harnesses that don't run under NODE_ENV=test).
|
|
31
|
+
*
|
|
32
|
+
* `options.force: true` bypasses suppression entirely — the one sanctioned
|
|
33
|
+
* escape hatch, for tests that specifically exercise this shell-out layer.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isNotifySuppressed(force?: boolean): boolean;
|
|
36
|
+
export declare function notifyCompletion(title: string, message: string, durationMs: number, options?: NotifyOptions): void;
|
|
13
37
|
//# sourceMappingURL=notify.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../../src/platform/utils/notify.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAKrF
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../../src/platform/utils/notify.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAKrF;;;;;;;;GAQG;AACH,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAM3D;AAiCD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAwBlH"}
|
|
@@ -8,11 +8,35 @@ import { logger } from './logger.js';
|
|
|
8
8
|
* @param title - Notification title
|
|
9
9
|
* @param message - Notification body
|
|
10
10
|
* @param durationMs - Turn duration in milliseconds
|
|
11
|
+
* @param options - Optional overrides (see {@link NotifyOptions}).
|
|
11
12
|
*/
|
|
12
13
|
/** Escape a string for safe interpolation into an AppleScript string literal. */
|
|
13
14
|
export function escapeAppleScript(s) {
|
|
14
15
|
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* True when this call must be a no-op: real desktop notifications (and the
|
|
19
|
+
* terminal bell) must never fire from an automated test run and spam
|
|
20
|
+
* whoever's desktop the tests happen to execute on.
|
|
21
|
+
*
|
|
22
|
+
* Suppressed when either:
|
|
23
|
+
* - `NODE_ENV === 'test'` (set automatically by `bun test`), or
|
|
24
|
+
* - `GOODVIBES_SUPPRESS_NOTIFY` is set to a truthy value (explicit
|
|
25
|
+
* override for harnesses that don't run under NODE_ENV=test).
|
|
26
|
+
*
|
|
27
|
+
* `options.force: true` bypasses suppression entirely — the one sanctioned
|
|
28
|
+
* escape hatch, for tests that specifically exercise this shell-out layer.
|
|
29
|
+
*/
|
|
30
|
+
export function isNotifySuppressed(force) {
|
|
31
|
+
if (force)
|
|
32
|
+
return false;
|
|
33
|
+
if (process.env.NODE_ENV === 'test')
|
|
34
|
+
return true;
|
|
35
|
+
const override = process.env.GOODVIBES_SUPPRESS_NOTIFY;
|
|
36
|
+
if (override && override !== '0' && override.toLowerCase() !== 'false')
|
|
37
|
+
return true;
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
16
40
|
function trimNotificationOutput(value) {
|
|
17
41
|
const trimmed = value.trim();
|
|
18
42
|
return trimmed.length > 0 ? trimmed.slice(0, 500) : undefined;
|
|
@@ -43,7 +67,11 @@ function spawnNotification(command) {
|
|
|
43
67
|
});
|
|
44
68
|
}
|
|
45
69
|
}
|
|
46
|
-
export function notifyCompletion(title, message, durationMs) {
|
|
70
|
+
export function notifyCompletion(title, message, durationMs, options) {
|
|
71
|
+
if (isNotifySuppressed(options?.force)) {
|
|
72
|
+
logger.debug('Completion notification suppressed under test', { title, durationMs });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
47
75
|
// Terminal bell for responses > 5s.
|
|
48
76
|
// Surface-specific: this writes directly to stdout and is only meaningful
|
|
49
77
|
// in a terminal context. Host surfaces that manage their own output stream
|
package/dist/platform/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
let version = '1.
|
|
3
|
+
let version = '1.3.0';
|
|
4
4
|
try {
|
|
5
5
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf-8'));
|
|
6
6
|
version = pkg.version ?? version;
|
|
@@ -4,4 +4,6 @@ export type { VoiceProviderDescriptor } from './provider-registry.js';
|
|
|
4
4
|
export { VoiceService } from './service.js';
|
|
5
5
|
export type { VoiceServiceStatus } from './service.js';
|
|
6
6
|
export { ensureBuiltinVoiceProviders } from './builtin-providers.js';
|
|
7
|
+
export { SpokenTurnController, TtsTextChunker, normalizeSpeechText } from './spoken-turn/index.js';
|
|
8
|
+
export type { SpokenTurnControllerOptions, TtsTextChunkerOptions, AudioSink, AudioSinkPlaybackOptions, } from './spoken-turn/index.js';
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/platform/voice/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,2BAA2B,EAC3B,qBAAqB,EACrB,oBAAoB,EACpB,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/platform/voice/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,2BAA2B,EAC3B,qBAAqB,EACrB,oBAAoB,EACpB,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAIrE,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACnG,YAAY,EACV,2BAA2B,EAC3B,qBAAqB,EACrB,SAAS,EACT,wBAAwB,GACzB,MAAM,wBAAwB,CAAC"}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export { VoiceProviderRegistry } from './provider-registry.js';
|
|
2
2
|
export { VoiceService } from './service.js';
|
|
3
3
|
export { ensureBuiltinVoiceProviders } from './builtin-providers.js';
|
|
4
|
+
// Spoken-turn (live TTS) policy engine — shared behavioral contract; consumers
|
|
5
|
+
// inject an AudioSink for I/O. See ./spoken-turn.
|
|
6
|
+
export { SpokenTurnController, TtsTextChunker, normalizeSpeechText } from './spoken-turn/index.js';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { VoiceAudioChunk } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* AudioSink — the injectable I/O boundary the spoken-turn policy engine plays
|
|
4
|
+
* through. The SDK owns POLICY (chunking, the bounded synthesis window, merge
|
|
5
|
+
* coalescing, retry/backoff, the turn state machine); a sink owns I/O (turning
|
|
6
|
+
* an ordered stream of audio bytes into sound and reporting when it has stopped
|
|
7
|
+
* making sound). The controller never spawns a process, opens a device, or
|
|
8
|
+
* touches Web Audio — it only talks to this interface, so the same policy runs
|
|
9
|
+
* unchanged behind a terminal subprocess player, a browser Web Audio sink, or a
|
|
10
|
+
* test fake.
|
|
11
|
+
*
|
|
12
|
+
* ## Behavioral contract
|
|
13
|
+
*
|
|
14
|
+
* The controller depends on these guarantees, not on any implementation detail:
|
|
15
|
+
*
|
|
16
|
+
* 1. **Readiness / head survival.** `play()` must not drop the leading bytes of
|
|
17
|
+
* the stream while the underlying output is still coming up. A subprocess
|
|
18
|
+
* sink holds the first byte until the player has actually exec'd (its spawn
|
|
19
|
+
* event); a browser sink holds until the MediaSource `sourceopen` /
|
|
20
|
+
* SourceBuffer is ready. The controller writes the whole stream and trusts
|
|
21
|
+
* the sink to have played the head — it does not re-send or pre-buffer.
|
|
22
|
+
* `available` is the coarse, synchronous readiness signal (is any output
|
|
23
|
+
* device usable at all); per-call readiness is internal to `play()`.
|
|
24
|
+
*
|
|
25
|
+
* 2. **Natural drain plays everything.** When the input stream ends without an
|
|
26
|
+
* abort, `play()` resolves only after the last buffered sample has been
|
|
27
|
+
* heard — never at end-of-input. Truncating the tail of a response is the
|
|
28
|
+
* bug this contract exists to prevent.
|
|
29
|
+
*
|
|
30
|
+
* 3. **Abort cuts immediately.** When `options.signal` aborts (a deliberate
|
|
31
|
+
* interrupt — new turn, Ctrl+C, /tts stop, turn cancel), `play()` stops
|
|
32
|
+
* emitting sound and resolves promptly; it must NOT wait on a graceful
|
|
33
|
+
* drain. `stop()` is the same instant cut driven imperatively rather than by
|
|
34
|
+
* a signal.
|
|
35
|
+
*
|
|
36
|
+
* 4. **Bounded exit drain.** `waitForDrain(timeoutMs)` resolves when the sound
|
|
37
|
+
* currently playing finishes naturally OR after `timeoutMs`, whichever comes
|
|
38
|
+
* first, and resolves immediately when nothing is playing. The exit path
|
|
39
|
+
* uses this to let the final audio of a finished response play out inside a
|
|
40
|
+
* short window instead of being cut mid-word.
|
|
41
|
+
*
|
|
42
|
+
* ## Web Audio / browser sink mapping (proof the interface fits a browser)
|
|
43
|
+
*
|
|
44
|
+
* A browser sink for the webui voice build implements this same interface over
|
|
45
|
+
* streaming MP3 into MediaSource; no interface change is required:
|
|
46
|
+
*
|
|
47
|
+
* - `available` -> `('MediaSource' in window) && MediaSource.isTypeSupported('audio/mpeg')`
|
|
48
|
+
* (fall back to Web Audio `decodeAudioData` where MSE is absent).
|
|
49
|
+
* - `play(chunks, { format, signal })` ->
|
|
50
|
+
* - create a `MediaSource`, attach it to an `HTMLAudioElement` via
|
|
51
|
+
* `URL.createObjectURL`, and `await` its `sourceopen` event — this is the
|
|
52
|
+
* readiness gate (contract 1);
|
|
53
|
+
* - `addSourceBuffer(mimeFor(format))` where `mimeFor('mp3') === 'audio/mpeg'`
|
|
54
|
+
* (the same `format` string the controller forwards from the synthesis
|
|
55
|
+
* result), then `await audioEl.play()`;
|
|
56
|
+
* - `for await (const chunk of chunks)` -> `sourceBuffer.appendBuffer(chunk.data)`,
|
|
57
|
+
* awaiting each `updateend`, honoring `signal.aborted` between chunks;
|
|
58
|
+
* - on input end without abort: `mediaSource.endOfStream()` and resolve on
|
|
59
|
+
* the audio element's `ended` event -> natural drain (contract 2);
|
|
60
|
+
* - on `signal` abort: `audioEl.pause()`, `sourceBuffer.abort()`,
|
|
61
|
+
* `mediaSource.endOfStream()`, revoke the object URL, resolve now
|
|
62
|
+
* (contract 3).
|
|
63
|
+
* - `stop()` -> the same teardown as the abort branch, driven imperatively.
|
|
64
|
+
* - `waitForDrain(timeoutMs)` -> resolve on the audio element's `ended` event or
|
|
65
|
+
* a `setTimeout(timeoutMs)`, whichever fires first (contract 4).
|
|
66
|
+
*
|
|
67
|
+
* `VoiceAudioChunk.data` is a `Uint8Array`, which `appendBuffer` accepts
|
|
68
|
+
* directly, and the `format` string maps 1:1 to a MediaSource MIME type, so the
|
|
69
|
+
* byte-and-format shape the controller emits is exactly what a browser sink
|
|
70
|
+
* consumes. (The subprocess sink — mpv/ffplay over stdin — stays consumer-side
|
|
71
|
+
* and is not part of the SDK.)
|
|
72
|
+
*/
|
|
73
|
+
export interface AudioSink {
|
|
74
|
+
/** Human-readable sink name for status lines (e.g. 'mpv', 'web-audio'). */
|
|
75
|
+
readonly label: string;
|
|
76
|
+
/**
|
|
77
|
+
* Coarse, synchronous readiness: is any output device usable at all. When
|
|
78
|
+
* false the controller arms nothing and reports the graceful-degradation
|
|
79
|
+
* notice instead of attempting playback.
|
|
80
|
+
*/
|
|
81
|
+
readonly available: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Play an ordered stream of audio chunks to completion. Resolves after the
|
|
84
|
+
* final sample is heard on a natural end, or promptly on abort. See the
|
|
85
|
+
* interface contract above for readiness, drain, and abort obligations.
|
|
86
|
+
*/
|
|
87
|
+
play(chunks: AsyncIterable<VoiceAudioChunk>, options: AudioSinkPlaybackOptions): Promise<void>;
|
|
88
|
+
/** Instant cut: stop emitting sound now and release the output. Idempotent; a no-op when idle. */
|
|
89
|
+
stop(): void;
|
|
90
|
+
/**
|
|
91
|
+
* Resolves once the currently playing sound has finished naturally or after
|
|
92
|
+
* `timeoutMs`, whichever comes first; resolves immediately when nothing is
|
|
93
|
+
* playing. The bounded exit drain — see contract 4.
|
|
94
|
+
*/
|
|
95
|
+
waitForDrain(timeoutMs: number): Promise<void>;
|
|
96
|
+
}
|
|
97
|
+
export interface AudioSinkPlaybackOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Container/codec hint for the byte stream (e.g. 'mp3'), forwarded verbatim
|
|
100
|
+
* from the synthesis result's `format`. A subprocess sink maps it to a
|
|
101
|
+
* demuxer flag; a browser sink maps it to a MediaSource MIME type. Omitted or
|
|
102
|
+
* a value containing '/' (already a MIME type) leaves the sink on its default.
|
|
103
|
+
*/
|
|
104
|
+
readonly format?: string | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* Deliberate-interrupt signal. When it aborts, playback cuts immediately (no
|
|
107
|
+
* graceful drain) and `play()` resolves. Absence means "play to natural end".
|
|
108
|
+
*/
|
|
109
|
+
readonly signal?: AbortSignal | undefined;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=audio-sink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-sink.d.ts","sourceRoot":"","sources":["../../../../src/platform/voice/spoken-turn/audio-sink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AACH,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,EAAE,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/F,kGAAkG;IAClG,IAAI,IAAI,IAAI,CAAC;IACb;;;;OAIG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC3C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ConfigManager } from '../../config/index.js';
|
|
2
|
+
import type { TurnEvent } from '../../../events/turn.js';
|
|
3
|
+
import type { VoiceService } from '../service.js';
|
|
4
|
+
import type { AudioSink } from './audio-sink.js';
|
|
5
|
+
export interface SpokenTurnControllerOptions {
|
|
6
|
+
readonly voiceService: Pick<VoiceService, 'synthesizeStream'>;
|
|
7
|
+
readonly configManager: Pick<ConfigManager, 'get'>;
|
|
8
|
+
/** The injectable I/O boundary. See {@link AudioSink}. */
|
|
9
|
+
readonly sink: AudioSink;
|
|
10
|
+
readonly notify?: ((message: string) => void) | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Attribution label recorded in each synthesis request's metadata.source
|
|
13
|
+
* (e.g. 'goodvibes-tui', 'goodvibes-agent', 'goodvibes-webui'). Defaults to
|
|
14
|
+
* 'goodvibes-sdk'.
|
|
15
|
+
*/
|
|
16
|
+
readonly source?: string | undefined;
|
|
17
|
+
readonly now?: (() => number) | undefined;
|
|
18
|
+
readonly setInterval?: typeof setInterval | undefined;
|
|
19
|
+
readonly clearInterval?: typeof clearInterval | undefined;
|
|
20
|
+
/** Injectable clock for retry backoff timers (tests use fakes). */
|
|
21
|
+
readonly setTimeout?: typeof setTimeout | undefined;
|
|
22
|
+
readonly clearTimeout?: typeof clearTimeout | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* SpokenTurnController — the shared spoken-output policy engine. It watches a
|
|
26
|
+
* turn's lifecycle events, chunks the streamed answer into speech-sized pieces,
|
|
27
|
+
* merges and dispatches synthesis requests through a bounded 2-slot window with
|
|
28
|
+
* retry/backoff, and drives an injected {@link AudioSink} with honest
|
|
29
|
+
* drain-vs-interrupt semantics. All I/O is the sink's; this class is pure
|
|
30
|
+
* policy and runs unchanged in the terminal, the agent, and the browser.
|
|
31
|
+
*/
|
|
32
|
+
export declare class SpokenTurnController {
|
|
33
|
+
private pendingPrompt;
|
|
34
|
+
private activeTurnId;
|
|
35
|
+
private chunker;
|
|
36
|
+
private chunkSequence;
|
|
37
|
+
private playbackChain;
|
|
38
|
+
private readonly abortControllers;
|
|
39
|
+
private timer;
|
|
40
|
+
private errorReportedForTurn;
|
|
41
|
+
private noSinkNoticed;
|
|
42
|
+
/** Chunker output waiting to be merged into a synthesis request. */
|
|
43
|
+
private pendingTexts;
|
|
44
|
+
/** Requests currently in the pipeline (synthesizing / waiting / playing). */
|
|
45
|
+
private pipelineDepth;
|
|
46
|
+
/** Bumped on every teardown so stale pipeline releases are ignored. */
|
|
47
|
+
private pipelineGeneration;
|
|
48
|
+
private pumpScheduled;
|
|
49
|
+
/** Set when TURN_COMPLETED arrives; the turn releases once the pipeline drains. */
|
|
50
|
+
private completedTurnId;
|
|
51
|
+
private readonly voiceService;
|
|
52
|
+
private readonly configManager;
|
|
53
|
+
private readonly sink;
|
|
54
|
+
private readonly notify?;
|
|
55
|
+
private readonly source;
|
|
56
|
+
private readonly now;
|
|
57
|
+
private readonly setIntervalImpl;
|
|
58
|
+
private readonly clearIntervalImpl;
|
|
59
|
+
private readonly setTimeoutImpl;
|
|
60
|
+
private readonly clearTimeoutImpl;
|
|
61
|
+
constructor(options: SpokenTurnControllerOptions);
|
|
62
|
+
submitNextTurn(prompt: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Returns whether speech was actually ACTIVE when stopped. The notice only
|
|
65
|
+
* prints in that case — stop() on an idle controller used to notify anyway,
|
|
66
|
+
* spamming "[TTS] Spoken output stopped." on every Ctrl+C (an earlier replay
|
|
67
|
+
* fix); callers use the return to decide whether the press "did a
|
|
68
|
+
* job" (see handleCtrlC's consume-on-speech-stop).
|
|
69
|
+
*/
|
|
70
|
+
stop(message?: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Exit-path teardown: drops everything not yet audible (pending arm,
|
|
73
|
+
* buffered text, queued chunks) but lets the audio the user is already
|
|
74
|
+
* hearing finish naturally, capped at `drainTimeoutMs`, before the hard
|
|
75
|
+
* stop. Deliberate interrupts (Ctrl+C, /tts stop, turn cancel) keep their
|
|
76
|
+
* instant path through stop(); this is only for exiting the app while the
|
|
77
|
+
* final audio of a completed response is still draining.
|
|
78
|
+
*/
|
|
79
|
+
stopForExit(drainTimeoutMs?: number): Promise<void>;
|
|
80
|
+
handleTurnEvent(event: TurnEvent): void;
|
|
81
|
+
private maybeStartTurn;
|
|
82
|
+
private finishTurn;
|
|
83
|
+
private resetPipeline;
|
|
84
|
+
private startTimer;
|
|
85
|
+
private stopTimer;
|
|
86
|
+
/**
|
|
87
|
+
* Chunker output does NOT map 1:1 to synthesis requests. Text queues here
|
|
88
|
+
* and the pump merges everything pending into one request whenever a
|
|
89
|
+
* pipeline slot is free — so the request count tracks how often the model
|
|
90
|
+
* out-paces the audio, not how many sentences it wrote. A short answer that
|
|
91
|
+
* arrives before the first pump tick is exactly one request.
|
|
92
|
+
*/
|
|
93
|
+
private queueTexts;
|
|
94
|
+
/**
|
|
95
|
+
* Deferred one tick so text delivered in the same synchronous burst (fast
|
|
96
|
+
* deltas, or a turn that completes instantly) coalesces into a single
|
|
97
|
+
* request instead of firing per sentence boundary.
|
|
98
|
+
*/
|
|
99
|
+
private schedulePump;
|
|
100
|
+
private pump;
|
|
101
|
+
/** Merge everything pending into one request, capped at the per-request text limit. */
|
|
102
|
+
private takeMergedText;
|
|
103
|
+
private dispatchChunk;
|
|
104
|
+
private releasePipelineSlot;
|
|
105
|
+
private maybeReleaseTurn;
|
|
106
|
+
private synthesizeWithRetry;
|
|
107
|
+
/** Abortable backoff sleep — an abort clears the timer and rejects, so a stop mid-backoff leaves nothing running. */
|
|
108
|
+
private delay;
|
|
109
|
+
private synthesize;
|
|
110
|
+
/**
|
|
111
|
+
* One synthesis request failed after its retries. Report once per turn and
|
|
112
|
+
* keep going — the rest of the response still plays.
|
|
113
|
+
*/
|
|
114
|
+
private reportSkippedChunk;
|
|
115
|
+
private reportError;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../../src/platform/voice/spoken-turn/controller.ts"],"names":[],"mappings":"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"}
|