@pellux/goodvibes-tui 1.25.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +9605 -3119
  4. package/package.json +9 -7
  5. package/scripts/postinstall.js +57 -1
  6. package/src/audio/activation-sound.ts +117 -0
  7. package/src/audio/capture.ts +112 -0
  8. package/src/audio/onnxruntime-assets.d.ts +24 -0
  9. package/src/audio/voice-capture-wiring.ts +119 -0
  10. package/src/audio/voice-input-session.ts +181 -0
  11. package/src/audio/wake-inference.ts +182 -0
  12. package/src/audio/wake-runtime.ts +341 -0
  13. package/src/cli/bundle-command.ts +4 -1
  14. package/src/cli/help.ts +10 -0
  15. package/src/cli/management-utils.ts +4 -1
  16. package/src/cli/redaction.ts +68 -3
  17. package/src/config/goodvibes-home.ts +10 -0
  18. package/src/config/secret-config.ts +64 -8
  19. package/src/core/turn-event-wiring.ts +7 -16
  20. package/src/core/voice-capture-status.ts +53 -0
  21. package/src/core/voice-stt-gateway.ts +75 -0
  22. package/src/core/wake-provision-runner.ts +86 -0
  23. package/src/core/wake-provision-status.ts +170 -0
  24. package/src/core/workstream-notification.ts +69 -0
  25. package/src/daemon/cli.ts +86 -5
  26. package/src/daemon/handlers/credentials.ts +15 -5
  27. package/src/daemon/provision-wake-model.ts +81 -0
  28. package/src/daemon/send/channels.ts +200 -0
  29. package/src/daemon/send/command.ts +332 -0
  30. package/src/daemon/send/composition.ts +99 -0
  31. package/src/daemon/send/failure-text.ts +93 -0
  32. package/src/daemon/send/inert-text.ts +225 -0
  33. package/src/daemon/send/stdin.ts +24 -0
  34. package/src/input/command-registry.ts +6 -0
  35. package/src/input/commands/config.ts +68 -2
  36. package/src/input/commands/experience-runtime.ts +37 -3
  37. package/src/input/commands/local-provider-runtime.ts +9 -1
  38. package/src/input/commands/operator-runtime.ts +6 -1
  39. package/src/input/commands/payment-card-intake.ts +200 -0
  40. package/src/input/commands/profile-render.ts +250 -0
  41. package/src/input/commands/profile-runtime.ts +362 -0
  42. package/src/input/commands/profile-types.ts +381 -0
  43. package/src/input/commands.ts +7 -0
  44. package/src/input/composer-editor.ts +4 -0
  45. package/src/input/handler-shortcuts.ts +8 -0
  46. package/src/input/keybindings.ts +10 -1
  47. package/src/input/onboarding/onboarding-feature-units.ts +5 -5
  48. package/src/input/onboarding/onboarding-wizard-apply.ts +13 -1
  49. package/src/input/payments-config.ts +167 -0
  50. package/src/input/provider-key-intake.ts +10 -2
  51. package/src/input/settings-modal-commit.ts +146 -0
  52. package/src/input/settings-modal-data.ts +30 -0
  53. package/src/input/settings-modal-reset.ts +6 -2
  54. package/src/input/settings-modal-secrets.ts +9 -2
  55. package/src/input/settings-modal-types.ts +30 -2
  56. package/src/input/settings-modal.ts +27 -107
  57. package/src/main.ts +12 -14
  58. package/src/panels/modals/marketplace-modal.ts +5 -0
  59. package/src/panels/modals/skills-modal.ts +5 -0
  60. package/src/renderer/process-indicator.ts +66 -0
  61. package/src/renderer/settings-modal-helpers.ts +8 -2
  62. package/src/renderer/settings-modal.ts +16 -1
  63. package/src/renderer/shell-surface.ts +22 -3
  64. package/src/runtime/bootstrap-core.ts +7 -7
  65. package/src/runtime/bootstrap.ts +3 -3
  66. package/src/runtime/device-posture-composition.ts +110 -0
  67. package/src/runtime/disposal-wiring.ts +16 -0
  68. package/src/runtime/install-self-check-startup.ts +23 -0
  69. package/src/runtime/onboarding/types.ts +6 -1
  70. package/src/runtime/path-shadow-startup.ts +243 -0
  71. package/src/runtime/path-shadow.ts +418 -0
  72. package/src/runtime/reachability-notice.ts +100 -0
  73. package/src/runtime/runtime-services-types.ts +260 -0
  74. package/src/runtime/services.ts +95 -209
  75. package/src/runtime/voice-setup-services.ts +50 -4
  76. package/src/shell/voice-capture-shell.ts +69 -0
  77. package/src/verification/verification-ledger.ts +163 -24
  78. package/src/version.ts +1 -1
