@spotpatch/dev-server 0.5.0 → 0.7.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,
@@ -50,8 +51,10 @@ __export(index_exports, {
50
51
  readRuntimeBootstrap: () => readRuntimeBootstrap,
51
52
  resolveCredentialEnvironment: () => resolveCredentialEnvironment,
52
53
  resolveEnvironmentAiConfiguration: () => resolveEnvironmentAiConfiguration,
54
+ resolveManagedExecutionValidation: () => resolveManagedExecutionValidation,
53
55
  resolveOptions: () => resolveOptions,
54
56
  resolveProjectOptions: () => resolveProjectOptions,
57
+ resolveProjectValidationChecks: () => resolveProjectValidationChecks,
55
58
  resolveRuntimeBootstrapOptions: () => resolveRuntimeBootstrapOptions,
56
59
  serializeResolvedSpotPatchOptions: () => serializeResolvedSpotPatchOptions
57
60
  });
@@ -521,6 +524,1064 @@ function createAgentJobManager(options) {
521
524
  });
522
525
  }
523
526
 
527
+ // src/external-handoff/service.ts
528
+ var import_shared7 = require("@spotpatch/shared");
529
+ var import_external_agent_node3 = require("@spotpatch/shared/external-agent-node");
530
+
531
+ // src/external-handoff/active-registry.ts
532
+ var import_node_crypto2 = require("crypto");
533
+ var import_shared2 = require("@spotpatch/shared");
534
+
535
+ // src/external-handoff/clock.ts
536
+ var import_node_perf_hooks = require("perf_hooks");
537
+ var SYSTEM_EXTERNAL_HANDOFF_CLOCK = Object.freeze({
538
+ monotonicNow: () => import_node_perf_hooks.performance.now(),
539
+ wallNow: () => Date.now()
540
+ });
541
+
542
+ // src/external-handoff/active-registry.ts
543
+ var ALLOWED_TRANSITIONS = Object.freeze({
544
+ queued: ["dispatching", "failed"],
545
+ dispatching: ["dispatched", "working", "failed", "delivery-unknown"],
546
+ dispatched: ["working", "failed", "delivery-unknown"],
547
+ working: ["completed", "failed", "delivery-unknown"],
548
+ completed: [],
549
+ failed: [],
550
+ "delivery-unknown": []
551
+ });
552
+ var TERMINAL_PHASES = /* @__PURE__ */ new Set([
553
+ "completed",
554
+ "failed",
555
+ "delivery-unknown"
556
+ ]);
557
+ function defaultRandomId() {
558
+ return (0, import_node_crypto2.randomBytes)(32).toString("base64url");
559
+ }
560
+ function requirePresent(value) {
561
+ if (value === null) throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.INTERNAL_ERROR);
562
+ return value;
563
+ }
564
+ function createActiveAdapterRegistry(options = {}) {
565
+ const clock = options.clock ?? SYSTEM_EXTERNAL_HANDOFF_CLOCK;
566
+ const randomId = options.randomId ?? defaultRandomId;
567
+ let blocked;
568
+ let closed = false;
569
+ let dispatch;
570
+ let lastReleasedToken;
571
+ let lease;
572
+ const nowIso = () => new Date(clock.wallNow()).toISOString();
573
+ const requireOpen = () => {
574
+ if (closed) throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.SESSION_CLOSED);
575
+ };
576
+ const dispatchSummary = () => dispatch === void 0 ? null : Object.freeze({
577
+ adapterKind: dispatch.adapterKind,
578
+ revision: dispatch.revision,
579
+ phase: dispatch.phase,
580
+ updatedAt: dispatch.updatedAt
581
+ });
582
+ const activeSummary = () => {
583
+ if (blocked !== void 0) {
584
+ return Object.freeze({
585
+ kind: blocked.adapterKind,
586
+ state: "blocked",
587
+ canDispatch: false,
588
+ connectedAt: blocked.connectedAt,
589
+ updatedAt: blocked.updatedAt
590
+ });
591
+ }
592
+ if (lease === void 0) return null;
593
+ const busy = dispatch !== void 0 && !TERMINAL_PHASES.has(dispatch.phase);
594
+ return Object.freeze({
595
+ kind: lease.adapterKind,
596
+ state: busy ? "busy" : "ready",
597
+ canDispatch: !busy,
598
+ connectedAt: lease.connectedAt,
599
+ updatedAt: lease.updatedAt
600
+ });
601
+ };
602
+ const state = (cursor) => Object.freeze({
603
+ activeAdapter: activeSummary(),
604
+ dispatch: cursor === void 0 || dispatch?.cursor === cursor ? dispatchSummary() : null
605
+ });
606
+ const enterUnknown = (activeLease) => {
607
+ if (dispatch === void 0) return;
608
+ const updatedAt = nowIso();
609
+ dispatch.phase = "delivery-unknown";
610
+ dispatch.updatedAt = updatedAt;
611
+ blocked = Object.freeze({
612
+ adapterKind: activeLease.adapterKind,
613
+ connectedAt: activeLease.connectedAt,
614
+ updatedAt
615
+ });
616
+ };
617
+ const endLease = (activeLease) => {
618
+ if (dispatch?.phase === "queued") {
619
+ dispatch.phase = "failed";
620
+ dispatch.updatedAt = nowIso();
621
+ } else if (dispatch?.phase === "dispatching" || dispatch?.phase === "dispatched" || dispatch?.phase === "working") {
622
+ enterUnknown(activeLease);
623
+ }
624
+ lastReleasedToken = activeLease.token;
625
+ lease = void 0;
626
+ };
627
+ const sweep = () => {
628
+ if (lease === void 0) return;
629
+ const monotonicNow = clock.monotonicNow();
630
+ if (monotonicNow >= lease.expiresAtMonotonic || dispatch !== void 0 && !TERMINAL_PHASES.has(dispatch.phase) && monotonicNow >= dispatch.deadlineMonotonic) {
631
+ endLease(lease);
632
+ }
633
+ };
634
+ const assertPublishable = () => {
635
+ requireOpen();
636
+ sweep();
637
+ if (blocked !== void 0 || lease !== void 0 && dispatch !== void 0 && !TERMINAL_PHASES.has(dispatch.phase)) {
638
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.EXTERNAL_AGENT_BUSY);
639
+ }
640
+ };
641
+ const requireLease = (leaseToken) => {
642
+ requireOpen();
643
+ sweep();
644
+ if (lease?.token !== leaseToken) {
645
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID);
646
+ }
647
+ return lease;
648
+ };
649
+ return Object.freeze({
650
+ assertPublishable,
651
+ claim(adapterKind, connectorInstanceId, baselineCursor) {
652
+ requireOpen();
653
+ sweep();
654
+ if (blocked !== void 0) {
655
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.EXTERNAL_AGENT_BUSY);
656
+ }
657
+ if (lease !== void 0) {
658
+ if (lease.adapterKind === adapterKind && lease.connectorInstanceId === connectorInstanceId) {
659
+ lease.expiresAtMonotonic = clock.monotonicNow() + import_shared2.EXTERNAL_HANDOFF_LIMITS.activeLeaseDurationMs;
660
+ lease.updatedAt = nowIso();
661
+ return Object.freeze({
662
+ leaseToken: lease.token,
663
+ heartbeatIntervalMs: import_shared2.EXTERNAL_HANDOFF_LIMITS.activeHeartbeatIntervalMs,
664
+ baselineCursor: lease.baselineCursor,
665
+ activeAdapter: requirePresent(activeSummary())
666
+ });
667
+ }
668
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT);
669
+ }
670
+ const timestamp = nowIso();
671
+ lease = {
672
+ adapterKind,
673
+ baselineCursor,
674
+ connectedAt: timestamp,
675
+ connectorInstanceId,
676
+ token: randomId(),
677
+ expiresAtMonotonic: clock.monotonicNow() + import_shared2.EXTERNAL_HANDOFF_LIMITS.activeLeaseDurationMs,
678
+ updatedAt: timestamp
679
+ };
680
+ lastReleasedToken = void 0;
681
+ return Object.freeze({
682
+ leaseToken: lease.token,
683
+ heartbeatIntervalMs: import_shared2.EXTERNAL_HANDOFF_LIMITS.activeHeartbeatIntervalMs,
684
+ baselineCursor,
685
+ activeAdapter: requirePresent(activeSummary())
686
+ });
687
+ },
688
+ heartbeat(leaseToken) {
689
+ const activeLease = requireLease(leaseToken);
690
+ activeLease.expiresAtMonotonic = clock.monotonicNow() + import_shared2.EXTERNAL_HANDOFF_LIMITS.activeLeaseDurationMs;
691
+ activeLease.updatedAt = nowIso();
692
+ return state();
693
+ },
694
+ report(leaseToken, cursor, phase) {
695
+ const activeLease = requireLease(leaseToken);
696
+ if (dispatch?.cursor !== cursor || dispatch.adapterKind !== activeLease.adapterKind) {
697
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_DISPATCH_INVALID);
698
+ }
699
+ if (dispatch.phase === phase) return state(cursor);
700
+ const allowed = ALLOWED_TRANSITIONS[dispatch.phase];
701
+ if (!allowed.includes(phase)) {
702
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_DISPATCH_INVALID);
703
+ }
704
+ const updatedAt = nowIso();
705
+ dispatch.phase = phase;
706
+ dispatch.updatedAt = updatedAt;
707
+ activeLease.updatedAt = updatedAt;
708
+ if (phase === "delivery-unknown") {
709
+ blocked = Object.freeze({
710
+ adapterKind: activeLease.adapterKind,
711
+ connectedAt: activeLease.connectedAt,
712
+ updatedAt
713
+ });
714
+ lastReleasedToken = activeLease.token;
715
+ lease = void 0;
716
+ }
717
+ return state(cursor);
718
+ },
719
+ release(leaseToken) {
720
+ requireOpen();
721
+ sweep();
722
+ if (lease === void 0 && lastReleasedToken === leaseToken) return state();
723
+ const activeLease = lease;
724
+ if (activeLease?.token !== leaseToken) {
725
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID);
726
+ }
727
+ endLease(activeLease);
728
+ return state();
729
+ },
730
+ reserve(cursor, revision) {
731
+ assertPublishable();
732
+ if (lease === void 0) return Object.freeze({ mode: "inbox" });
733
+ const updatedAt = nowIso();
734
+ dispatch = {
735
+ adapterKind: lease.adapterKind,
736
+ cursor,
737
+ deadlineMonotonic: clock.monotonicNow() + import_shared2.EXTERNAL_HANDOFF_LIMITS.activeDispatchTimeoutMs,
738
+ revision,
739
+ phase: "queued",
740
+ updatedAt
741
+ };
742
+ lease.updatedAt = updatedAt;
743
+ return Object.freeze({
744
+ mode: "active",
745
+ adapter: requirePresent(activeSummary()),
746
+ dispatch: requirePresent(dispatchSummary())
747
+ });
748
+ },
749
+ resolveDelivery(cursor) {
750
+ requireOpen();
751
+ sweep();
752
+ const activeDispatch = dispatch;
753
+ if (blocked === void 0 || activeDispatch?.cursor !== cursor || activeDispatch.phase !== "delivery-unknown") {
754
+ throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.ACTIVE_DISPATCH_INVALID);
755
+ }
756
+ blocked = void 0;
757
+ return state(cursor);
758
+ },
759
+ snapshot(cursor) {
760
+ requireOpen();
761
+ sweep();
762
+ return state(cursor);
763
+ },
764
+ close() {
765
+ if (closed) return;
766
+ closed = true;
767
+ blocked = void 0;
768
+ dispatch = void 0;
769
+ lease = void 0;
770
+ lastReleasedToken = void 0;
771
+ }
772
+ });
773
+ }
774
+
775
+ // src/external-handoff/broker.ts
776
+ var import_node_crypto3 = require("crypto");
777
+ var import_node_http = require("http");
778
+ var import_shared4 = require("@spotpatch/shared");
779
+ var import_external_agent_node = require("@spotpatch/shared/external-agent-node");
780
+
781
+ // src/server/request-body.ts
782
+ var import_shared3 = require("@spotpatch/shared");
783
+
784
+ // src/server/constants.ts
785
+ var MAX_REQUEST_BODY_BYTES = 32 * 1024;
786
+ var MAX_AGENT_REQUEST_BODY_BYTES = 256 * 1024;
787
+ var MAX_SOURCE_FILE_BYTES = 1024 * 1024;
788
+
789
+ // src/server/request-body.ts
790
+ function isJsonContentType(value) {
791
+ return value?.split(";", 1)[0]?.trim().toLowerCase() === "application/json";
792
+ }
793
+ async function readJsonRequestBody(request, maximumBytes = MAX_REQUEST_BODY_BYTES) {
794
+ if (!isJsonContentType(request.headers["content-type"])) {
795
+ throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.INVALID_REQUEST);
796
+ }
797
+ const declaredLength = Number(request.headers["content-length"]);
798
+ if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {
799
+ throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.INVALID_REQUEST);
800
+ }
801
+ const chunks = [];
802
+ let byteLength = 0;
803
+ let exceededLimit = false;
804
+ for await (const rawChunk of request) {
805
+ const chunk = rawChunk;
806
+ if (typeof chunk !== "string" && !(chunk instanceof Uint8Array)) {
807
+ throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.INVALID_REQUEST);
808
+ }
809
+ const buffer = Buffer.from(chunk);
810
+ byteLength += buffer.byteLength;
811
+ if (byteLength > maximumBytes) {
812
+ exceededLimit = true;
813
+ continue;
814
+ }
815
+ chunks.push(buffer);
816
+ }
817
+ if (exceededLimit || byteLength === 0) {
818
+ throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.INVALID_REQUEST);
819
+ }
820
+ try {
821
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
822
+ } catch (error) {
823
+ throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.INVALID_REQUEST, void 0, {
824
+ cause: error
825
+ });
826
+ }
827
+ }
828
+
829
+ // src/external-handoff/broker.ts
830
+ function singleHeader(request, name) {
831
+ const value = request.headers[name.toLowerCase()];
832
+ return Array.isArray(value) ? void 0 : value;
833
+ }
834
+ function tokensMatch(actual, expected) {
835
+ if (actual === void 0) return false;
836
+ const actualBytes = Buffer.from(actual);
837
+ const expectedBytes = Buffer.from(expected);
838
+ return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto3.timingSafeEqual)(actualBytes, expectedBytes);
839
+ }
840
+ function writeJson(response, status, payload) {
841
+ response.statusCode = status;
842
+ response.setHeader("Cache-Control", "no-store");
843
+ response.setHeader("Connection", "close");
844
+ response.setHeader("Content-Type", "application/json; charset=utf-8");
845
+ response.setHeader("X-Content-Type-Options", "nosniff");
846
+ response.end(JSON.stringify(payload));
847
+ }
848
+ function statusForError(code) {
849
+ if (code === import_shared4.ERROR_CODES.BRIDGE_UNAUTHORIZED) return 401;
850
+ if (code === import_shared4.ERROR_CODES.HANDOFF_NOT_FOUND) return 404;
851
+ if (code === import_shared4.ERROR_CODES.HANDOFF_EXPIRED || code === import_shared4.ERROR_CODES.SESSION_CLOSED) {
852
+ return 410;
853
+ }
854
+ if (code === import_shared4.ERROR_CODES.BRIDGE_BUSY) return 429;
855
+ if (code === import_shared4.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID) return 401;
856
+ if (code === import_shared4.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE) return 413;
857
+ 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) {
858
+ return 409;
859
+ }
860
+ if (code === import_shared4.ERROR_CODES.INVALID_REQUEST) return 400;
861
+ return 500;
862
+ }
863
+ function normalizeError2(error) {
864
+ return error instanceof import_shared4.SpotPatchError ? error : new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
865
+ }
866
+ function assertAuthorized(request, expectedHost, bridgeToken) {
867
+ if (request.socket.remoteAddress !== "127.0.0.1" || singleHeader(request, "host") !== expectedHost || !tokensMatch(singleHeader(request, import_external_agent_node.SPOTPATCH_BRIDGE_TOKEN_HEADER), bridgeToken)) {
868
+ throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.BRIDGE_UNAUTHORIZED);
869
+ }
870
+ }
871
+ async function closeServer(server, sockets) {
872
+ await new Promise((resolve) => {
873
+ server.close(() => {
874
+ resolve();
875
+ });
876
+ for (const socket of sockets) {
877
+ socket.destroy();
878
+ }
879
+ });
880
+ }
881
+ async function createExternalHandoffBroker(options) {
882
+ const bridgeToken = (0, import_node_crypto3.randomBytes)(32).toString("base64url");
883
+ const sockets = /* @__PURE__ */ new Set();
884
+ let expectedHost = "";
885
+ const server = (0, import_node_http.createServer)(
886
+ { maxHeaderSize: import_shared4.EXTERNAL_HANDOFF_LIMITS.maximumBrokerHeaderBytes },
887
+ (request, response) => {
888
+ const handle = async () => {
889
+ assertAuthorized(request, expectedHost, bridgeToken);
890
+ if (request.method !== "POST") {
891
+ throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
892
+ }
893
+ const body = await readJsonRequestBody(
894
+ request,
895
+ import_shared4.EXTERNAL_HANDOFF_LIMITS.maximumBrokerRequestBytes
896
+ );
897
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.status) {
898
+ const parsed = import_external_agent_node.bridgeStatusRequestSchema.safeParse(body);
899
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
900
+ let current = null;
901
+ try {
902
+ current = options.store.status();
903
+ } catch (error) {
904
+ if (!(error instanceof import_shared4.SpotPatchError) || error.code !== import_shared4.ERROR_CODES.HANDOFF_NOT_FOUND) {
905
+ throw error;
906
+ }
907
+ }
908
+ writeJson(response, 200, {
909
+ ok: true,
910
+ data: Object.freeze({
911
+ brokerProtocolVersion: import_shared4.EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION,
912
+ projectKey: options.projectKey,
913
+ sessionId: options.sessionId,
914
+ framework: options.framework,
915
+ current
916
+ })
917
+ });
918
+ return;
919
+ }
920
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.current) {
921
+ const parsed = import_external_agent_node.bridgeCurrentRequestSchema.safeParse(body);
922
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
923
+ const snapshot2 = options.store.current(parsed.data.cursor);
924
+ writeJson(response, 200, {
925
+ ok: true,
926
+ data: Object.freeze({ outcome: "handoff", snapshot: snapshot2 })
927
+ });
928
+ return;
929
+ }
930
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.ack) {
931
+ const parsed = import_external_agent_node.bridgeAckRequestSchema.safeParse(body);
932
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
933
+ const summary = options.store.ack(
934
+ parsed.data.cursor,
935
+ parsed.data.connectorInstanceId
936
+ );
937
+ writeJson(response, 200, {
938
+ ok: true,
939
+ data: Object.freeze({ summary })
940
+ });
941
+ return;
942
+ }
943
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.wait) {
944
+ const parsed = import_external_agent_node.bridgeWaitRequestSchema.safeParse(body);
945
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
946
+ const controller = new AbortController();
947
+ const abort = () => {
948
+ if (!response.writableEnded) controller.abort("bridge-client-closed");
949
+ };
950
+ response.once("close", abort);
951
+ try {
952
+ const data = await options.store.wait(
953
+ parsed.data.afterCursor,
954
+ parsed.data.timeoutMs,
955
+ controller.signal
956
+ );
957
+ writeJson(response, 200, { ok: true, data });
958
+ } finally {
959
+ response.removeListener("close", abort);
960
+ }
961
+ return;
962
+ }
963
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.activeClaim) {
964
+ const parsed = import_external_agent_node.bridgeActiveClaimRequestSchema.safeParse(body);
965
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
966
+ const data = options.activeRegistry.claim(
967
+ parsed.data.adapterKind,
968
+ parsed.data.connectorInstanceId,
969
+ options.store.currentCursor()
970
+ );
971
+ writeJson(response, 200, { ok: true, data });
972
+ return;
973
+ }
974
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.activeHeartbeat) {
975
+ const parsed = import_external_agent_node.bridgeActiveHeartbeatRequestSchema.safeParse(body);
976
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
977
+ const data = options.activeRegistry.heartbeat(parsed.data.leaseToken);
978
+ writeJson(response, 200, { ok: true, data });
979
+ return;
980
+ }
981
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.activeReport) {
982
+ const parsed = import_external_agent_node.bridgeActiveReportRequestSchema.safeParse(body);
983
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
984
+ const data = options.activeRegistry.report(
985
+ parsed.data.leaseToken,
986
+ parsed.data.cursor,
987
+ parsed.data.phase
988
+ );
989
+ writeJson(response, 200, { ok: true, data });
990
+ return;
991
+ }
992
+ if (request.url === import_external_agent_node.SPOTPATCH_BRIDGE_PATHS.activeRelease) {
993
+ const parsed = import_external_agent_node.bridgeActiveReleaseRequestSchema.safeParse(body);
994
+ if (!parsed.success) throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
995
+ const data = options.activeRegistry.release(parsed.data.leaseToken);
996
+ writeJson(response, 200, { ok: true, data });
997
+ return;
998
+ }
999
+ throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
1000
+ };
1001
+ void handle().catch((error) => {
1002
+ if (response.writableEnded || response.destroyed) return;
1003
+ const normalized = normalizeError2(error);
1004
+ if (normalized.code === import_shared4.ERROR_CODES.BRIDGE_BUSY) {
1005
+ response.setHeader("Retry-After", "1");
1006
+ }
1007
+ writeJson(response, statusForError(normalized.code), {
1008
+ ok: false,
1009
+ error: {
1010
+ code: normalized.code,
1011
+ message: "The local SpotPatch bridge request failed."
1012
+ }
1013
+ });
1014
+ });
1015
+ }
1016
+ );
1017
+ server.maxConnections = import_shared4.EXTERNAL_HANDOFF_LIMITS.maximumBrokerSockets;
1018
+ server.headersTimeout = 5e3;
1019
+ server.requestTimeout = import_shared4.EXTERNAL_HANDOFF_LIMITS.maximumWaitMs + 5e3;
1020
+ server.keepAliveTimeout = 1;
1021
+ server.on("connection", (socket) => {
1022
+ if (sockets.size >= import_shared4.EXTERNAL_HANDOFF_LIMITS.maximumBrokerSockets) {
1023
+ socket.destroy();
1024
+ return;
1025
+ }
1026
+ sockets.add(socket);
1027
+ socket.once("close", () => sockets.delete(socket));
1028
+ });
1029
+ await new Promise((resolve, reject) => {
1030
+ server.once("error", reject);
1031
+ server.listen(0, "127.0.0.1", resolve);
1032
+ });
1033
+ const address = server.address();
1034
+ if (address === null || typeof address === "string" || address.address !== "127.0.0.1") {
1035
+ await closeServer(server, sockets);
1036
+ throw new Error("SpotPatch external Agent broker did not bind IPv4 loopback.");
1037
+ }
1038
+ expectedHost = `127.0.0.1:${String(address.port)}`;
1039
+ let closed = false;
1040
+ let ready = true;
1041
+ server.removeAllListeners("error");
1042
+ server.on("error", () => {
1043
+ ready = false;
1044
+ for (const socket of sockets) socket.destroy();
1045
+ });
1046
+ return Object.freeze({
1047
+ bridgeToken,
1048
+ endpoint: `http://${expectedHost}`,
1049
+ isReady: () => ready && !closed,
1050
+ async close() {
1051
+ if (closed) return;
1052
+ closed = true;
1053
+ ready = false;
1054
+ await closeServer(server, sockets);
1055
+ }
1056
+ });
1057
+ }
1058
+
1059
+ // src/external-handoff/discovery.ts
1060
+ var import_node_crypto4 = require("crypto");
1061
+ var import_promises = require("fs/promises");
1062
+ var import_node_path = __toESM(require("path"), 1);
1063
+ var import_shared5 = require("@spotpatch/shared");
1064
+ var import_external_agent_node2 = require("@spotpatch/shared/external-agent-node");
1065
+ async function syncDirectory(directory) {
1066
+ const handle = await (0, import_promises.open)(directory, "r");
1067
+ try {
1068
+ await handle.sync();
1069
+ } catch (error) {
1070
+ const code = error.code;
1071
+ if (code !== "EINVAL" && code !== "ENOTSUP") {
1072
+ throw error;
1073
+ }
1074
+ } finally {
1075
+ await handle.close();
1076
+ }
1077
+ }
1078
+ async function publishExternalHandoffDescriptor(options) {
1079
+ const directory = await (0, import_external_agent_node2.resolveExternalHandoffRuntimeDirectory)(true);
1080
+ const descriptor = import_external_agent_node2.externalHandoffDescriptorSchema.parse({
1081
+ schemaVersion: 1,
1082
+ brokerProtocolVersion: import_shared5.EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION,
1083
+ projectKey: await (0, import_external_agent_node2.computeExternalHandoffProjectKey)(options.root),
1084
+ sessionId: options.sessionId,
1085
+ framework: options.framework,
1086
+ endpoint: options.endpoint,
1087
+ bridgeToken: options.bridgeToken,
1088
+ pid: process.pid,
1089
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1090
+ });
1091
+ const serialized = JSON.stringify(descriptor);
1092
+ if (Buffer.byteLength(serialized, "utf8") > import_shared5.EXTERNAL_HANDOFF_LIMITS.maximumDescriptorBytes) {
1093
+ throw new RangeError("SpotPatch external Agent descriptor exceeds its limit.");
1094
+ }
1095
+ const destination = import_node_path.default.join(directory, `${descriptor.sessionId}.json`);
1096
+ const temporary = import_node_path.default.join(
1097
+ directory,
1098
+ `.${descriptor.sessionId}.${(0, import_node_crypto4.randomBytes)(8).toString("hex")}.tmp`
1099
+ );
1100
+ let temporaryExists = false;
1101
+ let published = false;
1102
+ let descriptorIdentity = Object.freeze({
1103
+ device: -1,
1104
+ inode: -1
1105
+ });
1106
+ try {
1107
+ const handle = await (0, import_promises.open)(temporary, "wx", 384);
1108
+ temporaryExists = true;
1109
+ try {
1110
+ await handle.writeFile(serialized, "utf8");
1111
+ await handle.sync();
1112
+ } finally {
1113
+ await handle.close();
1114
+ }
1115
+ await (0, import_promises.rename)(temporary, destination);
1116
+ temporaryExists = false;
1117
+ published = true;
1118
+ const status = await (0, import_promises.lstat)(destination);
1119
+ const uid = process.getuid?.();
1120
+ if (!status.isFile() || status.isSymbolicLink() || uid === void 0 || status.uid !== uid || (status.mode & 63) !== 0) {
1121
+ throw new Error("SpotPatch external Agent descriptor is not private.");
1122
+ }
1123
+ descriptorIdentity = Object.freeze({ device: status.dev, inode: status.ino });
1124
+ await syncDirectory(directory);
1125
+ } catch (error) {
1126
+ if (temporaryExists) {
1127
+ await (0, import_promises.unlink)(temporary).catch(() => void 0);
1128
+ }
1129
+ if (published) {
1130
+ await (0, import_promises.unlink)(destination).catch(() => void 0);
1131
+ }
1132
+ throw error;
1133
+ }
1134
+ let closed = false;
1135
+ return Object.freeze({
1136
+ descriptor,
1137
+ async close() {
1138
+ if (closed) return;
1139
+ closed = true;
1140
+ await (0, import_promises.lstat)(destination).then(async (status) => {
1141
+ if (status.dev === descriptorIdentity.device && status.ino === descriptorIdentity.inode) {
1142
+ await (0, import_promises.unlink)(destination);
1143
+ }
1144
+ }).catch((error) => {
1145
+ if (error.code !== "ENOENT") {
1146
+ throw error;
1147
+ }
1148
+ });
1149
+ await syncDirectory(directory);
1150
+ }
1151
+ });
1152
+ }
1153
+
1154
+ // src/external-handoff/fingerprint.ts
1155
+ var import_node_crypto5 = require("crypto");
1156
+ function canonicalJson(value) {
1157
+ if (value === null || typeof value === "boolean" || typeof value === "string") {
1158
+ return JSON.stringify(value);
1159
+ }
1160
+ if (typeof value === "number") {
1161
+ if (!Number.isFinite(value)) throw new TypeError("Non-finite JSON number.");
1162
+ return JSON.stringify(value);
1163
+ }
1164
+ if (Array.isArray(value)) {
1165
+ return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
1166
+ }
1167
+ if (typeof value === "object") {
1168
+ const record = value;
1169
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
1170
+ }
1171
+ throw new TypeError("Unsupported JSON value.");
1172
+ }
1173
+ function fingerprintExternalHandoffAnnotation(annotation) {
1174
+ return (0, import_node_crypto5.createHash)("sha256").update(canonicalJson(annotation)).digest("hex");
1175
+ }
1176
+
1177
+ // src/external-handoff/store.ts
1178
+ var import_node_crypto6 = require("crypto");
1179
+ var import_shared6 = require("@spotpatch/shared");
1180
+ function defaultRandomId2() {
1181
+ return (0, import_node_crypto6.randomBytes)(24).toString("base64url");
1182
+ }
1183
+ function pageSummary(annotation) {
1184
+ let origin = "[unavailable]";
1185
+ try {
1186
+ const url = new URL(annotation.page.url);
1187
+ origin = url.origin === "null" ? "[unavailable]" : url.origin;
1188
+ } catch {
1189
+ }
1190
+ return Object.freeze({ origin, pathname: annotation.page.pathname });
1191
+ }
1192
+ function summaryOf(current, state) {
1193
+ const snapshot2 = current.snapshot;
1194
+ return Object.freeze({
1195
+ sessionId: snapshot2.session.id,
1196
+ framework: snapshot2.session.framework,
1197
+ revision: snapshot2.revision,
1198
+ cursor: snapshot2.cursor,
1199
+ targetCount: snapshot2.annotation.targets.length,
1200
+ page: pageSummary(snapshot2.annotation),
1201
+ publishedAt: snapshot2.publishedAt,
1202
+ expiresAt: snapshot2.expiresAt,
1203
+ state,
1204
+ pickupCount: current.receipts.size,
1205
+ ...current.pickedUpAt === void 0 ? {} : { pickedUpAt: current.pickedUpAt }
1206
+ });
1207
+ }
1208
+ function replayResult(record) {
1209
+ return Object.freeze({ ...record.result, replayed: true });
1210
+ }
1211
+ function createExternalHandoffStore(options) {
1212
+ const clock = options.clock ?? SYSTEM_EXTERNAL_HANDOFF_CLOCK;
1213
+ const randomId = options.randomId ?? defaultRandomId2;
1214
+ const history = [];
1215
+ const idempotency = /* @__PURE__ */ new Map();
1216
+ const waiters = /* @__PURE__ */ new Set();
1217
+ let closed = false;
1218
+ let current;
1219
+ let revision = 0;
1220
+ const requireOpen = () => {
1221
+ if (closed) throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.SESSION_CLOSED);
1222
+ };
1223
+ const archive = (state) => {
1224
+ if (current === void 0) return;
1225
+ history.unshift(summaryOf(current, state));
1226
+ history.length = Math.min(
1227
+ history.length,
1228
+ import_shared6.EXTERNAL_HANDOFF_LIMITS.maximumHistorySummaries
1229
+ );
1230
+ current = void 0;
1231
+ };
1232
+ const sweep = () => {
1233
+ const monotonicNow = clock.monotonicNow();
1234
+ if (current !== void 0 && monotonicNow >= current.expiresAtMonotonic) {
1235
+ archive("expired");
1236
+ }
1237
+ for (const [requestId, record] of idempotency) {
1238
+ if (monotonicNow >= record.expiresAtMonotonic) idempotency.delete(requestId);
1239
+ }
1240
+ };
1241
+ const knownSummary = (cursor) => {
1242
+ sweep();
1243
+ if (current?.snapshot.cursor === cursor) {
1244
+ return summaryOf(current, "available");
1245
+ }
1246
+ return history.find((summary) => summary.cursor === cursor);
1247
+ };
1248
+ const readCurrent = (cursor) => {
1249
+ requireOpen();
1250
+ sweep();
1251
+ if (current === void 0) {
1252
+ const prior = cursor === void 0 ? void 0 : knownSummary(cursor);
1253
+ throw new import_shared6.SpotPatchError(
1254
+ 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
1255
+ );
1256
+ }
1257
+ if (cursor !== void 0 && cursor !== current.snapshot.cursor) {
1258
+ const prior = knownSummary(cursor);
1259
+ throw new import_shared6.SpotPatchError(
1260
+ prior?.state === "expired" ? import_shared6.ERROR_CODES.HANDOFF_EXPIRED : import_shared6.ERROR_CODES.HANDOFF_CURSOR_INVALID
1261
+ );
1262
+ }
1263
+ return current.snapshot;
1264
+ };
1265
+ const findReplay = (requestId, fingerprint) => {
1266
+ requireOpen();
1267
+ sweep();
1268
+ const record = idempotency.get(requestId);
1269
+ if (record === void 0) return void 0;
1270
+ if (record.fingerprint !== fingerprint) {
1271
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
1272
+ }
1273
+ return replayResult(record);
1274
+ };
1275
+ const settleWaiters = (result) => {
1276
+ const pending = [...waiters];
1277
+ waiters.clear();
1278
+ for (const waiter of pending) waiter.resolve(result);
1279
+ };
1280
+ return Object.freeze({
1281
+ activeWaitCount: () => waiters.size,
1282
+ replay: findReplay,
1283
+ publish(input) {
1284
+ requireOpen();
1285
+ const replayed = findReplay(input.requestId, input.fingerprint);
1286
+ if (replayed !== void 0) return replayed;
1287
+ if (idempotency.size >= import_shared6.EXTERNAL_HANDOFF_LIMITS.maximumRequestIdRecords) {
1288
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE);
1289
+ }
1290
+ const nextRevision = revision + 1;
1291
+ const publishedAtMs = clock.wallNow();
1292
+ const publishedAtMonotonic = clock.monotonicNow();
1293
+ const snapshot2 = Object.freeze({
1294
+ schemaVersion: import_shared6.EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION,
1295
+ cursor: randomId(),
1296
+ session: Object.freeze({ id: options.sessionId, framework: options.framework }),
1297
+ revision: nextRevision,
1298
+ publishedAt: new Date(publishedAtMs).toISOString(),
1299
+ expiresAt: new Date(
1300
+ publishedAtMs + import_shared6.EXTERNAL_HANDOFF_LIMITS.handoffTtlMs
1301
+ ).toISOString(),
1302
+ annotation: input.annotation
1303
+ });
1304
+ if (Buffer.byteLength(JSON.stringify(snapshot2), "utf8") > import_shared6.EXTERNAL_HANDOFF_LIMITS.maximumSnapshotBytes) {
1305
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE);
1306
+ }
1307
+ const delivery = input.reserve(snapshot2.cursor, nextRevision);
1308
+ if (current !== void 0) archive("superseded");
1309
+ revision = nextRevision;
1310
+ current = {
1311
+ expiresAtMonotonic: publishedAtMonotonic + import_shared6.EXTERNAL_HANDOFF_LIMITS.handoffTtlMs,
1312
+ receipts: /* @__PURE__ */ new Set(),
1313
+ snapshot: snapshot2
1314
+ };
1315
+ const result = Object.freeze({
1316
+ handoff: summaryOf(current, "available"),
1317
+ delivery,
1318
+ replayed: false
1319
+ });
1320
+ idempotency.set(
1321
+ input.requestId,
1322
+ Object.freeze({
1323
+ expiresAtMonotonic: publishedAtMonotonic + import_shared6.EXTERNAL_HANDOFF_LIMITS.requestIdTtlMs,
1324
+ fingerprint: input.fingerprint,
1325
+ result
1326
+ })
1327
+ );
1328
+ settleWaiters(Object.freeze({ outcome: "handoff", snapshot: snapshot2 }));
1329
+ return result;
1330
+ },
1331
+ current: readCurrent,
1332
+ currentCursor() {
1333
+ requireOpen();
1334
+ sweep();
1335
+ return current?.snapshot.cursor ?? null;
1336
+ },
1337
+ status(cursor) {
1338
+ requireOpen();
1339
+ sweep();
1340
+ if (cursor === void 0) {
1341
+ if (current === void 0) {
1342
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_NOT_FOUND);
1343
+ }
1344
+ return summaryOf(current, "available");
1345
+ }
1346
+ const summary = knownSummary(cursor);
1347
+ if (summary === void 0) {
1348
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_CURSOR_INVALID);
1349
+ }
1350
+ return summary;
1351
+ },
1352
+ ack(cursor, connectorInstanceId) {
1353
+ const snapshot2 = readCurrent(cursor);
1354
+ if (snapshot2 !== current?.snapshot) {
1355
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_CURSOR_INVALID);
1356
+ }
1357
+ if (!current.receipts.has(connectorInstanceId)) {
1358
+ if (current.receipts.size >= import_shared6.EXTERNAL_HANDOFF_LIMITS.maximumConnectorReceipts) {
1359
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.BRIDGE_BUSY);
1360
+ }
1361
+ current.receipts.add(connectorInstanceId);
1362
+ current.pickedUpAt = new Date(clock.wallNow()).toISOString();
1363
+ }
1364
+ return summaryOf(current, "available");
1365
+ },
1366
+ async wait(afterCursor, timeoutMs, signal) {
1367
+ requireOpen();
1368
+ sweep();
1369
+ if (afterCursor === void 0 && current !== void 0) {
1370
+ return Object.freeze({ outcome: "handoff", snapshot: current.snapshot });
1371
+ }
1372
+ if (afterCursor !== void 0) {
1373
+ const known = knownSummary(afterCursor);
1374
+ if (known === void 0) {
1375
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_CURSOR_INVALID);
1376
+ }
1377
+ if (current !== void 0 && current.snapshot.cursor !== afterCursor) {
1378
+ return Object.freeze({ outcome: "handoff", snapshot: current.snapshot });
1379
+ }
1380
+ }
1381
+ if (waiters.size >= import_shared6.EXTERNAL_HANDOFF_LIMITS.maximumWaiters) {
1382
+ throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.BRIDGE_BUSY);
1383
+ }
1384
+ if (signal.aborted) throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.SESSION_CLOSED);
1385
+ return new Promise((resolve, reject) => {
1386
+ let settled = false;
1387
+ const finish = () => {
1388
+ if (settled) return false;
1389
+ settled = true;
1390
+ waiters.delete(waiter);
1391
+ clearTimeout(timer);
1392
+ signal.removeEventListener("abort", abort);
1393
+ return true;
1394
+ };
1395
+ const waiter = {
1396
+ reject(error) {
1397
+ if (finish()) reject(error);
1398
+ },
1399
+ resolve(result) {
1400
+ if (finish()) resolve(result);
1401
+ }
1402
+ };
1403
+ const abort = () => {
1404
+ waiter.reject(new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.SESSION_CLOSED));
1405
+ };
1406
+ const timer = setTimeout(() => {
1407
+ waiter.resolve(Object.freeze({ outcome: "timeout" }));
1408
+ }, timeoutMs);
1409
+ timer.unref();
1410
+ signal.addEventListener("abort", abort, { once: true });
1411
+ waiters.add(waiter);
1412
+ });
1413
+ },
1414
+ close() {
1415
+ if (closed) return;
1416
+ closed = true;
1417
+ current = void 0;
1418
+ history.length = 0;
1419
+ idempotency.clear();
1420
+ const pending = [...waiters];
1421
+ waiters.clear();
1422
+ for (const waiter of pending) {
1423
+ waiter.reject(new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.SESSION_CLOSED));
1424
+ }
1425
+ }
1426
+ });
1427
+ }
1428
+
1429
+ // src/external-handoff/service.ts
1430
+ function asReplay(result) {
1431
+ return Object.freeze({ ...result, replayed: true });
1432
+ }
1433
+ function createExternalHandoffService(options) {
1434
+ const activeRegistry = createActiveAdapterRegistry();
1435
+ const store = createExternalHandoffStore({
1436
+ framework: options.framework,
1437
+ sessionId: options.sessionId
1438
+ });
1439
+ const inFlight = /* @__PURE__ */ new Map();
1440
+ let broker;
1441
+ let descriptor;
1442
+ let startPromise;
1443
+ let closePromise;
1444
+ let state = "idle";
1445
+ const isClosed = () => state === "closed";
1446
+ const requireReady = () => {
1447
+ if (state !== "ready" || broker?.isReady() !== true) {
1448
+ throw new import_shared7.SpotPatchError(
1449
+ state === "closed" ? import_shared7.ERROR_CODES.SESSION_CLOSED : import_shared7.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE
1450
+ );
1451
+ }
1452
+ };
1453
+ const start = async () => {
1454
+ if (state === "ready") return;
1455
+ if (state === "closed") throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.SESSION_CLOSED);
1456
+ if (startPromise !== void 0) return startPromise;
1457
+ state = "starting";
1458
+ startPromise = (async () => {
1459
+ let createdBroker;
1460
+ let createdDescriptor;
1461
+ try {
1462
+ const projectKey = await (0, import_external_agent_node3.computeExternalHandoffProjectKey)(options.root);
1463
+ createdBroker = await createExternalHandoffBroker({
1464
+ activeRegistry,
1465
+ framework: options.framework,
1466
+ projectKey,
1467
+ sessionId: options.sessionId,
1468
+ store
1469
+ });
1470
+ createdDescriptor = await publishExternalHandoffDescriptor({
1471
+ bridgeToken: createdBroker.bridgeToken,
1472
+ endpoint: createdBroker.endpoint,
1473
+ framework: options.framework,
1474
+ root: options.root,
1475
+ sessionId: options.sessionId
1476
+ });
1477
+ if (isClosed()) {
1478
+ await createdDescriptor.close();
1479
+ await createdBroker.close();
1480
+ throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.SESSION_CLOSED);
1481
+ }
1482
+ broker = createdBroker;
1483
+ descriptor = createdDescriptor;
1484
+ state = "ready";
1485
+ } catch (error) {
1486
+ if (createdDescriptor !== void 0 && descriptor !== createdDescriptor) {
1487
+ await createdDescriptor.close().catch(() => void 0);
1488
+ }
1489
+ if (createdBroker !== void 0 && broker !== createdBroker) {
1490
+ await createdBroker.close().catch(() => void 0);
1491
+ }
1492
+ if (!isClosed()) state = "failed";
1493
+ throw error;
1494
+ }
1495
+ })();
1496
+ return startPromise;
1497
+ };
1498
+ return Object.freeze({
1499
+ start,
1500
+ capability() {
1501
+ const currentCursor = store.currentCursor();
1502
+ const active = activeRegistry.snapshot(currentCursor ?? void 0);
1503
+ return Object.freeze({
1504
+ enabled: true,
1505
+ brokerReady: state === "ready" && broker?.isReady() === true,
1506
+ activeWaitCount: store.activeWaitCount(),
1507
+ snapshotSchemaVersion: import_shared7.EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION,
1508
+ brokerProtocolVersion: import_shared7.EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION,
1509
+ activeAdapter: active.activeAdapter,
1510
+ dispatch: currentCursor === null ? null : active.dispatch
1511
+ });
1512
+ },
1513
+ async publish(request, authorize) {
1514
+ requireReady();
1515
+ const fingerprint = fingerprintExternalHandoffAnnotation(request.annotation);
1516
+ const replayed = store.replay(request.requestId, fingerprint);
1517
+ if (replayed !== void 0) return replayed;
1518
+ const pending = inFlight.get(request.requestId);
1519
+ if (pending !== void 0) {
1520
+ if (pending.fingerprint !== fingerprint) {
1521
+ throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
1522
+ }
1523
+ return asReplay(await pending.promise);
1524
+ }
1525
+ activeRegistry.assertPublishable();
1526
+ const promise = (async () => {
1527
+ const annotation = await authorize(request.annotation);
1528
+ return store.publish({
1529
+ annotation,
1530
+ fingerprint,
1531
+ requestId: request.requestId,
1532
+ reserve: activeRegistry.reserve
1533
+ });
1534
+ })();
1535
+ const activePublish = Object.freeze({ fingerprint, promise });
1536
+ inFlight.set(request.requestId, activePublish);
1537
+ try {
1538
+ return await promise;
1539
+ } finally {
1540
+ if (inFlight.get(request.requestId) === activePublish) {
1541
+ inFlight.delete(request.requestId);
1542
+ }
1543
+ }
1544
+ },
1545
+ status(cursor) {
1546
+ requireReady();
1547
+ const handoff = store.status(cursor);
1548
+ const active = activeRegistry.snapshot(cursor ?? handoff.cursor);
1549
+ return Object.freeze({
1550
+ handoff,
1551
+ activeAdapter: active.activeAdapter,
1552
+ dispatch: active.dispatch
1553
+ });
1554
+ },
1555
+ resolveDelivery(cursor) {
1556
+ requireReady();
1557
+ const handoff = store.status(cursor);
1558
+ const active = activeRegistry.resolveDelivery(cursor);
1559
+ return Object.freeze({
1560
+ handoff,
1561
+ activeAdapter: active.activeAdapter,
1562
+ dispatch: active.dispatch
1563
+ });
1564
+ },
1565
+ close() {
1566
+ closePromise ??= (async () => {
1567
+ if (state === "closed") return;
1568
+ state = "closed";
1569
+ activeRegistry.close();
1570
+ store.close();
1571
+ inFlight.clear();
1572
+ await startPromise?.catch(() => void 0);
1573
+ const publishedDescriptor = descriptor;
1574
+ descriptor = void 0;
1575
+ const activeBroker = broker;
1576
+ broker = void 0;
1577
+ await publishedDescriptor?.close().catch(() => void 0);
1578
+ await activeBroker?.close().catch(() => void 0);
1579
+ })();
1580
+ return closePromise;
1581
+ }
1582
+ });
1583
+ }
1584
+
524
1585
  // src/environment-ai.ts
