mindwire 0.1.36 → 0.1.40

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.
Files changed (42) hide show
  1. package/README.md +16 -0
  2. package/dist/{chunk-RTGZDLSG.js → chunk-BJL5K6M7.js} +74 -21
  3. package/dist/chunk-BJL5K6M7.js.map +1 -0
  4. package/dist/chunk-ZP7F7LP7.js +306 -0
  5. package/dist/chunk-ZP7F7LP7.js.map +1 -0
  6. package/dist/cli.js +499 -250
  7. package/dist/cli.js.map +1 -1
  8. package/dist/client.d.ts +6 -8
  9. package/dist/computer/dashboard.d.ts +1 -0
  10. package/dist/computer/desktop.d.ts +17 -0
  11. package/dist/computer/directory-http.d.ts +1 -1
  12. package/dist/computer/discovery.d.ts +22 -0
  13. package/dist/computer/inspection.d.ts +30 -0
  14. package/dist/computer/lifecycle.d.ts +4 -2
  15. package/dist/computer/runtime-version.d.ts +6 -0
  16. package/dist/computer/service-update.d.ts +26 -0
  17. package/dist/computer/setup-prompt.d.ts +2 -0
  18. package/dist/computer/startup.d.ts +7 -0
  19. package/dist/computer/terminal-ui.d.ts +9 -0
  20. package/dist/computer.d.ts +2 -0
  21. package/dist/{connection-change-IBNL42BL.js → connection-change-7ZV6O75Q.js} +3 -3
  22. package/dist/{connection-change-IBNL42BL.js.map → connection-change-7ZV6O75Q.js.map} +1 -1
  23. package/dist/{controller-2SJLZMJZ.js → controller-FBMGMSLM.js} +67 -9
  24. package/dist/controller-FBMGMSLM.js.map +1 -0
  25. package/dist/{controller-handoff-RUUHODNC.js → controller-handoff-CJKXUFQI.js} +3 -3
  26. package/dist/{controller-handoff-RUUHODNC.js.map → controller-handoff-CJKXUFQI.js.map} +1 -1
  27. package/dist/conversations.d.ts +57 -0
  28. package/dist/daemon-binary.d.ts +2 -0
  29. package/dist/index.cjs +66 -12
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.ts +3 -1
  32. package/dist/index.js +66 -13
  33. package/dist/index.js.map +1 -1
  34. package/dist/project-sync.d.ts +29 -0
  35. package/dist/surfaces.d.ts +20 -1
  36. package/dist/types.d.ts +18 -0
  37. package/dist/workspace.d.ts +2 -0
  38. package/package.json +1 -1
  39. package/dist/chunk-67BNDGRK.js +0 -20
  40. package/dist/chunk-67BNDGRK.js.map +0 -1
  41. package/dist/chunk-RTGZDLSG.js.map +0 -1
  42. package/dist/controller-2SJLZMJZ.js.map +0 -1
package/dist/client.d.ts CHANGED
@@ -7,7 +7,7 @@ import { ExecutionApi } from "./execution.js";
7
7
  import { ComputerApi } from "./computer.js";
8
8
  import { type Target } from "./target/index.js";
9
9
  import type { EnsureEvent } from "./target/host.js";
