@pellux/goodvibes-tui 1.26.0 → 1.27.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 +1 -1
- package/docs/foundation-artifacts/operator-contract.json +3756 -1110
- package/package.json +8 -7
- package/scripts/postinstall.js +57 -1
- package/src/audio/activation-sound.ts +117 -0
- package/src/audio/capture.ts +112 -0
- package/src/audio/onnxruntime-assets.d.ts +24 -0
- package/src/audio/voice-capture-wiring.ts +119 -0
- package/src/audio/voice-input-session.ts +181 -0
- package/src/audio/wake-inference.ts +182 -0
- package/src/audio/wake-runtime.ts +341 -0
- package/src/cli/bundle-command.ts +4 -1
- package/src/cli/management-utils.ts +4 -1
- package/src/core/voice-capture-status.ts +53 -0
- package/src/core/voice-stt-gateway.ts +75 -0
- package/src/core/wake-provision-runner.ts +86 -0
- package/src/core/wake-provision-status.ts +170 -0
- package/src/daemon/cli.ts +38 -1
- package/src/daemon/provision-wake-model.ts +81 -0
- package/src/input/command-registry.ts +6 -0
- package/src/input/commands/experience-runtime.ts +37 -3
- package/src/input/handler-shortcuts.ts +8 -0
- package/src/input/keybindings.ts +10 -1
- package/src/input/onboarding/onboarding-feature-units.ts +5 -5
- package/src/input/settings-modal-types.ts +14 -1
- package/src/main.ts +12 -14
- package/src/renderer/process-indicator.ts +66 -0
- package/src/renderer/settings-modal-helpers.ts +4 -2
- package/src/renderer/shell-surface.ts +22 -3
- package/src/runtime/bootstrap-core.ts +7 -7
- package/src/runtime/bootstrap.ts +1 -1
- package/src/runtime/device-posture-composition.ts +110 -0
- package/src/runtime/disposal-wiring.ts +16 -0
- package/src/runtime/runtime-services-types.ts +17 -0
- package/src/runtime/services.ts +81 -4
- package/src/runtime/voice-setup-services.ts +50 -4
- package/src/shell/voice-capture-shell.ts +69 -0
- package/src/verification/verification-ledger.ts +59 -20
- package/src/version.ts +1 -1
package/src/runtime/services.ts
CHANGED
|
@@ -55,6 +55,10 @@ import { createWorkstreamServices } from './workstream-services.ts';
|
|
|
55
55
|
import { wireFleetNeedsInputPush } from './fleet-needs-input-push.ts';
|
|
56
56
|
import { codeIndexDbPath, createCodeIndexServices, createStoreRerooter, isCodeInjectionSettingEnabled } from './code-index-services.ts';
|
|
57
57
|
import { createDaemonHandlerComposition } from './daemon-handler-composition.ts';
|
|
58
|
+
import { createDevicePostureServices } from './device-posture-composition.ts';
|
|
59
|
+
// Re-exported so the shell's bootstrap reaches the install through the same
|
|
60
|
+
// module it already imports the runtime graph from.
|
|
61
|
+
export { installDevicePosture } from './device-posture-composition.ts';
|
|
58
62
|
import { createClusterServices, startClusterServices } from './cluster-group-composition.ts';
|
|
59
63
|
import { WorkspaceTrustManager } from './trust/workspace-trust.ts';
|
|
60
64
|
import { ensureConfiguredModelIsRoutable } from './provider-fallback.ts';
|
|
@@ -159,8 +163,24 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
159
163
|
join(surface.sessionsDir, 'task-graph.json'),
|
|
160
164
|
);
|
|
161
165
|
const hookActivityTracker = new HookActivityTracker();
|
|
166
|
+
// featureFlags is REQUIRED here in practice, even though the SDK types it
|
|
167
|
+
// optional. isFeatureGateEnabled(null, ...) is permissive by design — a
|
|
168
|
+
// narrow embed with no manager wired gets the capability rather than a
|
|
169
|
+
// silent off — so omitting it did not disable the watcher framework when
|
|
170
|
+
// watchers.enabled is turned off; it made the setting configure nothing.
|
|
171
|
+
// Threading it preserves current effective behaviour rather than changing
|
|
172
|
+
// it: watchers.enabled defaults true, the watcher-framework flag's own
|
|
173
|
+
// defaultState is 'enabled', and the flag declares no notOperable record —
|
|
174
|
+
// so with nothing configured the gate reads exactly as before, and the
|
|
175
|
+
// difference is only that turning it OFF now turns it off. This composes
|
|
176
|
+
// with bootstrap-core.ts's own `if (configManager.get('watchers.enabled'))`
|
|
177
|
+
// check around the built-in polling watcher registration: that check reads
|
|
178
|
+
// the same key, so the two never disagree, and this fix only reaches the
|
|
179
|
+
// watcher registry's OTHER entry points (registerWatcher/startWatcher/etc.)
|
|
180
|
+
// that bootstrap-core.ts's narrower check does not cover.
|
|
162
181
|
const watcherRegistry = new WatcherRegistry({
|
|
163
182
|
storePath: shellPaths.resolveProjectPath('tui', 'watchers.json'),
|
|
183
|
+
featureFlags,
|
|
164
184
|
});
|
|
165
185
|
watcherRegistry.attachRuntime({
|
|
166
186
|
runtimeStore: options.runtimeStore,
|
|
@@ -223,6 +243,15 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
223
243
|
// modal): bootstrap.ts activates the wire transport when a compatible
|
|
224
244
|
// external daemon is adopted, same signal as the session spine.
|
|
225
245
|
const memorySpine = new MemorySpineClient({ local: createLocalMemoryAccess(memoryRegistry) });
|
|
246
|
+
// featureFlags is REQUIRED here in practice, even though the SDK types it
|
|
247
|
+
// optional. isFeatureGateEnabled(null, ...) is permissive by design, so
|
|
248
|
+
// omitting it did not disable delivery tracking when
|
|
249
|
+
// integrations.deliveryTracking is turned off — it made the setting
|
|
250
|
+
// configure nothing: deliverText/deliverJobRun kept running either way.
|
|
251
|
+
// Threading it preserves current effective behaviour rather than changing
|
|
252
|
+
// it: the config default is true, the delivery-engine flag's own
|
|
253
|
+
// defaultState is 'enabled', and the flag declares no notOperable record —
|
|
254
|
+
// so with nothing configured the gate reads exactly as before.
|
|
226
255
|
const deliveryManager = new AutomationDeliveryManager({
|
|
227
256
|
configManager,
|
|
228
257
|
// This manager builds the delivery router the daemon actually replies
|
|
@@ -236,7 +265,20 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
236
265
|
runtimeStore: options.runtimeStore,
|
|
237
266
|
routeBindings,
|
|
238
267
|
artifactStore,
|
|
268
|
+
featureFlags,
|
|
239
269
|
});
|
|
270
|
+
// Same shape as deliveryManager above: automation.enabled defaults true,
|
|
271
|
+
// the automation-domain flag's own defaultState is 'enabled', so threading
|
|
272
|
+
// featureFlags here does not change what a default install does — it only
|
|
273
|
+
// makes turning automation.enabled off actually turn AutomationManager's own
|
|
274
|
+
// create/update/run/list surface off. This composes with bootstrap.ts's own
|
|
275
|
+
// `if (configManager.get('automation.enabled'))` gate around scheduling
|
|
276
|
+
// automationManager.start(): that check reads the same key, and
|
|
277
|
+
// AutomationManager.start() re-checks the same gate internally (it no-ops
|
|
278
|
+
// and stops rather than starting when disabled), so the two can never
|
|
279
|
+
// disagree — this fix only reaches the manager's OTHER entry points
|
|
280
|
+
// (createJob/updateJob/runNow/etc.) that the bootstrap.ts gate does not
|
|
281
|
+
// cover, the same way watchers.enabled and the watcher registry above do.
|
|
240
282
|
const automationManager = new AutomationManager({
|
|
241
283
|
configManager,
|
|
242
284
|
defaultSurfaceKind: 'tui',
|
|
@@ -248,6 +290,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
248
290
|
// Same live registry: a bare model id on an automation job resolves through
|
|
249
291
|
// the shared resolver instead of a format-only rejection.
|
|
250
292
|
providerRegistry,
|
|
293
|
+
featureFlags,
|
|
251
294
|
spawnTask: (input) => {
|
|
252
295
|
const record = agentManager.spawn({
|
|
253
296
|
mode: 'spawn',
|
|
@@ -309,6 +352,16 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
309
352
|
approvalBridge: approvalBroker,
|
|
310
353
|
automationBridge: automationManager,
|
|
311
354
|
});
|
|
355
|
+
// The paired-phone feature for this host, on the SAME runtime phones pair onto
|
|
356
|
+
// and the SAME approval broker every other confirmation rides. Every `device.*`
|
|
357
|
+
// setting is read live through this; see device-posture-composition.ts.
|
|
358
|
+
const { devicePosture } = createDevicePostureServices({
|
|
359
|
+
configManager,
|
|
360
|
+
distributedRuntime,
|
|
361
|
+
approvals: approvalBroker,
|
|
362
|
+
stateDirectory: shellPaths.resolveProjectPath('tui', 'devices'),
|
|
363
|
+
gatewayMethods,
|
|
364
|
+
});
|
|
312
365
|
|
|
313
366
|
// Which machines on this network are "us", and which of them reads the
|
|
314
367
|
// shared inbox. Both inert until startCluster() — no socket, no key material
|
|
@@ -334,7 +387,18 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
334
387
|
= createRemoteExecutionServices({
|
|
335
388
|
agentManager, workingDirectory, hookDispatcher, configManager, runtimeBus: options.runtimeBus,
|
|
336
389
|
});
|
|
337
|
-
|
|
390
|
+
// featureFlags is REQUIRED here in practice, even though the SDK types it
|
|
391
|
+
// optional. isFeatureGateEnabled(null, ...) is permissive by design, so
|
|
392
|
+
// omitting it did not disable managed blocking when
|
|
393
|
+
// security.tokenAudit.enabled is turned off. Threading it preserves current
|
|
394
|
+
// effective behaviour rather than changing it: managed is hardcoded false
|
|
395
|
+
// here (advisory reporting only; excess-scope/overdue tokens are reported,
|
|
396
|
+
// never blocked, regardless of this flag — see isBlocked()'s
|
|
397
|
+
// `this._config.managed && this._managedBlockingEnabled()` guard), the
|
|
398
|
+
// config default for security.tokenAudit.enabled is true, and the
|
|
399
|
+
// token-scope-rotation-audit flag's own defaultState is 'enabled' — so with
|
|
400
|
+
// nothing configured the gate reads exactly as before either way.
|
|
401
|
+
const tokenAuditor = new ApiTokenAuditor({ managed: false, featureFlags });
|
|
338
402
|
const componentHealthMonitor = new ComponentHealthMonitor();
|
|
339
403
|
const worktreeRegistry = new WorktreeRegistry(workingDirectory);
|
|
340
404
|
const webhookNotifier = new WebhookNotifier();
|
|
@@ -386,7 +450,15 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
386
450
|
runtimeBus: options.runtimeBus,
|
|
387
451
|
observeExternalAgents: options.observeExternalAgents, providerRegistry, // observeExternalAgents is daemon-side only
|
|
388
452
|
});
|
|
389
|
-
|
|
453
|
+
// featureFlags is REQUIRED here in practice, even though the SDK types it
|
|
454
|
+
// optional. isFeatureGateEnabled(null, ...) is permissive by design, so
|
|
455
|
+
// omitting it did not disable the HITL UX mode system when
|
|
456
|
+
// behavior.hitlMode is set to 'off' — setHITLMode/setDomainVerbosity kept
|
|
457
|
+
// accepting writes either way. Threading it preserves current effective
|
|
458
|
+
// behaviour rather than changing it: behavior.hitlMode defaults to
|
|
459
|
+
// 'balanced' (not 'off'), and the hitl-ux-modes flag's own defaultState is
|
|
460
|
+
// 'enabled' — so with nothing configured the gate reads exactly as before.
|
|
461
|
+
const modeManager = new ModeManager({ featureFlags }); const fileUndoManager = new FileUndoManager();
|
|
390
462
|
const workspaceCheckpointManager = createWorkspaceCheckpointing({ workspaceRoot: workingDirectory, surface, runtimeBus: options.runtimeBus, configManager });
|
|
391
463
|
// memory-consolidation honors governor backpressure: it ticks only when idle
|
|
392
464
|
// AND the 'memory-consolidation' job is not paused AND expensive work is
|
|
@@ -414,7 +486,11 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
414
486
|
|
|
415
487
|
// Managed local-voice provisioning (voice.local.status/install) — single-flight
|
|
416
488
|
// one-act install + no-network status; see voice-setup-services.ts.
|
|
417
|
-
const { voiceSetup } = wireVoiceSetup({ configManager, shellPaths, voiceProviders, admitExpensiveWork
|
|
489
|
+
const { voiceSetup, stopWakeHousekeeping } = wireVoiceSetup({ configManager, shellPaths, voiceProviders, admitExpensiveWork,
|
|
490
|
+
// Boot provisioning of the wake-word model + its recovery sweep, opted into
|
|
491
|
+
// by the real entrypoints only (same treatment as powerSeam) so a one-shot
|
|
492
|
+
// CLI command and a test composing this graph fetch nothing and start no timer.
|
|
493
|
+
provisionWakeModelsAtBoot: options.provisionWakeModelsAtBoot === true });
|
|
418
494
|
|
|
419
495
|
// Terminal-shell wrapper over the SDK registerGatewayVerbGroups (gateway-verbs.ts); checkin.*/fleet-needs-input/pairing.* register only when their deps are present. memoryGovernor lights up ops.memory.get; voiceSetup lights up voice.local.status/install.
|
|
420
496
|
// calendar.*/email.* are platform-served; these two let it register (mail-composition.ts).
|
|
@@ -552,6 +628,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
552
628
|
providerRegistry,
|
|
553
629
|
toolLLM,
|
|
554
630
|
distributedRuntime,
|
|
631
|
+
devicePosture,
|
|
555
632
|
daemonHandlers,
|
|
556
633
|
clusterCoordinator,
|
|
557
634
|
clusterGroup,
|
|
@@ -577,7 +654,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
577
654
|
workstreamCommands,
|
|
578
655
|
codeIndexStore,
|
|
579
656
|
codeIndexReindexScheduler,
|
|
580
|
-
storeSnapshotScheduler, appendOnlyRetentionScheduler, stopDurabilityHousekeeping,
|
|
657
|
+
storeSnapshotScheduler, appendOnlyRetentionScheduler, stopDurabilityHousekeeping, stopWakeHousekeeping,
|
|
581
658
|
memoryConsolidationScheduler,
|
|
582
659
|
powerManager,
|
|
583
660
|
memoryGovernor,
|
|
@@ -23,30 +23,76 @@ import {
|
|
|
23
23
|
createVoiceSetupService,
|
|
24
24
|
type VoiceSetupService,
|
|
25
25
|
} from '@pellux/goodvibes-sdk/platform/runtime/voice-setup';
|
|
26
|
-
import
|
|
26
|
+
import {
|
|
27
|
+
startWakeBootProvisioning,
|
|
28
|
+
type VoiceProviderRegistry,
|
|
29
|
+
} from '@pellux/goodvibes-sdk/platform/voice';
|
|
27
30
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
31
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
28
32
|
import type { ShellPathService } from '@/runtime/index.ts';
|
|
29
33
|
|
|
30
34
|
export type { VoiceSetupService };
|
|
31
35
|
|
|
36
|
+
/**
|
|
37
|
+
* The recovery act this surface names when a boot provision degrades. The SDK
|
|
38
|
+
* would otherwise name the control-plane verb, which is correct everywhere and
|
|
39
|
+
* useless to someone sitting in a terminal.
|
|
40
|
+
*/
|
|
41
|
+
export const WAKE_RECOVERY_COMMAND = '/voice wake setup';
|
|
42
|
+
|
|
32
43
|
export interface VoiceSetupServicesDeps {
|
|
33
44
|
readonly configManager: ConfigManager;
|
|
34
45
|
readonly shellPaths: Pick<ShellPathService, 'resolveUserPath'>;
|
|
35
46
|
readonly voiceProviders: VoiceProviderRegistry;
|
|
36
47
|
/** Critical-tier admission gate — a provision run allocates archive + model buffers. */
|
|
37
48
|
readonly admitExpensiveWork: (label: string) => { allowed: boolean; reason?: string | undefined };
|
|
49
|
+
/**
|
|
50
|
+
* Opt into boot-time wake-model provisioning and the recovery sweeper.
|
|
51
|
+
*
|
|
52
|
+
* Off unless a real entrypoint asks for it, matching how this graph treats the
|
|
53
|
+
* host power seam: `daemon/cli.ts` and `runtime/bootstrap-core.ts` opt in, the
|
|
54
|
+
* one-shot CLI commands do not, and a test composing the graph gets neither a
|
|
55
|
+
* network fetch nor a repeating timer it did not ask for.
|
|
56
|
+
*/
|
|
57
|
+
readonly provisionWakeModelsAtBoot?: boolean | undefined;
|
|
58
|
+
/** Injected in tests: the boot starter, so no sweeper and no fetch are real. */
|
|
59
|
+
readonly startBootProvisioning?: typeof startWakeBootProvisioning | undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface VoiceSetupServices {
|
|
63
|
+
readonly voiceSetup: VoiceSetupService;
|
|
64
|
+
/**
|
|
65
|
+
* Stop the wake-word recovery sweeper and cancel a pending boot provision.
|
|
66
|
+
* A no-op when boot provisioning was not opted into. Registered on the
|
|
67
|
+
* disposal scope (disposal-wiring.ts) — an hourly timer nothing stops is a
|
|
68
|
+
* poller this surface leaked, which is exactly what that scope exists for.
|
|
69
|
+
*/
|
|
70
|
+
readonly stopWakeHousekeeping: () => void;
|
|
38
71
|
}
|
|
39
72
|
|
|
40
73
|
/** Compose the managed-voice single-flight install + no-network status read. */
|
|
41
|
-
export function wireVoiceSetup(deps: VoiceSetupServicesDeps):
|
|
74
|
+
export function wireVoiceSetup(deps: VoiceSetupServicesDeps): VoiceSetupServices {
|
|
42
75
|
const { configManager, voiceProviders } = deps;
|
|
76
|
+
const managedVoiceRoot = deps.shellPaths.resolveUserPath('voice');
|
|
43
77
|
const voiceSetup = createVoiceSetupService({
|
|
44
|
-
managedVoiceRoot
|
|
78
|
+
managedVoiceRoot,
|
|
45
79
|
getConfig: (key) => String(configManager.get(key as Parameters<typeof configManager.get>[0]) ?? ''),
|
|
46
80
|
setConfig: (key, value) => configManager.setDynamic(key as Parameters<typeof configManager.setDynamic>[0], value),
|
|
47
81
|
// A successful (re-)install clears any tripped local-engine breaker.
|
|
48
82
|
resetLocalEngineFailureState: () => voiceProviders.get('local')?.resetEngineFailureState?.(),
|
|
49
83
|
admitExpensiveWork: deps.admitExpensiveWork,
|
|
50
84
|
});
|
|
51
|
-
|
|
85
|
+
if (deps.provisionWakeModelsAtBoot !== true) {
|
|
86
|
+
return { voiceSetup, stopWakeHousekeeping: () => {} };
|
|
87
|
+
}
|
|
88
|
+
// Sweep the wake tree, then fetch whatever the install could not. The SDK owns
|
|
89
|
+
// the policy — never throws, one plain message, reaps before it retries — and
|
|
90
|
+
// routes the attempt through the setup service's single flight so a boot
|
|
91
|
+
// attempt and a user typing /voice wake setup are one download.
|
|
92
|
+
const boot = (deps.startBootProvisioning ?? startWakeBootProvisioning)({
|
|
93
|
+
managedRoot: managedVoiceRoot,
|
|
94
|
+
ensureProvisioned: () => voiceSetup.wakeEnsureProvisioned({ recoveryHint: WAKE_RECOVERY_COMMAND }),
|
|
95
|
+
announce: (message) => { logger.info(message); },
|
|
96
|
+
});
|
|
97
|
+
return { voiceSetup, stopWakeHousekeeping: () => boot.stop() };
|
|
52
98
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* voice-capture-shell.ts — the shell's side of microphone capture.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from main() for the same reason the process-lifecycle wiring was:
|
|
5
|
+
* the entrypoint is a wiring file held under a source-line gate, and new shell
|
|
6
|
+
* composition gets its own module and a single call there.
|
|
7
|
+
*
|
|
8
|
+
* What this owns is only the shell-facing half — where a transcript lands (the
|
|
9
|
+
* composer draft, through the same public `prompt`/`cursorPos` fields the
|
|
10
|
+
* external-editor path writes), how a turn is submitted, which keybinding seam
|
|
11
|
+
* the Alt+V action hangs off, and the teardown registration that guarantees a
|
|
12
|
+
* live recorder subprocess dies with the process. Everything about audio itself
|
|
13
|
+
* lives in src/audio/voice-capture-wiring.ts, which must not (and does not)
|
|
14
|
+
* import shell-UI.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
18
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
19
|
+
import type { ShellPathService } from '@/runtime/index.ts';
|
|
20
|
+
import type { VoiceCaptureIndicatorState } from '../core/voice-capture-status.ts';
|
|
21
|
+
import { wireVoiceCapture } from '../audio/voice-capture-wiring.ts';
|
|
22
|
+
|
|
23
|
+
export interface VoiceCaptureShellDeps {
|
|
24
|
+
readonly configManager: ConfigManager;
|
|
25
|
+
readonly shellPaths: Pick<ShellPathService, 'resolveUserPath'>;
|
|
26
|
+
readonly homeDirectory: string | (() => string);
|
|
27
|
+
/** Names retained wake clips so the SDK's sweeper reaps them when this session ends. */
|
|
28
|
+
readonly sessionId: string;
|
|
29
|
+
/** The Alt+V seam is hung here (commandContext.toggleVoiceInput). */
|
|
30
|
+
readonly commandContext: CommandContext;
|
|
31
|
+
/** The shell's teardown registry; the device release is appended to it. */
|
|
32
|
+
readonly unsubs: Array<() => void>;
|
|
33
|
+
/** The live composer buffer — InputHandler exposes public `prompt`/`cursorPos`. */
|
|
34
|
+
readonly buffer: { prompt: string; cursorPos: number };
|
|
35
|
+
readonly submitInput: (text: string) => void;
|
|
36
|
+
readonly notify: (message: string) => void;
|
|
37
|
+
readonly render: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Compose voice capture and hand back the footer-row reader.
|
|
42
|
+
*
|
|
43
|
+
* Opens no device: wake detection consults `voice.wake.*` and refuses without
|
|
44
|
+
* touching the capture opener when it is off, and push-to-talk opens one only on
|
|
45
|
+
* a keypress.
|
|
46
|
+
*/
|
|
47
|
+
export function installVoiceCapture(deps: VoiceCaptureShellDeps): () => VoiceCaptureIndicatorState | null {
|
|
48
|
+
const capture = wireVoiceCapture({
|
|
49
|
+
configManager: deps.configManager,
|
|
50
|
+
// The same managed root `/voice setup` uses; the wake tree is `<root>/wake`.
|
|
51
|
+
managedVoiceRoot: deps.shellPaths.resolveUserPath('voice'),
|
|
52
|
+
// Surface-scoped: the extracted onnxruntime assets belong to this surface's
|
|
53
|
+
// own directory, beside its other managed state.
|
|
54
|
+
assetDirectory: deps.shellPaths.resolveUserPath('tui', 'onnxruntime'),
|
|
55
|
+
homeDirectory: deps.homeDirectory,
|
|
56
|
+
sessionId: deps.sessionId,
|
|
57
|
+
writeDraft: (text) => {
|
|
58
|
+
deps.buffer.prompt = text;
|
|
59
|
+
deps.buffer.cursorPos = text.length;
|
|
60
|
+
deps.render();
|
|
61
|
+
},
|
|
62
|
+
submitTurn: (text) => deps.submitInput(text),
|
|
63
|
+
notify: deps.notify,
|
|
64
|
+
render: deps.render,
|
|
65
|
+
});
|
|
66
|
+
deps.commandContext.toggleVoiceInput = capture.toggleVoiceInput;
|
|
67
|
+
deps.unsubs.push(...capture.unsubs);
|
|
68
|
+
return capture.status;
|
|
69
|
+
}
|
|
@@ -209,16 +209,16 @@ export const FEATURE_KNOB_LOCAL_SETTINGS = [
|
|
|
209
209
|
* watchers.triggers.onExitMaxDurationMs persistence · live (on-exit trigger duration cap)
|
|
210
210
|
* watchers.triggers.onExitStdin persistence · live (on-exit stdin posture)
|
|
211
211
|
* watchers.triggers.outputTailBytes persistence · live (captured output tail)
|
|
212
|
-
* device.capabilities.allowAlwaysOffer persistence ·
|
|
213
|
-
* device.capabilities.requestTimeoutSeconds persistence ·
|
|
214
|
-
* device.location.precision persistence ·
|
|
215
|
-
* device.clipboard.readMode persistence ·
|
|
216
|
-
* device.capture.retentionHours persistence ·
|
|
217
|
-
* device.capture.maxArtifacts persistence ·
|
|
218
|
-
* device.capture.sweepIntervalMinutes persistence ·
|
|
219
|
-
* device.grants.expiryDays persistence ·
|
|
220
|
-
* device.grants.maxPerNode persistence ·
|
|
221
|
-
* device.grants.auditRetentionDays persistence ·
|
|
212
|
+
* device.capabilities.allowAlwaysOffer persistence · behavior (durable-grant offer by sensitivity)
|
|
213
|
+
* device.capabilities.requestTimeoutSeconds persistence · behavior (dispatch, wire payload and prompt deadline)
|
|
214
|
+
* device.location.precision persistence · behavior (precise fix refused / never granted)
|
|
215
|
+
* device.clipboard.readMode persistence · behavior (clipboard read refused / never granted)
|
|
216
|
+
* device.capture.retentionHours persistence · behavior (capture TTL, swept off disk at expiry)
|
|
217
|
+
* device.capture.maxArtifacts persistence · behavior (capture count cap at the sweep)
|
|
218
|
+
* device.capture.sweepIntervalMinutes persistence · behavior (period of the sweep that reaps)
|
|
219
|
+
* device.grants.expiryDays persistence · behavior (grant TTL, then asks again)
|
|
220
|
+
* device.grants.maxPerNode persistence · behavior (per-node grant cap at the sweep)
|
|
221
|
+
* device.grants.auditRetentionDays persistence · behavior (grant ledger retention)
|
|
222
222
|
* device.nodes.maxPaired persistence · live (enforced at the pairing path)
|
|
223
223
|
*
|
|
224
224
|
* On `device.nodes.maxPaired`: the SDK enforces it where a device pairs —
|
|
@@ -235,17 +235,56 @@ export const FEATURE_KNOB_LOCAL_SETTINGS = [
|
|
|
235
235
|
* in this column so the "how live is it" audit stays accurate, not to claim
|
|
236
236
|
* coverage this repo did not write.
|
|
237
237
|
*
|
|
238
|
-
* On the other ten `device.*` rows:
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
238
|
+
* On the other ten `device.*` rows: they are live now. This app composes the
|
|
239
|
+
* platform's device posture runtime (src/runtime/device-posture-composition.ts)
|
|
240
|
+
* on the same distributed runtime phones pair onto and the same approval broker
|
|
241
|
+
* every other confirmation rides, registers the `phone` tool on it, and binds the
|
|
242
|
+
* devices.* verbs to it — so each of these keys is read, per request and per
|
|
243
|
+
* sweep, by the DeviceCapabilityService / DeviceGrantStore /
|
|
244
|
+
* DeviceCaptureArtifactStore / DeviceHousekeeper that serve them. They were dark
|
|
245
|
+
* before that: the policy struct carried matching defaults and named the keys in
|
|
246
|
+
* its refusal messages, but nothing mapped configuration into it in either tree.
|
|
247
|
+
* The mapping now lives in the SDK, so every daemon host honours it rather than
|
|
248
|
+
* only whichever consumer had written its own.
|
|
244
249
|
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
250
|
+
* The second column above is upgraded to "behavior" on the strength of
|
|
251
|
+
* `src/test/verification/device-posture-behavior.test.ts`, which drives THIS
|
|
252
|
+
* app's composition with a real ConfigManager over a temp home and the platform's
|
|
253
|
+
* real stores, stubs only the peer transport and the approval bridge, and asserts
|
|
254
|
+
* per key at two values that the observable outcome differs — refusal code, how
|
|
255
|
+
* authority was established, what reached the transport, what the prompt was
|
|
256
|
+
* given, and what survives on disk after a sweep. It also invokes the `phone`
|
|
257
|
+
* tool and the devices.* verbs off the composed runtime graph, because a mapping
|
|
258
|
+
* nothing composes is the same defect in a different place.
|
|
259
|
+
*
|
|
260
|
+
* STILL NOT COUNTED, FOR A DIFFERENT REASON NOW: the 24 non-enablement
|
|
261
|
+
* `voice.wake.*` keys from the same release. When this list was written they were
|
|
262
|
+
* excluded because nothing captured audio at all. That is no longer true — this
|
|
263
|
+
* terminal opens a recorder subprocess (src/audio/capture.ts), runs the SDK
|
|
264
|
+
* detector over it through onnxruntime-web (src/audio/wake-inference.ts), and
|
|
265
|
+
* hands both a confirmed wake's utterance and a push-to-talk recording to the
|
|
266
|
+
* daemon's `voice.stt` verb. Every one of those rows is read by
|
|
267
|
+
* resolveWakeRuntimeSettings and reaches this surface's runtime.
|
|
268
|
+
*
|
|
269
|
+
* They stay out of the NUMERATOR here because this constant counts exactly one
|
|
270
|
+
* thing — keys whose four-part PERSISTENCE contract is exercised by
|
|
271
|
+
* device-and-trigger-settings-persistence.test.ts — and that test does not cover
|
|
272
|
+
* them. The behaviour they now drive is covered instead by the capture and wake
|
|
273
|
+
* tests under src/test/audio/, which assert the frame path, the detection
|
|
274
|
+
* chain, the disabled-means-no-capture rule, the supervisor's restart and latch,
|
|
275
|
+
* and the recorder argv. Counting them in both places would double-count; adding
|
|
276
|
+
* them here without writing their persistence round-trip would overclaim. So the
|
|
277
|
+
* number this list raises coverage by is unchanged, and the reason it excludes
|
|
278
|
+
* them is recorded above rather than left reading as "the feature does nothing".
|
|
279
|
+
*
|
|
280
|
+
* Every wake row is now in force on this surface, with one condition stated at
|
|
281
|
+
* every surfacing: `voice.wake.vadThreshold` above 0 needs the speech gate's own
|
|
282
|
+
* pinned artifact provisioned (`/voice wake setup` fetches it with the models),
|
|
283
|
+
* and until it is, startup BLOCKS with that reason rather than scoring frames
|
|
284
|
+
* ungated behind a row that says they are screened. `voice.wake.noiseSuppression:
|
|
285
|
+
* speex` runs here: the filter is a WebAssembly module the SDK carries, applied
|
|
286
|
+
* by the wrapper the listener and the push-to-talk session put around this
|
|
287
|
+
* surface's capture opener.
|
|
249
288
|
*/
|
|
250
289
|
export const DEVICE_AND_TRIGGER_LOCAL_SETTINGS = [
|
|
251
290
|
'watchers.triggers.backoffLadderMs',
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '1.
|
|
9
|
+
let _version = '1.27.0';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
// Only trust a version read from OUR OWN package.json. A Bun single-file
|