525
1586
  var AI_ENVIRONMENT_NAMES = Object.freeze({
526
1587
  authentication: "SPOTPATCH_AI_AUTHENTICATION",
@@ -611,46 +1672,46 @@ function resolveEnvironmentAiConfiguration(environment) {
611
1672
  }
612
1673
 
613
1674
  // 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);
1675
+ var import_node_crypto7 = require("crypto");
1676
+ var import_promises2 = require("fs/promises");
1677
+ var import_node_path2 = __toESM(require("path"), 1);
617
1678
  function isMissingPathError(error) {
618
1679
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
619
1680
  }
620
1681
  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);
1682
+ const relative = import_node_path2.default.relative(root, target);
1683
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path2.default.sep}`) && !import_node_path2.default.isAbsolute(relative);
623
1684
  }
624
1685
  function relativePathWithin(root, target) {
625
- const relative = import_node_path.default.relative(root, target);
1686
+ const relative = import_node_path2.default.relative(root, target);
626
1687
  if (relative.length === 0 || !isPathWithin(root, target)) {
627
1688
  throw new Error("SpotPatch init refuses to modify a path outside the app root.");
628
1689
  }
629
- return relative.split(import_node_path.default.sep).join("/");
1690
+ return relative.split(import_node_path2.default.sep).join("/");
630
1691
  }
631
1692
  async function integrationPathExists(absolutePath) {
632
1693
  try {
633
- await (0, import_promises.access)(absolutePath);
1694
+ await (0, import_promises2.access)(absolutePath);
634
1695
  return true;
635
1696
  } catch {
636
1697
  return false;
637
1698
  }
638
1699
  }
639
1700
  async function readIntegrationFile(absolutePath) {
640
- const metadata = await (0, import_promises.lstat)(absolutePath);
1701
+ const metadata = await (0, import_promises2.lstat)(absolutePath);
641
1702
  if (!metadata.isFile() || metadata.isSymbolicLink()) {
642
1703
  throw new Error(
643
- `SpotPatch refuses to modify the non-regular file ${import_node_path.default.basename(absolutePath)}.`
1704
+ `SpotPatch refuses to modify the non-regular file ${import_node_path2.default.basename(absolutePath)}.`
644
1705
  );
645
1706
  }
646
- return (0, import_promises.readFile)(absolutePath, "utf8");
1707
+ return (0, import_promises2.readFile)(absolutePath, "utf8");
647
1708
  }
648
1709
  function createIntegrationFileChange(appRoot, absolutePath, nextContent, previousContent) {
649
1710
  if (previousContent === nextContent) {
650
1711
  return void 0;
651
1712
  }
652
- const root = import_node_path.default.resolve(appRoot);
653
- const target = import_node_path.default.resolve(absolutePath);
1713
+ const root = import_node_path2.default.resolve(appRoot);
1714
+ const target = import_node_path2.default.resolve(absolutePath);
654
1715
  return Object.freeze({
655
1716
  absolutePath: target,
656
1717
  nextContent,
@@ -659,19 +1720,19 @@ function createIntegrationFileChange(appRoot, absolutePath, nextContent, previou
659
1720
  });
660
1721
  }
661
1722
  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")}`
