@pellux/goodvibes-daemon 1.28.18 → 1.28.20

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 (108) hide show
  1. package/CHANGELOG.md +100 -63
  2. package/README.md +13 -13
  3. package/bin/launcher-support.js +5 -5
  4. package/package.json +6 -4
  5. package/scripts/postinstall.js +8 -8
  6. package/src/cli/command-catalog.ts +22 -22
  7. package/src/cli/completion.ts +4 -4
  8. package/src/cli/help.ts +5 -5
  9. package/src/cli/index.ts +3 -3
  10. package/src/cli/parser.ts +2 -2
  11. package/src/cli/surface-catalog.ts +1 -1
  12. package/src/cli/types.ts +2 -2
  13. package/src/cluster/daemon-ws-call.ts +5 -5
  14. package/src/cluster/raw-reply-route.ts +5 -5
  15. package/src/config/checkpoint-settings.ts +7 -7
  16. package/src/config/config-key-guard.ts +22 -0
  17. package/src/config/run-daemon-config-migration.ts +3 -3
  18. package/src/config/secret-config.ts +7 -7
  19. package/src/config/surface.ts +3 -3
  20. package/src/core/pairing-banner.ts +5 -5
  21. package/src/daemon/cli.ts +45 -43
  22. package/src/daemon/config-command.ts +15 -15
  23. package/src/daemon/handlers/context.ts +1 -1
  24. package/src/daemon/handlers/contracts.ts +4 -4
  25. package/src/daemon/handlers/credentials.ts +1 -1
  26. package/src/daemon/handlers/drafts/draft-store.ts +3 -3
  27. package/src/daemon/handlers/drafts/register.ts +4 -4
  28. package/src/daemon/handlers/inbox/aggregator.ts +8 -8
  29. package/src/daemon/handlers/inbox/cursor-store.ts +10 -10
  30. package/src/daemon/handlers/inbox/index.ts +7 -7
  31. package/src/daemon/handlers/inbox/mapping.ts +2 -2
  32. package/src/daemon/handlers/inbox/poller.ts +5 -5
  33. package/src/daemon/handlers/inbox/provider-adapter.ts +8 -8
  34. package/src/daemon/handlers/inbox/providers/discord.ts +6 -6
  35. package/src/daemon/handlers/inbox/providers/email.ts +3 -3
  36. package/src/daemon/handlers/inbox/providers/imap-client.ts +1 -1
  37. package/src/daemon/handlers/inbox/providers/slack.ts +4 -4
  38. package/src/daemon/handlers/index.ts +18 -8
  39. package/src/daemon/handlers/payments/card-store.ts +486 -0
  40. package/src/daemon/handlers/payments/index.ts +32 -0
  41. package/src/daemon/handlers/payments/purchase-ledger.ts +108 -0
  42. package/src/daemon/handlers/payments/register.ts +391 -0
  43. package/src/daemon/handlers/register.ts +3 -3
  44. package/src/daemon/handlers/remote/backends/cloud-terminal.ts +9 -1
  45. package/src/daemon/handlers/remote/backends/process-runner.ts +1 -1
  46. package/src/daemon/handlers/remote/backends/ssh.ts +9 -1
  47. package/src/daemon/handlers/remote/backends/types.ts +2 -2
  48. package/src/daemon/handlers/remote/dispatcher.ts +3 -3
  49. package/src/daemon/handlers/remote/index.ts +1 -1
  50. package/src/daemon/handlers/remote/peer-registry.ts +62 -13
  51. package/src/daemon/handlers/routing/inbox-bridge.ts +5 -5
  52. package/src/daemon/handlers/routing/index.ts +1 -1
  53. package/src/daemon/handlers/routing/route-store.ts +1 -1
  54. package/src/daemon/handlers/routing/routing-resolver.ts +3 -3
  55. package/src/daemon/handlers/sqlite-store.ts +9 -9
  56. package/src/daemon/handlers/triage/index.ts +1 -1
  57. package/src/daemon/handlers/triage/integration.ts +3 -3
  58. package/src/daemon/handlers/triage/pipeline.ts +2 -2
  59. package/src/daemon/handlers/triage/scorer.ts +2 -2
  60. package/src/daemon/handlers/triage/tagger/discord.ts +3 -3
  61. package/src/daemon/handlers/triage/tagger/imap.ts +7 -7
  62. package/src/daemon/handlers/triage/tagger/index.ts +1 -1
  63. package/src/daemon/handlers/triage/tagger/shared.ts +3 -3
  64. package/src/daemon/handlers/triage/tagger/slack.ts +1 -1
  65. package/src/daemon/handlers/triage/types.ts +2 -2
  66. package/src/daemon/lifecycle.ts +5 -5
  67. package/src/daemon/local-daemon-state.ts +7 -7
  68. package/src/daemon/pair-command.ts +14 -14
  69. package/src/daemon/provision-wake-model.ts +5 -5
  70. package/src/daemon/send/channels.ts +7 -7
  71. package/src/daemon/send/command.ts +11 -11
  72. package/src/daemon/send/composition.ts +7 -7
  73. package/src/daemon/send/failure-text.ts +6 -6
  74. package/src/daemon/send/inert-text.ts +18 -18
  75. package/src/daemon/send/stdin.ts +3 -3
  76. package/src/daemon/service-commands.ts +32 -32
  77. package/src/daemon/sessions-command.ts +7 -7
  78. package/src/daemon/status-command.ts +22 -22
  79. package/src/daemon/webui-command.ts +14 -14
  80. package/src/runtime/boot-tasks.ts +1 -1
  81. package/src/runtime/cluster-composition.ts +9 -9
  82. package/src/runtime/cluster-group-composition.ts +7 -7
  83. package/src/runtime/conversation-rewind-port.ts +8 -8
  84. package/src/runtime/credential-composition.ts +2 -2
  85. package/src/runtime/daemon-handler-composition.ts +44 -4
  86. package/src/runtime/device-posture-composition.ts +10 -10
  87. package/src/runtime/disposal-wiring.ts +8 -8
  88. package/src/runtime/fleet-needs-input-push.ts +4 -4
  89. package/src/runtime/fleet-services.ts +1 -1
  90. package/src/runtime/hosted-session-composition.ts +13 -13
  91. package/src/runtime/index.ts +1 -1
  92. package/src/runtime/knowledge-services.ts +2 -2
  93. package/src/runtime/legacy-daemon-migration.ts +43 -43
  94. package/src/runtime/legacy-daemon-reconcile.ts +30 -30
  95. package/src/runtime/mail-composition.ts +6 -6
  96. package/src/runtime/notification-dispatch.ts +7 -7
  97. package/src/runtime/payments-composition.ts +143 -0
  98. package/src/runtime/plugin-composition.ts +7 -7
  99. package/src/runtime/runtime-services-types.ts +9 -9
  100. package/src/runtime/services.ts +31 -29
  101. package/src/runtime/trigger-services.ts +1 -1
  102. package/src/runtime/trust/checkpoint-eligibility.ts +5 -5
  103. package/src/runtime/trust/trust-gated-approvals.ts +9 -9
  104. package/src/runtime/update-check.ts +4 -4
  105. package/src/runtime/workspace-checkpointing.ts +6 -6
  106. package/src/testing/daemon-fixture.ts +11 -11
  107. package/src/testing/hosted-session-failures.ts +4 -4
  108. package/src/version.ts +2 -2
