@spotpatch/dev-server 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
35
35
  applyIntegrationPlan: () => applyIntegrationPlan,
36
36
  createAgentJobManager: () => createAgentJobManager,
37
+ createExternalHandoffService: () => createExternalHandoffService,
37
38
  createIntegrationFileChange: () => createIntegrationFileChange,
38
39
  createRuntimeAiConfig: () => createRuntimeAiConfig,
39
40
  createRuntimeDataFlowConfig: () => createRuntimeDataFlowConfig,
@@ -521,6 +522,1064 @@ function createAgentJobManager(options) {
521
522
  });
522
523
  }
523
524
 
525
+ // src/external-handoff/service.ts
526
+ var import_shared7 = require("@spotpatch/shared");
527
+ var import_external_agent_node3 = require("@spotpatch/shared/external-agent-node");
528
+
529
+ // src/external-handoff/active-registry.ts
530
+ var import_node_crypto2 = require("crypto");
531
+ var import_shared2 = require("@spotpatch/shared");
532
+
533
+ // src/external-handoff/clock.ts
534
+ var import_node_perf_hooks = require("perf_hooks");
535
+ var SYSTEM_EXTERNAL_HANDOFF_CLOCK = Object.freeze({
536
+ monotonicNow: () => import_node_perf_hooks.performance.now(),
537
+ wallNow: () => Date.now()
538
+ });
539
+
540
+ // src/external-handoff/active-registry.ts
541
+ var ALLOWED_TRANSITIONS = Object.freeze({
542
+ queued: ["dispatching", "failed"],
543
+ dispatching: ["dispatched", "working", "failed", "delivery-unknown"],
544
+ dispatched: ["working", "failed", "delivery-unknown"],
545
+ working: ["completed", "failed", "delivery-unknown"],
546
+ completed: [],
547
+ failed: [],
548
+ "delivery-unknown": []
549
+ });
550
+ var TERMINAL_PHASES = /* @__PURE__ */ new Set([
551
+ "completed",
552
+ "failed",
553
+ "delivery-unknown"
554
+ ]);
555
+ function defaultRandomId() {
556
+ return (0, import_node_crypto2.randomBytes)(32).toString("base64url");
557
+ }
558
+ function requirePresent(value) {
559
+ if (value === null) throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.INTERNAL_ERROR);
560
+ return value;
561
+ }
562
+ function createActiveAdapterRegistry(options = {}) {
563
+ const clock = options.clock ?? SYSTEM_EXTERNAL_HANDOFF_CLOCK;
564
+ const randomId = options.randomId ?? defaultRandomId;
565
+ let blocked;
566
+ let closed = false;
567
+ let dispatch;
568
+ let lastReleasedToken;
569
+ let lease;
570
+ const nowIso = () => new Date(clock.wallNow()).toISOString();
571
+ const requireOpen = () => {
572
+ if (closed) throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.SESSION_CLOSED);
573
+ };
574
+ const dispatchSummary = () => dispatch === void 0 ? null : Object.freeze({
575
+ adapterKind: dispatch.adapterKind,
576
+ revision: dispatch.revision,
577
+ phase: dispatch.phase,
578
+ updatedAt: dispatch.updatedAt
579
+ });
580
+ const activeSummary = () => {
581
+ if (blocked !== void 0) {
582
+ return Object.freeze({
583
+ kind: blocked.adapterKind,
584
+ state: "blocked",
585
+ canDispatch: false,
586
+ connectedAt: blocked.connectedAt,
587
+ updatedAt: blocked.updatedAt
588
+ });
589
+ }
590
+ if (lease === void 0) return null;
591
+ const busy = dispatch !== void 0 && !TERMINAL_PHASES.has(dispatch.phase);
592
+ return Object.freeze({
593
+ kind: lease.adapterKind,
594
+ state: busy ? "busy" : "ready",
595
+ canDispatch: !busy,
596
+ connectedAt: lease.connectedAt,
597
+ updatedAt: lease.updatedAt
598
+ });
599
+ };
600
+ const state = (cursor) => Object.freeze({
601
+ activeAdapter: activeSummary(),
602
+ dispatch: cursor === void 0 || dispatch?.cursor === cursor ? dispatchSummary() : null
603
+ });
604
+ const enterUnknown = (activeLease) => {
605
+ if (dispatch === void 0) return;
606
+ const updatedAt = nowIso();
607
+ dispatch.phase = "delivery-unknown";
608
+ dispatch.updatedAt = updatedAt;
609
+ blocked = Object.freeze({
610
+ adapterKind: activeLease.adapterKind,
611
+ connectedAt: activeLease.connectedAt,
612
+ updatedAt
613
+ });
614
+ };
615
+ const endLease = (activeLease) => {
616
+ if (dispatch?.phase === "queued") {
617
+ dispatch.phase = "failed";
618
+ dispatch.updatedAt = nowIso();
619
+ } else if (dispatch?.phase === "dispatching" || dispatch?.phase === "dispatched" || dispatch?.phase === "working") {
620
+ enterUnknown(activeLease);
621
+ }
622
+ lastReleasedToken = activeLease.token;
623
+ lease = void 0;
624
+ };
625
+ const sweep = () => {
626
+ if (lease === void 0) return;
627
+ const monotonicNow = clock.monotonicNow();
628
+ if (monotonicNow >= lease.expiresAtMonotonic || dispatch !== void 0 && !TERMINAL_PHASES.has(dispatch.phase) && monotonicNow >= dispatch.deadlineMonotonic) {
629
+ endLease(lease);
630
+ }
631
+ };
632
+ const assertPublishable = () => {
633
+ requireOpen();
634
+ sweep();
635
+ if (blocked !== void 0 || lease !== void 0 && dispatch !== void 0 && !TERMINAL_PHASES.has(dispatch.phase)) {
636
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.EXTERNAL_AGENT_BUSY);
637
+ }
638
+ };
639
+ const requireLease = (leaseToken) => {
640
+ requireOpen();
641
+ sweep();
642
+ if (lease?.token !== leaseToken) {
643
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID);
644
+ }
645
+ return lease;
646
+ };
647
+ return Object.freeze({
648
+ assertPublishable,
649
+ claim(adapterKind, connectorInstanceId, baselineCursor) {
650
+ requireOpen();
651
+ sweep();
652
+ if (blocked !== void 0) {
653
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.EXTERNAL_AGENT_BUSY);
654
+ }
655
+ if (lease !== void 0) {
656
+ if (lease.adapterKind === adapterKind && lease.connectorInstanceId === connectorInstanceId) {
657
+ lease.expiresAtMonotonic = clock.monotonicNow() + import_shared2.EXTERNAL_HANDOFF_LIMITS.activeLeaseDurationMs;
658
+ lease.updatedAt = nowIso();
659
+ return Object.freeze({
660
+ leaseToken: lease.token,
661
+ heartbeatIntervalMs: import_shared2.EXTERNAL_HANDOFF_LIMITS.activeHeartbeatIntervalMs,
662
+ baselineCursor: lease.baselineCursor,
663
+ activeAdapter: requirePresent(activeSummary())
664
+ });
665
+ }
666
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT);
667
+ }
668
+ const timestamp = nowIso();
669
+ lease = {
670
+ adapterKind,
671
+ baselineCursor,
672
+ connectedAt: timestamp,
673
+ connectorInstanceId,
674
+ token: randomId(),
675
+ expiresAtMonotonic: clock.monotonicNow() + import_shared2.EXTERNAL_HANDOFF_LIMITS.activeLeaseDurationMs,
676
+ updatedAt: timestamp
677
+ };
678
+ lastReleasedToken = void 0;
679
+ return Object.freeze({
680
+ leaseToken: lease.token,
681
+ heartbeatIntervalMs: import_shared2.EXTERNAL_HANDOFF_LIMITS.activeHeartbeatIntervalMs,
682
+ baselineCursor,
683
+ activeAdapter: requirePresent(activeSummary())
684
+ });
685
+ },
686
+ heartbeat(leaseToken) {
687
+ const activeLease = requireLease(leaseToken);
688
+ activeLease.expiresAtMonotonic = clock.monotonicNow() + import_shared2.EXTERNAL_HANDOFF_LIMITS.activeLeaseDurationMs;
689
+ activeLease.updatedAt = nowIso();
690
+ return state();
691
+ },
692
+ report(leaseToken, cursor, phase) {
693
+ const activeLease = requireLease(leaseToken);
694
+ if (dispatch?.cursor !== cursor || dispatch.adapterKind !== activeLease.adapterKind) {
695
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_DISPATCH_INVALID);
696
+ }
697
+ if (dispatch.phase === phase) return state(cursor);
698
+ const allowed = ALLOWED_TRANSITIONS[dispatch.phase];
699
+ if (!allowed.includes(phase)) {
700
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_DISPATCH_INVALID);
701
+ }
702
+ const updatedAt = nowIso();
703
+ dispatch.phase = phase;
704
+ dispatch.updatedAt = updatedAt;
705
+ activeLease.updatedAt = updatedAt;
706
+ if (phase === "delivery-unknown") {
707
+ blocked = Object.freeze({
708
+ adapterKind: activeLease.adapterKind,
709
+ connectedAt: activeLease.connectedAt,
710
+ updatedAt
711
+ });
712
+ lastReleasedToken = activeLease.token;
713
+ lease = void 0;
714
+ }
715
+ return state(cursor);
716
+ },
717
+ release(leaseToken) {
718
+ requireOpen();
719
+ sweep();
720
+ if (lease === void 0 && lastReleasedToken === leaseToken) return state();
721
+ const activeLease = lease;
722
+ if (activeLease?.token !== leaseToken) {
723
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID);
724
+ }
725
+ endLease(activeLease);
726
+ return state();
727
+ },
728
+ reserve(cursor, revision) {
729
+ assertPublishable();
730
+ if (lease === void 0) return Object.freeze({ mode: "inbox" });
731
+ const updatedAt = nowIso();
732
+ dispatch = {
733
+ adapterKind: lease.adapterKind,
734
+ cursor,
735
+ deadlineMonotonic: clock.monotonicNow() + import_shared2.EXTERNAL_HANDOFF_LIMITS.activeDispatchTimeoutMs,
736
+ revision,
737
+ phase: "queued",
738
+ updatedAt
739
+ };
740
+ lease.updatedAt = updatedAt;
741
+ return Object.freeze({
742
+ mode: "active",
743
+ adapter: requirePresent(activeSummary()),
744
+ dispatch: requirePresent(dispatchSummary())
745
+ });
746
+ },
747
+ resolveDelivery(cursor) {
748
+ requireOpen();
749
+ sweep();
750
+ const activeDispatch = dispatch;
751
+ if (blocked === void 0 || activeDispatch?.cursor !== cursor || activeDispatch.phase !== "delivery-unknown") {
752
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_DISPATCH_INVALID);
753
+ }
754
+ blocked = void 0;
755
+ return state(cursor);
756
+ },
757
+ snapshot(cursor) {
758
+ requireOpen();
759
+ sweep();
760
+ return state(cursor);
761
+ },
762
+ close() {
763
+ if (closed) return;
764
+ closed = true;
765
+ blocked = void 0;
766
+ dispatch = void 0;
767
+ lease = void 0;
768
+ lastReleasedToken = void 0;
769
+ }
770
+ });
771
+ }
772
+
773
+ // src/external-handoff/broker.ts
774
+ var import_node_crypto3 = require("crypto");
775
+ var import_node_http = require("http");
776
+ var import_shared4 = require("@spotpatch/shared");
777
+ var import_external_agent_node = require("@spotpatch/shared/external-agent-node");
778
+
779
+ // src/server/request-body.ts
780
+ var import_shared3 = require("@spotpatch/shared");
781
+
782
+ // src/server/constants.ts
783
+ var MAX_REQUEST_BODY_BYTES = 32 * 1024;
784
+ var MAX_AGENT_REQUEST_BODY_BYTES = 256 * 1024;
785
+ var MAX_SOURCE_FILE_BYTES = 1024 * 1024;
786
+
787
+ // src/server/request-body.ts
788
+ function isJsonContentType(value) {
789
+ return value?.split(";", 1)[0]?.trim().toLowerCase() === "application/json";
790
+ }
791
+ async function readJsonRequestBody(request, maximumBytes = MAX_REQUEST_BODY_BYTES) {
792
+ if (!isJsonContentType(request.headers["content-type"])) {
793
+ throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.INVALID_REQUEST);
794
+ }
795
+ const declaredLength = Number(request.headers["content-length"]);
796
+ if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {
797
+ throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.INVALID_REQUEST);
798
+ }
799
+ const chunks = [];
800
+ let byteLength = 0;
801
+ let exceededLimit = false;
802
+ for await (const rawChunk of request) {
803
+ const chunk = rawChunk;
804
+ if (typeof chunk !== "string" && !(chunk instanceof Uint8Array)) {
805
+ throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.INVALID_REQUEST);
806
+ }
807
+ const buffer = Buffer.from(chunk);
808
+ byteLength += buffer.byteLength;
809
+ if (byteLength > maximumBytes) {
810
+ exceededLimit = true;
811
+ continue;
812
+ }
813
+ chunks.push(buffer);
814
+ }
815
+ if (exceededLimit || byteLength === 0) {
816
+ throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.INVALID_REQUEST);
817
+ }
818
+ try {
819
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
820
+ } catch (error) {
821
+ throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.INVALID_REQUEST, void 0, {
822
+ cause: error
823
+ });
824
+ }
825
+ }
826
+
827
+ // src/external-handoff/broker.ts
828
+ function singleHeader(request, name) {
829
+ const value = request.headers[name.toLowerCase()];
830
+ return Array.isArray(value) ? void 0 : value;
831
+ }
832
+ function tokensMatch(actual, expected) {
833
+ if (actual === void 0) return false;
834
+ const actualBytes = Buffer.from(actual);
835
+ const expectedBytes = Buffer.from(expected);
836
+ return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto3.timingSafeEqual)(actualBytes, expectedBytes);
837
+ }
838
+ function writeJson(response, status, payload) {
839
+ response.statusCode = status;
840
+ response.setHeader("Cache-Control", "no-store");
841
+ response.setHeader("Connection", "close");
842
+ response.setHeader("Content-Type", "application/json; charset=utf-8");
843
+ response.setHeader("X-Content-Type-Options", "nosniff");
844
+ response.end(JSON.stringify(payload));
845
+ }
846
+ function statusForError(code) {
847
+ if (code === import_shared4.ERROR_CODES.BRIDGE_UNAUTHORIZED) return 401;
848
+ if (code === import_shared4.ERROR_CODES.HANDOFF_NOT_FOUND) return 404;
849
+ if (code === import_shared4.ERROR_CODES.HANDOFF_EXPIRED || code === import_shared4.ERROR_CODES.SESSION_CLOSED) {
850
+ return 410;
851
+ }
852
+ if (code === import_shared4.ERROR_CODES.BRIDGE_BUSY) return 429;
853
+ if (code === import_shared4.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID) return 401;
854
+ if (code === import_shared4.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE) return 413;
855
+ if (code === import_shared4.ERROR_CODES.HANDOFF_CURSOR_INVALID || code === import_shared4.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH || code === import_shared4.ERROR_CODES.EXTERNAL_AGENT_BUSY || code === import_shared4.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT || code === import_shared4.ERROR_CODES.ACTIVE_DISPATCH_INVALID) {
856
+ return 409;
857
+ }
858
+ if (code === import_shared4.ERROR_CODES.INVALID_REQUEST) return 400;
859
+ return 500;
860
+ }
861
+ function normalizeError2(error) {
862
+ return error instanceof import_shared4.SpotPatchError ? error : new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
863
+ }
864
+ function assertAuthorized(request, expectedHost, bridgeToken) {
865
+ if (request.socket.remoteAddress !== "127.0.0.1" || singleHeader(request, "host") !== expectedHost || !tokensMatch(singleHeader(request, import_external_agent_node.SPOTPATCH_BRIDGE_TOKEN_HEADER), bridgeToken)) {
866
+ throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.BRIDGE_UNAUTHORIZED);
867
+ }
868
+ }
869
+ async function closeServer(server, sockets) {
870
+ await new Promise((resolve) => {
871
+ server.close(() => {
872
+ resolve();
873
+ });
874
+ for (const socket of sockets) {
875
+ socket.destroy();
876
+ }
877
+ });
878
+ }
879
+ async function createExternalHandoffBroker(options) {
880
+ const bridgeToken = (0, import_node_crypto3.randomBytes)(32).toString("base64url");
881
+ const sockets = /* @__PURE__ */ new Set();
882
+ let expectedHost = "";
883
+ const server = (0, import_node_http.createServer)(
884
+ { maxHeaderSize: import_shared4.EXTERNAL_HANDOFF_LIMITS.maximumBrokerHeaderBytes },
885
+ (request, response) => {
886
+ const handle = async () => {
887
+ assertAuthorized(request, expectedHost, bridgeToken);
888
+ if (request.method !== "POST") {
889
+ throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
890
+ }
891
+ const body = await readJsonRequestBody(
892
+ request,
893
+ import_shared4.EXTERNAL_HANDOFF_LIMITS.maximumBrokerRequestBytes
894
+ );
895
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.status) {
896
+ const parsed = import_external_agent_node.bridgeStatusRequestSchema.safeParse(body);
897
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
898
+ let current = null;
899
+ try {
900
+ current = options.store.status();
901
+ } catch (error) {
902
+ if (!(error instanceof import_shared4.SpotPatchError) || error.code !== import_shared4.ERROR_CODES.HANDOFF_NOT_FOUND) {
903
+ throw error;
904
+ }
905
+ }
906
+ writeJson(response, 200, {
907
+ ok: true,
908
+ data: Object.freeze({
909
+ brokerProtocolVersion: import_shared4.EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION,
910
+ projectKey: options.projectKey,
911
+ sessionId: options.sessionId,
912
+ framework: options.framework,
913
+ current
914
+ })
915
+ });
916
+ return;
917
+ }
918
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.current) {
919
+ const parsed = import_external_agent_node.bridgeCurrentRequestSchema.safeParse(body);
920
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
921
+ const snapshot2 = options.store.current(parsed.data.cursor);
922
+ writeJson(response, 200, {
923
+ ok: true,
924
+ data: Object.freeze({ outcome: "handoff", snapshot: snapshot2 })
925
+ });
926
+ return;
927
+ }
928
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.ack) {
929
+ const parsed = import_external_agent_node.bridgeAckRequestSchema.safeParse(body);
930
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
931
+ const summary = options.store.ack(
932
+ parsed.data.cursor,
933
+ parsed.data.connectorInstanceId
934
+ );
935
+ writeJson(response, 200, {
936
+ ok: true,
937
+ data: Object.freeze({ summary })
938
+ });
939
+ return;
940
+ }
941
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.wait) {
942
+ const parsed = import_external_agent_node.bridgeWaitRequestSchema.safeParse(body);
943
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
944
+ const controller = new AbortController();
945
+ const abort = () => {
946
+ if (!response.writableEnded) controller.abort("bridge-client-closed");
947
+ };
948
+ response.once("close", abort);
949
+ try {
950
+ const data = await options.store.wait(
951
+ parsed.data.afterCursor,
952
+ parsed.data.timeoutMs,
953
+ controller.signal
954
+ );
955
+ writeJson(response, 200, { ok: true, data });
956
+ } finally {
957
+ response.removeListener("close", abort);
958
+ }
959
+ return;
960
+ }
961
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.activeClaim) {
962
+ const parsed = import_external_agent_node.bridgeActiveClaimRequestSchema.safeParse(body);
963
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
964
+ const data = options.activeRegistry.claim(
965
+ parsed.data.adapterKind,
966
+ parsed.data.connectorInstanceId,
967
+ options.store.currentCursor()
968
+ );
969
+ writeJson(response, 200, { ok: true, data });
970
+ return;
971
+ }
972
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.activeHeartbeat) {
973
+ const parsed = import_external_agent_node.bridgeActiveHeartbeatRequestSchema.safeParse(body);
974
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
975
+ const data = options.activeRegistry.heartbeat(parsed.data.leaseToken);
976
+ writeJson(response, 200, { ok: true, data });
977
+ return;
978
+ }
979
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.activeReport) {
980
+ const parsed = import_external_agent_node.bridgeActiveReportRequestSchema.safeParse(body);
981
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
982
+ const data = options.activeRegistry.report(
983
+ parsed.data.leaseToken,
984
+ parsed.data.cursor,
985
+ parsed.data.phase
986
+ );
987
+ writeJson(response, 200, { ok: true, data });
988
+ return;
989
+ }
990
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.activeRelease) {
991
+ const parsed = import_external_agent_node.bridgeActiveReleaseRequestSchema.safeParse(body);
992
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
993
+ const data = options.activeRegistry.release(parsed.data.leaseToken);
994
+ writeJson(response, 200, { ok: true, data });
995
+ return;
996
+ }
997
+ throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
998
+ };
999
+ void handle().catch((error) => {
1000
+ if (response.writableEnded || response.destroyed) return;
1001
+ const normalized = normalizeError2(error);
1002
+ if (normalized.code === import_shared4.ERROR_CODES.BRIDGE_BUSY) {
1003
+ response.setHeader("Retry-After", "1");
1004
+ }
1005
+ writeJson(response, statusForError(normalized.code), {
1006
+ ok: false,
1007
+ error: {
1008
+ code: normalized.code,
1009
+ message: "The local SpotPatch bridge request failed."
1010
+ }
1011
+ });
1012
+ });
1013
+ }
1014
+ );
1015
+ server.maxConnections = import_shared4.EXTERNAL_HANDOFF_LIMITS.maximumBrokerSockets;
1016
+ server.headersTimeout = 5e3;
1017
+ server.requestTimeout = import_shared4.EXTERNAL_HANDOFF_LIMITS.maximumWaitMs + 5e3;
1018
+ server.keepAliveTimeout = 1;
1019
+ server.on("connection", (socket) => {
1020
+ if (sockets.size >= import_shared4.EXTERNAL_HANDOFF_LIMITS.maximumBrokerSockets) {
1021
+ socket.destroy();
1022
+ return;
1023
+ }
1024
+ sockets.add(socket);
1025
+ socket.once("close", () => sockets.delete(socket));
1026
+ });
1027
+ await new Promise((resolve, reject) => {
1028
+ server.once("error", reject);
1029
+ server.listen(0, "127.0.0.1", resolve);
1030
+ });
1031
+ const address = server.address();
1032
+ if (address === null || typeof address === "string" || address.address !== "127.0.0.1") {
1033
+ await closeServer(server, sockets);
1034
+ throw new Error("SpotPatch external Agent broker did not bind IPv4 loopback.");
1035
+ }
1036
+ expectedHost = `127.0.0.1:${String(address.port)}`;
1037
+ let closed = false;
1038
+ let ready = true;
1039
+ server.removeAllListeners("error");
1040
+ server.on("error", () => {
1041
+ ready = false;
1042
+ for (const socket of sockets) socket.destroy();
1043
+ });
1044
+ return Object.freeze({
1045
+ bridgeToken,
1046
+ endpoint: `http://${expectedHost}`,
1047
+ isReady: () => ready && !closed,
1048
+ async close() {
1049
+ if (closed) return;
1050
+ closed = true;
1051
+ ready = false;
1052
+ await closeServer(server, sockets);
1053
+ }
1054
+ });
1055
+ }
1056
+
1057
+ // src/external-handoff/discovery.ts
1058
+ var import_node_crypto4 = require("crypto");
1059
+ var import_promises = require("fs/promises");
1060
+ var import_node_path = __toESM(require("path"), 1);
1061
+ var import_shared5 = require("@spotpatch/shared");
1062
+ var import_external_agent_node2 = require("@spotpatch/shared/external-agent-node");
1063
+ async function syncDirectory(directory) {
1064
+ const handle = await (0, import_promises.open)(directory, "r");
1065
+ try {
1066
+ await handle.sync();
1067
+ } catch (error) {
1068
+ const code = error.code;
1069
+ if (code !== "EINVAL" && code !== "ENOTSUP") {
1070
+ throw error;
1071
+ }
1072
+ } finally {
1073
+ await handle.close();
1074
+ }
1075
+ }
1076
+ async function publishExternalHandoffDescriptor(options) {
1077
+ const directory = await (0, import_external_agent_node2.resolveExternalHandoffRuntimeDirectory)(true);
1078
+ const descriptor = import_external_agent_node2.externalHandoffDescriptorSchema.parse({
1079
+ schemaVersion: 1,
1080
+ brokerProtocolVersion: import_shared5.EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION,
1081
+ projectKey: await (0, import_external_agent_node2.computeExternalHandoffProjectKey)(options.root),
1082
+ sessionId: options.sessionId,
1083
+ framework: options.framework,
1084
+ endpoint: options.endpoint,
1085
+ bridgeToken: options.bridgeToken,
1086
+ pid: process.pid,
1087
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1088
+ });
1089
+ const serialized = JSON.stringify(descriptor);
1090
+ if (Buffer.byteLength(serialized, "utf8") > import_shared5.EXTERNAL_HANDOFF_LIMITS.maximumDescriptorBytes) {
1091
+ throw new RangeError("SpotPatch external Agent descriptor exceeds its limit.");
1092
+ }
1093
+ const destination = import_node_path.default.join(directory, `${descriptor.sessionId}.json`);
1094
+ const temporary = import_node_path.default.join(
1095
+ directory,
1096
+ `.${descriptor.sessionId}.${(0, import_node_crypto4.randomBytes)(8).toString("hex")}.tmp`
1097
+ );
1098
+ let temporaryExists = false;
1099
+ let published = false;
1100
+ let descriptorIdentity = Object.freeze({
1101
+ device: -1,
1102
+ inode: -1
1103
+ });
1104
+ try {
1105
+ const handle = await (0, import_promises.open)(temporary, "wx", 384);
1106
+ temporaryExists = true;
1107
+ try {
1108
+ await handle.writeFile(serialized, "utf8");
1109
+ await handle.sync();
1110
+ } finally {
1111
+ await handle.close();
1112
+ }
1113
+ await (0, import_promises.rename)(temporary, destination);
1114
+ temporaryExists = false;
1115
+ published = true;
1116
+ const status = await (0, import_promises.lstat)(destination);
1117
+ const uid = process.getuid?.();
1118
+ if (!status.isFile() || status.isSymbolicLink() || uid === void 0 || status.uid !== uid || (status.mode & 63) !== 0) {
1119
+ throw new Error("SpotPatch external Agent descriptor is not private.");
1120
+ }
1121
+ descriptorIdentity = Object.freeze({ device: status.dev, inode: status.ino });
1122
+ await syncDirectory(directory);
1123
+ } catch (error) {
1124
+ if (temporaryExists) {
1125
+ await (0, import_promises.unlink)(temporary).catch(() => void 0);
1126
+ }
1127
+ if (published) {
1128
+ await (0, import_promises.unlink)(destination).catch(() => void 0);
1129
+ }
1130
+ throw error;
1131
+ }
1132
+ let closed = false;
1133
+ return Object.freeze({
1134
+ descriptor,
1135
+ async close() {
1136
+ if (closed) return;
1137
+ closed = true;
1138
+ await (0, import_promises.lstat)(destination).then(async (status) => {
1139
+ if (status.dev === descriptorIdentity.device && status.ino === descriptorIdentity.inode) {
1140
+ await (0, import_promises.unlink)(destination);
1141
+ }
1142
+ }).catch((error) => {
1143
+ if (error.code !== "ENOENT") {
1144
+ throw error;
1145
+ }
1146
+ });
1147
+ await syncDirectory(directory);
1148
+ }
1149
+ });
1150
+ }
1151
+
1152
+ // src/external-handoff/fingerprint.ts
1153
+ var import_node_crypto5 = require("crypto");
1154
+ function canonicalJson(value) {
1155
+ if (value === null || typeof value === "boolean" || typeof value === "string") {
1156
+ return JSON.stringify(value);
1157
+ }
1158
+ if (typeof value === "number") {
1159
+ if (!Number.isFinite(value)) throw new TypeError("Non-finite JSON number.");
1160
+ return JSON.stringify(value);
1161
+ }
1162
+ if (Array.isArray(value)) {
1163
+ return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
1164
+ }
1165
+ if (typeof value === "object") {
1166
+ const record = value;
1167
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
1168
+ }
1169
+ throw new TypeError("Unsupported JSON value.");
1170
+ }
1171
+ function fingerprintExternalHandoffAnnotation(annotation) {
1172
+ return (0, import_node_crypto5.createHash)("sha256").update(canonicalJson(annotation)).digest("hex");
1173
+ }
1174
+
1175
+ // src/external-handoff/store.ts
1176
+ var import_node_crypto6 = require("crypto");
1177
+ var import_shared6 = require("@spotpatch/shared");
1178
+ function defaultRandomId2() {
1179
+ return (0, import_node_crypto6.randomBytes)(24).toString("base64url");
1180
+ }
1181
+ function pageSummary(annotation) {
1182
+ let origin = "[unavailable]";
1183
+ try {
1184
+ const url = new URL(annotation.page.url);
1185
+ origin = url.origin === "null" ? "[unavailable]" : url.origin;
1186
+ } catch {
1187
+ }
1188
+ return Object.freeze({ origin, pathname: annotation.page.pathname });
1189
+ }
1190
+ function summaryOf(current, state) {
1191
+ const snapshot2 = current.snapshot;
1192
+ return Object.freeze({
1193
+ sessionId: snapshot2.session.id,
1194
+ framework: snapshot2.session.framework,
1195
+ revision: snapshot2.revision,
1196
+ cursor: snapshot2.cursor,
1197
+ targetCount: snapshot2.annotation.targets.length,
1198
+ page: pageSummary(snapshot2.annotation),
1199
+ publishedAt: snapshot2.publishedAt,
1200
+ expiresAt: snapshot2.expiresAt,
1201
+ state,
1202
+ pickupCount: current.receipts.size,
1203
+ ...current.pickedUpAt === void 0 ? {} : { pickedUpAt: current.pickedUpAt }
1204
+ });
1205
+ }
1206
+ function replayResult(record) {
1207
+ return Object.freeze({ ...record.result, replayed: true });
1208
+ }
1209
+ function createExternalHandoffStore(options) {
1210
+ const clock = options.clock ?? SYSTEM_EXTERNAL_HANDOFF_CLOCK;
1211
+ const randomId = options.randomId ?? defaultRandomId2;
1212
+ const history = [];
1213
+ const idempotency = /* @__PURE__ */ new Map();
1214
+ const waiters = /* @__PURE__ */ new Set();
1215
+ let closed = false;
1216
+ let current;
1217
+ let revision = 0;
1218
+ const requireOpen = () => {
1219
+ if (closed) throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.SESSION_CLOSED);
1220
+ };
1221
+ const archive = (state) => {
1222
+ if (current === void 0) return;
1223
+ history.unshift(summaryOf(current, state));
1224
+ history.length = Math.min(
1225
+ history.length,
1226
+ import_shared6.EXTERNAL_HANDOFF_LIMITS.maximumHistorySummaries
1227
+ );
1228
+ current = void 0;
1229
+ };
1230
+ const sweep = () => {
1231
+ const monotonicNow = clock.monotonicNow();
1232
+ if (current !== void 0 && monotonicNow >= current.expiresAtMonotonic) {
1233
+ archive("expired");
1234
+ }
1235
+ for (const [requestId, record] of idempotency) {
1236
+ if (monotonicNow >= record.expiresAtMonotonic) idempotency.delete(requestId);
1237
+ }
1238
+ };
1239
+ const knownSummary = (cursor) => {
1240
+ sweep();
1241
+ if (current?.snapshot.cursor === cursor) {
1242
+ return summaryOf(current, "available");
1243
+ }
1244
+ return history.find((summary) => summary.cursor === cursor);
1245
+ };
1246
+ const readCurrent = (cursor) => {
1247
+ requireOpen();
1248
+ sweep();
1249
+ if (current === void 0) {
1250
+ const prior = cursor === void 0 ? void 0 : knownSummary(cursor);
1251
+ throw new import_shared6.SpotPatchError(
1252
+ prior?.state === "expired" ? import_shared6.ERROR_CODES.HANDOFF_EXPIRED : cursor === void 0 ? import_shared6.ERROR_CODES.HANDOFF_NOT_FOUND : import_shared6.ERROR_CODES.HANDOFF_CURSOR_INVALID
1253
+ );
1254
+ }
1255
+ if (cursor !== void 0 && cursor !== current.snapshot.cursor) {
1256
+ const prior = knownSummary(cursor);
1257
+ throw new import_shared6.SpotPatchError(
1258
+ prior?.state === "expired" ? import_shared6.ERROR_CODES.HANDOFF_EXPIRED : import_shared6.ERROR_CODES.HANDOFF_CURSOR_INVALID
1259
+ );
1260
+ }
1261
+ return current.snapshot;
1262
+ };
1263
+ const findReplay = (requestId, fingerprint) => {
1264
+ requireOpen();
1265
+ sweep();
1266
+ const record = idempotency.get(requestId);
1267
+ if (record === void 0) return void 0;
1268
+ if (record.fingerprint !== fingerprint) {
1269
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
1270
+ }
1271
+ return replayResult(record);
1272
+ };
1273
+ const settleWaiters = (result) => {
1274
+ const pending = [...waiters];
1275
+ waiters.clear();
1276
+ for (const waiter of pending) waiter.resolve(result);
1277
+ };
1278
+ return Object.freeze({
1279
+ activeWaitCount: () => waiters.size,
1280
+ replay: findReplay,
1281
+ publish(input) {
1282
+ requireOpen();
1283
+ const replayed = findReplay(input.requestId, input.fingerprint);
1284
+ if (replayed !== void 0) return replayed;
1285
+ if (idempotency.size >= import_shared6.EXTERNAL_HANDOFF_LIMITS.maximumRequestIdRecords) {
1286
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE);
1287
+ }
1288
+ const nextRevision = revision + 1;
1289
+ const publishedAtMs = clock.wallNow();
1290
+ const publishedAtMonotonic = clock.monotonicNow();
1291
+ const snapshot2 = Object.freeze({
1292
+ schemaVersion: import_shared6.EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION,
1293
+ cursor: randomId(),
1294
+ session: Object.freeze({ id: options.sessionId, framework: options.framework }),
1295
+ revision: nextRevision,
1296
+ publishedAt: new Date(publishedAtMs).toISOString(),
1297
+ expiresAt: new Date(
1298
+ publishedAtMs + import_shared6.EXTERNAL_HANDOFF_LIMITS.handoffTtlMs
1299
+ ).toISOString(),
1300
+ annotation: input.annotation
1301
+ });
1302
+ if (Buffer.byteLength(JSON.stringify(snapshot2), "utf8") > import_shared6.EXTERNAL_HANDOFF_LIMITS.maximumSnapshotBytes) {
1303
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE);
1304
+ }
1305
+ const delivery = input.reserve(snapshot2.cursor, nextRevision);
1306
+ if (current !== void 0) archive("superseded");
1307
+ revision = nextRevision;
1308
+ current = {
1309
+ expiresAtMonotonic: publishedAtMonotonic + import_shared6.EXTERNAL_HANDOFF_LIMITS.handoffTtlMs,
1310
+ receipts: /* @__PURE__ */ new Set(),
1311
+ snapshot: snapshot2
1312
+ };
1313
+ const result = Object.freeze({
1314
+ handoff: summaryOf(current, "available"),
1315
+ delivery,
1316
+ replayed: false
1317
+ });
1318
+ idempotency.set(
1319
+ input.requestId,
1320
+ Object.freeze({
1321
+ expiresAtMonotonic: publishedAtMonotonic + import_shared6.EXTERNAL_HANDOFF_LIMITS.requestIdTtlMs,
1322
+ fingerprint: input.fingerprint,
1323
+ result
1324
+ })
1325
+ );
1326
+ settleWaiters(Object.freeze({ outcome: "handoff", snapshot: snapshot2 }));
1327
+ return result;
1328
+ },
1329
+ current: readCurrent,
1330
+ currentCursor() {
1331
+ requireOpen();
1332
+ sweep();
1333
+ return current?.snapshot.cursor ?? null;
1334
+ },
1335
+ status(cursor) {
1336
+ requireOpen();
1337
+ sweep();
1338
+ if (cursor === void 0) {
1339
+ if (current === void 0) {
1340
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_NOT_FOUND);
1341
+ }
1342
+ return summaryOf(current, "available");
1343
+ }
1344
+ const summary = knownSummary(cursor);
1345
+ if (summary === void 0) {
1346
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_CURSOR_INVALID);
1347
+ }
1348
+ return summary;
1349
+ },
1350
+ ack(cursor, connectorInstanceId) {
1351
+ const snapshot2 = readCurrent(cursor);
1352
+ if (snapshot2 !== current?.snapshot) {
1353
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_CURSOR_INVALID);
1354
+ }
1355
+ if (!current.receipts.has(connectorInstanceId)) {
1356
+ if (current.receipts.size >= import_shared6.EXTERNAL_HANDOFF_LIMITS.maximumConnectorReceipts) {
1357
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.BRIDGE_BUSY);
1358
+ }
1359
+ current.receipts.add(connectorInstanceId);
1360
+ current.pickedUpAt = new Date(clock.wallNow()).toISOString();
1361
+ }
1362
+ return summaryOf(current, "available");
1363
+ },
1364
+ async wait(afterCursor, timeoutMs, signal) {
1365
+ requireOpen();
1366
+ sweep();
1367
+ if (afterCursor === void 0 && current !== void 0) {
1368
+ return Object.freeze({ outcome: "handoff", snapshot: current.snapshot });
1369
+ }
1370
+ if (afterCursor !== void 0) {
1371
+ const known = knownSummary(afterCursor);
1372
+ if (known === void 0) {
1373
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_CURSOR_INVALID);
1374
+ }
1375
+ if (current !== void 0 && current.snapshot.cursor !== afterCursor) {
1376
+ return Object.freeze({ outcome: "handoff", snapshot: current.snapshot });
1377
+ }
1378
+ }
1379
+ if (waiters.size >= import_shared6.EXTERNAL_HANDOFF_LIMITS.maximumWaiters) {
1380
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.BRIDGE_BUSY);
1381
+ }
1382
+ if (signal.aborted) throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.SESSION_CLOSED);
1383
+ return new Promise((resolve, reject) => {
1384
+ let settled = false;
1385
+ const finish = () => {
1386
+ if (settled) return false;
1387
+ settled = true;
1388
+ waiters.delete(waiter);
1389
+ clearTimeout(timer);
1390
+ signal.removeEventListener("abort", abort);
1391
+ return true;
1392
+ };
1393
+ const waiter = {
1394
+ reject(error) {
1395
+ if (finish()) reject(error);
1396
+ },
1397
+ resolve(result) {
1398
+ if (finish()) resolve(result);
1399
+ }
1400
+ };
1401
+ const abort = () => {
1402
+ waiter.reject(new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.SESSION_CLOSED));
1403
+ };
1404
+ const timer = setTimeout(() => {
1405
+ waiter.resolve(Object.freeze({ outcome: "timeout" }));
1406
+ }, timeoutMs);
1407
+ timer.unref();
1408
+ signal.addEventListener("abort", abort, { once: true });
1409
+ waiters.add(waiter);
1410
+ });
1411
+ },
1412
+ close() {
1413
+ if (closed) return;
1414
+ closed = true;
1415
+ current = void 0;
1416
+ history.length = 0;
1417
+ idempotency.clear();
1418
+ const pending = [...waiters];
1419
+ waiters.clear();
1420
+ for (const waiter of pending) {
1421
+ waiter.reject(new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.SESSION_CLOSED));
1422
+ }
1423
+ }
1424
+ });
1425
+ }
1426
+
1427
+ // src/external-handoff/service.ts
1428
+ function asReplay(result) {
1429
+ return Object.freeze({ ...result, replayed: true });
1430
+ }
1431
+ function createExternalHandoffService(options) {
1432
+ const activeRegistry = createActiveAdapterRegistry();
1433
+ const store = createExternalHandoffStore({
1434
+ framework: options.framework,
1435
+ sessionId: options.sessionId
1436
+ });
1437
+ const inFlight = /* @__PURE__ */ new Map();
1438
+ let broker;
1439
+ let descriptor;
1440
+ let startPromise;
1441
+ let closePromise;
1442
+ let state = "idle";
1443
+ const isClosed = () => state === "closed";
1444
+ const requireReady = () => {
1445
+ if (state !== "ready" || broker?.isReady() !== true) {
1446
+ throw new import_shared7.SpotPatchError(
1447
+ state === "closed" ? import_shared7.ERROR_CODES.SESSION_CLOSED : import_shared7.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE
1448
+ );
1449
+ }
1450
+ };
1451
+ const start = async () => {
1452
+ if (state === "ready") return;
1453
+ if (state === "closed") throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.SESSION_CLOSED);
1454
+ if (startPromise !== void 0) return startPromise;
1455
+ state = "starting";
1456
+ startPromise = (async () => {
1457
+ let createdBroker;
1458
+ let createdDescriptor;
1459
+ try {
1460
+ const projectKey = await (0, import_external_agent_node3.computeExternalHandoffProjectKey)(options.root);
1461
+ createdBroker = await createExternalHandoffBroker({
1462
+ activeRegistry,
1463
+ framework: options.framework,
1464
+ projectKey,
1465
+ sessionId: options.sessionId,
1466
+ store
1467
+ });
1468
+ createdDescriptor = await publishExternalHandoffDescriptor({
1469
+ bridgeToken: createdBroker.bridgeToken,
1470
+ endpoint: createdBroker.endpoint,
1471
+ framework: options.framework,
1472
+ root: options.root,
1473
+ sessionId: options.sessionId
1474
+ });
1475
+ if (isClosed()) {
1476
+ await createdDescriptor.close();
1477
+ await createdBroker.close();
1478
+ throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.SESSION_CLOSED);
1479
+ }
1480
+ broker = createdBroker;
1481
+ descriptor = createdDescriptor;
1482
+ state = "ready";
1483
+ } catch (error) {
1484
+ if (createdDescriptor !== void 0 && descriptor !== createdDescriptor) {
1485
+ await createdDescriptor.close().catch(() => void 0);
1486
+ }
1487
+ if (createdBroker !== void 0 && broker !== createdBroker) {
1488
+ await createdBroker.close().catch(() => void 0);
1489
+ }
1490
+ if (!isClosed()) state = "failed";
1491
+ throw error;
1492
+ }
1493
+ })();
1494
+ return startPromise;
1495
+ };
1496
+ return Object.freeze({
1497
+ start,
1498
+ capability() {
1499
+ const currentCursor = store.currentCursor();
1500
+ const active = activeRegistry.snapshot(currentCursor ?? void 0);
1501
+ return Object.freeze({
1502
+ enabled: true,
1503
+ brokerReady: state === "ready" && broker?.isReady() === true,
1504
+ activeWaitCount: store.activeWaitCount(),
1505
+ snapshotSchemaVersion: import_shared7.EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION,
1506
+ brokerProtocolVersion: import_shared7.EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION,
1507
+ activeAdapter: active.activeAdapter,
1508
+ dispatch: currentCursor === null ? null : active.dispatch
1509
+ });
1510
+ },
1511
+ async publish(request, authorize) {
1512
+ requireReady();
1513
+ const fingerprint = fingerprintExternalHandoffAnnotation(request.annotation);
1514
+ const replayed = store.replay(request.requestId, fingerprint);
1515
+ if (replayed !== void 0) return replayed;
1516
+ const pending = inFlight.get(request.requestId);
1517
+ if (pending !== void 0) {
1518
+ if (pending.fingerprint !== fingerprint) {
1519
+ throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
1520
+ }
1521
+ return asReplay(await pending.promise);
1522
+ }
1523
+ activeRegistry.assertPublishable();
1524
+ const promise = (async () => {
1525
+ const annotation = await authorize(request.annotation);
1526
+ return store.publish({
1527
+ annotation,
1528
+ fingerprint,
1529
+ requestId: request.requestId,
1530
+ reserve: activeRegistry.reserve
1531
+ });
1532
+ })();
1533
+ const activePublish = Object.freeze({ fingerprint, promise });
1534
+ inFlight.set(request.requestId, activePublish);
1535
+ try {
1536
+ return await promise;
1537
+ } finally {
1538
+ if (inFlight.get(request.requestId) === activePublish) {
1539
+ inFlight.delete(request.requestId);
1540
+ }
1541
+ }
1542
+ },
1543
+ status(cursor) {
1544
+ requireReady();
1545
+ const handoff = store.status(cursor);
1546
+ const active = activeRegistry.snapshot(cursor ?? handoff.cursor);
1547
+ return Object.freeze({
1548
+ handoff,
1549
+ activeAdapter: active.activeAdapter,
1550
+ dispatch: active.dispatch
1551
+ });
1552
+ },
1553
+ resolveDelivery(cursor) {
1554
+ requireReady();
1555
+ const handoff = store.status(cursor);
1556
+ const active = activeRegistry.resolveDelivery(cursor);
1557
+ return Object.freeze({
1558
+ handoff,
1559
+ activeAdapter: active.activeAdapter,
1560
+ dispatch: active.dispatch
1561
+ });
1562
+ },
1563
+ close() {
1564
+ closePromise ??= (async () => {
1565
+ if (state === "closed") return;
1566
+ state = "closed";
1567
+ activeRegistry.close();
1568
+ store.close();
1569
+ inFlight.clear();
1570
+ await startPromise?.catch(() => void 0);
1571
+ const publishedDescriptor = descriptor;
1572
+ descriptor = void 0;
1573
+ const activeBroker = broker;
1574
+ broker = void 0;
1575
+ await publishedDescriptor?.close().catch(() => void 0);
1576
+ await activeBroker?.close().catch(() => void 0);
1577
+ })();
1578
+ return closePromise;
1579
+ }
1580
+ });
1581
+ }
1582
+
524
1583
  // src/environment-ai.ts