1723
+ return import_node_path2.default.join(
1724
+ import_node_path2.default.dirname(absolutePath),
1725
+ `.${import_node_path2.default.basename(absolutePath)}.spotpatch-${label}-${String(process.pid)}-${(0, import_node_crypto7.randomBytes)(8).toString("hex")}`
665
1726
  );
666
1727
  }
667
1728
  async function writeAtomic(absolutePath, content, mode) {
668
- await (0, import_promises.mkdir)(import_node_path.default.dirname(absolutePath), { recursive: true });
1729
+ await (0, import_promises2.mkdir)(import_node_path2.default.dirname(absolutePath), { recursive: true });
669
1730
  const stagedPath = temporaryPath(absolutePath, "stage");
670
1731
  try {
671
- await (0, import_promises.writeFile)(stagedPath, content, { encoding: "utf8", flag: "wx", mode });
672
- await (0, import_promises.rename)(stagedPath, absolutePath);
1732
+ await (0, import_promises2.writeFile)(stagedPath, content, { encoding: "utf8", flag: "wx", mode });
1733
+ await (0, import_promises2.rename)(stagedPath, absolutePath);
673
1734
  } catch (error) {
674
- await (0, import_promises.unlink)(stagedPath).catch(() => void 0);
1735
+ await (0, import_promises2.unlink)(stagedPath).catch(() => void 0);
675
1736
  throw error;
676
1737
  }
677
1738
  }