@@ -9,22 +9,22 @@
9
9
  // `getProfileForChannel(surfaceKind, routeId?)` applies the canonical
10
10
  // resolution order:
11
11
  //
12
- // 1. exact surfaceKind AND routeId both match
13
- // 2. surface surfaceKind matches, route has no routeId
14
- // 3. wildcard a route with surfaceKind === 'any' (no routeId)
12
+ // 1. exact , surfaceKind AND routeId both match
13
+ // 2. surface , surfaceKind matches, route has no routeId
14
+ // 3. wildcard , a route with surfaceKind === 'any' (no routeId)
15
15
  //
16
16
  // This module bridges the two shapes. The bridge maps an inbound item's
17
17
  // `provider` to the routing `surfaceKind` and forwards an optional `routeId`
18
18
  // refinement; `fromDigest`/`kind` are NOT used as routing keys (the routing
19
19
  // store is keyed by surfaceKind + optional routeId only, never by sender digest
20
- // or message kind see RoutingChannelRoute). When no binding matches the
20
+ // or message kind, see RoutingChannelRoute). When no binding matches the
21
21
  // resolver returns null and the bridge yields `undefined`, so the adapter falls
22
22
  // back to leaving the item unrouted (offline/default behaviour intact). The
23
23
  // bridge NEVER throws: the underlying resolver is a pure in-memory lookup.
24
24
  //
25
25
  // The inbox surface is a separate follow-up module, so this bridge declares the
26
26
  // minimal `RouteResolver` seam locally rather than importing a sibling that the
27
- // routing slice does not own keeping the routing handler layer free of any
27
+ // routing slice does not own, keeping the routing handler layer free of any
28
28
  // cross-surface import edge.
29
29
  // ---------------------------------------------------------------------------
30
30
 
@@ -1,5 +1,5 @@
1
1
  // ---------------------------------------------------------------------------
2
- // Channel routing surface HANDLERS for the SDK-defined methods
2
+ // Channel routing surface, HANDLERS for the SDK-defined methods
3
3
  // - channels.routing.list (read-only)
4
4
  // - channels.routing.assign (admin, confirmation-gated)
5
5
  // - channels.routing.delete (admin, dangerous, confirmation-gated)
