@pellux/goodvibes-daemon 1.28.7 → 1.28.10
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
CHANGED
|
@@ -6,6 +6,76 @@ All notable changes to the GoodVibes daemon.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.28.10] - 2026-08-05
|
|
10
|
+
|
|
11
|
+
### Changes
|
|
12
|
+
|
|
13
|
+
- **Fixed: this daemon's answers match its own contract.** Profile field
|
|
14
|
+
reads leaked internal properties past a strict schema, so up-to-date
|
|
15
|
+
clients refused every profile answer and the platform could not tell its
|
|
16
|
+
owner his own address. Responses are explicit wire projections now, and a
|
|
17
|
+
conformance test runs the real routes against the contract (platform
|
|
18
|
+
runtime 2.0.8).
|
|
19
|
+
- **Fixed: one control-plane store.** A path defect made every boot rewrite
|
|
20
|
+
a legacy store nothing reads; state that still lived only at the legacy
|
|
21
|
+
location (occasions, workspace registrations) migrates to the daemon's
|
|
22
|
+
surface-scoped store on boot with a receipt, duplicates are quarantined
|
|
23
|
+
with receipts, and the legacy directory empties and is removed.
|
|
24
|
+
- **Changed: the daemon hosts conversation turns for the agent.** Hosted
|
|
25
|
+
session event streams are render-grade and session-scoped, so a client
|
|
26
|
+
rendering from the stream sees what the model said and what it did — and
|
|
27
|
+
agent conversations become genuinely cross-visible with their messages.
|
|
28
|
+
- Changed: connecting Google is one action, with every needed scope in a
|
|
29
|
+
single consent and a live mail-and-calendar proof at the end; the settings
|
|
30
|
+
catalog now carries the calendar/google/email keys this daemon really
|
|
31
|
+
reads (platform runtime 2.0.8).
|
|
32
|
+
|
|
33
|
+
## [1.28.9] - 2026-08-02
|
|
34
|
+
|
|
35
|
+
### Changes
|
|
36
|
+
|
|
37
|
+
- Changed: a conversational turn is told to understand what it captures, not
|
|
38
|
+
just file it — an itinerary also means an away-span said back in plain
|
|
39
|
+
words, travelers who are people in the owner's life, and durable facts
|
|
40
|
+
about the destination, and the turn uses what it stored: it names
|
|
41
|
+
collisions with existing plans and offers the obviously useful next steps
|
|
42
|
+
once (a reminder before departure, weather at the destination). Anything
|
|
43
|
+
beyond the conversation is still proposed and waits for a yes (platform
|
|
44
|
+
runtime 2.0.7).
|
|
45
|
+
|
|
46
|
+
## [1.28.8] - 2026-08-02
|
|
47
|
+
|
|
48
|
+
### Changes
|
|
49
|
+
|
|
50
|
+
- **Fixed: a chat message gets an answer, never workflow paperwork.** The
|
|
51
|
+
conversation gate's decision that a channel message is conversation is now
|
|
52
|
+
authoritative — the review-wording heuristic that turned "I'll review the
|
|
53
|
+
route" in a transcript into a full write-review-fix-confirm chain can no
|
|
54
|
+
longer override it. When a chain does legitimately run, the reply sent back
|
|
55
|
+
over the channel carries what the agent actually found or did; chain status
|
|
56
|
+
lines stay in the operator progress feed. Duplicate replies from the two
|
|
57
|
+
completion reporters are gone (platform runtime 2.0.6).
|
|
58
|
+
- **Added: personal information shared in conversation gets captured.** A
|
|
59
|
+
conversational channel turn — which previously ran with no tools at all —
|
|
60
|
+
now carries the profile capture tool with per-run owner authority: a trip
|
|
61
|
+
itinerary pasted into Telegram lands in the owner profile's Plans section
|
|
62
|
+
with its dates, flights, travelers and confirmation number, and the reply
|
|
63
|
+
states concretely what was stored. Configurable via
|
|
64
|
+
`profile.conversationalCapture` (default on) and `profile.ownerChannels`
|
|
65
|
+
(defaults to the occasions nudge channel).
|
|
66
|
+
- **Fixed: a chat session is filed as a chat.** Channel-originated sessions
|
|
67
|
+
are classified by their channel instead of as TUI project sessions rooted
|
|
68
|
+
in a filesystem path, rollover re-derives the classification, and a sender
|
|
69
|
+
on the channel's owner allowlist is attributed as the owner principal
|
|
70
|
+
instead of unknown. The daemon now hands its channel ingress policy to the
|
|
71
|
+
session intake so that attribution works end to end.
|
|
72
|
+
- Fixed: this daemon's settings file is rewritten only by this daemon.
|
|
73
|
+
Clients reading the daemon settings tier migrate their in-memory view and
|
|
74
|
+
leave the file bytes untouched, so a newer client can no longer rewrite the
|
|
75
|
+
file out from under an older running daemon; the daemon's own migration
|
|
76
|
+
folds the rename, the receipt, and a reader floor into one atomic write
|
|
77
|
+
(platform runtime 2.0.6).
|
|
78
|
+
|
|
9
79
|
## [1.28.7] - 2026-08-02
|
|
10
80
|
|
|
11
81
|
### Changes
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mgd34msu/goodvibes-daemon/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://github.com/mgd34msu/goodvibes-daemon)
|
|
6
6
|
|
|
7
7
|
The GoodVibes daemon: one long-running process per machine that holds the control plane every
|
|
8
8
|
GoodVibes client talks to. It answers the operator verb families over HTTP, reads and replies on
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-daemon",
|
|
3
|
-
"version": "1.28.
|
|
3
|
+
"version": "1.28.10",
|
|
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",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"@anthropic-ai/bedrock-sdk": "^0.28.1",
|
|
66
66
|
"@anthropic-ai/sdk": "^0.82.0",
|
|
67
67
|
"@ast-grep/napi": "^0.42.0",
|
|
68
|
-
"@pellux/goodvibes-sdk": "2.0.
|
|
69
|
-
"@pellux/goodvibes-terminal-shell": "2.0.
|
|
68
|
+
"@pellux/goodvibes-sdk": "2.0.8",
|
|
69
|
+
"@pellux/goodvibes-terminal-shell": "2.0.8",
|
|
70
70
|
"bash-language-server": "^5.6.0",
|
|
71
71
|
"fuse.js": "^7.1.0",
|
|
72
72
|
"graphql": "^16.13.2",
|
|
@@ -102,6 +102,13 @@ export interface RuntimeServicesOptions {
|
|
|
102
102
|
export interface RuntimeServices {
|
|
103
103
|
readonly workingDirectory: string;
|
|
104
104
|
readonly homeDirectory: string;
|
|
105
|
+
/**
|
|
106
|
+
* The `.goodvibes/<surface root>/` segment this daemon's own state lives
|
|
107
|
+
* under — always GOODVIBES_DAEMON_SURFACE_ROOT here (config/surface.ts).
|
|
108
|
+
* Declared so a consumer ASKS for it instead of deriving a second one; the
|
|
109
|
+
* unscoped pre-split control-plane store is what deriving it twice produced.
|
|
110
|
+
*/
|
|
111
|
+
readonly surfaceRoot: string;
|
|
105
112
|
/** The declare-once session-storage handle every session reader and writer threads through. */
|
|
106
113
|
readonly surface: SessionSurface;
|
|
107
114
|
readonly shellPaths: ShellPathService;
|
package/src/runtime/services.ts
CHANGED
|
@@ -2,9 +2,10 @@ import { join } from 'node:path';
|
|
|
2
2
|
import { ServiceRegistry, SubscriptionManager, ToolLLM } from '@pellux/goodvibes-sdk/platform/config';
|
|
3
3
|
import { AutomationDeliveryManager, AutomationManager } from '@pellux/goodvibes-sdk/platform/automation';
|
|
4
4
|
import { ChannelPolicyManager } from '@pellux/goodvibes-sdk/platform/channels';
|
|
5
|
-
import { ApprovalBroker, GatewayMethodCatalog, SharedSessionBroker, buildSharedSessionAgentSpawnRoutingInput } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
5
|
+
import { ApprovalBroker, GatewayMethodCatalog, SharedSessionBroker, buildSharedSessionAgentSpawnRoutingInput, controlPlaneStorePath } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
6
6
|
import { AcpHostService } from '@pellux/goodvibes-sdk/platform/acp';
|
|
7
7
|
import { continuationChainOptions } from '@pellux/goodvibes-sdk/platform/agents';
|
|
8
|
+
import { PersonalCaptureHolder, conversationalTurnSpawnOptions } from '@pellux/goodvibes-sdk/platform/personal-capture';
|
|
8
9
|
import { resolvePairingWebOrigin } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
9
10
|
import { attachWsOnlyGatewayVerbHandlers } from '@pellux/goodvibes-terminal-shell';
|
|
10
11
|
import { composeMailDeps } from './mail-composition.ts';
|
|
@@ -131,7 +132,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
131
132
|
const { secretsManager, stepUpService, pairingTokens } = composeCredentialServices({
|
|
132
133
|
workingDirectory, homeDirectory, configManager,
|
|
133
134
|
daemonHomeDirectory: options.daemonHomeDirectory,
|
|
134
|
-
pairingTokenPath: shellPaths
|
|
135
|
+
pairingTokenPath: controlPlaneStorePath(shellPaths, GOODVIBES_DAEMON_SURFACE_ROOT, 'pairing-tokens.json'),
|
|
135
136
|
});
|
|
136
137
|
const subscriptionManager = new SubscriptionManager(shellPaths.resolveUserPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'subscriptions.json'));
|
|
137
138
|
const serviceRegistry = new ServiceRegistry(shellPaths.resolveProjectPath(GOODVIBES_DAEMON_SURFACE_ROOT, 'services.json'), {
|
|
@@ -205,6 +206,13 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
205
206
|
} = createAgentGraph({
|
|
206
207
|
runtimeBus: options.runtimeBus, workingDirectory, configManager, providerRegistry,
|
|
207
208
|
});
|
|
209
|
+
// The one late-binding holder for the personal-capture port. The gateway verb
|
|
210
|
+
// groups fill it (they own the owner-profile store and the occasions service),
|
|
211
|
+
// and the agent orchestrator reads it when it builds a run's tool registry.
|
|
212
|
+
// Registration happens before setDependencies in this file, but the registry
|
|
213
|
+
// is built per run, so by the time a conversational turn asks for `profile`
|
|
214
|
+
// the port is already in place.
|
|
215
|
+
const personalCapture = new PersonalCaptureHolder();
|
|
208
216
|
const hookDispatcher = new HookDispatcher({ agentManager, toolLLM, projectRoot: workingDirectory }, hookActivityTracker);
|
|
209
217
|
configManager.attachHookDispatcher(hookDispatcher);
|
|
210
218
|
const hookWorkbench = createHookWorkbench({
|
|
@@ -238,11 +246,23 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
238
246
|
getCategory: (name: string) => configManager.getCategory(name as never),
|
|
239
247
|
},
|
|
240
248
|
}),
|
|
249
|
+
// What the answering agent is given and what it is told. `restrictTools:
|
|
250
|
+
// true` with no tool list used to mean an EMPTY registry: the agent could
|
|
251
|
+
// emit text and nothing else, which is why a trip pasted into a channel
|
|
252
|
+
// was answered and stored nowhere. This names the tools a conversational
|
|
253
|
+
// turn actually needs (read, find, fetch, and the `profile` capture tool),
|
|
254
|
+
// supplies the instruction that makes recording part of answering, and
|
|
255
|
+
// carries the write authority for the surface the message arrived on.
|
|
256
|
+
// It also supplies the run's context, which is why the bare
|
|
257
|
+
// `context: shared-session:<id>` line that used to sit below is gone.
|
|
258
|
+
// Spread BEFORE the routing builder so an explicit tool list coming from
|
|
259
|
+
// a routing intent still wins — that builder emits `tools` only when it
|
|
260
|
+
// actually has one.
|
|
261
|
+
...conversationalTurnSpawnOptions(input, { configReader: configManager }),
|
|
241
262
|
// Spawn routing through the SDK's shared model-reference resolver
|
|
242
263
|
// (unique-across-registry auto-qualifies; ambiguous and unknown ids throw
|
|
243
264
|
// errors naming real candidates), against the live registry's models.
|
|
244
265
|
...buildSharedSessionAgentSpawnRoutingInput(input.routing, { restrictTools: true, modelCandidates: providerRegistry.listModels() }),
|
|
245
|
-
context: `shared-session:${input.sessionId}`,
|
|
246
266
|
});
|
|
247
267
|
return { agentId: record.id };
|
|
248
268
|
});
|
|
@@ -506,11 +526,22 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
506
526
|
// calendar.*/email.* are platform-served; these two let it register (mail-composition.ts).
|
|
507
527
|
const { emailServiceDeps, describeEmailConfigProblem } = composeMailDeps({ configManager, secretsManager });
|
|
508
528
|
attachWsOnlyGatewayVerbHandlers(gatewayMethods, {
|
|
529
|
+
// The surface segment every control-plane store path is built from
|
|
530
|
+
// (SDK control-plane-store-paths.ts). Required, not defaulted: a default
|
|
531
|
+
// is what let these stores write to the unscoped orphan directory.
|
|
532
|
+
surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT,
|
|
509
533
|
homeDirectory, emailServiceDeps, describeEmailConfigProblem, processRegistry,
|
|
510
534
|
// The registration-gated surface, not the raw manager: an explicit create in
|
|
511
535
|
// an unregistered workspace refuses with something actionable.
|
|
512
536
|
workspaceCheckpointManager: checkpointing.gatewayManager,
|
|
513
537
|
conversationRewindPort: createSessionConversationRewindPort(), sessionBroker, secretsManager, stepUpService,
|
|
538
|
+
// Fills the capture port with the owner-profile store and the occasions
|
|
539
|
+
// service, so the `profile` tool a conversational turn is given has
|
|
540
|
+
// somewhere real to write.
|
|
541
|
+
personalCapture,
|
|
542
|
+
// The session intake attributes a sender on this policy's owner allowlist
|
|
543
|
+
// to the owner principal instead of unknown.
|
|
544
|
+
channelPolicy,
|
|
514
545
|
approvalBroker, requestApproval: (input) => approvalBroker.requestApproval(input),
|
|
515
546
|
// approvals.raise — a surface CREATING an ask in this broker. Without it the
|
|
516
547
|
// verb is cataloged and unhandled, and a client whose prompt runs outside
|
|
@@ -596,6 +627,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
596
627
|
sandboxSessionRegistry,
|
|
597
628
|
workflowServices: workflow,
|
|
598
629
|
contextAccountingHolder,
|
|
630
|
+
// Without this the `profile` tool is never registered, and a conversational
|
|
631
|
+
// turn that was told to record what the owner said has nothing to call.
|
|
632
|
+
personalCapture,
|
|
599
633
|
});
|
|
600
634
|
|
|
601
635
|
// Continuity reads (recovery-file presence, last-session pointer) scoped to
|
|
@@ -628,6 +662,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
628
662
|
const services: RuntimeServices = {
|
|
629
663
|
workingDirectory,
|
|
630
664
|
homeDirectory,
|
|
665
|
+
surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT,
|
|
631
666
|
surface,
|
|
632
667
|
shellPaths,
|
|
633
668
|
workspaceTrustManager,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolveWorkspaceRegisterReadPath } from '@pellux/goodvibes-sdk/platform/workspace';
|
|
2
3
|
import type { ShellPathService } from '@/runtime/index.ts';
|
|
3
4
|
import {
|
|
4
5
|
normalizeWorkspaceRoot,
|
|
@@ -30,9 +31,24 @@ import {
|
|
|
30
31
|
|
|
31
32
|
export type StoreShellPaths = Pick<ShellPathService, 'resolveUserPath' | 'homeDirectory'>;
|
|
32
33
|
|
|
33
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Path of the shared store's JSON document — the same path the SDK's gateway
|
|
36
|
+
* verb group constructs its own store over.
|
|
37
|
+
*
|
|
38
|
+
* "The same path" is the whole contract, and it is why this goes through the
|
|
39
|
+
* SDK's resolver rather than spelling any location out. goodvibes-agent reads
|
|
40
|
+
* and writes this same file directly and the SDK's gateway writes it, so the
|
|
41
|
+
* register is cross-product state rather than the daemon's own — it lives in
|
|
42
|
+
* the shared tier (~/.goodvibes/shared/), which takes no surface root, so all
|
|
43
|
+
* three resolve one identical path. Until the boot fold has moved it, the
|
|
44
|
+
* resolver falls back to the pre-split location read-only, so this reader never
|
|
45
|
+
* reports an empty register and silently refuses every checkpoint.
|
|
46
|
+
*/
|
|
34
47
|
export function sharedWorkspaceRegistrationStorePath(shellPaths: StoreShellPaths): string {
|
|
35
|
-
|
|
48
|
+
// READ path: the shared tier when it is there, the pre-split location until
|
|
49
|
+
// the daemon's boot fold has moved it. This is a reader, so the fallback is
|
|
50
|
+
// read-only and nothing here ever writes to the legacy address.
|
|
51
|
+
return resolveWorkspaceRegisterReadPath(shellPaths, existsSync);
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '1.28.
|
|
9
|
+
let _version = '1.28.10';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
// Only trust a version read from OUR OWN package.json. A Bun single-file
|