mindwire 0.1.32 → 0.1.34

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.cjs CHANGED
@@ -555,6 +555,172 @@ var Run = class _Run {
555
555
  }
556
556
  };
557
557
 
558
+ // src/project-sync.ts
559
+ init_errors();
560
+ var ProjectSyncError = class extends exports.MindwireError {
561
+ constructor(operation) {
562
+ super(operation.error ?? (operation.status === "conflicts" ? "Changes conflict. Both workspace copies and their checkpoints are preserved." : `Project synchronization ${operation.status}.`));
563
+ this.operation = operation;
564
+ this.name = "ProjectSyncError";
565
+ }
566
+ operation;
567
+ };
568
+ var active = (o) => ["queued", "exporting", "preparing", "applying"].includes(o.status);
569
+ var prefix = "/workspace/sync";
570
+ function delay(ms, signal) {
571
+ signal?.throwIfAborted();
572
+ return new Promise((resolve, reject) => {
573
+ const done = () => {
574
+ signal?.removeEventListener("abort", abort);
575
+ resolve();
576
+ };
577
+ const timer = setTimeout(done, ms);
578
+ const abort = () => {
579
+ clearTimeout(timer);
580
+ signal?.removeEventListener("abort", abort);
581
+ reject(signal?.reason);
582
+ };
583
+ signal?.addEventListener("abort", abort, { once: true });
584
+ });
585
+ }
586
+ var ProjectSyncApi = class {
587
+ constructor(mw) {
588
+ this.mw = mw;
589
+ }
590
+ mw;
591
+ flights = /* @__PURE__ */ new Map();
592
+ export(request, options = {}) {
593
+ return this.mw.http.request("POST", `${prefix}/exports`, { body: request, ...options });
594
+ }
595
+ import(request, options = {}) {
596
+ return this.mw.http.request("POST", `${prefix}/imports`, { body: request, ...options });
597
+ }
598
+ async list(options = {}) {
599
+ const response = await this.mw.http.request("GET", `${prefix}/operations`, options);
600
+ return response.operations;
601
+ }
602
+ get(id, options = {}) {
603
+ return this.mw.http.request("GET", `${prefix}/operations/${encodeURIComponent(id)}`, options);
604
+ }
605
+ retry(id, options = {}) {
606
+ return this.mw.http.request("POST", `${prefix}/operations/${encodeURIComponent(id)}/retry`, options);
607
+ }
608
+ cancel(id) {
609
+ return this.mw.http.request("POST", `${prefix}/operations/${encodeURIComponent(id)}/cancel`);
610
+ }
611
+ checkpoint(id, options = {}) {
612
+ return this.mw.http.request("GET", `${prefix}/checkpoints/${encodeURIComponent(id)}`, options);
613
+ }
614
+ acceptCheckpoint(checkpoint, options = {}) {
615
+ return this.mw.http.request("PUT", `${prefix}/checkpoints/${encodeURIComponent(checkpoint.id)}`, { body: checkpoint, ...options });
616
+ }
617
+ objects(id, cursor = 0, options = {}) {
618
+ return this.mw.http.request("GET", `${prefix}/checkpoints/${encodeURIComponent(id)}/objects`, { query: { cursor }, ...options });
619
+ }
620
+ async missing(objects, options = {}) {
621
+ const response = await this.mw.http.request("POST", `${prefix}/objects/missing`, { body: { objects }, ...options });
622
+ return response.objects;
623
+ }
624
+ /** Base64 chunk, at most 256 KiB before encoding. */
625
+ async getObject(id, options = {}) {
626
+ const response = await this.mw.http.request("GET", `${prefix}/objects/${encodeURIComponent(id)}`, options);
627
+ return response.data;
628
+ }
629
+ async putObject(id, data, options = {}) {
630
+ await this.mw.http.request("PUT", `${prefix}/objects/${encodeURIComponent(id)}`, { body: { data }, ...options });
631
+ }
632
+ async wait(id, options = {}) {
633
+ for (; ; ) {
634
+ const o = await this.get(id, { signal: options.signal });
635
+ options.onUpdate?.(o);
636
+ if (!active(o)) return o;
637
+ await delay(500, options.signal);
638
+ }
639
+ }
640
+ /** Repeating this relay skips acknowledged checkpoints and verified chunks.
641
+ * Aborting detaches the relay; daemon export/apply operations remain durable.
642
+ */
643
+ async relayCheckpoint(target, id, options = {}) {
644
+ const visited = /* @__PURE__ */ new Set();
645
+ const visit = async (checkpointId) => {
646
+ options.signal?.throwIfAborted();
647
+ if (visited.has(checkpointId)) return;
648
+ visited.add(checkpointId);
649
+ try {
650
+ await target.checkpoint(checkpointId, options);
651
+ return;
652
+ } catch (error) {
653
+ if (!(error instanceof exports.ApiError) || error.status !== 404) throw error;
654
+ }
655
+ const checkpoint = await this.checkpoint(checkpointId, options);
656
+ for (const parent of checkpoint.parents) await visit(parent);
657
+ let cursor = 0;
658
+ do {
659
+ const page = await this.objects(checkpointId, cursor, options);
660
+ const missing = await target.missing(page.objects, options);
661
+ for (const object of missing) {
662
+ const data = await this.getObject(object, options);
663
+ await target.putObject(object, data, options);
664
+ const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
665
+ options.onBytes?.(Math.floor(data.length * 3 / 4) - padding);
666
+ }
667
+ cursor = page.next ?? 0;
668
+ } while (cursor);
669
+ await target.acceptCheckpoint(checkpoint, options);
670
+ };
671
+ await visit(id);
672
+ }
673
+ /** Safe X → Y and Y → X. Persist request.id until acknowledged. A conflict
674
+ * throws ProjectSyncError carrying the complete conflict/recovery operation.
675
+ */
676
+ switchTo(target, request, options = {}) {
677
+ const intent = JSON.stringify(request);
678
+ const old = this.flights.get(request.id);
679
+ if (old) {
680
+ if (old.intent !== intent || old.target !== target) return Promise.reject(new exports.MindwireError("This sync ID already belongs to a different request or destination."));
681
+ return old.result;
682
+ }
683
+ const work = async () => {
684
+ if (target === this || target.mw.http === this.mw.http) throw new exports.MindwireError("Choose another workspace for this project.");
685
+ let accepted;
686
+ try {
687
+ accepted = await target.get(`${request.id}-import`, options);
688
+ } catch (error) {
689
+ if (!(error instanceof exports.ApiError) || error.status !== 404) throw error;
690
+ }
691
+ if (accepted) {
692
+ if ((accepted.path ?? "") !== (request.path ?? "")) throw new exports.MindwireError("This sync ID belongs to a different destination folder.");
693
+ const operation = await target.wait(accepted.id, {
694
+ signal: options.signal,
695
+ onUpdate: (operation2) => options.onProgress?.({ phase: "apply", transferredBytes: 0, operation: operation2 })
696
+ });
697
+ if (operation.status !== "succeeded") throw new ProjectSyncError(operation);
698
+ return operation;
699
+ }
700
+ const health = await Promise.all([this.mw.health(), target.mw.health()]);
701
+ if (health.some((h) => (h.projectSyncVersion ?? 0) < 1)) throw new exports.MindwireError("Update Mindwire on both workspaces to switch this project.");
702
+ let bytes = 0;
703
+ const emit2 = (phase, operation) => options.onProgress?.({ phase, transferredBytes: bytes, operation });
704
+ emit2("checkpoint");
705
+ let o = await this.export({ id: `${request.id}-export`, projectId: request.projectId }, options);
706
+ o = await this.wait(o.id, { signal: options.signal, onUpdate: (value) => emit2("checkpoint", value) });
707
+ if (o.status !== "succeeded" || !o.resultCheckpointId) throw new ProjectSyncError(o);
708
+ emit2("transfer");
709
+ await this.relayCheckpoint(target, o.resultCheckpointId, { signal: options.signal, onBytes: (n) => {
710
+ bytes += n;
711
+ emit2("transfer");
712
+ } });
713
+ o = await target.import({ id: `${request.id}-import`, checkpointId: o.resultCheckpointId, path: request.path }, options);
714
+ o = await target.wait(o.id, { signal: options.signal, onUpdate: (value) => emit2("apply", value) });
715
+ if (o.status !== "succeeded") throw new ProjectSyncError(o);
716
+ return o;
717
+ };
718
+ const result = work().finally(() => this.flights.delete(request.id));
719
+ this.flights.set(request.id, { intent, target, result });
720
+ return result;
721
+ }
722
+ };
723
+
558
724
  // src/workspace.ts