@@ -96,7 +96,7 @@ const SCHEMA: string[] = [
96
96
  updatedAt TEXT NOT NULL
97
97
  )`,
98
98
  `CREATE INDEX IF NOT EXISTS idx_routes_surface_route ON routes (surfaceKind, routeId)`,
99
- // Enforce one assignment per channel the upsert path relies on this.
99
+ // Enforce one assignment per channel, the upsert path relies on this.
100
100
  `CREATE UNIQUE INDEX IF NOT EXISTS idx_routes_channel ON routes (channelId)`,
101
101
  ];
102
102
 
@@ -9,9 +9,9 @@ export const WILDCARD_SURFACE = 'any';
9
9
  * order as the agent-side `getProfileForChannel()` so offline and online
10
10
  * routing produce identical results:
11
11
  *
12
- * 1. exact match surfaceKind AND routeId both match
13
- * 2. surface-only surfaceKind matches, route has no routeId
14
- * 3. wildcard a route with surfaceKind === 'any' (no routeId)
12
+ * 1. exact match , surfaceKind AND routeId both match
13
+ * 2. surface-only , surfaceKind matches, route has no routeId
14
+ * 3. wildcard , a route with surfaceKind === 'any' (no routeId)
15
15
  *
16
16
  * Returns the matching profileId, or `null` when nothing matches.
17
17
  */
@@ -1,6 +1,6 @@
1
1
  /// <reference types="@pellux/goodvibes-sdk/sql-js" />
2
2
  // `sql.js` ships no types. The declaration is SDK-owned and reaches here
3
- // through that reference there is no local copy to keep in step.
3
+ // through that reference, there is no local copy to keep in step.
4
4
 
5
5
  import { mkdir, rename, writeFile } from 'node:fs/promises';
6
6
  import { readFileSync, readdirSync, renameSync, rmSync, statSync } from 'node:fs';
@@ -23,7 +23,7 @@ export interface SqliteStoreOptions {
23
23
 
24
24
  /**
25
25
  * The 16 bytes every SQLite file begins with ("SQLite format 3" + NUL). A file
26
- * that does not start with these is not a database, whatever its extension
26
+ * that does not start with these is not a database, whatever its extension,
27
27
  * which is exactly what a crash mid-create, a zero-fill, or a partially
28
28
  * restored backup leaves behind.
29
29
  */
@@ -93,7 +93,7 @@ export class HandlerSqliteStore {
93
93
  /**
94
94
  * Move a database file that cannot be opened aside instead of deleting it, so
95
95
  * an operator still has something to salvage, and return the quarantine path.
96
- * Returns null when the move itself failed in which case the caller starts
96
+ * Returns null when the move itself failed, in which case the caller starts
97
97
  * fresh in memory and the bad file is left exactly where it was rather than
98
98
  * being overwritten on the next save.
99
99
  */
@@ -110,8 +110,8 @@ export class HandlerSqliteStore {
110
110
  /**
111
111
  * Bound the quarantine directory: drop `.corrupt-*` copies of THIS store that
112
112
  * are past the TTL or beyond the keep-newest count. Best-effort and
113
- * idempotent a file another process already removed is success, not an
114
- * error so two daemons opening the same store at once cannot fight.
113
+ * idempotent, a file another process already removed is success, not an
114
+ * error, so two daemons opening the same store at once cannot fight.
115
115
  * Returns how many were reclaimed.
116
116
  */
117
117
  private reapCorruptQuarantines(nowMs: number): number {
@@ -158,7 +158,7 @@ export class HandlerSqliteStore {
158
158
 
159
159
  // Validate the file by its CONTENT, not by existsSync. `save()` writes
160
160
  // through a pid-and-timestamp temp file and an atomic rename, so it cannot
161
- // itself leave a half-written database but a zero-filled file recovered
161
+ // itself leave a half-written database, but a zero-filled file recovered
162
162
  // by a filesystem, a truncated restore, or a copy interrupted by something
163
163
  // outside this process all produce a path that exists and holds no usable
164
164
  // database. Handing those bytes to `new SQL.Database(...)` throws out of
@@ -200,7 +200,7 @@ export class HandlerSqliteStore {
200
200
  }
201
201
  } catch (error) {
202
202
  // Header present but the body does not hold up: a truncated or damaged
203
- // database. Same treatment set aside, disclose, start clean.
203
+ // database. Same treatment, set aside, disclose, start clean.
204
204
  quarantineReason = `database would not open: ${error instanceof Error ? error.message : String(error)}`;
205
205
  this.db = null;
206
206
  quarantined = this.quarantineUnreadable();
@@ -215,7 +215,7 @@ export class HandlerSqliteStore {
215
215
  // Disclosure: starting a store from scratch is data loss from the user's
216
216
  // point of view, so it is never allowed to happen quietly.
217
217
  if (quarantineReason) {
218
- logger.warn('daemon store could not be opened starting a fresh one', {
218
+ logger.warn('daemon store could not be opened; starting a fresh one', {
219
219
  store: this.options.fileName,
220
220
  path: this.resolvedPath,
221
221
  reason: quarantineReason,
@@ -266,7 +266,7 @@ export class HandlerSqliteStore {
266
266
  * saves of the same store in the SAME millisecond produced the same temp path
267
267
  * before it did: both wrote it, the first rename moved it away, and the
268
268
  * second failed with ENOENT on a file it had just written. That is not a
269
- * hypothetical race the inbox poller flushes once per provider and polls
269
+ * hypothetical race, the inbox poller flushes once per provider and polls
270
270
  * every provider concurrently, so it hit on an ordinary two-provider startup,
271
271
  * and the failure surfaced as a provider reporting a filesystem error for its
272
272
  * feed. Every store on this base shares the hazard, so the counter lives here.
@@ -5,7 +5,7 @@
5
5
  // runtime composition root calls. It DECORATES the inbox surface's
6
6
  // `channels.inbox.list` handler (overlaying persisted triage metadata) and
7
7
  // returns the poller-facing pipeline + tagger. inbox.triage.* are NOT published
8
- // catalog methods this surface registers no triage method id.
8
+ // catalog methods, this surface registers no triage method id.
9
9
  // ---------------------------------------------------------------------------
10
10
 
11
11
  export {
@@ -12,12 +12,12 @@
12
12
  // overlaid with the persisted triageScore/triageTags via
13
13
  // enrichItemsWithTriage(). Every other registration passes straight
14
14
  // through to the real catalog. The inbox descriptor/schema/id is never
15
- // re-authored only its handler is wrapped.
15
+ // re-authored, only its handler is wrapped.
16
16
  // 2. It exposes the triage pipeline + tagger (`runInboxTriage`, `tagger`) for
17
17
  // the daemon-internal poller, which scores items and persists them to the
18
18
  // co-located inbox-triage.sqlite store the decorator reads from.
19
19
  // 3. inbox.triage.* are intentionally NOT registered on the catalog (they are
20
- // a daemon-internal pipeline, not published methods) so this module
20
+ // a daemon-internal pipeline, not published methods), so this module
21
21
  // makes ZERO catalog.register call for any triage id.
22
22
  //
23
23
  // Reads in the decorator are best-effort and degrade to the raw item when no
@@ -51,7 +51,7 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
51
51
 
52
52
  /**
53
53
  * Canonical id of the inbox list method whose handler we decorate. This is the
54
- * SDK's published id referenced as a plain string for matching during
54
+ * SDK's published id, referenced as a plain string for matching during
55
55
  * registration; no descriptor or schema is authored here.
56
56
  */
57
57
  export const INBOX_LIST_METHOD_ID = 'channels.inbox.list';
@@ -57,7 +57,7 @@ export interface RunInboxTriageOptions {
57
57
  scorer?: TriageScorerOptions;
58
58
  /** Inject a store (tests). When omitted, a triage store is opened/closed. */
59
59
  store?: HandlerSqliteStore;
60
- /** When true, do not persist only compute (used by inbox.triage.list). */
60
+ /** When true, do not persist, only compute (used by inbox.triage.list). */
61
61
  dryRun?: boolean;
62
62
  /** Clock injection for deterministic updatedAt in tests. */
63
63
  now?: () => Date;
@@ -254,7 +254,7 @@ export function enrichItemsWithTriage<T extends { id: string }>(
254
254
  items: readonly T[],
255
255
  ): Array<T & TriageOverlay> {
256
256
  if (items.length === 0) return [];
257
- // Single batched read (`WHERE id IN (...)`) instead of one SELECT per item
257
+ // Single batched read (`WHERE id IN (...)`) instead of one SELECT per item,
258
258
  // this is a hot read path (every channels.inbox.list call), so the N+1 is
259
259
  // collapsed to one query keyed by id.
260
260
  const byId = readTriageMetadataBatch(
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // Pure, dependency-free heuristic + naive-Bayes-style spam/priority scoring
5
5
  // over an InboundChannelItem's textual surface (subject + snippet). No I/O,
6
- // no credentials, no network safe to run on every polled item.
6
+ // no credentials, no network, safe to run on every polled item.
7
7
  //
8
8
  // The score is a normalized 0..1 confidence in the assigned label. The label
9
9
  // is one of 'spam' | 'priority' | 'normal'. Scoring is deterministic so the
@@ -247,7 +247,7 @@ export function scoreInboundItem(
247
247
  // A trailing question mark suggests an awaited answer.
248
248
  if (/\?\s*$/.test(raw)) priorityLL += 0.5;
249
249
 
250
- // Spam strongly suppresses priority promotional text is rarely urgent.
250
+ // Spam strongly suppresses priority, promotional text is rarely urgent.
251
251
  priorityLL -= spam * 2.5;
252
252
 
253
253
  const priority = clamp01(sigmoid(priorityLL));
@@ -1,10 +1,10 @@
1
1
  // ---------------------------------------------------------------------------
2
- // Triage tagger Discord provider.
2
+ // Triage tagger, Discord provider.
3
3
  //
4
4
  // Two paths, in priority order:
5
5
  // 1. REAL thread tags: when the item targets a forum/media-channel thread and
6
6
  // a forum-tag mapping resolves >=1 tag id, PATCH the thread's applied_tags
7
- // (read-then-merge never blind overwrite). Exact fidelity to the
7
+ // (read-then-merge, never blind overwrite). Exact fidelity to the
8
8
  // contract's "Discord thread tags".
9
9
  // 2. Reaction analog: Discord has no arbitrary per-message tags, so otherwise
10
10
  // add a unicode reaction (PUT .../reactions/{emoji}/@me).
@@ -114,7 +114,7 @@ async function applyDiscordThreadTags(
114
114
  // DATA-LOSS GUARD: we PATCH the thread's full applied_tags array, so we must
115
115
  // first read the EXISTING tags and merge. If that read fails (network error
116
116
  // OR a non-ok HTTP status), we have no idea what tags are currently on the
117
- // thread PATCHing with only our new ids would silently destroy whatever
117
+ // thread, PATCHing with only our new ids would silently destroy whatever
118
118
  // forum tags were already applied. Abort instead of overwriting.
119
119
  const existing = await fetchDiscordAppliedTags(url, token, fetchImpl, ctx);
120
120
  if (existing === null) {
@@ -1,5 +1,5 @@
1
1
  // ---------------------------------------------------------------------------
2
- // Triage tagger IMAP provider.
2
+ // Triage tagger, IMAP provider.
3
3
  //
4
4
  // Applies a triage label as an IMAP keyword flag (STORE +FLAGS) over a minimal
5
5
  // IMAP4rev1-over-TLS client. Credentials are resolved per-apply from the daemon
@@ -63,7 +63,7 @@ export type ImapConnect = (opts: {
63
63
  servername: string;
64
64
  }) => ImapSocketLike;
65
65
 
66
- /** Production connector a real IMAP4rev1-over-TLS socket via node:tls. */
66
+ /** Production connector, a real IMAP4rev1-over-TLS socket via node:tls. */
67
67
  export const tlsImapConnect: ImapConnect = (opts) =>
68
68
  tlsConnect(opts, () => {
69
69
  /* greeting handled in the data pump */
@@ -128,7 +128,7 @@ export class ImapStoreError extends Error {
128
128
  * in a value destined for an IMAP command line. CR/LF are the dangerous ones:
129
129
  * an unescaped CRLF would terminate the current command and let an attacker
130
130
  * inject a second IMAP command (CRLF injection). IMAP's quoted-string syntax
131
- * has no escape for these control chars backslash only escapes `\` and `"` —
131
+ * has no escape for these control chars, backslash only escapes `\` and `"`,
132
132
  * so the only safe handling is to refuse the value outright.
