@pellux/goodvibes-daemon 1.28.19 → 1.28.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -63
- package/README.md +13 -13
- 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 +4 -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/card-store.ts +486 -0
- package/src/daemon/handlers/payments/index.ts +32 -0
- package/src/daemon/handlers/payments/purchase-ledger.ts +108 -0
- package/src/daemon/handlers/payments/register.ts +391 -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/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 +44 -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 +143 -0
- package/src/runtime/plugin-composition.ts +7 -7
- package/src/runtime/runtime-services-types.ts +9 -9
- package/src/runtime/services.ts +29 -27
- 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,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The unattended startup reconcile for the redundant install-script
|
|
3
|
-
* `goodvibes-daemon.service` unit
|
|
3
|
+
* `goodvibes-daemon.service` unit, split out of `legacy-daemon-migration.ts`
|
|
4
4
|
* (which owns the shared unit definition, detection, and the CONSENTED
|
|
5
5
|
* `migrate-service` engine) so each module stays within the architecture
|
|
6
6
|
* gate's file-size cap. Same layer (`src/runtime/`), same injectable-seam
|
|
@@ -24,8 +24,8 @@ type ManagedServiceActionResult = ReturnType<ManagedServiceActionRunner>;
|
|
|
24
24
|
/**
|
|
25
25
|
* The exact marker string scripts/install.sh writes into every unit it
|
|
26
26
|
* creates (as a `# managed by goodvibes install.sh` comment). The reconcile
|
|
27
|
-
* check below keys on it to tell an installer-created legacy unit
|
|
28
|
-
* auto-retire
|
|
27
|
+
* check below keys on it to tell an installer-created legacy unit, safe to
|
|
28
|
+
* auto-retire, apart from a hand-written one that must only ever be reported.
|
|
29
29
|
* Kept in lockstep with `INSTALLER_MARKER` in scripts/install.sh.
|
|
30
30
|
*/
|
|
31
31
|
export const INSTALLER_UNIT_MARKER = 'managed by goodvibes install.sh';
|
|
@@ -35,7 +35,7 @@ export const INSTALLER_UNIT_MARKER = 'managed by goodvibes install.sh';
|
|
|
35
35
|
/**
|
|
36
36
|
* CUMULATIVE ceiling on the reconcile's whole boot-path pass. Per-call
|
|
37
37
|
* timeouts alone still allow ~5 sequential slow-but-completing calls to stack
|
|
38
|
-
* up (a degraded
|
|
38
|
+
* up (a degraded, not wedged, user bus), blocking the already-listening
|
|
39
39
|
* daemon's event loop for tens of seconds. One overall deadline covers the
|
|
40
40
|
* pass: once it is exceeded, every remaining call is skipped (reported as
|
|
41
41
|
* `status: null`, which the guards treat as unknown → refusal) and the result
|
|
@@ -48,7 +48,7 @@ export const RECONCILE_DEADLINE_MS = 8_000;
|
|
|
48
48
|
/**
|
|
49
49
|
* Wrap a runner with the reconcile's cumulative deadline: calls after the
|
|
50
50
|
* deadline are skipped outright; calls near it get only the remaining budget
|
|
51
|
-
* as their per-call timeout (default runner only
|
|
51
|
+
* as their per-call timeout (default runner only, injected runners are test
|
|
52
52
|
* fakes that answer instantly).
|
|
53
53
|
*/
|
|
54
54
|
function makeDeadlineBoundRunner(
|
|
@@ -62,7 +62,7 @@ function makeDeadlineBoundRunner(
|
|
|
62
62
|
const remaining = deadlineMs - (Date.now() - startedAt);
|
|
63
63
|
if (remaining <= 0) {
|
|
64
64
|
hit = true;
|
|
65
|
-
return { status: null, stdout: '', stderr: 'reconcile time budget exceeded
|
|
65
|
+
return { status: null, stdout: '', stderr: 'reconcile time budget exceeded; call skipped' } as ManagedServiceActionResult;
|
|
66
66
|
}
|
|
67
67
|
if (injected) return injected(command, args);
|
|
68
68
|
return defaultActionRunner(Math.min(perCallTimeoutMs, remaining))(command, args);
|
|
@@ -167,7 +167,7 @@ function defaultReadOwnCgroup(): string {
|
|
|
167
167
|
* This runs on daemon startup (no user invocation needed) and, guard-railed:
|
|
168
168
|
* - Only acts when the canonical unit is CONFIRMED serving: `is-active`
|
|
169
169
|
* reports active AND its MainPID resolves to a live process. `is-active`
|
|
170
|
-
* alone is not proof
|
|
170
|
+
* alone is not proof, a Type=simple unit reports active from fork onward,
|
|
171
171
|
* including the pre-bind window of a daemon that is about to crash-loop.
|
|
172
172
|
* - NEVER acts from inside the legacy unit itself: if this process IS the
|
|
173
173
|
* legacy unit's MainPID, or /proc/self/cgroup names the legacy unit,
|
|
@@ -175,20 +175,20 @@ function defaultReadOwnCgroup(): string {
|
|
|
175
175
|
* code (mid-boot, from inside a blocking spawnSync). Refuses instead.
|
|
176
176
|
* - NEVER stops a RUNNING legacy daemon: if the legacy unit's MainPID is a
|
|
177
177
|
* live process, it may be the daemon serving the endpoint clients are
|
|
178
|
-
* configured to reach (the wrong-port two-daemon state)
|
|
178
|
+
* configured to reach (the wrong-port two-daemon state), the unattended
|
|
179
179
|
* path only retires enabled-but-NOT-running legacy units; a running one
|
|
180
180
|
* gets a notice pointing at the consented migrate-service.
|
|
181
181
|
* - Requires the CONFIGURED endpoint (the one clients resolve from
|
|
182
|
-
* settings.json, no runtime overrides) to be answering before retiring
|
|
182
|
+
* settings.json, no runtime overrides) to be answering before retiring,
|
|
183
183
|
* a canonical daemon alive on some other port proves nothing about the
|
|
184
184
|
* endpoint clients use.
|
|
185
185
|
* - AUTO-RETIRES only an installer-MARKER-managed legacy unit, and only
|
|
186
186
|
* removes its unit file AFTER `disable --now` reports success. A NONZERO
|
|
187
187
|
* exit leaves everything in place and says so; a TIMED-OUT disable
|
|
188
|
-
* (status null) has an UNKNOWN outcome
|
|
188
|
+
* (status null) has an UNKNOWN outcome, the enablement state is
|
|
189
189
|
* re-inspected before anything is printed, and the receipt states what
|
|
190
190
|
* was actually confirmed, never a blanket claim.
|
|
191
|
-
* - A hand-written legacy unit (no marker) is never touched
|
|
191
|
+
* - A hand-written legacy unit (no marker) is never touched, a one-line
|
|
192
192
|
* actionable notice. An UNREADABLE unit file is reported as unreadable,
|
|
193
193
|
* never misdiagnosed as hand-written.
|
|
194
194
|
* - The whole pass shares ONE cumulative time budget (RECONCILE_DEADLINE_MS)
|
|
@@ -214,11 +214,11 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
214
214
|
const legacyUnit = `${LEGACY_SERVICE_UNIT_NAME}.service`;
|
|
215
215
|
const deadlineNote = (): string[] =>
|
|
216
216
|
deadlineHit()
|
|
217
|
-
? ['note: the reconcile hit its overall time budget
|
|
217
|
+
? ['note: the reconcile hit its overall time budget: remaining checks were skipped; it will re-run at the next daemon start.']
|
|
218
218
|
: [];
|
|
219
219
|
|
|
220
220
|
// Guard 1: the canonical unit must report active. (A timed-out, skipped, or
|
|
221
|
-
// failed probe
|
|
221
|
+
// failed probe, e.g. a wedged user bus, lands here too and refuses.)
|
|
222
222
|
const probe = run(systemctl, ['--user', 'is-active', canonicalUnit]);
|
|
223
223
|
const canonicalActive = (probe.status ?? 1) === 0 && (probe.stdout ?? '').trim() === 'active';
|
|
224
224
|
if (!canonicalActive) {
|
|
@@ -226,7 +226,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
226
226
|
action: 'noop',
|
|
227
227
|
reason: 'canonical-not-active',
|
|
228
228
|
lines: [
|
|
229
|
-
`legacy-unit reconcile: a ${legacyUnit} unit file exists at ${path} but ${canonicalUnit} is not confirmably active
|
|
229
|
+
`legacy-unit reconcile: a ${legacyUnit} unit file exists at ${path} but ${canonicalUnit} is not confirmably active; ` +
|
|
230
230
|
'left untouched (it may be the only daemon).',
|
|
231
231
|
...deadlineNote(),
|
|
232
232
|
],
|
|
@@ -243,7 +243,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
243
243
|
action: 'noop',
|
|
244
244
|
reason: 'canonical-mainpid-not-alive',
|
|
245
245
|
lines: [
|
|
246
|
-
`legacy-unit reconcile: ${canonicalUnit} reports active but its MainPID could not be confirmed alive
|
|
246
|
+
`legacy-unit reconcile: ${canonicalUnit} reports active but its MainPID could not be confirmed alive; ` +
|
|
247
247
|
`left the ${legacyUnit} unit untouched.`,
|
|
248
248
|
...deadlineNote(),
|
|
249
249
|
],
|
|
@@ -256,7 +256,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
256
256
|
//
|
|
257
257
|
// The legacy MainPID probe is TRI-STATE, exactly like guards 1-2: a reply
|
|
258
258
|
// whose status is non-zero or null (bus flap, timeout, skipped-by-deadline)
|
|
259
|
-
// does NOT mean "MainPID=0, unit affirmatively stopped"
|
|
259
|
+
// does NOT mean "MainPID=0, unit affirmatively stopped", reading it that
|
|
260
260
|
// way let one transient systemctl failure on exactly this call authorize
|
|
261
261
|
// `disable --now` against a LIVE legacy daemon (and guard 5 cannot catch
|
|
262
262
|
// it: in the wrong-port state the legacy daemon itself answers the
|
|
@@ -271,7 +271,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
271
271
|
action: 'noop',
|
|
272
272
|
reason: 'self-supervised-by-legacy',
|
|
273
273
|
lines: [
|
|
274
|
-
`legacy-unit reconcile: this daemon appears to be running UNDER ${legacyUnit} itself
|
|
274
|
+
`legacy-unit reconcile: this daemon appears to be running UNDER ${legacyUnit} itself; refusing to disable ` +
|
|
275
275
|
`the unit supervising the current process. Migrate from the canonical side instead: goodvibes-daemon migrate-service`,
|
|
276
276
|
],
|
|
277
277
|
};
|
|
@@ -282,14 +282,14 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
282
282
|
reason: 'legacy-state-unknown',
|
|
283
283
|
lines: [
|
|
284
284
|
`legacy-unit reconcile: could not determine whether ${legacyUnit} has a running daemon (the MainPID query ` +
|
|
285
|
-
'failed or timed out)
|
|
285
|
+
'failed or timed out), refusing to act on a guess; it will be re-checked at the next daemon start.',
|
|
286
286
|
...deadlineNote(),
|
|
287
287
|
],
|
|
288
288
|
};
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
// Guard 4: never stop a RUNNING legacy daemon from the unattended path. A
|
|
292
|
-
// live legacy MainPID means a second daemon is actually serving something
|
|
292
|
+
// live legacy MainPID means a second daemon is actually serving something,
|
|
293
293
|
// possibly the endpoint clients resolve from settings.json (the wrong-port
|
|
294
294
|
// two-daemon state). Retiring an enabled-but-idle unit needs no --now kill;
|
|
295
295
|
// stopping a serving one needs consent. (A KNOWN reply of MainPID=0 is the
|
|
@@ -299,7 +299,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
299
299
|
action: 'noop',
|
|
300
300
|
reason: 'legacy-running',
|
|
301
301
|
lines: [
|
|
302
|
-
`legacy-unit reconcile: ${legacyUnit} has a live main process (pid ${legacyPid})
|
|
302
|
+
`legacy-unit reconcile: ${legacyUnit} has a live main process (pid ${legacyPid}); a second daemon is ` +
|
|
303
303
|
'actually running, and it may be the one serving the endpoint your clients are configured to reach. ' +
|
|
304
304
|
'Refusing to stop it unattended; migrate deliberately with: goodvibes-daemon migrate-service',
|
|
305
305
|
],
|
|
@@ -308,7 +308,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
308
308
|
|
|
309
309
|
// Guard 5: the endpoint clients resolve from settings.json must be answered
|
|
310
310
|
// before anything is retired. The canonical daemon being alive proves only
|
|
311
|
-
// that A daemon runs
|
|
311
|
+
// that A daemon runs, not that the configured endpoint is served (its unit
|
|
312
312
|
// may pin different launch args).
|
|
313
313
|
if (input.configuredEndpoint) {
|
|
314
314
|
const endpointProbe = input.endpointProbe ?? defaultPortProbe;
|
|
@@ -319,7 +319,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
319
319
|
reason: 'configured-endpoint-unserved',
|
|
320
320
|
lines: [
|
|
321
321
|
`legacy-unit reconcile: nothing is answering on the CONFIGURED endpoint ` +
|
|
322
|
-
`${input.configuredEndpoint.host}:${input.configuredEndpoint.port}
|
|
322
|
+
`${input.configuredEndpoint.host}:${input.configuredEndpoint.port}; the canonical daemon is alive but not ` +
|
|
323
323
|
`provably serving what clients resolve from settings. Left the ${legacyUnit} unit untouched; check the ` +
|
|
324
324
|
"canonical unit's launch arguments against the controlPlane settings.",
|
|
325
325
|
],
|
|
@@ -345,7 +345,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
345
345
|
reason: 'marker-unreadable',
|
|
346
346
|
lines: [
|
|
347
347
|
`note: ${canonicalUnit} is active and a separate ${legacyUnit} exists at ${path}, but its unit file could not ` +
|
|
348
|
-
`be read (${readError})
|
|
348
|
+
`be read (${readError}). Left untouched. Inspect it yourself; if it is redundant, retire it with: ` +
|
|
349
349
|
`systemctl --user disable --now ${legacyUnit} && rm ${path} && systemctl --user daemon-reload`,
|
|
350
350
|
],
|
|
351
351
|
};
|
|
@@ -365,14 +365,14 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
// Installer-marker-managed and redundant: disable first, and only remove the
|
|
368
|
-
// unit file if the disable actually succeeded
|
|
368
|
+
// unit file if the disable actually succeeded, otherwise the enablement
|
|
369
369
|
// symlink dangles at a deleted file and this tool can never repair it (the
|
|
370
370
|
// next pass no-ops at the file-exists check).
|
|
371
371
|
const disableResult = run(systemctl, ['--user', 'disable', '--now', legacyUnit]);
|
|
372
372
|
const disableStatus = disableResult.status ?? 1;
|
|
373
373
|
if (disableStatus !== 0) {
|
|
374
374
|
if (disableResult.status === null) {
|
|
375
|
-
// TIMED OUT: the outcome is UNKNOWN, not failed
|
|
375
|
+
// TIMED OUT: the outcome is UNKNOWN, not failed, `disable --now`
|
|
376
376
|
// removes the enablement symlinks synchronously BEFORE waiting on the
|
|
377
377
|
// stop job, so the disable may well have taken effect even though the
|
|
378
378
|
// client was killed. Re-inspect instead of printing a blanket denial.
|
|
@@ -393,11 +393,11 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
393
393
|
run(systemctl, ['--user', 'daemon-reload']);
|
|
394
394
|
const lines = [
|
|
395
395
|
`reconciled: the disable command timed out waiting on the stop job, but re-inspection confirms the ` +
|
|
396
|
-
`installer-managed ${legacyUnit} is no longer enabled${removeErrorAfterTimeout ? '' :
|
|
396
|
+
`installer-managed ${legacyUnit} is no longer enabled${removeErrorAfterTimeout ? '' : `, its unit file was removed (${path})`}. ` +
|
|
397
397
|
'Its stop may still be completing.',
|
|
398
398
|
];
|
|
399
399
|
if (removeErrorAfterTimeout) {
|
|
400
|
-
lines.push(`note: could not remove ${path}: ${removeErrorAfterTimeout}
|
|
400
|
+
lines.push(`note: could not remove ${path}: ${removeErrorAfterTimeout}; remove it by hand.`);
|
|
401
401
|
}
|
|
402
402
|
lines.push(...deadlineNote());
|
|
403
403
|
return { action: 'removed', reason: 'retired', lines };
|
|
@@ -406,7 +406,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
406
406
|
action: 'failed',
|
|
407
407
|
reason: 'disable-timeout',
|
|
408
408
|
lines: [
|
|
409
|
-
`legacy-unit reconcile: the disable of ${legacyUnit} timed out and its outcome is UNKNOWN
|
|
409
|
+
`legacy-unit reconcile: the disable of ${legacyUnit} timed out and its outcome is UNKNOWN; the enablement ` +
|
|
410
410
|
`state could not be re-confirmed. This tool removed nothing; the unit file at ${path} was left in place ` +
|
|
411
411
|
'and will be re-checked at the next daemon start.',
|
|
412
412
|
`Verify it yourself: systemctl --user is-enabled ${legacyUnit}`,
|
|
@@ -420,7 +420,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
420
420
|
reason: 'disable-failed',
|
|
421
421
|
lines: [
|
|
422
422
|
`legacy-unit reconcile: the disable of the redundant installer-managed ${legacyUnit} reported failure ` +
|
|
423
|
-
`(${detail})
|
|
423
|
+
`(${detail}); this tool removed nothing; the unit file at ${path} was left in place.`,
|
|
424
424
|
`Verify its state and retire it yourself: systemctl --user is-enabled ${legacyUnit} ; ` +
|
|
425
425
|
`systemctl --user disable --now ${legacyUnit} && rm ${path} && systemctl --user daemon-reload`,
|
|
426
426
|
],
|
|
@@ -441,7 +441,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
441
441
|
`was disabled${removeError ? '' : ` and removed (${path})`}.`,
|
|
442
442
|
];
|
|
443
443
|
if (removeError) {
|
|
444
|
-
lines.push(`note: could not remove ${path}: ${removeError}
|
|
444
|
+
lines.push(`note: could not remove ${path}: ${removeError}; remove it by hand.`);
|
|
445
445
|
}
|
|
446
446
|
lines.push(...deadlineNote());
|
|
447
447
|
return { action: 'removed', reason: 'retired', lines };
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The deps that let the platform register this daemon's `email.*` verbs.
|
|
5
5
|
*
|
|
6
6
|
* `calendar.*` and `email.*` are served by the platform now, not by handlers in
|
|
7
|
-
* this repository
|
|
7
|
+
* this repository, those were deleted when the platform gained an
|
|
8
8
|
* implementation the daemon could call. What is left for a product to supply is
|
|
9
9
|
* the wiring, and it is not optional: without `homeDirectory` the calendar
|
|
10
10
|
* composition returns null, without these deps the mail one does, and either way
|
|
@@ -27,7 +27,7 @@ import { nodeEmailTransport } from '@pellux/goodvibes-sdk/platform/email/node';
|
|
|
27
27
|
|
|
28
28
|
/** The narrow slices this composition needs; the real managers satisfy them. */
|
|
29
29
|
interface MailCompositionInput {
|
|
30
|
-
readonly configManager: { get(key:
|
|
30
|
+
readonly configManager: { get(key: string): unknown };
|
|
31
31
|
readonly secretsManager: { get(key: string): Promise<string | null> };
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -36,8 +36,8 @@ interface MailCompositionInput {
|
|
|
36
36
|
* `registerGatewayVerbGroups`.
|
|
37
37
|
*
|
|
38
38
|
* The settings come from the daemon's own `surfaces.email.*` keys through
|
|
39
|
-
* `withSurfaceEmailConfig`, so the keys an operator has already set
|
|
40
|
-
* the settings modal now shows
|
|
39
|
+
* `withSurfaceEmailConfig`, so the keys an operator has already set, and that
|
|
40
|
+
* the settings modal now shows, keep working unchanged, and a not-configured
|
|
41
41
|
* answer names the keys THIS operator actually has rather than the ones the
|
|
42
42
|
* service validates internally.
|
|
43
43
|
*/
|
|
@@ -45,7 +45,7 @@ export function composeMailDeps(input: MailCompositionInput): {
|
|
|
45
45
|
readonly emailServiceDeps: EmailServiceDeps;
|
|
46
46
|
readonly describeEmailConfigProblem: () => Promise<SurfaceEmailConfigProblem | null>;
|
|
47
47
|
} {
|
|
48
|
-
const getConfig = (key: string): unknown => input.configManager.get(key
|
|
48
|
+
const getConfig = (key: string): unknown => input.configManager.get(key);
|
|
49
49
|
const emailServiceDeps = withSurfaceEmailConfig({
|
|
50
50
|
getConfig,
|
|
51
51
|
secretsManager: input.secretsManager,
|
|
@@ -56,7 +56,7 @@ export function composeMailDeps(input: MailCompositionInput): {
|
|
|
56
56
|
// raises display confidence only, and commandAuthority is the literal
|
|
57
57
|
// 'none'.
|
|
58
58
|
describeSenderClaim: describeSenderClaimNeutrally,
|
|
59
|
-
}
|
|
59
|
+
});
|
|
60
60
|
return {
|
|
61
61
|
emailServiceDeps,
|
|
62
62
|
describeEmailConfigProblem: () =>
|
|
@@ -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,143 @@
|
|
|
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 in-process, and that is a statement ──────────────
|
|
35
|
+
*
|
|
36
|
+
* `BudgetLedger` is constructed empty here and never persisted. That is correct
|
|
37
|
+
* ONLY while checkout is unattached: the sole writer of a spend record is the
|
|
38
|
+
* checkout flow, so there is nothing to persist, and a state file nothing writes
|
|
39
|
+
* would be decoration. It stops being correct the moment `payments.checkout.begin`
|
|
40
|
+
* is wired, and the failure would be silent and expensive, a daemon restarted at
|
|
41
|
+
* noon would hand back a daily budget it had already spent. Making this ledger
|
|
42
|
+
* durable is part of wiring checkout.
|
|
43
|
+
*
|
|
44
|
+
* ── What is NOT composed ──────────────────────────────────────────────────
|
|
45
|
+
*
|
|
46
|
+
* `payments.checkout.begin` and `payments.checkout.fillCard` stay unattached and
|
|
47
|
+
* keep answering 501. They need a `CheckoutPageDriver` over an open browser
|
|
48
|
+
* page, and this composition has no way to obtain one: the SDK builds its
|
|
49
|
+
* browser engine inside `registerGatewayVerbGroups`
|
|
50
|
+
* (control-plane/routes/browser-composition.ts), hands back only the
|
|
51
|
+
* `BrowserGatewayService` slice, which exposes no page handle and no
|
|
52
|
+
* `fillSecret`, and constructs it with no `cardFieldGuard`, which its own
|
|
53
|
+
* secret-fill path refuses without. There is also no adapter anywhere from a
|
|
54
|
+
* browser session to a `CheckoutPageDriver`. Writing one here would put the
|
|
55
|
+
* card-into-page seam in this repository, and card-material.ts is explicit that
|
|
56
|
+
* exactly one module in the platform may produce card material. So the two verbs
|
|
57
|
+
* refuse honestly instead.
|
|
58
|
+
*/
|
|
59
|
+
import { controlPlaneStorePath } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
60
|
+
import type { GatewayMethodCatalog } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
61
|
+
import { BudgetLedger, readCvvHandling } from '@pellux/goodvibes-sdk/platform/payments';
|
|
62
|
+
import type { PaymentsConfigReader } from '@pellux/goodvibes-sdk/platform/payments';
|
|
63
|
+
import type { ConfigManager, SecretsManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
64
|
+
import type { ShellPathService } from '@/runtime/index.ts';
|
|
65
|
+
import {
|
|
66
|
+
DaemonCardStore,
|
|
67
|
+
DaemonPurchaseLedger,
|
|
68
|
+
registerPaymentsMethods,
|
|
69
|
+
type PaymentsSecretStore,
|
|
70
|
+
} from '../daemon/handlers/payments/index.ts';
|
|
71
|
+
import { GOODVIBES_DAEMON_SURFACE_ROOT } from '../config/surface.ts';
|
|
72
|
+
|
|
73
|
+
export interface PaymentsCompositionOptions {
|
|
74
|
+
readonly configManager: ConfigManager;
|
|
75
|
+
readonly shellPaths: ShellPathService;
|
|
76
|
+
readonly secretsManager: SecretsManager;
|
|
77
|
+
/** Binding the catalog is what turns the family from a 501 facade into handlers. */
|
|
78
|
+
readonly gatewayMethods: GatewayMethodCatalog;
|
|
79
|
+
/**
|
|
80
|
+
* Whether this node is the one currently allowed to spend.
|
|
81
|
+
*
|
|
82
|
+
* Reported by `payments.budget.status` and never defaulted, see the SDK's
|
|
83
|
+
* gates.ts: on a clustered install the wrong answer is a double-spend.
|
|
84
|
+
*/
|
|
85
|
+
readonly isPaymentsLeader: () => boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface PaymentsServices {
|
|
89
|
+
readonly cards: DaemonCardStore;
|
|
90
|
+
readonly purchases: DaemonPurchaseLedger;
|
|
91
|
+
readonly budget: BudgetLedger;
|
|
92
|
+
/** Detaches the handlers. Held by the runtime disposal scope. */
|
|
93
|
+
readonly unregister: () => void;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The narrow secret port the card store gets: three operations over the daemon
|
|
98
|
+
* tier, and no way to reach any other credential in the process. The same
|
|
99
|
+
* treatment cluster-group-composition.ts gives the group key.
|
|
100
|
+
*/
|
|
101
|
+
function daemonScopedSecrets(secretsManager: SecretsManager): PaymentsSecretStore {
|
|
102
|
+
return {
|
|
103
|
+
get: (key) => secretsManager.get(key),
|
|
104
|
+
set: async (key, value) => {
|
|
105
|
+
await secretsManager.set(key, value, { scope: 'daemon', medium: 'secure' });
|
|
106
|
+
},
|
|
107
|
+
delete: async (key) => {
|
|
108
|
+
await secretsManager.delete(key, { scope: 'daemon' });
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Read live, per call: a budget raised five minutes ago applies to the next read. */
|
|
114
|
+
function livePaymentsConfig(configManager: ConfigManager): PaymentsConfigReader {
|
|
115
|
+
return { get: (key: string) => configManager.get(key as Parameters<ConfigManager['get']>[0]) };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Build the payment stores and bind the answerable verbs to them.
|
|
120
|
+
*
|
|
121
|
+
* Constructing this touches no disk: both stores read lazily and write only when
|
|
122
|
+
* a verb asks them to, so composing a runtime in a test creates no files.
|
|
123
|
+
*/
|
|
124
|
+
export function createPaymentsServices(options: PaymentsCompositionOptions): PaymentsServices {
|
|
125
|
+
const config = livePaymentsConfig(options.configManager);
|
|
126
|
+
const cards = new DaemonCardStore({
|
|
127
|
+
filePath: controlPlaneStorePath(options.shellPaths, GOODVIBES_DAEMON_SURFACE_ROOT, 'payments-cards.json'),
|
|
128
|
+
secrets: daemonScopedSecrets(options.secretsManager),
|
|
129
|
+
cvvHandling: () => readCvvHandling(config),
|
|
130
|
+
});
|
|
131
|
+
const purchases = new DaemonPurchaseLedger({
|
|
132
|
+
filePath: controlPlaneStorePath(options.shellPaths, GOODVIBES_DAEMON_SURFACE_ROOT, 'payments-purchases.json'),
|
|
133
|
+
});
|
|
134
|
+
const budget = new BudgetLedger();
|
|
135
|
+
const unregister = registerPaymentsMethods(options.gatewayMethods, {
|
|
136
|
+
cards,
|
|
137
|
+
purchases,
|
|
138
|
+
budget,
|
|
139
|
+
config,
|
|
140
|
+
isPaymentsLeader: options.isPaymentsLeader,
|
|
141
|
+
});
|
|
142
|
+
return { cards, purchases, budget, unregister };
|
|
143
|
+
}
|
|
@@ -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
|