@pellux/goodvibes-daemon 1.28.19 → 1.28.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -63
- package/README.md +24 -19
- package/bin/launcher-support.js +5 -5
- package/package.json +6 -4
- package/scripts/postinstall.js +8 -8
- package/src/cli/command-catalog.ts +22 -22
- package/src/cli/completion.ts +4 -4
- package/src/cli/help.ts +5 -5
- package/src/cli/index.ts +3 -3
- package/src/cli/parser.ts +2 -2
- package/src/cli/surface-catalog.ts +1 -1
- package/src/cli/types.ts +2 -2
- package/src/cluster/daemon-ws-call.ts +5 -5
- package/src/cluster/raw-reply-route.ts +5 -5
- package/src/config/checkpoint-settings.ts +7 -7
- package/src/config/config-key-guard.ts +22 -0
- package/src/config/run-daemon-config-migration.ts +3 -3
- package/src/config/secret-config.ts +7 -7
- package/src/config/surface.ts +3 -3
- package/src/core/pairing-banner.ts +5 -5
- package/src/daemon/cli.ts +45 -43
- package/src/daemon/config-command.ts +15 -15
- package/src/daemon/handlers/context.ts +1 -1
- package/src/daemon/handlers/contracts.ts +19 -4
- package/src/daemon/handlers/credentials.ts +1 -1
- package/src/daemon/handlers/drafts/draft-store.ts +3 -3
- package/src/daemon/handlers/drafts/register.ts +4 -4
- package/src/daemon/handlers/inbox/aggregator.ts +8 -8
- package/src/daemon/handlers/inbox/cursor-store.ts +10 -10
- package/src/daemon/handlers/inbox/index.ts +7 -7
- package/src/daemon/handlers/inbox/mapping.ts +2 -2
- package/src/daemon/handlers/inbox/poller.ts +5 -5
- package/src/daemon/handlers/inbox/provider-adapter.ts +8 -8
- package/src/daemon/handlers/inbox/providers/discord.ts +6 -6
- package/src/daemon/handlers/inbox/providers/email.ts +3 -3
- package/src/daemon/handlers/inbox/providers/imap-client.ts +1 -1
- package/src/daemon/handlers/inbox/providers/slack.ts +4 -4
- package/src/daemon/handlers/index.ts +18 -8
- package/src/daemon/handlers/payments/address-store.ts +54 -0
- package/src/daemon/handlers/payments/budget-store.ts +356 -0
- package/src/daemon/handlers/payments/card-store.ts +486 -0
- package/src/daemon/handlers/payments/checkout-handlers.ts +526 -0
- package/src/daemon/handlers/payments/index.ts +38 -0
- package/src/daemon/handlers/payments/merchant-judge.ts +57 -0
- package/src/daemon/handlers/payments/notifier.ts +112 -0
- package/src/daemon/handlers/payments/purchase-ledger.ts +108 -0
- package/src/daemon/handlers/payments/register.ts +518 -0
- package/src/daemon/handlers/register.ts +3 -3
- package/src/daemon/handlers/remote/backends/cloud-terminal.ts +9 -1
- package/src/daemon/handlers/remote/backends/process-runner.ts +1 -1
- package/src/daemon/handlers/remote/backends/ssh.ts +9 -1
- package/src/daemon/handlers/remote/backends/types.ts +2 -2
- package/src/daemon/handlers/remote/dispatcher.ts +3 -3
- package/src/daemon/handlers/remote/index.ts +1 -1
- package/src/daemon/handlers/remote/peer-registry.ts +62 -13
- package/src/daemon/handlers/routing/inbox-bridge.ts +5 -5
- package/src/daemon/handlers/routing/index.ts +1 -1
- package/src/daemon/handlers/routing/route-store.ts +1 -1
- package/src/daemon/handlers/routing/routing-resolver.ts +3 -3
- package/src/daemon/handlers/sqlite-store.ts +9 -9
- package/src/daemon/handlers/triage/index.ts +1 -1
- package/src/daemon/handlers/triage/integration.ts +3 -3
- package/src/daemon/handlers/triage/pipeline.ts +2 -2
- package/src/daemon/handlers/triage/scorer.ts +2 -2
- package/src/daemon/handlers/triage/tagger/discord.ts +3 -3
- package/src/daemon/handlers/triage/tagger/imap.ts +7 -7
- package/src/daemon/handlers/triage/tagger/index.ts +1 -1
- package/src/daemon/handlers/triage/tagger/shared.ts +3 -3
- package/src/daemon/handlers/triage/tagger/slack.ts +1 -1
- package/src/daemon/handlers/triage/types.ts +2 -2
- package/src/daemon/lifecycle.ts +5 -5
- package/src/daemon/local-daemon-state.ts +7 -7
- package/src/daemon/pair-command.ts +14 -14
- package/src/daemon/provision-wake-model.ts +5 -5
- package/src/daemon/send/channels.ts +7 -7
- package/src/daemon/send/command.ts +11 -11
- package/src/daemon/send/composition.ts +5 -5
- package/src/daemon/send/failure-text.ts +6 -6
- package/src/daemon/send/inert-text.ts +18 -18
- package/src/daemon/send/stdin.ts +3 -3
- package/src/daemon/service-commands.ts +32 -32
- package/src/daemon/sessions-command.ts +7 -7
- package/src/daemon/status-command.ts +22 -22
- package/src/daemon/webui-command.ts +14 -14
- package/src/runtime/boot-tasks.ts +1 -1
- package/src/runtime/browser-checkout-seam-holder.ts +55 -0
- package/src/runtime/cluster-composition.ts +9 -9
- package/src/runtime/cluster-group-composition.ts +7 -7
- package/src/runtime/conversation-rewind-port.ts +8 -8
- package/src/runtime/credential-composition.ts +2 -2
- package/src/runtime/daemon-handler-composition.ts +61 -4
- package/src/runtime/device-posture-composition.ts +10 -10
- package/src/runtime/disposal-wiring.ts +8 -8
- package/src/runtime/fleet-needs-input-push.ts +4 -4
- package/src/runtime/fleet-services.ts +1 -1
- package/src/runtime/hosted-session-composition.ts +13 -13
- package/src/runtime/index.ts +1 -1
- package/src/runtime/knowledge-services.ts +2 -2
- package/src/runtime/legacy-daemon-migration.ts +43 -43
- package/src/runtime/legacy-daemon-reconcile.ts +30 -30
- package/src/runtime/mail-composition.ts +6 -6
- package/src/runtime/notification-dispatch.ts +7 -7
- package/src/runtime/payments-composition.ts +187 -0
- package/src/runtime/plugin-composition.ts +7 -7
- package/src/runtime/runtime-services-types.ts +9 -9
- package/src/runtime/services.ts +41 -32
- package/src/runtime/trigger-services.ts +1 -1
- package/src/runtime/trust/checkpoint-eligibility.ts +5 -5
- package/src/runtime/trust/trust-gated-approvals.ts +9 -9
- package/src/runtime/update-check.ts +4 -4
- package/src/runtime/workspace-checkpointing.ts +6 -6
- package/src/testing/daemon-fixture.ts +11 -11
- package/src/testing/hosted-session-failures.ts +4 -4
- package/src/version.ts +2 -2
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* notification-dispatch.ts
|
|
2
|
+
* notification-dispatch.ts, how this daemon dispatches notices without a screen.
|
|
3
3
|
*
|
|
4
4
|
* The SDK's NotificationRouter decides where a domain notification goes and
|
|
5
|
-
* collapses bursts and batches. Its three targets
|
|
6
|
-
* `status_bar`, `panel_only
|
|
5
|
+
* collapses bursts and batches. Its three targets, `conversation`,
|
|
6
|
+
* `status_bar`, `panel_only`, are all SCREEN targets: an inline conversation
|
|
7
7
|
* line, a status bar, a panel. There is no channel member in that type, and
|
|
8
8
|
* there never was. Which means the router is a surface mechanism end to end,
|
|
9
9
|
* and this process has no screen.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* This module used to wire the router to every curated domain anyway, writing
|
|
12
12
|
* into a bounded ring whose `list()` had no caller anywhere in this repository.
|
|
13
13
|
* Six domains of events, for the daemon's whole lifetime, into a buffer nobody
|
|
14
|
-
* read
|
|
14
|
+
* read, and the type declaring it still described the ring as "the panel's
|
|
15
15
|
* live producer", for a product with no panels. That silent-success failure
|
|
16
16
|
* class is exactly what this module removes: the producer goes.
|
|
17
17
|
*
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* One notice had no such path: memory pressure. The MemoryGovernor measures the
|
|
25
25
|
* process it runs in, so the daemon's pressure is the daemon's own and no
|
|
26
|
-
* surface can report it
|
|
26
|
+
* surface can report it, and the daemon that ran out of memory is exactly the
|
|
27
27
|
* one that cannot tell you afterwards. It now goes out over the operator's
|
|
28
28
|
* configured notice destination (`notifications.webhookUrls`, the same list the
|
|
29
29
|
* bus bridge uses), and says so at its own level in the activity log when no
|
|
@@ -56,7 +56,7 @@ export interface DaemonNoticeChannel {
|
|
|
56
56
|
* one event type rather than a subscription to the domain: the tier change is
|
|
57
57
|
* the operator's business and the churn is not.
|
|
58
58
|
*
|
|
59
|
-
* Delivery failure is logged, never thrown
|
|
59
|
+
* Delivery failure is logged, never thrown, a webhook endpoint being down is
|
|
60
60
|
* not a reason for the process reporting memory pressure to also crash.
|
|
61
61
|
*/
|
|
62
62
|
export function wireMemoryPressureChannelNotice(
|
|
@@ -69,7 +69,7 @@ export function wireMemoryPressureChannelNotice(
|
|
|
69
69
|
const level = memoryPressureLevel(payload);
|
|
70
70
|
const line = memoryPressureLine(payload);
|
|
71
71
|
if (!channel.isConfigured()) {
|
|
72
|
-
// No destination configured. The notice still exists
|
|
72
|
+
// No destination configured. The notice still exists, at its own
|
|
73
73
|
// severity, where the operator looks when the daemon misbehaves.
|
|
74
74
|
if (level === 'critical') logger.error(line);
|
|
75
75
|
else if (level === 'warning') logger.warn(line);
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* payments-composition.ts, the payment capability inside THIS daemon.
|
|
3
|
+
*
|
|
4
|
+
* ── What was wrong ────────────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* All seven `payments.*` verbs were cataloged, advertised with a real HTTP
|
|
7
|
+
* route, and answered 501 NOT_INVOKABLE to every caller. The route existed, the
|
|
8
|
+
* descriptor was honest about its shape, and nothing was attached, so the webui
|
|
9
|
+
* and the desktop app could read the contract and never use it. The terminal app
|
|
10
|
+
* did not paper over this: it has no payments handlers either; what it has is a
|
|
11
|
+
* settings surface (`/payments card`) that writes four flat config keys, which
|
|
12
|
+
* is a per-surface stopgap, not a served capability.
|
|
13
|
+
*
|
|
14
|
+
* ── What this composes ────────────────────────────────────────────────────
|
|
15
|
+
*
|
|
16
|
+
* The SDK owns the capability (`platform/payments`) and states, in
|
|
17
|
+
* card-material.ts, that the store behind it "is implemented by the daemon
|
|
18
|
+
* against its own secret store". It ships no such implementation. So this
|
|
19
|
+
* composes the two stores that were missing, from paths the daemon already owns:
|
|
20
|
+
*
|
|
21
|
+
* - card metadata at `<home>/.goodvibes/<surface>/control-plane/payments-cards.json`,
|
|
22
|
+
* through `controlPlaneStorePath`, the one resolver every control-plane
|
|
23
|
+
* store path goes through, so it lands beside pairing-tokens.json rather
|
|
24
|
+
* than in the unscoped orphan directory that resolver exists to prevent;
|
|
25
|
+
* - card MATERIAL one field per key in the DAEMON secret tier, written with an
|
|
26
|
+
* explicit `scope: 'daemon'`. Explicit because a payments card key is not in
|
|
27
|
+
* the SDK's daemon-needed registry, so an omitted scope would file the
|
|
28
|
+
* material in the surface tier while the daemon reads the daemon tier, the
|
|
29
|
+
* exact split secret-config.ts's `defaultSecretBackedScope` documents as the
|
|
30
|
+
* mailbox-password failure and names "the same shape of failure at purchase
|
|
31
|
+
* time" for a card;
|
|
32
|
+
* - the purchase audit ledger beside the card file.
|
|
33
|
+
*
|
|
34
|
+
* ── The budget ledger is durable ──────────────────────────────────────────
|
|
35
|
+
*
|
|
36
|
+
* `BudgetLedger` used to be constructed empty here and never persisted, correct
|
|
37
|
+
* only while checkout stayed unattached (the sole writer of a spend record was
|
|
38
|
+
* the checkout flow, so there was nothing to persist). Now that checkout is
|
|
39
|
+
* wired below, `DurableBudgetLedger`
|
|
40
|
+
* (daemon/handlers/payments/budget-store.ts) is used instead: it loads its
|
|
41
|
+
* state from `payments-budget.json` beside the card and purchase files at
|
|
42
|
+
* construction and writes back after every reservation, commit and release, so
|
|
43
|
+
* a daemon restarted mid-day does not hand back a budget it already spent.
|
|
44
|
+
*
|
|
45
|
+
* ── The checkout pair, over the sdk 2.0.19 browser-checkout seam ──────────
|
|
46
|
+
*
|
|
47
|
+
* `payments.checkout.begin`/`.fillCard` need a `CardMaterialRedactor` bound to
|
|
48
|
+
* the SAME browser engine the `browser.*` verbs drive, and this daemon does not
|
|
49
|
+
* build that engine, `composeDaemonBrowser` does (control-plane/routes/
|
|
50
|
+
* browser-composition.ts), inside `registerGatewayVerbGroups`, which THIS
|
|
51
|
+
* composition runs before (see runtime/services.ts). `checkoutSeam` is
|
|
52
|
+
* therefore a GETTER, not a value: services.ts passes `onBrowserCheckout` to
|
|
53
|
+
* `attachWsOnlyGatewayVerbHandlers` wired to fill the SAME holder this getter
|
|
54
|
+
* reads (runtime/browser-checkout-seam-holder.ts), and `register.ts`'s checkout
|
|
55
|
+
* handlers read it fresh on every call rather than once at composition time.
|
|
56
|
+
*
|
|
57
|
+
* The rest of `PaymentsGatewayServiceImpl`'s dependencies this composition owns
|
|
58
|
+
* outright: `configBackedAddressStore` reads the shipping/billing addresses the
|
|
59
|
+
* owner profile already writes into `payments.*Address.*` config keys (see
|
|
60
|
+
* that module's header for the exact defect this closes), and
|
|
61
|
+
* `channelBackedPaymentNotifier`/`createProviderBackedMerchantJudgeModel` adapt
|
|
62
|
+
* this daemon's channel router and provider registry to the ports the SDK
|
|
63
|
+
* declares. The untrusted-content ledger is the SAME process-wide singleton the
|
|
64
|
+
* browser composition binds its engine to (`getProcessUntrustedContentLedger`),
|
|
65
|
+
* never a private one, for the reason browser-composition.ts's header gives:
|
|
66
|
+
* a private ledger would make cross-capability derivation invisible.
|
|
67
|
+
*
|
|
68
|
+
* `channelBackedPaymentNotifier`'s own header names the one piece deliberately
|
|
69
|
+
* left for a later pass: no live inbound-reply correlation, so every purchase
|
|
70
|
+
* settles on the windows' own silence rules rather than an early answer. That
|
|
71
|
+
* is a scoped, disclosed gap, not a silent one.
|
|
72
|
+
*/
|
|
73
|
+
import { controlPlaneStorePath } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
74
|
+
import type { GatewayMethodCatalog } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
75
|
+
import { createModelMerchantJudge, readCvvHandling } from '@pellux/goodvibes-sdk/platform/payments';
|
|
76
|
+
import type { BudgetLedger, PaymentsConfigReader } from '@pellux/goodvibes-sdk/platform/payments';
|
|
77
|
+
import { getProcessUntrustedContentLedger } from '@pellux/goodvibes-sdk/platform/security';
|
|
78
|
+
import type { ConfigManager, SecretsManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
79
|
+
import type { ChannelDeliveryRouter } from '@pellux/goodvibes-sdk/platform/channels';
|
|
80
|
+
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
81
|
+
import type { ShellPathService } from '@/runtime/index.ts';
|
|
82
|
+
import {
|
|
83
|
+
DaemonCardStore,
|
|
84
|
+
DaemonPurchaseLedger,
|
|
85
|
+
DurableBudgetLedger,
|
|
86
|
+
channelBackedPaymentNotifier,
|
|
87
|
+
configBackedAddressStore,
|
|
88
|
+
createProviderBackedMerchantJudgeModel,
|
|
89
|
+
registerPaymentsMethods,
|
|
90
|
+
type CheckoutComposition,
|
|
91
|
+
type PaymentsSecretStore,
|
|
92
|
+
} from '../daemon/handlers/payments/index.ts';
|
|
93
|
+
import { GOODVIBES_DAEMON_SURFACE_ROOT } from '../config/surface.ts';
|
|
94
|
+
import type { BrowserCheckoutSeamHolder } from './browser-checkout-seam-holder.ts';
|
|
95
|
+
|
|
96
|
+
export interface PaymentsCompositionOptions {
|
|
97
|
+
readonly configManager: ConfigManager;
|
|
98
|
+
readonly shellPaths: ShellPathService;
|
|
99
|
+
readonly secretsManager: SecretsManager;
|
|
100
|
+
/** Binding the catalog is what turns the family from a 501 facade into handlers. */
|
|
101
|
+
readonly gatewayMethods: GatewayMethodCatalog;
|
|
102
|
+
/**
|
|
103
|
+
* Whether this node is the one currently allowed to spend.
|
|
104
|
+
*
|
|
105
|
+
* Reported by `payments.budget.status` and never defaulted, see the SDK's
|
|
106
|
+
* gates.ts: on a clustered install the wrong answer is a double-spend.
|
|
107
|
+
*/
|
|
108
|
+
readonly isPaymentsLeader: () => boolean;
|
|
109
|
+
/** Where the checkout pair reads the browser-checkout seam; see this file's header. */
|
|
110
|
+
readonly checkoutSeam: BrowserCheckoutSeamHolder['get'];
|
|
111
|
+
/** Delivers a purchase notice; the SAME router every other channel send in this daemon uses. */
|
|
112
|
+
readonly channelDeliveryRouter: Pick<ChannelDeliveryRouter, 'deliver'>;
|
|
113
|
+
/** Judges an unfamiliar merchant's recourse through the currently configured model. */
|
|
114
|
+
readonly providerRegistry: Pick<ProviderRegistry, 'getCurrentModel' | 'getForModel'>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface PaymentsServices {
|
|
118
|
+
readonly cards: DaemonCardStore;
|
|
119
|
+
readonly purchases: DaemonPurchaseLedger;
|
|
120
|
+
readonly budget: BudgetLedger;
|
|
121
|
+
/** Detaches the handlers. Held by the runtime disposal scope. */
|
|
122
|
+
readonly unregister: () => void;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The narrow secret port the card store gets: three operations over the daemon
|
|
127
|
+
* tier, and no way to reach any other credential in the process. The same
|
|
128
|
+
* treatment cluster-group-composition.ts gives the group key.
|
|
129
|
+
*/
|
|
130
|
+
function daemonScopedSecrets(secretsManager: SecretsManager): PaymentsSecretStore {
|
|
131
|
+
return {
|
|
132
|
+
get: (key) => secretsManager.get(key),
|
|
133
|
+
set: async (key, value) => {
|
|
134
|
+
await secretsManager.set(key, value, { scope: 'daemon', medium: 'secure' });
|
|
135
|
+
},
|
|
136
|
+
delete: async (key) => {
|
|
137
|
+
await secretsManager.delete(key, { scope: 'daemon' });
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Read live, per call: a budget raised five minutes ago applies to the next read. */
|
|
143
|
+
function livePaymentsConfig(configManager: ConfigManager): PaymentsConfigReader {
|
|
144
|
+
return { get: (key: string) => configManager.get(key as Parameters<ConfigManager['get']>[0]) };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Build the payment stores and bind the answerable verbs to them.
|
|
149
|
+
*
|
|
150
|
+
* The card and purchase stores read lazily and write only when a verb asks
|
|
151
|
+
* them to, so composing either by itself creates no file activity. The budget
|
|
152
|
+
* ledger is different: `DurableBudgetLedger`'s constructor reads
|
|
153
|
+
* `payments-budget.json` synchronously to load today's pools
|
|
154
|
+
* (daemon/handlers/payments/budget-store.ts), so constructing the result of
|
|
155
|
+
* THIS function does touch disk, once, for that one file, before any verb is
|
|
156
|
+
* ever called.
|
|
157
|
+
*/
|
|
158
|
+
export function createPaymentsServices(options: PaymentsCompositionOptions): PaymentsServices {
|
|
159
|
+
const config = livePaymentsConfig(options.configManager);
|
|
160
|
+
const cards = new DaemonCardStore({
|
|
161
|
+
filePath: controlPlaneStorePath(options.shellPaths, GOODVIBES_DAEMON_SURFACE_ROOT, 'payments-cards.json'),
|
|
162
|
+
secrets: daemonScopedSecrets(options.secretsManager),
|
|
163
|
+
cvvHandling: () => readCvvHandling(config),
|
|
164
|
+
});
|
|
165
|
+
const purchases = new DaemonPurchaseLedger({
|
|
166
|
+
filePath: controlPlaneStorePath(options.shellPaths, GOODVIBES_DAEMON_SURFACE_ROOT, 'payments-purchases.json'),
|
|
167
|
+
});
|
|
168
|
+
const budget = new DurableBudgetLedger(
|
|
169
|
+
controlPlaneStorePath(options.shellPaths, GOODVIBES_DAEMON_SURFACE_ROOT, 'payments-budget.json'),
|
|
170
|
+
);
|
|
171
|
+
const checkout: CheckoutComposition = {
|
|
172
|
+
seam: options.checkoutSeam,
|
|
173
|
+
addresses: configBackedAddressStore(config),
|
|
174
|
+
notifier: channelBackedPaymentNotifier(config, options.channelDeliveryRouter),
|
|
175
|
+
merchantJudge: createModelMerchantJudge(createProviderBackedMerchantJudgeModel(options.providerRegistry)),
|
|
176
|
+
untrusted: getProcessUntrustedContentLedger(),
|
|
177
|
+
};
|
|
178
|
+
const unregister = registerPaymentsMethods(options.gatewayMethods, {
|
|
179
|
+
cards,
|
|
180
|
+
purchases,
|
|
181
|
+
budget,
|
|
182
|
+
config,
|
|
183
|
+
isPaymentsLeader: options.isPaymentsLeader,
|
|
184
|
+
checkout,
|
|
185
|
+
});
|
|
186
|
+
return { cards, purchases, budget, unregister };
|
|
187
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* plugin-composition.ts
|
|
2
|
+
* plugin-composition.ts, what a plugin dropped into the plugin directory gets
|
|
3
3
|
* to register when the host loading it is the daemon.
|
|
4
4
|
*
|
|
5
5
|
* A plugin registers into whatever registries the host hands its loader. Two
|
|
6
|
-
* hosts load a plugin today
|
|
6
|
+
* hosts load a plugin today, the terminal app and this daemon, and neither
|
|
7
7
|
* holds every registry a plugin might want. The design (see
|
|
8
8
|
* goodvibes-tui/docs/decisions/2026-07-30-plugin-registrations-split-verb-side-and-surface-side.md)
|
|
9
9
|
* is: one plugin package, loaded by both hosts, each loading the registrations
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* The two surface-side kinds are the ones this host cannot serve. The SDK's
|
|
24
24
|
* PluginLoaderDeps has no optional members and its API guards nothing, so a
|
|
25
|
-
* host that passes nothing for a registry does not decline that kind
|
|
25
|
+
* host that passes nothing for a registry does not decline that kind, it
|
|
26
26
|
* throws a TypeError inside the plugin's own init, which the loader catches by
|
|
27
27
|
* dropping THE WHOLE PLUGIN, including the halves this host could have run.
|
|
28
28
|
* "Ignores the rest" therefore has to be a real registry that accepts the
|
|
@@ -67,8 +67,8 @@ export function createUnservedCommandRegistry(): CommandRegistryLike {
|
|
|
67
67
|
*
|
|
68
68
|
* The runs this daemon hosts build their tools through the agent orchestrator's
|
|
69
69
|
* own registry, not this one, so a plugin tool registered here is cataloged and
|
|
70
|
-
* unhandled. That is the recorded classification
|
|
71
|
-
* surface-side list
|
|
70
|
+
* unhandled. That is the recorded classification, `registerTool` is on the
|
|
71
|
+
* surface-side list, and it was written when the daemon hosted no runs of its
|
|
72
72
|
* own; the round that moves session hosting daemon-side is the one that
|
|
73
73
|
* re-examines it. Until then, a registration here is honest rather than quiet.
|
|
74
74
|
*/
|
|
@@ -84,8 +84,8 @@ export class UnservedToolRegistry extends ToolRegistry {
|
|
|
84
84
|
/**
|
|
85
85
|
* Build the loader dependencies for this host.
|
|
86
86
|
*
|
|
87
|
-
* The delivery router is deliberately the manager's own
|
|
88
|
-
* through
|
|
87
|
+
* The delivery router is deliberately the manager's own, the one replies leave
|
|
88
|
+
* through, so a registered strategy is a strategy that sends. Handing over a
|
|
89
89
|
* second router built from the same arguments is how a registration succeeds
|
|
90
90
|
* and reaches nothing, which is the shape this whole seam is about.
|
|
91
91
|
*/
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* runtime-services-types.ts
|
|
2
|
+
* runtime-services-types.ts, the public contract createRuntimeServices() takes
|
|
3
3
|
* and returns.
|
|
4
4
|
*
|
|
5
5
|
* Split out of services.ts (the composition root that builds every one of
|
|
6
6
|
* these fields) so the construction logic can stay under the repo's
|
|
7
7
|
* architecture line-count gate without trimming 35 arbitrary lines to clear
|
|
8
8
|
* the number. This module owns ONLY the shape of the input options and the
|
|
9
|
-
* output surface
|
|
9
|
+
* output surface, no runtime code, no wiring order, nothing that constructs
|
|
10
10
|
* anything. services.ts re-exports both types from here, so no import site
|
|
11
11
|
* anywhere else in the app had to change.
|
|
12
12
|
*/
|
|
@@ -76,7 +76,7 @@ export interface RuntimeServicesOptions {
|
|
|
76
76
|
* Threaded into `SecretsManager` so the override MOVES the daemon-scoped
|
|
77
77
|
* credential store; without it a daemon told to run out of a temp tree still
|
|
78
78
|
* read the real home's daemon secrets, so an "isolated" test daemon held the
|
|
79
|
-
* owner's live credentials. One name for one thing
|
|
79
|
+
* owner's live credentials. One name for one thing, `resolveGoodVibesHomeOwnership`
|
|
80
80
|
* is the single reader that produces it.
|
|
81
81
|
*/
|
|
82
82
|
readonly daemonHomeDirectory?: string | undefined;
|
|
@@ -104,7 +104,7 @@ export interface RuntimeServices {
|
|
|
104
104
|
readonly homeDirectory: string;
|
|
105
105
|
/**
|
|
106
106
|
* The `.goodvibes/<surface root>/` segment this daemon's own state lives
|
|
107
|
-
* under
|
|
107
|
+
* under, always GOODVIBES_DAEMON_SURFACE_ROOT here (config/surface.ts).
|
|
108
108
|
* Declared so a consumer ASKS for it instead of deriving a second one; the
|
|
109
109
|
* unscoped pre-split control-plane store is what deriving it twice produced.
|
|
110
110
|
*/
|
|
@@ -162,7 +162,7 @@ export interface RuntimeServices {
|
|
|
162
162
|
readonly hookWorkbench: HookWorkbench;
|
|
163
163
|
readonly pluginManager: PluginManager;
|
|
164
164
|
readonly workflow: WorkflowServices;
|
|
165
|
-
/** Stream watchers, on-exit process triggers and condition checks, supervised as one
|
|
165
|
+
/** Stream watchers, on-exit process triggers and condition checks, supervised as one, see trigger-services.ts. */
|
|
166
166
|
readonly triggerManager: TriggerManager;
|
|
167
167
|
readonly voiceProviders: VoiceProviderRegistry;
|
|
168
168
|
readonly voiceService: VoiceService;
|
|
@@ -219,13 +219,13 @@ export interface RuntimeServices {
|
|
|
219
219
|
readonly contextAccountingHolder: ContextAccountingHolder; // bound at bootstrap.ts; see context-accounting-source.ts
|
|
220
220
|
readonly wrfcController: WrfcController;
|
|
221
221
|
readonly processManager: ProcessManager;
|
|
222
|
-
/** The phase/work-item orchestration engine
|
|
222
|
+
/** The phase/work-item orchestration engine, the SDK's platform/orchestration. */
|
|
223
223
|
readonly orchestrationEngine: OrchestrationEngine;
|
|
224
224
|
readonly workstreamCommands: WorkstreamCommandService;
|
|
225
225
|
/** The repo source-tree code index. */
|
|
226
226
|
readonly codeIndexStore: CodeIndexStore;
|
|
227
227
|
readonly codeIndexReindexScheduler: CodeIndexReindexScheduler; // tool-site reindex
|
|
228
|
-
/** Daily snapshots of every SQLite store this runtime writes, with bounded retention; unref'd timers (mirrors the SDK composition
|
|
228
|
+
/** Daily snapshots of every SQLite store this runtime writes, with bounded retention; unref'd timers (mirrors the SDK composition, hosts that tear down a runtime stop() it themselves). */
|
|
229
229
|
readonly storeSnapshotScheduler: StoreSnapshotScheduler;
|
|
230
230
|
readonly appendOnlyRetentionScheduler: operations.DurabilityServices['appendOnlyRetentionScheduler']; // periodic append-only sweep; unref'd timers, stop() on teardown
|
|
231
231
|
/** Stops the recurring crash-residue sweep; idempotent, unref'd timer (hosts that tear a runtime down call it). */
|
|
@@ -241,7 +241,7 @@ export interface RuntimeServices {
|
|
|
241
241
|
/** Controller the governor uses to pause/resume the deferrable background jobs under pressure. */
|
|
242
242
|
readonly pauseController: PauseController;
|
|
243
243
|
readonly sessionLiveTurnControls: SessionLiveTurnControlsHolder;
|
|
244
|
-
/** Unified live process registry (agents, WRFC chains, workflows, watchers, background processes) backing the Fleet panel; archive-aware
|
|
244
|
+
/** Unified live process registry (agents, WRFC chains, workflows, watchers, background processes) backing the Fleet panel; archive-aware, finished subtrees can be moved to the session archive view. */
|
|
245
245
|
readonly processRegistry: ArchivableProcessRegistry;
|
|
246
246
|
readonly modeManager: ModeManager;
|
|
247
247
|
readonly fileUndoManager: FileUndoManager;
|
|
@@ -250,7 +250,7 @@ export interface RuntimeServices {
|
|
|
250
250
|
readonly checkpointsCurrentlyAllowed: () => boolean;
|
|
251
251
|
/** Surface-scoped continuity reads (recovery-file presence, last-session pointer). */
|
|
252
252
|
readonly integrationHelpers: IntegrationHelperService;
|
|
253
|
-
/** Per-workspace trust gate
|
|
253
|
+
/** Per-workspace trust gate, restricts write/execute/delegate tools until the workspace is trusted. */
|
|
254
254
|
readonly workspaceTrustManager: operations.WorkspaceTrustManager;
|
|
255
255
|
/**
|
|
256
256
|
* The permission manager the runs this daemon hosts ask through. Its ask seam
|
package/src/runtime/services.ts
CHANGED
|
@@ -56,11 +56,12 @@ import { createTriggerServices } from './trigger-services.ts';
|
|
|
56
56
|
import { createWorkstreamServices } from '@pellux/goodvibes-sdk/platform/orchestration';
|
|
57
57
|
import { wireFleetNeedsInputPush } from './fleet-needs-input-push.ts';
|
|
58
58
|
import { createDaemonHandlerComposition } from './daemon-handler-composition.ts';
|
|
59
|
+
import { createBrowserCheckoutSeamHolder } from './browser-checkout-seam-holder.ts';
|
|
59
60
|
import { createDevicePostureServices } from './device-posture-composition.ts';
|
|
60
61
|
// Re-exported so the daemon entrypoint reaches the housekeeping sweep through
|
|
61
62
|
// the same module it already imports the runtime graph from. `installDevicePosture`
|
|
62
63
|
// is deliberately NOT re-exported: it registers the phone TOOL into a tool
|
|
63
|
-
// registry, and the daemon registers no tools
|
|
64
|
+
// registry, and the daemon registers no tools, the sweep is the half it needs.
|
|
64
65
|
export { startDeviceHousekeeping } from './device-posture-composition.ts';
|
|
65
66
|
import { createClusterServices, startClusterServices } from './cluster-group-composition.ts';
|
|
66
67
|
import { createWorkspaceTrustDecisionAsk, trustGatedApprovalRaiser } from './trust/trust-gated-approvals.ts';
|
|
@@ -69,7 +70,7 @@ import type { RuntimeServicesOptions, RuntimeServices } from './runtime-services
|
|
|
69
70
|
export type { RuntimeServicesOptions, RuntimeServices } from './runtime-services-types.ts';
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
|
-
* createRuntimeServices
|
|
73
|
+
* createRuntimeServices, the daemon's service graph.
|
|
73
74
|
*
|
|
74
75
|
* This is the one composition root the daemon has. Capabilities a client and
|
|
75
76
|
* the daemon both need live in the SDK and are composed from there (memory
|
|
@@ -81,7 +82,7 @@ export type { RuntimeServicesOptions, RuntimeServices } from './runtime-services
|
|
|
81
82
|
*/
|
|
82
83
|
export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeServices {
|
|
83
84
|
// The SDK's disposal scope and its all-required poller list, plus the four
|
|
84
|
-
// pollers only the daemon has
|
|
85
|
+
// pollers only the daemon has, see disposal-wiring.ts.
|
|
85
86
|
const disposalScope = createDisposalScope('RuntimeServices');
|
|
86
87
|
const workingDirectory = options.workingDir;
|
|
87
88
|
const homeDirectory = options.homeDirectory;
|
|
@@ -105,7 +106,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
105
106
|
// so the scheduler gates and the knowledge background jobs can consult the pause
|
|
106
107
|
// controller before the MemoryGovernor (constructed at the composition tail)
|
|
107
108
|
// drives it. The admission gate is late-bound: expensive entry points capture
|
|
108
|
-
// this closure now and the governor binds into it at the tail
|
|
109
|
+
// this closure now and the governor binds into it at the tail, until then
|
|
109
110
|
// everything is admitted (the daemon is still booting).
|
|
110
111
|
const cacheRegistry = new CacheRegistry();
|
|
111
112
|
const pauseController = new PauseController();
|
|
@@ -149,7 +150,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
149
150
|
// Launch-tolerant: a provider whose API key is absent from the environment is
|
|
150
151
|
// constructed with a placeholder that is stripped immediately afterwards, so
|
|
151
152
|
// it lands unconfigured instead of throwing during construction. The daemon
|
|
152
|
-
// has the same must-boot property the agent has
|
|
153
|
+
// has the same must-boot property the agent has, it is a supervised service,
|
|
153
154
|
// and a constructor that throws on a missing key turns one unset variable into
|
|
154
155
|
// a crash loop with no screen to explain it.
|
|
155
156
|
const providerRegistry = createLaunchTolerantProviderRegistry({
|
|
@@ -186,8 +187,8 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
186
187
|
);
|
|
187
188
|
const hookActivityTracker = new HookActivityTracker();
|
|
188
189
|
// featureFlags is REQUIRED here in practice, even though the SDK types it
|
|
189
|
-
// optional. isFeatureGateEnabled(null, ...) is permissive by design
|
|
190
|
-
// embed with no manager wired gets the capability rather than a silent off
|
|
190
|
+
// optional. isFeatureGateEnabled(null, ...) is permissive by design, a narrow
|
|
191
|
+
// embed with no manager wired gets the capability rather than a silent off,
|
|
191
192
|
// so omitting it did not disable the watcher framework when watchers.enabled
|
|
192
193
|
// is turned off; it made the setting configure nothing.
|
|
193
194
|
const watcherRegistry = new WatcherRegistry({
|
|
@@ -235,9 +236,9 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
235
236
|
task,
|
|
236
237
|
// Conversation first: a follow-up message in a session gets an answer, not
|
|
237
238
|
// a write-review-fix-confirm chain with a reviewer, quality gates and a
|
|
238
|
-
// second agent. A chain opens only for an explicit authorization marker
|
|
239
|
+
// second agent. A chain opens only for an explicit authorization marker,
|
|
239
240
|
// the channel confirmation the owner gave, or the schedule/trigger that
|
|
240
|
-
// was confirmed when it was created
|
|
241
|
+
// was confirmed when it was created, or for a follow-up typed on a local
|
|
241
242
|
// surface. Both `conversationGate.mode` and the gated-surfaces list are
|
|
242
243
|
// read live.
|
|
243
244
|
...continuationChainOptions(input, {
|
|
@@ -256,7 +257,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
256
257
|
// It also supplies the run's context, which is why the bare
|
|
257
258
|
// `context: shared-session:<id>` line that used to sit below is gone.
|
|
258
259
|
// Spread BEFORE the routing builder so an explicit tool list coming from
|
|
259
|
-
// a routing intent still wins
|
|
260
|
+
// a routing intent still wins, that builder emits `tools` only when it
|
|
260
261
|
// actually has one.
|
|
261
262
|
...conversationalTurnSpawnOptions(input, { configReader: configManager }),
|
|
262
263
|
// Spawn routing through the SDK's shared model-reference resolver
|
|
@@ -286,7 +287,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
286
287
|
// through. Without the secrets manager it cannot resolve a
|
|
287
288
|
// goodvibes://secrets/... credential, so Telegram accepted every inbound
|
|
288
289
|
// message and dropped every reply with "Missing Telegram bot token" while
|
|
289
|
-
// ntfy
|
|
290
|
+
// ntfy, which needs no secret, worked.
|
|
290
291
|
secretsManager,
|
|
291
292
|
serviceRegistry,
|
|
292
293
|
runtimeBus: options.runtimeBus,
|
|
@@ -326,7 +327,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
326
327
|
return record.id;
|
|
327
328
|
},
|
|
328
329
|
});
|
|
329
|
-
// Knowledge/wiki + home-graph stack (governor backpressure wired in)
|
|
330
|
+
// Knowledge/wiki + home-graph stack (governor backpressure wired in), see knowledge-services.ts.
|
|
330
331
|
const {
|
|
331
332
|
knowledgeStore, agentKnowledgeStore, homeGraphKnowledgeStore,
|
|
332
333
|
knowledgeSemanticService, homeGraphSemanticService, agentKnowledgeSemanticService,
|
|
@@ -382,21 +383,32 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
382
383
|
});
|
|
383
384
|
|
|
384
385
|
// Which machines on this network are "us", and which of them reads the shared
|
|
385
|
-
// inbox. Both inert until startCluster()
|
|
386
|
+
// inbox. Both inert until startCluster(), no socket, no key material read;
|
|
386
387
|
// see cluster-group-composition.ts for why they are built together.
|
|
387
388
|
const { clusterGroup, clusterCoordinator } = createClusterServices({
|
|
388
389
|
configManager, shellPaths, secretsManager,
|
|
389
390
|
});
|
|
391
|
+
// ONE router, not two (a second built from the same four arguments would
|
|
392
|
+
// differ from the one replies leave through). Moved up from its original
|
|
393
|
+
// spot near the ws-only verb options (still consumed there): the payments
|
|
394
|
+
// composition below needs it too.
|
|
395
|
+
const channelDeliveryRouter = deliveryManager.getDeliveryRouter();
|
|
390
396
|
// Daemon handler surfaces (see daemon-handler-composition.ts); the inbox
|
|
391
|
-
// poller registers itself with the coordinator rather than starting eagerly
|
|
397
|
+
// poller registers itself with the coordinator rather than starting eagerly,
|
|
398
|
+
// and the payments family stops being a cataloged 501 facade there.
|
|
399
|
+
// browserCheckoutSeam fills once `onBrowserCheckout` fires below, after this
|
|
400
|
+
// composition; read lazily, per call. See browser-checkout-seam-holder.ts.
|
|
401
|
+
const browserCheckoutSeam = createBrowserCheckoutSeamHolder();
|
|
392
402
|
const daemonHandlers = createDaemonHandlerComposition({
|
|
393
403
|
gatewayMethods,
|
|
394
404
|
secretsManager,
|
|
395
405
|
configManager,
|
|
396
406
|
workingDirectory,
|
|
397
407
|
homeDirectory,
|
|
408
|
+
shellPaths,
|
|
398
409
|
distributedRuntime,
|
|
399
410
|
clusterCoordinator,
|
|
411
|
+
checkoutSeam: browserCheckoutSeam.get, channelDeliveryRouter, providerRegistry,
|
|
400
412
|
});
|
|
401
413
|
|
|
402
414
|
// Remote runners and the sandboxes tool calls are confined to; see
|
|
@@ -424,12 +436,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
424
436
|
const policyRuntimeState = new PolicyRuntimeState();
|
|
425
437
|
const fileCache = new FileStateCache();
|
|
426
438
|
const projectIndex = new ProjectIndex(workingDirectory);
|
|
427
|
-
//
|
|
428
|
-
// same four arguments AutomationDeliveryManager builds its own from, so the
|
|
429
|
-
// router the gateway verbs held and the router replies actually leave through
|
|
430
|
-
// were different objects — and a delivery strategy registered on one was
|
|
431
|
-
// invisible to the other. The manager's is the one that replies; it is the one.
|
|
432
|
-
const channelDeliveryRouter = deliveryManager.getDeliveryRouter();
|
|
439
|
+
// channelDeliveryRouter now built earlier, near clusterCoordinator above.
|
|
433
440
|
const processManager = new ProcessManager();
|
|
434
441
|
// The phase/work-item orchestration engine, constructed before the process
|
|
435
442
|
// registry so its fleet nodes (workstream/phase/work-item) can be folded in
|
|
@@ -447,7 +454,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
447
454
|
});
|
|
448
455
|
const codeInjectionOrchestratorDeps = { codeIndex: codeIndexStore, isCodeInjectionSettingEnabled: () => isCodeInjectionSettingEnabled(configManager), codeIndexReindexScheduler };
|
|
449
456
|
// The trigger family: stream watchers, on-exit process triggers, condition
|
|
450
|
-
// checks
|
|
457
|
+
// checks, fed to the fleet below as its trigger supervisor, so a trigger
|
|
451
458
|
// is visible and steerable like every other running thing.
|
|
452
459
|
const triggerManager = createTriggerServices({
|
|
453
460
|
configManager, shellPaths, surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT,
|
|
@@ -477,7 +484,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
477
484
|
acpHost, // Folds live hosted-agent sessions into the fleet as 'acp' rows
|
|
478
485
|
});
|
|
479
486
|
const modeManager = new ModeManager({ featureFlags }); const fileUndoManager = new FileUndoManager();
|
|
480
|
-
// Checkpoints, gated on live workspace registration
|
|
487
|
+
// Checkpoints, gated on live workspace registration, see workspace-checkpointing.ts.
|
|
481
488
|
const checkpointing = createWorkspaceCheckpointing({
|
|
482
489
|
workspaceRoot: workingDirectory, surface, runtimeBus: options.runtimeBus, configManager, shellPaths,
|
|
483
490
|
});
|
|
@@ -487,7 +494,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
487
494
|
// admitted (mirrors the SDK's own createRuntimeServices idle gate).
|
|
488
495
|
const { memoryConsolidationScheduler, powerManager, sessionLiveTurnControls } = wireIdlePowerAndLiveTurn({ configManager, memoryRegistry, runtimeBus: options.runtimeBus, isIdle: () => sessionBroker.countBusySessions() === 0 && !pauseController.isPaused('memory-consolidation') && admitExpensiveWork('memory consolidation').allowed, snapshotTick: () => storeSnapshotScheduler.tick(), heartbeat: async () => { await automationManager.triggerHeartbeat({ source: 'wake-catchup' }); }, powerSeam: options.powerSeam });
|
|
489
496
|
|
|
490
|
-
// Construct + start the MemoryGovernor (default ON
|
|
497
|
+
// Construct + start the MemoryGovernor (default ON, a safety feature) with the
|
|
491
498
|
// standard KNOWN cache adapters (knowledge stores + shared session broker),
|
|
492
499
|
// then late-bind the admission gate the expensive entry points captured
|
|
493
500
|
// earlier. The SDK owns this wiring.
|
|
@@ -514,7 +521,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
514
521
|
});
|
|
515
522
|
admitExpensiveWorkRef.current = (label) => memoryGovernor.admitExpensiveWork(label);
|
|
516
523
|
|
|
517
|
-
// Managed local-voice provisioning (voice.local.status/install)
|
|
524
|
+
// Managed local-voice provisioning (voice.local.status/install), single-flight
|
|
518
525
|
// one-act install + no-network status.
|
|
519
526
|
const { voiceSetup, stopWakeHousekeeping } = wireVoiceSetup({ configManager, shellPaths, voiceProviders, admitExpensiveWork,
|
|
520
527
|
// Boot provisioning of the wake-word model + its recovery sweep, opted into
|
|
@@ -531,6 +538,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
531
538
|
// is what let these stores write to the unscoped orphan directory.
|
|
532
539
|
surfaceRoot: GOODVIBES_DAEMON_SURFACE_ROOT,
|
|
533
540
|
homeDirectory, emailServiceDeps, describeEmailConfigProblem, processRegistry,
|
|
541
|
+
onBrowserCheckout: browserCheckoutSeam.set,
|
|
534
542
|
// The registration-gated surface, not the raw manager: an explicit create in
|
|
535
543
|
// an unregistered workspace refuses with something actionable.
|
|
536
544
|
workspaceCheckpointManager: checkpointing.gatewayManager,
|
|
@@ -543,11 +551,11 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
543
551
|
// to the owner principal instead of unknown.
|
|
544
552
|
channelPolicy,
|
|
545
553
|
approvalBroker, requestApproval: (input) => approvalBroker.requestApproval(input),
|
|
546
|
-
// approvals.raise
|
|
554
|
+
// approvals.raise, a surface CREATING an ask in this broker. Without it the
|
|
547
555
|
// verb is cataloged and unhandled, and a client whose prompt runs outside
|
|
548
556
|
// this process has no way to raise one.
|
|
549
557
|
approvalRaise: approvalBroker,
|
|
550
|
-
// credentials.set / credentials.delete
|
|
558
|
+
// credentials.set / credentials.delete, a credential written THROUGH the
|
|
551
559
|
// control plane, so a client with no access to the daemon's settings file can
|
|
552
560
|
// configure one. The value lands in the daemon's secret tier and the verb
|
|
553
561
|
// never echoes it back.
|
|
@@ -562,6 +570,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
562
570
|
disposal: disposalScope.registry,
|
|
563
571
|
...wireFleetNeedsInputPush({ registry: processRegistry, runtimeBus: options.runtimeBus, sessionBroker }),
|
|
564
572
|
});
|
|
573
|
+
disposalScope.registry.add('browser checkout seam holder', () => browserCheckoutSeam.clear()); // newer than 'browser sessions' above, so runs first
|
|
565
574
|
// A loopback fetch that isn't allow-listed asks once through the approval
|
|
566
575
|
// broker; "allow for this project" persists and later fetches never ask. Built
|
|
567
576
|
// once and shared with the tool registry so both ask alike.
|
|
@@ -573,15 +582,15 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
573
582
|
const execPromptAnswerHandler = buildExecPromptAnswerHandler({ requestApproval: (input) => approvalBroker.requestApproval(input) });
|
|
574
583
|
// Tool asks from the runs this daemon HOSTS. Without a manager here, the
|
|
575
584
|
// background permission gate short-circuits to approved and every hosted
|
|
576
|
-
// write, command and delegation ran ungated
|
|
585
|
+
// write, command and delegation ran ungated, the workspace trust decision
|
|
577
586
|
// was read by nobody in this process.
|
|
578
587
|
//
|
|
579
588
|
// The ask seam is the trust gate wrapping the approval broker: a workspace
|
|
580
589
|
// with no decision yet has the question raised as an approval record and
|
|
581
|
-
// answered by whichever surface is attached (trust-gated-approvals.ts)
|
|
590
|
+
// answered by whichever surface is attached (trust-gated-approvals.ts),
|
|
582
591
|
// there is no screen here to show a modal on, so the raise replaces it. The
|
|
583
|
-
// manager's own layers
|
|
584
|
-
// user rules
|
|
592
|
+
// manager's own layers, permission mode, policy, session cache, durable
|
|
593
|
+
// user rules, still run first and are unchanged.
|
|
585
594
|
const permissionManager = createBrokeredPermissionManager({
|
|
586
595
|
requestApproval: trustGatedApprovalRaiser(
|
|
587
596
|
workspaceTrustManager,
|
|
@@ -648,7 +657,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
648
657
|
// whole high-churn 'ops' domain, and sent over the SAME WebhookNotifier the
|
|
649
658
|
// notification verbs keep live and boot-tasks attaches to the bus. There is
|
|
650
659
|
// no panel notification router here: its targets are all screen targets and
|
|
651
|
-
// this product has no screen
|
|
660
|
+
// this product has no screen, see notification-dispatch.ts.
|
|
652
661
|
wireMemoryPressureChannelNotice(options.runtimeBus, webhookNotifier);
|
|
653
662
|
|
|
654
663
|
// In-process config changes become key-level events on the `config` domain, so
|
|
@@ -780,7 +789,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
780
789
|
// Cancels the agent runs this graph was hosting. By dispose() time the fleet
|
|
781
790
|
// registry, orchestration engine, process registry and bus these runs report
|
|
782
791
|
// through are already down, so a run still described as "running" is orphaned
|
|
783
|
-
// rather than preserved
|
|
792
|
+
// rather than preserved, and this is the only shutdown-reachable way to
|
|
784
793
|
// abort its in-flight provider call instead of letting it sleep out a retry
|
|
785
794
|
// backoff nobody is waiting on.
|
|
786
795
|
cancelHostedAgentRuns: () => cancelAllAgentRuns(agentManager),
|
|
@@ -11,7 +11,7 @@ import type { ShellPathService } from '@/runtime/index.ts';
|
|
|
11
11
|
*
|
|
12
12
|
* This daemon composes the full family and feeds the manager to the fleet as
|
|
13
13
|
* its trigger supervisor, so a trigger defined against the daemon fires
|
|
14
|
-
* reliably. The daemon is the right process to own it
|
|
14
|
+
* reliably. The daemon is the right process to own it, it is the one that
|
|
15
15
|
* stays running.
|
|
16
16
|
*
|
|
17
17
|
* Two things about the shape are load-bearing:
|