@@ -683,21 +1744,21 @@ async function rollbackChange(change) {
683
1744
  );
684
1745
  }
685
1746
  if (change.previousContent === void 0) {
686
- await (0, import_promises.unlink)(change.absolutePath);
1747
+ await (0, import_promises2.unlink)(change.absolutePath);
687
1748
  return;
688
1749
  }
689
- const mode = (await (0, import_promises.stat)(change.absolutePath)).mode & 511;
1750
+ const mode = (await (0, import_promises2.stat)(change.absolutePath)).mode & 511;
690
1751
  await writeAtomic(change.absolutePath, change.previousContent, mode);
691
1752
  }
692
1753
  async function assertSafeTarget(appRoot, realAppRoot, change) {
693
- const target = import_node_path.default.resolve(change.absolutePath);
1754
+ const target = import_node_path2.default.resolve(change.absolutePath);
694
1755
  const relativePath = relativePathWithin(appRoot, target);
695
- if (target !== change.absolutePath || relativePath !== change.relativePath || import_node_path.default.dirname(target) === target) {
1756
+ if (target !== change.absolutePath || relativePath !== change.relativePath || import_node_path2.default.dirname(target) === target) {
696
1757
  throw new Error("SpotPatch init received an invalid integration file plan.");
697
1758
  }
698
1759
  let targetMetadata;
699
1760
  try {
700
- targetMetadata = await (0, import_promises.lstat)(target);
1761
+ targetMetadata = await (0, import_promises2.lstat)(target);
701
1762
  } catch (error) {
702
1763
  if (!isMissingPathError(error)) {
703
1764
  throw error;
@@ -708,8 +1769,8 @@ async function assertSafeTarget(appRoot, realAppRoot, change) {
708
1769
  `SpotPatch refuses to modify the symbolic link ${change.relativePath}.`
709
1770
  );
710
1771
  }
711
- const containmentAnchor = await (0, import_promises.realpath)(
712
- targetMetadata === void 0 ? import_node_path.default.dirname(target) : target
1772
+ const containmentAnchor = await (0, import_promises2.realpath)(
1773
+ targetMetadata === void 0 ? import_node_path2.default.dirname(target) : target
713
1774
  );
714
1775
  if (!isPathWithin(realAppRoot, containmentAnchor)) {
715
1776
  throw new Error("SpotPatch init refuses to modify a path outside the app root.");
@@ -718,7 +1779,7 @@ async function assertSafeTarget(appRoot, realAppRoot, change) {
718
1779
  async function assertCurrentBaseline(change) {
719
1780
  if (change.previousContent === void 0) {
720
1781
  try {
721
- await (0, import_promises.lstat)(change.absolutePath);
1782
+ await (0, import_promises2.lstat)(change.absolutePath);
722
1783
  } catch (error) {
723
1784
  if (isMissingPathError(error)) {
724
1785
  return;
@@ -740,8 +1801,8 @@ async function applyIntegrationPlan(plan) {
740
1801
  if (plan.changes.length === 0) {
741
1802
  return;
742
1803
  }
743
- const appRoot = import_node_path.default.resolve(plan.appRoot);
744
- const realAppRoot = await (0, import_promises.realpath)(appRoot);
1804
+ const appRoot = import_node_path2.default.resolve(plan.appRoot);
1805
+ const realAppRoot = await (0, import_promises2.realpath)(appRoot);
745
1806
  const targets = /* @__PURE__ */ new Set();
746
1807
  for (const change of plan.changes) {
747
1808
  if (targets.has(change.absolutePath)) {
@@ -755,7 +1816,7 @@ async function applyIntegrationPlan(plan) {
755
1816
  try {
756
1817
  for (const change of plan.changes) {
757
1818
  await assertCurrentBaseline(change);
758
- const mode = change.previousContent === void 0 ? 384 : (await (0, import_promises.stat)(change.absolutePath)).mode & 511;
1819
+ const mode = change.previousContent === void 0 ? 384 : (await (0, import_promises2.stat)(change.absolutePath)).mode & 511;
759
1820
  await writeAtomic(change.absolutePath, change.nextContent, mode);
760
1821
  applied.push(change);
761
1822
  }
@@ -776,7 +1837,7 @@ async function applyIntegrationPlan(plan) {
776
1837
  }
777
1838
 
778
1839
  // src/options.ts
779
- var import_shared2 = require("@spotpatch/shared");
1840
+ var import_shared8 = require("@spotpatch/shared");
780
1841
  var import_zod = require("zod");
781
1842
  var DEFAULT_EXCLUDE = Object.freeze([
782
1843
  /node_modules/,
@@ -811,8 +1872,9 @@ var DEFAULT_OPTIONS = Object.freeze({
811
1872
  dataFlow: Object.freeze({
812
1873
  enabled: false,
813
1874
  runtime: "dispatch",
814
- limits: import_shared2.DEFAULT_DATA_FLOW_LIMITS
815
- })
1875
+ limits: import_shared8.DEFAULT_DATA_FLOW_LIMITS
1876
+ }),
1877
+ externalAgent: Object.freeze({ enabled: false })
816
1878
  });
817
1879
  var PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
818
1880
  var ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{1,127}$/;
@@ -857,7 +1919,7 @@ var aiOptionsSchema = import_zod.z.strictObject({
857
1919
  defaultProvider: import_zod.z.string(),
858
1920
  execution: import_zod.z.strictObject({
859
1921
  isolation: import_zod.z.literal("git-worktree").optional(),
860
- applyMode: import_zod.z.enum(import_shared2.AGENT_APPLY_MODES).optional(),
1922
+ applyMode: import_zod.z.enum(import_shared8.AGENT_APPLY_MODES).optional(),
861
1923
  checks: import_zod.z.record(import_zod.z.string(), agentCheckSchema).optional(),
862
1924
  limits: agentLimitsSchema
863
1925
  }).optional()
@@ -903,18 +1965,18 @@ function normalizeProviderBaseURL(value) {
903
1965
  }
904
1966
  function resolveLimits(limits) {
905
1967
  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
1968
+ maxTurns: limits?.maxTurns ?? import_shared8.DEFAULT_AGENT_LIMITS.maxTurns,
1969
+ maxToolCalls: limits?.maxToolCalls ?? import_shared8.DEFAULT_AGENT_LIMITS.maxToolCalls,
1970
+ maxChangedFiles: limits?.maxChangedFiles ?? import_shared8.DEFAULT_AGENT_LIMITS.maxChangedFiles,
1971
+ maxDiffBytes: limits?.maxDiffBytes ?? import_shared8.DEFAULT_AGENT_LIMITS.maxDiffBytes,
1972
+ maxReadBytesPerFile: limits?.maxReadBytesPerFile ?? import_shared8.DEFAULT_AGENT_LIMITS.maxReadBytesPerFile,
1973
+ maxToolOutputCharacters: limits?.maxToolOutputCharacters ?? import_shared8.DEFAULT_AGENT_LIMITS.maxToolOutputCharacters,
1974
+ maxProviderResponseBytes: limits?.maxProviderResponseBytes ?? import_shared8.DEFAULT_AGENT_LIMITS.maxProviderResponseBytes,
1975
+ providerConnectTimeoutMs: limits?.providerConnectTimeoutMs ?? import_shared8.DEFAULT_AGENT_LIMITS.providerConnectTimeoutMs,
1976
+ providerFirstByteTimeoutMs: limits?.providerFirstByteTimeoutMs ?? import_shared8.DEFAULT_AGENT_LIMITS.providerFirstByteTimeoutMs,
1977
+ providerIdleTimeoutMs: limits?.providerIdleTimeoutMs ?? import_shared8.DEFAULT_AGENT_LIMITS.providerIdleTimeoutMs,
1978
+ checkTimeoutMs: limits?.checkTimeoutMs ?? import_shared8.DEFAULT_AGENT_LIMITS.checkTimeoutMs,
1979
+ jobTimeoutMs: limits?.jobTimeoutMs ?? import_shared8.DEFAULT_AGENT_LIMITS.jobTimeoutMs
918
1980
  });
919
1981
  for (const [name, value] of Object.entries(resolved)) {
920
1982
  if (!Number.isSafeInteger(value) || value <= 0) {
@@ -1119,7 +2181,7 @@ function resolveDataFlowOptions(options) {
1119
2181
  return Object.freeze({
1120
2182
  enabled: true,
1121
2183
  runtime,
1122
- limits: import_shared2.DEFAULT_DATA_FLOW_LIMITS
2184
+ limits: import_shared8.DEFAULT_DATA_FLOW_LIMITS
1123
2185
  });
1124
2186
  }
1125
2187
  function createRuntimeDataFlowConfig(options) {
@@ -1138,6 +2200,9 @@ function resolveOptions(options = {}, environmentAi) {
1138
2200
  if (options.trustedFastMode !== void 0 && typeof options.trustedFastMode !== "boolean") {
1139
2201
  throw new RangeError("SpotPatch trustedFastMode must be a boolean.");
1140
2202
  }
2203
+ if (options.externalAgent !== void 0 && typeof options.externalAgent !== "boolean") {
2204
+ throw new RangeError("SpotPatch externalAgent must be a boolean.");
2205
+ }
1141
2206
  const budget = Object.freeze({
1142
2207
  ...DEFAULT_OPTIONS.budget,
1143
2208
  ...options.budget
@@ -1146,15 +2211,15 @@ function resolveOptions(options = {}, environmentAi) {
1146
2211
  const maxTargets = options.maxTargets ?? DEFAULT_OPTIONS.maxTargets;
1147
2212
  const locale = options.locale ?? DEFAULT_OPTIONS.locale;
1148
2213
  const editor = options.editor ?? DEFAULT_OPTIONS.editor;
1149
- if (!import_shared2.SPOTPATCH_LOCALE_PREFERENCES.includes(locale)) {
2214
+ if (!import_shared8.SPOTPATCH_LOCALE_PREFERENCES.includes(locale)) {
1150
2215
  throw new RangeError("SpotPatch locale must be auto, en-US, or zh-CN.");
1151
2216
  }
1152
- if (!import_shared2.SPOTPATCH_EDITOR_PREFERENCES.includes(editor)) {
2217
+ if (!import_shared8.SPOTPATCH_EDITOR_PREFERENCES.includes(editor)) {
1153
2218
  throw new RangeError("SpotPatch editor must be auto, vscode, or cursor.");
1154
2219
  }
1155
- if (!Number.isSafeInteger(maxTargets) || maxTargets < 1 || maxTargets > import_shared2.MAX_ANNOTATION_TARGETS) {
2220
+ if (!Number.isSafeInteger(maxTargets) || maxTargets < 1 || maxTargets > import_shared8.MAX_ANNOTATION_TARGETS) {
1156
2221
  throw new RangeError(
1157
- `SpotPatch maxTargets must be an integer between 1 and ${String(import_shared2.MAX_ANNOTATION_TARGETS)}.`
2222
+ `SpotPatch maxTargets must be an integer between 1 and ${String(import_shared8.MAX_ANNOTATION_TARGETS)}.`
1158
2223
  );
1159
2224
  }
1160
2225
  const resolved = {
@@ -1170,7 +2235,10 @@ function resolveOptions(options = {}, environmentAi) {
1170
2235
  locale,
1171
2236
  maxTargets,
1172
2237
  ai: resolveAiOptions(options.ai ?? environmentAi),
1173
- dataFlow: resolveDataFlowOptions(options.dataFlow)
2238
+ dataFlow: resolveDataFlowOptions(options.dataFlow),
2239
+ externalAgent: Object.freeze({
2240
+ enabled: options.externalAgent ?? DEFAULT_OPTIONS.externalAgent.enabled
2241
+ })
1174
2242
  };
1175
2243
  if (resolved.shortcut.trim().length === 0 || resolved.shortcut.length > 128 || resolved.shortcut.includes("\0")) {
1176
2244
  throw new RangeError("SpotPatch shortcut is invalid.");
@@ -1180,10 +2248,11 @@ function resolveOptions(options = {}, environmentAi) {
1180
2248
 
1181
2249
  // src/project-validation.ts
1182
2250
  var import_node_child_process = require("child_process");
1183
- var import_promises2 = require("fs/promises");
2251
+ var import_promises3 = require("fs/promises");
1184
2252
  var import_node_module = require("module");
1185
- var import_node_path2 = __toESM(require("path"), 1);
2253
+ var import_node_path3 = __toESM(require("path"), 1);
1186
2254
  var import_node_util = require("util");
2255
+ var import_shared9 = require("@spotpatch/shared");
1187
2256
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
1188
2257
  var TYPESCRIPT_CHECK_ID = "spotpatch-typecheck";
1189
2258
  var TYPESCRIPT_CHECK_LABEL = "TypeScript";
@@ -1192,19 +2261,19 @@ function isRecord(value) {
1192
2261
  }
1193
2262
  async function isRegularFile(absolutePath) {
1194
2263
  try {
1195
- const metadata = await (0, import_promises2.lstat)(absolutePath);
2264
+ const metadata = await (0, import_promises3.lstat)(absolutePath);
1196
2265
  return metadata.isFile() && !metadata.isSymbolicLink();
1197
2266
  } catch {
1198
2267
  return false;
1199
2268
  }
1200
2269
  }
1201
2270
  async function readManifest(appRoot) {
1202
- const manifestPath = import_node_path2.default.join(appRoot, "package.json");
2271
+ const manifestPath = import_node_path3.default.join(appRoot, "package.json");
1203
2272
  if (!await isRegularFile(manifestPath)) {
1204
2273
  return void 0;
1205
2274
  }
1206
2275
  try {
1207
- const value = JSON.parse(await (0, import_promises2.readFile)(manifestPath, "utf8"));
2276
+ const value = JSON.parse(await (0, import_promises3.readFile)(manifestPath, "utf8"));
1208
2277
  return isRecord(value) ? value : void 0;
1209
2278
  } catch {
1210
2279
  return void 0;
@@ -1227,9 +2296,9 @@ async function findGitRoot(appRoot) {
1227
2296
  timeout: 5e3,
1228
2297
  windowsHide: true
1229
2298
  });
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)) {
2299
+ const root = await (0, import_promises3.realpath)(result.stdout.trim());
2300
+ const relative = import_node_path3.default.relative(root, appRoot);
2301
+ if (relative === "" || !relative.startsWith(`..${import_node_path3.default.sep}`) && relative !== ".." && !import_node_path3.default.isAbsolute(relative)) {
1233
2302
  return root;
1234
2303
  }
1235
2304
  } catch {
@@ -1238,22 +2307,31 @@ async function findGitRoot(appRoot) {
1238
2307
  return void 0;
1239
2308
  }
1240
2309
  async function resolveTypeScriptCli(appRoot) {
1241
- const resolveFromApplication = (0, import_node_module.createRequire)(import_node_path2.default.join(appRoot, "package.json"));
2310
+ const resolveFromApplication = (0, import_node_module.createRequire)(import_node_path3.default.join(appRoot, "package.json"));
1242
2311
  try {
1243
2312
  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);
2313
+ const cliPath = import_node_path3.default.join(import_node_path3.default.dirname(packagePath), "bin", "tsc");
2314
+ await (0, import_promises3.access)(cliPath);
2315
+ return await (0, import_promises3.realpath)(cliPath);
1247
2316
  } catch {
1248
2317
  return void 0;
1249
2318
  }
1250
2319
  }
1251
2320
  function portableRelativePath(from, to) {
1252
- return import_node_path2.default.relative(from, to).split(import_node_path2.default.sep).join("/");
2321
+ return import_node_path3.default.relative(from, to).split(import_node_path3.default.sep).join("/");
2322
+ }
2323
+ function hasRequiredCheck(checks) {
2324
+ return Object.values(checks).some((check) => check.required);
2325
+ }
2326
+ function availableCheckId(checks, preferred) {
2327
+ if (checks[preferred] === void 0) return preferred;
2328
+ let suffix = 2;
2329
+ while (checks[`${preferred}-${String(suffix)}`] !== void 0) suffix += 1;
2330
+ return `${preferred}-${String(suffix)}`;
1253
2331
  }
1254
2332
  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");
2333
+ const appRoot = await (0, import_promises3.realpath)(options.appRoot);
2334
+ const tsconfigPath = import_node_path3.default.join(appRoot, "tsconfig.json");
1257
2335
  const [manifest, projectRoot, hasTsconfig] = await Promise.all([
1258
2336
  readManifest(appRoot),
1259
2337
  findGitRoot(appRoot),
@@ -1279,6 +2357,8 @@ async function discoverProjectValidationCheck(options) {
1279
2357
  "--noEmit",
1280
2358
  "--pretty",
1281
2359
  "false",
2360
+ "--incremental",
2361
+ "false",
1282
2362
  "--project",
1283
2363
  projectPath
1284
2364
  ]),
@@ -1286,21 +2366,30 @@ async function discoverProjectValidationCheck(options) {
1286
2366
  timeoutMs: options.timeoutMs
1287
2367
  });
1288
2368
  }
1289
-
1290
- // src/project-options.ts
1291
- function hasRequiredCheck(ai) {
1292
- return Object.values(ai.execution.checks).some((check) => check.required);
2369
+ async function resolveProjectValidationChecks(options) {
2370
+ if (hasRequiredCheck(options.checks)) return options.checks;
2371
+ const discovered = await discoverProjectValidationCheck(options);
2372
+ if (discovered === void 0) return options.checks;
2373
+ const id = availableCheckId(options.checks, discovered.id);
2374
+ return Object.freeze({
2375
+ ...options.checks,
2376
+ [id]: Object.freeze({ ...discovered, id })
2377
+ });
1293
2378
  }
1294
- function availableCheckId(checks, preferred) {
1295
- if (checks[preferred] === void 0) {
1296
- return preferred;
1297
- }
1298
- let suffix = 2;
1299
- while (checks[`${preferred}-${String(suffix)}`] !== void 0) {
1300
- suffix += 1;
1301
- }
1302
- return `${preferred}-${String(suffix)}`;
2379
+ async function resolveManagedExecutionValidation(options) {
2380
+ const checks = options.ai === false ? Object.freeze({}) : options.ai.execution.checks;
2381
+ const limits = options.ai === false ? import_shared9.DEFAULT_AGENT_LIMITS : options.ai.execution.limits;
2382
+ return Object.freeze({
2383
+ checks: await resolveProjectValidationChecks({
2384
+ appRoot: options.appRoot,
2385
+ checks,
2386
+ timeoutMs: limits.checkTimeoutMs
2387
+ }),
2388
+ limits
2389
+ });
1303
2390
  }
2391
+
2392
+ // src/project-options.ts
1304
2393
  async function resolveProjectOptions(input) {
1305
2394
  const userOptions = input.options ?? {};
1306
2395
  const resolved = resolveOptions(userOptions, input.environmentAi);
@@ -1312,22 +2401,15 @@ async function resolveProjectOptions(input) {
1312
2401
  "SpotPatch trustedFastMode cannot be combined with applyMode auto."
1313
2402
  );
1314
2403
  }
1315
- let checks = resolved.ai.execution.checks;
1316
- if (!hasRequiredCheck(resolved.ai)) {
1317
- const discovered = await discoverProjectValidationCheck({
1318
- appRoot: input.appRoot,
1319
- timeoutMs: resolved.ai.execution.limits.checkTimeoutMs
1320
- });
1321
- if (discovered === void 0) {
1322
- throw new RangeError(
1323
- "SpotPatch trustedFastMode requires a configured required check or a local TypeScript project with tsconfig.json."
1324
- );
1325
- }
1326
- const id = availableCheckId(checks, discovered.id);
1327
- checks = Object.freeze({
1328
- ...checks,
1329
- [id]: Object.freeze({ ...discovered, id })
1330
- });
2404
+ const checks = await resolveProjectValidationChecks({
2405
+ appRoot: input.appRoot,
2406
+ checks: resolved.ai.execution.checks,
2407
+ timeoutMs: resolved.ai.execution.limits.checkTimeoutMs
2408
+ });
2409
+ if (!Object.values(checks).some((check) => check.required)) {
2410
+ throw new RangeError(
2411
+ "SpotPatch trustedFastMode requires a configured required check or a local TypeScript project with tsconfig.json."
2412
+ );
1331
2413
  }
1332
2414
  const ai = Object.freeze({
1333
2415
  ...resolved.ai,
@@ -1341,16 +2423,16 @@ async function resolveProjectOptions(input) {
1341
2423
  }
1342
2424
 
1343
2425
  // src/registry/source-registry.ts
1344
- var import_node_path3 = __toESM(require("path"), 1);
2426
+ var import_node_path4 = __toESM(require("path"), 1);
1345
2427
 
1346
2428
  // src/registry/source-id.ts
1347
- var import_node_crypto3 = require("crypto");
2429
+ var import_node_crypto8 = require("crypto");
1348
2430
  var SOURCE_ID_BYTES = 8;
1349
- var createRandomSourceId = () => (0, import_node_crypto3.randomBytes)(SOURCE_ID_BYTES).toString("base64url");
2431
+ var createRandomSourceId = () => (0, import_node_crypto8.randomBytes)(SOURCE_ID_BYTES).toString("base64url");
1350
2432
 
1351
2433
  // src/registry/source-registry.ts
1352
2434
  function normalizeAbsolutePath(absolutePath) {
1353
- return import_node_path3.default.normalize(import_node_path3.default.resolve(absolutePath));
2435
+ return import_node_path4.default.normalize(import_node_path4.default.resolve(absolutePath));
1354
2436
  }
1355
2437
  function createSourceRegistry(options = {}) {
1356
2438
  const createId = options.createId ?? createRandomSourceId;
@@ -1407,20 +2489,20 @@ function createSourceRegistry(options = {}) {
1407
2489
  }
1408
2490
 
1409
2491
  // src/server/middleware.ts
1410
- var import_shared11 = require("@spotpatch/shared");
2492
+ var import_shared20 = require("@spotpatch/shared");
1411
2493
 
1412
- // src/server/agent-http.ts
1413
- var import_shared7 = require("@spotpatch/shared");
2494
+ // src/external-handoff/browser-http.ts
2495
+ var import_shared13 = require("@spotpatch/shared");
1414
2496
 
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");
2497
+ // src/server/annotation-authorizer.ts
2498
+ var import_promises6 = require("fs/promises");
2499
+ var import_node_path7 = __toESM(require("path"), 1);
2500
+ var import_shared12 = require("@spotpatch/shared");
1419
2501
 
1420
2502
  // 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");
2503
+ var import_promises5 = require("fs/promises");
2504
+ var import_node_path6 = __toESM(require("path"), 1);
2505
+ var import_shared11 = require("@spotpatch/shared");
1424
2506
 
1425
2507
  // src/server/extract-code-context.ts
1426
2508
  var import_oxc_parser = require("oxc-parser");
@@ -1649,16 +2731,9 @@ function extractCodeContext(options) {
1649
2731
  }
1650
2732
 
1651
2733
  // 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
2734
+ var import_promises4 = require("fs/promises");
2735
+ var import_node_path5 = __toESM(require("path"), 1);
2736
+ var import_shared10 = require("@spotpatch/shared");
1662
2737
  var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
1663
2738
  function isMissingFileError(error) {
1664
2739
  return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
@@ -1668,56 +2743,56 @@ async function assertInsideRoot(root, candidate) {
1668
2743
  let realCandidate;
1669
2744
  try {
1670
2745
  [realRoot, realCandidate] = await Promise.all([
1671
- (0, import_promises3.realpath)(root),
1672
- (0, import_promises3.realpath)(candidate)
2746
+ (0, import_promises4.realpath)(root),
2747
+ (0, import_promises4.realpath)(candidate)
1673
2748
  ]);
1674
2749
  } catch (error) {
1675
2750
  if (isMissingFileError(error)) {
1676
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2751
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
1677
2752
  cause: error
1678
2753
  });
1679
2754
  }
1680
2755
  throw error;
1681
2756
  }
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);
2757
+ const relative = import_node_path5.default.relative(realRoot, realCandidate);
2758
+ const outside = relative.startsWith(`..${import_node_path5.default.sep}`) || relative === ".." || import_node_path5.default.isAbsolute(relative);
1684
2759
  if (outside) {
1685
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
2760
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
1686
2761
  }
1687
2762
  return realCandidate;
1688
2763
  }
1689
2764
  async function resolveSourceFile(options) {
1690
2765
  const registeredPath = options.registry.resolve(options.fileId);
1691
2766
  if (registeredPath === void 0) {
1692
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND);
2767
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
1693
2768
  }
1694
2769
  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);
2770
+ if (!ALLOWED_EXTENSIONS.has(import_node_path5.default.extname(sourcePath).toLowerCase())) {
2771
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
1697
2772
  }
1698
2773
  let sourceStat;
1699
2774
  try {
1700
- sourceStat = await (0, import_promises3.stat)(sourcePath);
2775
+ sourceStat = await (0, import_promises4.stat)(sourcePath);
1701
2776
  } catch (error) {
1702
2777
  if (isMissingFileError(error)) {
1703
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
2778
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
1704
2779
  cause: error
1705
2780
  });
1706
2781
  }
1707
2782
  throw error;
1708
2783
  }
1709
2784
  if (!sourceStat.isFile()) {
1710
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND);
2785
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_NOT_FOUND);
1711
2786
  }
1712
2787
  if (sourceStat.size > MAX_SOURCE_FILE_BYTES) {
1713
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_TOO_LARGE);
2788
+ throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.SOURCE_TOO_LARGE);
1714
2789
  }
1715
2790
  return sourcePath;
1716
2791
  }
1717
2792
 
1718
2793
  // src/server/source-context.ts
1719
2794
  function toDisplayPath(root, sourcePath) {
1720
- return import_node_path5.default.relative(root, sourcePath).split(import_node_path5.default.sep).join("/");
2795
+ return import_node_path6.default.relative(root, sourcePath).split(import_node_path6.default.sep).join("/");
1721
2796
  }
1722
2797
  async function readSourceContext(options) {
1723
2798
  const sourcePath = await resolveSourceFile({
@@ -1727,10 +2802,10 @@ async function readSourceContext(options) {
1727
2802
  });
1728
2803
  let source;
1729
2804
  try {
1730
- source = await (0, import_promises4.readFile)(sourcePath, "utf8");
2805
+ source = await (0, import_promises5.readFile)(sourcePath, "utf8");
1731
2806
  } catch (error) {
1732
2807
  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, {
2808
+ throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
1734
2809
  cause: error
1735
2810
  });
1736
2811
  }
@@ -1738,13 +2813,13 @@ async function readSourceContext(options) {
1738
2813
  }
1739
2814
  const lines = source.split(/\r?\n/);
1740
2815
  if (options.request.line > lines.length) {
1741
- throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
2816
+ throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
1742
2817
  }
1743
- const extension = import_node_path5.default.extname(sourcePath).toLowerCase();
2818
+ const extension = import_node_path6.default.extname(sourcePath).toLowerCase();
1744
2819
  return extractCodeContext({
1745
2820
  source,
1746
2821
  sourcePath,
1747
- relativePath: toDisplayPath(await (0, import_promises4.realpath)(options.root), sourcePath),
2822
+ relativePath: toDisplayPath(await (0, import_promises5.realpath)(options.root), sourcePath),
1748
2823
  language: extension === ".tsx" ? "tsx" : "jsx",
1749
2824
  line: options.request.line,
1750
2825
  column: options.request.column,
@@ -1753,7 +2828,17 @@ async function readSourceContext(options) {
1753
2828
  });
1754
2829
  }
1755
2830
 
1756
- // src/server/agent-request.ts
2831
+ // src/server/annotation-authorizer.ts
2832
+ function sanitizePageContext(page) {
2833
+ return Object.freeze({
2834
+ url: (0, import_shared12.sanitizeUrl)(page.url, page.url),
2835
+ pathname: (0, import_shared12.redactSensitiveText)(page.pathname),
2836
+ title: (0, import_shared12.redactSensitiveText)(page.title),
2837
+ viewportWidth: page.viewportWidth,
2838
+ viewportHeight: page.viewportHeight,
2839
+ devicePixelRatio: page.devicePixelRatio
2840
+ });
2841
+ }
1757
2842
  function compactSourceRef(source) {
1758
2843
  return Object.freeze({
1759
2844
  origin: source.origin,
@@ -1767,27 +2852,20 @@ function compactSourceRef(source) {
1767
2852
  async function authorizeSourceRef(source, registry, root) {
1768
2853
  const markerOrigin = source.origin === "jsx-host" || source.origin === "dom-ancestor";
1769
2854
  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);
2855
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
1771
2856
  }
1772
2857
  if (source.fileId === void 0) {
1773
2858
  if (source.origin === "none" && source.relativePath !== void 0) {
1774
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2859
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
1775
2860
  }
1776
2861
  return compactSourceRef(source);
1777
2862
  }
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("/");
2863
+ const sourcePath = await resolveSourceFile({ fileId: source.fileId, registry, root });
2864
+ const relativePath = import_node_path7.default.relative(await (0, import_promises6.realpath)(root), sourcePath).split(import_node_path7.default.sep).join("/");
1784
2865
  if (source.relativePath !== void 0 && source.relativePath !== relativePath) {
1785
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2866
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
1786
2867
  }
1787
- return Object.freeze({
1788
- ...compactSourceRef(source),
1789
- relativePath
1790
- });
2868
+ return Object.freeze({ ...compactSourceRef(source), relativePath });
1791
2869
  }
1792
2870
  function freezeMatchedRule(rule) {
1793
2871
  return Object.freeze({
@@ -1821,7 +2899,7 @@ async function authorizeTarget(target, input) {
1821
2899
  maxLines: input.options.budget.maxCodeLines
1822
2900
  });
1823
2901
  if (marker === void 0 && target.code !== void 0) {
1824
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2902
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
1825
2903
  }
1826
2904
  const code = marker === void 0 ? void 0 : await readSourceContext({
1827
2905
  request: marker,
@@ -1831,11 +2909,11 @@ async function authorizeTarget(target, input) {
1831
2909
  maxLines: input.options.budget.maxCodeLines
1832
2910
  });
1833
2911
  if (target.code !== void 0 && target.code.relativePath !== code?.relativePath) {
1834
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2912
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
1835
2913
  }
1836
2914
  return Object.freeze({
1837
2915
  instruction: target.instruction,
1838
- ...target.page === void 0 ? {} : { page: Object.freeze({ ...target.page }) },
2916
+ ...target.page === void 0 ? {} : { page: sanitizePageContext(target.page) },
1839
2917
  source,
1840
2918
  react: Object.freeze({
1841
2919
  supported: target.react.supported,
@@ -1863,76 +2941,276 @@ async function authorizeTarget(target, input) {
1863
2941
  warnings: Object.freeze([...target.warnings])
1864
2942
  });
1865
2943
  }
1866
- async function authorizeAgentJobRequest(input) {
1867
- const requestedTargets = input.request.annotation.targets;
2944
+ async function authorizeAnnotation(input) {
2945
+ const requestedTargets = input.annotation.targets;
1868
2946
  if (requestedTargets.length > input.options.maxTargets) {
1869
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2947
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
1870
2948
  }
1871
2949
  const identities = requestedTargets.map(targetIdentity);
1872
2950
  if (new Set(identities).size !== identities.length) {
1873
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
2951
+ throw new import_shared12.SpotPatchError(import_shared12.ERROR_CODES.INVALID_REQUEST);
1874
2952
  }
1875
2953
  const targets = Object.freeze(
1876
2954
  await Promise.all(requestedTargets.map((target) => authorizeTarget(target, input)))
1877
2955
  );
1878
- const annotation = Object.freeze({
2956
+ return Object.freeze({
1879
2957
  schemaVersion: 3,
1880
- id: input.request.annotation.id,
1881
- locale: input.request.annotation.locale,
1882
- page: Object.freeze({ ...input.request.annotation.page }),
2958
+ id: input.annotation.id,
2959
+ locale: input.annotation.locale,
2960
+ page: sanitizePageContext(input.annotation.page),
1883
2961
  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
2962
+ createdAt: input.annotation.createdAt
1894
2963
  });
1895
2964
  }
1896
2965
 
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);
2966
+ // src/external-handoff/browser-http.ts
2967
+ function matchExternalHandoffBrowserPath(path9) {
2968
+ if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffCapability) return "capability";
2969
+ if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffPublish) return "publish";
2970
+ if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffStatus) return "status";
2971
+ if (path9 === import_shared13.SPOTPATCH_ENDPOINTS.externalHandoffResolveDelivery) {
2972
+ return "resolve-delivery";
1905
2973
  }
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);
2974
+ return void 0;
2975
+ }
2976
+ function requireService(options) {
2977
+ if (!options.options.externalAgent.enabled || options.service === void 0) {
2978
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
1909
2979
  }
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);
2980
+ return options.service;
2981
+ }
2982
+ function remapAuthorizationError(error) {
2983
+ if (error instanceof import_shared13.SpotPatchError) {
2984
+ if (error.code === import_shared13.ERROR_CODES.SOURCE_NOT_FOUND || error.code === import_shared13.ERROR_CODES.SOURCE_OUTSIDE_ROOT || error.code === import_shared13.ERROR_CODES.SOURCE_TOO_LARGE) {
2985
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_SOURCE_STALE, void 0, {
2986
+ cause: error
2987
+ });
1917
2988
  }
1918
- const buffer = Buffer.from(chunk);
1919
- byteLength += buffer.byteLength;
1920
- if (byteLength > maximumBytes) {
1921
- exceededLimit = true;
1922
- continue;
2989
+ if (error.code === import_shared13.ERROR_CODES.INVALID_REQUEST) {
2990
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_VALIDATION_FAILED, void 0, {
2991
+ cause: error
2992
+ });
1923
2993
  }
1924
- chunks.push(buffer);
1925
2994
  }
1926
- if (exceededLimit || byteLength === 0) {
1927
- throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.INVALID_REQUEST);
2995
+ throw error;
2996
+ }
2997
+ async function handleExternalHandoffBrowserRequest(request, response, route, options, writeSuccess) {
2998
+ if (request.method !== "POST") {
2999
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
1928
3000
  }
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
- });
3001
+ const service = requireService(options);
3002
+ if (route === "capability") {
3003
+ const parsed2 = import_shared13.externalHandoffCapabilityRequestSchema.safeParse(
3004
+ await readJsonRequestBody(request)
3005
+ );
3006
+ if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
3007
+ writeSuccess(response, 200, service.capability());
3008
+ return;
3009
+ }
3010
+ if (route === "status") {
3011
+ const parsed2 = import_shared13.externalHandoffStatusRequestSchema.safeParse(
3012
+ await readJsonRequestBody(request)
3013
+ );
3014
+ if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
3015
+ writeSuccess(response, 200, service.status(parsed2.data.cursor));
3016
+ return;
3017
+ }
3018
+ if (route === "resolve-delivery") {
3019
+ const parsed2 = import_shared13.externalHandoffResolveDeliveryRequestSchema.safeParse(
3020
+ await readJsonRequestBody(request)
3021
+ );
3022
+ if (!parsed2.success) throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.INVALID_REQUEST);
3023
+ writeSuccess(response, 200, service.resolveDelivery(parsed2.data.cursor));
3024
+ return;
1935
3025
  }
3026
+ const parsed = import_shared13.externalHandoffPublishRequestSchema.safeParse(
3027
+ await readJsonRequestBody(request, import_shared13.EXTERNAL_HANDOFF_LIMITS.maximumPublishBodyBytes)
3028
+ );
3029
+ if (!parsed.success) {
3030
+ throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
3031
+ }
3032
+ const result = await service.publish(parsed.data, async (annotation) => {
3033
+ try {
3034
+ return await authorizeAnnotation({
3035
+ annotation,
3036
+ options: options.options,
3037
+ registry: options.registry,
3038
+ root: options.root
3039
+ });
3040
+ } catch (error) {
3041
+ remapAuthorizationError(error);
3042
+ }
3043
+ });
3044
+ writeSuccess(response, result.replayed ? 200 : 201, result);
3045
+ }
3046
+
3047
+ // src/external-agent/browser-http.ts
3048
+ var import_shared14 = require("@spotpatch/shared");
3049
+ function matchExternalAgentBrowserPath(path9) {
3050
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlStatus) return "status";
3051
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlConnect) return "connect";
3052
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlDisconnect) {
3053
+ return "disconnect";
3054
+ }
3055
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentControlCancel) return "cancel";
3056
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentEvents) return "events";
3057
+ if (path9 === import_shared14.SPOTPATCH_ENDPOINTS.externalAgentResult) return "result";
3058
+ return void 0;
3059
+ }
3060
+ function writeEvent(response, event) {
3061
+ response.write(`${JSON.stringify(import_shared14.externalAgentEventSchema.parse(event))}
3062
+ `);
3063
+ }
3064
+ function createExternalAgentBrowserController(port) {
3065
+ const streams = /* @__PURE__ */ new Set();
3066
+ let disposed = false;
3067
+ const handleEvents = async (request, response) => {
3068
+ const parsed = import_shared14.externalAgentEventsRequestSchema.safeParse(
3069
+ await readJsonRequestBody(request)
3070
+ );
3071
+ if (!parsed.success) {
3072
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3073
+ }
3074
+ if (disposed || streams.size >= import_shared14.EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventSubscribers) {
3075
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.BRIDGE_BUSY);
3076
+ }
3077
+ response.statusCode = 200;
3078
+ response.setHeader("Cache-Control", "no-store");
3079
+ response.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
3080
+ response.setHeader("X-Content-Type-Options", "nosniff");
3081
+ streams.add(response);
3082
+ const initial = import_shared14.externalAgentControlStatusSchema.parse(port.getStatus());
3083
+ writeEvent(response, { type: "status", data: initial });
3084
+ const unsubscribe = port.subscribe((status) => {
3085
+ if (!response.destroyed && status.sequence > (parsed.data.afterSequence ?? -1)) {
3086
+ writeEvent(response, { type: "status", data: status });
3087
+ }
3088
+ });
3089
+ const heartbeat = setInterval(() => {
3090
+ if (!response.destroyed) {
3091
+ const status = port.getStatus();
3092
+ writeEvent(response, {
3093
+ type: "heartbeat",
3094
+ sequence: status.sequence,
3095
+ emittedAt: (/* @__PURE__ */ new Date()).toISOString()
3096
+ });
3097
+ }
3098
+ }, import_shared14.EXTERNAL_AGENT_CONTROL_LIMITS.eventHeartbeatMs);
3099
+ heartbeat.unref();
3100
+ await new Promise((resolve) => {
3101
+ const close = () => {
3102
+ response.off("close", close);
3103
+ clearInterval(heartbeat);
3104
+ unsubscribe();
3105
+ streams.delete(response);
3106
+ resolve();
3107
+ };
3108
+ response.once("close", close);
3109
+ });
3110
+ };
3111
+ return Object.freeze({
3112
+ async handle(request, response, route, writeSuccess) {
3113
+ if (request.method !== "POST" || disposed) {
3114
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3115
+ }
3116
+ if (route === "events") {
3117
+ await handleEvents(request, response);
3118
+ return;
3119
+ }
3120
+ if (route === "status") {
3121
+ const parsed2 = import_shared14.externalAgentControlStatusRequestSchema.safeParse(
3122
+ await readJsonRequestBody(request)
3123
+ );
3124
+ if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3125
+ writeSuccess(
3126
+ response,
3127
+ 200,
3128
+ import_shared14.externalAgentControlStatusSchema.parse(port.getStatus())
3129
+ );
3130
+ return;
3131
+ }
3132
+ if (route === "connect") {
3133
+ const parsed2 = import_shared14.externalAgentControlConnectRequestSchema.safeParse(
3134
+ await readJsonRequestBody(request)
3135
+ );
3136
+ if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3137
+ const controller = new AbortController();
3138
+ response.once("close", () => {
3139
+ if (!response.writableEnded) controller.abort("browser-disconnected");
3140
+ });
3141
+ writeSuccess(
3142
+ response,
3143
+ 200,
3144
+ import_shared14.externalAgentControlStatusSchema.parse(
3145
+ await port.connect(parsed2.data, controller.signal)
3146
+ )
3147
+ );
3148
+ return;
3149
+ }
3150
+ if (route === "disconnect") {
3151
+ const parsed2 = import_shared14.externalAgentControlDisconnectRequestSchema.safeParse(
3152
+ await readJsonRequestBody(request)
3153
+ );
3154
+ if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3155
+ writeSuccess(
3156
+ response,
3157
+ 200,
3158
+ import_shared14.externalAgentControlStatusSchema.parse(await port.disconnect(parsed2.data))
3159
+ );
3160
+ return;
3161
+ }
3162
+ if (route === "cancel") {
3163
+ const parsed2 = import_shared14.externalAgentControlCancelRequestSchema.safeParse(
3164
+ await readJsonRequestBody(request)
3165
+ );
3166
+ if (!parsed2.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3167
+ writeSuccess(
3168
+ response,
3169
+ 200,
3170
+ import_shared14.externalAgentControlStatusSchema.parse(await port.cancel(parsed2.data))
3171
+ );
3172
+ return;
3173
+ }
3174
+ const parsed = import_shared14.externalAgentResultRequestSchema.safeParse(
3175
+ await readJsonRequestBody(request)
3176
+ );
3177
+ if (!parsed.success) throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
3178
+ const result = port.getResult(parsed.data.revision);
3179
+ if (result === void 0) {
3180
+ throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.HANDOFF_NOT_FOUND);
3181
+ }
3182
+ writeSuccess(response, 200, import_shared14.externalAgentManagedResultSchema.parse(result));
3183
+ },
3184
+ dispose() {
3185
+ if (disposed) return;
3186
+ disposed = true;
3187
+ for (const response of streams) response.end();
3188
+ streams.clear();
3189
+ }
3190
+ });
3191
+ }
3192
+
3193
+ // src/server/agent-http.ts
3194
+ var import_shared16 = require("@spotpatch/shared");
3195
+
3196
+ // src/server/agent-request.ts
3197
+ var import_shared15 = require("@spotpatch/shared");
3198
+ async function authorizeAgentJobRequest(input) {
3199
+ const annotation = await authorizeAnnotation({
3200
+ annotation: input.request.annotation,
3201
+ options: input.options,
3202
+ registry: input.registry,
3203
+ root: input.root
3204
+ });
3205
+ return Object.freeze({
3206
+ annotation,
3207
+ ...input.request.applyMode === void 0 ? {} : { applyMode: input.request.applyMode },
3208
+ providerProfileId: input.request.providerProfileId,
3209
+ modelProfileId: input.request.modelProfileId,
3210
+ providerDataConsent: true,
3211
+ ...input.request.trustedFastModeConsent === true ? { trustedFastModeConsent: true } : {},
3212
+ workingTreeMode: input.request.workingTreeMode
3213
+ });
1936
3214
  }
1937
3215
 
1938
3216
  // src/server/agent-http.ts
@@ -1952,21 +3230,21 @@ var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
1952
3230
  "reverted",
1953
3231
  "failed"
1954
3232
  ]);
1955
- function matchAgentRequestPath(path8) {
1956
- if (path8 === import_shared7.SPOTPATCH_ENDPOINTS.agentCapability) {
3233
+ function matchAgentRequestPath(path9) {
3234
+ if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentCapability) {
1957
3235
  return Object.freeze({ kind: "capability" });
1958
3236
  }
1959
- if (path8 === import_shared7.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
3237
+ if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
1960
3238
  return Object.freeze({ kind: "workspace-health" });
1961
3239
  }
1962
- if (path8 === import_shared7.SPOTPATCH_ENDPOINTS.agentJobs) {
3240
+ if (path9 === import_shared16.SPOTPATCH_ENDPOINTS.agentJobs) {
1963
3241
  return Object.freeze({ kind: "create-job" });
1964
3242
  }
1965
- const prefix = `${import_shared7.SPOTPATCH_ENDPOINTS.agentJobs}/`;
1966
- if (!path8.startsWith(prefix)) {
3243
+ const prefix = `${import_shared16.SPOTPATCH_ENDPOINTS.agentJobs}/`;
3244
+ if (!path9.startsWith(prefix)) {
1967
3245
  return void 0;
1968
3246
  }
1969
- const segments = path8.slice(prefix.length).split("/");
3247
+ const segments = path9.slice(prefix.length).split("/");
1970
3248
  const jobId = segments[0];
1971
3249
  const action = segments[1];
1972
3250
  if (segments.length !== 2 || jobId === void 0 || !AGENT_JOB_ID_PATTERN.test(jobId) || action === void 0 || !AGENT_JOB_ACTIONS.has(action)) {
@@ -1980,7 +3258,7 @@ function matchAgentRequestPath(path8) {
1980
3258
  }
1981
3259
  function requireAgentManager(options) {
1982
3260
  if (options.agentManager === void 0 || options.options.ai === false) {
1983
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.AI_DISABLED);
3261
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.AI_DISABLED);
1984
3262
  }
1985
3263
  return options.agentManager;
1986
3264
  }
@@ -2033,13 +3311,13 @@ function streamAgentJobEvents(response, manager, jobId) {
2033
3311
  }
2034
3312
  async function handleCapability(request, response, options, writeSuccess) {
2035
3313
  if (request.method !== "POST") {
2036
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3314
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
2037
3315
  }
2038
- const parsed = import_shared7.agentCapabilityRequestSchema.safeParse(
3316
+ const parsed = import_shared16.agentCapabilityRequestSchema.safeParse(
2039
3317
  await readJsonRequestBody(request)
2040
3318
  );
2041
3319
  if (!parsed.success) {
2042
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3320
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
2043
3321
  }
2044
3322
  const controller = new AbortController();
2045
3323
  const abort = () => {
@@ -2058,13 +3336,13 @@ async function handleCapability(request, response, options, writeSuccess) {
2058
3336
  }
2059
3337
  async function handleCreateJob(request, response, options, writeSuccess) {
2060
3338
  if (request.method !== "POST") {
2061
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3339
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
2062
3340
  }
2063
- const parsed = import_shared7.agentJobCreateRequestSchema.safeParse(
3341
+ const parsed = import_shared16.agentJobCreateRequestSchema.safeParse(
2064
3342
  await readJsonRequestBody(request, MAX_AGENT_REQUEST_BODY_BYTES)
2065
3343
  );
2066
3344
  if (!parsed.success) {
2067
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3345
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
2068
3346
  }
2069
3347
  const authorizedRequest = await authorizeAgentJobRequest({
2070
3348
  request: parsed.data,
@@ -2077,13 +3355,13 @@ async function handleCreateJob(request, response, options, writeSuccess) {
2077
3355
  }
2078
3356
  async function handleWorkspaceHealth(request, response, options, writeSuccess) {
2079
3357
  if (request.method !== "POST") {
2080
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3358
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
2081
3359
  }
2082
- const parsed = import_shared7.agentWorkspaceHealthRequestSchema.safeParse(
3360
+ const parsed = import_shared16.agentWorkspaceHealthRequestSchema.safeParse(
2083
3361
  await readJsonRequestBody(request)
2084
3362
  );
2085
3363
  if (!parsed.success) {
2086
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3364
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
2087
3365
  }
2088
3366
  const controller = new AbortController();
2089
3367
  const abort = () => {
@@ -2100,13 +3378,13 @@ async function handleWorkspaceHealth(request, response, options, writeSuccess) {
2100
3378
  async function handleJobAction(request, response, options, route, writeSuccess) {
2101
3379
  const manager = requireAgentManager(options);
2102
3380
  if (request.method !== "POST") {
2103
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3381
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
2104
3382
  }
2105
- const parsed = import_shared7.agentJobActionRequestSchema.safeParse(
3383
+ const parsed = import_shared16.agentJobActionRequestSchema.safeParse(
2106
3384
  await readJsonRequestBody(request)
2107
3385
  );
2108
3386
  if (!parsed.success) {
2109
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
3387
+ throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
2110
3388
  }
2111
3389
  if (route.action === "events") {
2112
3390
  streamAgentJobEvents(response, manager, route.jobId);
@@ -2221,27 +3499,27 @@ function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
2221
3499
  var launchConfiguredEditor = createEditorLauncher();
2222
3500
 
2223
3501
  // src/server/data-flow-http.ts
2224
- var import_node_crypto4 = require("crypto");
3502
+ var import_node_crypto9 = require("crypto");
2225
3503
  var import_analyzer = require("@spotpatch/analyzer");
2226
- var import_shared8 = require("@spotpatch/shared");
3504
+ var import_shared17 = require("@spotpatch/shared");
2227
3505
  function envelopeBytes(report) {
2228
3506
  return Buffer.byteLength(JSON.stringify({ ok: true, data: report }), "utf8");
2229
3507
  }
2230
3508
  function limitDataFlowReportToBytes(report, maximumBytes) {
2231
- const structurallyLimited = (0, import_shared8.limitDataFlowReportCollections)(report);
3509
+ const structurallyLimited = (0, import_shared17.limitDataFlowReportCollections)(report);
2232
3510
  if (envelopeBytes(structurallyLimited) <= maximumBytes) {
2233
3511
  return structurallyLimited;
2234
3512
  }
2235
- let limited = (0, import_shared8.limitDataFlowReportCollections)(structurallyLimited, {
3513
+ let limited = (0, import_shared17.limitDataFlowReportCollections)(structurallyLimited, {
2236
3514
  forceTruncation: true,
2237
3515
  maximumDependencies: 0,
2238
3516
  truncatedBy: "bytes"
2239
3517
  });
2240
3518
  if (envelopeBytes(limited) > maximumBytes) {
2241
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.INTERNAL_ERROR);
3519
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INTERNAL_ERROR);
2242
3520
  }
2243
3521
  for (let maximumDependencies = 1; maximumDependencies <= structurallyLimited.dependencies.length; maximumDependencies += 1) {
2244
- const candidate = (0, import_shared8.limitDataFlowReportCollections)(structurallyLimited, {
3522
+ const candidate = (0, import_shared17.limitDataFlowReportCollections)(structurallyLimited, {
2245
3523
  forceTruncation: true,
2246
3524
  maximumDependencies,
2247
3525
  truncatedBy: "bytes"
@@ -2267,7 +3545,7 @@ async function analyzeTarget(request, analyzer, options) {
2267
3545
  request.componentSourceId
2268
3546
  );
2269
3547
  if (anchor?.sourceVersion !== request.sourceVersion) {
2270
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
3548
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
2271
3549
  }
2272
3550
  return anchor;
2273
3551
  }
@@ -2284,7 +3562,7 @@ async function analyzeTarget(request, analyzer, options) {
2284
3562
  column: resolvedRequest.column
2285
3563
  });
2286
3564
  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);
3565
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
2288
3566
  }
2289
3567
  return limitDataFlowReportToBytes(
2290
3568
  report,
@@ -2293,31 +3571,31 @@ async function analyzeTarget(request, analyzer, options) {
2293
3571
  }
2294
3572
  function requireAnalyzer(analyzer) {
2295
3573
  if (analyzer === void 0) {
2296
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.DATA_FLOW_DISABLED);
3574
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.DATA_FLOW_DISABLED);
2297
3575
  }
2298
3576
  return analyzer;
2299
3577
  }
2300
3578
  async function handleComponentDataFlowReport(request, analyzer, options) {
2301
- const parsed = import_shared8.dataFlowComponentReportRequestSchema.safeParse(
3579
+ const parsed = import_shared17.dataFlowComponentReportRequestSchema.safeParse(
2302
3580
  await readJsonRequestBody(
2303
3581
  request,
2304
3582
  options.options.dataFlow.limits.protocolRequestMaxBytes
2305
3583
  )
2306
3584
  );
2307
3585
  if (!parsed.success) {
2308
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.INVALID_REQUEST);
3586
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
2309
3587
  }
2310
3588
  return analyzeTarget(parsed.data, requireAnalyzer(analyzer), options);
2311
3589
  }
2312
3590
  async function handlePageDataFlowReport(request, analyzer, options) {
2313
- const parsed = import_shared8.dataFlowPageReportRequestSchema.safeParse(
3591
+ const parsed = import_shared17.dataFlowPageReportRequestSchema.safeParse(
2314
3592
  await readJsonRequestBody(
2315
3593
  request,
2316
3594
  options.options.dataFlow.limits.protocolRequestMaxBytes
2317
3595
  )
2318
3596
  );
2319
3597
  if (!parsed.success) {
2320
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.INVALID_REQUEST);
3598
+ throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
2321
3599
  }
2322
3600
  const activeAnalyzer = requireAnalyzer(analyzer);
2323
3601
  const componentReports = await Promise.all(
@@ -2341,10 +3619,10 @@ async function handlePageDataFlowReport(request, analyzer, options) {
2341
3619
  const analyzedVersions = new Set(
2342
3620
  componentReports.flatMap((report2) => report2.baseline.analyzedSourceVersions)
2343
3621
  );
2344
- const reportId = `page_${(0, import_node_crypto4.createHash)("sha256").update(componentReports.map((report2) => report2.reportId).join("\0")).digest("base64url").slice(0, 22)}`;
3622
+ const reportId = `page_${(0, import_node_crypto9.createHash)("sha256").update(componentReports.map((report2) => report2.reportId).join("\0")).digest("base64url").slice(0, 22)}`;
2345
3623
  const complete = componentReports.every((report2) => report2.completeness.complete);
2346
3624
  const report = Object.freeze({
2347
- schemaVersion: import_shared8.DATA_FLOW_SCHEMA_VERSION,
3625
+ schemaVersion: import_shared17.DATA_FLOW_SCHEMA_VERSION,
2348
3626
  reportId,
2349
3627
  baseline: Object.freeze({
2350
3628
  registryEpoch: options.session.id,
@@ -2388,20 +3666,20 @@ async function handlePageDataFlowReport(request, analyzer, options) {
2388
3666
  }
2389
3667
 
2390
3668
  // src/server/request-security.ts
2391
- var import_node_crypto5 = require("crypto");
3669
+ var import_node_crypto10 = require("crypto");
2392
3670
  var import_node_net = require("net");
2393
- var import_shared9 = require("@spotpatch/shared");
3671
+ var import_shared18 = require("@spotpatch/shared");
2394
3672
  function getSingleHeader(request, name) {
2395
3673
  const value = request.headers[name.toLowerCase()];
2396
3674
  return Array.isArray(value) ? value[0] : value;
2397
3675
  }
2398
- function tokensMatch(actual, expected) {
3676
+ function tokensMatch2(actual, expected) {
2399
3677
  if (actual === void 0) {
2400
3678
  return false;
2401
3679
  }
2402
3680
  const actualBytes = Buffer.from(actual);
2403
3681
  const expectedBytes = Buffer.from(expected);
2404
- return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto5.timingSafeEqual)(actualBytes, expectedBytes);
3682
+ return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto10.timingSafeEqual)(actualBytes, expectedBytes);
2405
3683
  }
2406
3684
  function isLoopbackHostname(hostname) {
2407
3685
  const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
@@ -2435,32 +3713,32 @@ function parseOrigin(value) {
2435
3713
  }
2436
3714
  }
2437
3715
  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);
3716
+ const actualToken = getSingleHeader(request, import_shared18.SPOTPATCH_TOKEN_HEADER);
3717
+ if (!tokensMatch2(actualToken, options.sessionToken)) {
3718
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.INVALID_TOKEN);
2441
3719
  }
2442
3720
  const hostHeader = getSingleHeader(request, "host");
2443
3721
  const originHeader = getSingleHeader(request, "origin");
2444
3722
  const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
2445
3723
  const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
2446
3724
  if (host === void 0 || origin === void 0) {
2447
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3725
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
2448
3726
  }
2449
3727
  const hostIsLoopback = isLoopbackHostname(host.hostname);
2450
3728
  const originIsLoopback = isLoopbackHostname(origin.hostname);
2451
3729
  if (!options.allowLan) {
2452
3730
  if (!hostIsLoopback || !originIsLoopback) {
2453
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3731
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
2454
3732
  }
2455
3733
  return;
2456
3734
  }
2457
3735
  if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
2458
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.ORIGIN_NOT_ALLOWED);
3736
+ throw new import_shared18.SpotPatchError(import_shared18.ERROR_CODES.ORIGIN_NOT_ALLOWED);
2459
3737
  }
2460
3738
  }
2461
3739
 
2462
3740
  // src/server/runtime-bootstrap.ts
2463
- var import_shared10 = require("@spotpatch/shared");
3741
+ var import_shared19 = require("@spotpatch/shared");
2464
3742
  function getSingleHeader2(request, name) {
2465
3743
  const value = request.headers[name.toLowerCase()];
2466
3744
  return Array.isArray(value) ? value[0] : value;
@@ -2475,7 +3753,7 @@ function resolveRuntimeBootstrapOptions(options) {
2475
3753
  if (expectedOrigin.origin !== options.expectedOrigin || expectedOrigin.protocol !== "http:" || !isLoopbackHostname(expectedOrigin.hostname)) {
2476
3754
  throw new TypeError("The SpotPatch bootstrap origin must be a loopback origin.");
2477
3755
  }
2478
- const parsedConfig = import_shared10.runtimeConfigSchema.safeParse(options.runtimeConfig);
3756
+ const parsedConfig = import_shared19.runtimeConfigSchema.safeParse(options.runtimeConfig);
2479
3757
  if (!parsedConfig.success) {
2480
3758
  throw new TypeError("The SpotPatch Runtime configuration is invalid.");
2481
3759
  }
@@ -2487,7 +3765,7 @@ function resolveRuntimeBootstrapOptions(options) {
2487
3765
  function assertRuntimeBootstrapRequest(request, expectedOrigin) {
2488
3766
  const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
2489
3767
  if (request.method !== "POST" || contentType !== "application/json") {
2490
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.INVALID_REQUEST);
3768
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
2491
3769
  }
2492
3770
  const host = getSingleHeader2(request, "host");
2493
3771
  let hostIsLoopback = false;
@@ -2499,112 +3777,148 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
2499
3777
  }
2500
3778
  }
2501
3779
  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);
3780
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.ORIGIN_NOT_ALLOWED);
2503
3781
  }
2504
3782
  }
2505
3783
  async function readRuntimeBootstrap(request, options) {
2506
3784
  assertRuntimeBootstrapRequest(request, options.expectedOrigin);
2507
- const parsedBody = import_shared10.runtimeBootstrapRequestSchema.safeParse(
3785
+ const parsedBody = import_shared19.runtimeBootstrapRequestSchema.safeParse(
2508
3786
  await readJsonRequestBody(request)
2509
3787
  );
2510
3788
  if (!parsedBody.success) {
2511
- throw new import_shared10.SpotPatchError(import_shared10.ERROR_CODES.INVALID_REQUEST);
3789
+ throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
2512
3790
  }
2513
3791
  return options.runtimeConfig;
2514
3792
  }
2515
3793
 
2516
3794
  // src/server/middleware.ts
2517
3795
  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
3796
+ [import_shared20.ERROR_CODES.INVALID_REQUEST]: 400,
3797
+ [import_shared20.ERROR_CODES.INVALID_TOKEN]: 401,
3798
+ [import_shared20.ERROR_CODES.ORIGIN_NOT_ALLOWED]: 403,
3799
+ [import_shared20.ERROR_CODES.SOURCE_NOT_FOUND]: 404,
3800
+ [import_shared20.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: 403,
3801
+ [import_shared20.ERROR_CODES.SOURCE_TOO_LARGE]: 413,
3802
+ [import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED]: 500,
3803
+ [import_shared20.ERROR_CODES.DATA_FLOW_DISABLED]: 404,
3804
+ [import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: 409,
3805
+ [import_shared20.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
3806
+ [import_shared20.ERROR_CODES.AI_DISABLED]: 404,
3807
+ [import_shared20.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: 503,
3808
+ [import_shared20.ERROR_CODES.PROVIDER_AUTH_FAILED]: 502,
3809
+ [import_shared20.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
3810
+ [import_shared20.ERROR_CODES.MODEL_NOT_ALLOWED]: 400,
3811
+ [import_shared20.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
3812
+ [import_shared20.ERROR_CODES.PROVIDER_RATE_LIMITED]: 429,
3813
+ [import_shared20.ERROR_CODES.AGENT_BUSY]: 409,
3814
+ [import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: 413,
3815
+ [import_shared20.ERROR_CODES.AGENT_CANCELLED]: 409,
3816
+ [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: 404,
3817
+ [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: 503,
3818
+ [import_shared20.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: 422,
3819
+ [import_shared20.ERROR_CODES.HANDOFF_SOURCE_STALE]: 409,
3820
+ [import_shared20.ERROR_CODES.HANDOFF_NOT_FOUND]: 404,
3821
+ [import_shared20.ERROR_CODES.HANDOFF_EXPIRED]: 410,
3822
+ [import_shared20.ERROR_CODES.HANDOFF_CURSOR_INVALID]: 409,
3823
+ [import_shared20.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: 413,
3824
+ [import_shared20.ERROR_CODES.BRIDGE_UNAUTHORIZED]: 401,
3825
+ [import_shared20.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: 409,
3826
+ [import_shared20.ERROR_CODES.BRIDGE_BUSY]: 429,
3827
+ [import_shared20.ERROR_CODES.EXTERNAL_AGENT_BUSY]: 409,
3828
+ [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: 409,
3829
+ [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: 409,
3830
+ [import_shared20.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: 409,
3831
+ [import_shared20.ERROR_CODES.SESSION_NOT_FOUND]: 404,
3832
+ [import_shared20.ERROR_CODES.SESSION_AMBIGUOUS]: 409,
3833
+ [import_shared20.ERROR_CODES.SESSION_CLOSED]: 410,
3834
+ [import_shared20.ERROR_CODES.WORKTREE_DIRTY]: 409,
3835
+ [import_shared20.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: 409,
3836
+ [import_shared20.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: 409,
3837
+ [import_shared20.ERROR_CODES.WORKTREE_CONFLICTED]: 409,
3838
+ [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
3839
+ [import_shared20.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
3840
+ [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
3841
+ [import_shared20.ERROR_CODES.TOOL_DENIED]: 403,
3842
+ [import_shared20.ERROR_CODES.TOOL_INPUT_INVALID]: 422,
3843
+ [import_shared20.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: 422,
3844
+ [import_shared20.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: 422,
3845
+ [import_shared20.ERROR_CODES.TOOL_PATH_DENIED]: 403,
3846
+ [import_shared20.ERROR_CODES.PATCH_REJECTED]: 422,
3847
+ [import_shared20.ERROR_CODES.VALIDATION_FAILED]: 422,
3848
+ [import_shared20.ERROR_CODES.APPLY_CONFLICT]: 409,
3849
+ [import_shared20.ERROR_CODES.INTERNAL_ERROR]: 500
2554
3850
  });
2555
3851
  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."
3852
+ [import_shared20.ERROR_CODES.INVALID_REQUEST]: "The request is invalid.",
3853
+ [import_shared20.ERROR_CODES.INVALID_TOKEN]: "The session token is invalid.",
3854
+ [import_shared20.ERROR_CODES.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
3855
+ [import_shared20.ERROR_CODES.SOURCE_NOT_FOUND]: "The source file is unavailable.",
3856
+ [import_shared20.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
3857
+ [import_shared20.ERROR_CODES.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
3858
+ [import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
3859
+ [import_shared20.ERROR_CODES.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
3860
+ [import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
3861
+ [import_shared20.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
3862
+ [import_shared20.ERROR_CODES.AI_DISABLED]: "AI execution is not enabled.",
3863
+ [import_shared20.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
3864
+ [import_shared20.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
3865
+ [import_shared20.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
3866
+ [import_shared20.ERROR_CODES.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
3867
+ [import_shared20.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
3868
+ [import_shared20.ERROR_CODES.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
3869
+ [import_shared20.ERROR_CODES.AGENT_BUSY]: "Another Agent job is already running.",
3870
+ [import_shared20.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
3871
+ [import_shared20.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
3872
+ [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: "External Agent handoff is not enabled.",
3873
+ [import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: "External Agent handoff is temporarily unavailable.",
3874
+ [import_shared20.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: "The handoff content is invalid.",
3875
+ [import_shared20.ERROR_CODES.HANDOFF_SOURCE_STALE]: "The selected source is stale.",
3876
+ [import_shared20.ERROR_CODES.HANDOFF_NOT_FOUND]: "No current handoff is available.",
3877
+ [import_shared20.ERROR_CODES.HANDOFF_EXPIRED]: "The handoff has expired.",
3878
+ [import_shared20.ERROR_CODES.HANDOFF_CURSOR_INVALID]: "The handoff cursor is invalid.",
3879
+ [import_shared20.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: "The handoff exceeds the size limit.",
3880
+ [import_shared20.ERROR_CODES.BRIDGE_UNAUTHORIZED]: "The local bridge request is unauthorized.",
3881
+ [import_shared20.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: "The local bridge protocol is incompatible.",
3882
+ [import_shared20.ERROR_CODES.BRIDGE_BUSY]: "The local bridge is busy.",
3883
+ [import_shared20.ERROR_CODES.EXTERNAL_AGENT_BUSY]: "The connected external Agent is busy.",
3884
+ [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: "Another active Agent adapter is connected.",
3885
+ [import_shared20.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: "The active Agent adapter lease is invalid.",
3886
+ [import_shared20.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: "The active Agent dispatch transition is invalid.",
3887
+ [import_shared20.ERROR_CODES.SESSION_NOT_FOUND]: "No active SpotPatch session was found.",
3888
+ [import_shared20.ERROR_CODES.SESSION_AMBIGUOUS]: "More than one SpotPatch session matches.",
3889
+ [import_shared20.ERROR_CODES.SESSION_CLOSED]: "The SpotPatch session has closed.",
3890
+ [import_shared20.ERROR_CODES.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
3891
+ [import_shared20.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
3892
+ [import_shared20.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
3893
+ [import_shared20.ERROR_CODES.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
3894
+ [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
3895
+ [import_shared20.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
3896
+ [import_shared20.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
3897
+ [import_shared20.ERROR_CODES.TOOL_DENIED]: "The Agent tool request was denied.",
3898
+ [import_shared20.ERROR_CODES.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
3899
+ [import_shared20.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
3900
+ [import_shared20.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
3901
+ [import_shared20.ERROR_CODES.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
3902
+ [import_shared20.ERROR_CODES.PATCH_REJECTED]: "The proposed patch was rejected.",
3903
+ [import_shared20.ERROR_CODES.VALIDATION_FAILED]: "The proposed change failed validation.",
3904
+ [import_shared20.ERROR_CODES.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
3905
+ [import_shared20.ERROR_CODES.INTERNAL_ERROR]: "The request could not be completed."
2592
3906
  });
2593
- function writeJson(response, status, payload) {
3907
+ function writeJson2(response, status, payload) {
2594
3908
  response.statusCode = status;
2595
3909
  response.setHeader("Cache-Control", "no-store");
2596
3910
  response.setHeader("Content-Type", "application/json; charset=utf-8");
2597
3911
  response.end(JSON.stringify(payload));
2598
3912
  }
2599
3913
  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 });
3914
+ return error instanceof import_shared20.SpotPatchError ? error : new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
2601
3915
  }
2602
3916
  function writeError(response, error, logger) {
2603
3917
  const normalized = asSpotPatchError(error);
2604
- if (normalized.code === import_shared11.ERROR_CODES.INTERNAL_ERROR) {
3918
+ if (normalized.code === import_shared20.ERROR_CODES.INTERNAL_ERROR) {
2605
3919
  logger?.warn("[spotpatch:server] Internal request failure.");
2606
3920
  }
2607
- writeJson(response, STATUS_BY_ERROR[normalized.code], {
3921
+ writeJson2(response, STATUS_BY_ERROR[normalized.code], {
2608
3922
  ok: false,
2609
3923
  error: {
2610
3924
  code: normalized.code,
@@ -2620,11 +3934,11 @@ function requestPath(request) {
2620
3934
  }
2621
3935
  }
2622
3936
  async function handleSourceContext(request, options) {
2623
- const parsed = import_shared11.sourceContextRequestSchema.safeParse(
3937
+ const parsed = import_shared20.sourceContextRequestSchema.safeParse(
2624
3938
  await readJsonRequestBody(request)
2625
3939
  );
2626
3940
  if (!parsed.success) {
2627
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
3941
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
2628
3942
  }
2629
3943
  return readSourceContext({
2630
3944
  request: parsed.data,
@@ -2635,9 +3949,9 @@ async function handleSourceContext(request, options) {
2635
3949
  });
2636
3950
  }
2637
3951
  async function handleOpenEditor(request, options) {
2638
- const parsed = import_shared11.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
3952
+ const parsed = import_shared20.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
2639
3953
  if (!parsed.success) {
2640
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
3954
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
2641
3955
  }
2642
3956
  const body = parsed.data;
2643
3957
  const sourcePath = await resolveSourceFile({
@@ -2654,7 +3968,7 @@ async function handleOpenEditor(request, options) {
2654
3968
  options.logger?.warn(
2655
3969
  `[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
2656
3970
  );
2657
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
3971
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
2658
3972
  cause: error
2659
3973
  });
2660
3974
  }
@@ -2662,64 +3976,98 @@ async function handleOpenEditor(request, options) {
2662
3976
  function createSpotPatchMiddleware(options) {
2663
3977
  const bootstrap = options.bootstrap === void 0 ? void 0 : resolveRuntimeBootstrapOptions(options.bootstrap);
2664
3978
  const dataFlowAnalyzer = createDataFlowAnalyzer(options);
2665
- 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}/`)) {
3979
+ const externalAgentController = options.externalAgentControl === void 0 ? void 0 : createExternalAgentBrowserController(options.externalAgentControl);
3980
+ const middleware = (request, response, next) => {
3981
+ const path9 = requestPath(request);
3982
+ const agentRoute = matchAgentRequestPath(path9);
3983
+ const externalAgentRoute = matchExternalAgentBrowserPath(path9);
3984
+ const externalHandoffRoute = matchExternalHandoffBrowserPath(path9);
3985
+ if (path9 !== import_shared20.SPOTPATCH_ENDPOINTS.sourceContext && path9 !== import_shared20.SPOTPATCH_ENDPOINTS.openEditor && path9 !== import_shared20.SPOTPATCH_ENDPOINTS.dataFlowComponentReport && path9 !== import_shared20.SPOTPATCH_ENDPOINTS.dataFlowPageReport && agentRoute === void 0 && externalAgentRoute === void 0 && externalHandoffRoute === void 0 && !path9.startsWith(`${import_shared20.SPOTPATCH_API_BASE}/`)) {
2669
3986
  next();
2670
3987
  return;
2671
3988
  }
2672
3989
  const handle = async () => {
2673
- if (path8 === import_shared11.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
3990
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
2674
3991
  const data = await readRuntimeBootstrap(
2675
3992
  request,
2676
3993
  bootstrap
2677
3994
  );
2678
- writeJson(response, 200, { ok: true, data });
3995
+ writeJson2(response, 200, { ok: true, data });
2679
3996
  return;
2680
3997
  }
2681
3998
  assertRequestAuthorized(request, {
2682
3999
  allowLan: options.options.allowLan,
2683
4000
  sessionToken: options.session.token
2684
4001
  });
2685
- if (path8 === import_shared11.SPOTPATCH_ENDPOINTS.sourceContext) {
4002
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.sourceContext) {
2686
4003
  if (request.method !== "POST") {
2687
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
4004
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
2688
4005
  }
2689
4006
  const data = await handleSourceContext(request, options);
2690
- writeJson(response, 200, { ok: true, data });
4007
+ writeJson2(response, 200, { ok: true, data });
2691
4008
  return;
2692
4009
  }
2693
- if (path8 === import_shared11.SPOTPATCH_ENDPOINTS.openEditor) {
4010
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.openEditor) {
2694
4011
  if (request.method !== "POST") {
2695
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
4012
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
2696
4013
  }
2697
4014
  const data = await handleOpenEditor(request, options);
2698
- writeJson(response, 200, { ok: true, data });
4015
+ writeJson2(response, 200, { ok: true, data });
2699
4016
  return;
2700
4017
  }
2701
- if (path8 === import_shared11.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
4018
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
2702
4019
  if (request.method !== "POST") {
2703
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
4020
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
2704
4021
  }
2705
4022
  const data = await handleComponentDataFlowReport(
2706
4023
  request,
2707
4024
  dataFlowAnalyzer,
2708
4025
  options
2709
4026
  );
2710
- writeJson(response, 200, { ok: true, data });
4027
+ writeJson2(response, 200, { ok: true, data });
2711
4028
  return;
2712
4029
  }
2713
- if (path8 === import_shared11.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
4030
+ if (path9 === import_shared20.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
2714
4031
  if (request.method !== "POST") {
2715
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
4032
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
2716
4033
  }
2717
4034
  const data = await handlePageDataFlowReport(request, dataFlowAnalyzer, options);
2718
- writeJson(response, 200, { ok: true, data });
4035
+ writeJson2(response, 200, { ok: true, data });
4036
+ return;
4037
+ }
4038
+ if (externalAgentRoute !== void 0) {
4039
+ if (!options.options.externalAgent.enabled || externalAgentController === void 0) {
4040
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
4041
+ }
4042
+ await externalAgentController.handle(
4043
+ request,
4044
+ response,
4045
+ externalAgentRoute,
4046
+ (target, status, data) => {
4047
+ writeJson2(target, status, { ok: true, data });
4048
+ }
4049
+ );
4050
+ return;
4051
+ }
4052
+ if (externalHandoffRoute !== void 0) {
4053
+ await handleExternalHandoffBrowserRequest(
4054
+ request,
4055
+ response,
4056
+ externalHandoffRoute,
4057
+ {
4058
+ options: options.options,
4059
+ registry: options.registry,
4060
+ root: options.root,
4061
+ ...options.externalHandoffService === void 0 ? {} : { service: options.externalHandoffService }
4062
+ },
4063
+ (target, status, data) => {
4064
+ writeJson2(target, status, { ok: true, data });
4065
+ }
4066
+ );
2719
4067
  return;
2720
4068
  }
2721
4069
  if (agentRoute === void 0) {
2722
- throw new import_shared11.SpotPatchError(import_shared11.ERROR_CODES.INVALID_REQUEST);
4070
+ throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
2723
4071
  }
2724
4072
  await handleAgentRequest(
2725
4073
  request,
@@ -2727,7 +4075,7 @@ function createSpotPatchMiddleware(options) {
2727
4075
  options,
2728
4076
  agentRoute,
2729
4077
  (target, status, data) => {
2730
- writeJson(target, status, { ok: true, data });
4078
+ writeJson2(target, status, { ok: true, data });
2731
4079
  }
2732
4080
  );
2733
4081
  };
@@ -2735,12 +4083,17 @@ function createSpotPatchMiddleware(options) {
2735
4083
  writeError(response, error, options.logger);
2736
4084
  });
2737
4085
  };
4086
+ return Object.assign(middleware, {
4087
+ dispose() {
4088
+ externalAgentController?.dispose();
4089
+ }
4090
+ });
2738
4091
  }
2739
4092
 
2740
4093
  // 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);
4094
+ var import_node_crypto11 = require("crypto");
4095
+ var import_promises7 = require("fs/promises");
4096
+ var import_node_path8 = __toESM(require("path"), 1);
2744
4097
  var import_compiler = require("@spotpatch/compiler");
2745
4098
  var import_zod2 = require("zod");
2746
4099
  var REGISTRATION_BODY_LIMIT_BYTES = 4096;
@@ -2761,16 +4114,16 @@ function identitiesMatch(actual, expected) {
2761
4114
  }
2762
4115
  const actualBytes = Buffer.from(actual);
2763
4116
  const expectedBytes = Buffer.from(expected);
2764
- return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto6.timingSafeEqual)(actualBytes, expectedBytes);
4117
+ return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto11.timingSafeEqual)(actualBytes, expectedBytes);
2765
4118
  }
2766
4119
  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);
4120
+ const relative = import_node_path8.default.relative(root, candidate);
4121
+ return relative === "" || !relative.startsWith(`..${import_node_path8.default.sep}`) && relative !== ".." && !import_node_path8.default.isAbsolute(relative);
2769
4122
  }
2770
4123
  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));
4124
+ return import_node_path8.default.relative(root, candidate).split(import_node_path8.default.sep).some((segment) => FORBIDDEN_SOURCE_SEGMENTS.has(segment));
2772
4125
  }
2773
- function writeJson2(response, statusCode, payload) {
4126
+ function writeJson3(response, statusCode, payload) {
2774
4127
  const body = JSON.stringify(payload);
2775
4128
  response.statusCode = statusCode;
2776
4129
  response.setHeader("Cache-Control", "no-store");
@@ -2790,15 +4143,15 @@ function requestComesFromLoopbackWorker(request) {
2790
4143
  }
2791
4144
  }
2792
4145
  async function resolveAuthorizedSource(root, requestedPath, shouldTransform) {
2793
- if (!import_node_path7.default.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
4146
+ if (!import_node_path8.default.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
2794
4147
  return void 0;
2795
4148
  }
2796
4149
  try {
2797
- const sourceStat = await (0, import_promises6.lstat)(requestedPath);
4150
+ const sourceStat = await (0, import_promises7.lstat)(requestedPath);
2798
4151
  if (!sourceStat.isFile() || sourceStat.isSymbolicLink()) {
2799
4152
  return void 0;
2800
4153
  }
2801
- const resolvedPath = await (0, import_promises6.realpath)(requestedPath);
4154
+ const resolvedPath = await (0, import_promises7.realpath)(requestedPath);
2802
4155
  if (!isWithinRoot(root, resolvedPath) || hasForbiddenSegment(root, resolvedPath) || !shouldTransform(resolvedPath)) {
2803
4156
  return void 0;
2804
4157
  }
@@ -2811,7 +4164,7 @@ async function createSourceRegistrationService(input) {
2811
4164
  if (!REGISTRATION_IDENTITY_PATTERN.test(input.internalSecret) || !REGISTRATION_IDENTITY_PATTERN.test(input.registryEpoch)) {
2812
4165
  throw new TypeError("The source registration identity is invalid.");
2813
4166
  }
2814
- const root = await (0, import_promises6.realpath)(input.root);
4167
+ const root = await (0, import_promises7.realpath)(input.root);
2815
4168
  const sourceFilter = (0, import_compiler.createSourceFilter)(root, input.options);
2816
4169
  const handler = (request, response) => {
2817
4170
  const handle = async () => {
@@ -2820,14 +4173,14 @@ async function createSourceRegistrationService(input) {
2820
4173
  getSingleHeader3(request, INTERNAL_SECRET_HEADER),
2821
4174
  input.internalSecret
2822
4175
  )) {
2823
- writeJson2(response, 403, { ok: false });
4176
+ writeJson3(response, 403, { ok: false });
2824
4177
  return;
2825
4178
  }
2826
4179
  const parsed = registrationRequestSchema.safeParse(
2827
4180
  await readJsonRequestBody(request, REGISTRATION_BODY_LIMIT_BYTES)
2828
4181
  );
2829
4182
  if (!parsed.success || parsed.data.epoch !== input.registryEpoch) {
2830
- writeJson2(response, 400, { ok: false });
4183
+ writeJson3(response, 400, { ok: false });
2831
4184
  return;
2832
4185
  }
2833
4186
  const sourcePath = await resolveAuthorizedSource(
@@ -2836,17 +4189,17 @@ async function createSourceRegistrationService(input) {
2836
4189
  (absolutePath) => sourceFilter.shouldTransform(absolutePath, "<")
2837
4190
  );
2838
4191
  if (sourcePath === void 0) {
2839
- writeJson2(response, 403, { ok: false });
4192
+ writeJson3(response, 403, { ok: false });
2840
4193
  return;
2841
4194
  }
2842
- writeJson2(response, 200, {
4195
+ writeJson3(response, 200, {
2843
4196
  epoch: input.registryEpoch,
2844
4197
  fileId: input.registry.register(sourcePath)
2845
4198
  });
2846
4199
  };
2847
4200
  void handle().catch(() => {
2848
4201
  if (!response.headersSent) {
2849
- writeJson2(response, 400, { ok: false });
4202
+ writeJson3(response, 400, { ok: false });
2850
4203
  } else {
2851
4204
  response.destroy();
2852
4205
  }
@@ -2856,11 +4209,11 @@ async function createSourceRegistrationService(input) {
2856
4209
  }
2857
4210
 
2858
4211
  // src/session/session.ts
2859
- var import_node_crypto7 = require("crypto");
4212
+ var import_node_crypto12 = require("crypto");
2860
4213
  function createSession() {
2861
4214
  return Object.freeze({
2862
- id: (0, import_node_crypto7.randomBytes)(16).toString("base64url"),
2863
- token: (0, import_node_crypto7.randomBytes)(16).toString("base64url")
4215
+ id: (0, import_node_crypto12.randomBytes)(16).toString("base64url"),
4216
+ token: (0, import_node_crypto12.randomBytes)(16).toString("base64url")
2864
4217
  });
2865
4218
  }
2866
4219
 
@@ -2873,6 +4226,7 @@ var OPTION_KEYS = Object.freeze([
2873
4226
  "dataFlow",
2874
4227
  "editor",
2875
4228
  "enabled",
4229
+ "externalAgent",
2876
4230
  "exclude",
2877
4231
  "include",
2878
4232
  "locale",
@@ -2963,6 +4317,7 @@ function serializeResolvedSpotPatchOptions(options) {
2963
4317
  }) : false,
2964
4318
  editor: options.editor,
2965
4319
  enabled: options.enabled,
4320
+ externalAgent: options.externalAgent.enabled,
2966
4321
  exclude: Object.freeze(options.exclude.map(serializeFilter)),
2967
4322
  include: Object.freeze(options.include.map(serializeFilter)),
2968
4323
  locale: options.locale,
@@ -3016,7 +4371,7 @@ function parseSerializedSpotPatchOptions(value) {
3016
4371
  if (!isRecord2(value) || !hasExactKeys(value, OPTION_KEYS)) {
3017
4372
  throw new TypeError("The SpotPatch options transport is invalid.");
3018
4373
  }
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)) {
4374
+ 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
4375
  throw new TypeError("The SpotPatch options transport is invalid.");
3021
4376
  }
3022
4377
  try {
@@ -3028,6 +4383,7 @@ function parseSerializedSpotPatchOptions(value) {
3028
4383
  dataFlow: parseDataFlow(value.dataFlow),
3029
4384
  editor: value.editor,
3030
4385
  enabled: value.enabled,
4386
+ externalAgent: value.externalAgent,
3031
4387
  exclude: parseFilterList(value.exclude),
3032
4388
  include: parseFilterList(value.include),
3033
4389
  locale: value.locale,
@@ -3047,6 +4403,7 @@ function parseSerializedSpotPatchOptions(value) {
3047
4403
  DEFAULT_OPTIONS,
3048
4404
  applyIntegrationPlan,
3049
4405
  createAgentJobManager,
4406
+ createExternalHandoffService,
3050
4407
  createIntegrationFileChange,
3051
4408
  createRuntimeAiConfig,
3052
4409
  createRuntimeDataFlowConfig,
@@ -3063,8 +4420,10 @@ function parseSerializedSpotPatchOptions(value) {
3063
4420
  readRuntimeBootstrap,
3064
4421
  resolveCredentialEnvironment,
3065
4422
  resolveEnvironmentAiConfiguration,
4423
+ resolveManagedExecutionValidation,
3066
4424
  resolveOptions,
3067
4425
  resolveProjectOptions,
4426
+ resolveProjectValidationChecks,
3068
4427
  resolveRuntimeBootstrapOptions,
3069
4428
  serializeResolvedSpotPatchOptions
3070
4429
  });