@@ -70,12 +70,20 @@ const SETTINGS_BEHAVIOR_BASELINE = 184;
70
70
  * overlaps neither other counted set, so nothing is double-counted and nothing
71
71
  * drifts in uncounted.
72
72
  *
73
- * PER-KEY EVIDENCE. All 25 are read by a LIVE consumer, which is why they were
73
+ * PER-KEY EVIDENCE. All 42 are read by a LIVE consumer, which is why they were
74
74
  * declared at all: the SDK's mail and calendar gateway compositions
75
75
  * (platform/email/surface-config.ts and platform/calendar/caldav-gateway-config.ts)
76
- * resolve every one of them when the daemon serves `email.*` and `calendar.*`.
77
- * The three password keys additionally resolve through the daemon secret tier
78
- * rather than from config, and carry no secret value in config themselves.
76
+ * resolve every one of them when the daemon serves `email.*` and `calendar.*`,
77
+ * and the `surfaces.email.inbound.*` block is read by the daemon's inbound mail
78
+ * reader — the poller/IDLE path that makes incoming mail visible at all.
79
+ * The five password keys (email.password, email.imapPassword,
80
+ * email.imap.password, email.smtp.password, calendar.caldavPassword)
81
+ * additionally resolve through the daemon secret tier rather than from config,
82
+ * and carry no secret value in config themselves. That is now enforced on the
83
+ * write side too: all five are in `config/secret-config.ts`'s
84
+ * SECRET_CONFIG_KEYS, so the settings modal and `/config set` route an entered
85
+ * value into the secret store and leave only a `goodvibes://` reference in
86
+ * config, rather than writing the password itself into a settings file.
79
87
  */