525
1584
  var AI_ENVIRONMENT_NAMES = Object.freeze({
526
1585
  authentication: "SPOTPATCH_AI_AUTHENTICATION",
@@ -611,46 +1670,46 @@ function resolveEnvironmentAiConfiguration(environment) {
611
1670
  }
612
1671
 
613
1672
  // src/integration/file-plan.ts
614
- var import_node_crypto2 = require("crypto");
615
- var import_promises = require("fs/promises");
616
- var import_node_path = __toESM(require("path"), 1);
1673
+ var import_node_crypto7 = require("crypto");
1674
+ var import_promises2 = require("fs/promises");
1675
+ var import_node_path2 = __toESM(require("path"), 1);
617
1676
  function isMissingPathError(error) {
618
1677
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
619
1678
  }
620
1679
  function isPathWithin(root, target) {
621
- const relative = import_node_path.default.relative(root, target);
622
- return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(relative);
1680
+ const relative = import_node_path2.default.relative(root, target);
1681
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path2.default.sep}`) && !import_node_path2.default.isAbsolute(relative);
623
1682
  }
624
1683
  function relativePathWithin(root, target) {
625
- const relative = import_node_path.default.relative(root, target);
1684
+ const relative = import_node_path2.default.relative(root, target);
626
1685
  if (relative.length === 0 || !isPathWithin(root, target)) {
627
1686
  throw new Error("SpotPatch init refuses to modify a path outside the app root.");
628
1687
  }
629
- return relative.split(import_node_path.default.sep).join("/");
1688
+ return relative.split(import_node_path2.default.sep).join("/");
630
1689
  }
631
1690
  async function integrationPathExists(absolutePath) {
632
1691
  try {
633
- await (0, import_promises.access)(absolutePath);
1692
+ await (0, import_promises2.access)(absolutePath);
634
1693
  return true;
635
1694
  } catch {
636
1695
  return false;
637
1696
  }
638
1697
  }
639
1698
  async function readIntegrationFile(absolutePath) {
640
- const metadata = await (0, import_promises.lstat)(absolutePath);
1699
+ const metadata = await (0, import_promises2.lstat)(absolutePath);
641
1700
  if (!metadata.isFile() || metadata.isSymbolicLink()) {
642
1701
  throw new Error(
643
- `SpotPatch refuses to modify the non-regular file ${import_node_path.default.basename(absolutePath)}.`
1702
+ `SpotPatch refuses to modify the non-regular file ${import_node_path2.default.basename(absolutePath)}.`
644
1703
  );
645
1704
  }
646
- return (0, import_promises.readFile)(absolutePath, "utf8");
1705
+ return (0, import_promises2.readFile)(absolutePath, "utf8");
647
1706
  }
648
1707
  function createIntegrationFileChange(appRoot, absolutePath, nextContent, previousContent) {
649
1708
  if (previousContent === nextContent) {
650
1709
  return void 0;
651
1710
  }
652
- const root = import_node_path.default.resolve(appRoot);
653
- const target = import_node_path.default.resolve(absolutePath);
1711
+ const root = import_node_path2.default.resolve(appRoot);
1712
+ const target = import_node_path2.default.resolve(absolutePath);
654
1713
  return Object.freeze({
655
1714
  absolutePath: target,
656
1715
  nextContent,
@@ -659,19 +1718,19 @@ function createIntegrationFileChange(appRoot, absolutePath, nextContent, previou
659
1718
  });
660
1719
  }
661
1720
  function temporaryPath(absolutePath, label) {
662
- return import_node_path.default.join(
663
- import_node_path.default.dirname(absolutePath),
664
- `.${import_node_path.default.basename(absolutePath)}.spotpatch-${label}-${String(process.pid)}-${(0, import_node_crypto2.randomBytes)(8).toString("hex")}`
1721
+ return import_node_path2.default.join(
1722
+ import_node_path2.default.dirname(absolutePath),
1723
+ `.${import_node_path2.default.basename(absolutePath)}.spotpatch-${label}-${String(process.pid)}-${(0, import_node_crypto7.randomBytes)(8).toString("hex")}`
665
1724
  );
666
1725
  }
667
1726
  async function writeAtomic(absolutePath, content, mode) {
668
- await (0, import_promises.mkdir)(import_node_path.default.dirname(absolutePath), { recursive: true });
1727
+ await (0, import_promises2.mkdir)(import_node_path2.default.dirname(absolutePath), { recursive: true });
669
1728
  const stagedPath = temporaryPath(absolutePath, "stage");
670
1729
  try {
671
- await (0, import_promises.writeFile)(stagedPath, content, { encoding: "utf8", flag: "wx", mode });
672
- await (0, import_promises.rename)(stagedPath, absolutePath);
1730
+ await (0, import_promises2.writeFile)(stagedPath, content, { encoding: "utf8", flag: "wx", mode });
1731
+ await (0, import_promises2.rename)(stagedPath, absolutePath);
673
1732
  } catch (error) {
674
- await (0, import_promises.unlink)(stagedPath).catch(() => void 0);
1733
+ await (0, import_promises2.unlink)(stagedPath).catch(() => void 0);
675
1734
  throw error;
676
1735
  }
677
1736
  }
@@ -683,21 +1742,21 @@ async function rollbackChange(change) {
683
1742
  );
684
1743
  }
685
1744
  if (change.previousContent === void 0) {
686
- await (0, import_promises.unlink)(change.absolutePath);
1745
+ await (0, import_promises2.unlink)(change.absolutePath);
687
1746
  return;
688
1747
  }
689
- const mode = (await (0, import_promises.stat)(change.absolutePath)).mode & 511;
1748
+ const mode = (await (0, import_promises2.stat)(change.absolutePath)).mode & 511;
690
1749
  await writeAtomic(change.absolutePath, change.previousContent, mode);
691
1750
  }
692
1751
  async function assertSafeTarget(appRoot, realAppRoot, change) {
693
- const target = import_node_path.default.resolve(change.absolutePath);
1752
+ const target = import_node_path2.default.resolve(change.absolutePath);
694
1753
  const relativePath = relativePathWithin(appRoot, target);
695
- if (target !== change.absolutePath || relativePath !== change.relativePath || import_node_path.default.dirname(target) === target) {
1754
+ if (target !== change.absolutePath || relativePath !== change.relativePath || import_node_path2.default.dirname(target) === target) {
696
1755
  throw new Error("SpotPatch init received an invalid integration file plan.");
697
1756
  }
698
1757
  let targetMetadata;
699
1758
  try {
700
- targetMetadata = await (0, import_promises.lstat)(target);
1759
+ targetMetadata = await (0, import_promises2.lstat)(target);
701
1760
  } catch (error) {
702
1761
  if (!isMissingPathError(error)) {
703
1762
  throw error;
@@ -708,8 +1767,8 @@ async function assertSafeTarget(appRoot, realAppRoot, change) {
708
1767
  `SpotPatch refuses to modify the symbolic link ${change.relativePath}.`
709
1768
  );
710
1769
  }
711
- const containmentAnchor = await (0, import_promises.realpath)(
712
- targetMetadata === void 0 ? import_node_path.default.dirname(target) : target
1770
+ const containmentAnchor = await (0, import_promises2.realpath)(
1771
+ targetMetadata === void 0 ? import_node_path2.default.dirname(target) : target
713
1772
  );
714
1773
  if (!isPathWithin(realAppRoot, containmentAnchor)) {
715
1774
  throw new Error("SpotPatch init refuses to modify a path outside the app root.");
@@ -718,7 +1777,7 @@ async function assertSafeTarget(appRoot, realAppRoot, change) {
718
1777
  async function assertCurrentBaseline(change) {
719
1778
  if (change.previousContent === void 0) {
720
1779
  try {
721
- await (0, import_promises.lstat)(change.absolutePath);
1780
+ await (0, import_promises2.lstat)(change.absolutePath);
722
1781
  } catch (error) {
723
1782
  if (isMissingPathError(error)) {
724
1783
  return;
@@ -740,8 +1799,8 @@ async function applyIntegrationPlan(plan) {
740
1799
  if (plan.changes.length === 0) {
741
1800
  return;
742
1801
  }
743
- const appRoot = import_node_path.default.resolve(plan.appRoot);
744
- const realAppRoot = await (0, import_promises.realpath)(appRoot);
1802
+ const appRoot = import_node_path2.default.resolve(plan.appRoot);
1803
+ const realAppRoot = await (0, import_promises2.realpath)(appRoot);
745
1804
  const targets = /* @__PURE__ */ new Set();
746
1805
  for (const change of plan.changes) {
747
1806
  if (targets.has(change.absolutePath)) {
@@ -755,7 +1814,7 @@ async function applyIntegrationPlan(plan) {
755
1814
  try {
756
1815
  for (const change of plan.changes) {
757
1816
  await assertCurrentBaseline(change);
758
- const mode = change.previousContent === void 0 ? 384 : (await (0, import_promises.stat)(change.absolutePath)).mode & 511;
1817
+ const mode = change.previousContent === void 0 ? 384 : (await (0, import_promises2.stat)(change.absolutePath)).mode & 511;
759
1818
  await writeAtomic(change.absolutePath, change.nextContent, mode);
760
1819
  applied.push(change);
761
1820
  }
@@ -776,7 +1835,7 @@ async function applyIntegrationPlan(plan) {
776
1835
  }
777
1836
 
778
1837
  // src/options.ts
779
- var import_shared2 = require("@spotpatch/shared");
1838
+ var import_shared8 = require("@spotpatch/shared");
780
1839
  var import_zod = require("zod");
781
1840
  var DEFAULT_EXCLUDE = Object.freeze([
782
1841
  /node_modules/,
@@ -811,8 +1870,9 @@ var DEFAULT_OPTIONS = Object.freeze({
811
1870
  dataFlow: Object.freeze({
812
1871
  enabled: false,
813
1872
  runtime: "dispatch",
814
- limits: import_shared2.DEFAULT_DATA_FLOW_LIMITS
815
- })
1873
+ limits: import_shared8.DEFAULT_DATA_FLOW_LIMITS
1874
+ }),
1875
+ externalAgent: Object.freeze({ enabled: false })
816
1876
  });
817
1877
  var PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
818
1878
  var ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{1,127}$/;
@@ -857,7 +1917,7 @@ var aiOptionsSchema = import_zod.z.strictObject({
857
1917
  defaultProvider: import_zod.z.string(),
858
1918
  execution: import_zod.z.strictObject({
859
1919
  isolation: import_zod.z.literal("git-worktree").optional(),
860
- applyMode: import_zod.z.enum(import_shared2.AGENT_APPLY_MODES).optional(),
1920
+ applyMode: import_zod.z.enum(import_shared8.AGENT_APPLY_MODES).optional(),
861
1921
  checks: import_zod.z.record(import_zod.z.string(), agentCheckSchema).optional(),
862
1922
  limits: agentLimitsSchema
863
1923
  }).optional()
@@ -903,18 +1963,18 @@ function normalizeProviderBaseURL(value) {
903
1963
  }
904
1964
  function resolveLimits(limits) {
905
1965
  const resolved = Object.freeze({
906
- maxTurns: limits?.maxTurns ?? import_shared2.DEFAULT_AGENT_LIMITS.maxTurns,
907
- maxToolCalls: limits?.maxToolCalls ?? import_shared2.DEFAULT_AGENT_LIMITS.maxToolCalls,
908
- maxChangedFiles: limits?.maxChangedFiles ?? import_shared2.DEFAULT_AGENT_LIMITS.maxChangedFiles,
909
- maxDiffBytes: limits?.maxDiffBytes ?? import_shared2.DEFAULT_AGENT_LIMITS.maxDiffBytes,
910
- maxReadBytesPerFile: limits?.maxReadBytesPerFile ?? import_shared2.DEFAULT_AGENT_LIMITS.maxReadBytesPerFile,
911
- maxToolOutputCharacters: limits?.maxToolOutputCharacters ?? import_shared2.DEFAULT_AGENT_LIMITS.maxToolOutputCharacters,
912
- maxProviderResponseBytes: limits?.maxProviderResponseBytes ?? import_shared2.DEFAULT_AGENT_LIMITS.maxProviderResponseBytes,
913
- providerConnectTimeoutMs: limits?.providerConnectTimeoutMs ?? import_shared2.DEFAULT_AGENT_LIMITS.providerConnectTimeoutMs,
914
- providerFirstByteTimeoutMs: limits?.providerFirstByteTimeoutMs ?? import_shared2.DEFAULT_AGENT_LIMITS.providerFirstByteTimeoutMs,
915
- providerIdleTimeoutMs: limits?.providerIdleTimeoutMs ?? import_shared2.DEFAULT_AGENT_LIMITS.providerIdleTimeoutMs,
916
- checkTimeoutMs: limits?.checkTimeoutMs ?? import_shared2.DEFAULT_AGENT_LIMITS.checkTimeoutMs,
917
- jobTimeoutMs: limits?.jobTimeoutMs ?? import_shared2.DEFAULT_AGENT_LIMITS.jobTimeoutMs
1966
+ maxTurns: limits?.maxTurns ?? import_shared8.DEFAULT_AGENT_LIMITS.maxTurns,
1967
+ maxToolCalls: limits?.maxToolCalls ?? import_shared8.DEFAULT_AGENT_LIMITS.maxToolCalls,
1968
+ maxChangedFiles: limits?.maxChangedFiles ?? import_shared8.DEFAULT_AGENT_LIMITS.maxChangedFiles,
1969
+ maxDiffBytes: limits?.maxDiffBytes ?? import_shared8.DEFAULT_AGENT_LIMITS.maxDiffBytes,
1970
+ maxReadBytesPerFile: limits?.maxReadBytesPerFile ?? import_shared8.DEFAULT_AGENT_LIMITS.maxReadBytesPerFile,
1971
+ maxToolOutputCharacters: limits?.maxToolOutputCharacters ?? import_shared8.DEFAULT_AGENT_LIMITS.maxToolOutputCharacters,
1972
+ maxProviderResponseBytes: limits?.maxProviderResponseBytes ?? import_shared8.DEFAULT_AGENT_LIMITS.maxProviderResponseBytes,
1973
+ providerConnectTimeoutMs: limits?.providerConnectTimeoutMs ?? import_shared8.DEFAULT_AGENT_LIMITS.providerConnectTimeoutMs,
1974
+ providerFirstByteTimeoutMs: limits?.providerFirstByteTimeoutMs ?? import_shared8.DEFAULT_AGENT_LIMITS.providerFirstByteTimeoutMs,
1975
+ providerIdleTimeoutMs: limits?.providerIdleTimeoutMs ?? import_shared8.DEFAULT_AGENT_LIMITS.providerIdleTimeoutMs,
1976
+ checkTimeoutMs: limits?.checkTimeoutMs ?? import_shared8.DEFAULT_AGENT_LIMITS.checkTimeoutMs,
1977
+ jobTimeoutMs: limits?.jobTimeoutMs ?? import_shared8.DEFAULT_AGENT_LIMITS.jobTimeoutMs
918
1978
  });
919
1979
  for (const [name, value] of Object.entries(resolved)) {
920
1980
  if (!Number.isSafeInteger(value) || value <= 0) {
@@ -1119,7 +2179,7 @@ function resolveDataFlowOptions(options) {
1119
2179
  return Object.freeze({
1120
2180
  enabled: true,
1121
2181
  runtime,
1122
- limits: import_shared2.DEFAULT_DATA_FLOW_LIMITS
2182
+ limits: import_shared8.DEFAULT_DATA_FLOW_LIMITS
1123
2183
  });
1124
2184
  }
1125
2185
  function createRuntimeDataFlowConfig(options) {
@@ -1138,6 +2198,9 @@ function resolveOptions(options = {}, environmentAi) {
1138
2198
  if (options.trustedFastMode !== void 0 && typeof options.trustedFastMode !== "boolean") {
1139
2199
  throw new RangeError("SpotPatch trustedFastMode must be a boolean.");
1140
2200
  }
2201
+ if (options.externalAgent !== void 0 && typeof options.externalAgent !== "boolean") {
2202
+ throw new RangeError("SpotPatch externalAgent must be a boolean.");
2203
+ }
1141
2204
  const budget = Object.freeze({
1142
2205
  ...DEFAULT_OPTIONS.budget,
1143
2206
  ...options.budget
@@ -1146,15 +2209,15 @@ function resolveOptions(options = {}, environmentAi) {
1146
2209
  const maxTargets = options.maxTargets ?? DEFAULT_OPTIONS.maxTargets;
1147
2210
  const locale = options.locale ?? DEFAULT_OPTIONS.locale;
1148
2211
  const editor = options.editor ?? DEFAULT_OPTIONS.editor;
1149
- if (!import_shared2.SPOTPATCH_LOCALE_PREFERENCES.includes(locale)) {
2212
+ if (!import_shared8.SPOTPATCH_LOCALE_PREFERENCES.includes(locale)) {
1150
2213
  throw new RangeError("SpotPatch locale must be auto, en-US, or zh-CN.");
1151
2214
  }
1152
- if (!import_shared2.SPOTPATCH_EDITOR_PREFERENCES.includes(editor)) {
2215
+ if (!import_shared8.SPOTPATCH_EDITOR_PREFERENCES.includes(editor)) {
1153
2216
  throw new RangeError("SpotPatch editor must be auto, vscode, or cursor.");
1154
2217
  }
1155
- if (!Number.isSafeInteger(maxTargets) || maxTargets < 1 || maxTargets > import_shared2.MAX_ANNOTATION_TARGETS) {
2218
+ if (!Number.isSafeInteger(maxTargets) || maxTargets < 1 || maxTargets > import_shared8.MAX_ANNOTATION_TARGETS) {
1156
2219
  throw new RangeError(
1157
- `SpotPatch maxTargets must be an integer between 1 and ${String(import_shared2.MAX_ANNOTATION_TARGETS)}.`
2220
+ `SpotPatch maxTargets must be an integer between 1 and ${String(import_shared8.MAX_ANNOTATION_TARGETS)}.`
1158
2221
  );
1159
2222
  }
1160
2223
  const resolved = {
@@ -1170,7 +2233,10 @@ function resolveOptions(options = {}, environmentAi) {
1170
2233
  locale,
1171
2234
  maxTargets,
1172
2235
  ai: resolveAiOptions(options.ai ?? environmentAi),
1173
- dataFlow: resolveDataFlowOptions(options.dataFlow)
2236
+ dataFlow: resolveDataFlowOptions(options.dataFlow),
2237
+ externalAgent: Object.freeze({
2238
+ enabled: options.externalAgent ?? DEFAULT_OPTIONS.externalAgent.enabled
2239
+ })
1174
2240
  };
1175
2241
  if (resolved.shortcut.trim().length === 0 || resolved.shortcut.length > 128 || resolved.shortcut.includes("\0")) {
1176
2242
  throw new RangeError("SpotPatch shortcut is invalid.");
@@ -1180,9 +2246,9 @@ function resolveOptions(options = {}, environmentAi) {
1180
2246
 
1181
2247
  // src/project-validation.ts
1182
2248
  var import_node_child_process = require("child_process");
1183
- var import_promises2 = require("fs/promises");
2249
+ var import_promises3 = require("fs/promises");
1184
2250
  var import_node_module = require("module");
1185
- var import_node_path2 = __toESM(require("path"), 1);
2251
+ var import_node_path3 = __toESM(require("path"), 1);
1186
2252
  var import_node_util = require("util");
1187
2253
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
1188
2254
  var TYPESCRIPT_CHECK_ID = "spotpatch-typecheck";
@@ -1192,19 +2258,19 @@ function isRecord(value) {
1192
2258
  }
1193
2259
  async function isRegularFile(absolutePath) {
1194
2260
  try {
1195
- const metadata = await (0, import_promises2.lstat)(absolutePath);
2261
+ const metadata = await (0, import_promises3.lstat)(absolutePath);
1196
2262
  return metadata.isFile() && !metadata.isSymbolicLink();
1197
2263
  } catch {
1198
2264
  return false;
1199
2265
  }
1200
2266
  }
1201
2267
  async function readManifest(appRoot) {
1202
- const manifestPath = import_node_path2.default.join(appRoot, "package.json");
2268
+ const manifestPath = import_node_path3.default.join(appRoot, "package.json");
1203
2269
  if (!await isRegularFile(manifestPath)) {
1204
2270
  return void 0;
1205
2271
  }
1206
2272
  try {
1207
- const value = JSON.parse(await (0, import_promises2.readFile)(manifestPath, "utf8"));
2273
+ const value = JSON.parse(await (0, import_promises3.readFile)(manifestPath, "utf8"));
1208
2274
  return isRecord(value) ? value : void 0;
1209
2275
  } catch {
1210
2276
  return void 0;
@@ -1227,9 +2293,9 @@ async function findGitRoot(appRoot) {
1227
2293
  timeout: 5e3,
1228
2294
  windowsHide: true
1229
2295
  });
1230
- const root = await (0, import_promises2.realpath)(result.stdout.trim());
1231
- const relative = import_node_path2.default.relative(root, appRoot);
1232
- if (relative === "" || !relative.startsWith(`..${import_node_path2.default.sep}`) && relative !== ".." && !import_node_path2.default.isAbsolute(relative)) {
2296
+ const root = await (0, import_promises3.realpath)(result.stdout.trim());
2297
+ const relative = import_node_path3.default.relative(root, appRoot);
2298
+ if (relative === "" || !relative.startsWith(`..${import_node_path3.default.sep}`) && relative !== ".." && !import_node_path3.default.isAbsolute(relative)) {
1233
2299
  return root;
1234
2300
  }
1235
2301
  } catch {
@@ -1238,22 +2304,22 @@ async function findGitRoot(appRoot) {
1238
2304
  return void 0;
1239
2305
  }
1240
2306
  async function resolveTypeScriptCli(appRoot) {
1241
- const resolveFromApplication = (0, import_node_module.createRequire)(import_node_path2.default.join(appRoot, "package.json"));
2307
+ const resolveFromApplication = (0, import_node_module.createRequire)(import_node_path3.default.join(appRoot, "package.json"));
1242
2308
  try {
1243
2309
  const packagePath = resolveFromApplication.resolve("typescript/package.json");
1244
- const cliPath = import_node_path2.default.join(import_node_path2.default.dirname(packagePath), "bin", "tsc");
1245
- await (0, import_promises2.access)(cliPath);
1246
- return await (0, import_promises2.realpath)(cliPath);
2310
+ const cliPath = import_node_path3.default.join(import_node_path3.default.dirname(packagePath), "bin", "tsc");
2311
+ await (0, import_promises3.access)(cliPath);
2312
+ return await (0, import_promises3.realpath)(cliPath);
1247
2313
  } catch {
1248
2314
  return void 0;
1249
2315
  }
1250
2316
  }
1251
2317
  function portableRelativePath(from, to) {
1252
- return import_node_path2.default.relative(from, to).split(import_node_path2.default.sep).join("/");
2318
+ return import_node_path3.default.relative(from, to).split(import_node_path3.default.sep).join("/");
1253
2319
  }
1254
2320
  async function discoverProjectValidationCheck(options) {
1255
- const appRoot = await (0, import_promises2.realpath)(options.appRoot);
1256
- const tsconfigPath = import_node_path2.default.join(appRoot, "tsconfig.json");
2321
+ const appRoot = await (0, import_promises3.realpath)(options.appRoot);
2322
+ const tsconfigPath = import_node_path3.default.join(appRoot, "tsconfig.json");
1257
2323
  const [manifest, projectRoot, hasTsconfig] = await Promise.all([
1258
2324
  readManifest(appRoot),
1259
2325
  findGitRoot(appRoot),
@@ -1341,16 +2407,16 @@ async function resolveProjectOptions(input) {
1341
2407
  }
1342
2408
 
1343
2409
  // src/registry/source-registry.ts
1344
- var import_node_path3 = __toESM(require("path"), 1);
2410
+ var import_node_path4 = __toESM(require("path"), 1);
1345
2411
 
1346
2412
  // src/registry/source-id.ts
1347
- var import_node_crypto3 = require("crypto");
2413
+ var import_node_crypto8 = require("crypto");
1348
2414
  var SOURCE_ID_BYTES = 8;
1349
- var createRandomSourceId = () => (0, import_node_crypto3.randomBytes)(SOURCE_ID_BYTES).toString("base64url");
2415
+ var createRandomSourceId = () => (0, import_node_crypto8.randomBytes)(SOURCE_ID_BYTES).toString("base64url");
1350
2416
 
1351
2417
  // src/registry/source-registry.ts
1352
2418
  function normalizeAbsolutePath(absolutePath) {
1353
- return import_node_path3.default.normalize(import_node_path3.default.resolve(absolutePath));
2419
+ return import_node_path4.default.normalize(import_node_path4.default.resolve(absolutePath));
1354
2420
  }
1355
2421
  function createSourceRegistry(options = {}) {
1356
2422
  const createId = options.createId ?? createRandomSourceId;
@@ -1407,20 +2473,20 @@ function createSourceRegistry(options = {}) {
1407
2473
  }
1408
2474
 
1409
2475
  // src/server/middleware.ts
1410
- var import_shared11 = require("@spotpatch/shared");
2476
+ var import_shared18 = require("@spotpatch/shared");
1411
2477
 
1412
- // src/server/agent-http.ts
1413
- var import_shared7 = require("@spotpatch/shared");
2478
+ // src/external-handoff/browser-http.ts
2479
+ var import_shared12 = require("@spotpatch/shared");
1414
2480
 
1415
- // src/server/agent-request.ts
1416
- var import_promises5 = require("fs/promises");
1417
- var import_node_path6 = __toESM(require("path"), 1);
1418
- var import_shared5 = require("@spotpatch/shared");
2481
+ // src/server/annotation-authorizer.ts
2482
+ var import_promises6 = require("fs/promises");
2483
+ var import_node_path7 = __toESM(require("path"), 1);
2484
+ var import_shared11 = require("@spotpatch/shared");
1419
2485
 
1420
2486
  // src/server/source-context.ts
1421
- var import_promises4 = require("fs/promises");
1422
- var import_node_path5 = __toESM(require("path"), 1);
1423
- var import_shared4 = require("@spotpatch/shared");
2487
+ var import_promises5 = require("fs/promises");
2488
+ var import_node_path6 = __toESM(require("path"), 1);
2489
+ var import_shared10 = require("@spotpatch/shared");
1424
2490
 
1425
2491
  // src/server/extract-code-context.ts
1426
2492
  var import_oxc_parser = require("oxc-parser");
@@ -1649,16 +2715,9 @@ function extractCodeContext(options) {
1649
2715
  }
1650
2716
 
1651
2717
  // src/server/source-file.ts
1652
- var import_promises3 = require("fs/promises");
1653
- var import_node_path4 = __toESM(require("path"), 1);
1654
- var import_shared3 = require("@spotpatch/shared");
1655
-
1656
- // src/server/constants.ts
1657
- var MAX_REQUEST_BODY_BYTES = 32 * 1024;
1658
- var MAX_AGENT_REQUEST_BODY_BYTES = 256 * 1024;
1659
- var MAX_SOURCE_FILE_BYTES = 1024 * 1024;
1660
-
1661
- // src/server/source-file.ts
2718
+ var import_promises4 = require("fs/promises");
2719
+ var import_node_path5 = __toESM(require("path"), 1);
2720
+ var import_shared9 = require("@spotpatch/shared");
1662
2721
  var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
1663
2722
  function isMissingFileError(error) {
1664
2723
  return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
@@ -1668,56 +2727,56 @@ async function assertInsideRoot(root, candidate) {
1668
2727
  let realCandidate;
1669
2728
  try {
1670
2729
  [realRoot, realCandidate] = await Promise.all([
1671
- (0, import_promises3.realpath)(root),
1672
- (0, import_promises3.realpath)(candidate)
2730
+ (0, import_promises4.realpath)(root),
2731
+ (0, import_promises4.realpath)(candidate)
1673
2732
  ]);
1674
2733
  } catch (error) {
1675
2734
  if (isMissingFileError(error)) {
1676
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2735
+ throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
1677
2736
  cause: error
1678
2737
  });
1679
2738
  }
1680
2739
  throw error;
1681
2740
  }
1682
- const relative = import_node_path4.default.relative(realRoot, realCandidate);
1683
- const outside = relative.startsWith(`..${import_node_path4.default.sep}`) || relative === ".." || import_node_path4.default.isAbsolute(relative);
2741
+ const relative = import_node_path5.default.relative(realRoot, realCandidate);
2742
+ const outside = relative.startsWith(`..${import_node_path5.default.sep}`) || relative === ".." || import_node_path5.default.isAbsolute(relative);
1684
2743
  if (outside) {
1685
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
2744
+ throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
1686
2745
  }
1687
2746
  return realCandidate;
1688
2747
  }
1689
2748
  async function resolveSourceFile(options) {
1690
2749
  const registeredPath = options.registry.resolve(options.fileId);
1691
2750
  if (registeredPath === void 0) {
1692
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND);
2751
+ throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND);
1693
2752
  }
1694
2753
  const sourcePath = await assertInsideRoot(options.root, registeredPath);
1695
- if (!ALLOWED_EXTENSIONS.has(import_node_path4.default.extname(sourcePath).toLowerCase())) {
1696
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND);
2754
+ if (!ALLOWED_EXTENSIONS.has(import_node_path5.default.extname(sourcePath).toLowerCase())) {
2755
+ throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND);
1697
2756
  }
1698
2757
  let sourceStat;
1699
2758
  try {
1700
- sourceStat = await (0, import_promises3.stat)(sourcePath);
2759
+ sourceStat = await (0, import_promises4.stat)(sourcePath);
1701
2760
  } catch (error) {
1702
2761
  if (isMissingFileError(error)) {
1703
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2762
+ throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
1704
2763
  cause: error
1705
2764
  });
1706
2765
  }
1707
2766
  throw error;
1708
2767
  }
1709
2768
  if (!sourceStat.isFile()) {
1710
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND);
2769
+ throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_NOT_FOUND);
1711
2770
  }
1712
2771
  if (sourceStat.size > MAX_SOURCE_FILE_BYTES) {
1713
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_TOO_LARGE);
2772
+ throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.SOURCE_TOO_LARGE);
1714
2773
  }
1715
2774
  return sourcePath;
1716
2775
  }
1717
2776
 
1718
2777
  // src/server/source-context.ts
1719
2778
  function toDisplayPath(root, sourcePath) {
1720
- return import_node_path5.default.relative(root, sourcePath).split(import_node_path5.default.sep).join("/");
2779
+ return import_node_path6.default.relative(root, sourcePath).split(import_node_path6.default.sep).join("/");
1721
2780
  }
1722
2781
  async function readSourceContext(options) {
1723
2782
  const sourcePath = await resolveSourceFile({
@@ -1727,10 +2786,10 @@ async function readSourceContext(options) {
1727
2786
  });
1728
2787
  let source;
1729
2788
  try {
1730
- source = await (0, import_promises4.readFile)(sourcePath, "utf8");
2789
+ source = await (0, import_promises5.readFile)(sourcePath, "utf8");
1731
2790
  } catch (error) {
1732
2791
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
1733
- throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2792
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
1734
2793
  cause: error
1735
2794
  });
1736
2795
  }
@@ -1738,13 +2797,13 @@ async function readSourceContext(options) {
1738
2797
  }
1739
2798
  const lines = source.split(/\r?\n/);
1740
2799
  if (options.request.line > lines.length) {
1741
- throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
2800
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.INVALID_REQUEST);
1742
2801
  }
1743
- const extension = import_node_path5.default.extname(sourcePath).toLowerCase();
2802
+ const extension = import_node_path6.default.extname(sourcePath).toLowerCase();
1744
2803
  return extractCodeContext({
1745
2804
  source,
1746
2805
  sourcePath,
1747
- relativePath: toDisplayPath(await (0, import_promises4.realpath)(options.root), sourcePath),
2806
+ relativePath: toDisplayPath(await (0, import_promises5.realpath)(options.root), sourcePath),
1748
2807
  language: extension === ".tsx" ? "tsx" : "jsx",
1749
2808
  line: options.request.line,
1750
2809
  column: options.request.column,
@@ -1753,7 +2812,17 @@ async function readSourceContext(options) {
1753
2812
  });
1754
2813
  }
1755
2814
 
1756
- // src/server/agent-request.ts
2815
+ // src/server/annotation-authorizer.ts
2816
+ function sanitizePageContext(page) {
2817
+ return Object.freeze({
2818
+ url: (0, import_shared11.sanitizeUrl)(page.url, page.url),
2819
+ pathname: (0, import_shared11.redactSensitiveText)(page.pathname),
2820
+ title: (0, import_shared11.redactSensitiveText)(page.title),
2821
+ viewportWidth: page.viewportWidth,
2822
+ viewportHeight: page.viewportHeight,
2823
+ devicePixelRatio: page.devicePixelRatio
2824
+ });
2825
+ }
1757
2826
  function compactSourceRef(source) {
1758
2827
  return Object.freeze({
1759
2828
  origin: source.origin,
@@ -1767,27 +2836,20 @@ function compactSourceRef(source) {
1767
2836
  async function authorizeSourceRef(source, registry, root) {
1768
2837
  const markerOrigin = source.origin === "jsx-host" || source.origin === "dom-ancestor";
1769
2838
  if (markerOrigin && (source.fileId === void 0 || source.line === void 0 || source.column === void 0)) {
1770
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2839
+ throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
1771
2840
  }
1772
2841
  if (source.fileId === void 0) {
1773
2842
  if (source.origin === "none" && source.relativePath !== void 0) {
1774
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2843
+ throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
1775
2844
  }
1776
2845
  return compactSourceRef(source);
1777
2846
  }
1778
- const sourcePath = await resolveSourceFile({
1779
- fileId: source.fileId,
1780
- registry,
1781
- root
1782
- });
1783
- const relativePath = import_node_path6.default.relative(await (0, import_promises5.realpath)(root), sourcePath).split(import_node_path6.default.sep).join("/");
2847
+ const sourcePath = await resolveSourceFile({ fileId: source.fileId, registry, root });
2848
+ const relativePath = import_node_path7.default.relative(await (0, import_promises6.realpath)(root), sourcePath).split(import_node_path7.default.sep).join("/");
1784
2849
  if (source.relativePath !== void 0 && source.relativePath !== relativePath) {
1785
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2850
+ throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
1786
2851
  }
1787
- return Object.freeze({
1788
- ...compactSourceRef(source),
1789
- relativePath
1790
- });
2852
+ return Object.freeze({ ...compactSourceRef(source), relativePath });
1791
2853
  }
1792
2854
  function freezeMatchedRule(rule) {
1793
2855
  return Object.freeze({
@@ -1821,7 +2883,7 @@ async function authorizeTarget(target, input) {
1821
2883
  maxLines: input.options.budget.maxCodeLines
1822
2884
  });
1823
2885
  if (marker === void 0 && target.code !== void 0) {
1824
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2886
+ throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
1825
2887
  }
1826
2888
  const code = marker === void 0 ? void 0 : await readSourceContext({
1827
2889
  request: marker,
@@ -1831,11 +2893,11 @@ async function authorizeTarget(target, input) {
1831
2893
  maxLines: input.options.budget.maxCodeLines
1832
2894
  });
1833
2895
  if (target.code !== void 0 && target.code.relativePath !== code?.relativePath) {
1834
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2896
+ throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
1835
2897
  }
1836
2898
  return Object.freeze({
1837
2899
  instruction: target.instruction,
1838
- ...target.page === void 0 ? {} : { page: Object.freeze({ ...target.page }) },
2900
+ ...target.page === void 0 ? {} : { page: sanitizePageContext(target.page) },
1839
2901
  source,
1840
2902
  react: Object.freeze({
1841
2903
  supported: target.react.supported,
@@ -1863,76 +2925,130 @@ async function authorizeTarget(target, input) {
1863
2925
  warnings: Object.freeze([...target.warnings])
1864
2926
  });
1865
2927
  }
1866
- async function authorizeAgentJobRequest(input) {
1867
- const requestedTargets = input.request.annotation.targets;
2928
+ async function authorizeAnnotation(input) {
2929
+ const requestedTargets = input.annotation.targets;
1868
2930
  if (requestedTargets.length > input.options.maxTargets) {
1869
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2931
+ throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
1870
2932
  }
1871
2933
  const identities = requestedTargets.map(targetIdentity);
1872
2934
  if (new Set(identities).size !== identities.length) {
1873
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2935
+ throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
1874
2936
  }
1875
2937
  const targets = Object.freeze(
1876
2938
  await Promise.all(requestedTargets.map((target) => authorizeTarget(target, input)))
1877
2939
  );
1878
- const annotation = Object.freeze({
2940
+ return Object.freeze({
1879
2941
  schemaVersion: 3,
1880
- id: input.request.annotation.id,
1881
- locale: input.request.annotation.locale,
1882
- page: Object.freeze({ ...input.request.annotation.page }),
2942
+ id: input.annotation.id,
2943
+ locale: input.annotation.locale,
2944
+ page: sanitizePageContext(input.annotation.page),
1883
2945
  targets,
1884
- createdAt: input.request.annotation.createdAt
1885
- });
1886
- return Object.freeze({
1887
- annotation,
1888
- ...input.request.applyMode === void 0 ? {} : { applyMode: input.request.applyMode },
1889
- providerProfileId: input.request.providerProfileId,
1890
- modelProfileId: input.request.modelProfileId,
1891
- providerDataConsent: true,
1892
- ...input.request.trustedFastModeConsent === true ? { trustedFastModeConsent: true } : {},
1893
- workingTreeMode: input.request.workingTreeMode
2946
+ createdAt: input.annotation.createdAt
1894
2947
  });
1895
2948
  }
1896
2949
 
1897
- // src/server/request-body.ts
1898
- var import_shared6 = require("@spotpatch/shared");
1899
- function isJsonContentType(value) {
1900
- return value?.split(";", 1)[0]?.trim().toLowerCase() === "application/json";
1901
- }
1902
- async function readJsonRequestBody(request, maximumBytes = MAX_REQUEST_BODY_BYTES) {
1903
- if (!isJsonContentType(request.headers["content-type"])) {
1904
- throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.INVALID_REQUEST);
2950
+ // src/external-handoff/browser-http.ts
2951
+ function matchExternalHandoffBrowserPath(path9) {
2952
+ if (path9 === import_shared12.SPOTPATCH_ENDPOINTS.externalHandoffCapability) return "capability";
2953
+ if (path9 === import_shared12.SPOTPATCH_ENDPOINTS.externalHandoffPublish) return "publish";
2954
+ if (path9 === import_shared12.SPOTPATCH_ENDPOINTS.externalHandoffStatus) return "status";
2955
+ if (path9 === import_shared12.SPOTPATCH_ENDPOINTS.externalHandoffResolveDelivery) {
2956
+ return "resolve-delivery";
1905
2957
  }
1906
- const declaredLength = Number(request.headers["content-length"]);
1907
- if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {
1908
- throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.INVALID_REQUEST);
2958
+ return void 0;
2959
+ }
2960
+ function requireService(options) {
2961
+ if (!options.options.externalAgent.enabled || options.service === void 0) {
2962
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
1909
2963
  }
1910
- const chunks = [];
1911
- let byteLength = 0;
1912
- let exceededLimit = false;
1913
- for await (const rawChunk of request) {
1914
- const chunk = rawChunk;
1915
- if (typeof chunk !== "string" && !(chunk instanceof Uint8Array)) {
1916
- throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.INVALID_REQUEST);
2964
+ return options.service;
2965
+ }
2966
+ function remapAuthorizationError(error) {
2967
+ if (error instanceof import_shared12.SpotPatchError) {
2968
+ if (error.code === import_shared12.ERROR_CODES.SOURCE_NOT_FOUND || error.code === import_shared12.ERROR_CODES.SOURCE_OUTSIDE_ROOT || error.code === import_shared12.ERROR_CODES.SOURCE_TOO_LARGE) {
2969
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.HANDOFF_SOURCE_STALE, void 0, {
2970
+ cause: error
2971
+ });
1917
2972
  }
1918
- const buffer = Buffer.from(chunk);
1919
- byteLength += buffer.byteLength;
1920
- if (byteLength > maximumBytes) {
1921
- exceededLimit = true;
1922
- continue;
2973
+ if (error.code === import_shared12.ERROR_CODES.INVALID_REQUEST) {
2974
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.HANDOFF_VALIDATION_FAILED, void 0, {
2975
+ cause: error
2976
+ });
1923
2977
  }
1924
- chunks.push(buffer);
1925
2978
  }
1926
- if (exceededLimit || byteLength === 0) {
1927
- throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.INVALID_REQUEST);
2979
+ throw error;
2980
+ }
2981
+ async function handleExternalHandoffBrowserRequest(request, response, route, options, writeSuccess) {
2982
+ if (request.method !== "POST") {
2983
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
1928
2984
  }
1929
- try {
1930
- return JSON.parse(Buffer.concat(chunks).toString("utf8"));
1931
- } catch (error) {
1932
- throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.INVALID_REQUEST, void 0, {
1933
- cause: error
1934
- });
2985
+ const service = requireService(options);
2986
+ if (route === "capability") {
2987
+ const parsed2 = import_shared12.externalHandoffCapabilityRequestSchema.safeParse(
2988
+ await readJsonRequestBody(request)
2989
+ );
2990
+ if (!parsed2.success) throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
2991
+ writeSuccess(response, 200, service.capability());
2992
+ return;
2993
+ }
2994
+ if (route === "status") {
2995
+ const parsed2 = import_shared12.externalHandoffStatusRequestSchema.safeParse(
2996
+ await readJsonRequestBody(request)
2997
+ );
2998
+ if (!parsed2.success) throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
2999
+ writeSuccess(response, 200, service.status(parsed2.data.cursor));
3000
+ return;
3001
+ }
3002
+ if (route === "resolve-delivery") {
3003
+ const parsed2 = import_shared12.externalHandoffResolveDeliveryRequestSchema.safeParse(
3004
+ await readJsonRequestBody(request)
3005
+ );
3006
+ if (!parsed2.success) throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
3007
+ writeSuccess(response, 200, service.resolveDelivery(parsed2.data.cursor));
3008
+ return;
3009
+ }
3010
+ const parsed = import_shared12.externalHandoffPublishRequestSchema.safeParse(
3011
+ await readJsonRequestBody(request, import_shared12.EXTERNAL_HANDOFF_LIMITS.maximumPublishBodyBytes)
3012
+ );
3013
+ if (!parsed.success) {
3014
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
1935
3015
  }
3016
+ const result = await service.publish(parsed.data, async (annotation) => {
3017
+ try {
3018
+ return await authorizeAnnotation({
3019
+ annotation,
3020
+ options: options.options,
3021
+ registry: options.registry,
3022
+ root: options.root
3023
+ });
3024
+ } catch (error) {
3025
+ remapAuthorizationError(error);
3026
+ }
3027
+ });
3028
+ writeSuccess(response, result.replayed ? 200 : 201, result);
3029
+ }
3030
+
3031
+ // src/server/agent-http.ts
3032
+ var import_shared14 = require("@spotpatch/shared");
3033
+
3034
+ // src/server/agent-request.ts
3035
+ var import_shared13 = require("@spotpatch/shared");
3036
+ async function authorizeAgentJobRequest(input) {
3037
+ const annotation = await authorizeAnnotation({
3038
+ annotation: input.request.annotation,
3039
+ options: input.options,
3040
+ registry: input.registry,
3041
+ root: input.root
3042
+ });
3043
+ return Object.freeze({
3044
+ annotation,
3045
+ ...input.request.applyMode === void 0 ? {} : { applyMode: input.request.applyMode },
3046
+ providerProfileId: input.request.providerProfileId,
3047
+ modelProfileId: input.request.modelProfileId,
3048
+ providerDataConsent: true,
3049
+ ...input.request.trustedFastModeConsent === true ? { trustedFastModeConsent: true } : {},
3050
+ workingTreeMode: input.request.workingTreeMode
3051
+ });
1936
3052
  }
1937
3053
 
1938
3054
  // src/server/agent-http.ts
@@ -1952,21 +3068,21 @@ var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
1952
3068
  "reverted",
1953
3069
  "failed"
1954
3070
  ]);
1955
- function matchAgentRequestPath(path8) {
1956
- if (path8 === import_shared7.SPOTPATCH_ENDPOINTS.agentCapability) {
3071
+ function matchAgentRequestPath(path9) {
3072
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.agentCapability) {
1957
3073
  return Object.freeze({ kind: "capability" });
1958
3074
  }
1959
- if (path8 === import_shared7.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
3075
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
1960
3076
  return Object.freeze({ kind: "workspace-health" });
1961
3077
  }
1962
- if (path8 === import_shared7.SPOTPATCH_ENDPOINTS.agentJobs) {
3078
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.agentJobs) {
1963
3079
  return Object.freeze({ kind: "create-job" });
1964
3080
  }
1965
- const prefix = `${import_shared7.SPOTPATCH_ENDPOINTS.agentJobs}/`;
1966
- if (!path8.startsWith(prefix)) {
3081
+ const prefix = `${import_shared14.SPOTPATCH_ENDPOINTS.agentJobs}/`;
3082
+ if (!path9.startsWith(prefix)) {
1967
3083
  return void 0;
1968
3084
  }
1969
- const segments = path8.slice(prefix.length).split("/");
3085
+ const segments = path9.slice(prefix.length).split("/");
1970
3086
  const jobId = segments[0];
1971
3087
  const action = segments[1];
1972
3088
  if (segments.length !== 2 || jobId === void 0 || !AGENT_JOB_ID_PATTERN.test(jobId) || action === void 0 || !AGENT_JOB_ACTIONS.has(action)) {
@@ -1980,7 +3096,7 @@ function matchAgentRequestPath(path8) {
1980
3096
  }
1981
3097
  function requireAgentManager(options) {
1982
3098
  if (options.agentManager === void 0 || options.options.ai === false) {
1983
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.AI_DISABLED);
3099
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.AI_DISABLED);
1984
3100
  }
1985
3101
  return options.agentManager;
1986
3102
  }
@@ -2033,13 +3149,13 @@ function streamAgentJobEvents(response, manager, jobId) {
2033
3149
  }
2034
3150
  async function handleCapability(request, response, options, writeSuccess) {
2035
3151
  if (request.method !== "POST") {
2036
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3152
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
2037
3153
  }
2038
- const parsed = import_shared7.agentCapabilityRequestSchema.safeParse(
3154
+ const parsed = import_shared14.agentCapabilityRequestSchema.safeParse(
2039
3155
  await readJsonRequestBody(request)
2040
3156
  );
2041
3157
  if (!parsed.success) {
2042
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3158
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
2043
3159
  }
2044
3160
  const controller = new AbortController();
2045
3161
  const abort = () => {
@@ -2058,13 +3174,13 @@ async function handleCapability(request, response, options, writeSuccess) {
2058
3174
  }
2059
3175
  async function handleCreateJob(request, response, options, writeSuccess) {
2060
3176
  if (request.method !== "POST") {
2061
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3177
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
2062
3178
  }
2063
- const parsed = import_shared7.agentJobCreateRequestSchema.safeParse(
3179
+ const parsed = import_shared14.agentJobCreateRequestSchema.safeParse(
2064
3180
  await readJsonRequestBody(request, MAX_AGENT_REQUEST_BODY_BYTES)
2065
3181
  );
2066
3182
  if (!parsed.success) {
2067
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3183
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
2068
3184
  }
2069
3185
  const authorizedRequest = await authorizeAgentJobRequest({
2070
3186
  request: parsed.data,
@@ -2077,13 +3193,13 @@ async function handleCreateJob(request, response, options, writeSuccess) {
2077
3193
  }
2078
3194
  async function handleWorkspaceHealth(request, response, options, writeSuccess) {
2079
3195
  if (request.method !== "POST") {
2080
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3196
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
2081
3197
  }
2082
- const parsed = import_shared7.agentWorkspaceHealthRequestSchema.safeParse(
3198
+ const parsed = import_shared14.agentWorkspaceHealthRequestSchema.safeParse(
2083
3199
  await readJsonRequestBody(request)
2084
3200
  );
2085
3201
  if (!parsed.success) {
2086
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3202
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
2087
3203
  }
2088
3204
  const controller = new AbortController();
2089
3205
  const abort = () => {
@@ -2100,13 +3216,13 @@ async function handleWorkspaceHealth(request, response, options, writeSuccess) {
2100
3216
  async function handleJobAction(request, response, options, route, writeSuccess) {
2101
3217
  const manager = requireAgentManager(options);
2102
3218
  if (request.method !== "POST") {
2103
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3219
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
2104
3220
  }
2105
- const parsed = import_shared7.agentJobActionRequestSchema.safeParse(
3221
+ const parsed = import_shared14.agentJobActionRequestSchema.safeParse(
2106
3222
  await readJsonRequestBody(request)
2107
3223
  );
2108
3224
  if (!parsed.success) {
2109
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3225
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
2110
3226
  }
2111
3227
  if (route.action === "events") {
2112
3228
  streamAgentJobEvents(response, manager, route.jobId);
@@ -2221,27 +3337,27 @@ function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
2221
3337
  var launchConfiguredEditor = createEditorLauncher();
2222
3338
 
2223
3339
  // src/server/data-flow-http.ts
2224
- var import_node_crypto4 = require("crypto");
3340
+ var import_node_crypto9 = require("crypto");
2225
3341
  var import_analyzer = require("@spotpatch/analyzer");
2226
- var import_shared8 = require("@spotpatch/shared");
3342
+ var import_shared15 = require("@spotpatch/shared");
2227
3343
  function envelopeBytes(report) {
2228
3344
  return Buffer.byteLength(JSON.stringify({ ok: true, data: report }), "utf8");
2229
3345
  }
2230
3346
  function limitDataFlowReportToBytes(report, maximumBytes) {
2231
- const structurallyLimited = (0, import_shared8.limitDataFlowReportCollections)(report);
3347
+ const structurallyLimited = (0, import_shared15.limitDataFlowReportCollections)(report);
2232
3348
  if (envelopeBytes(structurallyLimited) <= maximumBytes) {
2233
3349
  return structurallyLimited;
2234
3350
  }
2235
- let limited = (0, import_shared8.limitDataFlowReportCollections)(structurallyLimited, {
3351
+ let limited = (0, import_shared15.limitDataFlowReportCollections)(structurallyLimited, {
2236
3352
  forceTruncation: true,
2237
3353
  maximumDependencies: 0,
2238
3354
  truncatedBy: "bytes"
2239
3355
  });
2240
3356
  if (envelopeBytes(limited) > maximumBytes) {
2241
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.INTERNAL_ERROR);
3357
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INTERNAL_ERROR);
2242
3358
  }
2243
3359
  for (let maximumDependencies = 1; maximumDependencies <= structurallyLimited.dependencies.length; maximumDependencies += 1) {
2244
- const candidate = (0, import_shared8.limitDataFlowReportCollections)(structurallyLimited, {
3360
+ const candidate = (0, import_shared15.limitDataFlowReportCollections)(structurallyLimited, {
2245
3361
  forceTruncation: true,
2246
3362
  maximumDependencies,
2247
3363
  truncatedBy: "bytes"
@@ -2267,7 +3383,7 @@ async function analyzeTarget(request, analyzer, options) {
2267
3383
  request.componentSourceId
2268
3384
  );
2269
3385
  if (anchor?.sourceVersion !== request.sourceVersion) {
2270
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
3386
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
2271
3387
  }
2272
3388
  return anchor;
2273
3389
  }
@@ -2284,7 +3400,7 @@ async function analyzeTarget(request, analyzer, options) {
2284
3400
  column: resolvedRequest.column
2285
3401
  });
2286
3402
  if (resolvedRequest.sourceVersion !== void 0 && resolvedRequest.sourceVersion !== report.component.source.sourceVersion) {
2287
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
3403
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
2288
3404
  }
2289
3405
  return limitDataFlowReportToBytes(
2290
3406
  report,
@@ -2293,31 +3409,31 @@ async function analyzeTarget(request, analyzer, options) {
2293
3409
  }
2294
3410
  function requireAnalyzer(analyzer) {
2295
3411
  if (analyzer === void 0) {
2296
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.DATA_FLOW_DISABLED);
3412
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.DATA_FLOW_DISABLED);
2297
3413
  }
2298
3414
  return analyzer;
2299
3415
  }
2300
3416
  async function handleComponentDataFlowReport(request, analyzer, options) {
2301
- const parsed = import_shared8.dataFlowComponentReportRequestSchema.safeParse(
3417
+ const parsed = import_shared15.dataFlowComponentReportRequestSchema.safeParse(
2302
3418
  await readJsonRequestBody(
2303
3419
  request,
2304
3420
  options.options.dataFlow.limits.protocolRequestMaxBytes
2305
3421
  )
2306
3422
  );
2307
3423
  if (!parsed.success) {
2308
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.INVALID_REQUEST);
3424
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2309
3425
  }
2310
3426
  return analyzeTarget(parsed.data, requireAnalyzer(analyzer), options);
2311
3427
  }
2312
3428
  async function handlePageDataFlowReport(request, analyzer, options) {
2313
- const parsed = import_shared8.dataFlowPageReportRequestSchema.safeParse(
3429
+ const parsed = import_shared15.dataFlowPageReportRequestSchema.safeParse(
2314
3430
  await readJsonRequestBody(
2315
3431
  request,
2316
3432
  options.options.dataFlow.limits.protocolRequestMaxBytes
2317
3433
  )
2318
3434
  );
2319
3435
  if (!parsed.success) {
2320
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.INVALID_REQUEST);
3436
+ throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
2321
3437
  }
2322
3438
  const activeAnalyzer = requireAnalyzer(analyzer);
2323
3439
  const componentReports = await Promise.all(
@@ -2341,10 +3457,10 @@ async function handlePageDataFlowReport(request, analyzer, options) {
2341
3457
  const analyzedVersions = new Set(
2342
3458
  componentReports.flatMap((report2) => report2.baseline.analyzedSourceVersions)
2343
3459
  );
2344
- const reportId = `page_${(0, import_node_crypto4.createHash)("sha256").update(componentReports.map((report2) => report2.reportId).join("\0")).digest("base64url").slice(0, 22)}`;
3460
+ const reportId = `page_${(0, import_node_crypto9.createHash)("sha256").update(componentReports.map((report2) => report2.reportId).join("\0")).digest("base64url").slice(0, 22)}`;
2345
3461
  const complete = componentReports.every((report2) => report2.completeness.complete);
2346
3462
  const report = Object.freeze({
2347
- schemaVersion: import_shared8.DATA_FLOW_SCHEMA_VERSION,
3463
+ schemaVersion: import_shared15.DATA_FLOW_SCHEMA_VERSION,
2348
3464
  reportId,
2349
3465
  baseline: Object.freeze({
2350
3466
  registryEpoch: options.session.id,
@@ -2388,20 +3504,20 @@ async function handlePageDataFlowReport(request, analyzer, options) {
2388
3504
  }
2389
3505
 
2390
3506
  // src/server/request-security.ts
2391
- var import_node_crypto5 = require("crypto");
3507
+ var import_node_crypto10 = require("crypto");
2392
3508
  var import_node_net = require("net");
2393
- var import_shared9 = require("@spotpatch/shared");
3509
+ var import_shared16 = require("@spotpatch/shared");
2394
3510
  function getSingleHeader(request, name) {
2395
3511
  const value = request.headers[name.toLowerCase()];
2396
3512
  return Array.isArray(value) ? value[0] : value;
2397
3513
  }
2398
- function tokensMatch(actual, expected) {
3514
+ function tokensMatch2(actual, expected) {
2399
3515
  if (actual === void 0) {
2400
3516
  return false;
2401
3517
  }
2402
3518
  const actualBytes = Buffer.from(actual);
2403
3519
  const expectedBytes = Buffer.from(expected);
2404
- return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto5.timingSafeEqual)(actualBytes, expectedBytes);
3520
+ return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto10.timingSafeEqual)(actualBytes, expectedBytes);
2405
3521
  }
2406
3522
  function isLoopbackHostname(hostname) {
2407
3523
  const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
@@ -2435,32 +3551,32 @@ function parseOrigin(value) {
2435
3551
  }
2436
3552
  }
2437
3553
  function assertRequestAuthorized(request, options) {
2438
- const actualToken = getSingleHeader(request, import_shared9.SPOTPATCH_TOKEN_HEADER);
2439
- if (!tokensMatch(actualToken, options.sessionToken)) {
2440
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.INVALID_TOKEN);
3554
+ const actualToken = getSingleHeader(request, import_shared16.SPOTPATCH_TOKEN_HEADER);
3555
+ if (!tokensMatch2(actualToken, options.sessionToken)) {
3556
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_TOKEN);
2441
3557
  }
2442
3558
  const hostHeader = getSingleHeader(request, "host");
2443
3559
  const originHeader = getSingleHeader(request, "origin");
2444
3560
  const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
2445
3561
  const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
2446
3562
  if (host === void 0 || origin === void 0) {
2447
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3563
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.ORIGIN_NOT_ALLOWED);
2448
3564
  }
2449
3565
  const hostIsLoopback = isLoopbackHostname(host.hostname);
2450
3566
  const originIsLoopback = isLoopbackHostname(origin.hostname);
2451
3567
  if (!options.allowLan) {
2452
3568
  if (!hostIsLoopback || !originIsLoopback) {
2453
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3569
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.ORIGIN_NOT_ALLOWED);
2454
3570
  }
2455
3571
  return;
2456
3572
  }
2457
3573
  if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
2458
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3574
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.ORIGIN_NOT_ALLOWED);
2459
3575
  }
2460
3576
  }
2461
3577
 
2462
3578
  // src/server/runtime-bootstrap.ts
2463
- var import_shared10 = require("@spotpatch/shared");
3579
+ var import_shared17 = require("@spotpatch/shared");
2464
3580
  function getSingleHeader2(request, name) {
2465
3581
  const value = request.headers[name.toLowerCase()];
2466
3582
  return Array.isArray(value) ? value[0] : value;
@@ -2475,7 +3591,7 @@ function resolveRuntimeBootstrapOptions(options) {
2475
3591
  if (expectedOrigin.origin !== options.expectedOrigin || expectedOrigin.protocol !== "http:" || !isLoopbackHostname(expectedOrigin.hostname)) {
2476
3592
  throw new TypeError("The SpotPatch bootstrap origin must be a loopback origin.");
2477
3593
  }
2478
- const parsedConfig = import_shared10.runtimeConfigSchema.safeParse(options.runtimeConfig);
3594
+ const parsedConfig = import_shared17.runtimeConfigSchema.safeParse(options.runtimeConfig);
2479
3595
  if (!parsedConfig.success) {
2480
3596
  throw new TypeError("The SpotPatch Runtime configuration is invalid.");
2481
3597
  }
@@ -2487,7 +3603,7 @@ function resolveRuntimeBootstrapOptions(options) {
2487
3603
  function assertRuntimeBootstrapRequest(request, expectedOrigin) {
2488
3604
  const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
2489
3605
  if (request.method !== "POST" || contentType !== "application/json") {
2490
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.INVALID_REQUEST);
3606
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
2491
3607
  }
2492
3608
  const host = getSingleHeader2(request, "host");
2493
3609
  let hostIsLoopback = false;
@@ -2499,112 +3615,148 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
2499
3615
  }
2500
3616
  }
2501
3617
  if (!hostIsLoopback || getSingleHeader2(request, "origin") !== expectedOrigin || getSingleHeader2(request, "sec-fetch-site") !== "same-origin") {
2502
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3618
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.ORIGIN_NOT_ALLOWED);
2503
3619
  }
2504
3620
  }
2505
3621
  async function readRuntimeBootstrap(request, options) {
2506
3622
  assertRuntimeBootstrapRequest(request, options.expectedOrigin);
2507
- const parsedBody = import_shared10.runtimeBootstrapRequestSchema.safeParse(
3623
+ const parsedBody = import_shared17.runtimeBootstrapRequestSchema.safeParse(
2508
3624
  await readJsonRequestBody(request)
2509
3625
  );
2510
3626
  if (!parsedBody.success) {
2511
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.INVALID_REQUEST);
3627
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
2512
3628
  }
2513
3629
  return options.runtimeConfig;
2514
3630
  }
2515
3631
 
2516
3632
  // src/server/middleware.ts
2517
3633
  var STATUS_BY_ERROR = Object.freeze({
2518
- [import_shared11.ERROR_CODES.INVALID_REQUEST]: 400,
2519
- [import_shared11.ERROR_CODES.INVALID_TOKEN]: 401,
2520
- [import_shared11.ERROR_CODES.ORIGIN_NOT_ALLOWED]: 403,
2521
- [import_shared11.ERROR_CODES.SOURCE_NOT_FOUND]: 404,
2522
- [import_shared11.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: 403,
2523
- [import_shared11.ERROR_CODES.SOURCE_TOO_LARGE]: 413,
2524
- [import_shared11.ERROR_CODES.EDITOR_OPEN_FAILED]: 500,
2525
- [import_shared11.ERROR_CODES.DATA_FLOW_DISABLED]: 404,
2526
- [import_shared11.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: 409,
2527
- [import_shared11.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
2528
- [import_shared11.ERROR_CODES.AI_DISABLED]: 404,
2529
- [import_shared11.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: 503,
2530
- [import_shared11.ERROR_CODES.PROVIDER_AUTH_FAILED]: 502,
2531
- [import_shared11.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
2532
- [import_shared11.ERROR_CODES.MODEL_NOT_ALLOWED]: 400,
2533
- [import_shared11.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
2534
- [import_shared11.ERROR_CODES.PROVIDER_RATE_LIMITED]: 429,
2535
- [import_shared11.ERROR_CODES.AGENT_BUSY]: 409,
2536
- [import_shared11.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: 413,
2537
- [import_shared11.ERROR_CODES.AGENT_CANCELLED]: 409,
2538
- [import_shared11.ERROR_CODES.WORKTREE_DIRTY]: 409,
2539
- [import_shared11.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: 409,
2540
- [import_shared11.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: 409,
2541
- [import_shared11.ERROR_CODES.WORKTREE_CONFLICTED]: 409,
2542
- [import_shared11.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
2543
- [import_shared11.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
2544
- [import_shared11.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
2545
- [import_shared11.ERROR_CODES.TOOL_DENIED]: 403,
2546
- [import_shared11.ERROR_CODES.TOOL_INPUT_INVALID]: 422,
2547
- [import_shared11.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: 422,
2548
- [import_shared11.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: 422,
2549
- [import_shared11.ERROR_CODES.TOOL_PATH_DENIED]: 403,
2550
- [import_shared11.ERROR_CODES.PATCH_REJECTED]: 422,
2551
- [import_shared11.ERROR_CODES.VALIDATION_FAILED]: 422,
2552
- [import_shared11.ERROR_CODES.APPLY_CONFLICT]: 409,
2553
- [import_shared11.ERROR_CODES.INTERNAL_ERROR]: 500
3634
+ [import_shared18.ERROR_CODES.INVALID_REQUEST]: 400,
3635
+ [import_shared18.ERROR_CODES.INVALID_TOKEN]: 401,
3636
+ [import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED]: 403,
3637
+ [import_shared18.ERROR_CODES.SOURCE_NOT_FOUND]: 404,
3638
+ [import_shared18.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: 403,
3639
+ [import_shared18.ERROR_CODES.SOURCE_TOO_LARGE]: 413,
3640
+ [import_shared18.ERROR_CODES.EDITOR_OPEN_FAILED]: 500,
3641
+ [import_shared18.ERROR_CODES.DATA_FLOW_DISABLED]: 404,
3642
+ [import_shared18.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: 409,
3643
+ [import_shared18.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
3644
+ [import_shared18.ERROR_CODES.AI_DISABLED]: 404,
3645
+ [import_shared18.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: 503,
3646
+ [import_shared18.ERROR_CODES.PROVIDER_AUTH_FAILED]: 502,
3647
+ [import_shared18.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
3648
+ [import_shared18.ERROR_CODES.MODEL_NOT_ALLOWED]: 400,
3649
+ [import_shared18.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
3650
+ [import_shared18.ERROR_CODES.PROVIDER_RATE_LIMITED]: 429,
3651
+ [import_shared18.ERROR_CODES.AGENT_BUSY]: 409,
3652
+ [import_shared18.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: 413,
3653
+ [import_shared18.ERROR_CODES.AGENT_CANCELLED]: 409,
3654
+ [import_shared18.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: 404,
3655
+ [import_shared18.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: 503,
3656
+ [import_shared18.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: 422,
3657
+ [import_shared18.ERROR_CODES.HANDOFF_SOURCE_STALE]: 409,
3658
+ [import_shared18.ERROR_CODES.HANDOFF_NOT_FOUND]: 404,
3659
+ [import_shared18.ERROR_CODES.HANDOFF_EXPIRED]: 410,
3660
+ [import_shared18.ERROR_CODES.HANDOFF_CURSOR_INVALID]: 409,
3661
+ [import_shared18.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: 413,
3662
+ [import_shared18.ERROR_CODES.BRIDGE_UNAUTHORIZED]: 401,
3663
+ [import_shared18.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: 409,
3664
+ [import_shared18.ERROR_CODES.BRIDGE_BUSY]: 429,
3665
+ [import_shared18.ERROR_CODES.EXTERNAL_AGENT_BUSY]: 409,
3666
+ [import_shared18.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: 409,
3667
+ [import_shared18.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: 409,
3668
+ [import_shared18.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: 409,
3669
+ [import_shared18.ERROR_CODES.SESSION_NOT_FOUND]: 404,
3670
+ [import_shared18.ERROR_CODES.SESSION_AMBIGUOUS]: 409,
3671
+ [import_shared18.ERROR_CODES.SESSION_CLOSED]: 410,
3672
+ [import_shared18.ERROR_CODES.WORKTREE_DIRTY]: 409,
3673
+ [import_shared18.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: 409,
3674
+ [import_shared18.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: 409,
3675
+ [import_shared18.ERROR_CODES.WORKTREE_CONFLICTED]: 409,
3676
+ [import_shared18.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
3677
+ [import_shared18.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
3678
+ [import_shared18.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
3679
+ [import_shared18.ERROR_CODES.TOOL_DENIED]: 403,
3680
+ [import_shared18.ERROR_CODES.TOOL_INPUT_INVALID]: 422,
3681
+ [import_shared18.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: 422,
3682
+ [import_shared18.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: 422,
3683
+ [import_shared18.ERROR_CODES.TOOL_PATH_DENIED]: 403,
3684
+ [import_shared18.ERROR_CODES.PATCH_REJECTED]: 422,
3685
+ [import_shared18.ERROR_CODES.VALIDATION_FAILED]: 422,
3686
+ [import_shared18.ERROR_CODES.APPLY_CONFLICT]: 409,
3687
+ [import_shared18.ERROR_CODES.INTERNAL_ERROR]: 500
2554
3688
  });
2555
3689
  var PUBLIC_MESSAGES = Object.freeze({
2556
- [import_shared11.ERROR_CODES.INVALID_REQUEST]: "The request is invalid.",
2557
- [import_shared11.ERROR_CODES.INVALID_TOKEN]: "The session token is invalid.",
2558
- [import_shared11.ERROR_CODES.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
2559
- [import_shared11.ERROR_CODES.SOURCE_NOT_FOUND]: "The source file is unavailable.",
2560
- [import_shared11.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
2561
- [import_shared11.ERROR_CODES.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
2562
- [import_shared11.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
2563
- [import_shared11.ERROR_CODES.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
2564
- [import_shared11.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
2565
- [import_shared11.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
2566
- [import_shared11.ERROR_CODES.AI_DISABLED]: "AI execution is not enabled.",
2567
- [import_shared11.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
2568
- [import_shared11.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
2569
- [import_shared11.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
2570
- [import_shared11.ERROR_CODES.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
2571
- [import_shared11.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
2572
- [import_shared11.ERROR_CODES.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
2573
- [import_shared11.ERROR_CODES.AGENT_BUSY]: "Another Agent job is already running.",
2574
- [import_shared11.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
2575
- [import_shared11.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
2576
- [import_shared11.ERROR_CODES.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
2577
- [import_shared11.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
2578
- [import_shared11.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
2579
- [import_shared11.ERROR_CODES.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
2580
- [import_shared11.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
2581
- [import_shared11.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
2582
- [import_shared11.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
2583
- [import_shared11.ERROR_CODES.TOOL_DENIED]: "The Agent tool request was denied.",
2584
- [import_shared11.ERROR_CODES.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
2585
- [import_shared11.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
2586
- [import_shared11.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
2587
- [import_shared11.ERROR_CODES.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
2588
- [import_shared11.ERROR_CODES.PATCH_REJECTED]: "The proposed patch was rejected.",
2589
- [import_shared11.ERROR_CODES.VALIDATION_FAILED]: "The proposed change failed validation.",
2590
- [import_shared11.ERROR_CODES.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
2591
- [import_shared11.ERROR_CODES.INTERNAL_ERROR]: "The request could not be completed."
3690
+ [import_shared18.ERROR_CODES.INVALID_REQUEST]: "The request is invalid.",
3691
+ [import_shared18.ERROR_CODES.INVALID_TOKEN]: "The session token is invalid.",
3692
+ [import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
3693
+ [import_shared18.ERROR_CODES.SOURCE_NOT_FOUND]: "The source file is unavailable.",
3694
+ [import_shared18.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
3695
+ [import_shared18.ERROR_CODES.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
3696
+ [import_shared18.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
3697
+ [import_shared18.ERROR_CODES.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
3698
+ [import_shared18.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
3699
+ [import_shared18.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
3700
+ [import_shared18.ERROR_CODES.AI_DISABLED]: "AI execution is not enabled.",
3701
+ [import_shared18.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
3702
+ [import_shared18.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
3703
+ [import_shared18.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
3704
+ [import_shared18.ERROR_CODES.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
3705
+ [import_shared18.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
3706
+ [import_shared18.ERROR_CODES.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
3707
+ [import_shared18.ERROR_CODES.AGENT_BUSY]: "Another Agent job is already running.",
3708
+ [import_shared18.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
3709
+ [import_shared18.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
3710
+ [import_shared18.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: "External Agent handoff is not enabled.",
3711
+ [import_shared18.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: "External Agent handoff is temporarily unavailable.",
3712
+ [import_shared18.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: "The handoff content is invalid.",
3713
+ [import_shared18.ERROR_CODES.HANDOFF_SOURCE_STALE]: "The selected source is stale.",
3714
+ [import_shared18.ERROR_CODES.HANDOFF_NOT_FOUND]: "No current handoff is available.",
3715
+ [import_shared18.ERROR_CODES.HANDOFF_EXPIRED]: "The handoff has expired.",
3716
+ [import_shared18.ERROR_CODES.HANDOFF_CURSOR_INVALID]: "The handoff cursor is invalid.",
3717
+ [import_shared18.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: "The handoff exceeds the size limit.",
3718
+ [import_shared18.ERROR_CODES.BRIDGE_UNAUTHORIZED]: "The local bridge request is unauthorized.",
3719
+ [import_shared18.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: "The local bridge protocol is incompatible.",
3720
+ [import_shared18.ERROR_CODES.BRIDGE_BUSY]: "The local bridge is busy.",
3721
+ [import_shared18.ERROR_CODES.EXTERNAL_AGENT_BUSY]: "The connected external Agent is busy.",
3722
+ [import_shared18.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: "Another active Agent adapter is connected.",
3723
+ [import_shared18.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: "The active Agent adapter lease is invalid.",
3724
+ [import_shared18.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: "The active Agent dispatch transition is invalid.",
3725
+ [import_shared18.ERROR_CODES.SESSION_NOT_FOUND]: "No active SpotPatch session was found.",
3726
+ [import_shared18.ERROR_CODES.SESSION_AMBIGUOUS]: "More than one SpotPatch session matches.",
3727
+ [import_shared18.ERROR_CODES.SESSION_CLOSED]: "The SpotPatch session has closed.",
3728
+ [import_shared18.ERROR_CODES.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
3729
+ [import_shared18.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
3730
+ [import_shared18.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
3731
+ [import_shared18.ERROR_CODES.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
3732
+ [import_shared18.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
3733
+ [import_shared18.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
3734
+ [import_shared18.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
3735
+ [import_shared18.ERROR_CODES.TOOL_DENIED]: "The Agent tool request was denied.",
3736
+ [import_shared18.ERROR_CODES.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
3737
+ [import_shared18.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
3738
+ [import_shared18.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
3739
+ [import_shared18.ERROR_CODES.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
3740
+ [import_shared18.ERROR_CODES.PATCH_REJECTED]: "The proposed patch was rejected.",
3741
+ [import_shared18.ERROR_CODES.VALIDATION_FAILED]: "The proposed change failed validation.",
3742
+ [import_shared18.ERROR_CODES.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
3743
+ [import_shared18.ERROR_CODES.INTERNAL_ERROR]: "The request could not be completed."
2592
3744
  });
2593
- function writeJson(response, status, payload) {
3745
+ function writeJson2(response, status, payload) {
2594
3746
  response.statusCode = status;
2595
3747
  response.setHeader("Cache-Control", "no-store");
2596
3748
  response.setHeader("Content-Type", "application/json; charset=utf-8");
2597
3749
  response.end(JSON.stringify(payload));
2598
3750
  }
2599
3751
  function asSpotPatchError(error) {
2600
- return error instanceof import_shared11.SpotPatchError ? error : new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
3752
+ return error instanceof import_shared18.SpotPatchError ? error : new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
2601
3753
  }
2602
3754
  function writeError(response, error, logger) {
2603
3755
  const normalized = asSpotPatchError(error);
2604
- if (normalized.code === import_shared11.ERROR_CODES.INTERNAL_ERROR) {
3756
+ if (normalized.code === import_shared18.ERROR_CODES.INTERNAL_ERROR) {
2605
3757
  logger?.warn("[spotpatch:server] Internal request failure.");
2606
3758
  }
2607
- writeJson(response, STATUS_BY_ERROR[normalized.code], {
3759
+ writeJson2(response, STATUS_BY_ERROR[normalized.code], {
2608
3760
  ok: false,
2609
3761
  error: {
2610
3762
  code: normalized.code,
@@ -2620,11 +3772,11 @@ function requestPath(request) {
2620
3772
  }
2621
3773
  }
2622
3774
  async function handleSourceContext(request, options) {
2623
- const parsed = import_shared11.sourceContextRequestSchema.safeParse(
3775
+ const parsed = import_shared18.sourceContextRequestSchema.safeParse(
2624
3776
  await readJsonRequestBody(request)
2625
3777
  );
2626
3778
  if (!parsed.success) {
2627
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
3779
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
2628
3780
  }
2629
3781
  return readSourceContext({
2630
3782
  request: parsed.data,
@@ -2635,9 +3787,9 @@ async function handleSourceContext(request, options) {
2635
3787
  });
2636
3788
  }
2637
3789
  async function handleOpenEditor(request, options) {
2638
- const parsed = import_shared11.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
3790
+ const parsed = import_shared18.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
2639
3791
  if (!parsed.success) {
2640
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
3792
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
2641
3793
  }
2642
3794
  const body = parsed.data;
2643
3795
  const sourcePath = await resolveSourceFile({
@@ -2654,7 +3806,7 @@ async function handleOpenEditor(request, options) {
2654
3806
  options.logger?.warn(
2655
3807
  `[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
2656
3808
  );
2657
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
3809
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
2658
3810
  cause: error
2659
3811
  });