10
- import type { AgentInfo, AuthMethod, AuthState, AuthStatus, Catalog, ChatSummary, CustomProvider, DeleteResult, DoctorReport, Health, MCPServer, MemoryDoc, MemoryScope, Message, MessagePage, ModelInfo, Notification, NotifyChannel, NotifyChannelInput, NotifyChannelTestResult, NotifyConfigInput, NotifyConfigStatus, NotifyRule, NotifyRuleInput, ProcessFrame, PromptTemplate, ResolveOptions, SetupStatus, HarnessSoftware, Stats, Subagent, TurnOptions } from "./types.js";
10
+ import type { AgentInfo, AuthMethod, AuthState, AuthStatus, Catalog, ChatSummary, ChatForkResult, ForkChatOptions, CustomProvider, DeleteResult, DoctorReport, Health, MCPServer, MemoryDoc, MemoryScope, Message, MessagePage, ModelInfo, Notification, NotifyChannel, NotifyChannelInput, NotifyChannelTestResult, NotifyConfigInput, NotifyConfigStatus, NotifyRule, NotifyRuleInput, ProcessFrame, PromptTemplate, ResolveOptions, SetupStatus, HarnessSoftware, Stats, Subagent, TurnOptions } from "./types.js";
11
11
  export interface MindwireOptions {
12
12
  /**
13
13
  * Default agent type for agent-scoped calls (e.g. `"claude-code"`). When omitted, the daemon
@@ -172,14 +172,12 @@ export declare class Mindwire {
172
172
  deleteChat(chatId: string): Promise<DeleteResult>;
173
173
  /**
174
174
  * `POST /chats/{id}/fork` — clone a chat into a new id (generated when `newChatId` is omitted).
175
- * The fork shares the source's native session until its first turn, which branches it (natively
176
- * on Claude via `--fork-session`; a fresh session on agents without native fork). Rejects with a
177
- * 409 if the source has a live turn, 404 if the source is unknown, 400 if the target id is in
178
- * use. Returns the new chat's summary.
175
+ * Codex and Claude preserve native context. `beforeMessageId` excludes that prompt and every
176
+ * later message; send the edited prompt to the returned chat with `turn`. Reuse `newChatId`
177
+ * and the turn's `requestId` on retries. The source chat and filesystem are unchanged.
178
+ * A source with a live turn returns 409; an unknown source returns 404.
179
179
  */
180
- forkChat(chatId: string, opts?: {
181
- newChatId?: string;
182
- }): Promise<ChatSummary>;
180
+ forkChat(chatId: string, opts?: ForkChatOptions): Promise<ChatForkResult>;
183
181
  /**
184
182
  * `GET /chats/{id}/messages` — a chat's transcript (native when the agent supports it, else
185
183
  * the recorded fallback). `limit` caps to the newest N; `before` pages older history.
@@ -0,0 +1 @@
1
+ export declare function computerDashboard(directory: string, run: (args: string[]) => Promise<void>): Promise<void>;
@@ -0,0 +1,17 @@
1
+ import type { Mindwire } from "../client.js";
2
+ import type { LocalDesktopInfo } from "../surfaces.js";
3
+ import { type SetupPrompt } from "./setup-prompt.js";
4
+ export declare function desktopSummary(info: LocalDesktopInfo): string;
5
+ export declare function openScreenSharingSettings(): void;
6
+ /** All activation happens here, on the Mac. Connecting/refreshing in the phone
7
+ * can only observe this state; it cannot enable sharing or retrieve a password. */
8
+ export declare function manageDesktop(options: {
9
+ client: Mindwire;
10
+ directory: string;
11
+ action?: string;
12
+ interactive: boolean;
13
+ json?: boolean;
14
+ prompt?: SetupPrompt;
15
+ openSettings?: () => void;
16
+ readControlToken?: () => Promise<string>;
17
+ }): Promise<LocalDesktopInfo>;
@@ -5,4 +5,4 @@ export declare class DirectoryRequestError extends Error {
5
5
  constructor(status: number, code: string, retryAfter?: number);
6
6
  }
7
7
  /** Bounded, same-origin requests. No redirects, cookie jar or retained credentials. */
8
- export declare function directoryRequest<T>(url: string, path: string, body: unknown): Promise<T>;
8
+ export declare function directoryRequest<T>(url: string, path: string, body: unknown, signal?: AbortSignal): Promise<T>;
@@ -1,13 +1,35 @@
1
1
  import type { Mindwire } from "../client.js";
2
2
  import type { ComputerDiscoveryStatus } from "../computer.js";
3
3
  import type { SetupPrompt } from "./setup-prompt.js";
4
+ import { type ComputerConfig } from "./lifecycle.js";
4
5
  export declare const DIRECTORY_API_PATH = "/api/computer-directory/v1";
5
6
  export declare const DIRECTORY_URL = "https://console.mindwire.sh/api/computer-directory/v1";
7
+ export interface DiscoveryPreference {
8
+ enabled: boolean;
9
+ url?: string;
10
+ }
11
+ export declare const discoveryPreference: (directory: string) => Promise<DiscoveryPreference | undefined>;
12
+ /** Desired setup is durable even if enrollment is temporarily offline. The
13
+ * daemon remains the authority for signed publications and phone access. */
14
+ export declare function saveDiscoveryPreference(directory: string, value: DiscoveryPreference): Promise<void>;
6
15
  export declare function directoryURL(value: string): string;
7
16
  export declare function enableAddressDiscovery(options: {
8
17
  directory: string;
9
18
  computer: Mindwire;
10
19
  prompt: SetupPrompt;
11
20
  url?: string;
21
+ waitForPublication?: boolean;
22
+ signal?: AbortSignal;
23
+ reconcile?: boolean;
12
24
  }): Promise<ComputerDiscoveryStatus>;
25
+ export declare function disableAddressDiscovery(directory: string, computer: Mindwire, reconcile?: boolean): Promise<ComputerDiscoveryStatus>;
26
+ /** Runs once after startup and retries failed enrollment in the controller. A
27
+ * healthy setup makes only a local status read: publication/renewal stays in Go.
28
+ * Direct/VPN/custom connections never opt into a hosted directory implicitly. */
29
+ export declare function reconcileAddressDiscovery(options: {
30
+ directory: string;
31
+ config: ComputerConfig;
32
+ computer: Mindwire;
33
+ signal?: AbortSignal;
34
+ }): Promise<void>;
13
35
  export declare function discoveryDescription(status: ComputerDiscoveryStatus | undefined): string;
@@ -0,0 +1,30 @@
1
+ import type { ComputerConnectionInfo, ComputerDevice, ComputerDiscoveryStatus, ComputerInfo, ComputerUpdate } from "../computer.js";
2
+ import { type StartupState } from "./startup.js";
3
+ import { type DiscoveryPreference } from "./discovery.js";
4
+ import type { ComputerConnectionChange } from "./connection-change.js";
5
+ export interface ComputerInspection {
6
+ name: string;
7
+ cliVersion: string;
8
+ service: "not-configured" | "stopped" | "starting" | "running" | "unavailable";
9
+ daemonVersion?: string;
10
+ online: boolean;
11
+ startup: StartupState;
12
+ connection?: ComputerConnectionInfo;
13
+ discovery?: ComputerDiscoveryStatus;
14
+ discoveryPreference?: DiscoveryPreference;
15
+ devices: ComputerDevice[];
16
+ update?: ComputerUpdate;
17
+ activeOperations?: number;
18
+ info?: ComputerInfo;
19
+ problem?: string;
20
+ recoveryProblem?: string;
21
+ recovering?: boolean;
22
+ relayError?: string;
23
+ relayErrorCode?: string;
24
+ connectionChange?: Pick<ComputerConnectionChange, "status" | "message">;
25
+ }
26
+ /** Inspection never starts a service or requires it to be reachable. Keep
27
+ * credentials/configuration files out of both human and JSON output. */
28
+ export declare function inspectComputer(directory: string): Promise<ComputerInspection>;
29
+ export declare function recoverySummary(state: ComputerInspection): string;
30
+ export declare function inspectionText(state: ComputerInspection, detailed?: boolean): string;
@@ -5,6 +5,7 @@ import { type ProcessState } from "./process.js";
5
5
  export interface ComputerConfig {
6
6
  daemonBin?: string;
7
7
  version?: string;
8
+ versionPinned?: boolean;
8
9
  directory: string;
9
10
  host?: string;
10
11
  bind: string;
@@ -12,7 +13,7 @@ export interface ComputerConfig {
12
13
  websocketPort: number;
13
14
  relay: RelayOptions;
14
15
  }
15
- export declare const CONTROLLER_PROTOCOL = 3;
16
+ export declare const CONTROLLER_PROTOCOL = 4;
16
17
  export interface ControllerState extends ProcessState {
17
18
  protocol?: number;
18
19
  cliVersion?: string;
@@ -25,12 +26,13 @@ export interface ControllerState extends ProcessState {
25
26
  checkedAt?: number;
26
27
  relayFailureSince?: number;
27
28
  relayFailures?: number;
29
+ discoveryError?: string;
28
30
  }
29
31
  export declare const defaultStateDirectory: () => string;
30
32
  export declare const defaultComputerConfig: () => ComputerConfig;
31
33
  export declare function readJSON<T>(file: string): Promise<T | undefined>;
32
34
  export declare function writeJSON(file: string, value: unknown): Promise<void>;
33
- export declare function computerClient(directory: string): Promise<Mindwire>;
35
+ export declare function computerClient(directory: string, requestTimeoutMs?: number): Promise<Mindwire>;
34
36
  export declare function directRoutes(config: ComputerConfig, port: number): ComputerRoute[];
35
37
  /** Launch once; the daemon additionally owns an OS file lock over its identity/state. */
36
38
  export declare function ensureComputer(directory: string, cliPath: string, patch?: Partial<ComputerConfig>, onProgress?: (message: string) => void, options?: {
@@ -0,0 +1,6 @@
1
+ import type { ComputerConfig } from "./lifecycle.js";
2
+ /** The saved version is the last installed release, not an accidental permanent
3
+ * pin. Upgrading the CLI must also upgrade its managed service. Never downgrade a
4
+ * newer service or replace an explicitly supplied development binary. */
5
+ export declare function managedServiceVersion(config: ComputerConfig, cliVersion?: string): string;
6
+ export declare function serviceUpgradeVersion(config: ComputerConfig, installed: string, cliVersion?: string): string | undefined;
@@ -0,0 +1,26 @@
1
+ import type { ComputerUpdate } from "../computer.js";
2
+ import type { Mindwire } from "../client.js";
3
+ type UpdateClient = Pick<Mindwire, "health" | "computer">;
4
+ export declare function activeUpdate(update: ComputerUpdate | undefined): update is ComputerUpdate;
5
+ /** The daemon arbitrates requests. Join an earlier version before requesting the
6
+ * next; promoting a request never creates a second download or bypasses its lease. */
7
+ export declare function queueServiceUpdate(client: UpdateClient, version: string, force?: boolean): Promise<ComputerUpdate>;
8
+ export interface ServiceUpdateResult {
9
+ version: string;
10
+ complete: boolean;
11
+ update?: ComputerUpdate;
12
+ }
13
+ /** A CLI may close after queuing: the controller owns download/restart. Human
14
+ * callers get a bounded result, including the exact older request being joined. */
15
+ export declare function updateComputer(options: {
16
+ directory: string;
17
+ version: string;
18
+ force?: boolean;
19
+ allowDowngrade?: boolean;
20
+ onProgress?: (update: ComputerUpdate) => void;
21
+ signal?: AbortSignal;
22
+ timeoutMs?: number;
23
+ connect?: () => Promise<UpdateClient>;
24
+ }): Promise<ServiceUpdateResult>;
25
+ export declare function updateProgress(update: ComputerUpdate): string;
26
+ export {};
@@ -1,8 +1,10 @@
1
+ import { type TerminalChoice } from "./terminal-ui.js";
1
2
  export interface SetupPrompt {
2
3
  question(text: string): Promise<string>;
3
4
  secret(text: string): Promise<string>;
4
5
  print(text: string): void;
5
6
  open(url: string): void;
7
+ select?<T extends string>(title: string, choices: TerminalChoice<T>[]): Promise<T | undefined>;
6
8
  }
7
9
  /** No shell interpolation, even for a URL returned by a provider. */
8
10
  export declare function openSetupURL(value: string): void;
@@ -11,10 +11,15 @@ export interface StartupPlan {
11
11
  export interface StartupState {
12
12
  enabled: boolean;
13
13
  running?: boolean;
14
+ registered?: boolean;
14
15
  kind?: StartupPlan["kind"];
15
16
  name?: string;
16
17
  file?: string;
17
18
  }
19
+ /** Homebrew resolves execPath to a versioned Cellar file. Prefer a stable alias
20
+ * only after verifying it points at this exact interpreter. Never run a different
21
+ * executable just because it appeared first on PATH. */
22
+ export declare function startupExecutable(executable?: string, environmentPath?: string): Promise<string>;
18
23
  /** Per-user startup, with no root service, password, or copied pairing secret. */
19
24
  export declare function startupPlan(options: {
20
25
  directory: string;
@@ -31,6 +36,8 @@ export declare function configureStartup(directory: string, cliPath: string, ena
31
36
  plan?: StartupPlan;
32
37
  run?: (command: Command) => Promise<void>;
33
38
  verify?: () => Promise<void>;
39
+ isRunning?: () => Promise<boolean>;
40
+ repair?: boolean;
34
41
  }): Promise<StartupState>;
35
42
  /** Native startup managers supervise this small guardian. It adopts an existing
36
43
  * controller and honours an explicit stop; it never owns or kills user work. */
@@ -0,0 +1,9 @@
1
+ export interface TerminalChoice<T extends string = string> {
2
+ value: T;
3
+ label: string;
4
+ }
5
+ export declare const terminalText: (value: string) => string;
6
+ export declare function accent(value: string): string;
7
+ /** Small native-terminal selector, without a UI dependency or an alternate
8
+ * screen. Every exit restores raw mode, the cursor, listeners and input flow. */
9
+ export declare function chooseTerminal<T extends string>(title: string | string[], choices: TerminalChoice<T>[]): Promise<T | undefined>;
@@ -107,6 +107,8 @@ export interface ComputerForwardRequest {
107
107
  id: string;
108
108
  deviceId: string;
109
109
  port: number;
110
+ /** A live human surface session is required for the virtual Mac display destination (8794). */
111
+ desktopSessionId?: string;
110
112
  }
111
113
  export interface ComputerForward extends ComputerForwardRequest {
112
114
  expiresAt: string;
@@ -1,4 +1,4 @@
1
- import { acquireProcessLock, readJSON, writeJSON } from './chunk-RTGZDLSG.js';
1
+ import { acquireProcessLock, readJSON, writeJSON } from './chunk-BJL5K6M7.js';
2
2
  import './chunk-PFYKJ6LX.js';
3
3
  import { randomUUID } from 'crypto';
4
4
  import * as path from 'path';
@@ -37,5 +37,5 @@ async function changeComputerConnection(directory, patch, options = {}) {
37
37
  }
38
38
 
39
39
  export { changeComputerConnection };
40
- //# sourceMappingURL=connection-change-IBNL42BL.js.map
41
- //# sourceMappingURL=connection-change-IBNL42BL.js.map
40
+ //# sourceMappingURL=connection-change-7ZV6O75Q.js.map
41
+ //# sourceMappingURL=connection-change-7ZV6O75Q.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/computer/connection-change.ts"],"names":["delay"],"mappings":";;;;;;AAeA,eAAsB,wBAAA,CAAyB,SAAA,EAAmB,KAAA,EAA0C,OAAA,GAExG,EAAC,EAAkB;AACrB,EAAA,MAAM,IAAA,GAAO,MAAM,kBAAA,CAAwB,IAAA,CAAA,IAAA,CAAK,SAAA,EAAW,wBAAwB,CAAA,EAAG,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAA;AAChH,EAAA,MAAM,IAAA,GAAY,IAAA,CAAA,IAAA,CAAK,SAAA,EAAW,iCAAiC,CAAA;AACnE,EAAA,IAAI;AACF,IAAA,MAAM,QAAQ,MAAM,QAAA,CAA8B,IAAA,CAAA,IAAA,CAAK,SAAA,EAAW,sBAAsB,CAAC,CAAA;AACzF,IAAA,IAAI,MAAA,CAAO,QAAQ,KAAK,CAAA,CAAE,MAAM,CAAC,CAAC,KAAK,KAAK,CAAA,KAAM,KAAK,SAAA,CAAU,KAAA,GAAQ,GAA2B,CAAC,CAAA,KAAM,KAAK,SAAA,CAAU,KAAK,CAAC,CAAA,EAAG;AACnI,IAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAmC,IAAI,CAAA;AAC9D,IAAA,IAAI,YAAY,CAAC,QAAA,EAAU,YAAY,CAAA,CAAE,SAAS,QAAA,CAAS,MAAM,CAAA,IAAK,IAAA,CAAK,UAAU,QAAA,CAAS,KAAK,MAAM,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AAC9H,MAAA,MAAM,IAAI,MAAM,8EAA8E,CAAA;AAAA,IAChG;AACA,IAAA,MAAM,UAAU,QAAA,IAAY,CAAC,QAAA,EAAU,YAAY,EAAE,QAAA,CAAS,QAAA,CAAS,MAAM,CAAA,GAAI,WAC7E,EAAE,EAAA,EAAI,YAAW,EAAG,KAAA,EAAO,QAAQ,QAAA,EAAkB;AACzD,IAAA,MAAM,SAAA,CAAU,MAAM,OAAO,CAAA;AAC7B,IAAA,IAAI,OAAA;AACJ,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA;AAC9B,IAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,QAAA,EAAU;AAC5B,MAAA,OAAA,CAAQ,QAAQ,cAAA,EAAe;AAC/B,MAAA,MAAM,OAAA,GAAU,MAAM,QAAA,CAAmC,IAAI,CAAA;AAC7D,MAAA,IAAI,SAAS,EAAA,KAAO,OAAA,CAAQ,IAAI,MAAM,IAAI,MAAM,0DAA0D,CAAA;AAC1G,MAAA,IAAI,OAAA,CAAQ,WAAW,UAAA,EAAY;AACnC,MAAA,IAAI,OAAA,CAAQ,WAAW,QAAA,EAAU,MAAM,IAAI,KAAA,CAAM,OAAA,CAAQ,WAAW,gEAAgE,CAAA;AACpI,MAAA,IAAI,OAAA,CAAQ,OAAA,IAAW,OAAA,KAAY,OAAA,CAAQ,OAAA,EAAS;AAAE,QAAA,OAAA,GAAU,OAAA,CAAQ,OAAA;AAAS,QAAA,OAAA,CAAQ,aAAa,OAAO,CAAA;AAAA,MAAG;AAChH,MAAA,MAAMA,WAAM,GAAA,EAAK,KAAA,CAAA,EAAW,EAAE,MAAA,EAAQ,OAAA,CAAQ,QAAQ,CAAA;AAAA,IACxD;AACA,IAAA,MAAM,IAAI,MAAM,0FAA0F,CAAA;AAAA,EAC5G,CAAA,SAAE;AAAU,IAAA,MAAM,MAAM,KAAA,EAAM;AAAA,EAAG;AACnC","file":"connection-change-IBNL42BL.js","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport * as path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { acquireProcessLock } from \"./lock.js\";\nimport { readJSON, writeJSON, type ComputerConfig } from \"./lifecycle.js\";\n\nexport interface ComputerConnectionChange {\n id: string;\n patch: Pick<Partial<ComputerConfig>, \"relay\" | \"host\">;\n status: \"queued\" | \"connecting\" | \"complete\" | \"failed\";\n message?: string;\n}\n\n/** The controller verifies a replacement before committing it. Parallel CLI\n * invocations join the same settings change, or run one after the other. */\nexport async function changeComputerConnection(directory: string, patch: ComputerConnectionChange[\"patch\"], options: {\n signal?: AbortSignal; onProgress?: (message: string) => void;\n} = {}): Promise<void> {\n const lock = await acquireProcessLock(path.join(directory, \"connection-change.lock\"), { signal: options.signal });\n const file = path.join(directory, \"computer-connection-change.json\");\n try {\n const saved = await readJSON<ComputerConfig>(path.join(directory, \"computer-config.json\"));\n if (Object.entries(patch).every(([key, value]) => JSON.stringify(saved?.[key as keyof ComputerConfig]) === JSON.stringify(value))) return;\n const previous = await readJSON<ComputerConnectionChange>(file);\n if (previous && [\"queued\", \"connecting\"].includes(previous.status) && JSON.stringify(previous.patch) !== JSON.stringify(patch)) {\n throw new Error(\"Another connection change is still running. Run mindwire status, then retry.\");\n }\n const request = previous && [\"queued\", \"connecting\"].includes(previous.status) ? previous\n : { id: randomUUID(), patch, status: \"queued\" as const };\n await writeJSON(file, request);\n let message: string | undefined;\n const deadline = Date.now() + 130_000;\n while (Date.now() < deadline) {\n options.signal?.throwIfAborted();\n const current = await readJSON<ComputerConnectionChange>(file);\n if (current?.id !== request.id) throw new Error(\"The connection change was replaced. Run mindwire status.\");\n if (current.status === \"complete\") return;\n if (current.status === \"failed\") throw new Error(current.message ?? \"Connection setup failed. The previous connection is unchanged.\");\n if (current.message && message !== current.message) { message = current.message; options.onProgress?.(message); }\n await delay(250, undefined, { signal: options.signal });\n }\n throw new Error(\"Connection setup is still running in the background. Run mindwire status for its result.\");\n } finally { await lock?.close(); }\n}\n"]}
1
+ {"version":3,"sources":["../src/computer/connection-change.ts"],"names":["delay"],"mappings":";;;;;;AAeA,eAAsB,wBAAA,CAAyB,SAAA,EAAmB,KAAA,EAA0C,OAAA,GAExG,EAAC,EAAkB;AACrB,EAAA,MAAM,IAAA,GAAO,MAAM,kBAAA,CAAwB,IAAA,CAAA,IAAA,CAAK,SAAA,EAAW,wBAAwB,CAAA,EAAG,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAA;AAChH,EAAA,MAAM,IAAA,GAAY,IAAA,CAAA,IAAA,CAAK,SAAA,EAAW,iCAAiC,CAAA;AACnE,EAAA,IAAI;AACF,IAAA,MAAM,QAAQ,MAAM,QAAA,CAA8B,IAAA,CAAA,IAAA,CAAK,SAAA,EAAW,sBAAsB,CAAC,CAAA;AACzF,IAAA,IAAI,MAAA,CAAO,QAAQ,KAAK,CAAA,CAAE,MAAM,CAAC,CAAC,KAAK,KAAK,CAAA,KAAM,KAAK,SAAA,CAAU,KAAA,GAAQ,GAA2B,CAAC,CAAA,KAAM,KAAK,SAAA,CAAU,KAAK,CAAC,CAAA,EAAG;AACnI,IAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAmC,IAAI,CAAA;AAC9D,IAAA,IAAI,YAAY,CAAC,QAAA,EAAU,YAAY,CAAA,CAAE,SAAS,QAAA,CAAS,MAAM,CAAA,IAAK,IAAA,CAAK,UAAU,QAAA,CAAS,KAAK,MAAM,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AAC9H,MAAA,MAAM,IAAI,MAAM,8EAA8E,CAAA;AAAA,IAChG;AACA,IAAA,MAAM,UAAU,QAAA,IAAY,CAAC,QAAA,EAAU,YAAY,EAAE,QAAA,CAAS,QAAA,CAAS,MAAM,CAAA,GAAI,WAC7E,EAAE,EAAA,EAAI,YAAW,EAAG,KAAA,EAAO,QAAQ,QAAA,EAAkB;AACzD,IAAA,MAAM,SAAA,CAAU,MAAM,OAAO,CAAA;AAC7B,IAAA,IAAI,OAAA;AACJ,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA;AAC9B,IAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,QAAA,EAAU;AAC5B,MAAA,OAAA,CAAQ,QAAQ,cAAA,EAAe;AAC/B,MAAA,MAAM,OAAA,GAAU,MAAM,QAAA,CAAmC,IAAI,CAAA;AAC7D,MAAA,IAAI,SAAS,EAAA,KAAO,OAAA,CAAQ,IAAI,MAAM,IAAI,MAAM,0DAA0D,CAAA;AAC1G,MAAA,IAAI,OAAA,CAAQ,WAAW,UAAA,EAAY;AACnC,MAAA,IAAI,OAAA,CAAQ,WAAW,QAAA,EAAU,MAAM,IAAI,KAAA,CAAM,OAAA,CAAQ,WAAW,gEAAgE,CAAA;AACpI,MAAA,IAAI,OAAA,CAAQ,OAAA,IAAW,OAAA,KAAY,OAAA,CAAQ,OAAA,EAAS;AAAE,QAAA,OAAA,GAAU,OAAA,CAAQ,OAAA;AAAS,QAAA,OAAA,CAAQ,aAAa,OAAO,CAAA;AAAA,MAAG;AAChH,MAAA,MAAMA,WAAM,GAAA,EAAK,KAAA,CAAA,EAAW,EAAE,MAAA,EAAQ,OAAA,CAAQ,QAAQ,CAAA;AAAA,IACxD;AACA,IAAA,MAAM,IAAI,MAAM,0FAA0F,CAAA;AAAA,EAC5G,CAAA,SAAE;AAAU,IAAA,MAAM,MAAM,KAAA,EAAM;AAAA,EAAG;AACnC","file":"connection-change-7ZV6O75Q.js","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport * as path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { acquireProcessLock } from \"./lock.js\";\nimport { readJSON, writeJSON, type ComputerConfig } from \"./lifecycle.js\";\n\nexport interface ComputerConnectionChange {\n id: string;\n patch: Pick<Partial<ComputerConfig>, \"relay\" | \"host\">;\n status: \"queued\" | \"connecting\" | \"complete\" | \"failed\";\n message?: string;\n}\n\n/** The controller verifies a replacement before committing it. Parallel CLI\n * invocations join the same settings change, or run one after the other. */\nexport async function changeComputerConnection(directory: string, patch: ComputerConnectionChange[\"patch\"], options: {\n signal?: AbortSignal; onProgress?: (message: string) => void;\n} = {}): Promise<void> {\n const lock = await acquireProcessLock(path.join(directory, \"connection-change.lock\"), { signal: options.signal });\n const file = path.join(directory, \"computer-connection-change.json\");\n try {\n const saved = await readJSON<ComputerConfig>(path.join(directory, \"computer-config.json\"));\n if (Object.entries(patch).every(([key, value]) => JSON.stringify(saved?.[key as keyof ComputerConfig]) === JSON.stringify(value))) return;\n const previous = await readJSON<ComputerConnectionChange>(file);\n if (previous && [\"queued\", \"connecting\"].includes(previous.status) && JSON.stringify(previous.patch) !== JSON.stringify(patch)) {\n throw new Error(\"Another connection change is still running. Run mindwire status, then retry.\");\n }\n const request = previous && [\"queued\", \"connecting\"].includes(previous.status) ? previous\n : { id: randomUUID(), patch, status: \"queued\" as const };\n await writeJSON(file, request);\n let message: string | undefined;\n const deadline = Date.now() + 130_000;\n while (Date.now() < deadline) {\n options.signal?.throwIfAborted();\n const current = await readJSON<ComputerConnectionChange>(file);\n if (current?.id !== request.id) throw new Error(\"The connection change was replaced. Run mindwire status.\");\n if (current.status === \"complete\") return;\n if (current.status === \"failed\") throw new Error(current.message ?? \"Connection setup failed. The previous connection is unchanged.\");\n if (current.message && message !== current.message) { message = current.message; options.onProgress?.(message); }\n await delay(250, undefined, { signal: options.signal });\n }\n throw new Error(\"Connection setup is still running in the background. Run mindwire status for its result.\");\n } finally { await lock?.close(); }\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { connectionInfo } from './chunk-67BNDGRK.js';
2
- import { readJSON, processStateAlive, currentProcessIdentity, ensureDaemonBinary, SDK_VERSION, computerClient, processIdentity, adoptProcess, adoptRelay, RelayOutput, writeJSON, startRelay, providerNeedsAction, directRoutes, ProviderConnectionError, CONTROLLER_PROTOCOL, ownChild, hasCertificateError } from './chunk-RTGZDLSG.js';
1
+ import { connectionInfo, queueServiceUpdate, reconcileAddressDiscovery, activeUpdate } from './chunk-ZP7F7LP7.js';
2
+ import { readJSON, processStateAlive, currentProcessIdentity, computerClient, SDK_VERSION, ensureDaemonBinary, processIdentity, adoptProcess, writeJSON, adoptRelay, RelayOutput, startRelay, providerNeedsAction, directRoutes, ProviderConnectionError, CONTROLLER_PROTOCOL, ownChild, versionAtLeast, hasCertificateError } from './chunk-BJL5K6M7.js';
3
3
  import './chunk-PFYKJ6LX.js';
4
4
  import { execFileSync, spawn } from 'child_process';
5
5
  import { randomBytes } from 'crypto';
@@ -179,6 +179,18 @@ async function relayProviderReachable(options, signal) {
179
179
  });
180
180
  }
181
181
 
182
+ // src/computer/runtime-version.ts
183
+ function managedServiceVersion(config, cliVersion = SDK_VERSION) {
184
+ if (config.versionPinned && config.version) return config.version;
185
+ if (!/^\d+\.\d+\.\d+$/.test(cliVersion)) return config.version ?? cliVersion;
186
+ return versionAtLeast(config.version, cliVersion) ? config.version : cliVersion;
187
+ }
188
+ function serviceUpgradeVersion(config, installed, cliVersion = SDK_VERSION) {
189
+ const wanted = managedServiceVersion(config, cliVersion);
190
+ if (config.daemonBin || !/^\d+\.\d+\.\d+$/.test(wanted) || versionAtLeast(installed, wanted)) return;
191
+ return wanted;
192
+ }
193
+
182
194
  // src/computer/controller.ts
183
195
  async function saveRelay(file, handle, port, options) {
184
196
  if (handle?.owner?.identity) await writeJSON(file, {
@@ -208,6 +220,7 @@ async function superviseComputer(directory) {
208
220
  let child, relay, stopping = false;
209
221
  let relayTask, updateTask;
210
222
  let connectionTask;
223
+ let maintenanceTask;
211
224
  let relayResult;
212
225
  let checkTask;
213
226
  let checkResult;
@@ -299,13 +312,21 @@ async function superviseComputer(directory) {
299
312
  let relayReachable = false, relayWasReachable = false, relayHealthFailures = 0;
300
313
  let relayFailedAt = 0;
301
314
  let nextCheckAt = 0, checkedAt = 0;
315
+ let nextMaintenanceAt = 0, maintenanceCheckedAt = 0, maintenanceFailures = 0;
316
+ let discoveryError;
302
317
  try {
303
318
  await phase("Checking the Mindwire service\u2026");
304
- let binary = config.daemonBin ?? await ensureDaemonBinary({ version: config.version ?? SDK_VERSION, cacheDir: process.env.MINDWIRE_RELEASE_CACHE_DIR });
305
319
  try {
306
320
  client = await computerClient(directory);
307
321
  } catch {
308
322
  }
323
+ const wantedVersion = managedServiceVersion(config);
324
+ const installedVersion = client ? (await client.health()).version : wantedVersion;
325
+ let binary = config.daemonBin ?? await ensureDaemonBinary({
326
+ version: installedVersion,
327
+ cacheDir: process.env.MINDWIRE_RELEASE_CACHE_DIR,
328
+ exactVersion: !!client || config.versionPinned
329
+ });
309
330
  if (client) {
310
331
  const info = await client.computer.info();
311
332
  const identity = await processIdentity(info.pid);
@@ -316,6 +337,11 @@ async function superviseComputer(directory) {
316
337
  } else {
317
338
  await phase("Starting the Mindwire service\u2026");
318
339
  client = await launch(binary);
340
+ const startedVersion = (await client.health()).version.replace(/^v/, "");
341
+ if (!config.daemonBin && config.version !== startedVersion) {
342
+ config = { ...config, version: startedVersion };
343
+ await writeJSON(path.join(directory, "computer-config.json"), config);
344
+ }
319
345
  }
320
346
  const saved = await readJSON(relayPath);
321
347
  const candidatePath = path.join(directory, "computer-relay-candidate.json");
@@ -348,7 +374,7 @@ async function superviseComputer(directory) {
348
374
  let lastController = "";
349
375
  while (!stopping) {
350
376
  const changePath = path.join(directory, "computer-connection-change.json");
351
- const change = !connectionTask && !relayTask && !updateTask && !changingDaemon && client && child?.alive() ? await readJSON(changePath) : void 0;
377
+ const change = !connectionTask && !relayTask && !updateTask && !maintenanceTask && !changingDaemon && client && child?.alive() ? await readJSON(changePath) : void 0;
352
378
  if (change && ["queued", "connecting"].includes(change.status)) {
353
379
  connectionTask = (async () => {
354
380
  const previousConfig = config;
@@ -485,6 +511,7 @@ async function superviseComputer(directory) {
485
511
  published = "";
486
512
  daemonError = void 0;
487
513
  daemonReadyAt = Date.now();
514
+ nextMaintenanceAt = 0;
488
515
  } catch (error) {
489
516
  if (child) await child.close();
490
517
  child = void 0;
@@ -494,6 +521,32 @@ async function superviseComputer(directory) {
494
521
  }
495
522
  }
496
523
  const requestedCheck = await readJSON(path.join(directory, "computer-check.json"));
524
+ if (!maintenanceTask && !changingDaemon && !updateTask && !connectionTask && client && child?.alive() && (Date.now() >= nextMaintenanceAt || (requestedCheck?.requestedAt ?? 0) > maintenanceCheckedAt)) {
525
+ const maintaining = client;
526
+ maintenanceCheckedAt = Date.now();
527
+ maintenanceTask = (async () => {
528
+ try {
529
+ const upgrade = serviceUpgradeVersion(config, (await maintaining.health()).version);
530
+ if (upgrade) {
531
+ await queueServiceUpdate(maintaining, upgrade);
532
+ discoveryError = `Service update to ${upgrade} is pending. Automatic updates wait for active work to finish.`;
533
+ nextMaintenanceAt = Date.now() + 2e3;
534
+ return;
535
+ }
536
+ await reconcileAddressDiscovery({ directory, config, computer: maintaining, signal: abort.signal });
537
+ discoveryError = void 0;
538
+ maintenanceFailures = 0;
539
+ nextMaintenanceAt = Date.now() + 6e4;
540
+ } catch (error) {
541
+ if (!stopping) {
542
+ discoveryError = error instanceof Error ? error.message : "Automatic reconnection setup will retry.";
543
+ nextMaintenanceAt = Date.now() + recoveryDelay(++maintenanceFailures);
544
+ }
545
+ }
546
+ })().finally(() => {
547
+ maintenanceTask = void 0;
548
+ });
549
+ }
497
550
  if (!connectionTask && relay?.owner && !relay.owner.alive()) {
498
551
  await relay.close();
499
552
  relay = void 0;
@@ -626,6 +679,7 @@ async function superviseComputer(directory) {
626
679
  error: daemonError ?? relayError,
627
680
  errorCode: daemonError ? void 0 : relayErrorCode,
628
681
  recovering: !ready,
682
+ discoveryError,
629
683
  relayFailureSince: relayFailedAt || void 0,
630
684
  relayFailures: relayHealthFailures || void 0
631
685
  };
@@ -635,8 +689,8 @@ async function superviseComputer(directory) {
635
689
  lastController = serialized;
636
690
  }
637
691
  const updatePath = path.join(directory, "computer-update.json");
638
- const update = !updateTask && !connectionTask && client && child?.alive() ? await readJSON(updatePath) : void 0;
639
- if (update && ["queued", "downloading", "waiting", "restarting"].includes(update.status)) {
692
+ const update = !updateTask && !connectionTask && !maintenanceTask && client && child?.alive() ? await readJSON(updatePath) : void 0;
693
+ if (activeUpdate(update)) {
640
694
  updateTask = (async () => {
641
695
  const status = async (state2, error) => writeJSON(
642
696
  updatePath,
@@ -644,7 +698,7 @@ async function superviseComputer(directory) {
644
698
  );
645
699
  try {
646
700
  await status("downloading");
647
- const replacement = await ensureDaemonBinary({ version: update.version, cacheDir: process.env.MINDWIRE_RELEASE_CACHE_DIR });
701
+ const replacement = await ensureDaemonBinary({ version: update.version, cacheDir: process.env.MINDWIRE_RELEASE_CACHE_DIR, exactVersion: true });
648
702
  await status("waiting");
649
703
  let lease;
650
704
  while (!stopping && !lease) {
@@ -678,11 +732,14 @@ async function superviseComputer(directory) {
678
732
  await client.computer.setRoutes(routes, connectionInfo(config.relay, config.host));
679
733
  published = "";
680
734
  await status("complete");
735
+ maintenanceFailures = 0;
681
736
  } catch (error) {
737
+ maintenanceFailures++;
682
738
  await status("failed", error instanceof Error ? error.message : "Service update failed.");
683
739
  } finally {
684
740
  changingDaemon = false;
685
741
  published = "";
742
+ nextMaintenanceAt = Date.now() + recoveryDelay(maintenanceFailures);
686
743
  }
687
744
  })().finally(() => {
688
745
  updateTask = void 0;
@@ -699,6 +756,7 @@ async function superviseComputer(directory) {
699
756
  await checkTask;
700
757
  await updateTask;
701
758
  await connectionTask;
759
+ await maintenanceTask;
702
760
  if (relayResult?.handle && relayResult.handle !== relay) await relayResult.handle.close();
703
761
  await relay?.close();
704
762
  if (child) await child.close();
@@ -709,5 +767,5 @@ async function superviseComputer(directory) {
709
767
  }
710
768
 
711
769
  export { recoveryDelay, superviseComputer };
712
- //# sourceMappingURL=controller-2SJLZMJZ.js.map
713
- //# sourceMappingURL=controller-2SJLZMJZ.js.map
770
+ //# sourceMappingURL=controller-FBMGMSLM.js.map
771
+ //# sourceMappingURL=controller-FBMGMSLM.js.map