@ericsanchezok/meta-synergy 1.1.26 → 1.2.18

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/src/service.ts CHANGED
@@ -3,38 +3,37 @@ import { closeSync, openSync } from "node:fs"
3
3
  import { MetaSynergyControlClient } from "./control/client"
4
4
  import type { MetaSynergyLogsPayload, MetaSynergyServiceSnapshot } from "./control/schema"
5
5
  import { Platform } from "./platform"
6
- import { MetaSynergyStore } from "./state/store"
6
+ import { MetaSynergyStore, type MetaSynergyState } from "./state/store"
7
7
  import { MetaSynergyLocalService } from "./service/local"
8
8
 
9
+ interface MetaSynergyLaunchContext {
10
+ launcherPath: string
11
+ invocationEntry?: string
12
+ printLogs?: boolean
13
+ }
14
+
15
+ interface PersistedServiceStateUpdate {
16
+ desiredState?: MetaSynergyState["service"]["desiredState"]
17
+ runtimeStatus?: MetaSynergyState["service"]["runtimeStatus"]
18
+ pid?: number
19
+ printLogs?: boolean
20
+ startedAt?: number
21
+ stoppedAt?: number
22
+ lastExitAt?: number
23
+ logPath?: string
24
+ }
25
+
9
26
  export namespace MetaSynergyService {
10
27
  export async function status(): Promise<MetaSynergyServiceSnapshot> {
11
28
  if (await MetaSynergyControlClient.isAvailable()) {
12
29
  return await MetaSynergyControlClient.request({ action: "service.status" })
13
30
  }
14
31
 
15
- const state = await MetaSynergyStore.loadState()
16
- const running = Boolean(state.service.pid && MetaSynergyLocalService.isPidRunning(state.service.pid))
17
- if (state.service.runtimeStatus !== (running ? "running" : "stopped") || (state.service.pid && !running)) {
18
- state.service.runtimeStatus = running ? "running" : "stopped"
19
- if (!running) {
20
- state.service.pid = undefined
21
- }
22
- await MetaSynergyStore.saveState(state)
23
- }
24
- return {
25
- desiredState: state.service.desiredState,
26
- runtimeStatus: state.service.runtimeStatus,
27
- running,
28
- pid: state.service.pid,
29
- startedAt: state.service.startedAt,
30
- stoppedAt: state.service.stoppedAt,
31
- lastExitAt: state.service.lastExitAt,
32
- printLogs: state.service.printLogs,
33
- logPath: state.logs.filePath || MetaSynergyStore.logsPath(),
34
- }
32
+ const { state, running } = await loadReconciledState()
33
+ return snapshotFromState(state, running)
35
34
  }
36
35
 
37
- export async function start(input: { execPath: string; argv1?: string; printLogs?: boolean }) {
36
+ export async function start(input: MetaSynergyLaunchContext) {
38
37
  if (await MetaSynergyControlClient.isAvailable()) {
39
38
  return {
40
39
  changed: false,
@@ -43,12 +42,12 @@ export namespace MetaSynergyService {
43
42
  }
44
43
  }
45
44
 
46
- const state = await MetaSynergyStore.loadState()
47
- const currentRunning = Boolean(state.service.pid && MetaSynergyLocalService.isPidRunning(state.service.pid))
45
+ const { state, running: currentRunning } = await loadReconciledState()
48
46
  if (currentRunning) {
49
- state.service.desiredState = "running"
50
- state.service.runtimeStatus = "running"
51
- await MetaSynergyStore.saveState(state)
47
+ await updatePersistedServiceState({
48
+ desiredState: "running",
49
+ runtimeStatus: "running",
50
+ })
52
51
  return {
53
52
  changed: false,
54
53
  alreadyRunning: true,
@@ -60,13 +59,14 @@ export namespace MetaSynergyService {
60
59
  await MetaSynergyLocalService.removeSocketFile(MetaSynergyStore.controlSocketPath())
61
60
  const outputPath = MetaSynergyStore.logsPath()
62
61
  const stdout = openSync(outputPath, "a")
63
- const command = resolveServiceCommand(input)
62
+ const command = resolveServerLaunchCommand(input)
64
63
 
65
- state.service.desiredState = "running"
66
- state.service.runtimeStatus = "starting"
67
- state.service.printLogs = input.printLogs ?? false
68
- state.logs.filePath = outputPath
69
- await MetaSynergyStore.saveState(state)
64
+ await updatePersistedServiceState({
65
+ desiredState: "running",
66
+ runtimeStatus: "starting",
67
+ printLogs: input.printLogs ?? false,
68
+ logPath: outputPath,
69
+ })
70
70
 
71
71
  try {
72
72
  const child = spawn(command.file, command.args, {
@@ -76,19 +76,19 @@ export namespace MetaSynergyService {
76
76
  })
77
77
  child.unref()
78
78
  await waitForControlPlane(2_500)
79
- const running = await MetaSynergyControlClient.isAvailable()
80
- const nextState = await MetaSynergyStore.loadState()
81
- nextState.service.desiredState = running ? "running" : "stopped"
82
- nextState.service.runtimeStatus = running ? "running" : "stopped"
83
- nextState.service.pid = running ? child.pid : undefined
84
- nextState.service.startedAt = running ? Date.now() : nextState.service.startedAt
85
- nextState.service.stoppedAt = running ? undefined : Date.now()
86
- nextState.service.lastExitAt = running ? nextState.service.lastExitAt : Date.now()
87
- nextState.service.printLogs = input.printLogs ?? false
88
- nextState.logs.filePath = outputPath
89
- await MetaSynergyStore.saveState(nextState)
79
+ const controlPlaneReady = await MetaSynergyControlClient.isAvailable()
80
+ await updatePersistedServiceState((currentState) => ({
81
+ desiredState: controlPlaneReady ? "running" : "stopped",
82
+ runtimeStatus: controlPlaneReady ? "running" : "stopped",
83
+ pid: controlPlaneReady ? child.pid : undefined,
84
+ startedAt: controlPlaneReady ? Date.now() : currentState.service.startedAt,
85
+ stoppedAt: controlPlaneReady ? undefined : Date.now(),
86
+ lastExitAt: controlPlaneReady ? currentState.service.lastExitAt : Date.now(),
87
+ printLogs: input.printLogs ?? false,
88
+ logPath: outputPath,
89
+ }))
90
90
  return {
91
- changed: running,
91
+ changed: controlPlaneReady,
92
92
  alreadyRunning: false,
93
93
  ...(await status()),
94
94
  }
@@ -102,13 +102,14 @@ export namespace MetaSynergyService {
102
102
  const snapshot = await status()
103
103
  await MetaSynergyControlClient.request({ action: "service.stop" }).catch(() => undefined)
104
104
  await waitForControlPlaneShutdown(2_500)
105
- const state = await MetaSynergyStore.loadState()
106
- state.service.desiredState = "stopped"
107
- state.service.runtimeStatus = "stopped"
108
- state.service.pid = undefined
109
- state.service.stoppedAt = Date.now()
110
- state.service.lastExitAt = state.service.stoppedAt
111
- await MetaSynergyStore.saveState(state)
105
+ const stoppedAt = Date.now()
106
+ await updatePersistedServiceState({
107
+ desiredState: "stopped",
108
+ runtimeStatus: "stopped",
109
+ pid: undefined,
110
+ stoppedAt,
111
+ lastExitAt: stoppedAt,
112
+ })
112
113
  return {
113
114
  changed: snapshot.running,
114
115
  alreadyStopped: !snapshot.running,
@@ -116,19 +117,20 @@ export namespace MetaSynergyService {
116
117
  }
117
118
  }
118
119
 
119
- const state = await MetaSynergyStore.loadState()
120
+ const { state, running } = await loadReconciledState()
120
121
  const pid = state.service.pid
121
- const running = Boolean(pid && MetaSynergyLocalService.isPidRunning(pid))
122
122
 
123
- state.service.desiredState = "stopped"
124
- state.service.runtimeStatus = running ? "stopping" : "stopped"
125
- await MetaSynergyStore.saveState(state)
123
+ await updatePersistedServiceState({
124
+ desiredState: "stopped",
125
+ runtimeStatus: running ? "stopping" : "stopped",
126
+ })
126
127
 
127
128
  if (!pid || !running) {
128
- state.service.pid = undefined
129
- state.service.runtimeStatus = "stopped"
130
- state.service.stoppedAt = Date.now()
131
- await MetaSynergyStore.saveState(state)
129
+ await updatePersistedServiceState({
130
+ pid: undefined,
131
+ runtimeStatus: "stopped",
132
+ stoppedAt: Date.now(),
133
+ })
132
134
  return {
133
135
  changed: false,
134
136
  alreadyStopped: true,
@@ -138,13 +140,14 @@ export namespace MetaSynergyService {
138
140
 
139
141
  await MetaSynergyLocalService.terminatePid(pid)
140
142
  await MetaSynergyLocalService.removeSocketFile(MetaSynergyStore.controlSocketPath())
141
- const nextState = await MetaSynergyStore.loadState()
142
- nextState.service.desiredState = "stopped"
143
- nextState.service.runtimeStatus = "stopped"
144
- nextState.service.pid = undefined
145
- nextState.service.stoppedAt = Date.now()
146
- nextState.service.lastExitAt = nextState.service.stoppedAt
147
- await MetaSynergyStore.saveState(nextState)
143
+ const stoppedAt = Date.now()
144
+ await updatePersistedServiceState({
145
+ desiredState: "stopped",
146
+ runtimeStatus: "stopped",
147
+ pid: undefined,
148
+ stoppedAt,
149
+ lastExitAt: stoppedAt,
150
+ })
148
151
  return {
149
152
  changed: true,
150
153
  alreadyStopped: false,
@@ -152,7 +155,7 @@ export namespace MetaSynergyService {
152
155
  }
153
156
  }
154
157
 
155
- export async function restart(input: { execPath: string; argv1?: string; printLogs?: boolean }) {
158
+ export async function restart(input: MetaSynergyLaunchContext) {
156
159
  const stopped = await stop()
157
160
  const started = await start(input)
158
161
  return { stopped, started }
@@ -192,16 +195,105 @@ export namespace MetaSynergyService {
192
195
  }
193
196
  }
194
197
 
195
- function resolveServiceCommand(input: { execPath: string; argv1?: string; printLogs?: boolean }) {
196
- if (!input.argv1 || input.argv1 === input.execPath) {
197
- const args = ["server"]
198
- if (input.printLogs) args.push("--print-logs")
199
- return { file: input.execPath, args }
198
+ function resolveServerLaunchCommand(input: MetaSynergyLaunchContext) {
199
+ const serverArgs = ["server"]
200
+ if (input.printLogs) serverArgs.push("--print-logs")
201
+
202
+ const invocationEntry = input.invocationEntry
203
+ if (!invocationEntry || invocationEntry === input.launcherPath) {
204
+ return { file: input.launcherPath, args: serverArgs }
205
+ }
206
+
207
+ const isBunVirtualEntrypoint = invocationEntry.startsWith("/$bunfs/") || invocationEntry.endsWith("/cli.js")
208
+ if (isBunVirtualEntrypoint) {
209
+ return { file: input.launcherPath, args: serverArgs }
210
+ }
211
+
212
+ return { file: input.launcherPath, args: [invocationEntry, ...serverArgs] }
213
+ }
214
+
215
+ function snapshotFromState(state: MetaSynergyState, running: boolean): MetaSynergyServiceSnapshot {
216
+ return {
217
+ desiredState: state.service.desiredState,
218
+ runtimeStatus: state.service.runtimeStatus,
219
+ running,
220
+ pid: state.service.pid,
221
+ startedAt: state.service.startedAt,
222
+ stoppedAt: state.service.stoppedAt,
223
+ lastExitAt: state.service.lastExitAt,
224
+ printLogs: state.service.printLogs,
225
+ logPath: state.logs.filePath || MetaSynergyStore.logsPath(),
226
+ }
227
+ }
228
+
229
+ async function loadReconciledState(): Promise<{ state: MetaSynergyState; running: boolean }> {
230
+ const state = await MetaSynergyStore.loadState()
231
+ const running = Boolean(state.service.pid && MetaSynergyLocalService.isPidRunning(state.service.pid))
232
+ if (reconcileObservedRuntimeState(state, running)) {
233
+ await MetaSynergyStore.saveState(state)
234
+ }
235
+ return { state, running }
236
+ }
237
+
238
+ async function updatePersistedServiceState(
239
+ update: PersistedServiceStateUpdate | ((state: MetaSynergyState) => PersistedServiceStateUpdate),
240
+ ): Promise<MetaSynergyState> {
241
+ const state = await MetaSynergyStore.loadState()
242
+ const nextUpdate = typeof update === "function" ? update(state) : update
243
+ if (applyPersistedServiceStateUpdate(state, nextUpdate)) {
244
+ await MetaSynergyStore.saveState(state)
245
+ }
246
+ return state
247
+ }
248
+
249
+ function reconcileObservedRuntimeState(state: MetaSynergyState, running: boolean): boolean {
250
+ const expectedRuntimeStatus = running ? "running" : "stopped"
251
+ return applyPersistedServiceStateUpdate(state, {
252
+ runtimeStatus:
253
+ state.service.runtimeStatus === expectedRuntimeStatus && !(state.service.pid && !running)
254
+ ? undefined
255
+ : expectedRuntimeStatus,
256
+ pid: state.service.pid && !running ? undefined : state.service.pid,
257
+ })
258
+ }
259
+
260
+ function applyPersistedServiceStateUpdate(state: MetaSynergyState, update: PersistedServiceStateUpdate): boolean {
261
+ let changed = false
262
+
263
+ if (update.desiredState !== undefined && state.service.desiredState !== update.desiredState) {
264
+ state.service.desiredState = update.desiredState
265
+ changed = true
266
+ }
267
+ if (update.runtimeStatus !== undefined && state.service.runtimeStatus !== update.runtimeStatus) {
268
+ state.service.runtimeStatus = update.runtimeStatus
269
+ changed = true
270
+ }
271
+ if (Object.hasOwn(update, "pid") && state.service.pid !== update.pid) {
272
+ state.service.pid = update.pid
273
+ changed = true
274
+ }
275
+ if (update.printLogs !== undefined && state.service.printLogs !== update.printLogs) {
276
+ state.service.printLogs = update.printLogs
277
+ changed = true
278
+ }
279
+ if (Object.hasOwn(update, "startedAt") && state.service.startedAt !== update.startedAt) {
280
+ state.service.startedAt = update.startedAt
281
+ changed = true
282
+ }
283
+ if (Object.hasOwn(update, "stoppedAt") && state.service.stoppedAt !== update.stoppedAt) {
284
+ state.service.stoppedAt = update.stoppedAt
285
+ changed = true
286
+ }
287
+ if (Object.hasOwn(update, "lastExitAt") && state.service.lastExitAt !== update.lastExitAt) {
288
+ state.service.lastExitAt = update.lastExitAt
289
+ changed = true
290
+ }
291
+ if (update.logPath !== undefined && state.logs.filePath !== update.logPath) {
292
+ state.logs.filePath = update.logPath
293
+ changed = true
200
294
  }
201
295
 
202
- const args = [input.argv1, "server"]
203
- if (input.printLogs) args.push("--print-logs")
204
- return { file: input.execPath, args }
296
+ return changed
205
297
  }
206
298
 
207
299
  async function waitForControlPlane(timeoutMs: number) {
@@ -0,0 +1,19 @@
1
+ import { readFile } from "node:fs/promises"
2
+ import { MetaSynergyStore } from "./store"
3
+ import type { MetaSynergyMigration } from "../migration/types"
4
+
5
+ export namespace MetaSynergyStateMigrations {
6
+ export const migrations: MetaSynergyMigration[] = [
7
+ {
8
+ id: "20260408-normalize-state",
9
+ description: "Normalize persisted meta-synergy state",
10
+ async run() {
11
+ const raw = await readFile(MetaSynergyStore.statePath(), "utf8").catch(() => undefined)
12
+ if (!raw) return
13
+ const parsed = JSON.parse(raw) as unknown
14
+ const next = MetaSynergyStore.hydrateStateForMigration(parsed)
15
+ await MetaSynergyStore.saveState(next)
16
+ },
17
+ },
18
+ ]
19
+ }
@@ -2,10 +2,12 @@ import os from "node:os"
2
2
  import path from "node:path"
3
3
  import process from "node:process"
4
4
  import { mkdir, readFile, unlink, writeFile } from "node:fs/promises"
5
+ import { MetaSynergyOwnerRegistry, type MetaSynergyOwnerRegistryState } from "../owner-registry"
5
6
 
6
7
  export type MetaSynergyApprovalMode = "auto" | "manual" | "trusted-only"
7
8
  export type MetaSynergyPendingRequestStatus = "pending" | "approved" | "denied"
8
9
  export type MetaSynergyConnectionStatus = "disconnected" | "connecting" | "connected"
10
+ export type MetaSynergyRuntimeMode = "managed" | "standalone"
9
11
  export type MetaSynergyServiceDesiredState = "running" | "stopped"
10
12
  export type MetaSynergyServiceRuntimeStatus = "starting" | "running" | "stopping" | "stopped"
11
13
 
@@ -59,6 +61,8 @@ export interface MetaSynergyState {
59
61
  envID?: string
60
62
  hostSessionID?: string
61
63
  label?: string
64
+ runtimeMode: MetaSynergyRuntimeMode
65
+ ownerRegistry: MetaSynergyOwnerRegistryState
62
66
  collaborationEnabled: boolean
63
67
  approvalMode: MetaSynergyApprovalMode
64
68
  trusted: MetaSynergyTrustedIdentityState
@@ -72,6 +76,8 @@ export interface MetaSynergyState {
72
76
  }
73
77
 
74
78
  const DEFAULT_STATE: MetaSynergyState = {
79
+ runtimeMode: "standalone",
80
+ ownerRegistry: MetaSynergyOwnerRegistry.defaultRegistry(),
75
81
  collaborationEnabled: true,
76
82
  approvalMode: "manual",
77
83
  trusted: {
@@ -97,7 +103,7 @@ export namespace MetaSynergyStore {
97
103
  return process.env.META_SYNERGY_HOME || path.join(os.homedir(), ".meta-synergy")
98
104
  }
99
105
 
100
- export function authPath(): string {
106
+ export function legacyAuthPath(): string {
101
107
  return path.join(root(), "auth.json")
102
108
  }
103
109
 
@@ -105,6 +111,14 @@ export namespace MetaSynergyStore {
105
111
  return path.join(root(), "state.json")
106
112
  }
107
113
 
114
+ export function migrationLogPath(): string {
115
+ return path.join(root(), "migrations.json")
116
+ }
117
+
118
+ export function ownerRegistryPath(): string {
119
+ return path.join(root(), "owner.json")
120
+ }
121
+
108
122
  export function logsPath(): string {
109
123
  return path.join(root(), "logs", "runtime.log")
110
124
  }
@@ -119,21 +133,21 @@ export namespace MetaSynergyStore {
119
133
  await mkdir(path.dirname(controlSocketPath()), { recursive: true })
120
134
  }
121
135
 
122
- export async function loadAuth(): Promise<MetaSynergyAuthState | undefined> {
136
+ export async function loadLegacyAuth(): Promise<MetaSynergyAuthState | undefined> {
123
137
  try {
124
- return JSON.parse(await readFile(authPath(), "utf8")) as MetaSynergyAuthState
138
+ return JSON.parse(await readFile(legacyAuthPath(), "utf8")) as MetaSynergyAuthState
125
139
  } catch {
126
140
  return undefined
127
141
  }
128
142
  }
129
143
 
130
- export async function saveAuth(auth: MetaSynergyAuthState): Promise<void> {
144
+ export async function saveLegacyAuth(auth: MetaSynergyAuthState): Promise<void> {
131
145
  await ensureRoot()
132
- await writeFile(authPath(), JSON.stringify(auth, null, 2) + "\n")
146
+ await writeFile(legacyAuthPath(), JSON.stringify(auth, null, 2) + "\n")
133
147
  }
134
148
 
135
- export async function clearAuth(): Promise<void> {
136
- await unlink(authPath()).catch(() => undefined)
149
+ export async function clearLegacyAuth(): Promise<void> {
150
+ await unlink(legacyAuthPath()).catch(() => undefined)
137
151
  }
138
152
 
139
153
  export async function loadState(): Promise<MetaSynergyState> {
@@ -149,6 +163,28 @@ export namespace MetaSynergyStore {
149
163
  await ensureRoot()
150
164
  await writeFile(statePath(), JSON.stringify(hydrateState(state), null, 2) + "\n")
151
165
  }
166
+
167
+ export async function loadMigrationLog(): Promise<Record<string, number>> {
168
+ try {
169
+ const parsed = JSON.parse(await readFile(migrationLogPath(), "utf8")) as Record<string, unknown>
170
+ return Object.fromEntries(
171
+ Object.entries(parsed).filter(
172
+ (entry): entry is [string, number] => typeof entry[0] === "string" && typeof entry[1] === "number",
173
+ ),
174
+ )
175
+ } catch {
176
+ return {}
177
+ }
178
+ }
179
+
180
+ export async function saveMigrationLog(log: Record<string, number>): Promise<void> {
181
+ await ensureRoot()
182
+ await writeFile(migrationLogPath(), JSON.stringify(log, null, 2) + "\n")
183
+ }
184
+
185
+ export function hydrateStateForMigration(parsed: unknown): MetaSynergyState {
186
+ return hydrateState(isPartialState(parsed) ? parsed : undefined)
187
+ }
152
188
  }
153
189
 
154
190
  function hydrateState(parsed: Partial<MetaSynergyState> | undefined): MetaSynergyState {
@@ -156,6 +192,8 @@ function hydrateState(parsed: Partial<MetaSynergyState> | undefined): MetaSynerg
156
192
  envID: parsed?.envID,
157
193
  hostSessionID: parsed?.hostSessionID,
158
194
  label: parsed?.label,
195
+ runtimeMode: parseRuntimeMode(parsed?.runtimeMode),
196
+ ownerRegistry: MetaSynergyOwnerRegistry.hydrate(parsed?.ownerRegistry),
159
197
  collaborationEnabled: parsed?.collaborationEnabled ?? DEFAULT_STATE.collaborationEnabled,
160
198
  approvalMode: parseApprovalMode(parsed?.approvalMode),
161
199
  trusted: {
@@ -235,6 +273,10 @@ function hydratePendingRequests(input: MetaSynergyState["pendingRequests"] | und
235
273
  return requests
236
274
  }
237
275
 
276
+ function parseRuntimeMode(input: unknown): MetaSynergyRuntimeMode {
277
+ return input === "managed" || input === "standalone" ? input : DEFAULT_STATE.runtimeMode
278
+ }
279
+
238
280
  function parseApprovalMode(input: unknown): MetaSynergyApprovalMode {
239
281
  return input === "auto" || input === "trusted-only" || input === "manual" ? input : DEFAULT_STATE.approvalMode
240
282
  }
@@ -268,3 +310,7 @@ function uniqueNumbers(values: number[] | undefined): number[] {
268
310
  ...new Set((values ?? []).filter((value): value is number => typeof value === "number" && Number.isFinite(value))),
269
311
  ]
270
312
  }
313
+
314
+ function isPartialState(value: unknown): value is Partial<MetaSynergyState> {
315
+ return typeof value === "object" && value !== null
316
+ }
package/src/types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import z from "zod"
1
2
  import type { MetaProtocolEnv, MetaProtocolHost } from "@ericsanchezok/meta-protocol"
2
3
 
3
4
  export type EnvID = MetaProtocolEnv.EnvID
@@ -10,6 +11,13 @@ export interface RemoteHostIdentity {
10
11
  label?: string
11
12
  }
12
13
 
14
+ export const HolosCallerSchema = z.object({
15
+ type: z.string(),
16
+ agentID: z.string(),
17
+ ownerUserID: z.number(),
18
+ profile: z.record(z.string(), z.unknown()).optional(),
19
+ })
20
+
13
21
  export interface HolosCaller {
14
22
  type: string
15
23
  agentID: string
@@ -0,0 +1,86 @@
1
+ import { afterAll, beforeEach, describe, expect, test } from "bun:test"
2
+ import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"
3
+ import os from "node:os"
4
+ import path from "node:path"
5
+ import process from "node:process"
6
+ import { MetaSynergyCLIBackend } from "../src/cli-backend"
7
+ import { MetaSynergyHolosAuth } from "../src/holos/auth"
8
+ import { MetaSynergyStore } from "../src/state/store"
9
+
10
+ const originalMetaHome = process.env.META_SYNERGY_HOME
11
+ const originalSynergyHome = process.env.SYNERGY_TEST_HOME
12
+ const originalFetch = globalThis.fetch
13
+ const tempRoots: string[] = []
14
+
15
+ beforeEach(async () => {
16
+ const metaRoot = await mkdtemp(path.join(os.tmpdir(), "meta-synergy-cli-auth-meta-"))
17
+ const synergyHome = await mkdtemp(path.join(os.tmpdir(), "meta-synergy-cli-auth-synergy-"))
18
+ tempRoots.push(metaRoot, synergyHome)
19
+ process.env.META_SYNERGY_HOME = metaRoot
20
+ process.env.SYNERGY_TEST_HOME = synergyHome
21
+ globalThis.fetch = (async () =>
22
+ new Response(JSON.stringify({ code: 0, data: { ws_token: "token", expires_in: 60 } }), {
23
+ status: 200,
24
+ headers: { "Content-Type": "application/json" },
25
+ })) as typeof fetch
26
+ })
27
+
28
+ afterAll(async () => {
29
+ globalThis.fetch = originalFetch
30
+ if (originalMetaHome === undefined) delete process.env.META_SYNERGY_HOME
31
+ else process.env.META_SYNERGY_HOME = originalMetaHome
32
+
33
+ if (originalSynergyHome === undefined) delete process.env.SYNERGY_TEST_HOME
34
+ else process.env.SYNERGY_TEST_HOME = originalSynergyHome
35
+
36
+ await Promise.all(tempRoots.map((root) => rm(root, { recursive: true, force: true })))
37
+ })
38
+
39
+ describe("meta-synergy cli backend auth payloads", () => {
40
+ test("whoami reports shared auth source", async () => {
41
+ const sharedPath = MetaSynergyHolosAuth.sharedAuthPath()
42
+ await mkdir(path.dirname(sharedPath), { recursive: true })
43
+ await writeFile(
44
+ sharedPath,
45
+ JSON.stringify({ holos: { type: "holos", agentId: "agent_shared", agentSecret: "secret_shared" } }, null, 2),
46
+ )
47
+
48
+ const result = await MetaSynergyCLIBackend.whoami()
49
+ expect(result.auth).toEqual({
50
+ loggedIn: true,
51
+ agentID: "agent_shared",
52
+ source: "shared",
53
+ })
54
+ })
55
+
56
+ test("doctor reports migrated legacy auth source in payload and checks", async () => {
57
+ await MetaSynergyStore.saveLegacyAuth({ agentID: "agent_legacy", agentSecret: "secret_legacy" })
58
+
59
+ const result = await MetaSynergyCLIBackend.doctor()
60
+ expect(result.auth).toEqual({
61
+ loggedIn: true,
62
+ agentID: "agent_legacy",
63
+ source: "legacy-migrated",
64
+ })
65
+ expect(result.checks.find((check) => check.name === "auth")).toEqual({
66
+ name: "auth",
67
+ ok: true,
68
+ detail: "agent agent_legacy (legacy-migrated)",
69
+ })
70
+ })
71
+
72
+ test("logout clears auth and reports logged-out source state", async () => {
73
+ await MetaSynergyStore.saveLegacyAuth({ agentID: "agent_clear", agentSecret: "secret_clear" })
74
+ await MetaSynergyHolosAuth.save({ agentID: "agent_clear", agentSecret: "secret_clear" })
75
+
76
+ const result = await MetaSynergyCLIBackend.logout()
77
+ expect(result.authCleared).toBe(true)
78
+
79
+ const whoami = await MetaSynergyCLIBackend.whoami()
80
+ expect(whoami.auth).toEqual({
81
+ loggedIn: false,
82
+ agentID: null,
83
+ source: null,
84
+ })
85
+ })
86
+ })
@@ -0,0 +1,49 @@
1
+ import { afterAll, beforeEach, describe, expect, test } from "bun:test"
2
+ import { mkdtemp, rm } from "node:fs/promises"
3
+ import os from "node:os"
4
+ import path from "node:path"
5
+ import process from "node:process"
6
+ import { MetaSynergyCLIBackend } from "../src/cli-backend"
7
+ import { MetaSynergyStore } from "../src/state/store"
8
+
9
+ const originalHome = process.env.META_SYNERGY_HOME
10
+ const tempRoots: string[] = []
11
+
12
+ beforeEach(async () => {
13
+ const root = await mkdtemp(path.join(os.tmpdir(), "meta-synergy-cli-mode-"))
14
+ tempRoots.push(root)
15
+ process.env.META_SYNERGY_HOME = root
16
+ })
17
+
18
+ afterAll(async () => {
19
+ if (originalHome === undefined) {
20
+ delete process.env.META_SYNERGY_HOME
21
+ } else {
22
+ process.env.META_SYNERGY_HOME = originalHome
23
+ }
24
+ await Promise.all(tempRoots.map((root) => rm(root, { recursive: true, force: true })))
25
+ })
26
+
27
+ describe("meta-synergy cli backend mode transitions", () => {
28
+ test("standalone mode clears managed ownership when service is offline", async () => {
29
+ const state = await MetaSynergyStore.loadState()
30
+ state.runtimeMode = "managed"
31
+ state.ownerRegistry.local.ownerIDs = ["synergy:test"]
32
+ state.ownerRegistry.local.activeOwnerID = "synergy:test"
33
+ state.ownerRegistry.local.leaseExpiresAt = Date.now() + 10_000
34
+ await MetaSynergyStore.saveState(state)
35
+
36
+ const result = (await MetaSynergyCLIBackend.enterStandaloneMode()) as {
37
+ mode: string
38
+ ownership: { local: { owned: boolean } }
39
+ connectionStatus: string
40
+ }
41
+ expect(result.mode).toBe("standalone")
42
+ expect(result.ownership.local.owned).toBe(false)
43
+ expect(result.connectionStatus).toBe("disconnected")
44
+
45
+ const next = await MetaSynergyStore.loadState()
46
+ expect(next.runtimeMode).toBe("standalone")
47
+ expect(next.ownerRegistry.local.activeOwnerID).toBeUndefined()
48
+ })
49
+ })