@pellux/goodvibes-tui 1.9.1 → 1.10.0
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 +36 -5
- package/README.md +1 -1
- package/docs/foundation-artifacts/knowledge-graphql.graphql +5 -0
- package/docs/foundation-artifacts/knowledge-store.sql +33 -0
- package/docs/foundation-artifacts/operator-contract.json +4026 -1376
- package/package.json +2 -2
- package/src/audio/player.ts +91 -5
- package/src/audio/spoken-turn-wiring.ts +10 -2
- package/src/cli/management-utils.ts +4 -0
- package/src/config/credential-availability.ts +1 -1
- package/src/config/index.ts +1 -1
- package/src/core/turn-cancellation.ts +1 -1
- package/src/daemon/cli.ts +4 -4
- package/src/daemon/service-commands.ts +8 -8
- package/src/export/cost-utils.ts +9 -1
- package/src/input/command-registry.ts +16 -5
- package/src/input/commands/config.ts +1 -1
- package/src/input/commands/control-room-runtime.ts +12 -5
- package/src/input/commands/incident-runtime.ts +2 -2
- package/src/input/commands/memory.ts +12 -12
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/recall-bundle.ts +8 -8
- package/src/input/commands/recall-capture.ts +3 -3
- package/src/input/commands/recall-query.ts +41 -15
- package/src/input/commands/recall-review.ts +12 -12
- package/src/input/commands/session-content.ts +1 -1
- package/src/input/feed-context-factory.ts +1 -1
- package/src/input/handler-content-actions.ts +2 -2
- package/src/input/handler-feed-routes.ts +2 -2
- package/src/input/handler-feed.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +3 -3
- package/src/input/handler-picker-routes.ts +2 -2
- package/src/input/handler-shortcuts.ts +1 -1
- package/src/input/model-picker.ts +2 -2
- package/src/input/onboarding/onboarding-wizard-apply.ts +3 -3
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +3 -3
- package/src/input/onboarding/onboarding-wizard-steps.ts +1 -1
- package/src/input/session-picker-modal.ts +1 -1
- package/src/input/settings-modal-data.ts +56 -2
- package/src/main.ts +8 -6
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/operations.ts +4 -4
- package/src/panels/builtin/shared.ts +4 -4
- package/src/panels/cost-tracker-panel.ts +2 -2
- package/src/panels/fleet-panel-format.ts +1 -1
- package/src/panels/fleet-panel.ts +15 -15
- package/src/panels/fleet-read-model.ts +11 -11
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +2 -2
- package/src/panels/fleet-tabs.ts +4 -4
- package/src/panels/fleet-transcript.ts +5 -5
- package/src/panels/modals/memory-modal.ts +77 -16
- package/src/panels/types.ts +1 -1
- package/src/renderer/compaction-quality.ts +1 -1
- package/src/renderer/fleet-tab-strip.ts +2 -2
- package/src/renderer/footer-tips.ts +1 -1
- package/src/renderer/model-workspace.ts +1 -1
- package/src/renderer/session-picker-modal.ts +4 -4
- package/src/renderer/shell-surface.ts +1 -1
- package/src/renderer/status-glyphs.ts +3 -3
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +2 -2
- package/src/renderer/turn-injection.ts +3 -3
- package/src/renderer/ui-factory.ts +7 -7
- package/src/renderer/ui-primitives.ts +3 -3
- package/src/runtime/bootstrap-command-context.ts +4 -0
- package/src/runtime/bootstrap-command-parts.ts +4 -1
- package/src/runtime/bootstrap-core.ts +3 -3
- package/src/runtime/bootstrap-hook-bridge.ts +2 -2
- package/src/runtime/bootstrap-shell.ts +18 -7
- package/src/runtime/bootstrap.ts +22 -22
- package/src/runtime/code-index-services.ts +2 -2
- package/src/runtime/index.ts +11 -3
- package/src/runtime/legacy-daemon-migration.ts +9 -9
- package/src/runtime/memory-fold.ts +1 -1
- package/src/runtime/memory-spine-transport.ts +289 -0
- package/src/runtime/onboarding/snapshot.ts +3 -3
- package/src/runtime/onboarding/types.ts +2 -2
- package/src/runtime/operator-token-cleanup.ts +2 -2
- package/src/runtime/orchestrator-core-services.ts +29 -4
- package/src/runtime/process-lifecycle.ts +19 -5
- package/src/runtime/services.ts +27 -28
- package/src/runtime/session-inbound-inputs.ts +1 -1
- package/src/runtime/session-spine-transport.ts +2 -2
- package/src/runtime/ui-services.ts +1 -1
- package/src/runtime/workstream-services.ts +1 -1
- package/src/runtime/wrfc-persistence.ts +2 -2
- package/src/shell/ui-openers.ts +2 -2
- package/src/version.ts +1 -1
- package/src/audio/spoken-turn-controller.ts +0 -243
- package/src/audio/text-chunker.ts +0 -110
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory-spine-transport.ts
|
|
3
|
+
*
|
|
4
|
+
* The TUI memory-spine cutover: the thin wire adapter that lets the TUI's
|
|
5
|
+
* bootstrap activate the SDK's
|
|
6
|
+
* `@pellux/goodvibes-sdk/platform/runtime/memory-spine` `MemorySpineClient`
|
|
7
|
+
* over the daemon's memory.records.* HTTP routes, mirroring
|
|
8
|
+
* session-spine-transport.ts's pattern for the session spine.
|
|
9
|
+
*
|
|
10
|
+
* The SDK does not (yet) ship a typed operator client for these routes (unlike
|
|
11
|
+
* `HttpTransport.operator.sessions`), so this adapter builds a small REST
|
|
12
|
+
* client directly against the known routes — exactly what the SDK's own
|
|
13
|
+
* `MemoryTransport` doc comment describes as the expected consumer shape.
|
|
14
|
+
*
|
|
15
|
+
* FULL DETACH (SDK 1.2.0). Beyond the five 1.1.0 CORE routes (add, search,
|
|
16
|
+
* get, review, delete) this now implements the full 1.2.0 EXTENDED catalog
|
|
17
|
+
* (list, search-semantic, update, links.list, links.add, export, import,
|
|
18
|
+
* vector, doctor, review-queue) — see
|
|
19
|
+
* docs/decisions/2026-07-06-memory-wire-full-detach.md in the SDK repo. This
|
|
20
|
+
* is what lets `/recall`'s browse/link/queue/export/import subcommands and
|
|
21
|
+
* the per-turn knowledge-injection bulk read fully detach from the local
|
|
22
|
+
* store file when a daemon is adopted: every one of those ops now has a wire
|
|
23
|
+
* equivalent, so this transport implements every EXTENDED verb rather than
|
|
24
|
+
* leaving some optional.
|
|
25
|
+
*
|
|
26
|
+
* HONESTY. Unlike the session mirror (fire-and-forget, folded into a soft
|
|
27
|
+
* 'offline' result), memory reads/writes return data the caller depends on —
|
|
28
|
+
* a transport failure here is NOT swallowed. It propagates as a rejected
|
|
29
|
+
* promise, matching `MemorySpineClient`'s documented contract: a wire client
|
|
30
|
+
* must never silently fall back to a divergent local copy in place of a real
|
|
31
|
+
* failure.
|
|
32
|
+
*
|
|
33
|
+
* A 404 is disambiguated by its RESPONSE CODE (`classifyMemoryWireError`, from
|
|
34
|
+
* the SDK's `platform/runtime/memory-spine` — this transport no longer inlines
|
|
35
|
+
* its own copy of the discriminator), never by the bare status. A record-missing
|
|
36
|
+
* 404 carries `MEMORY_RECORD_NOT_FOUND`: on a nullable verb
|
|
37
|
+
* (`get`/`updateReview`/`update`/`link`) it maps to the documented `null` "not
|
|
38
|
+
* found"; on a non-nullable verb (`linksFor` etc.) it is not representable as
|
|
39
|
+
* null so it propagates as a thrown error. ANY OTHER 404 — a route-not-found
|
|
40
|
+
* from an older daemon that never registered this route, or a bare legacy 404
|
|
41
|
+
* with no code — is treated as "this daemon does not serve this verb" and
|
|
42
|
+
* rejects with a stated reason on EVERY verb, never a silent `null`. That is
|
|
43
|
+
* what lets `/recall` surface an honest degraded message on a version-skewed
|
|
44
|
+
* daemon instead of reporting an existing record as gone.
|
|
45
|
+
*/
|
|
46
|
+
import { buildUrl, createJsonRequestInit, requestJsonRaw } from '@pellux/goodvibes-sdk/transport-http';
|
|
47
|
+
import {
|
|
48
|
+
classifyMemoryWireError,
|
|
49
|
+
memoryVerbUnavailableError,
|
|
50
|
+
type MemoryAccess,
|
|
51
|
+
type MemoryTransport,
|
|
52
|
+
type MemoryUpdatePatch,
|
|
53
|
+
} from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
|
|
54
|
+
import type {
|
|
55
|
+
HonestMemorySearchOptions,
|
|
56
|
+
HonestMemorySearchResult,
|
|
57
|
+
MemoryAddOptions,
|
|
58
|
+
MemoryBundle,
|
|
59
|
+
MemoryDoctorReport,
|
|
60
|
+
MemoryLink,
|
|
61
|
+
MemoryRecord,
|
|
62
|
+
MemoryScope,
|
|
63
|
+
MemorySearchFilter,
|
|
64
|
+
MemorySemanticSearchResult,
|
|
65
|
+
} from '@pellux/goodvibes-sdk/platform/state';
|
|
66
|
+
import type { MemoryVectorStats } from '@pellux/goodvibes-sdk/platform/state';
|
|
67
|
+
|
|
68
|
+
/** Derived from `MemoryAccess` itself rather than imported by name — `MemoryReviewPatch` is not re-exported from the SDK's public `platform/state` entry point. */
|
|
69
|
+
type MemoryReviewPatch = Parameters<MemoryAccess['updateReview']>[1];
|
|
70
|
+
/** Same reasoning — `MemoryImportResult` is not re-exported from a public SDK entry point. */
|
|
71
|
+
type MemoryImportResult = Awaited<ReturnType<MemoryAccess['importBundle']>>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The 404 discriminator and the "daemon does not serve this verb" rejection are
|
|
75
|
+
* now the SDK's own `classifyMemoryWireError` / `memoryVerbUnavailableError`
|
|
76
|
+
* (`@pellux/goodvibes-sdk/platform/runtime/memory-spine`) rather than a copy
|
|
77
|
+
* inlined in this transport — this surface's SDK pin now ships them as a
|
|
78
|
+
* shared constant/discriminator, so the two thin folds below just delegate.
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/** Fold for a NULLABLE record-scoped verb: record-miss → null; version-skew → honest reject; else rethrow. */
|
|
82
|
+
function foldNullableMemoryWire404(verb: string, error: unknown): null {
|
|
83
|
+
const kind = classifyMemoryWireError(error);
|
|
84
|
+
if (kind === 'record-missing') return null;
|
|
85
|
+
if (kind === 'method-unavailable') throw memoryVerbUnavailableError(verb, error);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Fold for a NON-NULLABLE (collection or record-required) verb: version-skew → honest reject; else rethrow. */
|
|
90
|
+
function rethrowMemoryWire404(verb: string, error: unknown): never {
|
|
91
|
+
if (classifyMemoryWireError(error) === 'method-unavailable') throw memoryVerbUnavailableError(verb, error);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface MemorySpineWireOptions {
|
|
96
|
+
readonly baseUrl: string;
|
|
97
|
+
readonly authToken: string | null;
|
|
98
|
+
readonly fetchImpl?: typeof fetch;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Build the `MemorySpineClient` wire transport: a thin REST adapter over the
|
|
103
|
+
* adopted daemon's full memory.records.* route catalog (five CORE routes plus
|
|
104
|
+
* the ten 1.2.0 EXTENDED routes). `bootstrap.ts` activates a client built from
|
|
105
|
+
* this the same way it adopts the session spine.
|
|
106
|
+
*/
|
|
107
|
+
export function createTuiMemorySpineTransport(options: MemorySpineWireOptions): MemoryTransport {
|
|
108
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
109
|
+
const token = options.authToken;
|
|
110
|
+
const url = (path: string): string => buildUrl(options.baseUrl, path);
|
|
111
|
+
return {
|
|
112
|
+
add: async (opts: MemoryAddOptions): Promise<MemoryRecord> => {
|
|
113
|
+
const response = await requestJsonRaw<{ record: MemoryRecord }>(
|
|
114
|
+
fetchImpl, url('/api/memory/records'), createJsonRequestInit(token, opts, 'POST'),
|
|
115
|
+
);
|
|
116
|
+
return response.record;
|
|
117
|
+
},
|
|
118
|
+
honestSearch: async (filter?: MemorySearchFilter, searchOptions?: HonestMemorySearchOptions): Promise<HonestMemorySearchResult> => {
|
|
119
|
+
const body = { ...(filter ?? {}), ...(searchOptions?.recall !== undefined ? { recall: searchOptions.recall } : {}) };
|
|
120
|
+
return await requestJsonRaw<HonestMemorySearchResult>(
|
|
121
|
+
fetchImpl, url('/api/memory/records/search'), createJsonRequestInit(token, body, 'POST'),
|
|
122
|
+
);
|
|
123
|
+
},
|
|
124
|
+
get: async (id: string): Promise<MemoryRecord | null> => {
|
|
125
|
+
try {
|
|
126
|
+
const response = await requestJsonRaw<{ record: MemoryRecord }>(
|
|
127
|
+
fetchImpl, url(`/api/memory/records/${encodeURIComponent(id)}`), createJsonRequestInit(token),
|
|
128
|
+
);
|
|
129
|
+
return response.record;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return foldNullableMemoryWire404('get', error);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
updateReview: async (id: string, patch: MemoryReviewPatch): Promise<MemoryRecord | null> => {
|
|
135
|
+
try {
|
|
136
|
+
const response = await requestJsonRaw<{ record: MemoryRecord }>(
|
|
137
|
+
fetchImpl, url(`/api/memory/records/${encodeURIComponent(id)}/review`), createJsonRequestInit(token, patch, 'POST'),
|
|
138
|
+
);
|
|
139
|
+
return response.record;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return foldNullableMemoryWire404('updateReview', error);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
delete: async (id: string): Promise<boolean> => {
|
|
145
|
+
const response = await requestJsonRaw<{ id: string; deleted: boolean }>(
|
|
146
|
+
fetchImpl, url(`/api/memory/records/${encodeURIComponent(id)}`), createJsonRequestInit(token, undefined, 'DELETE'),
|
|
147
|
+
);
|
|
148
|
+
return response.deleted;
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
// ── Extended verbs (1.2.0 full-detach) ──────────────────────────────────
|
|
152
|
+
|
|
153
|
+
list: async (filter?: MemorySearchFilter): Promise<readonly MemoryRecord[]> => {
|
|
154
|
+
try {
|
|
155
|
+
const response = await requestJsonRaw<{ records: readonly MemoryRecord[] }>(
|
|
156
|
+
fetchImpl, url('/api/memory/records/list'), createJsonRequestInit(token, filter ?? {}, 'POST'),
|
|
157
|
+
);
|
|
158
|
+
return response.records;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return rethrowMemoryWire404('list', error);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
searchSemantic: async (filter?: MemorySearchFilter): Promise<readonly MemorySemanticSearchResult[]> => {
|
|
164
|
+
try {
|
|
165
|
+
const response = await requestJsonRaw<{ results: readonly MemorySemanticSearchResult[] }>(
|
|
166
|
+
fetchImpl, url('/api/memory/records/search-semantic'), createJsonRequestInit(token, filter ?? {}, 'POST'),
|
|
167
|
+
);
|
|
168
|
+
return response.results;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
return rethrowMemoryWire404('searchSemantic', error);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
update: async (id: string, patch: MemoryUpdatePatch): Promise<MemoryRecord | null> => {
|
|
174
|
+
try {
|
|
175
|
+
const response = await requestJsonRaw<{ record: MemoryRecord }>(
|
|
176
|
+
fetchImpl, url(`/api/memory/records/${encodeURIComponent(id)}/update`), createJsonRequestInit(token, patch, 'POST'),
|
|
177
|
+
);
|
|
178
|
+
return response.record;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return foldNullableMemoryWire404('update', error);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
link: async (fromId: string, toId: string, relation: string): Promise<MemoryLink | null> => {
|
|
184
|
+
try {
|
|
185
|
+
const response = await requestJsonRaw<{ link: MemoryLink }>(
|
|
186
|
+
fetchImpl, url(`/api/memory/records/${encodeURIComponent(fromId)}/links`), createJsonRequestInit(token, { toId, relation }, 'POST'),
|
|
187
|
+
);
|
|
188
|
+
return response.link;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
return foldNullableMemoryWire404('link', error);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
linksFor: async (id: string): Promise<readonly MemoryLink[]> => {
|
|
194
|
+
try {
|
|
195
|
+
const response = await requestJsonRaw<{ links: readonly MemoryLink[] }>(
|
|
196
|
+
fetchImpl, url(`/api/memory/records/${encodeURIComponent(id)}/links`), createJsonRequestInit(token),
|
|
197
|
+
);
|
|
198
|
+
return response.links;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return rethrowMemoryWire404('linksFor', error);
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
reviewQueue: async (limit?: number, scope?: MemoryScope): Promise<readonly MemoryRecord[]> => {
|
|
204
|
+
const params = new URLSearchParams();
|
|
205
|
+
if (limit !== undefined) params.set('limit', String(limit));
|
|
206
|
+
if (scope !== undefined) params.set('scope', scope);
|
|
207
|
+
const query = params.toString();
|
|
208
|
+
try {
|
|
209
|
+
const response = await requestJsonRaw<{ records: readonly MemoryRecord[] }>(
|
|
210
|
+
fetchImpl, url(`/api/memory/review-queue${query ? `?${query}` : ''}`), createJsonRequestInit(token),
|
|
211
|
+
);
|
|
212
|
+
return response.records;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return rethrowMemoryWire404('reviewQueue', error);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
exportBundle: async (filter?: MemorySearchFilter): Promise<MemoryBundle> => {
|
|
218
|
+
try {
|
|
219
|
+
const response = await requestJsonRaw<{ bundle: MemoryBundle }>(
|
|
220
|
+
fetchImpl, url('/api/memory/records/export'), createJsonRequestInit(token, filter ?? {}, 'POST'),
|
|
221
|
+
);
|
|
222
|
+
return response.bundle;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
return rethrowMemoryWire404('exportBundle', error);
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
importBundle: async (bundle: MemoryBundle): Promise<MemoryImportResult> => {
|
|
228
|
+
try {
|
|
229
|
+
const response = await requestJsonRaw<{ result: MemoryImportResult }>(
|
|
230
|
+
fetchImpl, url('/api/memory/records/import'), createJsonRequestInit(token, { bundle }, 'POST'),
|
|
231
|
+
);
|
|
232
|
+
return response.result;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return rethrowMemoryWire404('importBundle', error);
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
vectorStats: async (): Promise<MemoryVectorStats> => {
|
|
238
|
+
try {
|
|
239
|
+
const response = await requestJsonRaw<{ vector: MemoryVectorStats }>(
|
|
240
|
+
fetchImpl, url('/api/memory/vector'), createJsonRequestInit(token),
|
|
241
|
+
);
|
|
242
|
+
return response.vector;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
return rethrowMemoryWire404('vectorStats', error);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
doctor: async (): Promise<MemoryDoctorReport> => {
|
|
248
|
+
try {
|
|
249
|
+
return await requestJsonRaw<MemoryDoctorReport>(
|
|
250
|
+
fetchImpl, url('/api/memory/doctor'), createJsonRequestInit(token),
|
|
251
|
+
);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
return rethrowMemoryWire404('doctor', error);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Mutable one-slot ref tracking which adopted daemon's baseUrl the memory spine is currently wired to (mirrors bootstrap.ts's `spineActiveForBaseUrl`). */
|
|
260
|
+
export interface MemorySpineActiveRef { value: string | null }
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Sync the memory spine to the current daemon-adoption status — the SAME
|
|
264
|
+
* signal bootstrap.ts's `syncSessionSpineToHostStatus` uses. 'external' (an
|
|
265
|
+
* adopted, separately-running daemon) activates the wire transport; every
|
|
266
|
+
* other mode ('embedded' — this process IS the daemon host, so its own store
|
|
267
|
+
* is already canonical — plus 'disabled'/'blocked'/'incompatible'/'unavailable')
|
|
268
|
+
* deactivates back to local access.
|
|
269
|
+
*/
|
|
270
|
+
export function syncMemorySpineToHostStatus(
|
|
271
|
+
memorySpine: { activate: (transport: MemoryTransport) => void; deactivate: (reason: string) => void },
|
|
272
|
+
daemonMode: string,
|
|
273
|
+
daemonBaseUrl: string,
|
|
274
|
+
sharedDaemonToken: string,
|
|
275
|
+
activeRef: MemorySpineActiveRef,
|
|
276
|
+
log: { info: (message: string) => void },
|
|
277
|
+
): void {
|
|
278
|
+
if (daemonMode !== 'external') {
|
|
279
|
+
if (activeRef.value !== null) {
|
|
280
|
+
memorySpine.deactivate(`daemon mode changed to '${daemonMode}'`);
|
|
281
|
+
activeRef.value = null;
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (activeRef.value === daemonBaseUrl) return; // already wired to this exact adopted daemon
|
|
286
|
+
memorySpine.activate(createTuiMemorySpineTransport({ baseUrl: daemonBaseUrl, authToken: sharedDaemonToken }));
|
|
287
|
+
activeRef.value = daemonBaseUrl;
|
|
288
|
+
log.info(`[bootstrap] memory spine: adopted external daemon at ${daemonBaseUrl} — routing memory ops over the wire`);
|
|
289
|
+
}
|
|
@@ -403,12 +403,12 @@ export async function collectOnboardingSnapshot(
|
|
|
403
403
|
snapshot: authSnapshotResult.value,
|
|
404
404
|
},
|
|
405
405
|
bindSettings: {
|
|
406
|
-
// danger.daemon (removed
|
|
407
|
-
// used to gate the
|
|
406
|
+
// danger.daemon (since removed — see docs/decisions/2026-07-05-daemon-by-default.md)
|
|
407
|
+
// used to gate the earlier opt-in daemon posture; this site read it RAW
|
|
408
408
|
// (not through resolveDaemonEnabled) so the wizard's network-mode
|
|
409
409
|
// classification tracked "did the user explicitly request the legacy
|
|
410
410
|
// dangerous posture" rather than "does the daemon run" (which has defaulted
|
|
411
|
-
// true unconditionally since
|
|
411
|
+
// true unconditionally since daemon-by-default landed, and would misclassify every default,
|
|
412
412
|
// local-only install as server-mode if read here). Every realistic config
|
|
413
413
|
// already evaluated this to `false` (unset, or an explicit `danger.daemon:
|
|
414
414
|
// false` both did); the alias's removal migration preserves the one case
|
|
@@ -126,7 +126,7 @@ export interface OnboardingSurfacesSnapshot {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
*
|
|
129
|
+
* Wizard wiring: read-only detection of a legacy `goodvibes-daemon.service`
|
|
130
130
|
* systemd unit (see `../legacy-daemon-migration.ts`'s `LegacyUnitInfo`), carried
|
|
131
131
|
* on the snapshot so the Network step can show the guided migration action only
|
|
132
132
|
* when there is actually something to migrate. Never implies anything was
|
|
@@ -137,7 +137,7 @@ export interface OnboardingLegacyDaemonSnapshot {
|
|
|
137
137
|
readonly active: boolean;
|
|
138
138
|
readonly path: string;
|
|
139
139
|
/**
|
|
140
|
-
*
|
|
140
|
+
* Follow-up: the unit name this tool actually manages on this host,
|
|
141
141
|
* resolved from the `service.serviceName` config key at snapshot-collection
|
|
142
142
|
* time (`resolveConfiguredServiceName`, `../legacy-daemon-migration.ts`) —
|
|
143
143
|
* so the wizard's detection banner names the real unit instead of the
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Shared helper that enumerates the legacy workspace-scoped `operator-tokens.json`
|
|
5
5
|
* locations the TUI has written at various pre-0.21.28 versions. Used by both the
|
|
6
6
|
* in-process bootstrap path (`src/runtime/bootstrap.ts`) and the standalone daemon
|
|
7
|
-
* CLI (`src/daemon/cli.ts`) so
|
|
7
|
+
* CLI (`src/daemon/cli.ts`) so stale-token pruning has a single source of
|
|
8
8
|
* truth for where to look.
|
|
9
9
|
*
|
|
10
10
|
* Adding a new legacy location: append to `workspaceOperatorTokenCandidates` and
|
|
@@ -32,7 +32,7 @@ export function workspaceOperatorTokenCandidates(workingDirectory: string): read
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* External-daemon adoption: resolve the operator/companion
|
|
36
36
|
* token this TUI process uses to authenticate with its daemon, honoring
|
|
37
37
|
* `GOODVIBES_DAEMON_TOKEN` as a non-interactive override.
|
|
38
38
|
*
|
|
@@ -12,7 +12,7 @@ export type OrchestratorCoreServicesSource = Pick<
|
|
|
12
12
|
| 'sessionMemoryStore'
|
|
13
13
|
| 'sessionLineageTracker'
|
|
14
14
|
| 'idempotencyStore'
|
|
15
|
-
| '
|
|
15
|
+
| 'memorySpine'
|
|
16
16
|
| 'codeIndexStore'
|
|
17
17
|
| 'codeIndexReindexScheduler'
|
|
18
18
|
>;
|
|
@@ -24,7 +24,7 @@ export type OrchestratorCoreServicesSource = Pick<
|
|
|
24
24
|
* cacheHitTracker; main: favoritesStore) — setCoreServices() merges, so the
|
|
25
25
|
* later main.ts call only overlays, never erases.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
27
|
+
* BLOCKER regression guard: `memoryRegistry` here is what turns
|
|
28
28
|
* on per-turn passive knowledge injection for the MAIN interactive session —
|
|
29
29
|
* the SDK turn loop hard-gates on `coreServices.memoryRegistry` (undefined is
|
|
30
30
|
* a silent no-op: Orchestrator.getTurnInjections() stays empty forever and
|
|
@@ -32,6 +32,18 @@ export type OrchestratorCoreServicesSource = Pick<
|
|
|
32
32
|
* omitted it independently; routing them through this one function (with
|
|
33
33
|
* src/test/runtime/orchestrator-core-services.test.ts pinning the field) is
|
|
34
34
|
* what keeps that from regressing.
|
|
35
|
+
*
|
|
36
|
+
* SDK 1.2.0 FULL DETACH: the SDK's turn loop reads `memoryRegistry.getAll()`
|
|
37
|
+
* SYNCHRONOUSLY (`TurnKnowledgeRegistrySource`), but the memory spine's wire
|
|
38
|
+
* reads are asynchronous — a sync function cannot await the wire. Per
|
|
39
|
+
* docs/decisions/2026-07-06-memory-wire-full-detach.md (SDK repo) this is
|
|
40
|
+
* satisfied by the spine's freshness-stamped recall snapshot instead of the
|
|
41
|
+
* raw local `memoryRegistry`: `getAll()` reads `memorySpine.recallSnapshot()`,
|
|
42
|
+
* which returns whatever the last `refreshRecallSnapshot()` (an async
|
|
43
|
+
* pre-turn hook — see the `handleUserInput` call sites) captured, honestly
|
|
44
|
+
* empty/stale until refreshed. This is what lets per-turn knowledge injection
|
|
45
|
+
* detach from the local store file when a daemon is adopted, instead of
|
|
46
|
+
* always reading the (possibly divergent) local registry regardless of mode.
|
|
35
47
|
*/
|
|
36
48
|
export function buildSharedOrchestratorCoreServices(input: {
|
|
37
49
|
readonly services: OrchestratorCoreServicesSource;
|
|
@@ -47,8 +59,8 @@ export function buildSharedOrchestratorCoreServices(input: {
|
|
|
47
59
|
sessionMemoryStore: services.sessionMemoryStore,
|
|
48
60
|
sessionLineageTracker: services.sessionLineageTracker,
|
|
49
61
|
idempotencyStore: services.idempotencyStore,
|
|
50
|
-
memoryRegistry: services.
|
|
51
|
-
//
|
|
62
|
+
memoryRegistry: { getAll: () => services.memorySpine.recallSnapshot().records },
|
|
63
|
+
// Main-session code auto-injection + tool-site reindex. Injection is
|
|
52
64
|
// additionally gated by the default-off `agent-passive-code-injection` flag inside the
|
|
53
65
|
// SDK; here we supply the source, the live storage.codeIndexEnabled predicate, and the
|
|
54
66
|
// reindex scheduler.
|
|
@@ -57,3 +69,16 @@ export function buildSharedOrchestratorCoreServices(input: {
|
|
|
57
69
|
codeIndexReindexScheduler: services.codeIndexReindexScheduler,
|
|
58
70
|
};
|
|
59
71
|
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The async pre-turn hook: refreshes the memory spine's recall snapshot over
|
|
75
|
+
* the CURRENT route (wire when adopted, local otherwise) so the synchronous
|
|
76
|
+
* per-turn knowledge injection (`memoryRegistry.getAll()` above) reads
|
|
77
|
+
* up-to-date records instead of whatever the previous refresh captured.
|
|
78
|
+
* Failures are swallowed — an honest stale/empty snapshot (with its own
|
|
79
|
+
* degradation note, surfaced via `recallSnapshot().note`) is preferable to
|
|
80
|
+
* blocking the user's turn on a memory-read failure.
|
|
81
|
+
*/
|
|
82
|
+
export async function refreshMemoryRecallSnapshot(services: Pick<RuntimeServices, 'memorySpine'>): Promise<void> {
|
|
83
|
+
await services.memorySpine.refreshRecallSnapshot().catch(() => {});
|
|
84
|
+
}
|
|
@@ -57,7 +57,7 @@ export interface ProcessLifecycleDeps {
|
|
|
57
57
|
readonly unsubs: ReadonlyArray<() => void>;
|
|
58
58
|
readonly getRecoveryInterval: () => ReturnType<typeof setInterval> | null;
|
|
59
59
|
readonly setRecoveryInterval: (value: ReturnType<typeof setInterval> | null) => void;
|
|
60
|
-
readonly getStopSpokenOutputForExit: () => (() => void) | null;
|
|
60
|
+
readonly getStopSpokenOutputForExit: () => (() => void | Promise<void>) | null;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export interface ProcessLifecycleHandlers {
|
|
@@ -153,7 +153,16 @@ export function installProcessLifecycle(deps: ProcessLifecycleDeps): ProcessLife
|
|
|
153
153
|
// must not re-run teardown or double-fire ctx.shutdown.
|
|
154
154
|
if (exiting) return;
|
|
155
155
|
exiting = true;
|
|
156
|
-
|
|
156
|
+
// Exit lets the spoken audio the user is already hearing finish inside a
|
|
157
|
+
// short bounded window (capped inside stopForExit, under the 3s shutdown
|
|
158
|
+
// budget below) while the rest of teardown proceeds; queued-but-unplayed
|
|
159
|
+
// speech is dropped. Deliberate interrupts (Ctrl+C, /tts stop) still cut
|
|
160
|
+
// instantly through controller.stop() before this path is ever reached.
|
|
161
|
+
let spokenOutputDrain: Promise<void> = Promise.resolve();
|
|
162
|
+
try {
|
|
163
|
+
spokenOutputDrain = Promise.resolve(getStopSpokenOutputForExit()?.()).then(() => undefined);
|
|
164
|
+
} catch { /* non-fatal to exit */ }
|
|
165
|
+
spokenOutputDrain = spokenOutputDrain.catch(() => undefined);
|
|
157
166
|
unsubs.forEach(fn => fn());
|
|
158
167
|
const interval = getRecoveryInterval();
|
|
159
168
|
if (interval !== null) { clearInterval(interval); setRecoveryInterval(null); }
|
|
@@ -169,9 +178,14 @@ export function installProcessLifecycle(deps: ProcessLifecycleDeps): ProcessLife
|
|
|
169
178
|
try {
|
|
170
179
|
// Race the graceful shutdown against a hard timeout — externalServices.stop() can hang
|
|
171
180
|
// and we must still exit; deferredStartup.drain only budgets 100ms internally.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
// The spoken-audio drain runs concurrently and is internally capped below
|
|
182
|
+
// this budget, so it never extends the exit beyond the hard timeout.
|
|
183
|
+
await Promise.all([
|
|
184
|
+
spokenOutputDrain,
|
|
185
|
+
Promise.race([
|
|
186
|
+
ctx.shutdown({ ...snapshot, ...buildPersistedSessionContext(snapshot.messages, ctx.conversation.getTitleSource(), buildSessionContinuityHints()) }).then(() => { shutdownOk = true; }),
|
|
187
|
+
new Promise<void>((resolve) => setTimeout(resolve, 3000)),
|
|
188
|
+
]),
|
|
175
189
|
]);
|
|
176
190
|
} catch (err) {
|
|
177
191
|
logger.debug('ctx.shutdown error during exitApp (non-fatal)', { error: summarizeError(err) });
|
package/src/runtime/services.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { MultimodalService } from '@pellux/goodvibes-sdk/platform/multimodal';
|
|
|
24
24
|
import { AgentMessageBus, AgentOrchestrator, ArchetypeLoader, WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
|
|
25
25
|
import { AgentManager, OverflowHandler, ProcessManager, createWorkflowServices, type WorkflowServices } from '@pellux/goodvibes-sdk/platform/tools';
|
|
26
26
|
import { FileStateCache, FileUndoManager, MemoryEmbeddingProviderRegistry, MemoryRegistry, MemoryStore, ModeManager, ProjectIndex, resolveCanonicalMemoryDbPath, type CodeIndexStore, type CodeIndexReindexScheduler } from '@pellux/goodvibes-sdk/platform/state';
|
|
27
|
+
import { MemorySpineClient, createLocalMemoryAccess } from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
|
|
27
28
|
import { WorkspaceCheckpointManager } from '@pellux/goodvibes-sdk/platform/workspace';
|
|
28
29
|
import type { RuntimeEventBus } from '@/runtime/index.ts';
|
|
29
30
|
import { createDomainDispatch } from './store/index.ts';
|
|
@@ -166,6 +167,8 @@ export interface RuntimeServices {
|
|
|
166
167
|
readonly workPlanStore: WorkPlanStore;
|
|
167
168
|
readonly memoryStore: MemoryStore;
|
|
168
169
|
readonly memoryRegistry: MemoryRegistry;
|
|
170
|
+
/** Host-vs-client memory access: local until bootstrap.ts activates it for an adopted 'external' daemon (mirrors sessionSpine). */
|
|
171
|
+
readonly memorySpine: MemorySpineClient;
|
|
169
172
|
readonly serviceRegistry: ServiceRegistry;
|
|
170
173
|
readonly secretsManager: SecretsManager;
|
|
171
174
|
readonly subscriptionManager: SubscriptionManager;
|
|
@@ -222,24 +225,19 @@ export interface RuntimeServices {
|
|
|
222
225
|
readonly agentOrchestrator: AgentOrchestrator;
|
|
223
226
|
readonly wrfcController: WrfcController;
|
|
224
227
|
readonly processManager: ProcessManager;
|
|
225
|
-
/**
|
|
228
|
+
/** The phase/work-item orchestration engine — see runtime/workstream-services.ts. */
|
|
226
229
|
readonly orchestrationEngine: OrchestrationEngine;
|
|
227
230
|
readonly workstreamCommands: WorkstreamCommandService;
|
|
228
|
-
/**
|
|
231
|
+
/** The repo source-tree code index — see runtime/code-index-services.ts. */
|
|
229
232
|
readonly codeIndexStore: CodeIndexStore;
|
|
230
|
-
readonly codeIndexReindexScheduler: CodeIndexReindexScheduler; //
|
|
233
|
+
readonly codeIndexReindexScheduler: CodeIndexReindexScheduler; // tool-site reindex
|
|
231
234
|
/** W2.1/W2.2: unified live process registry (agents, WRFC chains, workflows, watchers, background processes) backing the Fleet panel. */
|
|
232
235
|
readonly processRegistry: ProcessRegistry;
|
|
233
236
|
readonly modeManager: ModeManager;
|
|
234
237
|
readonly fileUndoManager: FileUndoManager;
|
|
235
238
|
readonly workspaceCheckpointManager: WorkspaceCheckpointManager;
|
|
236
239
|
readonly integrationHelpers: IntegrationHelperService;
|
|
237
|
-
/**
|
|
238
|
-
* Re-root path-bound stores (MemoryStore, ProjectIndex) to a new working directory.
|
|
239
|
-
* Called by WorkspaceSwapManager after the new directory has been verified.
|
|
240
|
-
* Stores that require a process restart emit a warn-level log; they continue serving
|
|
241
|
-
* the old path until the daemon restarts with the new --working-dir.
|
|
242
|
-
*/
|
|
240
|
+
/** Re-root path-bound stores (MemoryStore, ProjectIndex) to a new working directory, called by WorkspaceSwapManager after verification; stores needing a process restart just warn-log and keep serving the old path until the daemon restarts with the new --working-dir. */
|
|
243
241
|
rerootStores(newWorkingDir: string): Promise<void>;
|
|
244
242
|
}
|
|
245
243
|
|
|
@@ -338,7 +336,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
338
336
|
executor: agentOrchestrator,
|
|
339
337
|
configManager,
|
|
340
338
|
});
|
|
341
|
-
agentOrchestrator.setConversationSink({ //
|
|
339
|
+
agentOrchestrator.setConversationSink({ // Conversation-snapshot bridge (mirrors the SDK's own createRuntimeServices)
|
|
342
340
|
register: (agentId, source) => agentManager.registerConversationSource(agentId, source),
|
|
343
341
|
release: (agentId) => agentManager.releaseConversationSource(agentId),
|
|
344
342
|
});
|
|
@@ -387,12 +385,16 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
387
385
|
});
|
|
388
386
|
const artifactStore = new ArtifactStore({ configManager });
|
|
389
387
|
const memoryEmbeddingRegistry = new MemoryEmbeddingProviderRegistry({ configManager });
|
|
390
|
-
//
|
|
388
|
+
// Open the ONE home-scoped canonical store; legacy per-project TUI memory folds in at boot (foldTuiLegacyMemory).
|
|
391
389
|
const memoryDbPath = resolveCanonicalMemoryDbPath(homeDirectory);
|
|
392
390
|
const memoryStore = new MemoryStore(memoryDbPath, {
|
|
393
391
|
embeddingRegistry: memoryEmbeddingRegistry,
|
|
394
392
|
});
|
|
395
393
|
const memoryRegistry = new MemoryRegistry(memoryStore);
|
|
394
|
+
// Local-until-adopted access facade for spine-shaped consumers (the Memory
|
|
395
|
+
// modal): bootstrap.ts activates the wire transport when a compatible
|
|
396
|
+
// external daemon is adopted, same signal as the session spine.
|
|
397
|
+
const memorySpine = new MemorySpineClient({ local: createLocalMemoryAccess(memoryRegistry) });
|
|
396
398
|
const deliveryManager = new AutomationDeliveryManager({
|
|
397
399
|
configManager,
|
|
398
400
|
serviceRegistry,
|
|
@@ -526,14 +528,10 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
526
528
|
automationBridge: automationManager,
|
|
527
529
|
});
|
|
528
530
|
|
|
529
|
-
// Daemon handler surfaces: attach HOST handlers to the SDK-auto-registered
|
|
530
|
-
//
|
|
531
|
-
// owns every id/descriptor/schema
|
|
532
|
-
//
|
|
533
|
-
// the resolver the inbox surface consumes; triage decorates channels.inbox.list.
|
|
534
|
-
// The remote surface reuses the SAME DistributedRuntimeManager the SDK facade
|
|
535
|
-
// injects, so its peer/work methods share one persistent store; remote.peers.*
|
|
536
|
-
// stay SDK-published routes (not catalog methods).
|
|
531
|
+
// Daemon handler surfaces: attach HOST handlers to the SDK-auto-registered builtin
|
|
532
|
+
// gateway descriptors (channels.* / email.* / calendar.*) via catalog.register(descriptor,
|
|
533
|
+
// handler, { replace: true }) — the SDK owns every id/descriptor/schema. The remote
|
|
534
|
+
// surface reuses the SAME DistributedRuntimeManager the SDK facade injects.
|
|
537
535
|
const handlerLogger: HandlerLogger = {
|
|
538
536
|
info: (message, meta) => console.info(message, meta ?? ''),
|
|
539
537
|
warn: (message, meta) => console.warn(message, meta ?? ''),
|
|
@@ -595,18 +593,18 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
595
593
|
artifactStore,
|
|
596
594
|
});
|
|
597
595
|
const processManager = new ProcessManager();
|
|
598
|
-
//
|
|
596
|
+
// The phase/work-item orchestration engine, constructed
|
|
599
597
|
// before the process registry so its fleet nodes (workstream/phase/
|
|
600
598
|
// work-item) can be folded in below via the registry's optional
|
|
601
599
|
// orchestrationEngine dep.
|
|
602
600
|
const { orchestrationEngine, workstreamCommands } = createWorkstreamServices({
|
|
603
601
|
agentManager, configManager, adaptivePlanner, runtimeBus: options.runtimeBus, projectRoot: workingDirectory,
|
|
604
602
|
});
|
|
605
|
-
//
|
|
603
|
+
// Repo source-tree code index, sharing memoryEmbeddingRegistry
|
|
606
604
|
// with MemoryStore above. Auto-build is config-gated (default off) — see
|
|
607
605
|
// code-index-services.ts's header doc.
|
|
608
606
|
const { codeIndexStore, codeIndexReindexScheduler } = createCodeIndexServices({ workingDirectory, configManager, memoryEmbeddingRegistry });
|
|
609
|
-
const codeInjectionOrchestratorDeps = { codeIndex: codeIndexStore, isCodeInjectionSettingEnabled: () => isCodeInjectionSettingEnabled(configManager), codeIndexReindexScheduler }; //
|
|
607
|
+
const codeInjectionOrchestratorDeps = { codeIndex: codeIndexStore, isCodeInjectionSettingEnabled: () => isCodeInjectionSettingEnabled(configManager), codeIndexReindexScheduler }; // Code-injection seam (agent here; main via orchestrator-core-services.ts)
|
|
610
608
|
// W2.1/W2.2: one shared process registry aggregating the managers above —
|
|
611
609
|
// the Fleet panel (panels/fleet-read-model.ts) is its first consumer.
|
|
612
610
|
// Constructed once here (not per-consumer) so the coalesced tick and the
|
|
@@ -614,15 +612,15 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
614
612
|
const processRegistry = createProcessRegistry({
|
|
615
613
|
agentManager,
|
|
616
614
|
wrfcController,
|
|
617
|
-
orchestrationEngine, //
|
|
618
|
-
codeIndexService: codeIndexStore, //
|
|
615
|
+
orchestrationEngine, // Folds workstream/phase/work-item nodes into the fleet
|
|
616
|
+
codeIndexService: codeIndexStore, // Folds a single 'code-index' node into the fleet
|
|
619
617
|
processManager,
|
|
620
618
|
watcherRegistry,
|
|
621
619
|
workflow,
|
|
622
620
|
approvalBroker,
|
|
623
621
|
sessionBroker,
|
|
624
|
-
messageBus: agentMessageBus, //
|
|
625
|
-
automationManager, //
|
|
622
|
+
messageBus: agentMessageBus, // Backs steer()/`steerable` (the Fleet steer composer builds on top)
|
|
623
|
+
automationManager, // Folds /schedule AutomationJobs into the fleet as 'schedule' nodes
|
|
626
624
|
runtimeBus: options.runtimeBus,
|
|
627
625
|
// Honest pricing: never fabricate a cost for an unrecognized model.
|
|
628
626
|
priceUsage: (model, usage) => {
|
|
@@ -681,7 +679,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
681
679
|
remoteRunnerRegistry,
|
|
682
680
|
knowledgeService,
|
|
683
681
|
memoryRegistry,
|
|
684
|
-
...codeInjectionOrchestratorDeps, //
|
|
682
|
+
...codeInjectionOrchestratorDeps, // Agent-run code injection + tool-site reindex
|
|
685
683
|
archetypeLoader,
|
|
686
684
|
configManager,
|
|
687
685
|
providerRegistry,
|
|
@@ -725,6 +723,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
725
723
|
workPlanStore,
|
|
726
724
|
memoryStore,
|
|
727
725
|
memoryRegistry,
|
|
726
|
+
memorySpine,
|
|
728
727
|
serviceRegistry,
|
|
729
728
|
secretsManager,
|
|
730
729
|
subscriptionManager,
|
|
@@ -790,7 +789,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
790
789
|
workspaceCheckpointManager,
|
|
791
790
|
integrationHelpers,
|
|
792
791
|
async rerootStores(newWorkingDir: string): Promise<void> {
|
|
793
|
-
//
|
|
792
|
+
// The memory store is the home-scoped canonical cross-surface store and
|
|
794
793
|
// deliberately does NOT reroot per-project (that would re-silo memory, the E6
|
|
795
794
|
// regression). Only working-tree-bound stores (code index, project index) reroot.
|
|
796
795
|
await codeIndexStore.reroot(newWorkingDir, codeIndexDbPath(newWorkingDir));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* session-inbound-inputs.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Live-surface steer delivery (the inbound half of the
|
|
5
5
|
* session spine): the TUI's INBOUND path for steer/follow-up inputs that another
|
|
6
6
|
* live surface (e.g. the webui) queued against THIS TUI's session.
|
|
7
7
|
*
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* session-spine-transport.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The TUI session-spine cutover: the thin typed-client
|
|
5
5
|
* adapter that lets the TUI's bootstrap drive the SDK's
|
|
6
6
|
* `@pellux/goodvibes-sdk/platform/runtime/session-spine` `SessionSpineClient`
|
|
7
|
-
*
|
|
7
|
+
* via its injected `SpineTransport` seam.
|
|
8
8
|
*
|
|
9
9
|
* The TUI is SDK-clean here: `SpineSessionsClient` is exactly the narrow wire
|
|
10
10
|
* surface the old TUI-local `session-spine-client.ts` declared (structurally
|
|
@@ -114,7 +114,7 @@ export interface UiRuntimeSharedServices {
|
|
|
114
114
|
readonly remoteSupervisor: RuntimeServices['remoteSupervisor'] & RemoteSupervisor;
|
|
115
115
|
/** W2.2: the shared live process registry backing the Fleet panel. */
|
|
116
116
|
readonly processRegistry: RuntimeServices['processRegistry'];
|
|
117
|
-
/**
|
|
117
|
+
/** The shared runtime event bus — the Fleet panel subscribes to its 'communication' domain for the honest steer-consumed signal. */
|
|
118
118
|
readonly runtimeBus: RuntimeServices['runtimeBus'];
|
|
119
119
|
};
|
|
120
120
|
}
|