@omnicross/daemon 0.1.3 → 0.1.4

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/cli.cjs CHANGED
@@ -966,6 +966,7 @@ var CodexOAuthSessionStore = class {
966
966
  ttlMs;
967
967
  sessions = /* @__PURE__ */ new Map();
968
968
  activeSessionId = null;
969
+ aborters = /* @__PURE__ */ new Map();
969
970
  /** Whether a codex sign-in is currently in flight (port 1455 held). */
970
971
  isBusy() {
971
972
  this.sweep();
@@ -977,13 +978,22 @@ var CodexOAuthSessionStore = class {
977
978
  const sessionId = import_node_crypto3.default.randomBytes(24).toString("base64url");
978
979
  this.sessions.set(sessionId, { status: "pending", createdAt: Date.now() });
979
980
  this.activeSessionId = sessionId;
980
- return sessionId;
981
+ const controller = new AbortController();
982
+ this.aborters.set(sessionId, controller);
983
+ return { sessionId, signal: controller.signal };
981
984
  }
982
985
  /** Settle a flow (done/error) + free the active slot. */
983
986
  settle(sessionId, status, error) {
984
987
  const prior = this.sessions.get(sessionId);
985
988
  this.sessions.set(sessionId, { status, error, createdAt: prior?.createdAt ?? Date.now() });
986
989
  if (this.activeSessionId === sessionId) this.activeSessionId = null;
990
+ this.aborters.delete(sessionId);
991
+ }
992
+ cancel(sessionId) {
993
+ if (!this.sessions.has(sessionId)) return false;
994
+ this.aborters.get(sessionId)?.abort();
995
+ this.settle(sessionId, "error", "login: cancelled");
996
+ return true;
987
997
  }
988
998
  /** Read a flow's status (token-free), or null when unknown/expired. */
989
999
  get(sessionId) {
@@ -1012,13 +1022,13 @@ function handleCodexOAuthStart(deps) {
1012
1022
  );
1013
1023
  }
1014
1024
  const { authUrl, codeVerifier, state } = import_subscriptions.codexOAuth.generateAuthParams();
1015
- const sessionId = deps.codexSessions.begin();
1016
- void runCodexLoopback(sessionId, codeVerifier, state, deps);
1025
+ const { sessionId, signal } = deps.codexSessions.begin();
1026
+ void runCodexLoopback(sessionId, codeVerifier, state, signal, deps);
1017
1027
  return { status: 200, body: { authUrl, sessionId } };
1018
1028
  }
1019
- async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
1029
+ async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
1020
1030
  try {
1021
- const code = await deps.codexAwaitLoopback(state);
1031
+ const code = await deps.codexAwaitLoopback(state, void 0, signal);
1022
1032
  const result = await import_subscriptions.codexOAuth.exchangeCodeForTokens(
1023
1033
  { authorizationCode: code, codeVerifier, state },
1024
1034
  deps.oauthExchangeFetch
@@ -1040,6 +1050,10 @@ async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
1040
1050
  deps.codexSessions.settle(sessionId, "error", reason);
1041
1051
  }
1042
1052
  }
1053
+ function handleCodexOAuthCancel(sessionId, deps) {
1054
+ if (!deps.codexSessions.cancel(sessionId)) return err(404, "unknown or expired codex sign-in session");
1055
+ return { status: 200, body: { ok: true } };
1056
+ }
1043
1057
  function handleCodexOAuthStatus(sessionId, deps) {
1044
1058
  const s = deps.codexSessions.get(sessionId);
1045
1059
  if (!s) return err(404, "unknown or expired codex sign-in session");
@@ -3831,6 +3845,10 @@ async function handleAccounts(req, res, method, rest, deps) {
3831
3845
  const result = handleCodexOAuthStatus(rest[2], deps);
3832
3846
  return writeJson2(res, result.status, result.body);
3833
3847
  }
3848
+ if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
3849
+ const result = handleCodexOAuthCancel(rest[2], deps);
3850
+ return writeJson2(res, result.status, result.body);
3851
+ }
3834
3852
  if (method === "PUT" || method === "POST" || method === "DELETE") {
3835
3853
  const providerId = asSubscriptionProviderId(rest[0]);
3836
3854
  if (!providerId) {
@@ -4171,7 +4189,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
4171
4189
  }
4172
4190
 
4173
4191
  // src/admin/version.ts
4174
- var DAEMON_VERSION = true ? "0.1.3" : "0.0.0-dev";
4192
+ var DAEMON_VERSION = true ? "0.1.4" : "0.0.0-dev";
4175
4193
 
4176
4194
  // src/admin/AdminServer.ts
4177
4195
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -4426,7 +4444,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
4426
4444
  function pageHtml(message) {
4427
4445
  return `<!doctype html><meta charset="utf-8"><title>omnicross login</title><body style="font-family:sans-serif;padding:2rem"><h2>${message}</h2><p>You can close this window and return to the terminal.</p></body>`;
4428
4446
  }
4429
- function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
4447
+ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal) {
4430
4448
  return new Promise((resolve, reject) => {
4431
4449
  let settled = false;
4432
4450
  const finish = (server2, fn) => {
@@ -4460,6 +4478,12 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
4460
4478
  res.end(pageHtml("Login complete."));
4461
4479
  finish(server, () => resolve(code));
4462
4480
  });
4481
+ const abort = () => finish(server, () => reject(new Error("login: cancelled")));
4482
+ if (signal?.aborted) {
4483
+ abort();
4484
+ return;
4485
+ }
4486
+ signal?.addEventListener("abort", abort, { once: true });
4463
4487
  server.on("error", (err5) => {
4464
4488
  if (settled) return;
4465
4489
  settled = true;
@@ -7477,7 +7501,7 @@ function buildDaemon(config, paths) {
7477
7501
  // the app polls the token-free status. A test seam (`paths.codexAwaitLoopback`)
7478
7502
  // can inject a mock so no real port is bound.
7479
7503
  codexSessions: new CodexOAuthSessionStore(),
7480
- codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs) => awaitLoopbackCode(state, timeoutMs)),
7504
+ codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
7481
7505
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
7482
7506
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
7483
7507
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
package/dist/cli.js CHANGED
@@ -960,6 +960,7 @@ var CodexOAuthSessionStore = class {
960
960
  ttlMs;
961
961
  sessions = /* @__PURE__ */ new Map();
962
962
  activeSessionId = null;
963
+ aborters = /* @__PURE__ */ new Map();
963
964
  /** Whether a codex sign-in is currently in flight (port 1455 held). */
964
965
  isBusy() {
965
966
  this.sweep();
@@ -971,13 +972,22 @@ var CodexOAuthSessionStore = class {
971
972
  const sessionId = crypto.randomBytes(24).toString("base64url");
972
973
  this.sessions.set(sessionId, { status: "pending", createdAt: Date.now() });
973
974
  this.activeSessionId = sessionId;
974
- return sessionId;
975
+ const controller = new AbortController();
976
+ this.aborters.set(sessionId, controller);
977
+ return { sessionId, signal: controller.signal };
975
978
  }
976
979
  /** Settle a flow (done/error) + free the active slot. */
977
980
  settle(sessionId, status, error) {
978
981
  const prior = this.sessions.get(sessionId);
979
982
  this.sessions.set(sessionId, { status, error, createdAt: prior?.createdAt ?? Date.now() });
980
983
  if (this.activeSessionId === sessionId) this.activeSessionId = null;
984
+ this.aborters.delete(sessionId);
985
+ }
986
+ cancel(sessionId) {
987
+ if (!this.sessions.has(sessionId)) return false;
988
+ this.aborters.get(sessionId)?.abort();
989
+ this.settle(sessionId, "error", "login: cancelled");
990
+ return true;
981
991
  }
982
992
  /** Read a flow's status (token-free), or null when unknown/expired. */
983
993
  get(sessionId) {
@@ -1006,13 +1016,13 @@ function handleCodexOAuthStart(deps) {
1006
1016
  );
1007
1017
  }
1008
1018
  const { authUrl, codeVerifier, state } = codexOAuth.generateAuthParams();
1009
- const sessionId = deps.codexSessions.begin();
1010
- void runCodexLoopback(sessionId, codeVerifier, state, deps);
1019
+ const { sessionId, signal } = deps.codexSessions.begin();
1020
+ void runCodexLoopback(sessionId, codeVerifier, state, signal, deps);
1011
1021
  return { status: 200, body: { authUrl, sessionId } };
1012
1022
  }
1013
- async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
1023
+ async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
1014
1024
  try {
1015
- const code = await deps.codexAwaitLoopback(state);
1025
+ const code = await deps.codexAwaitLoopback(state, void 0, signal);
1016
1026
  const result = await codexOAuth.exchangeCodeForTokens(
1017
1027
  { authorizationCode: code, codeVerifier, state },
1018
1028
  deps.oauthExchangeFetch
@@ -1034,6 +1044,10 @@ async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
1034
1044
  deps.codexSessions.settle(sessionId, "error", reason);
1035
1045
  }
1036
1046
  }
1047
+ function handleCodexOAuthCancel(sessionId, deps) {
1048
+ if (!deps.codexSessions.cancel(sessionId)) return err(404, "unknown or expired codex sign-in session");
1049
+ return { status: 200, body: { ok: true } };
1050
+ }
1037
1051
  function handleCodexOAuthStatus(sessionId, deps) {
1038
1052
  const s = deps.codexSessions.get(sessionId);
1039
1053
  if (!s) return err(404, "unknown or expired codex sign-in session");
@@ -3852,6 +3866,10 @@ async function handleAccounts(req, res, method, rest, deps) {
3852
3866
  const result = handleCodexOAuthStatus(rest[2], deps);
3853
3867
  return writeJson2(res, result.status, result.body);
3854
3868
  }
3869
+ if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
3870
+ const result = handleCodexOAuthCancel(rest[2], deps);
3871
+ return writeJson2(res, result.status, result.body);
3872
+ }
3855
3873
  if (method === "PUT" || method === "POST" || method === "DELETE") {
3856
3874
  const providerId = asSubscriptionProviderId(rest[0]);
3857
3875
  if (!providerId) {
@@ -4191,7 +4209,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
4191
4209
  }
4192
4210
 
4193
4211
  // src/admin/version.ts
4194
- var DAEMON_VERSION = true ? "0.1.3" : "0.0.0-dev";
4212
+ var DAEMON_VERSION = true ? "0.1.4" : "0.0.0-dev";
4195
4213
 
4196
4214
  // src/admin/AdminServer.ts
4197
4215
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -4446,7 +4464,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
4446
4464
  function pageHtml(message) {
4447
4465
  return `<!doctype html><meta charset="utf-8"><title>omnicross login</title><body style="font-family:sans-serif;padding:2rem"><h2>${message}</h2><p>You can close this window and return to the terminal.</p></body>`;
4448
4466
  }
4449
- function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
4467
+ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal) {
4450
4468
  return new Promise((resolve, reject) => {
4451
4469
  let settled = false;
4452
4470
  const finish = (server2, fn) => {
@@ -4480,6 +4498,12 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
4480
4498
  res.end(pageHtml("Login complete."));
4481
4499
  finish(server, () => resolve(code));
4482
4500
  });
4501
+ const abort = () => finish(server, () => reject(new Error("login: cancelled")));
4502
+ if (signal?.aborted) {
4503
+ abort();
4504
+ return;
4505
+ }
4506
+ signal?.addEventListener("abort", abort, { once: true });
4483
4507
  server.on("error", (err5) => {
4484
4508
  if (settled) return;
4485
4509
  settled = true;
@@ -7504,7 +7528,7 @@ function buildDaemon(config, paths) {
7504
7528
  // the app polls the token-free status. A test seam (`paths.codexAwaitLoopback`)
7505
7529
  // can inject a mock so no real port is bound.
7506
7530
  codexSessions: new CodexOAuthSessionStore(),
7507
- codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs) => awaitLoopbackCode(state, timeoutMs)),
7531
+ codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
7508
7532
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
7509
7533
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
7510
7534
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
package/dist/index.cjs CHANGED
@@ -80,6 +80,7 @@ var CodexOAuthSessionStore = class {
80
80
  ttlMs;
81
81
  sessions = /* @__PURE__ */ new Map();
82
82
  activeSessionId = null;
83
+ aborters = /* @__PURE__ */ new Map();
83
84
  /** Whether a codex sign-in is currently in flight (port 1455 held). */
84
85
  isBusy() {
85
86
  this.sweep();
@@ -91,13 +92,22 @@ var CodexOAuthSessionStore = class {
91
92
  const sessionId = import_node_crypto.default.randomBytes(24).toString("base64url");
92
93
  this.sessions.set(sessionId, { status: "pending", createdAt: Date.now() });
93
94
  this.activeSessionId = sessionId;
94
- return sessionId;
95
+ const controller = new AbortController();
96
+ this.aborters.set(sessionId, controller);
97
+ return { sessionId, signal: controller.signal };
95
98
  }
96
99
  /** Settle a flow (done/error) + free the active slot. */
97
100
  settle(sessionId, status, error) {
98
101
  const prior = this.sessions.get(sessionId);
99
102
  this.sessions.set(sessionId, { status, error, createdAt: prior?.createdAt ?? Date.now() });
100
103
  if (this.activeSessionId === sessionId) this.activeSessionId = null;
104
+ this.aborters.delete(sessionId);
105
+ }
106
+ cancel(sessionId) {
107
+ if (!this.sessions.has(sessionId)) return false;
108
+ this.aborters.get(sessionId)?.abort();
109
+ this.settle(sessionId, "error", "login: cancelled");
110
+ return true;
101
111
  }
102
112
  /** Read a flow's status (token-free), or null when unknown/expired. */
103
113
  get(sessionId) {
@@ -126,13 +136,13 @@ function handleCodexOAuthStart(deps) {
126
136
  );
127
137
  }
128
138
  const { authUrl, codeVerifier, state } = import_subscriptions.codexOAuth.generateAuthParams();
129
- const sessionId = deps.codexSessions.begin();
130
- void runCodexLoopback(sessionId, codeVerifier, state, deps);
139
+ const { sessionId, signal } = deps.codexSessions.begin();
140
+ void runCodexLoopback(sessionId, codeVerifier, state, signal, deps);
131
141
  return { status: 200, body: { authUrl, sessionId } };
132
142
  }
133
- async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
143
+ async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
134
144
  try {
135
- const code = await deps.codexAwaitLoopback(state);
145
+ const code = await deps.codexAwaitLoopback(state, void 0, signal);
136
146
  const result = await import_subscriptions.codexOAuth.exchangeCodeForTokens(
137
147
  { authorizationCode: code, codeVerifier, state },
138
148
  deps.oauthExchangeFetch
@@ -154,6 +164,10 @@ async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
154
164
  deps.codexSessions.settle(sessionId, "error", reason);
155
165
  }
156
166
  }
167
+ function handleCodexOAuthCancel(sessionId, deps) {
168
+ if (!deps.codexSessions.cancel(sessionId)) return err(404, "unknown or expired codex sign-in session");
169
+ return { status: 200, body: { ok: true } };
170
+ }
157
171
  function handleCodexOAuthStatus(sessionId, deps) {
158
172
  const s = deps.codexSessions.get(sessionId);
159
173
  if (!s) return err(404, "unknown or expired codex sign-in session");
@@ -3535,6 +3549,10 @@ async function handleAccounts(req, res, method, rest, deps) {
3535
3549
  const result = handleCodexOAuthStatus(rest[2], deps);
3536
3550
  return writeJson2(res, result.status, result.body);
3537
3551
  }
3552
+ if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
3553
+ const result = handleCodexOAuthCancel(rest[2], deps);
3554
+ return writeJson2(res, result.status, result.body);
3555
+ }
3538
3556
  if (method === "PUT" || method === "POST" || method === "DELETE") {
3539
3557
  const providerId = asSubscriptionProviderId(rest[0]);
3540
3558
  if (!providerId) {
@@ -3875,7 +3893,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
3875
3893
  }
3876
3894
 
3877
3895
  // src/admin/version.ts
3878
- var DAEMON_VERSION = true ? "0.1.3" : "0.0.0-dev";
3896
+ var DAEMON_VERSION = true ? "0.1.4" : "0.0.0-dev";
3879
3897
 
3880
3898
  // src/admin/AdminServer.ts
3881
3899
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -4130,7 +4148,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
4130
4148
  function pageHtml(message) {
4131
4149
  return `<!doctype html><meta charset="utf-8"><title>omnicross login</title><body style="font-family:sans-serif;padding:2rem"><h2>${message}</h2><p>You can close this window and return to the terminal.</p></body>`;
4132
4150
  }
4133
- function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
4151
+ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal) {
4134
4152
  return new Promise((resolve, reject) => {
4135
4153
  let settled = false;
4136
4154
  const finish = (server2, fn) => {
@@ -4164,6 +4182,12 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
4164
4182
  res.end(pageHtml("Login complete."));
4165
4183
  finish(server, () => resolve(code));
4166
4184
  });
4185
+ const abort = () => finish(server, () => reject(new Error("login: cancelled")));
4186
+ if (signal?.aborted) {
4187
+ abort();
4188
+ return;
4189
+ }
4190
+ signal?.addEventListener("abort", abort, { once: true });
4167
4191
  server.on("error", (err5) => {
4168
4192
  if (settled) return;
4169
4193
  settled = true;
@@ -7321,7 +7345,7 @@ function buildDaemon(config, paths) {
7321
7345
  // the app polls the token-free status. A test seam (`paths.codexAwaitLoopback`)
7322
7346
  // can inject a mock so no real port is bound.
7323
7347
  codexSessions: new CodexOAuthSessionStore(),
7324
- codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs) => awaitLoopbackCode(state, timeoutMs)),
7348
+ codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
7325
7349
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
7326
7350
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
7327
7351
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
package/dist/index.d.cts CHANGED
@@ -1048,7 +1048,7 @@ interface SubscriptionAccountAppender {
1048
1048
  */
1049
1049
 
1050
1050
  /** The loopback-listener fn (injected so tests need not bind a real port). */
1051
- type CodexLoopbackFn = (state: string, timeoutMs?: number) => Promise<string>;
1051
+ type CodexLoopbackFn = (state: string, timeoutMs?: number, signal?: AbortSignal) => Promise<string>;
1052
1052
  /** One codex sign-in flow's polled status (NEVER carries a token). */
1053
1053
  interface CodexFlowState {
1054
1054
  status: 'pending' | 'done' | 'error';
@@ -1066,13 +1066,18 @@ declare class CodexOAuthSessionStore {
1066
1066
  private readonly ttlMs;
1067
1067
  private readonly sessions;
1068
1068
  private activeSessionId;
1069
+ private readonly aborters;
1069
1070
  constructor(ttlMs?: number);
1070
1071
  /** Whether a codex sign-in is currently in flight (port 1455 held). */
1071
1072
  isBusy(): boolean;
1072
1073
  /** Mint a fresh sessionId, mark it pending + active, return the id. */
1073
- begin(): string;
1074
+ begin(): {
1075
+ sessionId: string;
1076
+ signal: AbortSignal;
1077
+ };
1074
1078
  /** Settle a flow (done/error) + free the active slot. */
1075
1079
  settle(sessionId: string, status: 'done' | 'error', error?: string): void;
1080
+ cancel(sessionId: string): boolean;
1076
1081
  /** Read a flow's status (token-free), or null when unknown/expired. */
1077
1082
  get(sessionId: string): CodexFlowState | null;
1078
1083
  /** Drop expired flows; free the active slot if the active flow expired. */
package/dist/index.d.ts CHANGED
@@ -1048,7 +1048,7 @@ interface SubscriptionAccountAppender {
1048
1048
  */
1049
1049
 
1050
1050
  /** The loopback-listener fn (injected so tests need not bind a real port). */
1051
- type CodexLoopbackFn = (state: string, timeoutMs?: number) => Promise<string>;
1051
+ type CodexLoopbackFn = (state: string, timeoutMs?: number, signal?: AbortSignal) => Promise<string>;
1052
1052
  /** One codex sign-in flow's polled status (NEVER carries a token). */
1053
1053
  interface CodexFlowState {
1054
1054
  status: 'pending' | 'done' | 'error';
@@ -1066,13 +1066,18 @@ declare class CodexOAuthSessionStore {
1066
1066
  private readonly ttlMs;
1067
1067
  private readonly sessions;
1068
1068
  private activeSessionId;
1069
+ private readonly aborters;
1069
1070
  constructor(ttlMs?: number);
1070
1071
  /** Whether a codex sign-in is currently in flight (port 1455 held). */
1071
1072
  isBusy(): boolean;
1072
1073
  /** Mint a fresh sessionId, mark it pending + active, return the id. */
1073
- begin(): string;
1074
+ begin(): {
1075
+ sessionId: string;
1076
+ signal: AbortSignal;
1077
+ };
1074
1078
  /** Settle a flow (done/error) + free the active slot. */
1075
1079
  settle(sessionId: string, status: 'done' | 'error', error?: string): void;
1080
+ cancel(sessionId: string): boolean;
1076
1081
  /** Read a flow's status (token-free), or null when unknown/expired. */
1077
1082
  get(sessionId: string): CodexFlowState | null;
1078
1083
  /** Drop expired flows; free the active slot if the active flow expired. */
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ var CodexOAuthSessionStore = class {
38
38
  ttlMs;
39
39
  sessions = /* @__PURE__ */ new Map();
40
40
  activeSessionId = null;
41
+ aborters = /* @__PURE__ */ new Map();
41
42
  /** Whether a codex sign-in is currently in flight (port 1455 held). */
42
43
  isBusy() {
43
44
  this.sweep();
@@ -49,13 +50,22 @@ var CodexOAuthSessionStore = class {
49
50
  const sessionId = crypto.randomBytes(24).toString("base64url");
50
51
  this.sessions.set(sessionId, { status: "pending", createdAt: Date.now() });
51
52
  this.activeSessionId = sessionId;
52
- return sessionId;
53
+ const controller = new AbortController();
54
+ this.aborters.set(sessionId, controller);
55
+ return { sessionId, signal: controller.signal };
53
56
  }
54
57
  /** Settle a flow (done/error) + free the active slot. */
55
58
  settle(sessionId, status, error) {
56
59
  const prior = this.sessions.get(sessionId);
57
60
  this.sessions.set(sessionId, { status, error, createdAt: prior?.createdAt ?? Date.now() });
58
61
  if (this.activeSessionId === sessionId) this.activeSessionId = null;
62
+ this.aborters.delete(sessionId);
63
+ }
64
+ cancel(sessionId) {
65
+ if (!this.sessions.has(sessionId)) return false;
66
+ this.aborters.get(sessionId)?.abort();
67
+ this.settle(sessionId, "error", "login: cancelled");
68
+ return true;
59
69
  }
60
70
  /** Read a flow's status (token-free), or null when unknown/expired. */
61
71
  get(sessionId) {
@@ -84,13 +94,13 @@ function handleCodexOAuthStart(deps) {
84
94
  );
85
95
  }
86
96
  const { authUrl, codeVerifier, state } = codexOAuth.generateAuthParams();
87
- const sessionId = deps.codexSessions.begin();
88
- void runCodexLoopback(sessionId, codeVerifier, state, deps);
97
+ const { sessionId, signal } = deps.codexSessions.begin();
98
+ void runCodexLoopback(sessionId, codeVerifier, state, signal, deps);
89
99
  return { status: 200, body: { authUrl, sessionId } };
90
100
  }
91
- async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
101
+ async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
92
102
  try {
93
- const code = await deps.codexAwaitLoopback(state);
103
+ const code = await deps.codexAwaitLoopback(state, void 0, signal);
94
104
  const result = await codexOAuth.exchangeCodeForTokens(
95
105
  { authorizationCode: code, codeVerifier, state },
96
106
  deps.oauthExchangeFetch
@@ -112,6 +122,10 @@ async function runCodexLoopback(sessionId, codeVerifier, state, deps) {
112
122
  deps.codexSessions.settle(sessionId, "error", reason);
113
123
  }
114
124
  }
125
+ function handleCodexOAuthCancel(sessionId, deps) {
126
+ if (!deps.codexSessions.cancel(sessionId)) return err(404, "unknown or expired codex sign-in session");
127
+ return { status: 200, body: { ok: true } };
128
+ }
115
129
  function handleCodexOAuthStatus(sessionId, deps) {
116
130
  const s = deps.codexSessions.get(sessionId);
117
131
  if (!s) return err(404, "unknown or expired codex sign-in session");
@@ -3520,6 +3534,10 @@ async function handleAccounts(req, res, method, rest, deps) {
3520
3534
  const result = handleCodexOAuthStatus(rest[2], deps);
3521
3535
  return writeJson2(res, result.status, result.body);
3522
3536
  }
3537
+ if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
3538
+ const result = handleCodexOAuthCancel(rest[2], deps);
3539
+ return writeJson2(res, result.status, result.body);
3540
+ }
3523
3541
  if (method === "PUT" || method === "POST" || method === "DELETE") {
3524
3542
  const providerId = asSubscriptionProviderId(rest[0]);
3525
3543
  if (!providerId) {
@@ -3859,7 +3877,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
3859
3877
  }
3860
3878
 
3861
3879
  // src/admin/version.ts
3862
- var DAEMON_VERSION = true ? "0.1.3" : "0.0.0-dev";
3880
+ var DAEMON_VERSION = true ? "0.1.4" : "0.0.0-dev";
3863
3881
 
3864
3882
  // src/admin/AdminServer.ts
3865
3883
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -4114,7 +4132,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
4114
4132
  function pageHtml(message) {
4115
4133
  return `<!doctype html><meta charset="utf-8"><title>omnicross login</title><body style="font-family:sans-serif;padding:2rem"><h2>${message}</h2><p>You can close this window and return to the terminal.</p></body>`;
4116
4134
  }
4117
- function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
4135
+ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal) {
4118
4136
  return new Promise((resolve, reject) => {
4119
4137
  let settled = false;
4120
4138
  const finish = (server2, fn) => {
@@ -4148,6 +4166,12 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS) {
4148
4166
  res.end(pageHtml("Login complete."));
4149
4167
  finish(server, () => resolve(code));
4150
4168
  });
4169
+ const abort = () => finish(server, () => reject(new Error("login: cancelled")));
4170
+ if (signal?.aborted) {
4171
+ abort();
4172
+ return;
4173
+ }
4174
+ signal?.addEventListener("abort", abort, { once: true });
4151
4175
  server.on("error", (err5) => {
4152
4176
  if (settled) return;
4153
4177
  settled = true;
@@ -7312,7 +7336,7 @@ function buildDaemon(config, paths) {
7312
7336
  // the app polls the token-free status. A test seam (`paths.codexAwaitLoopback`)
7313
7337
  // can inject a mock so no real port is bound.
7314
7338
  codexSessions: new CodexOAuthSessionStore(),
7315
- codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs) => awaitLoopbackCode(state, timeoutMs)),
7339
+ codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
7316
7340
  // Migration pack (app-parity child 6, design D2/D3) — the concrete credential
7317
7341
  // store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
7318
7342
  // the multi-account append (`appendProviderAccount`, import re-encrypts at-
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@omnicross/daemon",
3
- "version": "0.1.3",
4
- "description": "Omnicross standalone daemon the bare-Node embedder of @omnicross/core with an admin HTTP API + dashboard.",
3
+ "version": "0.1.4",
4
+ "description": "Omnicross standalone daemon 鈥?the bare-Node embedder of @omnicross/core with an admin HTTP API + dashboard.",
5
5
  "license": "MIT",
6
6
  "author": "Sayo (https://github.com/Dumoedss)",
7
7
  "homepage": "https://github.com/Dumoedss/omnicross#readme",