@botiverse/raft-computer 0.0.60 → 0.0.62
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/dist/index.js +4062 -3741
- package/dist/lib/index.d.ts +847 -19
- package/dist/lib/index.js +4602 -637
- package/package.json +2 -2
package/dist/lib/index.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
declare class ComputerError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly exitCode: number;
|
|
4
|
+
constructor(code: string, message: string, exitCode?: number);
|
|
5
|
+
}
|
|
6
|
+
declare function isComputerError(err: unknown): err is ComputerError;
|
|
7
|
+
|
|
1
8
|
type DaemonState = {
|
|
2
9
|
running: true;
|
|
3
10
|
pid: number;
|
|
@@ -486,6 +493,314 @@ type MigrationDetection = {
|
|
|
486
493
|
kind: "server-unavailable";
|
|
487
494
|
};
|
|
488
495
|
|
|
496
|
+
type ComputerApiEvent = {
|
|
497
|
+
kind: "login.device-code";
|
|
498
|
+
verifyUrl: string;
|
|
499
|
+
userCode: string;
|
|
500
|
+
expiresAt: string;
|
|
501
|
+
expiresInSeconds: number;
|
|
502
|
+
} | {
|
|
503
|
+
kind: "login.polling";
|
|
504
|
+
} | {
|
|
505
|
+
kind: "login.approved";
|
|
506
|
+
userId: string;
|
|
507
|
+
sessionPath: string;
|
|
508
|
+
} | {
|
|
509
|
+
kind: "attach.attaching";
|
|
510
|
+
serverSlug: string;
|
|
511
|
+
} | {
|
|
512
|
+
kind: "attach.preflight";
|
|
513
|
+
resumed: boolean;
|
|
514
|
+
} | {
|
|
515
|
+
kind: "attach.attached";
|
|
516
|
+
serverId: string;
|
|
517
|
+
serverMachineId: string;
|
|
518
|
+
serverSlug: string;
|
|
519
|
+
attachmentPath: string;
|
|
520
|
+
resumed: boolean;
|
|
521
|
+
apiKeyRedactedPrefix: string;
|
|
522
|
+
} | {
|
|
523
|
+
kind: "detach.detaching";
|
|
524
|
+
serverId: string;
|
|
525
|
+
serverLabel: string;
|
|
526
|
+
} | {
|
|
527
|
+
kind: "detach.revoke_succeeded";
|
|
528
|
+
serverId: string;
|
|
529
|
+
serverLabel: string;
|
|
530
|
+
} | {
|
|
531
|
+
kind: "detach.revoke_failed";
|
|
532
|
+
serverId: string;
|
|
533
|
+
serverLabel: string;
|
|
534
|
+
reason: "http_error" | "network_error";
|
|
535
|
+
httpStatus?: number;
|
|
536
|
+
message: string;
|
|
537
|
+
} | {
|
|
538
|
+
kind: "detach.subtree_cleared";
|
|
539
|
+
serverId: string;
|
|
540
|
+
serverLabel: string;
|
|
541
|
+
} | {
|
|
542
|
+
kind: "detach.detached";
|
|
543
|
+
serverId: string;
|
|
544
|
+
serverLabel: string;
|
|
545
|
+
} | {
|
|
546
|
+
kind: "start.starting";
|
|
547
|
+
managedTargets: string[];
|
|
548
|
+
attachedCount: number;
|
|
549
|
+
foreground: boolean;
|
|
550
|
+
} | {
|
|
551
|
+
kind: "start.already_running";
|
|
552
|
+
servicePid: number;
|
|
553
|
+
managedTargets: string[];
|
|
554
|
+
attachedCount: number;
|
|
555
|
+
} | {
|
|
556
|
+
kind: "start.running";
|
|
557
|
+
managedTargets: string[];
|
|
558
|
+
attachedCount: number;
|
|
559
|
+
} | {
|
|
560
|
+
kind: "start.spawned";
|
|
561
|
+
servicePid: number;
|
|
562
|
+
managedTargets: string[];
|
|
563
|
+
attachedCount: number;
|
|
564
|
+
} | {
|
|
565
|
+
kind: "start.ready";
|
|
566
|
+
ready: Map<string, number>;
|
|
567
|
+
managedTargets: string[];
|
|
568
|
+
} | {
|
|
569
|
+
kind: "start.aborted";
|
|
570
|
+
servicePid: number;
|
|
571
|
+
managedTargets: string[];
|
|
572
|
+
ready: Map<string, number>;
|
|
573
|
+
} | {
|
|
574
|
+
kind: "stop.stopping";
|
|
575
|
+
pid: number | null;
|
|
576
|
+
} | {
|
|
577
|
+
kind: "stop.not_running";
|
|
578
|
+
} | {
|
|
579
|
+
kind: "stop.stale_pidfile_cleared";
|
|
580
|
+
pid: number;
|
|
581
|
+
} | {
|
|
582
|
+
kind: "stop.signaled";
|
|
583
|
+
pid: number;
|
|
584
|
+
} | {
|
|
585
|
+
kind: "stop.stopped";
|
|
586
|
+
pid: number;
|
|
587
|
+
} | {
|
|
588
|
+
kind: "log.line";
|
|
589
|
+
line: string;
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
interface LoginInput {
|
|
593
|
+
serverUrl?: string;
|
|
594
|
+
}
|
|
595
|
+
interface LoginResult {
|
|
596
|
+
userId: string;
|
|
597
|
+
sessionPath: string;
|
|
598
|
+
serverUrl: string;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
interface AttachInput {
|
|
602
|
+
serverSlug: string;
|
|
603
|
+
serverUrl?: string;
|
|
604
|
+
name?: string;
|
|
605
|
+
}
|
|
606
|
+
interface AttachResult {
|
|
607
|
+
serverId: string;
|
|
608
|
+
serverMachineId: string;
|
|
609
|
+
serverSlug: string;
|
|
610
|
+
serverUrl: string;
|
|
611
|
+
attachmentPath: string;
|
|
612
|
+
resumed: boolean;
|
|
613
|
+
/** First 8 characters of the freshly-issued sk_computer_*; mirrors
|
|
614
|
+
* adoption.log redacted_prefix convention. The raw key lives only in
|
|
615
|
+
* runner.state.json (mode 0o600) — NEVER on this event/result. */
|
|
616
|
+
apiKeyRedactedPrefix: string;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** Generic pidfile reader by absolute path. Returns null on missing / junk. */
|
|
620
|
+
declare function readPidfileAt(pidfilePath: string): Promise<number | null>;
|
|
621
|
+
/** Liveness via signal 0. */
|
|
622
|
+
declare function isProcessAlive(pid: number): boolean;
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Spawn a detached `slock-computer __service` child. The detached child
|
|
626
|
+
* survives the parent process exit, writes its stdio to the service
|
|
627
|
+
* log, and registers its pid in `service.pid`. Returns the pid.
|
|
628
|
+
*
|
|
629
|
+
* Used by `runStart` (foreground CLI → background service) AND by the
|
|
630
|
+
* §2.5 upgrade flow's restart phase: after `swap` renames the new bits
|
|
631
|
+
* into `currentBinaryDir`, this helper re-execs the freshly-installed
|
|
632
|
+
* binary as the new service. After a §2.3 rollback (`.prev` restored)
|
|
633
|
+
* it re-execs the old bits the same way — same code path, same log sink,
|
|
634
|
+
* same pidfile invariant. Dayu blocker (msg=911eb84e): without this
|
|
635
|
+
* shared helper, `runUpgradeCli` had no spawn callback wired into
|
|
636
|
+
* `runUpgrade`, so the production CLI's restart phase never spawned a
|
|
637
|
+
* fresh service — it would time out on health and then fail rollback
|
|
638
|
+
* respawn for the same reason.
|
|
639
|
+
*
|
|
640
|
+
* Both current callsites (`runStart`, `runUpgradeCli`) invoke this while
|
|
641
|
+
* holding `withMutationLock`, so the spawn always sets
|
|
642
|
+
* `PARENT_LOCK_HELD_ENV_VAR=1` to suppress the child service's
|
|
643
|
+
* unconditional `forceReleaseLock` startup step. See env-var docstring
|
|
644
|
+
* above.
|
|
645
|
+
*
|
|
646
|
+
* NOT for use inside `__service` itself (the service is already the
|
|
647
|
+
* detached process). NOT for use inside `restartPhase` defaults: the
|
|
648
|
+
* orchestrator's `deps.spawnFreshService` callback is the integration
|
|
649
|
+
* point so unit tests can stub it without spawning real children.
|
|
650
|
+
*
|
|
651
|
+
* Throws if the OS refuses the spawn (no pid available).
|
|
652
|
+
*/
|
|
653
|
+
declare function spawnDetachedService(slockHome: string): Promise<number>;
|
|
654
|
+
interface ResidentCore {
|
|
655
|
+
start: () => void | Promise<void>;
|
|
656
|
+
stop: () => void | Promise<void>;
|
|
657
|
+
}
|
|
658
|
+
type ResidentCoreFactory = (creds: {
|
|
659
|
+
serverId: string;
|
|
660
|
+
serverMachineId: string;
|
|
661
|
+
apiKey: string;
|
|
662
|
+
serverUrl: string;
|
|
663
|
+
}) => ResidentCore | Promise<ResidentCore>;
|
|
664
|
+
type RunStartDeps = {
|
|
665
|
+
coreFactory?: ResidentCoreFactory;
|
|
666
|
+
spawnDetachedService?: typeof spawnDetachedService;
|
|
667
|
+
readPidfile?: typeof readPidfileAt;
|
|
668
|
+
isProcessAlive?: typeof isProcessAlive;
|
|
669
|
+
sleep?: (ms: number) => Promise<void>;
|
|
670
|
+
ensureTimeoutMs?: number;
|
|
671
|
+
ensurePollIntervalMs?: number;
|
|
672
|
+
};
|
|
673
|
+
declare function runService(): Promise<void>;
|
|
674
|
+
/**
|
|
675
|
+
* `slock-computer start [serverId]` CLI adapter. Thin wrapper over the
|
|
676
|
+
* StartService (`./services/start.ts`); see service file header for the
|
|
677
|
+
* locked shape (RFC v0.8 contract v4 §6 line 80 — typed events +
|
|
678
|
+
* AbortSignal pre/post-spawn boundary + closed-set § codes byte-identical).
|
|
679
|
+
*
|
|
680
|
+
* This adapter's responsibilities:
|
|
681
|
+
* 1. Map start.* events → info() lines byte-identical to the
|
|
682
|
+
* pre-extraction implementation.
|
|
683
|
+
* 2. Map ComputerServiceError → fail(code, message), preserving
|
|
684
|
+
* CliExit semantics for the test harness.
|
|
685
|
+
* 3. Render `formatReadySummary` (kept in adapter on purpose:
|
|
686
|
+
* user-visible text formatter, not service axis).
|
|
687
|
+
*
|
|
688
|
+
* The pre-extraction `RunStartDeps` shape is preserved so existing
|
|
689
|
+
* service.test.ts test cases keep working — we forward the deps
|
|
690
|
+
* straight into the service.
|
|
691
|
+
*/
|
|
692
|
+
declare function runStart(opts?: {
|
|
693
|
+
foreground?: boolean;
|
|
694
|
+
serverId?: string | null;
|
|
695
|
+
serverLabel?: string | null;
|
|
696
|
+
}, deps?: RunStartDeps): Promise<void>;
|
|
697
|
+
|
|
698
|
+
type StartStatus = "running" | "already_running" | "spawned" | "aborted";
|
|
699
|
+
interface StartResult {
|
|
700
|
+
status: StartStatus;
|
|
701
|
+
/** Managed set for this start invocation (single id when serverId
|
|
702
|
+
* was set; full attached set otherwise). */
|
|
703
|
+
managedTargets: string[];
|
|
704
|
+
/** Total number of attached servers at the moment of start (includes
|
|
705
|
+
* managedTargets when start was global). */
|
|
706
|
+
attachedCount: number;
|
|
707
|
+
/** Map of serverId → daemon pid for daemons confirmed ready before
|
|
708
|
+
* return. Empty when status === "running" (foreground); on
|
|
709
|
+
* status === "aborted" reflects whatever was ready at the moment
|
|
710
|
+
* the abort was observed. */
|
|
711
|
+
ready: Map<string, number>;
|
|
712
|
+
/** Pid of the (newly spawned OR existing) service when known.
|
|
713
|
+
* null for status === "running" (the calling process IS the
|
|
714
|
+
* service in foreground mode). */
|
|
715
|
+
servicePid: number | null;
|
|
716
|
+
/** Path of the service's combined log (for adapter "Logs: …" line). */
|
|
717
|
+
serviceLogPath: string;
|
|
718
|
+
}
|
|
719
|
+
interface StartDeps {
|
|
720
|
+
/** Test seams (default to module-level real impls). Mirrors the
|
|
721
|
+
* pre-extraction `RunStartDeps` shape so existing service.test.ts
|
|
722
|
+
* test cases keep working through the adapter. */
|
|
723
|
+
spawnDetachedService?: typeof spawnDetachedService;
|
|
724
|
+
readPidfile?: typeof readPidfileAt;
|
|
725
|
+
isProcessAlive?: typeof isProcessAlive;
|
|
726
|
+
sleep?: (ms: number) => Promise<void>;
|
|
727
|
+
ensureTimeoutMs?: number;
|
|
728
|
+
ensurePollIntervalMs?: number;
|
|
729
|
+
/** Foreground service loop. Default: real `runService`. */
|
|
730
|
+
runService?: typeof runService;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
type StopStatus = "not_running" | "stale_pidfile_cleared" | "stopped";
|
|
734
|
+
interface StopResult {
|
|
735
|
+
status: StopStatus;
|
|
736
|
+
/** Pid the service observed in the pidfile when status !== "not_running".
|
|
737
|
+
* null on the not_running path (no pid was read). */
|
|
738
|
+
pid: number | null;
|
|
739
|
+
/** Path of the service pidfile (so adapters can render hints
|
|
740
|
+
* without re-resolving paths). */
|
|
741
|
+
pidfilePath: string;
|
|
742
|
+
}
|
|
743
|
+
interface StopDeps {
|
|
744
|
+
/** Test seams — pre-extraction `RunStopDeps` shape preserved so
|
|
745
|
+
* existing service.test.ts cases keep byte-identical imports
|
|
746
|
+
* through the adapter. */
|
|
747
|
+
readPidfile?: typeof readPidfileAt;
|
|
748
|
+
isProcessAlive?: typeof isProcessAlive;
|
|
749
|
+
killService?: (pid: number) => void;
|
|
750
|
+
sleep?: (ms: number) => Promise<void>;
|
|
751
|
+
pollIntervalMs?: number;
|
|
752
|
+
timeoutMs?: number;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
type DetachStatus = "detached";
|
|
756
|
+
type RevokeOutcome = "success" | "http_error" | "network_error";
|
|
757
|
+
interface DetachResult {
|
|
758
|
+
status: DetachStatus;
|
|
759
|
+
serverId: string;
|
|
760
|
+
serverLabel: string;
|
|
761
|
+
revokeOutcome: RevokeOutcome;
|
|
762
|
+
/** HTTP status code on http_error path; undefined otherwise. */
|
|
763
|
+
revokeHttpStatus?: number;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
interface DoctorCheck {
|
|
767
|
+
name: string;
|
|
768
|
+
ok: boolean;
|
|
769
|
+
detail: string;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
interface CleanupReport {
|
|
773
|
+
stalePidfiles: string[];
|
|
774
|
+
orphanProcesses: number[];
|
|
775
|
+
powerLossRecovered: string[];
|
|
776
|
+
tmpFilesCleared: string[];
|
|
777
|
+
staleLocks: string[];
|
|
778
|
+
/** Was any cleanup actually performed? false = clean baseline. */
|
|
779
|
+
anyAction: boolean;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
interface CrashEntry {
|
|
783
|
+
at: string;
|
|
784
|
+
exitCode: number | null;
|
|
785
|
+
signal: string | null;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Read the recent crash history (within the crash-window). Returns []
|
|
789
|
+
* if file missing/corrupt.
|
|
790
|
+
*/
|
|
791
|
+
declare function readCrashHistory(slockHome: string, serverId: string, nowMs?: number): Promise<CrashEntry[]>;
|
|
792
|
+
|
|
793
|
+
type Channel = "latest" | "alpha" | `pinned:${string}`;
|
|
794
|
+
|
|
795
|
+
declare function runAttach(opts: {
|
|
796
|
+
serverSlug: string;
|
|
797
|
+
serverUrl?: string;
|
|
798
|
+
name?: string;
|
|
799
|
+
start?: boolean;
|
|
800
|
+
foreground?: boolean;
|
|
801
|
+
orchestrated?: boolean;
|
|
802
|
+
}): Promise<void>;
|
|
803
|
+
|
|
489
804
|
/**
|
|
490
805
|
* Roster fetcher contract — `LegacyMachinesClient.list(serverSlug)`
|
|
491
806
|
* satisfies this structurally. Defining it as an interface here lets
|
|
@@ -542,6 +857,117 @@ type ManualPathValidation = {
|
|
|
542
857
|
};
|
|
543
858
|
declare function validateManualMigratePath(inputPath: string, roster: LegacyMachineRosterEntry[]): Promise<ManualPathValidation>;
|
|
544
859
|
|
|
860
|
+
declare function runLogin(opts: {
|
|
861
|
+
serverUrl?: string;
|
|
862
|
+
orchestrated?: boolean;
|
|
863
|
+
}): Promise<void>;
|
|
864
|
+
|
|
865
|
+
/** Credential bridge mode — §5.11.4 closed enum, snake_case byte-exact. */
|
|
866
|
+
type CredentialBridgeMode = "legacy_key_argv" | "legacy_key_file" | "legacy_key_stdin" | "legacy_key_env" | "legacy_fingerprint_roster";
|
|
867
|
+
interface AdoptLegacyByFingerprintInput {
|
|
868
|
+
serverSlug: string;
|
|
869
|
+
serverUrl?: string;
|
|
870
|
+
name?: string;
|
|
871
|
+
/** Server roster identity selected after user login. */
|
|
872
|
+
legacyMachineId: string;
|
|
873
|
+
/** sha256(legacy api key).slice(0,16), intersected with local owner.json. */
|
|
874
|
+
apiKeyFingerprint: string;
|
|
875
|
+
/** Absolute local owner.json path from detection/validation. */
|
|
876
|
+
legacyOwnerPath: string;
|
|
877
|
+
}
|
|
878
|
+
interface LegacyStopResult {
|
|
879
|
+
attempted: boolean;
|
|
880
|
+
pid?: number;
|
|
881
|
+
outcome: "absent" | "already_dead" | "stopped" | "timed_out" | "denied" | "error";
|
|
882
|
+
reason?: string;
|
|
883
|
+
}
|
|
884
|
+
interface AdoptLegacyResult {
|
|
885
|
+
serverId: string;
|
|
886
|
+
serverMachineId: string;
|
|
887
|
+
/** Legacy machine row id — emitted by the server on success so the
|
|
888
|
+
* attachment.json can carry `adoptedFromLegacy: true` / `legacyMachineId`. */
|
|
889
|
+
legacyMachineId: string;
|
|
890
|
+
serverSlug: string;
|
|
891
|
+
serverUrl: string;
|
|
892
|
+
attachmentPath: string;
|
|
893
|
+
resumed: boolean;
|
|
894
|
+
/** First 8 characters of the FRESHLY-ISSUED sk_computer_*; mirrors
|
|
895
|
+
* attachment.json/adoption.log convention. The raw fresh key lives ONLY
|
|
896
|
+
* in attachment.json (mode 0o600) — NEVER on this event/result. */
|
|
897
|
+
apiKeyRedactedPrefix: string;
|
|
898
|
+
legacyStop: LegacyStopResult;
|
|
899
|
+
}
|
|
900
|
+
type AdoptLegacyEvent = {
|
|
901
|
+
type: "adopting";
|
|
902
|
+
serverSlug: string;
|
|
903
|
+
mode: CredentialBridgeMode;
|
|
904
|
+
} | {
|
|
905
|
+
type: "preflight";
|
|
906
|
+
resumed: boolean;
|
|
907
|
+
} | {
|
|
908
|
+
type: "adopted";
|
|
909
|
+
serverId: string;
|
|
910
|
+
serverMachineId: string;
|
|
911
|
+
legacyMachineId: string;
|
|
912
|
+
serverSlug: string;
|
|
913
|
+
attachmentPath: string;
|
|
914
|
+
resumed: boolean;
|
|
915
|
+
apiKeyRedactedPrefix: string;
|
|
916
|
+
legacyStop: LegacyStopResult;
|
|
917
|
+
};
|
|
918
|
+
interface AdoptLegacyOptions {
|
|
919
|
+
signal?: AbortSignal;
|
|
920
|
+
onEvent?: (event: AdoptLegacyEvent) => void;
|
|
921
|
+
}
|
|
922
|
+
declare function adoptLegacyByFingerprint(input: AdoptLegacyByFingerprintInput, options?: AdoptLegacyOptions): Promise<AdoptLegacyResult>;
|
|
923
|
+
|
|
924
|
+
interface SetupOptions {
|
|
925
|
+
serverSlug: string;
|
|
926
|
+
serverUrl?: string;
|
|
927
|
+
name?: string;
|
|
928
|
+
start?: boolean;
|
|
929
|
+
foreground?: boolean;
|
|
930
|
+
yes?: boolean;
|
|
931
|
+
/**
|
|
932
|
+
* RFC v9.9 §X.4 manual escape hatch. When set, bypasses the picker
|
|
933
|
+
* and runs the three-gate validator
|
|
934
|
+
* (`MIGRATE_FROM_NOT_FOUND` / `MIGRATE_FROM_INVALID` /
|
|
935
|
+
* `MIGRATE_FROM_NOT_OWNED`) against the supplied path. Hard-fails on
|
|
936
|
+
* any failure — the operator chose this path explicitly.
|
|
937
|
+
*/
|
|
938
|
+
migrateFrom?: string;
|
|
939
|
+
}
|
|
940
|
+
interface SetupDeps {
|
|
941
|
+
isTty?: boolean;
|
|
942
|
+
runLogin?: typeof runLogin;
|
|
943
|
+
runAttach?: typeof runAttach;
|
|
944
|
+
runStart?: typeof runStart;
|
|
945
|
+
refreshUserSession?: typeof refreshUserSession;
|
|
946
|
+
detectLegacyMigration?: typeof detectLegacyMigration;
|
|
947
|
+
validateManualMigratePath?: typeof validateManualMigratePath;
|
|
948
|
+
/**
|
|
949
|
+
* Roster client factory — defaults to `new LegacyMachinesClient(...)`.
|
|
950
|
+
* Tests stub this to feed a fake roster without booting undici.
|
|
951
|
+
*/
|
|
952
|
+
buildRosterClient?: (baseUrl: string, accessToken: string) => {
|
|
953
|
+
list: LegacyMachinesClient["list"];
|
|
954
|
+
};
|
|
955
|
+
/**
|
|
956
|
+
* Picker prompt — returns the operator's selection. Defaults to a
|
|
957
|
+
* stdin reader. Each return value drives a distinct branch:
|
|
958
|
+
* - `{ kind: "candidate"; index: number }` — adopt picker entry 1..N
|
|
959
|
+
* - `{ kind: "fresh" }` — `0` / `<Enter>` / EOF / unrecognized
|
|
960
|
+
* - `{ kind: "manual"; path: string }` — `m`, then path on next line
|
|
961
|
+
*/
|
|
962
|
+
pickMigrationCandidate?: (candidates: LegacyMachineCandidate[]) => Promise<PickerSelection>;
|
|
963
|
+
/**
|
|
964
|
+
* Roster-fingerprint adoption service injection — defaults to
|
|
965
|
+
* `adoptLegacyByFingerprintService`. Tests stub this to verify picker
|
|
966
|
+
* wiring without hitting the network. Normal setup never reads a raw
|
|
967
|
+
* legacy key; the selected candidate's roster identity is the authority.
|
|
968
|
+
*/
|
|
969
|
+
adoptLegacyByFingerprint?: typeof adoptLegacyByFingerprint;
|
|
970
|
+
}
|
|
545
971
|
type PickerSelection = {
|
|
546
972
|
kind: "candidate";
|
|
547
973
|
index: number;
|
|
@@ -561,6 +987,7 @@ type PickerSelection = {
|
|
|
561
987
|
*/
|
|
562
988
|
declare const MIGRATION_FRESH_TRIGGERS: readonly ["empty-intersection", "explicit-zero", "eof", "non-tty", "server-unavailable"];
|
|
563
989
|
type MigrationFreshTrigger = (typeof MIGRATION_FRESH_TRIGGERS)[number];
|
|
990
|
+
declare function refreshUserSession(slockHome: string, serverUrl?: string): Promise<boolean>;
|
|
564
991
|
/**
|
|
565
992
|
* Pure picker grammar — extracted so it can be exercised by unit tests
|
|
566
993
|
* without stubbing global stdin/stdout. RFC v9.9 §X.4 (post-Jianwei FAIL
|
|
@@ -580,31 +1007,132 @@ declare function pickMigrationCandidateFromInput(candidates: LegacyMachineCandid
|
|
|
580
1007
|
eof: boolean;
|
|
581
1008
|
}>, write: (s: string) => void): Promise<PickerSelection>;
|
|
582
1009
|
|
|
1010
|
+
/** Public CDN root for published Computer SEA binaries (prod default). */
|
|
1011
|
+
declare const DEFAULT_UPGRADE_BASE_URL = "https://cdn.slock.ai/computer";
|
|
1012
|
+
interface SeaStageResult {
|
|
1013
|
+
/** Absolute path to the downloaded + sha256-verified staged binary. */
|
|
1014
|
+
readonly stagedBinaryPath: string;
|
|
1015
|
+
readonly version: string;
|
|
1016
|
+
/** Verified sha256 (hex) of the staged binary — matches the manifest. */
|
|
1017
|
+
readonly sha256: string;
|
|
1018
|
+
}
|
|
1019
|
+
interface SeaStageDeps {
|
|
1020
|
+
readonly fetchFn?: typeof fetch;
|
|
1021
|
+
readonly baseUrl?: string;
|
|
1022
|
+
/** The `computer:upgrade` requestId, echoed onto emitted download-progress frames. */
|
|
1023
|
+
readonly requestId?: string;
|
|
1024
|
+
/** Optional sink for throttled byte-% progress during the `downloading` phase. */
|
|
1025
|
+
readonly onProgress?: (e: SeaUpgradeProgressEvent) => void;
|
|
1026
|
+
}
|
|
583
1027
|
/**
|
|
584
|
-
*
|
|
585
|
-
*
|
|
586
|
-
*
|
|
1028
|
+
* SEA Phase 1 — download the target-version binary for this platform into
|
|
1029
|
+
* `stagingDir` and verify its sha256 against the published manifest. Throws on
|
|
1030
|
+
* any failure (no manifest, no target for this platform, download error, sha
|
|
1031
|
+
* mismatch) so the caller aborts BEFORE the swap — nothing on disk is touched
|
|
1032
|
+
* outside `stagingDir`.
|
|
587
1033
|
*/
|
|
588
|
-
declare function
|
|
1034
|
+
declare function stageSeaPhase(stagingDir: string, version: string, platform: NodeJS.Platform, arch: string, deps?: SeaStageDeps): Promise<SeaStageResult>;
|
|
1035
|
+
interface SeaSwapResult {
|
|
1036
|
+
/** Where the old binary was moved to (rollback restores from here). */
|
|
1037
|
+
readonly prevBinaryPath: string;
|
|
1038
|
+
/** The live executable path (unchanged identity; new bytes). */
|
|
1039
|
+
readonly currentBinaryPath: string;
|
|
1040
|
+
}
|
|
589
1041
|
/**
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
1042
|
+
* SEA Phase 4 — atomic single-file swap. Moves the current executable to
|
|
1043
|
+
* `<exe>.prev` (kept until the new service passes health-check), then renames
|
|
1044
|
+
* the staged binary into the executable's path. A stale `.prev` from a prior
|
|
1045
|
+
* failed upgrade is cleared first. On failure mid-swap the caller rolls back
|
|
1046
|
+
* via {@link rollbackSeaSwap}.
|
|
594
1047
|
*/
|
|
595
|
-
declare function
|
|
1048
|
+
declare function swapSeaPhase(currentBinaryPath: string, stagedBinaryPath: string): Promise<SeaSwapResult>;
|
|
1049
|
+
interface ManualRollbackResult {
|
|
1050
|
+
/** The `<exe>.prev` that was restored over the live executable. */
|
|
1051
|
+
readonly restoredFrom: string;
|
|
1052
|
+
/** The live executable path (unchanged identity; restored bytes). */
|
|
1053
|
+
readonly currentBinaryPath: string;
|
|
1054
|
+
}
|
|
596
1055
|
/**
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
1056
|
+
* Operator-initiated `upgrade --rollback`: restore the `<exe>.prev` backup the
|
|
1057
|
+
* last successful swap left behind, putting the previous binary back at the
|
|
1058
|
+
* live executable path. Reuses {@link rollbackSeaSwap} (the same primitive the
|
|
1059
|
+
* supervisor's bad-binary watchdog uses) once the `.prev` is confirmed present.
|
|
601
1060
|
*
|
|
602
|
-
*
|
|
603
|
-
* so
|
|
1061
|
+
* Throws `Error("UPGRADE_NO_ROLLBACK")` if no `.prev` exists (nothing to roll
|
|
1062
|
+
* back to) so the CLI can map it to the closed-set token. The supervisor
|
|
1063
|
+
* restarts the service on the restored binary (same as a normal swap).
|
|
604
1064
|
*/
|
|
605
|
-
declare function
|
|
606
|
-
|
|
607
|
-
|
|
1065
|
+
declare function rollbackToPrev(currentBinaryPath: string): Promise<ManualRollbackResult>;
|
|
1066
|
+
type SeaUpgradePhase = "downloading" | "verifying" | "applying" | "restarting";
|
|
1067
|
+
interface SeaUpgradeProgressEvent {
|
|
1068
|
+
readonly requestId: string;
|
|
1069
|
+
readonly phase: SeaUpgradePhase;
|
|
1070
|
+
readonly message?: string;
|
|
1071
|
+
/** 0-100 byte-progress, present ONLY on `downloading`-phase progress frames. */
|
|
1072
|
+
readonly percent?: number;
|
|
1073
|
+
}
|
|
1074
|
+
interface RunSeaUpgradeOpts {
|
|
1075
|
+
readonly slockHome: string;
|
|
1076
|
+
readonly requestId: string;
|
|
1077
|
+
readonly fromVersion: string;
|
|
1078
|
+
readonly targetVersion: string;
|
|
1079
|
+
/** The live executable to swap — normally process.execPath. */
|
|
1080
|
+
readonly currentBinaryPath: string;
|
|
1081
|
+
/** ISO timestamp for the marker (injected for deterministic tests). */
|
|
1082
|
+
readonly nowIso: string;
|
|
1083
|
+
readonly platform?: NodeJS.Platform;
|
|
1084
|
+
readonly arch?: string;
|
|
1085
|
+
readonly deps?: {
|
|
1086
|
+
readonly baseUrl?: string;
|
|
1087
|
+
readonly fetchFn?: typeof fetch;
|
|
1088
|
+
readonly onProgress?: (e: SeaUpgradeProgressEvent) => void;
|
|
1089
|
+
readonly stageFn?: typeof stageSeaPhase;
|
|
1090
|
+
readonly swapFn?: typeof swapSeaPhase;
|
|
1091
|
+
readonly stagingDir?: string;
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
interface RunSeaUpgradeResult {
|
|
1095
|
+
readonly ok: boolean;
|
|
1096
|
+
/** Which phase failed (absent on success). */
|
|
1097
|
+
readonly failedPhase?: "stage" | "swap";
|
|
1098
|
+
readonly reason?: string;
|
|
1099
|
+
/** Present on success — the swap, so the caller / watchdog can roll back via `.prev`. */
|
|
1100
|
+
readonly swap?: SeaSwapResult;
|
|
1101
|
+
}
|
|
1102
|
+
declare function runSeaUpgrade(opts: RunSeaUpgradeOpts): Promise<RunSeaUpgradeResult>;
|
|
1103
|
+
/** Resolve the CDN's always-latest version from the top-level manifest
|
|
1104
|
+
* (`<base>/manifest.json` → `{ version }`). This is the SAME pointer
|
|
1105
|
+
* `install.sh` consumes, so `install` and `upgrade` share one source of
|
|
1106
|
+
* "latest" — no npm registry round-trip (npm dist-tags went stale at the SEA
|
|
1107
|
+
* cutover; the binary store is the CDN). Returns null on any failure (caller
|
|
1108
|
+
* maps to a resolve error). */
|
|
1109
|
+
declare function fetchCdnLatestVersion(baseUrl: string, fetchFn?: typeof fetch): Promise<string | null>;
|
|
1110
|
+
interface ResolveAndRunSeaUpgradeOpts {
|
|
1111
|
+
readonly slockHome: string;
|
|
1112
|
+
readonly requestId: string;
|
|
1113
|
+
readonly fromVersion: string;
|
|
1114
|
+
readonly currentBinaryPath: string;
|
|
1115
|
+
readonly nowIso: string;
|
|
1116
|
+
readonly platform?: NodeJS.Platform;
|
|
1117
|
+
readonly arch?: string;
|
|
1118
|
+
readonly deps?: RunSeaUpgradeOpts["deps"] & {
|
|
1119
|
+
/** Override channel resolution (default: readChannel(slockHome)). */
|
|
1120
|
+
readonly readChannelFn?: (slockHome: string) => Promise<Channel>;
|
|
1121
|
+
/** Override the CDN latest-version fetch (default: read `<base>/manifest.json`). */
|
|
1122
|
+
readonly fetchLatestVersion?: (baseUrl: string) => Promise<string | null>;
|
|
1123
|
+
/** Override the full target-version resolver (bypasses channel resolution). */
|
|
1124
|
+
readonly resolveTargetVersion?: () => Promise<string | null>;
|
|
1125
|
+
readonly runSeaUpgradeFn?: typeof runSeaUpgrade;
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
interface ResolveAndRunSeaUpgradeResult extends Omit<RunSeaUpgradeResult, "failedPhase"> {
|
|
1129
|
+
readonly targetVersion?: string;
|
|
1130
|
+
/** True when already at the resolved target (no-op, no swap, no restart). */
|
|
1131
|
+
readonly alreadyCurrent?: boolean;
|
|
1132
|
+
/** Extends RunSeaUpgradeResult's failedPhase with the resolution step. */
|
|
1133
|
+
readonly failedPhase?: "resolve" | "stage" | "swap";
|
|
1134
|
+
}
|
|
1135
|
+
declare function resolveAndRunSeaUpgrade(opts: ResolveAndRunSeaUpgradeOpts): Promise<ResolveAndRunSeaUpgradeResult>;
|
|
608
1136
|
|
|
609
1137
|
type UpgradeTrigger = "cli" | "web" | "tray";
|
|
610
1138
|
/**
|
|
@@ -681,4 +1209,304 @@ interface UpgradeLogEntryErr {
|
|
|
681
1209
|
*/
|
|
682
1210
|
declare function assertUpgradeLogEntry(entry: UpgradeLogEntry): void;
|
|
683
1211
|
|
|
684
|
-
|
|
1212
|
+
interface UpgradeCliOptions {
|
|
1213
|
+
dryRun?: boolean;
|
|
1214
|
+
channel?: string;
|
|
1215
|
+
/**
|
|
1216
|
+
* Explicit target version (bypasses channel resolution). Surfaced on
|
|
1217
|
+
* the CLI as `--target-version`, NOT `--version` — Commander treats
|
|
1218
|
+
* `--version` as the root program version flag and silently shadows
|
|
1219
|
+
* any subcommand option of the same name.
|
|
1220
|
+
*/
|
|
1221
|
+
targetVersion?: string;
|
|
1222
|
+
/**
|
|
1223
|
+
* Restore the `<exe>.prev` backup the last successful swap left behind,
|
|
1224
|
+
* putting the previous binary back at the live executable path. Mutually
|
|
1225
|
+
* exclusive with --target-version / --channel / --dry-run (it resolves /
|
|
1226
|
+
* downloads nothing). Fails with `UPGRADE_NO_ROLLBACK` when no `.prev`
|
|
1227
|
+
* exists. SEA-only (same gate as a normal upgrade).
|
|
1228
|
+
*/
|
|
1229
|
+
rollback?: boolean;
|
|
1230
|
+
/**
|
|
1231
|
+
* §4.4 (4) trigger source attribution for upgrade.log audit trail.
|
|
1232
|
+
* Defaults to "cli" when invoked through the bare CLI entry point.
|
|
1233
|
+
*/
|
|
1234
|
+
trigger?: UpgradeTrigger;
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Pure upgrade core (CLI-over-lib convergence). Validates inputs, resolves
|
|
1238
|
+
* channel + target version, and dispatches to the SEA in-process upgrade. It
|
|
1239
|
+
* NEVER writes to stdout directly: human lines go through the `emit` sink the
|
|
1240
|
+
* presenter supplies, and the UPGRADE / CHANNEL_INVALID / UPGRADE_SEA_ONLY fail
|
|
1241
|
+
* points throw `ComputerError` (the presenter maps them to the
|
|
1242
|
+
* `{ok:false,code,message}` fail-shape). The dry-run / channel / target-version
|
|
1243
|
+
* / rollback flag handling + the SEA-only gate are byte-identical to the
|
|
1244
|
+
* pre-convergence `runUpgradeCli` body. (The IPC-routing + the
|
|
1245
|
+
* UPGRADE_ALREADY_RUNNING mutation-lock remap stay in the index.ts presenter.)
|
|
1246
|
+
*/
|
|
1247
|
+
declare function upgradeCliCore(slockHome: string, opts: UpgradeCliOptions, onEvent: (event: ComputerApiEvent) => void, deps?: {
|
|
1248
|
+
/** Override the CDN latest-version fetch (default: read `<base>/manifest.json`). */
|
|
1249
|
+
fetchLatestVersion?: (baseUrl: string) => Promise<string | null>;
|
|
1250
|
+
/** Override the CDN base-url resolver (default: `resolveUpgradeBaseUrl()`). */
|
|
1251
|
+
resolveUpgradeBaseUrlFn?: () => string;
|
|
1252
|
+
currentVersion?: () => Promise<string>;
|
|
1253
|
+
/** The live executable to swap (default: `process.execPath`). */
|
|
1254
|
+
currentBinaryPath?: () => string;
|
|
1255
|
+
/** Override the SEA upgrade runner (tests stub this). */
|
|
1256
|
+
resolveAndRunSeaUpgradeFn?: typeof resolveAndRunSeaUpgrade;
|
|
1257
|
+
/** Override the SEA stage primitive (dry-run download+verify). */
|
|
1258
|
+
stageSeaPhaseFn?: typeof stageSeaPhase;
|
|
1259
|
+
/** Override the rollback primitive (restore `<exe>.prev`). Tests stub this. */
|
|
1260
|
+
rollbackToPrevFn?: typeof rollbackToPrev;
|
|
1261
|
+
/**
|
|
1262
|
+
* Test seam for the SEA-only gate. Production uses `isSeaBinary()`.
|
|
1263
|
+
* Tests stub it true so the SEA flow is exercised without packaging a
|
|
1264
|
+
* real single-binary, or false to exercise the migration refusal.
|
|
1265
|
+
*/
|
|
1266
|
+
isSeaBinary?: () => boolean;
|
|
1267
|
+
}): Promise<void>;
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Pure result of `doctor` (`slock-computer doctor [serverSlug] [--fix]`). The
|
|
1271
|
+
* presenter formats it (with `redactSecrets` on every emitted line) and sets
|
|
1272
|
+
* the process exit code from `allOk`.
|
|
1273
|
+
*/
|
|
1274
|
+
interface DoctorReport {
|
|
1275
|
+
checks: DoctorCheck[];
|
|
1276
|
+
allOk: boolean;
|
|
1277
|
+
cleanup: CleanupReport | null;
|
|
1278
|
+
crashes: Awaited<ReturnType<typeof readCrashHistory>>;
|
|
1279
|
+
serverId?: string;
|
|
1280
|
+
serverLabel?: string;
|
|
1281
|
+
}
|
|
1282
|
+
interface RunnersListResult {
|
|
1283
|
+
serverSlug: string | null;
|
|
1284
|
+
serverId: string;
|
|
1285
|
+
runners: RunnerListItem[];
|
|
1286
|
+
}
|
|
1287
|
+
interface RunnersStopResult {
|
|
1288
|
+
serverSlug: string | null;
|
|
1289
|
+
serverId: string;
|
|
1290
|
+
agentId: string;
|
|
1291
|
+
}
|
|
1292
|
+
interface LogoutResult {
|
|
1293
|
+
status: "logged-out" | "already-logged-out";
|
|
1294
|
+
sessionPath: string;
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* The typed Computer library API. Each method is PURE (result | throw
|
|
1298
|
+
* `ComputerError`). `createComputerApi(slockHome)` binds an install root so the
|
|
1299
|
+
* methods take no ambient env reads of their own.
|
|
1300
|
+
*/
|
|
1301
|
+
interface ComputerApi {
|
|
1302
|
+
/**
|
|
1303
|
+
* Build the Computer-level aggregate status report. Read-only and
|
|
1304
|
+
* secret-free (status.ts §3.3.1 redline).
|
|
1305
|
+
*/
|
|
1306
|
+
getStatus(): Promise<ComputerStatusReport>;
|
|
1307
|
+
/**
|
|
1308
|
+
* Clear the service-level crash history and transition service state
|
|
1309
|
+
* `degraded → running` (§1.3). Routes through the running service via IPC
|
|
1310
|
+
* when one is up (single-writer), else applies the disk-only handler.
|
|
1311
|
+
*/
|
|
1312
|
+
resetService(): Promise<ResetServiceResult>;
|
|
1313
|
+
/**
|
|
1314
|
+
* Clear a per-runner crash history and transition runner state
|
|
1315
|
+
* `degraded → running` (§2.4). Same single-writer routing as `resetService`.
|
|
1316
|
+
* Returns `{status:"not-found"}` (a RESULT, not a throw) when `serverId` is
|
|
1317
|
+
* not an attached server.
|
|
1318
|
+
*/
|
|
1319
|
+
resetRunner(serverId: string): Promise<ResetRunnerResult>;
|
|
1320
|
+
/**
|
|
1321
|
+
* Device-code login (one user identity per Computer / SLOCK_HOME). Drives
|
|
1322
|
+
* the device-code flow; the presenter-supplied `onEvent` sink prints the
|
|
1323
|
+
* verification URL/code + waiting lines. The api itself never prints.
|
|
1324
|
+
*/
|
|
1325
|
+
login(opts: {
|
|
1326
|
+
serverUrl?: string;
|
|
1327
|
+
}, onEvent?: (event: ComputerApiEvent) => void): Promise<LoginResult>;
|
|
1328
|
+
/** Clear the saved user session (idempotent). Pure: returns a result. */
|
|
1329
|
+
logout(): Promise<LogoutResult>;
|
|
1330
|
+
/**
|
|
1331
|
+
* Attach this Computer to one server (add-not-replace). `onEvent` prints
|
|
1332
|
+
* the attach progress lines presenter-side.
|
|
1333
|
+
*/
|
|
1334
|
+
attach(opts: {
|
|
1335
|
+
serverSlug: string;
|
|
1336
|
+
serverUrl?: string;
|
|
1337
|
+
name?: string;
|
|
1338
|
+
}, onEvent?: (event: ComputerApiEvent) => void): Promise<AttachResult>;
|
|
1339
|
+
/** Remove ONE server's local attachment (per-server isolated). */
|
|
1340
|
+
detach(serverId: string, serverLabel: string, onEvent?: (event: ComputerApiEvent) => void): Promise<DetachResult>;
|
|
1341
|
+
/** Start/ensure the Computer service. `onEvent` formats the start lines. */
|
|
1342
|
+
start(opts: {
|
|
1343
|
+
foreground?: boolean;
|
|
1344
|
+
serverId?: string | null;
|
|
1345
|
+
serverLabel?: string | null;
|
|
1346
|
+
}, onEvent?: (event: ComputerApiEvent) => void, deps?: StartDeps): Promise<StartResult>;
|
|
1347
|
+
/** Stop the Computer service (idempotent). `onEvent` formats the stop lines. */
|
|
1348
|
+
stop(onEvent?: (event: ComputerApiEvent) => void, deps?: StopDeps): Promise<StopResult>;
|
|
1349
|
+
/**
|
|
1350
|
+
* Diagnose login + per-server attachments + preflight + service. Returns the
|
|
1351
|
+
* report (runs the cleanup pass when `cleanup` is true). Pure — the presenter
|
|
1352
|
+
* formats (with `redactSecrets`) and sets the exit code from `allOk`.
|
|
1353
|
+
*/
|
|
1354
|
+
doctor(opts: {
|
|
1355
|
+
cleanup?: boolean;
|
|
1356
|
+
serverId?: string;
|
|
1357
|
+
serverLabel?: string;
|
|
1358
|
+
}): Promise<DoctorReport>;
|
|
1359
|
+
/**
|
|
1360
|
+
* Tail one server's runner log (or the service log). Returns the trailing
|
|
1361
|
+
* (already secret-redacted) lines; the presenter prints them. Throws
|
|
1362
|
+
* `NO_DAEMON_LOG` when the target log is absent.
|
|
1363
|
+
*/
|
|
1364
|
+
logs(opts: {
|
|
1365
|
+
lines?: number;
|
|
1366
|
+
serverId?: string;
|
|
1367
|
+
service?: boolean;
|
|
1368
|
+
}): Promise<string[]>;
|
|
1369
|
+
/** List runners on one attached server. Throws RUNNERS_* on failure. */
|
|
1370
|
+
runnersList(serverId: string): Promise<RunnersListResult>;
|
|
1371
|
+
/** Stop a runner on one attached server. Throws the RUNNER/RUNNERS errors on failure. */
|
|
1372
|
+
runnersStop(agentId: string, attachment: {
|
|
1373
|
+
serverUrl: string;
|
|
1374
|
+
apiKey: string;
|
|
1375
|
+
serverSlug: string | null;
|
|
1376
|
+
serverId: string;
|
|
1377
|
+
}): Promise<RunnersStopResult>;
|
|
1378
|
+
/** Read the current release channel (default `latest` when unset). */
|
|
1379
|
+
channelShow(): Promise<Channel>;
|
|
1380
|
+
/** Validate + persist the release channel. Throws `CHANNEL_INVALID`. */
|
|
1381
|
+
channelSet(raw: string): Promise<Channel>;
|
|
1382
|
+
/**
|
|
1383
|
+
* Orchestrate `setup <serverSlug>`: login → (detect + picker / --migrate-from)
|
|
1384
|
+
* → attach → start. INTERACTIVE: `onEvent` is the event sink (the presenter
|
|
1385
|
+
* unwraps `log.line` events to `info`); the interactive picker is the
|
|
1386
|
+
* dep-injected `pickMigrationCandidate` prompt callback. The api drives the
|
|
1387
|
+
* flow and throws `ComputerError` for its closed-set fail points; it never
|
|
1388
|
+
* prints.
|
|
1389
|
+
*/
|
|
1390
|
+
setup(opts: SetupOptions, deps: SetupDeps, onEvent: (event: ComputerApiEvent) => void): Promise<void>;
|
|
1391
|
+
/**
|
|
1392
|
+
* SEA self-upgrade (or `--rollback`). Validates inputs, resolves channel +
|
|
1393
|
+
* target version, runs the SEA download+verify+swap (or the dry-run / rollback
|
|
1394
|
+
* primitive). INTERACTIVE-ish: `onEvent` is the event sink (the presenter
|
|
1395
|
+
* unwraps `log.line` events to `info`); the SEA-only gate +
|
|
1396
|
+
* dry-run/channel/target-version/rollback flag handling live here, throwing
|
|
1397
|
+
* `ComputerError` for the UPGRADE_* closed set. This is the STANDALONE swap
|
|
1398
|
+
* primitive (cold-boot path); the live-service IPC route is
|
|
1399
|
+
* `tryUpgradeViaService`. The in-process mutation-lock + the
|
|
1400
|
+
* UPGRADE_ALREADY_RUNNING remap stay in the index.ts presenter (the lock
|
|
1401
|
+
* owner) — they wrap this standalone call.
|
|
1402
|
+
*/
|
|
1403
|
+
upgrade(opts: UpgradeCliOptions, deps: Parameters<typeof upgradeCliCore>[3], onEvent: (event: ComputerApiEvent) => void): Promise<void>;
|
|
1404
|
+
/**
|
|
1405
|
+
* Route a PLAIN FORWARD upgrade through a live service via IPC
|
|
1406
|
+
* (`upgrade-start`): the service drives download/verify/swap + self-restart
|
|
1407
|
+
* in-process (single-writer). Returns `{routed:true}` after kicking it off
|
|
1408
|
+
* (the started / already-running line is emitted via `onEvent` as a
|
|
1409
|
+
* `log.line`); returns `{routed:false}` when NO service is live (cold boot)
|
|
1410
|
+
* so the caller runs the standalone `upgrade()` under its own mutation lock.
|
|
1411
|
+
* The cross-process upgrade race is guarded by the service's own
|
|
1412
|
+
* `inFlightUpgrade` + the `upgrade-start` already-running response — NOT a
|
|
1413
|
+
* caller-side lock. Only for plain forward upgrades; the presenter gates out
|
|
1414
|
+
* dry-run / channel-override / rollback (those are always standalone).
|
|
1415
|
+
*/
|
|
1416
|
+
tryUpgradeViaService(targetVersion: string | undefined, onEvent?: (event: ComputerApiEvent) => void): Promise<{
|
|
1417
|
+
routed: boolean;
|
|
1418
|
+
}>;
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Construct a ComputerApi bound to an explicit install root. The single-writer
|
|
1422
|
+
* reset routing (`resetViaServiceOrDisk`) lives here so the api owns the
|
|
1423
|
+
* IPC-vs-disk decision: when a service is running, mutations route through it
|
|
1424
|
+
* via IPC so the supervisor's in-memory runner state stays consistent; when no
|
|
1425
|
+
* service is up (cold boot) the disk-only lib-pure handlers apply directly.
|
|
1426
|
+
*/
|
|
1427
|
+
declare function createComputerApi(slockHome: string): ComputerApi;
|
|
1428
|
+
|
|
1429
|
+
/**
|
|
1430
|
+
* One Computer trace line. INTENTIONALLY shaped as `@slock-ai/shared`'s
|
|
1431
|
+
* canonical `TraceEvent` (`{ name, timeMs, attrs? }`) so the forthcoming shared
|
|
1432
|
+
* trace-client can route these through the real `Tracer` / `TraceSink` with ZERO
|
|
1433
|
+
* format reconciliation. We MIRROR the shape rather than import the type — that
|
|
1434
|
+
* keeps this interim file sink from pulling `@slock-ai/shared` into the Computer
|
|
1435
|
+
* package early; the shared-client work (B) replaces this helper's internals
|
|
1436
|
+
* (file-append → `Tracer.startSpan` + `TraceSink.record`) while the emitted
|
|
1437
|
+
* shape stays identical.
|
|
1438
|
+
*
|
|
1439
|
+
* `attrs` is the open structured bag (== shared `TraceAttributes`): route
|
|
1440
|
+
* decisions carry `{ method, decision }`, the menu-bar action trail carries
|
|
1441
|
+
* `{ kind, durationMs, outcome, errorCode }`, etc.
|
|
1442
|
+
*/
|
|
1443
|
+
interface ComputerTraceEvent {
|
|
1444
|
+
/** event name, e.g. "route-decision" | "action" */
|
|
1445
|
+
name: string;
|
|
1446
|
+
/** epoch milliseconds (== shared `TraceEvent.timeMs`) */
|
|
1447
|
+
timeMs: number;
|
|
1448
|
+
/** structured attributes (== shared `TraceAttributes`) */
|
|
1449
|
+
attrs?: Record<string, unknown>;
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Best-effort append of one `ComputerTraceEvent` JSON line to the Computer trace
|
|
1453
|
+
* sink (`run/trace.jsonl`). NEVER throws or blocks the traced operation — any fs
|
|
1454
|
+
* error is swallowed.
|
|
1455
|
+
*/
|
|
1456
|
+
declare function traceEvent(slockHome: string, event: ComputerTraceEvent): Promise<void>;
|
|
1457
|
+
/**
|
|
1458
|
+
* Record a single-writer route decision (reset / upgrade routing) as a
|
|
1459
|
+
* `route-decision` `ComputerTraceEvent` — the answer to "did this op take the
|
|
1460
|
+
* EXPECTED route" (`via-service` | `via-disk` | `ipc` | `standalone`).
|
|
1461
|
+
*/
|
|
1462
|
+
declare function recordRouteDecision(slockHome: string, opts: {
|
|
1463
|
+
method: string;
|
|
1464
|
+
decision: string;
|
|
1465
|
+
timeMs?: number;
|
|
1466
|
+
}): Promise<void>;
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* Read the Computer-level aggregate status from `installRoot`. Identical
|
|
1470
|
+
* shape to `buildStatusReport(installRoot)` — exposed under the
|
|
1471
|
+
* wire-aligned name `readServiceStatus` for IPC `service-status` parity.
|
|
1472
|
+
*/
|
|
1473
|
+
declare function readServiceStatus(installRoot: string): Promise<ServiceStatusResult>;
|
|
1474
|
+
/**
|
|
1475
|
+
* Read a single attached server's daemon state row plus the runners
|
|
1476
|
+
* currently running on it. Throws `StateReaderError("NOT_ATTACHED")` if
|
|
1477
|
+
* `serverId` is not in the attached set, or `"INVALID_ATTACHMENT"` if
|
|
1478
|
+
* the runner.state.json under that id is unreadable.
|
|
1479
|
+
*/
|
|
1480
|
+
declare function readRunnerStatus(installRoot: string, serverId: string): Promise<RunnerStatusResult>;
|
|
1481
|
+
/**
|
|
1482
|
+
* List runners across attached servers. With no `serverId` filter,
|
|
1483
|
+
* returns one block per attached server (zero if no attachments). With
|
|
1484
|
+
* a `serverId` filter, returns exactly that server's block or throws
|
|
1485
|
+
* `StateReaderError("NOT_ATTACHED")`.
|
|
1486
|
+
*
|
|
1487
|
+
* Per-server discriminator preserves `unauthorized` / `error` outcomes
|
|
1488
|
+
* so consumers can pattern-match without losing context.
|
|
1489
|
+
*/
|
|
1490
|
+
declare function listRunners(installRoot: string, opts?: {
|
|
1491
|
+
serverId?: string;
|
|
1492
|
+
}): Promise<ListRunnersResult>;
|
|
1493
|
+
|
|
1494
|
+
/**
|
|
1495
|
+
* Connect to the Computer service over the §4 IPC transport, perform the
|
|
1496
|
+
* versioned handshake (§4.3), and return a typed `ServiceClient`. Throws
|
|
1497
|
+
* `ServiceClientError` (member of `IPC_ERROR_CODES`) on any handshake or
|
|
1498
|
+
* connect-time failure.
|
|
1499
|
+
*/
|
|
1500
|
+
declare function connectService(installRoot: string, options?: ConnectServiceOptions): Promise<ServiceClient>;
|
|
1501
|
+
|
|
1502
|
+
declare class ComputerServiceError extends Error {
|
|
1503
|
+
readonly code: string;
|
|
1504
|
+
readonly cause?: unknown;
|
|
1505
|
+
constructor(code: string, message: string, cause?: unknown);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
declare function userSessionPath(slockHome: string): string;
|
|
1509
|
+
|
|
1510
|
+
declare const COMPUTER_VERSION: string;
|
|
1511
|
+
|
|
1512
|
+
export { type AttachInput, type AttachResult, COMPUTER_VERSION, type ComputerApi, type ComputerApiEvent, ComputerError, ComputerServiceError, type ComputerStatusReport, type ComputerTraceEvent, type ConnectService, type ConnectServiceOptions, DEFAULT_UPGRADE_BASE_URL, type DaemonState, IPC_ERROR_CODES, type IpcErrorCode, type LegacyMachineCandidate, type LegacyMachineRosterClient, type LegacyMachineRosterEntry, type LegacyMachineRosterResult, LegacyMachinesClient, type ListRunnersResult, type LoginInput, type LoginResult, MIGRATION_DETECTION_KINDS, MIGRATION_FRESH_TRIGGERS, type ManualPathValidation, type MigrationDetection, type MigrationDetectionKind, type MigrationFreshTrigger, type PickerSelection, RUNNER_STATE_VALUES, type RequestMethodMap, type RequestOptions, type ResetRunnerResult, type ResetServiceResult, type RunnerListItem as RunnerInfo, type RunnerListPerServer, type RunnerState, type RunnerStatusResult, SERVICE_STATE_VALUES, STATE_READER_ERROR_CODES, type ServerHealth, type ServerStatusRow, ServersClient, type ServiceClient, ServiceClientError, type ServiceEvent, type ServiceState, type ServiceStatusResult, StateReaderError, type StateReaderErrorCode, UPGRADE_ERROR_CODES, type UpgradeBundle, type UpgradeErrorCode, type UpgradeLogEntry, type UpgradeLogEntryErr, type UpgradeLogEntryOk, type UpgradeStartResult, type UpgradeTrigger, type UserServerEntry, type UserServersResult, assertUpgradeLogEntry, connectService, createComputerApi, detectLegacyMigration, fetchCdnLatestVersion, isComputerError, isIpcErrorCode, isMigrationDetectionKind, isRunnerState, isServiceState, listRunners, pickMigrationCandidateFromInput, readRunnerStatus, readServiceStatus, recordRouteDecision, traceEvent, userSessionPath, validateManualMigratePath };
|