@pellux/goodvibes-daemon 1.28.19 → 1.28.21
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/CHANGELOG.md +119 -63
- package/README.md +24 -19
- package/bin/launcher-support.js +5 -5
- package/package.json +6 -4
- package/scripts/postinstall.js +8 -8
- package/src/cli/command-catalog.ts +22 -22
- package/src/cli/completion.ts +4 -4
- package/src/cli/help.ts +5 -5
- package/src/cli/index.ts +3 -3
- package/src/cli/parser.ts +2 -2
- package/src/cli/surface-catalog.ts +1 -1
- package/src/cli/types.ts +2 -2
- package/src/cluster/daemon-ws-call.ts +5 -5
- package/src/cluster/raw-reply-route.ts +5 -5
- package/src/config/checkpoint-settings.ts +7 -7
- package/src/config/config-key-guard.ts +22 -0
- package/src/config/run-daemon-config-migration.ts +3 -3
- package/src/config/secret-config.ts +7 -7
- package/src/config/surface.ts +3 -3
- package/src/core/pairing-banner.ts +5 -5
- package/src/daemon/cli.ts +45 -43
- package/src/daemon/config-command.ts +15 -15
- package/src/daemon/handlers/context.ts +1 -1
- package/src/daemon/handlers/contracts.ts +19 -4
- package/src/daemon/handlers/credentials.ts +1 -1
- package/src/daemon/handlers/drafts/draft-store.ts +3 -3
- package/src/daemon/handlers/drafts/register.ts +4 -4
- package/src/daemon/handlers/inbox/aggregator.ts +8 -8
- package/src/daemon/handlers/inbox/cursor-store.ts +10 -10
- package/src/daemon/handlers/inbox/index.ts +7 -7
- package/src/daemon/handlers/inbox/mapping.ts +2 -2
- package/src/daemon/handlers/inbox/poller.ts +5 -5
- package/src/daemon/handlers/inbox/provider-adapter.ts +8 -8
- package/src/daemon/handlers/inbox/providers/discord.ts +6 -6
- package/src/daemon/handlers/inbox/providers/email.ts +3 -3
- package/src/daemon/handlers/inbox/providers/imap-client.ts +1 -1
- package/src/daemon/handlers/inbox/providers/slack.ts +4 -4
- package/src/daemon/handlers/index.ts +18 -8
- package/src/daemon/handlers/payments/address-store.ts +54 -0
- package/src/daemon/handlers/payments/budget-store.ts +356 -0
- package/src/daemon/handlers/payments/card-store.ts +486 -0
- package/src/daemon/handlers/payments/checkout-handlers.ts +526 -0
- package/src/daemon/handlers/payments/index.ts +38 -0
- package/src/daemon/handlers/payments/merchant-judge.ts +57 -0
- package/src/daemon/handlers/payments/notifier.ts +112 -0
- package/src/daemon/handlers/payments/purchase-ledger.ts +108 -0
- package/src/daemon/handlers/payments/register.ts +518 -0
- package/src/daemon/handlers/register.ts +3 -3
- package/src/daemon/handlers/remote/backends/cloud-terminal.ts +9 -1
- package/src/daemon/handlers/remote/backends/process-runner.ts +1 -1
- package/src/daemon/handlers/remote/backends/ssh.ts +9 -1
- package/src/daemon/handlers/remote/backends/types.ts +2 -2
- package/src/daemon/handlers/remote/dispatcher.ts +3 -3
- package/src/daemon/handlers/remote/index.ts +1 -1
- package/src/daemon/handlers/remote/peer-registry.ts +62 -13
- package/src/daemon/handlers/routing/inbox-bridge.ts +5 -5
- package/src/daemon/handlers/routing/index.ts +1 -1
- package/src/daemon/handlers/routing/route-store.ts +1 -1
- package/src/daemon/handlers/routing/routing-resolver.ts +3 -3
- package/src/daemon/handlers/sqlite-store.ts +9 -9
- package/src/daemon/handlers/triage/index.ts +1 -1
- package/src/daemon/handlers/triage/integration.ts +3 -3
- package/src/daemon/handlers/triage/pipeline.ts +2 -2
- package/src/daemon/handlers/triage/scorer.ts +2 -2
- package/src/daemon/handlers/triage/tagger/discord.ts +3 -3
- package/src/daemon/handlers/triage/tagger/imap.ts +7 -7
- package/src/daemon/handlers/triage/tagger/index.ts +1 -1
- package/src/daemon/handlers/triage/tagger/shared.ts +3 -3
- package/src/daemon/handlers/triage/tagger/slack.ts +1 -1
- package/src/daemon/handlers/triage/types.ts +2 -2
- package/src/daemon/lifecycle.ts +5 -5
- package/src/daemon/local-daemon-state.ts +7 -7
- package/src/daemon/pair-command.ts +14 -14
- package/src/daemon/provision-wake-model.ts +5 -5
- package/src/daemon/send/channels.ts +7 -7
- package/src/daemon/send/command.ts +11 -11
- package/src/daemon/send/composition.ts +5 -5
- package/src/daemon/send/failure-text.ts +6 -6
- package/src/daemon/send/inert-text.ts +18 -18
- package/src/daemon/send/stdin.ts +3 -3
- package/src/daemon/service-commands.ts +32 -32
- package/src/daemon/sessions-command.ts +7 -7
- package/src/daemon/status-command.ts +22 -22
- package/src/daemon/webui-command.ts +14 -14
- package/src/runtime/boot-tasks.ts +1 -1
- package/src/runtime/browser-checkout-seam-holder.ts +55 -0
- package/src/runtime/cluster-composition.ts +9 -9
- package/src/runtime/cluster-group-composition.ts +7 -7
- package/src/runtime/conversation-rewind-port.ts +8 -8
- package/src/runtime/credential-composition.ts +2 -2
- package/src/runtime/daemon-handler-composition.ts +61 -4
- package/src/runtime/device-posture-composition.ts +10 -10
- package/src/runtime/disposal-wiring.ts +8 -8
- package/src/runtime/fleet-needs-input-push.ts +4 -4
- package/src/runtime/fleet-services.ts +1 -1
- package/src/runtime/hosted-session-composition.ts +13 -13
- package/src/runtime/index.ts +1 -1
- package/src/runtime/knowledge-services.ts +2 -2
- package/src/runtime/legacy-daemon-migration.ts +43 -43
- package/src/runtime/legacy-daemon-reconcile.ts +30 -30
- package/src/runtime/mail-composition.ts +6 -6
- package/src/runtime/notification-dispatch.ts +7 -7
- package/src/runtime/payments-composition.ts +187 -0
- package/src/runtime/plugin-composition.ts +7 -7
- package/src/runtime/runtime-services-types.ts +9 -9
- package/src/runtime/services.ts +41 -32
- package/src/runtime/trigger-services.ts +1 -1
- package/src/runtime/trust/checkpoint-eligibility.ts +5 -5
- package/src/runtime/trust/trust-gated-approvals.ts +9 -9
- package/src/runtime/update-check.ts +4 -4
- package/src/runtime/workspace-checkpointing.ts +6 -6
- package/src/testing/daemon-fixture.ts +11 -11
- package/src/testing/hosted-session-failures.ts +4 -4
- package/src/version.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-daemon",
|
|
3
|
-
"version": "1.28.
|
|
3
|
+
"version": "1.28.21",
|
|
4
4
|
"description": "The GoodVibes daemon \u2014 the one long-running host for the control plane, channels, cluster membership, scheduled work, knowledge and memory stores, and the verb families every GoodVibes client calls.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/daemon/cli.ts",
|
|
@@ -32,10 +32,12 @@
|
|
|
32
32
|
"typecheck": "bunx tsc --noEmit",
|
|
33
33
|
"typecheck:test": "bunx tsc --noEmit -p tsconfig.test.json",
|
|
34
34
|
"test": "bun run scripts/run-tests.ts",
|
|
35
|
+
"test:coverage": "bun run scripts/coverage-gate.ts",
|
|
35
36
|
"preinstall": "sh scripts/check-bun.sh",
|
|
36
37
|
"postinstall": "bun scripts/postinstall.js",
|
|
37
38
|
"postbuild": "bun scripts/postinstall.js --no-download",
|
|
38
39
|
"version": "bun run scripts/prebuild.ts",
|
|
40
|
+
"architecture:check": "bun run scripts/check-architecture.ts",
|
|
39
41
|
"workflows:check": "bun run scripts/check-workflows.ts",
|
|
40
42
|
"changelog:check": "bun run scripts/check-changelog.ts",
|
|
41
43
|
"verify:tag-version": "bun run scripts/verify-release-tag-version.ts",
|
|
@@ -65,8 +67,8 @@
|
|
|
65
67
|
"@anthropic-ai/bedrock-sdk": "^0.28.1",
|
|
66
68
|
"@anthropic-ai/sdk": "^0.82.0",
|
|
67
69
|
"@ast-grep/napi": "^0.42.0",
|
|
68
|
-
"@pellux/goodvibes-sdk": "2.0.
|
|
69
|
-
"@pellux/goodvibes-terminal-shell": "2.0.
|
|
70
|
+
"@pellux/goodvibes-sdk": "2.0.19",
|
|
71
|
+
"@pellux/goodvibes-terminal-shell": "2.0.19",
|
|
70
72
|
"bash-language-server": "^5.6.0",
|
|
71
73
|
"fuse.js": "^7.1.0",
|
|
72
74
|
"graphql": "^16.13.2",
|
|
@@ -87,7 +89,7 @@
|
|
|
87
89
|
"web-tree-sitter": "^0.26.7"
|
|
88
90
|
},
|
|
89
91
|
"devDependencies": {
|
|
90
|
-
"@pellux/goodvibes-toolchain": "2.0.
|
|
92
|
+
"@pellux/goodvibes-toolchain": "2.0.19",
|
|
91
93
|
"@types/bun": "^1.3.10",
|
|
92
94
|
"typescript": "^5.9.3"
|
|
93
95
|
},
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* postinstall
|
|
3
|
+
* postinstall, place the compiled daemon binary AND the sqlite-vec native
|
|
4
4
|
* addon for this platform.
|
|
5
5
|
*
|
|
6
6
|
* The npm package carries source and the launcher; the daemon itself is a
|
|
@@ -11,22 +11,22 @@
|
|
|
11
11
|
* The sqlite-vec addon gets the identical treatment for the identical reason:
|
|
12
12
|
* `resolveSqliteVecPath()` (platform/state/sqlite-vec-loader.ts, reached from a
|
|
13
13
|
* compiled binary) looks for it at `<execDir>/lib/sqlite-vec-<platform>-<arch>/
|
|
14
|
-
* vec0.<suffix
|
|
15
|
-
* placed the binary there
|
|
14
|
+
* vec0.<suffix>`, `<execDir>` being vendor/ once bin/goodvibes-daemon has
|
|
15
|
+
* placed the binary there, and nothing else stages it for an npm install. Skip
|
|
16
16
|
* this and the addon is silently absent forever: the launcher's self-heal
|
|
17
17
|
* (bin/launcher-support.js) only ever re-fetches the BINARY, and the daemon's
|
|
18
18
|
* own auto-updater only refreshes the addon if a copy already exists on disk
|
|
19
19
|
* (there is never a first one to refresh). The daemon degrades to lexical
|
|
20
20
|
* search plus a log warning rather than failing loudly, so the loss is easy to
|
|
21
|
-
* miss
|
|
21
|
+
* miss, this download is what gives every npm install the same vector search
|
|
22
22
|
* a curl/install.sh install gets.
|
|
23
23
|
*
|
|
24
24
|
* What it deliberately does NOT do: deploy skills, deploy agents, or fetch the
|
|
25
25
|
* wake-word model. Skills and agents are surface artifacts that ship with the
|
|
26
26
|
* terminal app's own package, not this one. The wake-word model is fetched by
|
|
27
|
-
* the daemon itself
|
|
27
|
+
* the daemon itself, the installer
|
|
28
28
|
* runs `goodvibes-daemon provision-wake-model` on the placed binary, and every
|
|
29
|
-
* daemon start retries whatever is still missing
|
|
29
|
+
* daemon start retries whatever is still missing, so pulling it here as well
|
|
30
30
|
* would be a second copy of a pin that already has one owner.
|
|
31
31
|
*
|
|
32
32
|
* A source checkout is skipped: a repository clone is a development tree, not an
|
|
@@ -152,7 +152,7 @@ async function installPlatformBinary() {
|
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
154
|
* Places the sqlite-vec native addon at `vendor/lib/sqlite-vec-<platform>-
|
|
155
|
-
* <arch>/vec0.<suffix
|
|
155
|
+
* <arch>/vec0.<suffix>`, see the file banner for why this is a separate,
|
|
156
156
|
* equally load-bearing step from `installPlatformBinary`, not an optional
|
|
157
157
|
* extra. Mirrors that function's gating and verification exactly (skip on an
|
|
158
158
|
* unsupported target, `--no-download`, or a source checkout; the smoke-test
|
|
@@ -197,7 +197,7 @@ async function installSqliteVecAddon() {
|
|
|
197
197
|
|
|
198
198
|
// A fresh manifest fetch (rather than sharing the one `installPlatformBinary`
|
|
199
199
|
// already wrote to vendor/) keeps this function independently correct and
|
|
200
|
-
// testable
|
|
200
|
+
// testable, the extra request is a one-time postinstall cost, not a
|
|
201
201
|
// per-boot one.
|
|
202
202
|
const checksumText = await downloadText(`${releaseBaseUrl}/${CHECKSUM_MANIFEST_NAME}`);
|
|
203
203
|
const checksums = parseChecksumFile(checksumText);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* command-catalog.ts
|
|
2
|
+
* command-catalog.ts, WHAT the daemon binary understands.
|
|
3
3
|
*
|
|
4
4
|
* This file is data. It holds no parsing logic and reads no argv. The engine is
|
|
5
5
|
* `parseWithCatalog` in @pellux/goodvibes-terminal-shell: it knows tokens,
|
|
@@ -90,8 +90,8 @@ export type DaemonCliFlagField =
|
|
|
90
90
|
* How a flag consumes argv, and what shape its value has.
|
|
91
91
|
*
|
|
92
92
|
* Four of the engine's seven kinds. The other three (`string-optional`,
|
|
93
|
-
* `const`, `enum`) exist for a conversation-shaped vocabulary
|
|
94
|
-
* `--resume [id]`, two flags writing one field, a checked value set
|
|
93
|
+
* `const`, `enum`) exist for a conversation-shaped vocabulary, an optional
|
|
94
|
+
* `--resume [id]`, two flags writing one field, a checked value set, and this
|
|
95
95
|
* binary declares none of them.
|
|
96
96
|
*/
|
|
97
97
|
export type DaemonCliFlagKind = Extract<CliFlagKind, 'boolean' | 'string' | 'port' | 'string-list'>;
|
|
@@ -112,8 +112,8 @@ export type DaemonCommandFlagSpec = CommandFlagSpec<DaemonCliFlagField> & {
|
|
|
112
112
|
/**
|
|
113
113
|
* A command entry, narrowed the same way.
|
|
114
114
|
*
|
|
115
|
-
* `summary`, `usage` and `detail` are optional to the engine
|
|
116
|
-
* no help surface of its own may omit them
|
|
115
|
+
* `summary`, `usage` and `detail` are optional to the engine, a catalog with
|
|
116
|
+
* no help surface of its own may omit them, and required here, because this
|
|
117
117
|
* binary has a `help <command>` page for every command it answers to.
|
|
118
118
|
*/
|
|
119
119
|
export type DaemonCommandSpec = CommandSpec<DaemonCommand, DaemonCliFlagField> & {
|
|
@@ -167,7 +167,7 @@ const YES_FLAG: DaemonCommandFlagSpec = {
|
|
|
167
167
|
* This is the convention @pellux/goodvibes-terminal-shell's
|
|
168
168
|
* cluster-remote-daemon-target established and asked later subcommands to
|
|
169
169
|
* follow: `--host`/`--port`/`--token`, each
|
|
170
|
-
* defaulting to this machine's own daemon
|
|
170
|
+
* defaulting to this machine's own daemon, the configured control-plane
|
|
171
171
|
* binding and the operator token in `<daemon home>/operator-tokens.json`. A
|
|
172
172
|
* headless box the operator has SSHed into must work with no flags at all.
|
|
173
173
|
*/
|
|
@@ -251,13 +251,13 @@ const SERVE_FLAGS: readonly DaemonCommandFlagSpec[] = [
|
|
|
251
251
|
* Flags this binary once accepted in silence, without acting on them.
|
|
252
252
|
*
|
|
253
253
|
* Every one of them means "start or resume a conversation", which this binary
|
|
254
|
-
* does not do
|
|
254
|
+
* does not do, and each was accepted, stored in a flag record nothing read,
|
|
255
255
|
* and then ignored. `goodvibes-daemon --resume` started a fresh foreground
|
|
256
256
|
* daemon and said nothing about the flag. They are refused by name so the
|
|
257
257
|
* message names the surface that does own them.
|
|
258
258
|
*
|
|
259
259
|
* `reason` is a NOUN PHRASE the engine drops into
|
|
260
|
-
* "<flag> is not a <binary> flag
|
|
260
|
+
* "<flag> is not a <binary> flag, <reason> belongs to another surface.", so
|
|
261
261
|
* each one names the terminal app as well as the job, and the finished sentence
|
|
262
262
|
* points at where the flag actually works.
|
|
263
263
|
*
|
|
@@ -265,7 +265,7 @@ const SERVE_FLAGS: readonly DaemonCommandFlagSpec[] = [
|
|
|
265
265
|
* to skip a refused flag's VALUE while hunting for the command word, or
|
|
266
266
|
* `--prompt hello` reports "Unknown command: hello" instead of naming the flag
|
|
267
267
|
* that is actually wrong. `--resume` and `--fork` took an OPTIONAL value, so
|
|
268
|
-
* they are listed as taking none
|
|
268
|
+
* they are listed as taking none, over-skipping would swallow a real command
|
|
269
269
|
* word.
|
|
270
270
|
*/
|
|
271
271
|
export type { RejectedFlagSpec };
|
|
@@ -426,7 +426,7 @@ export const DAEMON_COMMANDS: readonly DaemonCommandSpec[] = [
|
|
|
426
426
|
summary: 'Restart the daemon service.',
|
|
427
427
|
usage: 'goodvibes-daemon restart-service',
|
|
428
428
|
detail: [
|
|
429
|
-
'Restart the service this binary manages
|
|
429
|
+
'Restart the service this binary manages: the usual way to pick up a settings',
|
|
430
430
|
'change that only applies at boot.',
|
|
431
431
|
...SERVICE_DETAIL_TAIL,
|
|
432
432
|
],
|
|
@@ -465,7 +465,7 @@ export const DAEMON_COMMANDS: readonly DaemonCommandSpec[] = [
|
|
|
465
465
|
summary: 'Print the pairing link and QR code again, or mint one on a remote daemon.',
|
|
466
466
|
usage: 'goodvibes-daemon pair [--json] [--host <name>] [--port <n>] [--token <t>] [-y]',
|
|
467
467
|
detail: [
|
|
468
|
-
'LOCAL FORM
|
|
468
|
+
'LOCAL FORM (no --host, or one naming this machine): print the same pairing',
|
|
469
469
|
'block a daemon prints once at startup: the web origin, the offers a new',
|
|
470
470
|
'device can accept, what it will be able to do, and a QR code encoding the',
|
|
471
471
|
'deep link that opens the web app already signed in.',
|
|
@@ -474,10 +474,10 @@ export const DAEMON_COMMANDS: readonly DaemonCommandSpec[] = [
|
|
|
474
474
|
'printed here and one printed at boot are the same link. Scrolling the startup',
|
|
475
475
|
'banner off the screen therefore costs nothing.',
|
|
476
476
|
'',
|
|
477
|
-
'REMOTE FORM
|
|
477
|
+
'REMOTE FORM (--host naming another machine): ask THAT daemon to MINT A NEW',
|
|
478
478
|
'per-device pairing token and print the pairing block for it. Minting is a',
|
|
479
479
|
'different act than reprinting: it is a fresh token, and every token that',
|
|
480
|
-
'daemon already issued
|
|
480
|
+
'daemon already issued, its shared token included, is left untouched.',
|
|
481
481
|
'',
|
|
482
482
|
'Because it changes state on a daemon that may not be this process\'s own, it',
|
|
483
483
|
'states the plan and asks for confirmation before acting: -y (or --yes) is the',
|
|
@@ -507,7 +507,7 @@ export const DAEMON_COMMANDS: readonly DaemonCommandSpec[] = [
|
|
|
507
507
|
'--all includes sessions that have already ended; they are kept, with the reason',
|
|
508
508
|
'they ended, until the retention window retires them.',
|
|
509
509
|
'',
|
|
510
|
-
'These are the daemon\'s own hosted sessions
|
|
510
|
+
'These are the daemon\'s own hosted sessions: conversations running INSIDE it,',
|
|
511
511
|
'which outlive the client that started them. Sessions a terminal runs on this',
|
|
512
512
|
'machine are that terminal\'s, and are not listed here.',
|
|
513
513
|
],
|
|
@@ -536,7 +536,7 @@ export const DAEMON_COMMANDS: readonly DaemonCommandSpec[] = [
|
|
|
536
536
|
'works whether or not a daemon is running. A running daemon picks up most',
|
|
537
537
|
'changes live; the ones that only apply at bind time say so.',
|
|
538
538
|
'',
|
|
539
|
-
'Anything that reads like a credential
|
|
539
|
+
'Anything that reads like a credential (a token, a password, an API key) is',
|
|
540
540
|
'printed as <redacted>. `config set` still writes the real value; it is the',
|
|
541
541
|
'OUTPUT that is redacted, so a settings dump pasted into an issue carries none.',
|
|
542
542
|
],
|
|
@@ -627,7 +627,7 @@ export const DAEMON_COMMANDS: readonly DaemonCommandSpec[] = [
|
|
|
627
627
|
usage: 'goodvibes-daemon webui enable|disable|status [--bundle-dir <dir>] [--lan|--loopback]',
|
|
628
628
|
detail: [
|
|
629
629
|
'The web UI is a built bundle of static files served by the daemon\'s own',
|
|
630
|
-
'control-plane listener, on the same origin as the API
|
|
630
|
+
'control-plane listener, on the same origin as the API; so the URL to open is',
|
|
631
631
|
'the control-plane one, not the declared web port.',
|
|
632
632
|
'',
|
|
633
633
|
' enable [--bundle-dir <dir>] serve the bundle at that directory',
|
|
@@ -652,7 +652,7 @@ export const DAEMON_COMMANDS: readonly DaemonCommandSpec[] = [
|
|
|
652
652
|
'',
|
|
653
653
|
'The installer runs this on a binary it has just placed, and a daemon start',
|
|
654
654
|
'retries it, so an install that happened offline heals on its own. A download',
|
|
655
|
-
'that fails is reported and exits 0
|
|
655
|
+
'that fails is reported and exits 0; a machine with no wake word still has a',
|
|
656
656
|
'perfectly good daemon.',
|
|
657
657
|
],
|
|
658
658
|
flags: [],
|
|
@@ -667,7 +667,7 @@ export const DAEMON_COMMANDS: readonly DaemonCommandSpec[] = [
|
|
|
667
667
|
detail: [
|
|
668
668
|
'Print a completion script for the named shell on stdout. It completes this',
|
|
669
669
|
'binary\'s commands, their sub-words and their flags, generated from the same',
|
|
670
|
-
'catalog the parser and the help text use
|
|
670
|
+
'catalog the parser and the help text use; so it cannot drift from what the',
|
|
671
671
|
'binary accepts.',
|
|
672
672
|
'',
|
|
673
673
|
'Install it by writing it somewhere the shell reads, for example:',
|
|
@@ -715,7 +715,7 @@ export const RAW_INTERCEPT_COMMANDS: readonly DaemonCommand[] = DAEMON_COMMANDS
|
|
|
715
715
|
.map((spec) => spec.name);
|
|
716
716
|
|
|
717
717
|
/**
|
|
718
|
-
* The flag record a parse starts from
|
|
718
|
+
* The flag record a parse starts from, every field at its empty value, so a
|
|
719
719
|
* command's dispatcher reads only what its own catalog entry declares.
|
|
720
720
|
*/
|
|
721
721
|
function createDefaultFlags(): DaemonCliFlags {
|
|
@@ -817,12 +817,12 @@ export function isRawInterceptCommand(command: DaemonCommand): boolean {
|
|
|
817
817
|
* The engine needs this BEFORE it knows which command it is parsing: to find
|
|
818
818
|
* the command word it has to skip over option values, and whether a token
|
|
819
819
|
* takes a value is a property of the token. Every token that appears in more
|
|
820
|
-
* than one command's flag list has the same kind in all of them
|
|
821
|
-
* a unit test rather than left as an assumption
|
|
820
|
+
* than one command's flag list has the same kind in all of them, asserted by
|
|
821
|
+
* a unit test rather than left as an assumption, so one table is honest.
|
|
822
822
|
*/
|
|
823
823
|
// Resolved on first use, not at module load: `catalogFlagArity` is an SDK
|
|
824
824
|
// import, and the single-file compiler's nondeterministic module order could
|
|
825
|
-
// run this line before the SDK module body exists
|
|
825
|
+
// run this line before the SDK module body exists, the binary then dies at
|
|
826
826
|
// load (the build-order lottery class fixed at runtime 2.0.13).
|
|
827
827
|
let allFlagArityCache: ReadonlyMap<string, CliFlagKind> | null = null;
|
|
828
828
|
export function allFlagArity(): ReadonlyMap<string, CliFlagKind> {
|
package/src/cli/completion.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* completion.ts
|
|
2
|
+
* completion.ts, shell completion generated from the command catalog.
|
|
3
3
|
*
|
|
4
4
|
* The scripts are DERIVED, never hand-maintained: a command added to
|
|
5
5
|
* `./command-catalog.ts` is completable the moment it exists, and one removed
|
|
@@ -63,7 +63,7 @@ function bashScript(binary: string): string {
|
|
|
63
63
|
}).join('\n');
|
|
64
64
|
|
|
65
65
|
return [
|
|
66
|
-
`# bash completion for ${binary}
|
|
66
|
+
`# bash completion for ${binary}, generated from its command catalog.`,
|
|
67
67
|
`# Install: ${binary} completion bash > ~/.local/share/bash-completion/completions/${binary}`,
|
|
68
68
|
'',
|
|
69
69
|
`${fn}() {`,
|
|
@@ -144,7 +144,7 @@ function zshScript(binary: string): string {
|
|
|
144
144
|
const fn = `_${shellIdent(binary)}`;
|
|
145
145
|
return [
|
|
146
146
|
`#compdef ${binary}`,
|
|
147
|
-
`# zsh completion for ${binary}
|
|
147
|
+
`# zsh completion for ${binary}, generated from its command catalog.`,
|
|
148
148
|
`# Install: ${binary} completion zsh > ~/.zfunc/_${binary} (with ~/.zfunc on $fpath)`,
|
|
149
149
|
'',
|
|
150
150
|
`${fn}() {`,
|
|
@@ -189,7 +189,7 @@ function fishEscape(value: string): string {
|
|
|
189
189
|
function fishScript(binary: string): string {
|
|
190
190
|
const guard = `__${shellIdent(binary)}_no_command`;
|
|
191
191
|
const lines: string[] = [
|
|
192
|
-
`# fish completion for ${binary}
|
|
192
|
+
`# fish completion for ${binary}, generated from its command catalog.`,
|
|
193
193
|
`# Install: ${binary} completion fish > ~/.config/fish/completions/${binary}.fish`,
|
|
194
194
|
'',
|
|
195
195
|
`function ${guard}`,
|
package/src/cli/help.ts
CHANGED
|
@@ -14,7 +14,7 @@ function readJsonVersion(path: string): string | null {
|
|
|
14
14
|
try {
|
|
15
15
|
if (!existsSync(path)) return null;
|
|
16
16
|
const parsed = JSON.parse(readFileSync(path, 'utf-8')) as { name?: unknown; version?: unknown };
|
|
17
|
-
// Only trust OUR package.json
|
|
17
|
+
// Only trust OUR package.json, a compiled single-file binary can resolve
|
|
18
18
|
// this path to a different package.json (a bundled dependency's) that
|
|
19
19
|
// reports a placeholder like "0.0.0". Fall through to the baked VERSION in
|
|
20
20
|
// that case rather than rendering a stray version in `--version`/banners.
|
|
@@ -37,7 +37,7 @@ export function renderGoodVibesVersion(binary = 'goodvibes-daemon'): string {
|
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Honest one-line startup identity for the daemon binary, emitted right as it
|
|
40
|
-
* begins serving
|
|
40
|
+
* begins serving, including on a bare (no-arg) systemd launch. It states the
|
|
41
41
|
* RESOLVED version (never a placeholder), the home/host/port it actually bound,
|
|
42
42
|
* and points at the real service-setup command. This replaces the field
|
|
43
43
|
* behavior where a bare launch showed a wrong "v0.0.0" banner and gave an
|
|
@@ -51,7 +51,7 @@ export function renderDaemonStartupBanner(
|
|
|
51
51
|
binary = 'goodvibes-daemon',
|
|
52
52
|
): string {
|
|
53
53
|
return (
|
|
54
|
-
`${binary} ${version} starting
|
|
54
|
+
`${binary} ${version} starting: ` +
|
|
55
55
|
`home=${binding.homeDir} host=${binding.host} port=${binding.port} ` +
|
|
56
56
|
`(manage as a service: ${binary} install-service)`
|
|
57
57
|
);
|
|
@@ -124,7 +124,7 @@ export function renderGoodVibesDaemonHelp(
|
|
|
124
124
|
'',
|
|
125
125
|
'Exit codes:',
|
|
126
126
|
`${pad(' 0')}the command did what it says`,
|
|
127
|
-
`${pad(' 1')}it ran and failed
|
|
127
|
+
`${pad(' 1')}it ran and failed: the reason is printed`,
|
|
128
128
|
`${pad(' 2')}the command line was wrong: an unknown command, an unknown flag,`,
|
|
129
129
|
`${pad(' ')}a flag this command does not take, or a missing value`,
|
|
130
130
|
`${pad(' 3')}service-status only: installed, but not running`,
|
|
@@ -133,7 +133,7 @@ export function renderGoodVibesDaemonHelp(
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
|
-
* `help <command
|
|
136
|
+
* `help <command>`, one command's usage, its own flags, and what it does.
|
|
137
137
|
*
|
|
138
138
|
* Returns null when the word names no command, so the caller can refuse with
|
|
139
139
|
* the same "Unknown command" message the parser produces rather than printing
|
package/src/cli/index.ts
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* CLI barrel for the daemon product.
|
|
3
3
|
*
|
|
4
4
|
* Two halves, deliberately separable:
|
|
5
|
-
* - `command-catalog.ts` is WHAT this binary understands
|
|
5
|
+
* - `command-catalog.ts` is WHAT this binary understands, every command,
|
|
6
6
|
* alias, flag and help string, as data.
|
|
7
7
|
* - `parser.ts`, `help.ts` and `completion.ts` are the ENGINE that reads a
|
|
8
8
|
* catalog and produces a parse, a help page or a completion script. None of
|
|
9
9
|
* them names a daemon command.
|
|
10
10
|
*
|
|
11
|
-
* The argument surface a `goodvibes` front-end shares
|
|
11
|
+
* The argument surface a `goodvibes` front-end shares, the parse engine's
|
|
12
12
|
* catalog contract, redaction, config overrides, settings-value reading and
|
|
13
|
-
* endpoint resolution
|
|
13
|
+
* endpoint resolution, is @pellux/goodvibes-terminal-shell's, imported at the
|
|
14
14
|
* point of use. What stays here is what only this binary has: its own command
|
|
15
15
|
* vocabulary, and the help and completion built on it.
|
|
16
16
|
*/
|
package/src/cli/parser.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* parser.ts
|
|
2
|
+
* parser.ts, this binary's command line, read by the shared argument engine.
|
|
3
3
|
*
|
|
4
4
|
* The engine is `parseWithCatalog` in @pellux/goodvibes-terminal-shell: tokens,
|
|
5
5
|
* values, arity, `--`, refusals, and no knowledge of any product's commands.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* all silently served. `unmatchedFirstToken: 'reject'` is what ends that.
|
|
16
16
|
*
|
|
17
17
|
* 2. Every refusal is a refusal. An unrecognized command, a flag that belongs
|
|
18
|
-
* to another surface, a flag this command does not take, a missing value
|
|
18
|
+
* to another surface, a flag this command does not take, a missing value,
|
|
19
19
|
* each produces an error line, and the caller exits 2 with the help. Nothing
|
|
20
20
|
* is accepted-and-ignored.
|
|
21
21
|
*/
|
|
@@ -4,7 +4,7 @@ import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
|
|
|
4
4
|
* The channel surfaces the platform can speak on, with the settings keys each
|
|
5
5
|
* one needs configured.
|
|
6
6
|
*
|
|
7
|
-
* The daemon reads this to answer "which channels are actually usable"
|
|
7
|
+
* The daemon reads this to answer "which channels are actually usable", the
|
|
8
8
|
* `send` subcommand lists them and refuses a channel whose keys are unset,
|
|
9
9
|
* rather than accepting the message and dropping it. A surface added here
|
|
10
10
|
* becomes visible to `send` with no further wiring.
|
package/src/cli/types.ts
CHANGED
|
@@ -8,9 +8,9 @@ import type { DaemonCommand } from './command-catalog.ts';
|
|
|
8
8
|
* the catalog is what decides which flags could have set them. A command's
|
|
9
9
|
* dispatcher reads only the fields its own catalog entry declares.
|
|
10
10
|
*
|
|
11
|
-
* Fields describing starting or resuming a conversation
|
|
11
|
+
* Fields describing starting or resuming a conversation, prompt, print,
|
|
12
12
|
* outputFormat, noAltScreen, open, continueLast, resume, session, fork,
|
|
13
|
-
* strict
|
|
13
|
+
* strict, are not here. This binary does not start or resume conversations,
|
|
14
14
|
* so those flags are parsed, stored, and read by nothing. See
|
|
15
15
|
* REJECTED_TERMINAL_FLAGS in ./command-catalog.ts for the refusal that
|
|
16
16
|
* replaced them.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* daemon-ws-call.ts
|
|
2
|
+
* daemon-ws-call.ts, invoking a verb that has no REST binding.
|
|
3
3
|
*
|
|
4
4
|
* Most control-plane verbs answer on a plain HTTP path and `callDaemonVerb` in
|
|
5
5
|
* @pellux/goodvibes-terminal-shell is all a subcommand needs. Some do not: the
|
|
@@ -171,7 +171,7 @@ export async function callDaemonWsVerb<T>(
|
|
|
171
171
|
ok: false,
|
|
172
172
|
error: `${where} refused the operator token`,
|
|
173
173
|
fix: target.isLocal
|
|
174
|
-
? 'the token may be stale
|
|
174
|
+
? 'the token may be stale; restart the daemon, or pass --token'
|
|
175
175
|
: 'pass --token with the operator token from that machine (its <daemon home>/operator-tokens.json)',
|
|
176
176
|
});
|
|
177
177
|
return;
|
|
@@ -197,7 +197,7 @@ export async function callDaemonWsVerb<T>(
|
|
|
197
197
|
finish({
|
|
198
198
|
ok: false,
|
|
199
199
|
error: `${where} does not know the verb ${methodId}`,
|
|
200
|
-
fix: 'that daemon is running a build without this capability
|
|
200
|
+
fix: 'that daemon is running a build without this capability; update it, then try again',
|
|
201
201
|
});
|
|
202
202
|
return;
|
|
203
203
|
}
|
|
@@ -221,14 +221,14 @@ export async function callDaemonWsVerb<T>(
|
|
|
221
221
|
};
|
|
222
222
|
|
|
223
223
|
socket.onclose = (): void => {
|
|
224
|
-
// A close before an answer is a refusal too
|
|
224
|
+
// A close before an answer is a refusal too, most often the upgrade
|
|
225
225
|
// itself was rejected, which happens before any frame is sent.
|
|
226
226
|
finish({
|
|
227
227
|
ok: false,
|
|
228
228
|
error: `${where} closed the connection before answering`,
|
|
229
229
|
fix: target.isLocal
|
|
230
230
|
? 'check the daemon is running: goodvibes-daemon service-status'
|
|
231
|
-
: 'check the operator token for that machine
|
|
231
|
+
: 'check the operator token for that machine; an upgrade with no valid token is closed immediately',
|
|
232
232
|
});
|
|
233
233
|
};
|
|
234
234
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* raw-reply-route.ts
|
|
2
|
+
* raw-reply-route.ts, invoking a route that answers with its payload itself.
|
|
3
3
|
*
|
|
4
4
|
* `callDaemonVerb` in @pellux/goodvibes-terminal-shell reads the wrapped
|
|
5
5
|
* convention every `/api/cluster/*` route follows: `{ ok: true, data }` on
|
|
6
6
|
* success, `{ ok: false, error, fix }` on a refusal. Three routes this daemon
|
|
7
|
-
* serves do not
|
|
7
|
+
* serves do not, `/status`, `/api/health` and `/api/channels/status` answer
|
|
8
8
|
* with the payload ITSELF and put the verdict in the HTTP status. Reading one
|
|
9
9
|
* as the other is not a subtle failure: a raw payload has no `ok` field, so the
|
|
10
10
|
* wrapped reader called a perfectly healthy 200 "the daemon refused the
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* So this is the second half of the SAME convention, next to daemon-ws-call.ts:
|
|
14
14
|
* the target is resolved by `resolveRemoteDaemonTarget`, the credential is the
|
|
15
15
|
* same operator token, the reachability / stale-credential / unreadable-reply
|
|
16
|
-
* refusals are the shared reader's
|
|
16
|
+
* refusals are the shared reader's, only the shape of a successful body
|
|
17
17
|
* differs. `rawReplyReader` restates a raw reply in the wrapped convention
|
|
18
18
|
* before the shared reader sees it, so there is one request path rather than
|
|
19
19
|
* two.
|
|
@@ -29,8 +29,8 @@ export type DaemonReplyEnvelope = 'wrapped' | 'raw';
|
|
|
29
29
|
/**
|
|
30
30
|
* The status a restated reply carries.
|
|
31
31
|
*
|
|
32
|
-
* The shared reader looks at the status for exactly three verdicts
|
|
33
|
-
* and 404
|
|
32
|
+
* The shared reader looks at the status for exactly three verdicts, 401, 403
|
|
33
|
+
* and 404, and those are passed through untouched below, before any body is
|
|
34
34
|
* read. Everything else it decides from the body, so a restated reply names a
|
|
35
35
|
* status that is legal to attach a body to (a 204 or a 304 is not) rather than
|
|
36
36
|
* echoing one that would make `new Response` throw.
|
|
@@ -7,15 +7,15 @@ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
|
7
7
|
* decide which directory it is safe to snapshot and how the first sweep and
|
|
8
8
|
* retention behave:
|
|
9
9
|
*
|
|
10
|
-
* - `checkpoints.preferGitRoot` (boolean, SDK default true)
|
|
10
|
+
* - `checkpoints.preferGitRoot` (boolean, SDK default true), prefer the
|
|
11
11
|
* enclosing git repository's top level over the raw working directory.
|
|
12
|
-
* - `checkpoints.allowBroadRoot` (boolean, SDK default false)
|
|
12
|
+
* - `checkpoints.allowBroadRoot` (boolean, SDK default false), opt in to
|
|
13
13
|
* snapshotting a broad root (filesystem root, home directory, ~/.goodvibes).
|
|
14
|
-
* - `checkpoints.allowLargeFirstSnapshot` (boolean, SDK default false)
|
|
14
|
+
* - `checkpoints.allowLargeFirstSnapshot` (boolean, SDK default false), opt
|
|
15
15
|
* in to a first snapshot whose full sweep exceeds `maxFirstSnapshotFiles`.
|
|
16
|
-
* - `checkpoints.maxFirstSnapshotFiles` (number, SDK default)
|
|
16
|
+
* - `checkpoints.maxFirstSnapshotFiles` (number, SDK default), ceiling for
|
|
17
17
|
* the first-ever snapshot's file sweep.
|
|
18
|
-
* - `checkpoints.autoRetention` (boolean, SDK default true)
|
|
18
|
+
* - `checkpoints.autoRetention` (boolean, SDK default true), run a retention
|
|
19
19
|
* sweep automatically after each successful create and once at init.
|
|
20
20
|
*
|
|
21
21
|
* A sixth key, `checkpoints.unregisteredWorkspaces`, is read separately by
|
|
@@ -90,14 +90,14 @@ export function readCheckpointGuardSettings(
|
|
|
90
90
|
* The registered-workspaces-only override (owner ruling, 2026-07-10):
|
|
91
91
|
*
|
|
92
92
|
* - `checkpoints.unregisteredWorkspaces` (`'off' | 'guarded'`, default `'off'`)
|
|
93
|
-
*
|
|
93
|
+
* , `'off'` is the ruling's default: automatic (turn-end/lifecycle)
|
|
94
94
|
* checkpoints, and explicit checkpoint creation through the ws-only
|
|
95
95
|
* `checkpoints.create` gateway verb, both refuse when the resolved
|
|
96
96
|
* workspace root is not covered by the shared registration store
|
|
97
97
|
* (the SDK's workspace registration, platform/workspace).
|
|
98
98
|
* `'guarded'` opts back into the pre-ruling behavior for an unregistered
|
|
99
99
|
* workspace: automatic snapshots subscribe and explicit create proceeds,
|
|
100
|
-
* subject only to the SDK's own root/size guards above
|
|
100
|
+
* subject only to the SDK's own root/size guards above, never a silent
|
|
101
101
|
* re-enable, an explicit per-workspace opt-out of the registration gate.
|
|
102
102
|
*
|
|
103
103
|
* Any other value (including absence) reads as `'off'`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-key-guard.ts
|
|
3
|
+
*
|
|
4
|
+
* ConfigManager.get/set/reset are typed `<K extends ConfigKey>`, a
|
|
5
|
+
* compile-time constraint that says nothing about a key that only exists as a
|
|
6
|
+
* plain string at runtime: a `config get <key>` CLI argument, or the
|
|
7
|
+
* `key: string` the SDK's readControlPlaneBinding hands back through its own
|
|
8
|
+
* read callback. Every one of those call sites used to write `key as
|
|
9
|
+
* ConfigKey` and trust the string was really a known key without the compiler
|
|
10
|
+
* ever seeing why that trust was justified.
|
|
11
|
+
*
|
|
12
|
+
* This guard makes the check honest: it is the schema lookup those call sites
|
|
13
|
+
* already ran (or could run) to decide the same thing, exposed as a real
|
|
14
|
+
* `key is ConfigKey` predicate so `key` narrows at the call site instead of
|
|
15
|
+
* being asserted. Same shape as isSecretConfigKey in ./secret-config.ts.
|
|
16
|
+
*/
|
|
17
|
+
import type { ConfigKey, ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
|
|
18
|
+
|
|
19
|
+
/** True when `key` names a setting in `schema`; narrows `key` to ConfigKey. */
|
|
20
|
+
export function isKnownConfigKey(key: string, schema: readonly ConfigSetting[]): key is ConfigKey {
|
|
21
|
+
return schema.some((setting) => setting.key === key);
|
|
22
|
+
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* run-daemon-config-migration.ts
|
|
2
|
+
* run-daemon-config-migration.ts, the one call every composition root in
|
|
3
3
|
* this daemon makes before constructing its `ConfigManager`.
|
|
4
4
|
*
|
|
5
5
|
* Daemon-owned configuration now has exactly one home:
|
|
6
6
|
* `~/.goodvibes/daemon/settings.json`. Before this migration, every product
|
|
7
7
|
* wrote every key (including daemon-only ones like `surfaces.telegram.*`)
|
|
8
8
|
* into its own per-surface silo, and the daemon only ever read
|
|
9
|
-
* `~/.goodvibes/tui/settings.json
|
|
9
|
+
* `~/.goodvibes/tui/settings.json`, so a value written by, say, the agent
|
|
10
10
|
* surface reported a successful save and configured nothing the daemon could
|
|
11
11
|
* see.
|
|
12
12
|
*
|
|
13
13
|
* `migrateDaemonOwnedConfig` (SDK, `platform/config`) is idempotent and cheap
|
|
14
14
|
* on the fast path (one file read + JSON parse), so it is safe and correct to
|
|
15
15
|
* call this at every composition root that is about to construct a
|
|
16
|
-
* `ConfigManager
|
|
16
|
+
* `ConfigManager`, not just the first one to run in a given process. It
|
|
17
17
|
* must never abort startup: any failure is caught, logged with the marker
|
|
18
18
|
* path so the failure is diagnosable, and startup continues on whatever
|
|
19
19
|
* config state already exists.
|
|
@@ -4,21 +4,21 @@ import type { SecretScope, SecretStorageMedium } from './secrets.ts';
|
|
|
4
4
|
|
|
5
5
|
export const SECRET_CONFIG_KEYS = new Set<ConfigKey>([
|
|
6
6
|
// Mailbox and CalDAV credentials. Their own CONFIG_SCHEMA descriptions read
|
|
7
|
-
// "Stored in the daemon secret tier, never in config"
|
|
7
|
+
// "Stored in the daemon secret tier, never in config", and until they were
|
|
8
8
|
// listed here that sentence was aspirational: the settings modal wrote them
|
|
9
9
|
// as plain strings into a config JSON file, because membership in this set is
|
|
10
10
|
// the thing that routes an edit through the secret manager instead.
|
|
11
11
|
//
|
|
12
12
|
// The daemon reads each of these back with
|
|
13
13
|
// resolveConfigSecret('<key>') → GOODVIBES_<KEY>, which is exactly the store
|
|
14
|
-
// key buildGoodVibesSecretKey() writes
|
|
14
|
+
// key buildGoodVibesSecretKey() writes, see
|
|
15
15
|
// daemon/handlers/inbox/providers/email.ts.
|
|
16
16
|
'surfaces.email.password',
|
|
17
17
|
'surfaces.email.imapPassword',
|
|
18
18
|
'surfaces.email.imap.password',
|
|
19
19
|
'surfaces.email.smtp.password',
|
|
20
20
|
'surfaces.calendar.caldavPassword',
|
|
21
|
-
// Telephony delivery credentials
|
|
21
|
+
// Telephony delivery credentials, same shape, same file, same gap.
|
|
22
22
|
'surfaces.telephony.authToken',
|
|
23
23
|
'surfaces.telephony.token',
|
|
24
24
|
'surfaces.telephony.webhookSecret',
|
|
@@ -43,7 +43,7 @@ export const SECRET_CONFIG_KEYS = new Set<ConfigKey>([
|
|
|
43
43
|
'surfaces.mattermost.botToken',
|
|
44
44
|
'surfaces.matrix.accessToken',
|
|
45
45
|
// Local synthetic sub-keys, one level under the SDK's real `payments`
|
|
46
|
-
// section (not yet a scalar CONFIG_SCHEMA entry
|
|
46
|
+
// section (not yet a scalar CONFIG_SCHEMA entry, same situation as
|
|
47
47
|
// tts.speed, behavior.notifyAfterSeconds, etc. in settings-modal-data.ts),
|
|
48
48
|
// hence the cast. See input/payments-config.ts for why these are named flat
|
|
49
49
|
// (payments.cardNumber, not payments.card.number): a flat one-level leaf is
|
|
@@ -131,14 +131,14 @@ export function buildSecretBackedConfigUpdate(configKey: ConfigKey, rawValue: st
|
|
|
131
131
|
* A daemon-owned config key (`surfaces.*`, `payments.*`, `controlPlane.*`, ...)
|
|
132
132
|
* names a credential the DAEMON executes with, not this interactive client, so
|
|
133
133
|
* its secret material belongs in the daemon-scoped tier the daemon actually
|
|
134
|
-
* reads
|
|
134
|
+
* reads, the same rule the SDK's config-ownership.ts already applies to the
|
|
135
135
|
* `goodvibes://` reference that points at it.
|
|
136
136
|
*
|
|
137
137
|
* Defaulting these to 'user' (the historical behavior here) split the pair: the
|
|
138
138
|
* reference landed in the daemon's own settings file, because ConfigManager
|
|
139
139
|
* routes daemon-owned keys there, while the value it pointed at sat in a tier
|
|
140
140
|
* the daemon never resolves. The surface reported success and the daemon found
|
|
141
|
-
* nothing. For the mailbox password that is the whole feature failing silently
|
|
141
|
+
* nothing. For the mailbox password that is the whole feature failing silently,
|
|
142
142
|
* the daemon is the process that polls IMAP and answers over Telegram, and it
|
|
143
143
|
* does so with every surface closed. A payment card entered through
|
|
144
144
|
* /payments card is the same shape of failure at purchase time.
|
|
@@ -166,7 +166,7 @@ export async function persistSecretBackedConfigValue(
|
|
|
166
166
|
await secretsManager.set(update.secretKey, update.secretValue, { scope, medium });
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
// 3. Clear old secret
|
|
169
|
+
// 3. Clear old secret, pass the same medium so plaintext-medium secrets are found for deletion.
|
|
170
170
|
if (update.clearSecretKey && secretsManager?.delete) {
|
|
171
171
|
await secretsManager.delete(update.clearSecretKey, { scope, medium });
|
|
172
172
|
}
|
package/src/config/surface.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* surface.ts
|
|
2
|
+
* surface.ts, the daemon's single surface-root identifier.
|
|
3
3
|
*
|
|
4
4
|
* Every piece of the daemon's own on-disk state (sessions, recovery snapshots,
|
|
5
5
|
* checkpoints, the transcript journal, watchers, triggers, control-plane stores)
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* ── Why the daemon's surface root is still `tui` ────────────────────────────
|
|
11
11
|
*
|
|
12
12
|
* Because that is where the running daemon's state already is. Every store
|
|
13
|
-
* this daemon has written on every installed machine
|
|
14
|
-
* watchers, devices, channel policies, the code index
|
|
13
|
+
* this daemon has written on every installed machine, sessions, approvals,
|
|
14
|
+
* watchers, devices, channel policies, the code index, sits under
|
|
15
15
|
* `.goodvibes/tui/`. Renaming the segment here would not move that state; it
|
|
16
16
|
* would make the daemon stop finding it, silently, on machines that have been
|
|
17
17
|
* running for months.
|