80
88
  export const DAEMON_MAILBOX_LOCAL_SETTINGS = [
81
89
  'surfaces.email.host',
@@ -98,6 +106,31 @@ export const DAEMON_MAILBOX_LOCAL_SETTINGS = [
98
106
  'surfaces.email.smtp.port',
99
107
  'surfaces.email.smtp.password',
100
108
  'surfaces.email.smtp.secure',
109
+ // Inbound mail — the poller/IDLE reader the daemon runs to actually READ mail,
110
+ // which arrived with the platform runtime. These are counted for exactly the
111
+ // reason the keys above are: the same four-part persistence contract runs over
112
+ // every one of them in
113
+ // src/test/verification/daemon-mailbox-settings-persistence.test.ts, and the
114
+ // list is asserted there to be the complete surfaces.email.*/surfaces.calendar.*
115
+ // schema set. Arriving uncounted is what dropped localBehaviorPercent through
116
+ // its floor when the schema grew.
117
+ 'surfaces.email.inbound.enabled',
118
+ 'surfaces.email.inbound.accounts',
119
+ 'surfaces.email.inbound.source',
120
+ 'surfaces.email.inbound.gmailPollSecondsExpecting',
121
+ 'surfaces.email.inbound.gmailPollSecondsIdle',
122
+ 'surfaces.email.inbound.mode',
123
+ 'surfaces.email.inbound.pollIntervalSeconds',
124
+ 'surfaces.email.inbound.idleReissueMinutes',
125
+ 'surfaces.email.inbound.reconnect.maxBackoffSeconds',
126
+ 'surfaces.email.inbound.notice.route',
127
+ 'surfaces.email.inbound.notice.mode',
128
+ 'surfaces.email.inbound.expectationWindowMinutes',
129
+ 'surfaces.email.inbound.dedupTtlMinutes',
130
+ 'surfaces.email.inbound.retentionDays',
131
+ 'surfaces.email.inbound.maxRecords',
132
+ 'surfaces.email.inbound.capabilityRecheckMinutes',
133
+ 'surfaces.email.inbound.onInsufficientCapability',
101
134
  'surfaces.calendar.caldavUrl',
102
135
  'surfaces.calendar.caldavUser',
103
136
  'surfaces.calendar.caldavPassword',
@@ -176,16 +209,16 @@ export const FEATURE_KNOB_LOCAL_SETTINGS = [
176
209
  * watchers.triggers.onExitMaxDurationMs persistence · live (on-exit trigger duration cap)
177
210
  * watchers.triggers.onExitStdin persistence · live (on-exit stdin posture)
178
211
  * watchers.triggers.outputTailBytes persistence · live (captured output tail)
179
- * device.capabilities.allowAlwaysOffer persistence · settings surface only in this build
180
- * device.capabilities.requestTimeoutSeconds persistence · settings surface only in this build
181
- * device.location.precision persistence · settings surface only in this build
182
- * device.clipboard.readMode persistence · settings surface only in this build
183
- * device.capture.retentionHours persistence · settings surface only in this build
184
- * device.capture.maxArtifacts persistence · settings surface only in this build
185
- * device.capture.sweepIntervalMinutes persistence · settings surface only in this build
186
- * device.grants.expiryDays persistence · settings surface only in this build
187
- * device.grants.maxPerNode persistence · settings surface only in this build
188
- * device.grants.auditRetentionDays persistence · settings surface only in this build
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)
189
222
  * device.nodes.maxPaired persistence · live (enforced at the pairing path)
190
223
  *
191
224
  * On `device.nodes.maxPaired`: the SDK enforces it where a device pairs —
@@ -202,17 +235,56 @@ export const FEATURE_KNOB_LOCAL_SETTINGS = [
202
235
  * in this column so the "how live is it" audit stays accurate, not to claim
203
236
  * coverage this repo did not write.
204
237
  *
205
- * On the other ten `device.*` rows: DeviceCapabilityService carries its own
206
- * policy struct whose defaults match those keys one for one, and names them in
207
- * its field comments and refusal messages, but no code path in either tree maps
208
- * configuration into that struct. Persistence is therefore the whole of what is
209
- * verified for them, and the settings-workspace description for the `device`
210
- * category says exactly that to the user rather than implying live knobs.
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.
211
249
  *
212
- * DELIBERATELY NOT COUNTED: the 24 non-enablement `voice.wake.*` keys from the
213
- * same release. `wake-word-detection` is declared `notOperable` — no surface
214
- * captures audio — so its rows stay in `total` and out of the numerator. That is
215
- * the honest reading, and it is why this list raises coverage by 29 and not 53.
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.
216
288
  */
217
289
  export const DEVICE_AND_TRIGGER_LOCAL_SETTINGS = [
218
290
  'watchers.triggers.backoffLadderMs',
@@ -300,11 +372,78 @@ export const CLUSTER_GROUP_LOCAL_SETTINGS = [
300
372
  'cluster.rosterGossipSeconds',
301
373
  ] as const;
302
374
 
375
+ /**
376
+ * Config keys for the daemon's payment capability: the master switch, which
377
+ * card to use by default, the settlement currency, CVV handling, the six
378
+ * budget knobs, the shipping-tier preference, the fourteen billing/shipping
379
+ * address sub-fields, the two approval/veto windows, and the notify-channel
380
+ * list. Card MATERIAL (number, expiry, CVV, cardholder name) is intentionally
381
+ * NOT here — see the SDK's schema-domain-payments.ts header and this repo's
382
+ * own input/payments-config.ts: that material lives write-only in the daemon
383
+ * secret store, never in CONFIG_SCHEMA, so there is nothing for a persistence
384
+ * contract to count.
385
+ *
386
+ * THIS CONSTANT IS NOT A DIAL, for the same reason as the sets above. Every
387
+ * key below is counted because
388
+ * `src/test/verification/payments-settings-persistence.test.ts` runs the same
389
+ * four-part persistence contract the other counted sets are counted for:
390
+ * schema default exposure, `set()` through the key's own validator to disk,
391
+ * reload into a fresh ConfigManager with read-back equality, and
392
+ * reset-to-default that also survives reload. That test also asserts this
393
+ * list is exactly the CONFIG_SCHEMA's `payments.*` key set, so a future SDK
394
+ * addition under this domain is caught (fails the inventory-integrity test)
395
+ * rather than silently uncounted.
396
+ *
397
+ * PER-KEY EVIDENCE. All 28 are read by the daemon's own payment capability
398
+ * (platform/payments, platform/control-plane/routes/payments.ts): the budget
399
+ * keys size the daily/overage/tolerance pools, the window keys size the
400
+ * veto/approval timers, cvvHandling and shipping.preferredTier select the
401
+ * enum behaviors `platform/payments` documents, and the address sub-fields
402
+ * are read directly by the daemon's shipping-address-required refusal and the
403
+ * card-issuer address-verification path. `enabled` and `defaultCardId` gate
404
+ * and select, respectively, whether/which card a purchase uses.
405
+ */
406
+ export const PAYMENTS_LOCAL_SETTINGS = [
407
+ 'payments.enabled',
408
+ 'payments.defaultCardId',
409
+ 'payments.currency',
410
+ 'payments.cvvHandling',
411
+ 'payments.budget.dailyItemCents',
412
+ 'payments.budget.dailyOverageCents',
413
+ 'payments.budget.perPurchaseCeilingEnabled',
414
+ 'payments.budget.perPurchaseCeilingCents',
415
+ 'payments.budget.overageToleranceEnabled',
416
+ 'payments.budget.overageToleranceDailyAllowanceCents',
417
+ 'payments.shipping.preferredTier',
418
+ 'payments.billingAddress.name',
419
+ 'payments.billingAddress.line1',
420
+ 'payments.billingAddress.line2',
421
+ 'payments.billingAddress.city',
422
+ 'payments.billingAddress.region',
423
+ 'payments.billingAddress.postalCode',
424
+ 'payments.billingAddress.country',
425
+ 'payments.shippingAddress.name',
426
+ 'payments.shippingAddress.line1',
427
+ 'payments.shippingAddress.line2',
428
+ 'payments.shippingAddress.city',
429
+ 'payments.shippingAddress.region',
430
+ 'payments.shippingAddress.postalCode',
431
+ 'payments.shippingAddress.country',
432
+ 'payments.windows.vetoMinutes',
433
+ 'payments.windows.approvalMinutes',
434
+ 'payments.majorRetailersAdditional',
435
+ 'payments.majorRetailersExcluded',
436
+ 'payments.ebayMinSellerFeedbackCount',
437
+ 'payments.ebayMinSellerPositivePercent',
438
+ 'payments.notifyChannels',
439
+ ] as const;
440
+
303
441
  const SETTINGS_BEHAVIOR_VERIFIED = SETTINGS_BEHAVIOR_BASELINE
304
442
  + FEATURE_KNOB_LOCAL_SETTINGS.length
305
443
  + DEVICE_AND_TRIGGER_LOCAL_SETTINGS.length
306
444
  + CLUSTER_GROUP_LOCAL_SETTINGS.length
307
445
  + DAEMON_MAILBOX_LOCAL_SETTINGS.length
446
+ + PAYMENTS_LOCAL_SETTINGS.length
308
447
  + ENABLEMENT_KEYS_BEHAVIOR_VERIFIED;
309
448
 
310
449
  const EXTERNAL_SLASH_COMMANDS = new Set([
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.25.0';
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