2660
3812
  }
@@ -2663,63 +3815,81 @@ function createSpotPatchMiddleware(options) {
2663
3815
  const bootstrap = options.bootstrap === void 0 ? void 0 : resolveRuntimeBootstrapOptions(options.bootstrap);
2664
3816
  const dataFlowAnalyzer = createDataFlowAnalyzer(options);
2665
3817
  return (request, response, next) => {
2666
- const path8 = requestPath(request);
2667
- const agentRoute = matchAgentRequestPath(path8);
2668
- if (path8 !== import_shared11.SPOTPATCH_ENDPOINTS.sourceContext && path8 !== import_shared11.SPOTPATCH_ENDPOINTS.openEditor && path8 !== import_shared11.SPOTPATCH_ENDPOINTS.dataFlowComponentReport && path8 !== import_shared11.SPOTPATCH_ENDPOINTS.dataFlowPageReport && agentRoute === void 0 && !path8.startsWith(`${import_shared11.SPOTPATCH_API_BASE}/`)) {
3818
+ const path9 = requestPath(request);
3819
+ const agentRoute = matchAgentRequestPath(path9);
3820
+ const externalHandoffRoute = matchExternalHandoffBrowserPath(path9);
3821
+ if (path9 !== import_shared18.SPOTPATCH_ENDPOINTS.sourceContext && path9 !== import_shared18.SPOTPATCH_ENDPOINTS.openEditor && path9 !== import_shared18.SPOTPATCH_ENDPOINTS.dataFlowComponentReport && path9 !== import_shared18.SPOTPATCH_ENDPOINTS.dataFlowPageReport && agentRoute === void 0 && externalHandoffRoute === void 0 && !path9.startsWith(`${import_shared18.SPOTPATCH_API_BASE}/`)) {
2669
3822
  next();
2670
3823
  return;
2671
3824
  }
2672
3825
  const handle = async () => {
2673
- if (path8 === import_shared11.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
3826
+ if (path9 === import_shared18.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
2674
3827
  const data = await readRuntimeBootstrap(
2675
3828
  request,
2676
3829
  bootstrap
2677
3830
  );
2678
- writeJson(response, 200, { ok: true, data });
3831
+ writeJson2(response, 200, { ok: true, data });
2679
3832
  return;
2680
3833
  }
2681
3834
  assertRequestAuthorized(request, {
2682
3835
  allowLan: options.options.allowLan,
2683
3836
  sessionToken: options.session.token
2684
3837
  });
2685
- if (path8 === import_shared11.SPOTPATCH_ENDPOINTS.sourceContext) {
3838
+ if (path9 === import_shared18.SPOTPATCH_ENDPOINTS.sourceContext) {
2686
3839
  if (request.method !== "POST") {
2687
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
3840
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
2688
3841
  }
2689
3842
  const data = await handleSourceContext(request, options);
2690
- writeJson(response, 200, { ok: true, data });
3843
+ writeJson2(response, 200, { ok: true, data });
2691
3844
  return;
2692
3845
  }
2693
- if (path8 === import_shared11.SPOTPATCH_ENDPOINTS.openEditor) {
3846
+ if (path9 === import_shared18.SPOTPATCH_ENDPOINTS.openEditor) {
2694
3847
  if (request.method !== "POST") {
2695
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
3848
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
2696
3849
  }
2697
3850
  const data = await handleOpenEditor(request, options);
2698
- writeJson(response, 200, { ok: true, data });
3851
+ writeJson2(response, 200, { ok: true, data });
2699
3852
  return;
2700
3853
  }
2701
- if (path8 === import_shared11.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
3854
+ if (path9 === import_shared18.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
2702
3855
  if (request.method !== "POST") {
2703
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
3856
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
2704
3857
  }
2705
3858
  const data = await handleComponentDataFlowReport(
2706
3859
  request,
2707
3860
  dataFlowAnalyzer,
2708
3861
  options
2709
3862
  );
2710
- writeJson(response, 200, { ok: true, data });
3863
+ writeJson2(response, 200, { ok: true, data });
2711
3864
  return;
2712
3865
  }
2713
- if (path8 === import_shared11.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
3866
+ if (path9 === import_shared18.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
2714
3867
  if (request.method !== "POST") {
2715
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
3868
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
2716
3869
  }
2717
3870
  const data = await handlePageDataFlowReport(request, dataFlowAnalyzer, options);
2718
- writeJson(response, 200, { ok: true, data });
3871
+ writeJson2(response, 200, { ok: true, data });
3872
+ return;
3873
+ }
3874
+ if (externalHandoffRoute !== void 0) {
3875
+ await handleExternalHandoffBrowserRequest(
3876
+ request,
3877
+ response,
3878
+ externalHandoffRoute,
3879
+ {
3880
+ options: options.options,
3881
+ registry: options.registry,
3882
+ root: options.root,
3883
+ ...options.externalHandoffService === void 0 ? {} : { service: options.externalHandoffService }
3884
+ },
3885
+ (target, status, data) => {
3886
+ writeJson2(target, status, { ok: true, data });
3887
+ }
3888
+ );
2719
3889
  return;
2720
3890
  }
2721
3891
  if (agentRoute === void 0) {
2722
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
3892
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_REQUEST);
2723
3893
  }
2724
3894
  await handleAgentRequest(
2725
3895
  request,
@@ -2727,7 +3897,7 @@ function createSpotPatchMiddleware(options) {
2727
3897
  options,
2728
3898
  agentRoute,
2729
3899
  (target, status, data) => {
2730
- writeJson(target, status, { ok: true, data });
3900
+ writeJson2(target, status, { ok: true, data });
2731
3901
  }
2732
3902
  );
2733
3903
  };
@@ -2738,9 +3908,9 @@ function createSpotPatchMiddleware(options) {
2738
3908
  }
2739
3909
 
2740
3910
  // src/server/source-registration.ts
2741
- var import_node_crypto6 = require("crypto");
2742
- var import_promises6 = require("fs/promises");
2743
- var import_node_path7 = __toESM(require("path"), 1);
3911
+ var import_node_crypto11 = require("crypto");
3912
+ var import_promises7 = require("fs/promises");
3913
+ var import_node_path8 = __toESM(require("path"), 1);
2744
3914
  var import_compiler = require("@spotpatch/compiler");
2745
3915
  var import_zod2 = require("zod");
2746
3916
  var REGISTRATION_BODY_LIMIT_BYTES = 4096;
@@ -2761,16 +3931,16 @@ function identitiesMatch(actual, expected) {
2761
3931
  }
2762
3932
  const actualBytes = Buffer.from(actual);
2763
3933
  const expectedBytes = Buffer.from(expected);
2764
- return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto6.timingSafeEqual)(actualBytes, expectedBytes);
3934
+ return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto11.timingSafeEqual)(actualBytes, expectedBytes);
2765
3935
  }
2766
3936
  function isWithinRoot(root, candidate) {
2767
- const relative = import_node_path7.default.relative(root, candidate);
2768
- return relative === "" || !relative.startsWith(`..${import_node_path7.default.sep}`) && relative !== ".." && !import_node_path7.default.isAbsolute(relative);
3937
+ const relative = import_node_path8.default.relative(root, candidate);
3938
+ return relative === "" || !relative.startsWith(`..${import_node_path8.default.sep}`) && relative !== ".." && !import_node_path8.default.isAbsolute(relative);
2769
3939
  }
2770
3940
  function hasForbiddenSegment(root, candidate) {
2771
- return import_node_path7.default.relative(root, candidate).split(import_node_path7.default.sep).some((segment) => FORBIDDEN_SOURCE_SEGMENTS.has(segment));
3941
+ return import_node_path8.default.relative(root, candidate).split(import_node_path8.default.sep).some((segment) => FORBIDDEN_SOURCE_SEGMENTS.has(segment));
2772
3942
  }
2773
- function writeJson2(response, statusCode, payload) {
3943
+ function writeJson3(response, statusCode, payload) {
2774
3944
  const body = JSON.stringify(payload);
2775
3945
  response.statusCode = statusCode;
2776
3946
  response.setHeader("Cache-Control", "no-store");
@@ -2790,15 +3960,15 @@ function requestComesFromLoopbackWorker(request) {
2790
3960
  }
2791
3961
  }
2792
3962
  async function resolveAuthorizedSource(root, requestedPath, shouldTransform) {
2793
- if (!import_node_path7.default.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
3963
+ if (!import_node_path8.default.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
2794
3964
  return void 0;
2795
3965
  }
2796
3966
  try {
2797
- const sourceStat = await (0, import_promises6.lstat)(requestedPath);
3967
+ const sourceStat = await (0, import_promises7.lstat)(requestedPath);
2798
3968
  if (!sourceStat.isFile() || sourceStat.isSymbolicLink()) {
2799
3969
  return void 0;
2800
3970
  }
2801
- const resolvedPath = await (0, import_promises6.realpath)(requestedPath);
3971
+ const resolvedPath = await (0, import_promises7.realpath)(requestedPath);
2802
3972
  if (!isWithinRoot(root, resolvedPath) || hasForbiddenSegment(root, resolvedPath) || !shouldTransform(resolvedPath)) {
2803
3973
  return void 0;
2804
3974
  }
@@ -2811,7 +3981,7 @@ async function createSourceRegistrationService(input) {
2811
3981
  if (!REGISTRATION_IDENTITY_PATTERN.test(input.internalSecret) || !REGISTRATION_IDENTITY_PATTERN.test(input.registryEpoch)) {
2812
3982
  throw new TypeError("The source registration identity is invalid.");
2813
3983
  }
2814
- const root = await (0, import_promises6.realpath)(input.root);
3984
+ const root = await (0, import_promises7.realpath)(input.root);
2815
3985
  const sourceFilter = (0, import_compiler.createSourceFilter)(root, input.options);
2816
3986
  const handler = (request, response) => {
2817
3987
  const handle = async () => {
@@ -2820,14 +3990,14 @@ async function createSourceRegistrationService(input) {
2820
3990
  getSingleHeader3(request, INTERNAL_SECRET_HEADER),
2821
3991
  input.internalSecret
2822
3992
  )) {
2823
- writeJson2(response, 403, { ok: false });
3993
+ writeJson3(response, 403, { ok: false });
2824
3994
  return;
2825
3995
  }
2826
3996
  const parsed = registrationRequestSchema.safeParse(
2827
3997
  await readJsonRequestBody(request, REGISTRATION_BODY_LIMIT_BYTES)
2828
3998
  );
2829
3999
  if (!parsed.success || parsed.data.epoch !== input.registryEpoch) {
2830
- writeJson2(response, 400, { ok: false });
4000
+ writeJson3(response, 400, { ok: false });
2831
4001
  return;
2832
4002
  }
2833
4003
  const sourcePath = await resolveAuthorizedSource(
@@ -2836,17 +4006,17 @@ async function createSourceRegistrationService(input) {
2836
4006
  (absolutePath) => sourceFilter.shouldTransform(absolutePath, "<")
2837
4007
  );
2838
4008
  if (sourcePath === void 0) {
2839
- writeJson2(response, 403, { ok: false });
4009
+ writeJson3(response, 403, { ok: false });
2840
4010
  return;
2841
4011
  }
2842
- writeJson2(response, 200, {
4012
+ writeJson3(response, 200, {
2843
4013
  epoch: input.registryEpoch,
2844
4014
  fileId: input.registry.register(sourcePath)
2845
4015
  });
2846
4016
  };
2847
4017
  void handle().catch(() => {
2848
4018
  if (!response.headersSent) {
2849
- writeJson2(response, 400, { ok: false });
4019
+ writeJson3(response, 400, { ok: false });
2850
4020
  } else {
2851
4021
  response.destroy();
2852
4022
  }
@@ -2856,11 +4026,11 @@ async function createSourceRegistrationService(input) {
2856
4026
  }
2857
4027
 
2858
4028
  // src/session/session.ts
2859
- var import_node_crypto7 = require("crypto");
4029
+ var import_node_crypto12 = require("crypto");
2860
4030
  function createSession() {
2861
4031
  return Object.freeze({
2862
- id: (0, import_node_crypto7.randomBytes)(16).toString("base64url"),
2863
- token: (0, import_node_crypto7.randomBytes)(16).toString("base64url")
4032
+ id: (0, import_node_crypto12.randomBytes)(16).toString("base64url"),
4033
+ token: (0, import_node_crypto12.randomBytes)(16).toString("base64url")
2864
4034
  });
2865
4035
  }
2866
4036
 
@@ -2873,6 +4043,7 @@ var OPTION_KEYS = Object.freeze([
2873
4043
  "dataFlow",
2874
4044
  "editor",
2875
4045
  "enabled",
4046
+ "externalAgent",
2876
4047
  "exclude",
2877
4048
  "include",
2878
4049
  "locale",
@@ -2963,6 +4134,7 @@ function serializeResolvedSpotPatchOptions(options) {
2963
4134
  }) : false,
2964
4135
  editor: options.editor,
2965
4136
  enabled: options.enabled,
4137
+ externalAgent: options.externalAgent.enabled,
2966
4138
  exclude: Object.freeze(options.exclude.map(serializeFilter)),
2967
4139
  include: Object.freeze(options.include.map(serializeFilter)),
2968
4140
  locale: options.locale,
@@ -3016,7 +4188,7 @@ function parseSerializedSpotPatchOptions(value) {
3016
4188
  if (!isRecord2(value) || !hasExactKeys(value, OPTION_KEYS)) {
3017
4189
  throw new TypeError("The SpotPatch options transport is invalid.");
3018
4190
  }
3019
- if (typeof value.enabled !== "boolean" || typeof value.redact !== "boolean" || typeof value.allowLan !== "boolean" || typeof value.debug !== "boolean" || typeof value.shortcut !== "string" || typeof value.maxTargets !== "number" || typeof value.editor !== "string" || typeof value.locale !== "string" || value.ai !== false && !isRecord2(value.ai)) {
4191
+ if (typeof value.enabled !== "boolean" || typeof value.externalAgent !== "boolean" || typeof value.redact !== "boolean" || typeof value.allowLan !== "boolean" || typeof value.debug !== "boolean" || typeof value.shortcut !== "string" || typeof value.maxTargets !== "number" || typeof value.editor !== "string" || typeof value.locale !== "string" || value.ai !== false && !isRecord2(value.ai)) {
3020
4192
  throw new TypeError("The SpotPatch options transport is invalid.");
3021
4193
  }
3022
4194
  try {
@@ -3028,6 +4200,7 @@ function parseSerializedSpotPatchOptions(value) {
3028
4200
  dataFlow: parseDataFlow(value.dataFlow),
3029
4201
  editor: value.editor,
3030
4202
  enabled: value.enabled,
4203
+ externalAgent: value.externalAgent,
3031
4204
  exclude: parseFilterList(value.exclude),
3032
4205
  include: parseFilterList(value.include),
3033
4206
  locale: value.locale,
@@ -3047,6 +4220,7 @@ function parseSerializedSpotPatchOptions(value) {
3047
4220
  DEFAULT_OPTIONS,
3048
4221
  applyIntegrationPlan,
3049
4222
  createAgentJobManager,
4223
+ createExternalHandoffService,
3050
4224
  createIntegrationFileChange,
3051
4225
  createRuntimeAiConfig,
3052
4226
  createRuntimeDataFlowConfig,