pipeline-moe 0.1.4 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pipeline-moe",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "description": "Local-first multi-agent chat room — N stateful pi agent sessions over a shared workspace, with terminal and web clients.",
6
6
  "license": "MIT",
package/src/server.ts CHANGED
@@ -718,8 +718,13 @@ async function main(): Promise<void> {
718
718
  })
719
719
 
720
720
  // Pending manual inputs for in-flight OAuth flows (pasted redirect URL or
721
- // authorization code), keyed by provider. Resolved by the /login/input route.
722
- const pendingOAuthInputs = new Map<string, (value: string) => void>()
721
+ // authorization code), keyed by provider. Resolved by the /login/input route,
722
+ // rejected by DELETE /login (cancel) rejecting is what makes pi's flow
723
+ // abort and release the localhost callback port instead of waiting forever.
724
+ const pendingOAuthInputs = new Map<
725
+ string,
726
+ { resolve: (value: string) => void; reject: (err: Error) => void }
727
+ >()
723
728
 
724
729
  // Start OAuth login flow for a provider (device-code or auth URL).
725
730
  app.post("/api/providers/:name/login", async (req, res) => {
@@ -735,9 +740,22 @@ async function main(): Promise<void> {
735
740
  return
736
741
  }
737
742
 
743
+ // Retrying while a previous flow for this provider is still waiting would
744
+ // collide on pi's fixed callback port (EADDRINUSE) — abort the old flow
745
+ // first so a retry always gets a fresh start.
746
+ const stale = pendingOAuthInputs.get(name)
747
+ if (stale) {
748
+ pendingOAuthInputs.delete(name)
749
+ stale.reject(new Error("Login restarted"))
750
+ await new Promise((r) => setTimeout(r, 100)) // let the old flow release the port
751
+ }
752
+
738
753
  // Start the login flow in the background — communicate progress via SSE.
739
754
  // Return 202 immediately so the client can wait for SSE events.
740
755
  const providerName = name // capture for closure
756
+ // Identity of THIS attempt's pending entry: the finally below must only
757
+ // clear the map when the entry is still ours, never a newer attempt's.
758
+ let myEntry: { resolve: (value: string) => void; reject: (err: Error) => void } | undefined
741
759
  setImmediate(async () => {
742
760
  try {
743
761
  await resolved.authStorage.login(name, {
@@ -770,8 +788,9 @@ async function main(): Promise<void> {
770
788
  // is on another machine. The promise resolves when a client POSTs
771
789
  // /login/input; cleanup happens in the outer finally.
772
790
  onManualCodeInput: () =>
773
- new Promise<string>((resolve) => {
774
- pendingOAuthInputs.set(providerName, resolve)
791
+ new Promise<string>((resolve, reject) => {
792
+ myEntry = { resolve, reject }
793
+ pendingOAuthInputs.set(providerName, myEntry)
775
794
  }),
776
795
  onPrompt: async (prompt) => {
777
796
  // Ask the clients for input over SSE and wait for /login/input.
@@ -781,8 +800,9 @@ async function main(): Promise<void> {
781
800
  message: prompt.message,
782
801
  placeholder: (prompt as { placeholder?: string }).placeholder,
783
802
  })
784
- return new Promise<string>((resolve) => {
785
- pendingOAuthInputs.set(providerName, resolve)
803
+ return new Promise<string>((resolve, reject) => {
804
+ myEntry = { resolve, reject }
805
+ pendingOAuthInputs.set(providerName, myEntry)
786
806
  })
787
807
  },
788
808
  onSelect: async () => {
@@ -814,7 +834,9 @@ async function main(): Promise<void> {
814
834
  message: msg,
815
835
  })
816
836
  } finally {
817
- pendingOAuthInputs.delete(providerName)
837
+ if (myEntry && pendingOAuthInputs.get(providerName) === myEntry) {
838
+ pendingOAuthInputs.delete(providerName)
839
+ }
818
840
  }
819
841
  })
820
842
 
@@ -836,7 +858,21 @@ async function main(): Promise<void> {
836
858
  return
837
859
  }
838
860
  pendingOAuthInputs.delete(name)
839
- resolveInput(value)
861
+ resolveInput.resolve(value)
862
+ res.json({ ok: true })
863
+ })
864
+
865
+ // Cancel an in-flight OAuth login: rejecting the pending input makes pi's
866
+ // flow abort and release its localhost callback port.
867
+ app.delete("/api/providers/:name/login", (req, res) => {
868
+ const name = req.params.name
869
+ const pending = pendingOAuthInputs.get(name)
870
+ if (!pending) {
871
+ res.status(404).json({ error: `no OAuth flow in flight for "${name}"` })
872
+ return
873
+ }
874
+ pendingOAuthInputs.delete(name)
875
+ pending.reject(new Error("Login cancelled"))
840
876
  res.json({ ok: true })
841
877
  })
842
878