133
133
  */
134
134
  function assertImapSafe(value: string, field: string): void {
@@ -155,8 +155,8 @@ function quoteImap(value: string): string {
155
155
  // A concrete UID sequence-set: one or more comma-separated single UIDs or
156
156
  // numeric ranges (`12`, `3:9`, `1,4,7:9`). RFC 3501 also permits `*` (the
157
157
  // largest UID in the mailbox) and ranges to `*`, but a wildcard would let a
158
- // single value like `1:*` apply the flag to the ENTIRE mailbox never the
159
- // intent when tagging one triaged item so `*` is rejected outright.
158
+ // single value like `1:*` apply the flag to the ENTIRE mailbox, never the
159
+ // intent when tagging one triaged item, so `*` is rejected outright.
160
160
  const IMAP_UID_SET = /^[0-9]+(:[0-9]+)?(,[0-9]+(:[0-9]+)?)*$/;
161
161
 
162
162
  // An IMAP flag: an optional leading `\` (system flag, e.g. `\Seen`) followed by
@@ -187,7 +187,7 @@ function assertImapUid(value: string): void {
187
187
  * UNQUOTED into the `+FLAGS (...)` list. assertImapSafe only blocks control
188
188
  * chars; a flag containing a space (e.g. `\Seen Junk`) would still pass that
189
189
  * guard and inject a second flag atom. This is the boundary check that
190
- * prevents it independent of any upstream normalizer.
190
+ * prevents it, independent of any upstream normalizer.
191
191
  */
192
192
  function assertImapFlag(value: string): void {
193
193
  if (!IMAP_FLAG.test(value)) {
@@ -313,7 +313,7 @@ export function imapStoreFlagOverTls(
313
313
  finish();
314
314
  }
315
315
  } else {
316
- // NO/BAD: deterministic protocol rejection do not retry.
316
+ // NO/BAD: deterministic protocol rejection, do not retry.
317
317
  current.reject(new ImapStoreError(`IMAP command failed: ${status}`, false));
318
318
  }
319
319
  };
@@ -159,7 +159,7 @@ export function createTriageTagger(
159
159
  return { ...base, reason: 'autotag-disabled' };
160
160
  }
161
161
 
162
- // Provider-side mutation is effectful require explicit confirmation.
162
+ // Provider-side mutation is effectful, require explicit confirmation.
163
163
  if (request.confirm !== true || request.explicitUserRequest !== true) {
164
164
  throw new HandlerError(
165
165
  'Provider-side triage tagging requires explicit user confirmation.',
@@ -1,5 +1,5 @@
1
1
  // ---------------------------------------------------------------------------
2
- // Triage tagger shared types and provider-agnostic helpers.
2
+ // Triage tagger, shared types and provider-agnostic helpers.
3
3
  //
4
4
  // Provider config shapes, the apply request/result contract, and the tag
5
5
  // normalization helpers used by the IMAP/Slack/Discord modules. No I/O here.
@@ -17,7 +17,7 @@ export interface TaggerProviderConfig {
17
17
  * optional forum-tag mapping. When `forumTagIds` maps a GoodVibes triage tag
18
18
  * (e.g. 'GoodVibes/Spam') to a forum tag SNOWFLAKE id, items that target a
19
19
  * forum/media-channel thread get that REAL thread tag applied (PATCH
20
- * applied_tags) exact fidelity to the contract's "Discord thread tags".
20
+ * applied_tags), exact fidelity to the contract's "Discord thread tags".
21
21
  * Without a mapping (or for non-thread messages) tagging degrades to a
22
22
  * unicode reaction analog.
23
23
  */
@@ -29,7 +29,7 @@ export interface ApplyTagsRequest {
29
29
  /** Provider-side tags to apply. Defaults to [labelToTag(label)] when omitted. */
30
30
  tags?: readonly string[];
31
31
  label?: TriageLabel;
32
- /** Must be true provider-side mutation requires explicit confirmation. */
32
+ /** Must be true, provider-side mutation requires explicit confirmation. */
33
33
  confirm?: boolean;
34
34
  /** Mirror of the operator invocation context flag. */
35
35
  explicitUserRequest?: boolean;
@@ -1,5 +1,5 @@
1
1
  // ---------------------------------------------------------------------------
2
- // Triage tagger Slack provider.
2
+ // Triage tagger, Slack provider.
3
3
  //
4
4
  // Applies a triage label as a Slack message reaction (reactions.add). The bot
5
5
  // token is resolved per-apply from the daemon credential store and is never
@@ -5,7 +5,7 @@
5
5
  // and the triage pipeline scores. It is NOT an SDK catalog contract: the
6
6
  // published `channels.inbox.list` output schema (CHANNEL_INBOX_ITEM_SCHEMA) is
7
7
  // owned by the SDK and never re-declared here. This is the internal poller
8
- // item per the handoff doc it carries `fromDigest` (never a raw sender id),
8
+ // item per the handoff doc, it carries `fromDigest` (never a raw sender id),
9
9
  // `subjectPreview`/`bodyPreview` (PII-stripped, length-bounded) and an opaque
10
10
  // `metadata` bag the tagger reads provider targeting from (imapUid, channelId,
11
11
  // Slack ts, Discord messageId/threadId).
@@ -31,7 +31,7 @@ export interface InboundChannelItem {
31
31
  /** Handoff-facing provider id ("slack" | "discord" | "email" | ...). */
32
32
  readonly provider?: string;
33
33
  readonly kind?: 'dm' | 'thread' | 'mention' | 'reaction';
34
- /** SHA-256 first-N of sender external id NEVER a raw identifier. */
34
+ /** SHA-256 first-N of sender external id, NEVER a raw identifier. */
35
35
  readonly fromDigest?: string;
36
36
  /** Conversation id (Slack/Discord channel, IMAP mailbox-scoped). */
37
37
  readonly conversationId?: string;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * lifecycle.ts resolve THIS binary's update-artifact identity for the SDK
2
+ * lifecycle.ts, resolve THIS binary's update-artifact identity for the SDK
3
3
  * DaemonServer facade's auto-update lifecycle.
4
4
  *
5
5
  * The SDK facade now runs the entire self-update loop itself when handed a
@@ -7,12 +7,12 @@
7
7
  * version against the release tags (no longer the version-blind sdk-package
8
8
  * comparison that used to restart-loop), swaps only at an idle moment, keeps
9
9
  * the outgoing file at `<path>.previous`, and leaves a receipt in the store it
10
- * serves on /status. When the artifact is ABSENT, updates are host-managed
10
+ * serves on /status. When the artifact is ABSENT, updates are host-managed,
11
11
  * the facade runs no loop (the safe embedded default).
12
12
  *
13
13
  * The one guard the facade does NOT apply is install-kind: it must never swap a
14
14
  * dev `bun run daemon` interpreter or a bun-global package install. This helper
15
- * is that guard it hands the facade an artifact ONLY for a compiled binary
15
+ * is that guard, it hands the facade an artifact ONLY for a compiled binary
16
16
  * install, so a dev run resolves to `undefined` (host-managed, no loop) and
17
17
  * only a real self-contained binary self-updates.
18
18
  */
@@ -23,13 +23,13 @@ import { detectInstallKind } from '../runtime/update-check.ts';
23
23
  export interface ResolveDaemonUpdateArtifactOptions {
24
24
  /** The executable to identify; defaults to process.execPath. */
25
25
  readonly execPath?: string;
26
- /** Injectable so tests pin a fixture version never the live build VERSION. */
26
+ /** Injectable so tests pin a fixture version, never the live build VERSION. */
27
27
  readonly version?: string;
28
28
  }
29
29
 
30
30
  /**
31
31
  * The update-artifact identity to hand the DaemonServer facade, or `undefined`
32
- * for a non-binary install (dev/source or bun-global package) in which case
32
+ * for a non-binary install (dev/source or bun-global package), in which case
33
33
  * the facade keeps updates host-managed and runs no swap loop.
34
34
  */
35
35
  export function resolveDaemonUpdateArtifact(
@@ -1,5 +1,5 @@
1
1
  /**
2
- * local-daemon-state.ts the two files a daemon writes about ITSELF.
2
+ * local-daemon-state.ts, the two files a daemon writes about ITSELF.
3
3
  *
4
4
  * `status` and `update` want to report things no control-plane verb answers:
5
5
  * how long the daemon has been up, whether the last start followed a crash,
@@ -11,7 +11,7 @@
11
11
  * <control-plane config dir>/control-plane/daemon-receipts.json
12
12
  *
13
13
  * They are READ here and never written, and the receipts are never marked
14
- * delivered `/status?receipts=consume` hands each receipt to the first
14
+ * delivered, `/status?receipts=consume` hands each receipt to the first
15
15
  * consuming reader exactly once, and a status command that quietly consumed
16
16
  * them would take them away from the surface they were written for.
17
17
  *
@@ -22,7 +22,7 @@
22
22
  * The SDK owns the writers (`platform/daemon/lifecycle-marker.ts`,
23
23
  * `platform/daemon/receipts.ts`) and its readers are not exported from the
24
24
  * published package this repository pins, so the readers below are this
25
- * repository's own bounded and content-validated the same way, and no more
25
+ * repository's own, bounded and content-validated the same way, and no more
26
26
  * trusting of the file than the SDK is. The shared-piece lane can re-point them
27
27
  * at the SDK's own readers once those are exported.
28
28
  */
@@ -42,7 +42,7 @@ export interface DaemonLifecycleMarker {
42
42
  readonly pid: number | undefined;
43
43
  readonly failedStarts: number;
44
44
  readonly version: string | undefined;
45
- /** The version an automatic rollback moved AWAY from the build that crash looped. */
45
+ /** The version an automatic rollback moved AWAY from, the build that crash looped. */
46
46
  readonly rejectedVersion: string | undefined;
47
47
  /** When an automatic rollback last restored the kept previous binary. */
48
48
  readonly autoRollbackAt: number | undefined;
@@ -164,7 +164,7 @@ export function readDaemonReceipts(
164
164
  }
165
165
 
166
166
  export interface LocalDaemonState {
167
- /** False when the caller asked about another machine nothing below was read. */
167
+ /** False when the caller asked about another machine, nothing below was read. */
168
168
  readonly available: boolean;
169
169
  /** Why it is unavailable, when it is. */
170
170
  readonly unavailableReason: string | undefined;
@@ -195,7 +195,7 @@ export function describeLocalDaemonState(input: DescribeLocalDaemonStateInput):
195
195
  return {
196
196
  available: false,
197
197
  unavailableReason:
198
- 'the uptime, update receipts and rollback state are read from files on the daemon\'s own host '
198
+ 'the uptime, update receipts and rollback state are read from files on the daemon\'s own host; '
199
199
  + 'run this command on that machine to see them',
200
200
  marker: null,
201
201
  receipts: [],
@@ -219,7 +219,7 @@ export function describeLocalDaemonState(input: DescribeLocalDaemonStateInput):
219
219
  };
220
220
  }
221
221
 
222
- /** `3d 4h`, `4h 12m`, `12m 3s`, `9s` two units, never more. */
222
+ /** `3d 4h`, `4h 12m`, `12m 3s`, `9s`, two units, never more. */
223
223
  export function formatDuration(milliseconds: number): string {
224
224
  const totalSeconds = Math.max(0, Math.floor(milliseconds / 1000));
225
225
  const days = Math.floor(totalSeconds / 86_400);
@@ -1,9 +1,9 @@
1
1
  /**
2
- * pair-command.ts `goodvibes-daemon pair`.
2
+ * pair-command.ts, `goodvibes-daemon pair`.
3
3
  *
4
4
  * Two forms.
5
5
  *
6
- * LOCAL FORM `pair` with no `--host`, or one naming this machine reprints
6
+ * LOCAL FORM, `pair` with no `--host`, or one naming this machine, reprints
7
7
  * the pairing block a daemon prints once as it starts: the web origin, the
8
8
  * offers a new device can accept, what it will be able to do, and a QR code
9
9
  * encoding the deep link that opens the web app already signed in. It reads
@@ -12,13 +12,13 @@
12
12
  * resolve to) and reprints the EXISTING token rather than minting a new one,
13
13
  * so a link printed here and the one printed at boot are the same link.
14
14
  *
15
- * REMOTE FORM `pair --host <name> [--port] [--token]` asks THAT daemon to
15
+ * REMOTE FORM, `pair --host <name> [--port] [--token]`, asks THAT daemon to
16
16
  * MINT A NEW per-device pairing token over `pairing.handoff.create` and prints
17
17
  * the pairing block for it. Minting is a different act than reprinting: it is
18
18
  * a fresh token, and every token that daemon already issued (its own shared
19
19
  * token included) is left exactly as it was. Because it changes state on a
20
20
  * daemon that may not be this process's own, it states the plan and asks for
21
- * confirmation before acting `-y`/`--yes` satisfies that non-interactively,
21
+ * confirmation before acting, `-y`/`--yes` satisfies that non-interactively,
22
22
  * the same convention `migrate-service` uses. An unreachable daemon, a
23
23
  * rejected token, and a daemon too old to serve the verb are each refused by
24
24
  * name (see `callDaemonWsVerb`), never a stack trace.
@@ -84,7 +84,7 @@ function failure(error: string, fix: string, json: boolean): DaemonCommandResult
84
84
  /**
85
85
  * The web origin a remote handoff's deep link opens, recovered from the link
86
86
  * itself. `buildPairingHandoffLink` in the SDK builds the link as exactly
87
- * `<webOrigin, trailing slashes stripped>/#<params>` so slicing at the
87
+ * `<webOrigin, trailing slashes stripped>/#<params>`, so slicing at the
88
88
  * first `/#` is the precise inverse, and feeding the result back into
89
89
  * `renderPairingBanner` (which rebuilds the SAME link from origin + token +
90
90
  * offers) reproduces byte-for-byte what the remote daemon already returned.
@@ -103,19 +103,19 @@ interface PairingHandoffOfferDetail {
103
103
  readonly available?: boolean;
104
104
  }
105
105
 
106
- /** The shape `pairing.handoff.create` returns see routes/pairing-handoff.ts. */
106
+ /** The shape `pairing.handoff.create` returns, see routes/pairing-handoff.ts. */
107
107
  interface PairingHandoffCreateResult {
108
108
  readonly token: {
109
109
  readonly id: string;
110
110
  readonly name: string;
111
- /** The plaintext secret returned exactly once. */
111
+ /** The plaintext secret, returned exactly once. */
112
112
  readonly token: string;
113
113
  readonly createdAt: number;
114
114
  };
115
115
  readonly offers: readonly PairingHandoffOfferDetail[];
116
- /** `#pair=<token>&offers=...` present even when no web origin is configured. */
116
+ /** `#pair=<token>&offers=...`, present even when no web origin is configured. */
117
117
  readonly fragment: string;
118
- /** `<webOrigin>/#pair=...` present only when that daemon has a web origin configured. */
118
+ /** `<webOrigin>/#pair=...`, present only when that daemon has a web origin configured. */
119
119
  readonly deepLink?: string;
120
120
  }
121
121
 
@@ -127,7 +127,7 @@ function runLocalReprint(input: RunPairCommandInput): DaemonCommandResult {
127
127
  if (token === undefined) {
128
128
  return failure(
129
129
  'no operator token was found for this machine, so there is no link to print',
130
- 'start the daemon once it creates the token as it starts: goodvibes-daemon serve',
130
+ 'start the daemon once; it creates the token as it starts: goodvibes-daemon serve',
131
131
  flags.json,
132
132
  );
133
133
  }
@@ -175,7 +175,7 @@ function mintPlanResult(target: RemoteDaemonTarget, json: boolean): DaemonComman
175
175
  `pair --host: this will MINT A NEW per-device pairing token on the daemon at ${target.baseUrl}`,
176
176
  'and print the pairing link/QR for that new token.',
177
177
  '',
178
- "That daemon's existing tokens its shared token and every other paired device are",
178
+ "That daemon's existing tokens (its shared token and every other paired device) are",
179
179
  'left exactly as they are: minting is a different act than reprinting, and this is a',
180
180
  'fresh token, not a link to one that already exists.',
181
181
  '',
@@ -208,15 +208,15 @@ async function runRemoteMint(input: RunPairCommandInput, target: RemoteDaemonTar
208
208
  const mintedLine = `minted a new per-device pairing token ("${data.token.name}") on the daemon at ${target.baseUrl}.`;
209
209
 
210
210
  if (data.deepLink === undefined) {
211
- // Honest degraded path: the mint itself succeeded the token is real and
212
- // usable but that daemon has no web origin configured, and this process
211
+ // Honest degraded path: the mint itself succeeded, the token is real and
212
+ // usable, but that daemon has no web origin configured, and this process
213
213
  // has no way to read or fabricate one for it. `resolvePairingWebOrigin` is
214
214
  // what the local form reads instead, and it is exactly the thing this
215
215
  // process cannot ask a REMOTE daemon for outside this verb's own reply.
216
216
  const lines = [
217
217
  mintedLine,
218
218
  '',
219
- 'that daemon has no web origin configured, so no deep link or QR could be built only',
219
+ 'that daemon has no web origin configured, so no deep link or QR could be built; only',
220
220
  'the raw token and pairing fragment:',
221
221
  '',
222
222
  ` token: ${data.token.token}`,
@@ -1,11 +1,11 @@
1
1
  // ---------------------------------------------------------------------------
2
- // provision-wake-model.ts `goodvibes-daemon provision-wake-model`.
2
+ // provision-wake-model.ts, `goodvibes-daemon provision-wake-model`.
3
3
  //
4
4
  // WHAT THIS IS FOR
5
5
  //
6
6
  // The curl installer needs to put the wake-word model on the machine, and it must
7
7
  // not hold the pinned URLs, byte counts or checksums to do it. Those live in ONE
8
- // place the SDK's wake-word manifest and a shell script that copied them
8
+ // place, the SDK's wake-word manifest, and a shell script that copied them
9
9
  // would be a second copy of a pin, drifting silently the first time the model is
10
10
  // retrained. So the installer runs the binary it just installed and lets the SDK
11
11
  // do what the SDK owns.
@@ -14,8 +14,8 @@
14
14
  //
15
15
  // The caller is an installer. A wake-word model is not a reason to fail installing
16
16
  // a coding tool, and an installer that aborts half-way through is worse than one
17
- // that finishes without a wake word. The outcome is printed either way one plain
18
- // line naming what happened and how to retry and a running daemon retries at
17
+ // that finishes without a wake word. The outcome is printed either way, one plain
18
+ // line naming what happened and how to retry, and a running daemon retries at
19
19
  // every boot. `--strict` is there for a caller that genuinely wants the exit code
20
20
  // to carry the result (a test, a provisioning script that is checking); the
21
21
  // installer does not pass it.
@@ -66,7 +66,7 @@ export async function runProvisionWakeModelCommand(
66
66
  const message = error instanceof Error ? error.message : String(error);
67
67
  return {
68
68
  exitCode: strict ? 1 : 0,
69
- lines: [`wake-word model: skipped ${message}`],
69
+ lines: [`wake-word model: skipped: ${message}`],
70
70
  };
71
71
  }
72
72
  const outcome = await provision({
@@ -1,5 +1,5 @@
1
1
  /**
2
- * channels.ts which channels `goodvibes-daemon send` can reach, and which one
2
+ * channels.ts, which channels `goodvibes-daemon send` can reach, and which one
3
3
  * it uses when the operator names none.
4
4
  *
5
5
  * The surface list, labels and required-setup keys are IMPORTED from
@@ -7,7 +7,7 @@
7
7
  * list` and `goodvibes-daemon send --list` can never disagree about what a
8
8
  * channel is called or what it needs. What this module adds on top is the two
9
9
  * facts that command needs and `SURFACE_CONFIGS` does not carry: the routable
10
- * `ChannelDeliverySurfaceKind` for each surface id (they differ in case
10
+ * `ChannelDeliverySurfaceKind` for each surface id (they differ in case,
11
11
  * `googleChat` vs `google-chat`), and the settings key holding each surface's
12
12
  * default destination.
13
13
  */
@@ -18,7 +18,7 @@ import { SURFACE_CONFIGS } from '../../cli/surface-catalog.ts';
18
18
  import { canRenderInert } from './inert-text.ts';
19
19
 
20
20
  /**
21
- * The settings key holding each supported surface's default destination the
21
+ * The settings key holding each supported surface's default destination, the
22
22
  * chat, channel, topic or URL a message goes to when `--to` is not given.
23
23
  *
24
24
  * These are the SAME keys the delivery strategies in the SDK's
@@ -58,7 +58,7 @@ const SURFACE_KIND_BY_ID: Readonly<Record<string, ChannelDeliverySurfaceKind>> =
58
58
  * This is naming, not a scheme: `--to` becomes `ChannelDeliveryTarget.address`,
59
59
  * which is the first thing every strategy in `strategies-core.ts`,
60
60
  * `strategies-bridge.ts` and `strategies-enterprise.ts` already checks before
61
- * falling back to its configured default. Nothing new is invented the label
61
+ * falling back to its configured default. Nothing new is invented, the label
62
62
  * exists so `--list` and the help can say "topic" where the channel says topic
63
63
  * and "chat id" where it says chat id, rather than making the operator work out
64
64
  * what an "address" is for ntfy.
@@ -120,7 +120,7 @@ export const SEND_CHANNELS: readonly SendChannel[] = SURFACE_CONFIGS
120
120
 
121
121
  /**
122
122
  * Look a channel up by the id an operator typed, accepting either the settings
123
- * id (`googleChat`) or the routable kind (`google-chat`) the two spellings
123
+ * id (`googleChat`) or the routable kind (`google-chat`), the two spellings
124
124
  * are both visible in this product's own output, and making the operator
125
125
  * remember which one this command wants would be a trap.
126
126
  */
@@ -172,7 +172,7 @@ export type DefaultChannelResolution =
172
172
  /**
173
173
  * Which channel a `send` with no `--channel` goes to.
174
174
  *
175
- * A channel qualifies when it is switched on AND has a destination configured
175
+ * A channel qualifies when it is switched on AND has a destination configured,
176
176
  * "enabled" alone is not enough, because an enabled surface with a blank
177
177
  * destination is a channel that would throw at the provider rather than deliver.
178
178
  *
@@ -180,7 +180,7 @@ export type DefaultChannelResolution =
180
180
  * no non-arbitrary winner, and this command has an outward effect: sending the
181
181
  * owner's message to the wrong one of his channels is worse than printing the
182
182
  * list and exiting non-zero. There is deliberately no priority order here to
183
- * silently break that tie a preference ordering baked into this file would be
183
+ * silently break that tie, a preference ordering baked into this file would be
184
184
  * an invisible decision about where his messages go.
185
185
  */
186
186
  export function resolveDefaultChannel(config: Pick<ConfigManager, 'get'>): DefaultChannelResolution {