559
725
  var ProjectOperationsApi = class {
560
726
  constructor(mw) {
@@ -626,6 +792,7 @@ var WorkspaceCollection = class {
626
792
  var WorkspaceApi = class {
627
793
  constructor(mw) {
628
794
  this.mw = mw;
795
+ this.sync = new ProjectSyncApi(mw);
629
796
  this.git = new GitAccessApi(mw);
630
797
  this.operations = new ProjectOperationsApi(mw);
631
798
  this.agents = new WorkspaceCollection(mw, "agents");
@@ -633,6 +800,7 @@ var WorkspaceApi = class {
633
800
  this.chats = new WorkspaceCollection(mw, "chats");
634
801
  }
635
802
  mw;
803
+ sync;
636
804
  git;
637
805
  operations;
638
806
  agents;
@@ -971,7 +1139,7 @@ init_errors();
971
1139
  init_errors();
972
1140
 
973
1141
  // src/version.ts
974
- var SDK_VERSION = "0.1.32" ;
1142
+ var SDK_VERSION = "0.1.34" ;
975
1143
  function versionAtLeast(actual, desired) {
976
1144
  if (actual === desired) return true;
977
1145
  if (!actual || !/^\d+\.\d+\.\d+$/.test(actual) || !/^\d+\.\d+\.\d+$/.test(desired)) return false;
@@ -2991,6 +3159,8 @@ exports.McpApi = McpApi;
2991
3159
  exports.Mindwire = Mindwire;
2992
3160
  exports.NotifyApi = NotifyApi;
2993
3161
  exports.ProjectOperationsApi = ProjectOperationsApi;
3162
+ exports.ProjectSyncApi = ProjectSyncApi;
3163
+ exports.ProjectSyncError = ProjectSyncError;
2994
3164
  exports.PromptsApi = PromptsApi;
2995
3165
  exports.ProvidersApi = ProvidersApi;
2996
3166
  exports.Run = Run;