@pellux/goodvibes-agent 1.8.0 → 1.8.2

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 (88) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +1 -1
  3. package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-44ar68xs.node} +0 -0
  4. package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-asvc2tv1.node} +0 -0
  5. package/dist/package/main.js +135434 -118764
  6. package/dist/package/{web-tree-sitter-vmqc5d26.wasm → web-tree-sitter-jbz042ba.wasm} +0 -0
  7. package/docs/tools-and-commands.md +9 -0
  8. package/package.json +3 -2
  9. package/src/agent/capability-registry.ts +300 -0
  10. package/src/agent/email/style-reply-lane.ts +13 -4
  11. package/src/agent/memory-consolidation-receipts.ts +79 -0
  12. package/src/agent/memory-prompt.ts +3 -1
  13. package/src/agent/operator-gateway-call.ts +134 -0
  14. package/src/agent/principal-attribution.ts +97 -0
  15. package/src/agent/prompt-context-receipts.ts +5 -1
  16. package/src/agent/unified-inbox.ts +17 -1
  17. package/src/cli/bundle-command.ts +2 -1
  18. package/src/cli/channel-profiles-command.ts +168 -0
  19. package/src/cli/ci-command.ts +214 -0
  20. package/src/cli/completion.ts +5 -0
  21. package/src/cli/connected-host-metrics.ts +165 -0
  22. package/src/cli/entrypoint.ts +25 -1
  23. package/src/cli/fleet-command.ts +178 -0
  24. package/src/cli/help.ts +115 -0
  25. package/src/cli/import-command.ts +208 -0
  26. package/src/cli/index.ts +1 -0
  27. package/src/cli/management-commands.ts +9 -8
  28. package/src/cli/management.ts +42 -0
  29. package/src/cli/memory-command.ts +4 -3
  30. package/src/cli/openclaw-import.ts +382 -0
  31. package/src/cli/operator-command-args.ts +88 -0
  32. package/src/cli/parser.ts +12 -0
  33. package/src/cli/personas-command.ts +3 -2
  34. package/src/cli/principals-command.ts +230 -0
  35. package/src/cli/profiles-command.ts +2 -1
  36. package/src/cli/relay-command.ts +100 -0
  37. package/src/cli/routines-command.ts +4 -3
  38. package/src/cli/skill-bundle-command.ts +3 -2
  39. package/src/cli/skills-command.ts +3 -2
  40. package/src/cli/status.ts +114 -2
  41. package/src/cli/temporal-label.ts +89 -0
  42. package/src/cli/types.ts +7 -0
  43. package/src/cli/workspaces-command.ts +135 -0
  44. package/src/config/agent-settings-policy.ts +8 -0
  45. package/src/config/checkpoint-settings.ts +113 -0
  46. package/src/config/workspace-registration.ts +299 -0
  47. package/src/core/rewind-turn-anchors.ts +82 -0
  48. package/src/input/agent-workspace-categories.ts +1 -0
  49. package/src/input/agent-workspace-channel-triage.ts +27 -8
  50. package/src/input/agent-workspace-local-library-snapshot.ts +5 -1
  51. package/src/input/agent-workspace-onboarding-categories.ts +17 -1
  52. package/src/input/agent-workspace-snapshot.ts +25 -3
  53. package/src/input/agent-workspace-types.ts +5 -5
  54. package/src/input/commands/connected-host-admin-runtime.ts +75 -0
  55. package/src/input/commands.ts +2 -0
  56. package/src/input/settings-modal-types.ts +16 -5
  57. package/src/main.ts +17 -12
  58. package/src/permissions/approval-posture.ts +40 -5
  59. package/src/permissions/prompt.ts +35 -1
  60. package/src/renderer/settings-modal-helpers.ts +11 -0
  61. package/src/renderer/settings-modal.ts +21 -0
  62. package/src/renderer/terminal-escapes.ts +65 -12
  63. package/src/runtime/agent-runtime-events.ts +102 -3
  64. package/src/runtime/bootstrap-core.ts +111 -10
  65. package/src/runtime/bootstrap-external-services.ts +156 -0
  66. package/src/runtime/bootstrap.ts +73 -69
  67. package/src/runtime/context-accounting-source.ts +114 -0
  68. package/src/runtime/conversation-rewind-port.ts +133 -0
  69. package/src/runtime/index.ts +8 -28
  70. package/src/runtime/memory-consolidation-scheduler.ts +67 -0
  71. package/src/runtime/memory-consolidation-wiring.ts +56 -0
  72. package/src/runtime/memory-usage-wiring.ts +75 -0
  73. package/src/runtime/services.ts +257 -6
  74. package/src/runtime/tool-permission-safety.ts +18 -1
  75. package/src/shell/blocking-input.ts +38 -8
  76. package/src/shell/startup-wiring.ts +26 -1
  77. package/src/tools/agent-context-policy.ts +1 -1
  78. package/src/tools/agent-harness-cli-command-policy.ts +29 -3
  79. package/src/tools/agent-harness-learning-auto-promote.ts +7 -3
  80. package/src/tools/agent-harness-memory-posture.ts +57 -2
  81. package/src/tools/agent-harness-metadata.ts +12 -0
  82. package/src/tools/agent-harness-mode-catalog.ts +1 -1
  83. package/src/tools/agent-harness-personal-ops-lanes.ts +8 -6
  84. package/src/tools/agent-harness-prompt-context.ts +3 -1
  85. package/src/tools/agent-harness-tool.ts +3 -2
  86. package/src/tools/agent-policy-explanation.ts +40 -0
  87. package/src/tools/tool-definition-compaction.ts +2 -0
  88. package/src/version.ts +1 -1
@@ -180,6 +180,12 @@ Foreground `exec` remains the default for tests, builds, and one-shot commands.
180
180
 
181
181
  Settings discovery accepts `settings action:"list"` with `category`, `prefix`, `query`, `includeHidden:true`, and `limit`. It is compact by default and each row includes a short first-class `modelRoute` that distinguishes read-only settings from set/reset-capable settings; use `includeParameters:true` or `settings action:"get"` for full descriptions/defaults. Single setting reads/writes resolve by `key`, `target`, or `query`; ambiguous matches are refused. Secret-backed setting writes through `settings action:"set"` store raw values through the secret manager and return redacted output. Shared GoodVibes settings import is `settings action:"import"`: it previews by default and applies with `confirm:true explicitUserRequest:"..."`. The preview includes source-package ownership metadata so users can see that goodvibes-tui or another published GoodVibes platform store remains source-owned and is not mutated by Agent. `import_goodvibes_settings action:"preview|apply"`, `setup action:"import_settings"`, and `workspace action:"run" actionId:"import-goodvibes-tui-settings"` remain available for compatibility or visible form parity; lower-level `agent_harness mode:"run_workspace_action"` remains a detailed compatibility route. Connected-host lifecycle/listener settings are read-only in Agent.
182
182
 
183
+ Workspace-checkpoint root and retention guards are configured through a manual `checkpoints` block in `settings.json` (these are passed straight through to the SDK's workspace checkpoint manager and are not part of the discoverable settings catalog, so `settings action:"list"` does not surface them). The recognized keys are `checkpoints.preferGitRoot` (prefer the enclosing git repository's top level over the raw working directory; default on), `checkpoints.allowBroadRoot` (opt in to snapshotting a broad root such as the filesystem root, the home directory, or `~/.goodvibes`; default off), `checkpoints.allowLargeFirstSnapshot` (opt in to a first snapshot whose full sweep exceeds the file ceiling; default off), `checkpoints.maxFirstSnapshotFiles` (the ceiling for the first-ever snapshot's file sweep), and `checkpoints.autoRetention` (run a retention sweep automatically after each successful checkpoint and once at init; default on). Any key you omit falls back to the manager's own default. These guards are defense in depth: even a registered workspace can still be refused as too broad or too large.
184
+
185
+ Automatic (turn-end/lifecycle) checkpoints are restricted to workspaces COVERED by the shared registration store (owner ruling, 2026-07-10; store migrated to the SDK's platform/workspace/registration in SDK 1.6.1). `goodvibes-agent workspaces list|register [path] [--label <label>] --yes|unregister [path] --yes` manages the registry (path defaults to the current working directory); it is user-scoped, agent-local state, not a connected-host concept. Coverage flows down a registered root's subtree and is inherited through the git worktree→main-repo link, so an orchestration-spawned sibling worktree of a registered repo checkpoints automatically without being registered itself. An unregistered/uncovered workspace gets no automatic checkpoints, and an explicit `checkpoints.create` gateway call against it is refused with a registration hint rather than silently registering the workspace on the caller's behalf. `checkpoints.unregisteredWorkspaces` (`"off"` default, or `"guarded"`) is the opt-out: `"guarded"` restores the pre-ruling behavior for that workspace (automatic checkpoints subscribe, explicit create proceeds) subject only to the root/retention guards above. `goodvibes-agent status`/`doctor` state the posture honestly — `checkpoints off: workspace not registered` when off, never silent.
186
+
187
+ Best-of-N attempt groups (SDK 1.6.1 orchestration engine) are reviewed and resolved with `goodvibes-agent fleet attempts list [--workstream <workstreamId>]|pick <groupId> <winnerItemId> --yes|judge <groupId>`. This reads this Agent's own orchestration engine state directly (disk-persisted workstream snapshots), not a connected-host call — `fleet.attempts.*` is a ws-only gateway verb family with no HTTP binding, so neither this CLI command nor `agent_operator_method` can reach it over HTTP. `list` shows every held-merge group (siblings that ran in isolated worktrees and are held for a winner pick instead of auto-merging) with each candidate's diff, usage, and any prior judge proposal. `judge` runs the optional judge model and PROPOSES a winner — always rendered as a clearly labeled model proposal, never a decision — and `pick` is the confirm-gated action that actually merges the winner and cleans losing worktrees.
188
+
183
189
  Keybinding discovery returns fixed shortcuts plus the live resolved binding table. Fixed shortcuts and configurable bindings include direct `modelRoute` and `modelAccess` metadata so the model can distinguish supported routes from direct-user-only controls. `workspace action:"run_keybinding"` only executes actions with faithful current-shell routes. Prompt-editor-only shortcuts, terminal text selection, category cycling, and reserved shortcuts stay direct user interaction.
184
190
 
185
191
  ## Connected Host And Daemon
@@ -265,7 +271,9 @@ Routine promotion is an explicit scheduling route. Local routines stay local unt
265
271
  | `/brief` | Show a concise Agent operator briefing and next actions. |
266
272
  | `/bundle` | Export, inspect, or import redacted Agent support bundles from the TUI. |
267
273
  | `/calendar` | Manage a local calendar: list upcoming events, import or export .ics files, and add or remove events. |
274
+ | `/channel-profiles` | List, set, or delete per-channel session profile bindings (model, provider, permission mode) on the connected host; mutations require `--yes`. |
268
275
  | `/channels` | Inspect channel readiness, delivery receipts, or send one explicitly confirmed delivery message. |
276
+ | `/ci` | Check a repo or PR's CI with per-job conclusions and manage standing CI watches with channel delivery; mutations require `--yes`. |
269
277
  | `/clear` | Clear the conversation display while keeping LLM context. |
270
278
  | `/collapse` | Collapse rendered blocks by type. |
271
279
  | `/commands` | Browse all commands in a scrollable list. |
@@ -298,6 +306,7 @@ Routine promotion is an explicit scheduling route. Local routines stay local unt
298
306
  | `/personas` | Manage Agent-local personas. |
299
307
  | `/pin` | Pin a model to the favorites list. |
300
308
  | `/prev-error` | Jump to the previous error message in the conversation. |
309
+ | `/principals` | Manage the connected host's cross-channel principal identity registry; unmapped senders show as "unknown principal" in session attribution. |
301
310
  | `/provider` | Switch provider or manage custom providers. |
302
311
  | `/qrcode` | Print companion pairing details and a QR code. |
303
312
  | `/quit` | Exit the application. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "private": false,
5
5
  "description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
6
6
  "type": "module",
@@ -92,7 +92,8 @@
92
92
  },
93
93
  "dependencies": {},
94
94
  "devDependencies": {
95
- "@pellux/goodvibes-sdk": "1.6.1",
95
+ "@pellux/goodvibes-sdk": "1.7.1",
96
+ "@pellux/goodvibes-terminal-shell": "1.7.1",
96
97
  "sql.js": "^1.14.1",
97
98
  "sqlite-vec": "^0.1.9",
98
99
  "zustand": "^5.0.12",
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Capability readiness registry — the single source of truth for how mature
3
+ * each advertised capability is.
4
+ *
5
+ * The agent advertises capabilities (voice, deep research, computer control,
6
+ * calendar, email, telephony, local models, and more) whose real maturity
7
+ * varies. This registry records, for every advertised capability, a declared
8
+ * readiness level that must be verifiable rather than aspirational.
9
+ *
10
+ * LEVELS (and nothing else):
11
+ * - `certified` A live-verification scenario exercises the capability and
12
+ * passed in the current release report. Each certified entry
13
+ * names the scenario id it maps to (see live-verifier.ts).
14
+ * - `working` Implemented and covered by unit/integration tests, but no
15
+ * live-verification scenario exercises it yet.
16
+ * - `needs-setup` Implemented, but the user must configure something
17
+ * (credentials, a provider, a device) before it can run.
18
+ * - `preview` Partial implementation. The note states plainly what works.
19
+ *
20
+ * A capability that would only be "planning-only" is NOT given a level here —
21
+ * it is removed from advertised surfaces instead, consistent with the honesty
22
+ * bar. So every entry below has a real, shipped surface behind it.
23
+ *
24
+ * Onboarding copy, help text, and the tool catalogs render the level FROM this
25
+ * registry (see renderCapabilityReadinessLine). No surface hand-writes a
26
+ * duplicate maturity claim.
27
+ */
28
+
29
+ export type CapabilityReadinessLevel = 'certified' | 'working' | 'needs-setup' | 'preview';
30
+
31
+ /** The four declared levels, in maturity order. */
32
+ export const CAPABILITY_READINESS_LEVELS: readonly CapabilityReadinessLevel[] = [
33
+ 'certified',
34
+ 'working',
35
+ 'needs-setup',
36
+ 'preview',
37
+ ];
38
+
39
+ /** Plain user-facing label for each level, rendered in parentheses. */
40
+ export const CAPABILITY_READINESS_USER_LABEL: Record<CapabilityReadinessLevel, string> = {
41
+ certified: 'verified live',
42
+ working: 'working',
43
+ 'needs-setup': 'needs setup',
44
+ preview: 'preview',
45
+ };
46
+
47
+ export interface AdvertisedCapability {
48
+ /** Stable capability id. */
49
+ readonly id: string;
50
+ /** Plain-language title shown to users. */
51
+ readonly title: string;
52
+ /** Declared readiness level. */
53
+ readonly level: CapabilityReadinessLevel;
54
+ /**
55
+ * The live-verification scenario id this capability maps to. Required for
56
+ * `certified`, and must be absent for every other level.
57
+ */
58
+ readonly scenarioId?: string;
59
+ /**
60
+ * Surface identifiers where this capability is advertised: onboarding
61
+ * category ids, model-tool names, and command names. Used to attach the
62
+ * rendered readiness line to the right surface.
63
+ */
64
+ readonly surfaces: readonly string[];
65
+ /**
66
+ * The literal capability nouns as they appear in user-facing copy. The
67
+ * enforcement tests grep advertised surfaces for these and require a
68
+ * registry entry, so a surface can never advertise a capability that has no
69
+ * declared level.
70
+ */
71
+ readonly advertisedNames: readonly string[];
72
+ /** Plain description of what works and what does not. */
73
+ readonly readinessNote: string;
74
+ }
75
+
76
+ export const CAPABILITY_REGISTRY: readonly AdvertisedCapability[] = [
77
+ {
78
+ id: 'models',
79
+ title: 'Model routing',
80
+ level: 'certified',
81
+ scenarioId: 'openai-compatible-models',
82
+ surfaces: ['setup', 'models'],
83
+ advertisedNames: ['model routing'],
84
+ readinessNote:
85
+ 'Cloud, subscription, and local model routing. A live check confirms the models route returns available models in each release report.',
86
+ },
87
+ {
88
+ id: 'agent-knowledge',
89
+ title: 'Agent Knowledge',
90
+ level: 'certified',
91
+ scenarioId: 'agent-knowledge-ask-isolated',
92
+ surfaces: ['onboarding-context', 'agent_knowledge'],
93
+ advertisedNames: ['Agent Knowledge', 'Knowledge'],
94
+ readinessNote:
95
+ 'Isolated per-home knowledge with source provenance. A live check exercises the isolated ask route in each release report, and answers fail closed when provenance is missing.',
96
+ },
97
+ {
98
+ id: 'local-model-cookbook',
99
+ title: 'Local model cookbook',
100
+ level: 'working',
101
+ surfaces: ['setup', 'models'],
102
+ advertisedNames: ['local model'],
103
+ readinessNote:
104
+ 'Hardware-scored local model recommendations scan processor, memory, and platform. Per-card memory and accelerator-model detection is not shipped.',
105
+ },
106
+ {
107
+ id: 'deep-research',
108
+ title: 'Deep research',
109
+ level: 'working',
110
+ surfaces: ['research'],
111
+ advertisedNames: ['research', 'deep research'],
112
+ readinessNote:
113
+ 'Research runs, source review, and saved reports are implemented and unit-tested. Browser-backed runs need a connected host.',
114
+ },
115
+ {
116
+ id: 'documents',
117
+ title: 'Documents and review packets',
118
+ level: 'working',
119
+ surfaces: ['documents'],
120
+ advertisedNames: ['documents'],
121
+ readinessNote:
122
+ 'Versioned drafts, suggestions, exports, and reviewer handoff packets are implemented and unit-tested.',
123
+ },
124
+ {
125
+ id: 'blind-model-comparison',
126
+ title: 'Model comparison',
127
+ level: 'working',
128
+ surfaces: ['documents', 'models'],
129
+ advertisedNames: ['model comparison'],
130
+ readinessNote:
131
+ 'Blind comparison with delayed reveal, saved judgments, and confirmed route decisions are implemented and unit-tested.',
132
+ },
133
+ {
134
+ id: 'schedules-automation',
135
+ title: 'Schedules and automation',
136
+ level: 'working',
137
+ surfaces: ['automation', 'schedule'],
138
+ advertisedNames: ['schedules', 'routines'],
139
+ readinessNote:
140
+ 'Schedules, reminders, and routines with a visible queue are implemented and unit-tested.',
141
+ },
142
+ {
143
+ id: 'local-context-memory',
144
+ title: 'Memory and personality',
145
+ level: 'working',
146
+ surfaces: ['onboarding-context', 'memory'],
147
+ advertisedNames: ['memory', 'personas', 'skills', 'routines'],
148
+ readinessNote:
149
+ 'Local memory, personas, skills, and routines with review-first promotion are implemented and unit-tested.',
150
+ },
151
+ {
152
+ id: 'execution-shell',
153
+ title: 'Local execution',
154
+ level: 'working',
155
+ surfaces: ['execution', 'terminal', 'process'],
156
+ advertisedNames: ['run commands', 'shell commands'],
157
+ readinessNote:
158
+ 'Local read, search, file edit, and bounded foreground commands are implemented and unit-tested.',
159
+ },
160
+ {
161
+ id: 'multi-agent-delegation',
162
+ title: 'Delegated work',
163
+ level: 'working',
164
+ surfaces: ['delegation'],
165
+ advertisedNames: ['delegation'],
166
+ readinessNote:
167
+ 'Visible serial-by-default subagent records are implemented and unit-tested. Remote execution backends are limited to a connected host.',
168
+ },
169
+ {
170
+ id: 'image-input',
171
+ title: 'Image input',
172
+ level: 'working',
173
+ surfaces: ['onboarding-voice-media'],
174
+ advertisedNames: ['image input'],
175
+ readinessNote:
176
+ 'Attaching an image is implemented and unit-tested. The selected model must support image understanding.',
177
+ },
178
+ {
179
+ id: 'messaging-channels',
180
+ title: 'Messaging',
181
+ level: 'needs-setup',
182
+ surfaces: ['onboarding-channels', 'channels'],
183
+ advertisedNames: ['messaging', 'Slack', 'Discord', 'Telegram'],
184
+ readinessNote:
185
+ 'Messaging adapters are implemented; each channel must be enabled and given credentials before it can deliver.',
186
+ },
187
+ {
188
+ id: 'voice',
189
+ title: 'Voice controls',
190
+ level: 'needs-setup',
191
+ surfaces: ['onboarding-voice-media', 'device'],
192
+ advertisedNames: ['voice'],
193
+ readinessNote:
194
+ 'Local listen-and-speak is implemented; it needs a microphone and a text-to-speech provider. Continuous talk mode and wake-word capture are not shipped.',
195
+ },
196
+ {
197
+ id: 'text-to-speech',
198
+ title: 'Spoken output',
199
+ level: 'needs-setup',
200
+ surfaces: ['onboarding-voice-media', 'device'],
201
+ advertisedNames: ['text-to-speech', 'TTS'],
202
+ readinessNote:
203
+ 'Spoken replies work once a text-to-speech provider is configured.',
204
+ },
205
+ {
206
+ id: 'media-generation',
207
+ title: 'Media generation',
208
+ level: 'needs-setup',
209
+ surfaces: ['onboarding-voice-media'],
210
+ advertisedNames: ['media generation'],
211
+ readinessNote:
212
+ 'Image and video generation is implemented; it needs configured media providers.',
213
+ },
214
+ {
215
+ id: 'telephony',
216
+ title: 'Telephony',
217
+ level: 'needs-setup',
218
+ surfaces: ['onboarding-voice-media', 'channels'],
219
+ advertisedNames: ['telephony', 'SMS'],
220
+ readinessNote:
221
+ 'SMS, voice call, and bridge delivery are backed by a real delivery strategy; they need Twilio or bridge credentials.',
222
+ },
223
+ {
224
+ id: 'email',
225
+ title: 'Email',
226
+ level: 'needs-setup',
227
+ surfaces: ['email', 'personal-ops'],
228
+ advertisedNames: ['email'],
229
+ readinessNote:
230
+ 'Direct IMAP and SMTP email is implemented; it needs account settings. Writing-style replies, auto-tagging, and spam triage are not shipped.',
231
+ },
232
+ {
233
+ id: 'calendar',
234
+ title: 'Calendar',
235
+ level: 'needs-setup',
236
+ surfaces: ['calendar', 'personal-ops'],
237
+ advertisedNames: ['calendar'],
238
+ readinessNote:
239
+ 'Calendar import and export through .ics files is implemented. Direct CalDAV server sync is not shipped.',
240
+ },
241
+ {
242
+ id: 'computer-control',
243
+ title: 'Computer control',
244
+ level: 'preview',
245
+ surfaces: ['computer'],
246
+ advertisedNames: ['computer control', 'desktop control'],
247
+ readinessNote:
248
+ 'Browser and desktop control is planning and readiness inspection in the agent today; actual control runs through a connected host once it certifies the route.',
249
+ },
250
+ ];
251
+
252
+ /** Return the plain user-facing label for a level (rendered in parentheses). */
253
+ export function capabilityReadinessUserLabel(level: CapabilityReadinessLevel): string {
254
+ return CAPABILITY_READINESS_USER_LABEL[level];
255
+ }
256
+
257
+ /** Look up a capability by its stable id. */
258
+ export function getCapabilityById(id: string): AdvertisedCapability | undefined {
259
+ return CAPABILITY_REGISTRY.find((capability) => capability.id === id);
260
+ }
261
+
262
+ /**
263
+ * Look up a capability by an advertised noun (case-insensitive). Used by the
264
+ * enforcement tests to pin that any advertised capability name resolves to a
265
+ * declared readiness level.
266
+ */
267
+ export function getCapabilityByAdvertisedName(name: string): AdvertisedCapability | undefined {
268
+ const needle = name.trim().toLowerCase();
269
+ return CAPABILITY_REGISTRY.find((capability) =>
270
+ capability.advertisedNames.some((advertised) => advertised.toLowerCase() === needle),
271
+ );
272
+ }
273
+
274
+ /** Every capability advertised on a given surface, in registry order. */
275
+ export function capabilitiesForSurface(surface: string): readonly AdvertisedCapability[] {
276
+ return CAPABILITY_REGISTRY.filter((capability) => capability.surfaces.includes(surface));
277
+ }
278
+
279
+ /** Inline readiness suffix for a single capability, e.g. " (needs setup)". */
280
+ export function capabilityReadinessSuffix(capability: AdvertisedCapability): string {
281
+ return ` (${capabilityReadinessUserLabel(capability.level)})`;
282
+ }
283
+
284
+ /**
285
+ * Render one plain readiness line for a surface, sourced entirely from the
286
+ * registry. Returns an empty string when no capability is advertised there,
287
+ * so a caller can append it unconditionally.
288
+ *
289
+ * Example: "Readiness: Voice controls (needs setup), Spoken output (needs
290
+ * setup), Image input (working), Media generation (needs setup), Telephony
291
+ * (needs setup)."
292
+ */
293
+ export function renderCapabilityReadinessLine(surface: string): string {
294
+ const capabilities = capabilitiesForSurface(surface);
295
+ if (capabilities.length === 0) return '';
296
+ const parts = capabilities.map(
297
+ (capability) => `${capability.title}${capabilityReadinessSuffix(capability)}`,
298
+ );
299
+ return `Readiness: ${parts.join(', ')}.`;
300
+ }
@@ -1,11 +1,20 @@
1
1
  /**
2
2
  * Personal Ops lane descriptor for the writing-style-matched draft-reply flow.
3
3
  *
4
+ * NOT SHIPPED / NOT WIRED (capability-honesty, 2026-07)
5
+ * ──────────────────────────────────────────────────────────────────────
6
+ * These descriptors are intentionally NOT wired into the advertised Personal
7
+ * Ops inbox lane (see agent-harness-personal-ops-lanes.ts). The Agent has no
8
+ * reader for the user's own sent-message corpus, so it cannot honestly claim a
9
+ * writing-style match, and competitive-feature-inventory.ts records
10
+ * writing-style-matched draft replies as "not yet shipped". The pure composer
11
+ * (style-reply.ts) and these descriptors stay as tested internal code for when
12
+ * the sent-corpus input and a real compose route ship together. Do not re-add
13
+ * buildStyleReplyLaneAdditions to buildLanes() until then.
14
+ *
4
15
  * This module defines the PersonalOpsWorkflow and PersonalOpsLiveRecord objects
5
- * that surface the style-reply capability inside the existing Personal Ops
6
- * 'inbox' lane. It is integrated into buildLanes() in
7
- * agent-harness-personal-ops-lanes.ts by appending the exported items to the
8
- * inbox lane's workflows and liveRecords arrays.
16
+ * that would surface the style-reply capability inside the Personal Ops 'inbox'
17
+ * lane once the capability is genuinely shipped.
9
18
  *
10
19
  * BEFORE-SEND REVIEW BOUNDARY (enforced here and in style-reply.ts)
11
20
  * ──────────────────────────────────────────────────────────────────────
@@ -0,0 +1,79 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import type { ShellPathService } from '@/runtime/index.ts';
4
+ import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
5
+ import type { MemoryConsolidationRunReceipt } from '@pellux/goodvibes-sdk/platform/state';
6
+
7
+ /**
8
+ * Durable, capped store of idle-consolidation run receipts. Same visible-receipt
9
+ * idiom as the prompt-context and per-phase consolidation receipts: an atomic
10
+ * JSON file under the Agent surface root, newest first, so every autonomous run
11
+ * leaves a record of what it merged, archived, decayed, and proposed.
12
+ */
13
+ const RUN_RECEIPT_LIMIT = 50;
14
+
15
+ export interface MemoryConsolidationReceiptFile {
16
+ readonly version: 1;
17
+ readonly receipts: readonly MemoryConsolidationRunReceipt[];
18
+ }
19
+
20
+ export function memoryConsolidationReceiptPath(shellPaths: ShellPathService): string {
21
+ return shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'learning', 'consolidation-run-receipts.json');
22
+ }
23
+
24
+ function isRunReceipt(value: unknown): value is MemoryConsolidationRunReceipt {
25
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
26
+ const record = value as Record<string, unknown>;
27
+ return typeof record.runId === 'string'
28
+ && typeof record.ranAt === 'string'
29
+ && Array.isArray(record.merged)
30
+ && Array.isArray(record.archived)
31
+ && Array.isArray(record.decayed)
32
+ && Array.isArray(record.proposed);
33
+ }
34
+
35
+ export class MemoryConsolidationReceiptStore {
36
+ public constructor(
37
+ private readonly shellPaths: ShellPathService,
38
+ private readonly limit = RUN_RECEIPT_LIMIT,
39
+ ) {}
40
+
41
+ public path(): string {
42
+ return memoryConsolidationReceiptPath(this.shellPaths);
43
+ }
44
+
45
+ public read(): readonly MemoryConsolidationRunReceipt[] {
46
+ const path = this.path();
47
+ if (!existsSync(path)) return [];
48
+ try {
49
+ const parsed: unknown = JSON.parse(readFileSync(path, 'utf-8'));
50
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return [];
51
+ const receipts = (parsed as Record<string, unknown>).receipts;
52
+ if (!Array.isArray(receipts)) return [];
53
+ return receipts.filter(isRunReceipt);
54
+ } catch {
55
+ return [];
56
+ }
57
+ }
58
+
59
+ public list(limit = 20): readonly MemoryConsolidationRunReceipt[] {
60
+ return this.read().slice(0, Math.max(1, Math.min(this.limit, Math.trunc(limit))));
61
+ }
62
+
63
+ public latest(): MemoryConsolidationRunReceipt | null {
64
+ return this.read()[0] ?? null;
65
+ }
66
+
67
+ public record(receipt: MemoryConsolidationRunReceipt): MemoryConsolidationRunReceipt {
68
+ const path = this.path();
69
+ const next: MemoryConsolidationReceiptFile = {
70
+ version: 1,
71
+ receipts: [receipt, ...this.read()].slice(0, this.limit),
72
+ };
73
+ mkdirSync(dirname(path), { recursive: true });
74
+ const tmpPath = `${path}.tmp`;
75
+ writeFileSync(tmpPath, `${JSON.stringify(next, null, 2)}\n`, 'utf-8');
76
+ renameSync(tmpPath, path);
77
+ return receipt;
78
+ }
79
+ }
@@ -164,7 +164,9 @@ export interface BuildReviewedMemoryPromptOptions {
164
164
 
165
165
  export function buildReviewedMemoryPrompt(memoryRegistry: MemoryRegistry, options: BuildReviewedMemoryPromptOptions = {}): string | null {
166
166
  const limit = options.limit ?? DEFAULT_LIMIT;
167
- const eligible = (options.records ?? memoryRegistry.getAll()).filter(isPromptActiveMemory);
167
+ // Bound to one arg — see prompt-context-receipts.ts's resolveMemoryRecords
168
+ // filter for why a bare `.filter(isPromptActiveMemory)` is unsafe here.
169
+ const eligible = (options.records ?? memoryRegistry.getAll()).filter((record) => isPromptActiveMemory(record));
168
170
  const ranking = rankMemoryForTurn(memoryRegistry, eligible, options.turnText);
169
171
  const records = ranking.records.slice(0, Math.max(0, limit));
170
172
 
@@ -0,0 +1,134 @@
1
+ /**
2
+ * A generic connected-host operator gateway call helper for CLI subcommands
3
+ * that reach ci.*, principals.*, and channels.profiles.* over the daemon's
4
+ * operator gateway. This mirrors the exact connection resolution and error
5
+ * classification pattern established in routine-schedule-promotion.ts
6
+ * (resolveAgentConnectedHostConnection + classifyScheduleError) so every
7
+ * connected-host CLI command reports failures the same way: auth_required
8
+ * when no token is on disk, connected_host_unavailable when the fetch itself
9
+ * fails, connected_host_route_unavailable / connected_host_incompatible when
10
+ * the method route answers 404, and connected_host_error otherwise.
11
+ */
12
+ import { createBrowserGoodVibesSdk } from '@pellux/goodvibes-sdk/browser';
13
+ import type { OperatorMethodId } from '@pellux/goodvibes-sdk/contracts';
14
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
15
+ import type { AgentConnectedHostConnection } from './routine-schedule-promotion.ts';
16
+
17
+ export type OperatorGatewayCallFailureKind =
18
+ | 'auth_required'
19
+ | 'connected_host_unavailable'
20
+ | 'connected_host_incompatible'
21
+ | 'connected_host_route_unavailable'
22
+ | 'connected_host_error';
23
+
24
+ export interface OperatorGatewayCallSuccess<T> {
25
+ readonly ok: true;
26
+ readonly data: T;
27
+ readonly methodId: OperatorMethodId;
28
+ readonly route: string;
29
+ }
30
+
31
+ export interface OperatorGatewayCallFailure {
32
+ readonly ok: false;
33
+ readonly kind: OperatorGatewayCallFailureKind;
34
+ readonly error: string;
35
+ readonly methodId: OperatorMethodId;
36
+ readonly route: string;
37
+ readonly baseUrl?: string;
38
+ }
39
+
40
+ export type OperatorGatewayCallResult<T> = OperatorGatewayCallSuccess<T> | OperatorGatewayCallFailure;
41
+
42
+ async function fetchConnectedHostStatus(connection: AgentConnectedHostConnection): Promise<{
43
+ readonly ok: boolean;
44
+ readonly status: number;
45
+ readonly body: unknown;
46
+ }> {
47
+ try {
48
+ const response = await fetch(`${connection.baseUrl}/status`, {
49
+ headers: connection.token ? { authorization: `Bearer ${connection.token}` } : undefined,
50
+ });
51
+ const text = await response.text();
52
+ let body: unknown = text;
53
+ try {
54
+ body = text.trim() ? JSON.parse(text) as unknown : {};
55
+ } catch {
56
+ body = text;
57
+ }
58
+ return { ok: response.ok, status: response.status, body };
59
+ } catch (error) {
60
+ return { ok: false, status: 0, body: summarizeError(error) };
61
+ }
62
+ }
63
+
64
+ async function classifyOperatorGatewayError(
65
+ error: unknown,
66
+ connection: AgentConnectedHostConnection,
67
+ methodId: OperatorMethodId,
68
+ route: string,
69
+ ): Promise<OperatorGatewayCallFailure> {
70
+ const message = summarizeError(error);
71
+ const lower = message.toLowerCase();
72
+ if (lower.includes('401') || lower.includes('unauthorized') || lower.includes('auth')) {
73
+ return { ok: false, kind: 'auth_required', error: message, methodId, route, baseUrl: connection.baseUrl };
74
+ }
75
+ if (lower.includes('404') || lower.includes('not found')) {
76
+ const connectedHost = await fetchConnectedHostStatus(connection);
77
+ if (connectedHost.ok) {
78
+ return {
79
+ ok: false,
80
+ kind: 'connected_host_incompatible',
81
+ error: `Connected GoodVibes host compatibility does not satisfy Agent requirements; ${methodId} is unavailable.`,
82
+ methodId,
83
+ route,
84
+ baseUrl: connection.baseUrl,
85
+ };
86
+ }
87
+ return { ok: false, kind: 'connected_host_route_unavailable', error: message, methodId, route, baseUrl: connection.baseUrl };
88
+ }
89
+ if (lower.includes('fetch') || lower.includes('connect') || lower.includes('econnrefused')) {
90
+ return { ok: false, kind: 'connected_host_unavailable', error: message, methodId, route, baseUrl: connection.baseUrl };
91
+ }
92
+ return { ok: false, kind: 'connected_host_error', error: message, methodId, route, baseUrl: connection.baseUrl };
93
+ }
94
+
95
+ /**
96
+ * Invoke one operator gateway method against the connected host, returning a
97
+ * discriminated result instead of throwing. `route` is an informational HTTP
98
+ * route label used in error/preview output; the SDK's operator.invoke resolves
99
+ * the actual HTTP method/path from the operator contract itself.
100
+ */
101
+ export async function invokeOperatorGatewayMethod<T>(
102
+ connection: AgentConnectedHostConnection,
103
+ methodId: OperatorMethodId,
104
+ route: string,
105
+ payload: unknown,
106
+ ): Promise<OperatorGatewayCallResult<T>> {
107
+ if (!connection.token) {
108
+ return {
109
+ ok: false,
110
+ kind: 'auth_required',
111
+ error: `No connected-host operator token found at ${connection.tokenPath}`,
112
+ methodId,
113
+ route,
114
+ baseUrl: connection.baseUrl,
115
+ };
116
+ }
117
+ try {
118
+ const sdk = createBrowserGoodVibesSdk({ baseUrl: connection.baseUrl, authToken: connection.token });
119
+ const data = await sdk.operator.invoke(methodId, payload as never) as T;
120
+ return { ok: true, data, methodId, route };
121
+ } catch (error) {
122
+ return classifyOperatorGatewayError(error, connection, methodId, route);
123
+ }
124
+ }
125
+
126
+ export function formatOperatorGatewayFailure(failure: OperatorGatewayCallFailure): string {
127
+ const lines = [
128
+ `Connected-host call failed: ${failure.methodId} (${failure.kind})`,
129
+ ` route ${failure.route}`,
130
+ failure.baseUrl ? ` connected host ${failure.baseUrl}` : '',
131
+ ` ${failure.error}`,
132
+ ];
133
+ return lines.filter(Boolean).join('\n');
134
+ }