@agentconnect/cli 0.1.3 → 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/dist/index.js CHANGED
@@ -1,28 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import path12 from "path";
4
+ import path13 from "path";
5
5
  import { promises as fs7 } from "fs";
6
6
  import { createPrivateKey, createPublicKey as createPublicKey2, sign as signData } from "crypto";
7
7
 
8
8
  // src/host.ts
9
9
  import http from "http";
10
10
  import { WebSocketServer } from "ws";
11
- import { spawn as spawn4 } from "child_process";
11
+ import { spawn as spawn5 } from "child_process";
12
12
  import fs2 from "fs";
13
13
  import { promises as fsp } from "fs";
14
14
  import net from "net";
15
- import path5 from "path";
15
+ import path6 from "path";
16
16
 
17
17
  // src/providers/claude.ts
18
18
  import { spawn as spawn2 } from "child_process";
19
19
  import { access, mkdir, readFile, rm, writeFile } from "fs/promises";
20
+ import https from "https";
20
21
  import os2 from "os";
21
22
  import path2 from "path";
22
23
 
23
24
  // src/providers/utils.ts
24
25
  import { spawn } from "child_process";
25
- import { existsSync } from "fs";
26
+ import { existsSync, realpathSync } from "fs";
26
27
  import os from "os";
27
28
  import path from "path";
28
29
  var DEBUG_ENABLED = Boolean(process.env.AGENTCONNECT_DEBUG?.trim());
@@ -128,6 +129,15 @@ function resolveCommandPath(command2) {
128
129
  }
129
130
  return null;
130
131
  }
132
+ function resolveCommandRealPath(command2) {
133
+ const resolved = resolveCommandPath(command2);
134
+ if (!resolved) return null;
135
+ try {
136
+ return realpathSync(resolved);
137
+ } catch {
138
+ return resolved;
139
+ }
140
+ }
131
141
  function commandExists(command2) {
132
142
  return Boolean(resolveCommandPath(command2));
133
143
  }
@@ -143,6 +153,12 @@ function runCommand(command2, args2, options = {}) {
143
153
  resolve({ code: 127, stdout: "", stderr: `Executable not found in PATH: "${command2}"` });
144
154
  return;
145
155
  }
156
+ const startedAt = Date.now();
157
+ debugLog("Command", "run", {
158
+ command: resolved,
159
+ args: args2,
160
+ startedAt: new Date(startedAt).toISOString()
161
+ });
146
162
  const child = spawn(resolved, args2, {
147
163
  ...spawnOptions,
148
164
  stdio: ["pipe", "pipe", "pipe"]
@@ -168,10 +184,21 @@ function runCommand(command2, args2, options = {}) {
168
184
  });
169
185
  child.on("error", (err) => {
170
186
  if (timeout) clearTimeout(timeout);
187
+ debugLog("Command", "result", {
188
+ command: resolved,
189
+ code: -1,
190
+ durationMs: Date.now() - startedAt,
191
+ error: err.message
192
+ });
171
193
  resolve({ code: -1, stdout, stderr: `${stderr}${err.message}` });
172
194
  });
173
195
  child.on("close", (code) => {
174
196
  if (timeout) clearTimeout(timeout);
197
+ debugLog("Command", "result", {
198
+ command: resolved,
199
+ code: code ?? 0,
200
+ durationMs: Date.now() - startedAt
201
+ });
175
202
  resolve({ code: code ?? 0, stdout, stderr });
176
203
  });
177
204
  });
@@ -268,10 +295,110 @@ var DEFAULT_LOGIN = "";
268
295
  var DEFAULT_STATUS = "";
269
296
  var CLAUDE_MODELS_CACHE_TTL_MS = 6e4;
270
297
  var CLAUDE_RECENT_MODELS_CACHE_TTL_MS = 6e4;
298
+ var CLAUDE_UPDATE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
271
299
  var claudeModelsCache = null;
272
300
  var claudeModelsCacheAt = 0;
273
301
  var claudeRecentModelsCache = null;
274
302
  var claudeRecentModelsCacheAt = 0;
303
+ var claudeUpdateCache = null;
304
+ var claudeUpdatePromise = null;
305
+ var CLAUDE_LOGIN_CACHE_TTL_MS = 3e4;
306
+ var claudeLoginCache = null;
307
+ var claudeLoginPromise = null;
308
+ function trimOutput(value, limit = 400) {
309
+ const cleaned = value.trim();
310
+ if (!cleaned) return "";
311
+ if (cleaned.length <= limit) return cleaned;
312
+ return `${cleaned.slice(0, limit)}...`;
313
+ }
314
+ function normalizePath(value) {
315
+ const normalized = value.replace(/\\/g, "/");
316
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
317
+ }
318
+ function parseSemver(value) {
319
+ if (!value) return null;
320
+ const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
321
+ if (!match) return null;
322
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
323
+ }
324
+ function compareSemver(a, b) {
325
+ if (a[0] !== b[0]) return a[0] - b[0];
326
+ if (a[1] !== b[1]) return a[1] - b[1];
327
+ return a[2] - b[2];
328
+ }
329
+ async function fetchLatestNpmVersion(pkg) {
330
+ const encoded = encodeURIComponent(pkg);
331
+ const data = await fetchJson(`https://registry.npmjs.org/${encoded}`);
332
+ if (!data || typeof data !== "object") return null;
333
+ const latest = data["dist-tags"]?.latest;
334
+ return typeof latest === "string" ? latest : null;
335
+ }
336
+ async function fetchBrewCaskVersion(cask) {
337
+ if (!commandExists("brew")) return null;
338
+ const result = await runCommand("brew", ["info", "--json=v2", "--cask", cask]);
339
+ if (result.code !== 0) return null;
340
+ try {
341
+ const parsed = JSON.parse(result.stdout);
342
+ const version = parsed?.casks?.[0]?.version;
343
+ return typeof version === "string" ? version : null;
344
+ } catch {
345
+ return null;
346
+ }
347
+ }
348
+ function getClaudeUpdateAction(commandPath) {
349
+ if (!commandPath) return null;
350
+ const normalized = normalizePath(commandPath);
351
+ const home = normalizePath(os2.homedir());
352
+ if (normalized.startsWith(`${home}/.bun/bin/`)) {
353
+ return {
354
+ command: "bun",
355
+ args: ["install", "-g", CLAUDE_PACKAGE],
356
+ source: "bun",
357
+ commandLabel: "bun install -g @anthropic-ai/claude-code"
358
+ };
359
+ }
360
+ if (normalized.includes("/node_modules/.bin/")) {
361
+ return {
362
+ command: "npm",
363
+ args: ["install", "-g", CLAUDE_PACKAGE],
364
+ source: "npm",
365
+ commandLabel: "npm install -g @anthropic-ai/claude-code"
366
+ };
367
+ }
368
+ if (normalized.includes("/cellar/") || normalized.includes("/caskroom/") || normalized.includes("/homebrew/")) {
369
+ return {
370
+ command: "brew",
371
+ args: ["upgrade", "--cask", "claude-code"],
372
+ source: "brew",
373
+ commandLabel: "brew upgrade --cask claude-code"
374
+ };
375
+ }
376
+ if (process.platform === "win32" && (normalized.includes("/program files/claudecode") || normalized.includes("/programdata/claudecode"))) {
377
+ return {
378
+ command: "winget",
379
+ args: ["upgrade", "Anthropic.ClaudeCode"],
380
+ source: "winget",
381
+ commandLabel: "winget upgrade Anthropic.ClaudeCode"
382
+ };
383
+ }
384
+ if (normalized.includes("/.local/bin/") || normalized.includes("/.local/share/claude/versions/") || normalized.includes("/.local/share/claude-code/versions/")) {
385
+ if (process.platform === "win32") {
386
+ return {
387
+ command: "powershell",
388
+ args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", INSTALL_WINDOWS_PS],
389
+ source: "script",
390
+ commandLabel: INSTALL_WINDOWS_PS
391
+ };
392
+ }
393
+ return {
394
+ command: "bash",
395
+ args: ["-lc", INSTALL_UNIX],
396
+ source: "script",
397
+ commandLabel: INSTALL_UNIX
398
+ };
399
+ }
400
+ return null;
401
+ }
275
402
  var DEFAULT_CLAUDE_MODELS = [
276
403
  {
277
404
  id: "default",
@@ -299,6 +426,23 @@ function getClaudeCommand() {
299
426
  function getClaudeConfigDir() {
300
427
  return process.env.CLAUDE_CONFIG_DIR || path2.join(os2.homedir(), ".claude");
301
428
  }
429
+ function fetchJson(url) {
430
+ return new Promise((resolve) => {
431
+ https.get(url, (res) => {
432
+ let data = "";
433
+ res.on("data", (chunk) => {
434
+ data += chunk;
435
+ });
436
+ res.on("end", () => {
437
+ try {
438
+ resolve(JSON.parse(data));
439
+ } catch {
440
+ resolve(null);
441
+ }
442
+ });
443
+ }).on("error", () => resolve(null));
444
+ });
445
+ }
302
446
  function formatClaudeDisplayName(modelId) {
303
447
  const value = modelId.trim();
304
448
  if (!value.startsWith("claude-")) return value;
@@ -391,6 +535,18 @@ function resolveClaudeLoginExperience(options) {
391
535
  if (process.env.AGENTCONNECT_HOST_MODE === "dev") return "terminal";
392
536
  return "embedded";
393
537
  }
538
+ async function resolveClaudeLoginHint(options) {
539
+ const raw = process.env.AGENTCONNECT_CLAUDE_LOGIN_HINT;
540
+ if (raw) {
541
+ const normalized = String(raw).trim().toLowerCase();
542
+ if (normalized === "setup") return "setup";
543
+ if (normalized === "login") return "login";
544
+ }
545
+ if (options?.loginExperience) {
546
+ }
547
+ const status = await checkClaudeCliStatus();
548
+ return status.loginHint ?? "login";
549
+ }
394
550
  async function createClaudeLoginSettingsFile(loginMethod) {
395
551
  if (!loginMethod) return null;
396
552
  const fileName = `agentconnect-claude-login-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`;
@@ -551,25 +707,154 @@ async function hasClaudeAuth() {
551
707
  }
552
708
  return false;
553
709
  }
710
+ function isClaudeAuthErrorText(value) {
711
+ const text = value.toLowerCase();
712
+ return text.includes("authentication_error") || text.includes("authentication_failed") || text.includes("oauth token has expired") || text.includes("token has expired") || text.includes("please run /login") || text.includes("unauthorized") || text.includes("api error: 401") || text.includes("status 401") || text.includes("invalid api key");
713
+ }
714
+ function extractClaudeMessageText(content) {
715
+ if (Array.isArray(content)) {
716
+ return content.map((part) => part?.text ?? "").join(" ");
717
+ }
718
+ return typeof content === "string" ? content : "";
719
+ }
720
+ function resolveClaudeLoginHintFromSource(apiKeySource) {
721
+ if (apiKeySource && apiKeySource.toLowerCase() === "none") return "setup";
722
+ return "login";
723
+ }
554
724
  async function checkClaudeCliStatus() {
555
- const command2 = resolveWindowsCommand(getClaudeCommand());
556
- const result = await runCommand(command2, ["--print"], {
557
- env: { ...process.env, CI: "1" },
558
- input: "/status\n",
559
- timeoutMs: 4e3
560
- });
561
- const output = `${result.stdout}
562
- ${result.stderr}`.toLowerCase();
563
- if (!output.trim()) {
564
- return null;
725
+ if (claudeLoginCache && Date.now() - claudeLoginCache.checkedAt < CLAUDE_LOGIN_CACHE_TTL_MS) {
726
+ return claudeLoginCache.status;
565
727
  }
566
- if (output.includes("not logged in") || output.includes("not authenticated") || output.includes("please log in") || output.includes("please login") || output.includes("run /login") || output.includes("sign in") || output.includes("invalid api key")) {
567
- return false;
728
+ if (claudeLoginPromise) return claudeLoginPromise;
729
+ claudeLoginPromise = (async () => {
730
+ const command2 = resolveWindowsCommand(getClaudeCommand());
731
+ const args2 = [
732
+ "--print",
733
+ "--output-format",
734
+ "stream-json",
735
+ "--no-session-persistence",
736
+ "--max-budget-usd",
737
+ "0.01"
738
+ ];
739
+ const result = await runCommand(command2, args2, {
740
+ env: { ...process.env, CI: "1" },
741
+ input: "ping\n",
742
+ timeoutMs: 8e3
743
+ });
744
+ const output = `${result.stdout}
745
+ ${result.stderr}`.trim();
746
+ if (!output) return { loggedIn: null };
747
+ let apiKeySource;
748
+ let authError = null;
749
+ let sawAssistant = false;
750
+ let sawSuccess = false;
751
+ const lines = output.split("\n");
752
+ for (const line of lines) {
753
+ const parsed = safeJsonParse(line);
754
+ if (!parsed || typeof parsed !== "object") continue;
755
+ const record = parsed;
756
+ const type = typeof record.type === "string" ? record.type : "";
757
+ if (type === "system" && record.subtype === "init") {
758
+ const source = typeof record.apiKeySource === "string" ? record.apiKeySource : void 0;
759
+ if (source) apiKeySource = source;
760
+ }
761
+ if (type === "result") {
762
+ const isError = Boolean(record.is_error);
763
+ const resultText = typeof record.result === "string" ? record.result : typeof record.error === "string" ? record.error : JSON.stringify(record.error || record);
764
+ if (isError && isClaudeAuthErrorText(resultText)) {
765
+ authError = authError ?? resultText;
766
+ }
767
+ if (!isError) sawSuccess = true;
768
+ }
769
+ if (type === "message") {
770
+ const message = record.message;
771
+ const role = typeof message?.role === "string" ? message.role : "";
772
+ if (role === "assistant") {
773
+ const text = extractClaudeMessageText(message?.content);
774
+ const errorText = typeof record.error === "string" ? record.error : typeof message?.error === "string" ? message.error : "";
775
+ if (isClaudeAuthErrorText(text) || errorText && isClaudeAuthErrorText(errorText)) {
776
+ authError = authError ?? (text || errorText);
777
+ } else if (text.trim()) {
778
+ sawAssistant = true;
779
+ }
780
+ }
781
+ }
782
+ }
783
+ if (authError) {
784
+ return {
785
+ loggedIn: false,
786
+ apiKeySource,
787
+ loginHint: resolveClaudeLoginHintFromSource(apiKeySource),
788
+ authError
789
+ };
790
+ }
791
+ if (sawAssistant || sawSuccess) {
792
+ return { loggedIn: true, apiKeySource };
793
+ }
794
+ if (apiKeySource && apiKeySource.toLowerCase() === "none") {
795
+ return { loggedIn: false, apiKeySource, loginHint: "setup" };
796
+ }
797
+ return { loggedIn: null, apiKeySource };
798
+ })().then((status) => {
799
+ claudeLoginCache = { checkedAt: Date.now(), status };
800
+ return status;
801
+ }).finally(() => {
802
+ claudeLoginPromise = null;
803
+ });
804
+ return claudeLoginPromise;
805
+ }
806
+ function getClaudeUpdateSnapshot(commandPath) {
807
+ if (claudeUpdateCache && Date.now() - claudeUpdateCache.checkedAt < CLAUDE_UPDATE_CACHE_TTL_MS) {
808
+ const action = getClaudeUpdateAction(commandPath);
809
+ return {
810
+ updateAvailable: claudeUpdateCache.updateAvailable,
811
+ latestVersion: claudeUpdateCache.latestVersion,
812
+ updateCheckedAt: claudeUpdateCache.checkedAt,
813
+ updateSource: action?.source ?? "unknown",
814
+ updateCommand: action?.commandLabel,
815
+ updateMessage: claudeUpdateCache.updateMessage
816
+ };
568
817
  }
569
- if (output.includes("logged in") || output.includes("authenticated") || output.includes("signed in") || output.includes("account") || output.includes("@")) {
570
- return true;
818
+ return {};
819
+ }
820
+ function ensureClaudeUpdateCheck(currentVersion, commandPath) {
821
+ if (claudeUpdateCache && Date.now() - claudeUpdateCache.checkedAt < CLAUDE_UPDATE_CACHE_TTL_MS) {
822
+ return;
571
823
  }
572
- return null;
824
+ if (claudeUpdatePromise) return;
825
+ claudeUpdatePromise = (async () => {
826
+ const action = getClaudeUpdateAction(commandPath || null);
827
+ let latest = null;
828
+ let updateAvailable;
829
+ let updateMessage;
830
+ if (action?.source === "npm" || action?.source === "bun") {
831
+ latest = await fetchLatestNpmVersion(CLAUDE_PACKAGE);
832
+ } else if (action?.source === "brew") {
833
+ latest = await fetchBrewCaskVersion("claude-code");
834
+ }
835
+ if (latest && currentVersion) {
836
+ const a = parseSemver(currentVersion);
837
+ const b = parseSemver(latest);
838
+ if (a && b) {
839
+ updateAvailable = compareSemver(a, b) < 0;
840
+ updateMessage = updateAvailable ? `Update available: ${currentVersion} -> ${latest}` : `Up to date (${currentVersion})`;
841
+ }
842
+ } else if (!action) {
843
+ updateMessage = "Update check unavailable";
844
+ }
845
+ debugLog("Claude", "update-check", {
846
+ updateAvailable,
847
+ message: updateMessage
848
+ });
849
+ claudeUpdateCache = {
850
+ checkedAt: Date.now(),
851
+ updateAvailable,
852
+ latestVersion: latest ?? void 0,
853
+ updateMessage
854
+ };
855
+ })().finally(() => {
856
+ claudeUpdatePromise = null;
857
+ });
573
858
  }
574
859
  async function ensureClaudeInstalled() {
575
860
  const command2 = getClaudeCommand();
@@ -633,18 +918,55 @@ async function getClaudeStatus() {
633
918
  const result = await runCommand(statusCommand, status.args);
634
919
  loggedIn = result.code === 0;
635
920
  } else {
636
- const hasAuth = await hasClaudeAuth();
637
921
  const cliStatus = await checkClaudeCliStatus();
638
- if (cliStatus === false) {
922
+ if (cliStatus.loggedIn === false) {
639
923
  loggedIn = false;
640
- } else if (cliStatus === true) {
924
+ } else if (cliStatus.loggedIn === true) {
641
925
  loggedIn = true;
926
+ } else if (cliStatus.apiKeySource?.toLowerCase() === "none") {
927
+ loggedIn = false;
642
928
  } else {
643
- loggedIn = hasAuth;
929
+ loggedIn = await hasClaudeAuth();
644
930
  }
645
931
  }
646
932
  }
647
- return { installed, loggedIn, version: versionCheck.version || void 0 };
933
+ if (installed) {
934
+ const resolved2 = resolveCommandRealPath(command2);
935
+ ensureClaudeUpdateCheck(versionCheck.version, resolved2 || null);
936
+ }
937
+ const resolved = resolveCommandRealPath(command2);
938
+ const updateInfo = installed ? getClaudeUpdateSnapshot(resolved || null) : {};
939
+ return { installed, loggedIn, version: versionCheck.version || void 0, ...updateInfo };
940
+ }
941
+ async function updateClaude() {
942
+ const command2 = getClaudeCommand();
943
+ if (!commandExists(command2)) {
944
+ return { installed: false, loggedIn: false };
945
+ }
946
+ const resolved = resolveCommandRealPath(command2);
947
+ const updateOverride = buildStatusCommand("AGENTCONNECT_CLAUDE_UPDATE", "");
948
+ const action = updateOverride.command ? null : getClaudeUpdateAction(resolved || null);
949
+ const updateCommand = updateOverride.command || action?.command || "";
950
+ const updateArgs = updateOverride.command ? updateOverride.args : action?.args || [];
951
+ if (!updateCommand) {
952
+ throw new Error("No update command available. Please update Claude manually.");
953
+ }
954
+ const cmd = resolveWindowsCommand(updateCommand);
955
+ debugLog("Claude", "update-run", { command: cmd, args: updateArgs });
956
+ const result = await runCommand(cmd, updateArgs, { env: { ...process.env, CI: "1" } });
957
+ debugLog("Claude", "update-result", {
958
+ code: result.code,
959
+ stdout: trimOutput(result.stdout),
960
+ stderr: trimOutput(result.stderr)
961
+ });
962
+ if (result.code !== 0 && result.code !== null) {
963
+ const message = trimOutput(`${result.stdout}
964
+ ${result.stderr}`, 800) || "Update failed";
965
+ throw new Error(message);
966
+ }
967
+ claudeUpdateCache = null;
968
+ claudeUpdatePromise = null;
969
+ return getClaudeStatus();
648
970
  }
649
971
  async function loginClaude(options) {
650
972
  const login = buildLoginCommand("AGENTCONNECT_CLAUDE_LOGIN", DEFAULT_LOGIN);
@@ -661,10 +983,12 @@ async function runClaudeLoginFlow(options) {
661
983
  const command2 = resolveWindowsCommand(getClaudeCommand());
662
984
  const loginMethod = resolveClaudeLoginMethod(options);
663
985
  const loginExperience = resolveClaudeLoginExperience(options);
986
+ const loginHint = await resolveClaudeLoginHint(options);
664
987
  await ensureClaudeOnboardingSettings();
665
988
  const settingsPath = await createClaudeLoginSettingsFile(loginMethod);
666
989
  const loginTimeoutMs = Number(process.env.AGENTCONNECT_CLAUDE_LOGIN_TIMEOUT_MS || 18e4);
667
990
  const loginArgs = settingsPath ? ["--settings", settingsPath] : [];
991
+ const includeLogin = loginHint === "login";
668
992
  let ptyProcess = null;
669
993
  let childExited = false;
670
994
  const cleanup = async () => {
@@ -674,7 +998,7 @@ async function runClaudeLoginFlow(options) {
674
998
  } catch {
675
999
  }
676
1000
  }
677
- if (settingsPath) {
1001
+ if (settingsPath && loginExperience !== "terminal") {
678
1002
  try {
679
1003
  await rm(settingsPath, { force: true });
680
1004
  } catch {
@@ -683,7 +1007,13 @@ async function runClaudeLoginFlow(options) {
683
1007
  };
684
1008
  try {
685
1009
  if (loginExperience === "terminal") {
686
- await openClaudeLoginTerminal(command2, loginArgs, false);
1010
+ await openClaudeLoginTerminal(command2, loginArgs, includeLogin);
1011
+ if (settingsPath) {
1012
+ setTimeout(() => {
1013
+ rm(settingsPath, { force: true }).catch(() => {
1014
+ });
1015
+ }, loginTimeoutMs);
1016
+ }
687
1017
  } else {
688
1018
  const ptyModule = await loadPtyModule();
689
1019
  if (!ptyModule) {
@@ -691,7 +1021,8 @@ async function runClaudeLoginFlow(options) {
691
1021
  "Claude login requires node-pty. Reinstall AgentConnect or run `claude /login` manually."
692
1022
  );
693
1023
  }
694
- ptyProcess = ptyModule.spawn(command2, [...loginArgs, "/login"], {
1024
+ const spawnArgs = includeLogin ? [...loginArgs, "/login"] : loginArgs;
1025
+ ptyProcess = ptyModule.spawn(command2, spawnArgs, {
695
1026
  name: "xterm-256color",
696
1027
  cols: 100,
697
1028
  rows: 30,
@@ -771,6 +1102,19 @@ function extractAssistantDelta(msg) {
771
1102
  const text = msg.delta?.text;
772
1103
  return typeof text === "string" && text ? text : null;
773
1104
  }
1105
+ function extractTextFromContent(content) {
1106
+ if (!content) return "";
1107
+ if (typeof content === "string") return content;
1108
+ if (Array.isArray(content)) {
1109
+ return content.map((part) => {
1110
+ if (!part || typeof part !== "object") return "";
1111
+ const text = part.text;
1112
+ if (typeof text === "string") return text;
1113
+ return "";
1114
+ }).filter(Boolean).join("");
1115
+ }
1116
+ return "";
1117
+ }
774
1118
  function extractAssistantText(msg) {
775
1119
  if (String(msg.type ?? "") !== "assistant") return null;
776
1120
  const content = msg.message?.content;
@@ -789,6 +1133,7 @@ function runClaudePrompt({
789
1133
  resumeSessionId,
790
1134
  model,
791
1135
  cwd,
1136
+ providerDetailLevel,
792
1137
  onEvent,
793
1138
  signal
794
1139
  }) {
@@ -821,45 +1166,189 @@ function runClaudePrompt({
821
1166
  let finalSessionId = null;
822
1167
  let didFinalize = false;
823
1168
  let sawError = false;
824
- const emitError = (message) => {
825
- if (sawError) return;
826
- sawError = true;
827
- onEvent({ type: "error", message });
1169
+ const toolBlocks = /* @__PURE__ */ new Map();
1170
+ const thinkingBlocks = /* @__PURE__ */ new Set();
1171
+ const includeRaw = providerDetailLevel === "raw";
1172
+ const buildProviderDetail = (eventType, data, raw) => {
1173
+ const detail = { eventType };
1174
+ if (data && Object.keys(data).length) detail.data = data;
1175
+ if (includeRaw && raw !== void 0) detail.raw = raw;
1176
+ return detail;
828
1177
  };
829
- const emitFinal = (text) => {
1178
+ const emit = (event) => {
830
1179
  if (finalSessionId) {
831
- onEvent({ type: "final", text, providerSessionId: finalSessionId });
1180
+ onEvent({ ...event, providerSessionId: finalSessionId });
832
1181
  } else {
833
- onEvent({ type: "final", text });
1182
+ onEvent(event);
834
1183
  }
835
1184
  };
1185
+ const emitError = (message) => {
1186
+ if (sawError) return;
1187
+ sawError = true;
1188
+ emit({ type: "error", message });
1189
+ };
1190
+ const emitFinal = (text, providerDetail) => {
1191
+ emit({ type: "final", text, providerDetail });
1192
+ };
836
1193
  const handleLine = (line) => {
837
1194
  const parsed = safeJsonParse(line);
838
1195
  if (!parsed || typeof parsed !== "object") {
839
1196
  if (line.trim()) {
840
- onEvent({ type: "raw_line", line });
1197
+ emit({ type: "raw_line", line });
841
1198
  }
842
1199
  return;
843
1200
  }
844
1201
  const msg = parsed;
845
1202
  const sid = extractSessionId(msg);
846
1203
  if (sid) finalSessionId = sid;
1204
+ const msgType = String(msg.type ?? "");
1205
+ let handled = false;
1206
+ if (msgType === "assistant" || msgType === "user" || msgType === "system") {
1207
+ const role = msg.message?.role === "assistant" || msg.message?.role === "user" || msg.message?.role === "system" ? msg.message.role : msgType;
1208
+ const rawContent = msg.message?.content;
1209
+ const content = extractTextFromContent(rawContent);
1210
+ emit({
1211
+ type: "message",
1212
+ provider: "claude",
1213
+ role,
1214
+ content,
1215
+ contentParts: rawContent ?? null,
1216
+ providerDetail: buildProviderDetail(msgType, {}, msg)
1217
+ });
1218
+ handled = true;
1219
+ }
1220
+ if (msgType === "stream_event" && msg.event) {
1221
+ const evType = String(msg.event.type ?? "");
1222
+ const index = typeof msg.event.index === "number" ? msg.event.index : void 0;
1223
+ const block = msg.event.content_block;
1224
+ if (evType === "content_block_start" && block && typeof index === "number") {
1225
+ if (block.type === "tool_use" || block.type === "server_tool_use" || block.type === "mcp_tool_use") {
1226
+ toolBlocks.set(index, { id: block.id, name: block.name });
1227
+ emit({
1228
+ type: "tool_call",
1229
+ provider: "claude",
1230
+ name: block.name,
1231
+ callId: block.id,
1232
+ input: block.input,
1233
+ phase: "start",
1234
+ providerDetail: buildProviderDetail(
1235
+ "content_block_start",
1236
+ { blockType: block.type, index, name: block.name, id: block.id },
1237
+ msg
1238
+ )
1239
+ });
1240
+ }
1241
+ if (block.type === "thinking" || block.type === "redacted_thinking") {
1242
+ thinkingBlocks.add(index);
1243
+ emit({
1244
+ type: "thinking",
1245
+ provider: "claude",
1246
+ phase: "start",
1247
+ text: typeof block.thinking === "string" ? block.thinking : void 0,
1248
+ providerDetail: buildProviderDetail(
1249
+ "content_block_start",
1250
+ { blockType: block.type, index },
1251
+ msg
1252
+ )
1253
+ });
1254
+ }
1255
+ handled = true;
1256
+ }
1257
+ if (evType === "content_block_delta") {
1258
+ const delta2 = msg.event.delta ?? {};
1259
+ if (delta2.type === "thinking_delta") {
1260
+ emit({
1261
+ type: "thinking",
1262
+ provider: "claude",
1263
+ phase: "delta",
1264
+ text: typeof delta2.thinking === "string" ? delta2.thinking : void 0,
1265
+ providerDetail: buildProviderDetail(
1266
+ "content_block_delta",
1267
+ { deltaType: delta2.type, index },
1268
+ msg
1269
+ )
1270
+ });
1271
+ handled = true;
1272
+ }
1273
+ if (delta2.type === "input_json_delta") {
1274
+ const tool = typeof index === "number" ? toolBlocks.get(index) : void 0;
1275
+ emit({
1276
+ type: "tool_call",
1277
+ provider: "claude",
1278
+ name: tool?.name,
1279
+ callId: tool?.id,
1280
+ input: delta2.partial_json,
1281
+ phase: "delta",
1282
+ providerDetail: buildProviderDetail(
1283
+ "content_block_delta",
1284
+ { deltaType: delta2.type, index, name: tool?.name, id: tool?.id },
1285
+ msg
1286
+ )
1287
+ });
1288
+ handled = true;
1289
+ }
1290
+ }
1291
+ if (evType === "content_block_stop" && typeof index === "number") {
1292
+ if (toolBlocks.has(index)) {
1293
+ const tool = toolBlocks.get(index);
1294
+ emit({
1295
+ type: "tool_call",
1296
+ provider: "claude",
1297
+ name: tool?.name,
1298
+ callId: tool?.id,
1299
+ phase: "completed",
1300
+ providerDetail: buildProviderDetail(
1301
+ "content_block_stop",
1302
+ { index, name: tool?.name, id: tool?.id },
1303
+ msg
1304
+ )
1305
+ });
1306
+ toolBlocks.delete(index);
1307
+ }
1308
+ if (thinkingBlocks.has(index)) {
1309
+ emit({
1310
+ type: "thinking",
1311
+ provider: "claude",
1312
+ phase: "completed",
1313
+ providerDetail: buildProviderDetail("content_block_stop", { index }, msg)
1314
+ });
1315
+ thinkingBlocks.delete(index);
1316
+ }
1317
+ handled = true;
1318
+ }
1319
+ }
847
1320
  const delta = extractAssistantDelta(msg);
848
1321
  if (delta) {
849
1322
  aggregated += delta;
850
- onEvent({ type: "delta", text: delta });
1323
+ emit({
1324
+ type: "delta",
1325
+ text: delta,
1326
+ providerDetail: buildProviderDetail(msgType || "delta", {}, msg)
1327
+ });
851
1328
  return;
852
1329
  }
853
1330
  const assistant = extractAssistantText(msg);
854
1331
  if (assistant && !aggregated) {
855
1332
  aggregated = assistant;
856
- onEvent({ type: "delta", text: assistant });
1333
+ emit({
1334
+ type: "delta",
1335
+ text: assistant,
1336
+ providerDetail: buildProviderDetail(msgType || "assistant", {}, msg)
1337
+ });
857
1338
  return;
858
1339
  }
859
1340
  const result = extractResultText(msg);
860
1341
  if (result && !didFinalize && !sawError) {
861
1342
  didFinalize = true;
862
- emitFinal(aggregated || result);
1343
+ emitFinal(aggregated || result, buildProviderDetail("result", {}, msg));
1344
+ handled = true;
1345
+ }
1346
+ if (!handled) {
1347
+ emit({
1348
+ type: "detail",
1349
+ provider: "claude",
1350
+ providerDetail: buildProviderDetail(msgType || "unknown", {}, msg)
1351
+ });
863
1352
  }
864
1353
  };
865
1354
  const stdoutParser = createLineParser(handleLine);
@@ -886,23 +1375,177 @@ function runClaudePrompt({
886
1375
  // src/providers/codex.ts
887
1376
  import { spawn as spawn3 } from "child_process";
888
1377
  import { readFile as readFile2 } from "fs/promises";
1378
+ import https2 from "https";
889
1379
  import os3 from "os";
890
1380
  import path3 from "path";
891
1381
  var CODEX_PACKAGE = "@openai/codex";
892
1382
  var DEFAULT_LOGIN2 = "codex login";
893
1383
  var DEFAULT_STATUS2 = "codex login status";
894
1384
  var CODEX_MODELS_CACHE_TTL_MS = 6e4;
1385
+ var CODEX_UPDATE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
895
1386
  var codexModelsCache = null;
896
1387
  var codexModelsCacheAt = 0;
897
- function trimOutput(value, limit = 400) {
1388
+ var codexUpdateCache = null;
1389
+ var codexUpdatePromise = null;
1390
+ function trimOutput2(value, limit = 400) {
898
1391
  const cleaned = value.trim();
899
1392
  if (!cleaned) return "";
900
1393
  if (cleaned.length <= limit) return cleaned;
901
1394
  return `${cleaned.slice(0, limit)}...`;
902
1395
  }
1396
+ function normalizePath2(value) {
1397
+ const normalized = value.replace(/\\/g, "/");
1398
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
1399
+ }
903
1400
  function getCodexConfigDir() {
904
1401
  return process.env.CODEX_CONFIG_DIR || path3.join(os3.homedir(), ".codex");
905
1402
  }
1403
+ function fetchJson2(url) {
1404
+ return new Promise((resolve) => {
1405
+ https2.get(url, (res) => {
1406
+ let data = "";
1407
+ res.on("data", (chunk) => {
1408
+ data += chunk;
1409
+ });
1410
+ res.on("end", () => {
1411
+ try {
1412
+ resolve(JSON.parse(data));
1413
+ } catch {
1414
+ resolve(null);
1415
+ }
1416
+ });
1417
+ }).on("error", () => resolve(null));
1418
+ });
1419
+ }
1420
+ function parseSemver2(value) {
1421
+ if (!value) return null;
1422
+ const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
1423
+ if (!match) return null;
1424
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
1425
+ }
1426
+ function compareSemver2(a, b) {
1427
+ if (a[0] !== b[0]) return a[0] - b[0];
1428
+ if (a[1] !== b[1]) return a[1] - b[1];
1429
+ return a[2] - b[2];
1430
+ }
1431
+ async function fetchLatestNpmVersion2(pkg) {
1432
+ const encoded = encodeURIComponent(pkg);
1433
+ const data = await fetchJson2(`https://registry.npmjs.org/${encoded}`);
1434
+ if (!data || typeof data !== "object") return null;
1435
+ const latest = data["dist-tags"]?.latest;
1436
+ return typeof latest === "string" ? latest : null;
1437
+ }
1438
+ async function fetchBrewFormulaVersion(formula) {
1439
+ if (!commandExists("brew")) return null;
1440
+ const result = await runCommand("brew", ["info", "--json=v2", formula]);
1441
+ if (result.code !== 0) return null;
1442
+ try {
1443
+ const parsed = JSON.parse(result.stdout);
1444
+ const version = parsed?.formulae?.[0]?.versions?.stable;
1445
+ return typeof version === "string" ? version : null;
1446
+ } catch {
1447
+ return null;
1448
+ }
1449
+ }
1450
+ function getCodexUpdateAction(commandPath) {
1451
+ if (process.env.CODEX_MANAGED_BY_NPM) {
1452
+ return {
1453
+ command: "npm",
1454
+ args: ["install", "-g", CODEX_PACKAGE],
1455
+ source: "npm",
1456
+ commandLabel: "npm install -g @openai/codex"
1457
+ };
1458
+ }
1459
+ if (process.env.CODEX_MANAGED_BY_BUN) {
1460
+ return {
1461
+ command: "bun",
1462
+ args: ["install", "-g", CODEX_PACKAGE],
1463
+ source: "bun",
1464
+ commandLabel: "bun install -g @openai/codex"
1465
+ };
1466
+ }
1467
+ if (commandPath) {
1468
+ const normalized = normalizePath2(commandPath);
1469
+ if (normalized.includes(".bun/install/global")) {
1470
+ return {
1471
+ command: "bun",
1472
+ args: ["install", "-g", CODEX_PACKAGE],
1473
+ source: "bun",
1474
+ commandLabel: "bun install -g @openai/codex"
1475
+ };
1476
+ }
1477
+ if (normalized.includes("/node_modules/.bin/") || normalized.includes("/lib/node_modules/")) {
1478
+ return {
1479
+ command: "npm",
1480
+ args: ["install", "-g", CODEX_PACKAGE],
1481
+ source: "npm",
1482
+ commandLabel: "npm install -g @openai/codex"
1483
+ };
1484
+ }
1485
+ }
1486
+ if (process.platform === "darwin" && commandPath && (commandPath.startsWith("/opt/homebrew") || commandPath.startsWith("/usr/local"))) {
1487
+ return {
1488
+ command: "brew",
1489
+ args: ["upgrade", "codex"],
1490
+ source: "brew",
1491
+ commandLabel: "brew upgrade codex"
1492
+ };
1493
+ }
1494
+ return null;
1495
+ }
1496
+ function getCodexUpdateSnapshot(commandPath) {
1497
+ if (codexUpdateCache && Date.now() - codexUpdateCache.checkedAt < CODEX_UPDATE_CACHE_TTL_MS) {
1498
+ const action = getCodexUpdateAction(commandPath);
1499
+ return {
1500
+ updateAvailable: codexUpdateCache.updateAvailable,
1501
+ latestVersion: codexUpdateCache.latestVersion,
1502
+ updateCheckedAt: codexUpdateCache.checkedAt,
1503
+ updateSource: action?.source ?? "unknown",
1504
+ updateCommand: action?.commandLabel,
1505
+ updateMessage: codexUpdateCache.updateMessage
1506
+ };
1507
+ }
1508
+ return {};
1509
+ }
1510
+ function ensureCodexUpdateCheck(currentVersion, commandPath) {
1511
+ if (codexUpdateCache && Date.now() - codexUpdateCache.checkedAt < CODEX_UPDATE_CACHE_TTL_MS) {
1512
+ return;
1513
+ }
1514
+ if (codexUpdatePromise) return;
1515
+ codexUpdatePromise = (async () => {
1516
+ const action = getCodexUpdateAction(commandPath || null);
1517
+ let latest = null;
1518
+ if (action?.source === "brew") {
1519
+ latest = await fetchBrewFormulaVersion("codex");
1520
+ } else {
1521
+ latest = await fetchLatestNpmVersion2(CODEX_PACKAGE);
1522
+ }
1523
+ let updateAvailable;
1524
+ let updateMessage;
1525
+ if (latest && currentVersion) {
1526
+ const a = parseSemver2(currentVersion);
1527
+ const b = parseSemver2(latest);
1528
+ if (a && b) {
1529
+ updateAvailable = compareSemver2(a, b) < 0;
1530
+ updateMessage = updateAvailable ? `Update available: ${currentVersion} -> ${latest}` : `Up to date (${currentVersion})`;
1531
+ }
1532
+ }
1533
+ codexUpdateCache = {
1534
+ checkedAt: Date.now(),
1535
+ updateAvailable,
1536
+ latestVersion: latest ?? void 0,
1537
+ updateMessage
1538
+ };
1539
+ debugLog("Codex", "update-check", {
1540
+ currentVersion,
1541
+ latest,
1542
+ updateAvailable,
1543
+ message: updateMessage
1544
+ });
1545
+ })().finally(() => {
1546
+ codexUpdatePromise = null;
1547
+ });
1548
+ }
906
1549
  function hasAuthValue(value) {
907
1550
  return typeof value === "string" && value.trim().length > 0;
908
1551
  }
@@ -983,7 +1626,7 @@ async function ensureCodexInstalled() {
983
1626
  });
984
1627
  debugLog("Codex", "install-result", {
985
1628
  code: installResult.code,
986
- stderr: trimOutput(installResult.stderr)
1629
+ stderr: trimOutput2(installResult.stderr)
987
1630
  });
988
1631
  const after = await checkCommandVersion(command2, [["--version"], ["-V"]]);
989
1632
  codexModelsCache = null;
@@ -1020,7 +1663,44 @@ ${result.stderr}`.toLowerCase();
1020
1663
  loggedIn = await hasCodexAuth();
1021
1664
  }
1022
1665
  }
1023
- return { installed, loggedIn, version: versionCheck.version || void 0 };
1666
+ const resolved = resolveCommandRealPath(command2);
1667
+ if (installed) {
1668
+ ensureCodexUpdateCheck(versionCheck.version, resolved || null);
1669
+ }
1670
+ const updateInfo = installed ? getCodexUpdateSnapshot(resolved || null) : {};
1671
+ return { installed, loggedIn, version: versionCheck.version || void 0, ...updateInfo };
1672
+ }
1673
+ async function updateCodex() {
1674
+ const command2 = getCodexCommand();
1675
+ if (!commandExists(command2)) {
1676
+ return { installed: false, loggedIn: false };
1677
+ }
1678
+ const resolved = resolveCommandRealPath(command2);
1679
+ const updateOverride = buildStatusCommand("AGENTCONNECT_CODEX_UPDATE", "");
1680
+ const action = updateOverride.command ? null : getCodexUpdateAction(resolved || null);
1681
+ const updateCommand = updateOverride.command || action?.command || "";
1682
+ const updateArgs = updateOverride.command ? updateOverride.args : action?.args || [];
1683
+ if (!updateCommand) {
1684
+ throw new Error("No update command available. Please update Codex manually.");
1685
+ }
1686
+ const cmd = resolveWindowsCommand(updateCommand);
1687
+ debugLog("Codex", "update-run", { command: cmd, args: updateArgs });
1688
+ const result = await runCommand(cmd, updateArgs, {
1689
+ env: { ...process.env, CI: "1" }
1690
+ });
1691
+ debugLog("Codex", "update-result", {
1692
+ code: result.code,
1693
+ stdout: trimOutput2(result.stdout),
1694
+ stderr: trimOutput2(result.stderr)
1695
+ });
1696
+ if (result.code !== 0 && result.code !== null) {
1697
+ const message = trimOutput2(`${result.stdout}
1698
+ ${result.stderr}`, 800) || "Update failed";
1699
+ throw new Error(message);
1700
+ }
1701
+ codexUpdateCache = null;
1702
+ codexUpdatePromise = null;
1703
+ return getCodexStatus();
1024
1704
  }
1025
1705
  async function loginCodex() {
1026
1706
  const login = buildLoginCommand("AGENTCONNECT_CODEX_LOGIN", DEFAULT_LOGIN2);
@@ -1030,7 +1710,7 @@ async function loginCodex() {
1030
1710
  const command2 = resolveWindowsCommand(login.command);
1031
1711
  debugLog("Codex", "login", { command: command2, args: login.args });
1032
1712
  const result = await runCommand(command2, login.args, { env: { ...process.env, CI: "1" } });
1033
- debugLog("Codex", "login-result", { code: result.code, stderr: trimOutput(result.stderr) });
1713
+ debugLog("Codex", "login-result", { code: result.code, stderr: trimOutput2(result.stderr) });
1034
1714
  const status = await getCodexStatus();
1035
1715
  codexModelsCache = null;
1036
1716
  codexModelsCacheAt = 0;
@@ -1257,6 +1937,7 @@ function runCodexPrompt({
1257
1937
  reasoningEffort,
1258
1938
  repoRoot,
1259
1939
  cwd,
1940
+ providerDetailLevel,
1260
1941
  onEvent,
1261
1942
  signal
1262
1943
  }) {
@@ -1294,10 +1975,88 @@ function runCodexPrompt({
1294
1975
  let finalSessionId = null;
1295
1976
  let didFinalize = false;
1296
1977
  let sawError = false;
1297
- const emitError = (message) => {
1978
+ const includeRaw = providerDetailLevel === "raw";
1979
+ const buildProviderDetail = (eventType, data, raw) => {
1980
+ const detail = { eventType };
1981
+ if (data && Object.keys(data).length) detail.data = data;
1982
+ if (includeRaw && raw !== void 0) detail.raw = raw;
1983
+ return detail;
1984
+ };
1985
+ const emit = (event) => {
1986
+ if (finalSessionId) {
1987
+ onEvent({ ...event, providerSessionId: finalSessionId });
1988
+ } else {
1989
+ onEvent(event);
1990
+ }
1991
+ };
1992
+ const emitError = (message, providerDetail) => {
1298
1993
  if (sawError) return;
1299
1994
  sawError = true;
1300
- onEvent({ type: "error", message });
1995
+ emit({ type: "error", message, providerDetail });
1996
+ };
1997
+ const emitItemEvent = (item, phase) => {
1998
+ const itemType = typeof item.type === "string" ? item.type : "";
1999
+ if (!itemType) return;
2000
+ const providerDetail = buildProviderDetail(
2001
+ phase === "start" ? "item.started" : "item.completed",
2002
+ {
2003
+ itemType,
2004
+ itemId: item.id,
2005
+ status: item.status
2006
+ },
2007
+ item
2008
+ );
2009
+ if (itemType === "agent_message") {
2010
+ if (phase === "completed" && typeof item.text === "string") {
2011
+ emit({
2012
+ type: "message",
2013
+ provider: "codex",
2014
+ role: "assistant",
2015
+ content: item.text,
2016
+ contentParts: item,
2017
+ providerDetail
2018
+ });
2019
+ }
2020
+ return;
2021
+ }
2022
+ if (itemType === "reasoning") {
2023
+ emit({
2024
+ type: "thinking",
2025
+ provider: "codex",
2026
+ phase,
2027
+ text: typeof item.text === "string" ? item.text : void 0,
2028
+ providerDetail
2029
+ });
2030
+ return;
2031
+ }
2032
+ if (itemType === "command_execution") {
2033
+ const output = phase === "completed" ? {
2034
+ output: item.aggregated_output,
2035
+ exitCode: item.exit_code,
2036
+ status: item.status
2037
+ } : void 0;
2038
+ emit({
2039
+ type: "tool_call",
2040
+ provider: "codex",
2041
+ name: "command_execution",
2042
+ callId: item.id,
2043
+ input: { command: item.command },
2044
+ output,
2045
+ phase,
2046
+ providerDetail
2047
+ });
2048
+ return;
2049
+ }
2050
+ emit({
2051
+ type: "tool_call",
2052
+ provider: "codex",
2053
+ name: itemType,
2054
+ callId: item.id,
2055
+ input: phase === "start" ? item : void 0,
2056
+ output: phase === "completed" ? item : void 0,
2057
+ phase,
2058
+ providerDetail
2059
+ });
1301
2060
  };
1302
2061
  let sawJson = false;
1303
2062
  const stdoutLines = [];
@@ -1307,18 +2066,14 @@ function runCodexPrompt({
1307
2066
  list.push(line);
1308
2067
  if (list.length > 12) list.shift();
1309
2068
  };
1310
- const emitFinal = (text) => {
1311
- if (finalSessionId) {
1312
- onEvent({ type: "final", text, providerSessionId: finalSessionId });
1313
- } else {
1314
- onEvent({ type: "final", text });
1315
- }
2069
+ const emitFinal = (text, providerDetail) => {
2070
+ emit({ type: "final", text, providerDetail });
1316
2071
  };
1317
2072
  const handleLine = (line, source) => {
1318
2073
  const parsed = safeJsonParse2(line);
1319
2074
  if (!parsed || typeof parsed !== "object") {
1320
2075
  if (line.trim()) {
1321
- onEvent({ type: "raw_line", line });
2076
+ emit({ type: "raw_line", line });
1322
2077
  }
1323
2078
  if (source === "stdout") {
1324
2079
  pushLine(stdoutLines, line);
@@ -1330,47 +2085,85 @@ function runCodexPrompt({
1330
2085
  sawJson = true;
1331
2086
  const ev = parsed;
1332
2087
  const normalized = normalizeEvent(ev);
1333
- onEvent({ type: "provider_event", provider: "codex", event: normalized });
1334
2088
  const sid = extractSessionId2(ev);
1335
2089
  if (sid) finalSessionId = sid;
2090
+ const eventType = typeof ev.type === "string" ? ev.type : normalized.type;
2091
+ const detailData = {};
2092
+ const threadId = ev.thread_id ?? ev.threadId;
2093
+ if (typeof threadId === "string" && threadId) detailData.threadId = threadId;
2094
+ const providerDetail = buildProviderDetail(eventType || "unknown", detailData, ev);
2095
+ let handled = false;
1336
2096
  const usage = extractUsage(ev);
1337
2097
  if (usage) {
1338
- onEvent({
2098
+ emit({
1339
2099
  type: "usage",
1340
2100
  inputTokens: usage.input_tokens,
1341
- outputTokens: usage.output_tokens
2101
+ outputTokens: usage.output_tokens,
2102
+ providerDetail
1342
2103
  });
2104
+ handled = true;
1343
2105
  }
1344
2106
  if (normalized.type === "agent_message") {
1345
2107
  const text = normalized.text;
1346
2108
  if (typeof text === "string" && text) {
1347
2109
  aggregated += text;
1348
- onEvent({ type: "delta", text });
2110
+ emit({ type: "delta", text, providerDetail });
2111
+ emit({
2112
+ type: "message",
2113
+ provider: "codex",
2114
+ role: "assistant",
2115
+ content: text,
2116
+ contentParts: ev,
2117
+ providerDetail
2118
+ });
2119
+ handled = true;
1349
2120
  }
1350
2121
  } else if (normalized.type === "item.completed") {
1351
2122
  const item = normalized.item;
1352
2123
  if (item && typeof item === "object") {
2124
+ const itemDetail = buildProviderDetail("item.completed", {
2125
+ itemType: item.type,
2126
+ itemId: item.id,
2127
+ status: item.status
2128
+ }, item);
1353
2129
  if (item.type === "command_execution" && typeof item.aggregated_output === "string") {
1354
- onEvent({ type: "delta", text: item.aggregated_output });
2130
+ emit({ type: "delta", text: item.aggregated_output, providerDetail: itemDetail });
1355
2131
  }
1356
2132
  if (item.type === "agent_message" && typeof item.text === "string") {
1357
2133
  aggregated += item.text;
1358
- onEvent({ type: "delta", text: item.text });
2134
+ emit({ type: "delta", text: item.text, providerDetail: itemDetail });
1359
2135
  }
1360
2136
  }
1361
2137
  }
2138
+ if (normalized.type === "item.started" && ev.item && typeof ev.item === "object") {
2139
+ emitItemEvent(ev.item, "start");
2140
+ handled = true;
2141
+ }
2142
+ if (normalized.type === "item.completed" && ev.item && typeof ev.item === "object") {
2143
+ emitItemEvent(ev.item, "completed");
2144
+ handled = true;
2145
+ }
2146
+ if (normalized.type === "error") {
2147
+ emitError(normalized.message || "Codex run failed", providerDetail);
2148
+ handled = true;
2149
+ }
1362
2150
  if (isTerminalEvent(ev) && !didFinalize) {
1363
2151
  if (ev.type === "turn.failed") {
1364
2152
  const message = ev.error?.message;
1365
- emitError(typeof message === "string" ? message : "Codex run failed");
2153
+ emitError(typeof message === "string" ? message : "Codex run failed", providerDetail);
1366
2154
  didFinalize = true;
2155
+ handled = true;
1367
2156
  return;
1368
2157
  }
1369
2158
  if (!sawError) {
1370
2159
  didFinalize = true;
1371
- emitFinal(aggregated);
2160
+ emitFinal(aggregated, providerDetail);
2161
+ handled = true;
1372
2162
  }
1373
2163
  }
2164
+ if (!handled) {
2165
+ emit({ type: "detail", provider: "codex", providerDetail });
2166
+ }
1374
2167
  };
1375
2168
  const stdoutParser = createLineParser((line) => handleLine(line, "stdout"));
1376
2169
  const stderrParser = createLineParser((line) => handleLine(line, "stderr"));
@@ -1421,49 +2214,841 @@ function runCodexPrompt({
1421
2214
  });
1422
2215
  }
1423
2216
 
1424
- // src/providers/local.ts
1425
- function getLocalBaseUrl() {
1426
- const base = process.env.AGENTCONNECT_LOCAL_BASE_URL || "http://localhost:11434/v1";
1427
- return base.replace(/\/+$/, "");
2217
+ // src/providers/cursor.ts
2218
+ import { spawn as spawn4 } from "child_process";
2219
+ import path4 from "path";
2220
+ var INSTALL_UNIX2 = "curl https://cursor.com/install -fsS | bash";
2221
+ var DEFAULT_LOGIN3 = "cursor-agent login";
2222
+ var DEFAULT_STATUS3 = "cursor-agent status";
2223
+ var CURSOR_MODELS_COMMAND = "cursor-agent models";
2224
+ var CURSOR_MODELS_CACHE_TTL_MS = 6e4;
2225
+ var CURSOR_UPDATE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
2226
+ var cursorModelsCache = null;
2227
+ var cursorModelsCacheAt = 0;
2228
+ var cursorUpdateCache = null;
2229
+ var cursorUpdatePromise = null;
2230
+ function trimOutput3(value, limit = 400) {
2231
+ const cleaned = value.trim();
2232
+ if (!cleaned) return "";
2233
+ if (cleaned.length <= limit) return cleaned;
2234
+ return `${cleaned.slice(0, limit)}...`;
1428
2235
  }
1429
- function getLocalApiKey() {
1430
- return process.env.AGENTCONNECT_LOCAL_API_KEY || "";
2236
+ function normalizePath3(value) {
2237
+ const normalized = value.replace(/\\/g, "/");
2238
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
1431
2239
  }
1432
- function resolveLocalModel(model, fallback) {
1433
- if (!model) return fallback;
1434
- const raw = String(model);
1435
- if (raw === "local") return fallback;
1436
- if (raw.startsWith("local:")) return raw.slice("local:".length);
1437
- if (raw.startsWith("local/")) return raw.slice("local/".length);
1438
- return raw;
2240
+ function parseSemver3(value) {
2241
+ if (!value) return null;
2242
+ const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
2243
+ if (!match) return null;
2244
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
1439
2245
  }
1440
- async function fetchJson(url, options = {}) {
1441
- const controller = new AbortController();
1442
- const timer = setTimeout(() => controller.abort(), 4e3);
2246
+ function compareSemver3(a, b) {
2247
+ if (a[0] !== b[0]) return a[0] - b[0];
2248
+ if (a[1] !== b[1]) return a[1] - b[1];
2249
+ return a[2] - b[2];
2250
+ }
2251
+ async function fetchBrewCaskVersion2(cask) {
2252
+ if (!commandExists("brew")) return null;
2253
+ const result = await runCommand("brew", ["info", "--json=v2", "--cask", cask]);
2254
+ if (result.code !== 0) return null;
1443
2255
  try {
1444
- const res = await fetch(url, { ...options, signal: controller.signal });
1445
- if (!res.ok) {
1446
- return { ok: false, status: res.status, data: null };
1447
- }
1448
- const data = await res.json();
1449
- return { ok: true, status: res.status, data };
2256
+ const parsed = JSON.parse(result.stdout);
2257
+ const version = parsed?.casks?.[0]?.version;
2258
+ return typeof version === "string" ? version : null;
1450
2259
  } catch {
1451
- return { ok: false, status: 0, data: null };
1452
- } finally {
1453
- clearTimeout(timer);
2260
+ return null;
2261
+ }
2262
+ }
2263
+ function getCursorUpdateAction(commandPath) {
2264
+ if (!commandPath) return null;
2265
+ const normalized = normalizePath3(commandPath);
2266
+ if (normalized.includes("/cellar/") || normalized.includes("/caskroom/") || normalized.includes("/homebrew/")) {
2267
+ return {
2268
+ command: "brew",
2269
+ args: ["upgrade", "--cask", "cursor"],
2270
+ source: "brew",
2271
+ commandLabel: "brew upgrade --cask cursor"
2272
+ };
2273
+ }
2274
+ if (normalized.includes("/.local/bin/") || normalized.includes("/.local/share/cursor-agent/versions/")) {
2275
+ return {
2276
+ command: "bash",
2277
+ args: ["-lc", INSTALL_UNIX2],
2278
+ source: "script",
2279
+ commandLabel: INSTALL_UNIX2
2280
+ };
2281
+ }
2282
+ return null;
2283
+ }
2284
+ function getCursorCommand() {
2285
+ const override = process.env.AGENTCONNECT_CURSOR_COMMAND;
2286
+ const base = override || "cursor-agent";
2287
+ const resolved = resolveCommandPath(base);
2288
+ return resolved || resolveWindowsCommand(base);
2289
+ }
2290
+ function getCursorApiKey() {
2291
+ return process.env.CURSOR_API_KEY || process.env.AGENTCONNECT_CURSOR_API_KEY || "";
2292
+ }
2293
+ function getCursorDefaultModel() {
2294
+ return process.env.AGENTCONNECT_CURSOR_MODEL?.trim() || "";
2295
+ }
2296
+ function resolveCursorEndpoint() {
2297
+ return process.env.AGENTCONNECT_CURSOR_ENDPOINT?.trim() || "";
2298
+ }
2299
+ function withCursorEndpoint(args2) {
2300
+ const endpoint = resolveCursorEndpoint();
2301
+ if (!endpoint) return args2;
2302
+ if (args2.includes("--endpoint")) return args2;
2303
+ return [...args2, "--endpoint", endpoint];
2304
+ }
2305
+ function resolveCursorModel(model, fallback) {
2306
+ if (!model) return fallback;
2307
+ const raw = String(model);
2308
+ if (raw === "cursor" || raw === "cursor-default") return fallback;
2309
+ if (raw.startsWith("cursor:")) return raw.slice("cursor:".length);
2310
+ if (raw.startsWith("cursor/")) return raw.slice("cursor/".length);
2311
+ return raw;
2312
+ }
2313
+ function formatCursorDefaultLabel(fallback) {
2314
+ if (!fallback) return "Default";
2315
+ return `Default \xB7 ${fallback}`;
2316
+ }
2317
+ function normalizeCursorModelId(value) {
2318
+ if (value.startsWith("cursor:") || value.startsWith("cursor/")) return value;
2319
+ return `cursor:${value}`;
2320
+ }
2321
+ function normalizeCursorModelDisplay(value) {
2322
+ if (value.startsWith("cursor:")) return value.slice("cursor:".length);
2323
+ if (value.startsWith("cursor/")) return value.slice("cursor/".length);
2324
+ return value;
2325
+ }
2326
+ async function listCursorModelsFromCli() {
2327
+ const command2 = getCursorCommand();
2328
+ if (!commandExists(command2)) return [];
2329
+ const modelsCommand = buildStatusCommand(
2330
+ "AGENTCONNECT_CURSOR_MODELS_COMMAND",
2331
+ CURSOR_MODELS_COMMAND
2332
+ );
2333
+ if (!modelsCommand.command) return [];
2334
+ const resolvedCommand = resolveWindowsCommand(modelsCommand.command);
2335
+ const result = await runCommand(resolvedCommand, withCursorEndpoint(modelsCommand.args), {
2336
+ env: buildCursorEnv()
2337
+ });
2338
+ const output = `${result.stdout}
2339
+ ${result.stderr}`.trim();
2340
+ if (!output) return [];
2341
+ const parsed = safeJsonParse3(output);
2342
+ if (Array.isArray(parsed)) {
2343
+ const models = parsed.map((entry) => {
2344
+ if (typeof entry === "string" && entry.trim()) {
2345
+ const value = entry.trim();
2346
+ return {
2347
+ id: normalizeCursorModelId(value),
2348
+ provider: "cursor",
2349
+ displayName: normalizeCursorModelDisplay(value)
2350
+ };
2351
+ }
2352
+ if (entry && typeof entry === "object") {
2353
+ const record = entry;
2354
+ const idRaw = typeof record.id === "string" ? record.id.trim() : "";
2355
+ const nameRaw = typeof record.name === "string" ? record.name.trim() : "";
2356
+ const displayRaw = typeof record.displayName === "string" ? record.displayName.trim() : "";
2357
+ const value = idRaw || nameRaw || displayRaw;
2358
+ if (!value) return null;
2359
+ return {
2360
+ id: normalizeCursorModelId(value),
2361
+ provider: "cursor",
2362
+ displayName: normalizeCursorModelDisplay(displayRaw || nameRaw || value)
2363
+ };
2364
+ }
2365
+ return null;
2366
+ }).filter(Boolean);
2367
+ return models;
2368
+ }
2369
+ const lines = output.split("\n").map((line) => line.trim()).filter(Boolean).filter((line) => !line.toLowerCase().startsWith("model")).filter((line) => !/^[-=]{2,}$/.test(line));
2370
+ return lines.map((line) => {
2371
+ const cleaned = line.replace(/^[-*•]\s*/, "");
2372
+ const value = cleaned.split(/\s+/)[0] || cleaned;
2373
+ return {
2374
+ id: normalizeCursorModelId(value),
2375
+ provider: "cursor",
2376
+ displayName: normalizeCursorModelDisplay(value)
2377
+ };
2378
+ });
2379
+ }
2380
+ async function listCursorModels() {
2381
+ if (cursorModelsCache && Date.now() - cursorModelsCacheAt < CURSOR_MODELS_CACHE_TTL_MS) {
2382
+ return cursorModelsCache;
2383
+ }
2384
+ const fallback = getCursorDefaultModel();
2385
+ const base = [
2386
+ {
2387
+ id: "cursor-default",
2388
+ provider: "cursor",
2389
+ displayName: formatCursorDefaultLabel(fallback)
2390
+ }
2391
+ ];
2392
+ const envModels = process.env.AGENTCONNECT_CURSOR_MODELS;
2393
+ if (envModels) {
2394
+ try {
2395
+ const parsed = JSON.parse(envModels);
2396
+ if (Array.isArray(parsed)) {
2397
+ for (const entry of parsed) {
2398
+ if (typeof entry === "string" && entry.trim()) {
2399
+ const trimmed = entry.trim();
2400
+ base.push({
2401
+ id: normalizeCursorModelId(trimmed),
2402
+ provider: "cursor",
2403
+ displayName: normalizeCursorModelDisplay(trimmed)
2404
+ });
2405
+ }
2406
+ }
2407
+ }
2408
+ } catch {
2409
+ }
2410
+ }
2411
+ const cliModels = await listCursorModelsFromCli();
2412
+ base.push(...cliModels);
2413
+ const seen = /* @__PURE__ */ new Set();
2414
+ const list = base.filter((entry) => {
2415
+ const key = `${entry.provider}:${entry.id}`;
2416
+ if (seen.has(key)) return false;
2417
+ seen.add(key);
2418
+ return true;
2419
+ });
2420
+ cursorModelsCache = list;
2421
+ cursorModelsCacheAt = Date.now();
2422
+ return list;
2423
+ }
2424
+ function normalizeCursorStatusOutput(output) {
2425
+ const text = output.toLowerCase();
2426
+ if (text.includes("not authenticated") || text.includes("not logged in") || text.includes("login required") || text.includes("please login") || text.includes("please log in") || text.includes("unauthorized")) {
2427
+ return false;
2428
+ }
2429
+ if (text.includes("authenticated") || text.includes("logged in") || text.includes("signed in") || text.includes("account")) {
2430
+ return true;
2431
+ }
2432
+ return null;
2433
+ }
2434
+ function getCursorUpdateSnapshot(commandPath) {
2435
+ if (cursorUpdateCache && Date.now() - cursorUpdateCache.checkedAt < CURSOR_UPDATE_CACHE_TTL_MS) {
2436
+ const action = getCursorUpdateAction(commandPath);
2437
+ return {
2438
+ updateAvailable: cursorUpdateCache.updateAvailable,
2439
+ latestVersion: cursorUpdateCache.latestVersion,
2440
+ updateCheckedAt: cursorUpdateCache.checkedAt,
2441
+ updateSource: action?.source ?? "unknown",
2442
+ updateCommand: action?.commandLabel,
2443
+ updateMessage: cursorUpdateCache.updateMessage
2444
+ };
2445
+ }
2446
+ return {};
2447
+ }
2448
+ function ensureCursorUpdateCheck(currentVersion, commandPath) {
2449
+ if (cursorUpdateCache && Date.now() - cursorUpdateCache.checkedAt < CURSOR_UPDATE_CACHE_TTL_MS) {
2450
+ return;
2451
+ }
2452
+ if (cursorUpdatePromise) return;
2453
+ cursorUpdatePromise = (async () => {
2454
+ const action = getCursorUpdateAction(commandPath || null);
2455
+ let latest = null;
2456
+ let updateAvailable;
2457
+ let updateMessage;
2458
+ if (action?.source === "brew") {
2459
+ latest = await fetchBrewCaskVersion2("cursor");
2460
+ }
2461
+ if (latest && currentVersion) {
2462
+ const a = parseSemver3(currentVersion);
2463
+ const b = parseSemver3(latest);
2464
+ if (a && b) {
2465
+ updateAvailable = compareSemver3(a, b) < 0;
2466
+ updateMessage = updateAvailable ? `Update available: ${currentVersion} -> ${latest}` : `Up to date (${currentVersion})`;
2467
+ }
2468
+ } else if (!action) {
2469
+ updateMessage = "Update check unavailable";
2470
+ }
2471
+ debugLog("Cursor", "update-check", {
2472
+ updateAvailable,
2473
+ message: updateMessage
2474
+ });
2475
+ cursorUpdateCache = {
2476
+ checkedAt: Date.now(),
2477
+ updateAvailable,
2478
+ latestVersion: latest ?? void 0,
2479
+ updateMessage
2480
+ };
2481
+ })().finally(() => {
2482
+ cursorUpdatePromise = null;
2483
+ });
2484
+ }
2485
+ async function ensureCursorInstalled() {
2486
+ const command2 = getCursorCommand();
2487
+ const versionCheck = await checkCommandVersion(command2, [["--version"], ["-V"]]);
2488
+ debugLog("Cursor", "install-check", {
2489
+ command: command2,
2490
+ versionOk: versionCheck.ok,
2491
+ version: versionCheck.version
2492
+ });
2493
+ if (versionCheck.ok) {
2494
+ return { installed: true, version: versionCheck.version || void 0 };
2495
+ }
2496
+ if (commandExists(command2)) {
2497
+ return { installed: true, version: void 0 };
2498
+ }
2499
+ const override = buildInstallCommand("AGENTCONNECT_CURSOR_INSTALL", "");
2500
+ let install = override;
2501
+ let packageManager = override.command ? "unknown" : "unknown";
2502
+ if (!install.command) {
2503
+ if (process.platform !== "win32" && commandExists("bash") && commandExists("curl")) {
2504
+ install = { command: "bash", args: ["-lc", INSTALL_UNIX2] };
2505
+ packageManager = "script";
2506
+ }
2507
+ }
2508
+ if (!install.command) {
2509
+ return { installed: false, version: void 0, packageManager };
2510
+ }
2511
+ await runCommand(install.command, install.args, { shell: process.platform === "win32" });
2512
+ const after = await checkCommandVersion(command2, [["--version"], ["-V"]]);
2513
+ return {
2514
+ installed: after.ok,
2515
+ version: after.version || void 0,
2516
+ packageManager
2517
+ };
2518
+ }
2519
+ async function getCursorStatus() {
2520
+ const command2 = getCursorCommand();
2521
+ const versionCheck = await checkCommandVersion(command2, [["--version"], ["-V"]]);
2522
+ const installed = versionCheck.ok || commandExists(command2);
2523
+ let loggedIn = false;
2524
+ if (installed) {
2525
+ const status = buildStatusCommand("AGENTCONNECT_CURSOR_STATUS", DEFAULT_STATUS3);
2526
+ if (status.command) {
2527
+ const statusCommand = resolveWindowsCommand(status.command);
2528
+ const result = await runCommand(statusCommand, withCursorEndpoint(status.args), {
2529
+ env: buildCursorEnv()
2530
+ });
2531
+ const output = `${result.stdout}
2532
+ ${result.stderr}`;
2533
+ const parsed = normalizeCursorStatusOutput(output);
2534
+ loggedIn = parsed ?? result.code === 0;
2535
+ }
2536
+ if (!loggedIn) {
2537
+ loggedIn = Boolean(getCursorApiKey().trim());
2538
+ }
2539
+ }
2540
+ if (installed) {
2541
+ const resolved2 = resolveCommandRealPath(command2);
2542
+ ensureCursorUpdateCheck(versionCheck.version, resolved2 || null);
2543
+ }
2544
+ const resolved = resolveCommandRealPath(command2);
2545
+ const updateInfo = installed ? getCursorUpdateSnapshot(resolved || null) : {};
2546
+ return { installed, loggedIn, version: versionCheck.version || void 0, ...updateInfo };
2547
+ }
2548
+ async function updateCursor() {
2549
+ const command2 = getCursorCommand();
2550
+ if (!commandExists(command2)) {
2551
+ return { installed: false, loggedIn: false };
2552
+ }
2553
+ const resolved = resolveCommandRealPath(command2);
2554
+ const updateOverride = buildStatusCommand("AGENTCONNECT_CURSOR_UPDATE", "");
2555
+ const action = updateOverride.command ? null : getCursorUpdateAction(resolved || null);
2556
+ const updateCommand = updateOverride.command || action?.command || "";
2557
+ const updateArgs = updateOverride.command ? updateOverride.args : action?.args || [];
2558
+ if (!updateCommand) {
2559
+ throw new Error("No update command available. Please update Cursor manually.");
2560
+ }
2561
+ const cmd = resolveWindowsCommand(updateCommand);
2562
+ debugLog("Cursor", "update-run", { command: cmd, args: updateArgs });
2563
+ const result = await runCommand(cmd, updateArgs, { env: buildCursorEnv() });
2564
+ debugLog("Cursor", "update-result", {
2565
+ code: result.code,
2566
+ stdout: trimOutput3(result.stdout),
2567
+ stderr: trimOutput3(result.stderr)
2568
+ });
2569
+ if (result.code !== 0 && result.code !== null) {
2570
+ const message = trimOutput3(`${result.stdout}
2571
+ ${result.stderr}`, 800) || "Update failed";
2572
+ throw new Error(message);
2573
+ }
2574
+ cursorUpdateCache = null;
2575
+ cursorUpdatePromise = null;
2576
+ return getCursorStatus();
2577
+ }
2578
+ function buildCursorEnv() {
2579
+ const env = { ...process.env };
2580
+ const apiKey = getCursorApiKey().trim();
2581
+ if (apiKey) {
2582
+ env.CURSOR_API_KEY = apiKey;
2583
+ }
2584
+ return env;
2585
+ }
2586
+ async function loginCursor(options) {
2587
+ if (typeof options?.apiKey === "string") {
2588
+ process.env.CURSOR_API_KEY = options.apiKey;
2589
+ }
2590
+ if (typeof options?.baseUrl === "string") {
2591
+ process.env.AGENTCONNECT_CURSOR_ENDPOINT = options.baseUrl;
2592
+ }
2593
+ if (typeof options?.model === "string") {
2594
+ process.env.AGENTCONNECT_CURSOR_MODEL = options.model;
2595
+ cursorModelsCache = null;
2596
+ cursorModelsCacheAt = 0;
2597
+ }
2598
+ if (Array.isArray(options?.models)) {
2599
+ process.env.AGENTCONNECT_CURSOR_MODELS = JSON.stringify(options.models.filter(Boolean));
2600
+ cursorModelsCache = null;
2601
+ cursorModelsCacheAt = 0;
2602
+ }
2603
+ if (!options?.apiKey) {
2604
+ const login = buildLoginCommand("AGENTCONNECT_CURSOR_LOGIN", DEFAULT_LOGIN3);
2605
+ if (login.command) {
2606
+ const command2 = resolveWindowsCommand(login.command);
2607
+ await runCommand(command2, withCursorEndpoint(login.args), { env: buildCursorEnv() });
2608
+ }
2609
+ }
2610
+ const timeoutMs = Number(process.env.AGENTCONNECT_CURSOR_LOGIN_TIMEOUT_MS || 2e4);
2611
+ const pollIntervalMs = Number(process.env.AGENTCONNECT_CURSOR_LOGIN_POLL_MS || 1e3);
2612
+ const start = Date.now();
2613
+ let status = await getCursorStatus();
2614
+ while (!status.loggedIn && Date.now() - start < timeoutMs) {
2615
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2616
+ status = await getCursorStatus();
2617
+ }
2618
+ return { loggedIn: status.loggedIn };
2619
+ }
2620
+ function safeJsonParse3(line) {
2621
+ try {
2622
+ return JSON.parse(line);
2623
+ } catch {
2624
+ return null;
2625
+ }
2626
+ }
2627
+ function extractSessionId3(ev) {
2628
+ const id = ev.session_id ?? ev.sessionId;
2629
+ return typeof id === "string" ? id : null;
2630
+ }
2631
+ function extractUsage2(ev) {
2632
+ const usage = ev.usage ?? ev.token_usage ?? ev.tokenUsage ?? ev.tokens;
2633
+ if (!usage || typeof usage !== "object") return null;
2634
+ const toNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
2635
+ const input = toNumber(
2636
+ usage.input_tokens ?? usage.prompt_tokens ?? usage.inputTokens ?? usage.promptTokens
2637
+ );
2638
+ const output = toNumber(
2639
+ usage.output_tokens ?? usage.completion_tokens ?? usage.outputTokens ?? usage.completionTokens
2640
+ );
2641
+ const total = toNumber(usage.total_tokens ?? usage.totalTokens);
2642
+ const out = {};
2643
+ if (input !== void 0) out.input_tokens = input;
2644
+ if (output !== void 0) out.output_tokens = output;
2645
+ if (total !== void 0) out.total_tokens = total;
2646
+ return Object.keys(out).length ? out : null;
2647
+ }
2648
+ function extractTextFromContent2(content) {
2649
+ if (!content) return "";
2650
+ if (typeof content === "string") return content;
2651
+ if (Array.isArray(content)) {
2652
+ return content.map((part) => {
2653
+ if (!part) return "";
2654
+ if (typeof part === "string") return part;
2655
+ if (typeof part === "object") {
2656
+ const text = part.text;
2657
+ if (typeof text === "string") return text;
2658
+ }
2659
+ return "";
2660
+ }).join("");
2661
+ }
2662
+ if (typeof content === "object") {
2663
+ const text = content.text;
2664
+ if (typeof text === "string") return text;
2665
+ }
2666
+ return "";
2667
+ }
2668
+ function extractToolCall(ev) {
2669
+ if (ev.type !== "tool_call") return null;
2670
+ const toolCall = ev.tool_call && typeof ev.tool_call === "object" ? ev.tool_call : null;
2671
+ if (!toolCall) return null;
2672
+ const keys = Object.keys(toolCall);
2673
+ if (!keys.length) return null;
2674
+ const name = keys[0];
2675
+ const entry = toolCall[name];
2676
+ const record = entry && typeof entry === "object" ? entry : null;
2677
+ const input = record?.args ?? record?.input ?? void 0;
2678
+ const output = record?.output ?? record?.result ?? void 0;
2679
+ const subtype = typeof ev.subtype === "string" ? ev.subtype : "";
2680
+ const phase = subtype === "completed" ? "completed" : subtype === "started" ? "start" : subtype === "error" ? "error" : subtype === "delta" ? "delta" : void 0;
2681
+ return {
2682
+ name,
2683
+ input,
2684
+ output,
2685
+ callId: typeof ev.call_id === "string" ? ev.call_id : void 0,
2686
+ phase
2687
+ };
2688
+ }
2689
+ function extractTextFromMessage(message) {
2690
+ if (!message || typeof message !== "object") return "";
2691
+ const content = message.content;
2692
+ return extractTextFromContent2(content);
2693
+ }
2694
+ function extractAssistantDelta2(ev) {
2695
+ if (ev.type !== "assistant" && ev.message?.role !== "assistant") return null;
2696
+ if (typeof ev.text === "string") return ev.text;
2697
+ if (typeof ev.delta === "string") return ev.delta;
2698
+ if (ev.message) {
2699
+ const text = extractTextFromMessage(ev.message);
2700
+ return text || null;
2701
+ }
2702
+ if (ev.content) {
2703
+ const text = extractTextFromContent2(ev.content);
2704
+ return text || null;
2705
+ }
2706
+ return null;
2707
+ }
2708
+ function extractResultText2(ev) {
2709
+ if (typeof ev.result === "string") return ev.result;
2710
+ if (ev.result && typeof ev.result === "object") {
2711
+ const result = ev.result;
2712
+ if (typeof result.text === "string") return result.text;
2713
+ if (result.message) {
2714
+ const text = extractTextFromMessage(result.message);
2715
+ if (text) return text;
2716
+ }
2717
+ if (result.content) {
2718
+ const text = extractTextFromContent2(result.content);
2719
+ if (text) return text;
2720
+ }
2721
+ }
2722
+ if (ev.message) {
2723
+ const text = extractTextFromMessage(ev.message);
2724
+ if (text) return text;
2725
+ }
2726
+ return null;
2727
+ }
2728
+ function isErrorEvent(ev) {
2729
+ if (ev.type === "error") return true;
2730
+ if (ev.type === "result" && ev.subtype) {
2731
+ const subtype = String(ev.subtype).toLowerCase();
2732
+ if (subtype.includes("error") || subtype.includes("failed")) return true;
2733
+ }
2734
+ return false;
2735
+ }
2736
+ function extractErrorMessage(ev) {
2737
+ if (typeof ev.error === "string") return ev.error;
2738
+ if (ev.error && typeof ev.error === "object" && typeof ev.error.message === "string") {
2739
+ return ev.error.message;
2740
+ }
2741
+ if (typeof ev.text === "string" && ev.type === "error") return ev.text;
2742
+ if (typeof ev.result === "string" && ev.type === "result") return ev.result;
2743
+ return null;
2744
+ }
2745
+ function normalizeCursorEvent(raw) {
2746
+ if (!raw || typeof raw !== "object") return { type: "unknown" };
2747
+ const type = typeof raw.type === "string" ? raw.type : "unknown";
2748
+ return { ...raw, type };
2749
+ }
2750
+ function runCursorPrompt({
2751
+ prompt,
2752
+ resumeSessionId,
2753
+ model,
2754
+ repoRoot,
2755
+ cwd,
2756
+ providerDetailLevel,
2757
+ onEvent,
2758
+ signal
2759
+ }) {
2760
+ return new Promise((resolve) => {
2761
+ const command2 = getCursorCommand();
2762
+ const resolvedRepoRoot = repoRoot ? path4.resolve(repoRoot) : null;
2763
+ const resolvedCwd = cwd ? path4.resolve(cwd) : null;
2764
+ const runDir = resolvedCwd || resolvedRepoRoot || process.cwd();
2765
+ const args2 = ["--print", "--output-format", "stream-json"];
2766
+ if (resumeSessionId) {
2767
+ args2.push("--resume", resumeSessionId);
2768
+ }
2769
+ const fallbackModel = getCursorDefaultModel();
2770
+ const resolvedModel = resolveCursorModel(model, fallbackModel);
2771
+ if (resolvedModel) {
2772
+ args2.push("--model", resolvedModel);
2773
+ }
2774
+ const endpoint = resolveCursorEndpoint();
2775
+ if (endpoint) {
2776
+ args2.push("--endpoint", endpoint);
2777
+ }
2778
+ args2.push(prompt);
2779
+ const argsPreview = [...args2];
2780
+ if (argsPreview.length > 0) {
2781
+ argsPreview[argsPreview.length - 1] = "[prompt]";
2782
+ }
2783
+ debugLog("Cursor", "spawn", {
2784
+ command: command2,
2785
+ args: argsPreview,
2786
+ cwd: runDir,
2787
+ model: resolvedModel || null,
2788
+ endpoint: endpoint || null,
2789
+ resume: resumeSessionId || null,
2790
+ apiKeyConfigured: Boolean(getCursorApiKey().trim()),
2791
+ promptChars: prompt.length
2792
+ });
2793
+ const child = spawn4(command2, args2, {
2794
+ cwd: runDir,
2795
+ env: buildCursorEnv(),
2796
+ stdio: ["ignore", "pipe", "pipe"]
2797
+ });
2798
+ if (signal) {
2799
+ signal.addEventListener("abort", () => {
2800
+ child.kill("SIGTERM");
2801
+ });
2802
+ }
2803
+ let aggregated = "";
2804
+ let finalSessionId = null;
2805
+ let didFinalize = false;
2806
+ let sawError = false;
2807
+ let sawJson = false;
2808
+ let rawOutput = "";
2809
+ const stdoutLines = [];
2810
+ const stderrLines = [];
2811
+ const includeRaw = providerDetailLevel === "raw";
2812
+ const buildProviderDetail = (eventType, data, raw) => {
2813
+ const detail = { eventType };
2814
+ if (data && Object.keys(data).length) detail.data = data;
2815
+ if (includeRaw && raw !== void 0) detail.raw = raw;
2816
+ return detail;
2817
+ };
2818
+ const emit = (event) => {
2819
+ if (finalSessionId) {
2820
+ onEvent({ ...event, providerSessionId: finalSessionId });
2821
+ } else {
2822
+ onEvent(event);
2823
+ }
2824
+ };
2825
+ const pushLine = (list, line) => {
2826
+ if (!line) return;
2827
+ list.push(line);
2828
+ if (list.length > 12) list.shift();
2829
+ };
2830
+ const emitError = (message, providerDetail) => {
2831
+ if (sawError) return;
2832
+ sawError = true;
2833
+ emit({ type: "error", message, providerDetail });
2834
+ };
2835
+ const emitFinal = (text) => {
2836
+ if (didFinalize) return;
2837
+ didFinalize = true;
2838
+ emit({ type: "final", text });
2839
+ };
2840
+ const handleEvent = (ev) => {
2841
+ const normalized = normalizeCursorEvent(ev);
2842
+ if (ev?.type === "system" && ev?.subtype === "init") {
2843
+ debugLog("Cursor", "init", {
2844
+ apiKeySource: ev.apiKeySource || null,
2845
+ cwd: ev.cwd || null,
2846
+ model: ev.model || null,
2847
+ permissionMode: ev.permissionMode || null,
2848
+ sessionId: ev.session_id ?? ev.sessionId ?? null
2849
+ });
2850
+ emit({
2851
+ type: "detail",
2852
+ provider: "cursor",
2853
+ providerDetail: buildProviderDetail(
2854
+ "system.init",
2855
+ {
2856
+ apiKeySource: ev.apiKeySource,
2857
+ cwd: ev.cwd,
2858
+ model: ev.model,
2859
+ permissionMode: ev.permissionMode
2860
+ },
2861
+ ev
2862
+ )
2863
+ });
2864
+ }
2865
+ const sid = extractSessionId3(ev);
2866
+ if (sid) finalSessionId = sid;
2867
+ if (ev?.type === "thinking") {
2868
+ const subtype = typeof ev.subtype === "string" ? ev.subtype : "";
2869
+ const phase = subtype === "completed" ? "completed" : subtype === "started" ? "start" : subtype === "error" ? "error" : "delta";
2870
+ emit({
2871
+ type: "thinking",
2872
+ provider: "cursor",
2873
+ phase,
2874
+ text: typeof ev.text === "string" ? ev.text : "",
2875
+ timestampMs: typeof ev.timestamp_ms === "number" ? ev.timestamp_ms : void 0,
2876
+ providerDetail: buildProviderDetail(
2877
+ subtype ? `thinking.${subtype}` : "thinking",
2878
+ {
2879
+ subtype: subtype || void 0
2880
+ },
2881
+ ev
2882
+ )
2883
+ });
2884
+ }
2885
+ if (ev?.type === "assistant" || ev?.type === "user") {
2886
+ const role = ev.message?.role === "assistant" || ev.message?.role === "user" ? ev.message?.role : ev.type;
2887
+ const rawContent = ev.message?.content ?? ev.content;
2888
+ const content = extractTextFromContent2(rawContent);
2889
+ emit({
2890
+ type: "message",
2891
+ provider: "cursor",
2892
+ role,
2893
+ content,
2894
+ contentParts: rawContent ?? null,
2895
+ providerDetail: buildProviderDetail(ev.type, {}, ev)
2896
+ });
2897
+ }
2898
+ const toolCall = extractToolCall(ev);
2899
+ if (toolCall) {
2900
+ emit({
2901
+ type: "tool_call",
2902
+ provider: "cursor",
2903
+ name: toolCall.name,
2904
+ callId: toolCall.callId,
2905
+ input: toolCall.input,
2906
+ output: toolCall.output,
2907
+ phase: toolCall.phase,
2908
+ providerDetail: buildProviderDetail(
2909
+ ev.subtype ? `tool_call.${ev.subtype}` : "tool_call",
2910
+ {
2911
+ name: toolCall.name,
2912
+ callId: toolCall.callId,
2913
+ subtype: ev.subtype
2914
+ },
2915
+ ev
2916
+ )
2917
+ });
2918
+ }
2919
+ const usage = extractUsage2(ev);
2920
+ if (usage) {
2921
+ emit({
2922
+ type: "usage",
2923
+ inputTokens: usage.input_tokens,
2924
+ outputTokens: usage.output_tokens
2925
+ });
2926
+ }
2927
+ if (isErrorEvent(ev)) {
2928
+ const message = extractErrorMessage(ev) || "Cursor run failed";
2929
+ emitError(message, buildProviderDetail(ev.subtype ? `error.${ev.subtype}` : "error", {}, ev));
2930
+ return;
2931
+ }
2932
+ const delta = extractAssistantDelta2(ev);
2933
+ if (delta) {
2934
+ aggregated += delta;
2935
+ emit({ type: "delta", text: delta, providerDetail: buildProviderDetail("delta", {}, ev) });
2936
+ }
2937
+ if (ev.type === "result") {
2938
+ const resultText = extractResultText2(ev);
2939
+ if (!aggregated && resultText) {
2940
+ aggregated = resultText;
2941
+ }
2942
+ if (!sawError) {
2943
+ emitFinal(aggregated || resultText || "");
2944
+ emit({
2945
+ type: "detail",
2946
+ provider: "cursor",
2947
+ providerDetail: buildProviderDetail(
2948
+ "result",
2949
+ {
2950
+ subtype: ev.subtype,
2951
+ duration_ms: typeof ev.duration_ms === "number" ? ev.duration_ms : void 0,
2952
+ request_id: typeof ev.request_id === "string" ? ev.request_id : void 0,
2953
+ is_error: typeof ev.is_error === "boolean" ? ev.is_error : void 0
2954
+ },
2955
+ ev
2956
+ )
2957
+ });
2958
+ }
2959
+ }
2960
+ };
2961
+ const handleLine = (line, source) => {
2962
+ const trimmed = line.trim();
2963
+ if (!trimmed) return;
2964
+ const payload = trimmed.startsWith("data: ") ? trimmed.slice(6).trim() : trimmed;
2965
+ const parsed = safeJsonParse3(payload);
2966
+ if (!parsed || typeof parsed !== "object") {
2967
+ emit({ type: "raw_line", line });
2968
+ if (source === "stdout") {
2969
+ rawOutput += `${line}
2970
+ `;
2971
+ pushLine(stdoutLines, line);
2972
+ } else {
2973
+ pushLine(stderrLines, line);
2974
+ }
2975
+ return;
2976
+ }
2977
+ sawJson = true;
2978
+ handleEvent(parsed);
2979
+ };
2980
+ const stdoutParser = createLineParser((line) => handleLine(line, "stdout"));
2981
+ const stderrParser = createLineParser((line) => handleLine(line, "stderr"));
2982
+ child.stdout?.on("data", stdoutParser);
2983
+ child.stderr?.on("data", stderrParser);
2984
+ child.on("close", (code) => {
2985
+ if (!didFinalize) {
2986
+ if (code && code !== 0) {
2987
+ const hint = stderrLines.at(-1) || stdoutLines.at(-1) || "";
2988
+ const suffix = hint ? `: ${hint}` : "";
2989
+ debugLog("Cursor", "exit", { code, stderr: stderrLines, stdout: stdoutLines });
2990
+ emitError(`Cursor CLI exited with code ${code}${suffix}`);
2991
+ } else if (!sawError) {
2992
+ const fallback = !sawJson ? rawOutput.trim() : "";
2993
+ emitFinal(aggregated || fallback);
2994
+ }
2995
+ }
2996
+ resolve({ sessionId: finalSessionId });
2997
+ });
2998
+ child.on("error", (err) => {
2999
+ debugLog("Cursor", "spawn-error", { message: err?.message });
3000
+ emitError(err?.message ?? "Cursor failed to start");
3001
+ resolve({ sessionId: finalSessionId });
3002
+ });
3003
+ });
3004
+ }
3005
+
3006
+ // src/providers/local.ts
3007
+ function getLocalBaseUrl() {
3008
+ const base = process.env.AGENTCONNECT_LOCAL_BASE_URL || "http://localhost:11434/v1";
3009
+ return base.replace(/\/+$/, "");
3010
+ }
3011
+ function getLocalApiKey() {
3012
+ return process.env.AGENTCONNECT_LOCAL_API_KEY || "";
3013
+ }
3014
+ function resolveLocalModel(model, fallback) {
3015
+ if (!model) return fallback;
3016
+ const raw = String(model);
3017
+ if (raw === "local") return fallback;
3018
+ if (raw.startsWith("local:")) return raw.slice("local:".length);
3019
+ if (raw.startsWith("local/")) return raw.slice("local/".length);
3020
+ return raw;
3021
+ }
3022
+ async function fetchJson3(url, options = {}) {
3023
+ const controller = new AbortController();
3024
+ const timer = setTimeout(() => controller.abort(), 4e3);
3025
+ try {
3026
+ const res = await fetch(url, { ...options, signal: controller.signal });
3027
+ if (!res.ok) {
3028
+ return { ok: false, status: res.status, data: null };
3029
+ }
3030
+ const data = await res.json();
3031
+ return { ok: true, status: res.status, data };
3032
+ } catch {
3033
+ return { ok: false, status: 0, data: null };
3034
+ } finally {
3035
+ clearTimeout(timer);
1454
3036
  }
1455
3037
  }
1456
3038
  async function ensureLocalInstalled() {
1457
3039
  const base = getLocalBaseUrl();
1458
- const res = await fetchJson(`${base}/models`);
3040
+ const res = await fetchJson3(`${base}/models`);
1459
3041
  return { installed: res.ok };
1460
3042
  }
1461
3043
  async function getLocalStatus() {
1462
3044
  const base = getLocalBaseUrl();
1463
- const res = await fetchJson(`${base}/models`);
3045
+ const res = await fetchJson3(`${base}/models`);
1464
3046
  if (!res.ok) return { installed: false, loggedIn: false };
1465
3047
  return { installed: true, loggedIn: true };
1466
3048
  }
3049
+ async function updateLocal() {
3050
+ return getLocalStatus();
3051
+ }
1467
3052
  async function loginLocal(options = {}) {
1468
3053
  if (typeof options.baseUrl === "string") {
1469
3054
  process.env.AGENTCONNECT_LOCAL_BASE_URL = options.baseUrl;
@@ -1482,7 +3067,7 @@ async function loginLocal(options = {}) {
1482
3067
  }
1483
3068
  async function listLocalModels() {
1484
3069
  const base = getLocalBaseUrl();
1485
- const res = await fetchJson(`${base}/models`);
3070
+ const res = await fetchJson3(`${base}/models`);
1486
3071
  if (!res.ok || !res.data || !Array.isArray(res.data.data)) return [];
1487
3072
  return res.data.data.map((entry) => ({ id: entry.id, provider: "local", displayName: entry.id })).filter((entry) => entry.id);
1488
3073
  }
@@ -1506,7 +3091,7 @@ async function runLocalPrompt({
1506
3091
  const headers = { "Content-Type": "application/json" };
1507
3092
  const apiKey = getLocalApiKey();
1508
3093
  if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
1509
- const res = await fetchJson(`${base}/chat/completions`, {
3094
+ const res = await fetchJson3(`${base}/chat/completions`, {
1510
3095
  method: "POST",
1511
3096
  headers,
1512
3097
  body: JSON.stringify(payload)
@@ -1533,6 +3118,7 @@ var providers = {
1533
3118
  name: "Claude",
1534
3119
  ensureInstalled: ensureClaudeInstalled,
1535
3120
  status: getClaudeStatus,
3121
+ update: updateClaude,
1536
3122
  login: loginClaude,
1537
3123
  logout: async () => {
1538
3124
  },
@@ -1543,16 +3129,29 @@ var providers = {
1543
3129
  name: "Codex",
1544
3130
  ensureInstalled: ensureCodexInstalled,
1545
3131
  status: getCodexStatus,
3132
+ update: updateCodex,
1546
3133
  login: loginCodex,
1547
3134
  logout: async () => {
1548
3135
  },
1549
3136
  runPrompt: runCodexPrompt
1550
3137
  },
3138
+ cursor: {
3139
+ id: "cursor",
3140
+ name: "Cursor",
3141
+ ensureInstalled: ensureCursorInstalled,
3142
+ status: getCursorStatus,
3143
+ update: updateCursor,
3144
+ login: loginCursor,
3145
+ logout: async () => {
3146
+ },
3147
+ runPrompt: runCursorPrompt
3148
+ },
1551
3149
  local: {
1552
3150
  id: "local",
1553
3151
  name: "Local",
1554
3152
  ensureInstalled: ensureLocalInstalled,
1555
3153
  status: getLocalStatus,
3154
+ update: updateLocal,
1556
3155
  login: loginLocal,
1557
3156
  logout: async () => {
1558
3157
  },
@@ -1562,8 +3161,10 @@ var providers = {
1562
3161
  async function listModels() {
1563
3162
  const claudeModels = await listClaudeModels();
1564
3163
  const codexModels = await listCodexModels();
3164
+ const cursorModels = await listCursorModels();
1565
3165
  const base = [
1566
3166
  ...claudeModels,
3167
+ ...cursorModels,
1567
3168
  { id: "local", provider: "local", displayName: "Local Model" }
1568
3169
  ];
1569
3170
  const envModels = process.env.AGENTCONNECT_LOCAL_MODELS;
@@ -1597,6 +3198,7 @@ async function listRecentModels(providerId) {
1597
3198
  }
1598
3199
  function resolveProviderForModel(model) {
1599
3200
  const lower = String(model || "").toLowerCase();
3201
+ if (lower.includes("cursor")) return "cursor";
1600
3202
  if (lower.includes("codex")) return "codex";
1601
3203
  if (lower.startsWith("gpt") || lower.startsWith("o1") || lower.startsWith("o3") || lower.startsWith("o4")) {
1602
3204
  return "codex";
@@ -1610,15 +3212,15 @@ function resolveProviderForModel(model) {
1610
3212
 
1611
3213
  // src/observed.ts
1612
3214
  import fs from "fs";
1613
- import path4 from "path";
3215
+ import path5 from "path";
1614
3216
  function createObservedTracker({
1615
3217
  basePath,
1616
3218
  appId,
1617
3219
  requested = []
1618
3220
  }) {
1619
3221
  const requestedList = Array.isArray(requested) ? requested.filter(Boolean) : [];
1620
- const dirPath = path4.join(basePath, ".agentconnect");
1621
- const filePath = path4.join(dirPath, "observed-capabilities.json");
3222
+ const dirPath = path5.join(basePath, ".agentconnect");
3223
+ const filePath = path5.join(dirPath, "observed-capabilities.json");
1622
3224
  const observed = /* @__PURE__ */ new Set();
1623
3225
  let writeTimer = null;
1624
3226
  function load() {
@@ -1691,6 +3293,15 @@ function replyError(socket, id, code, message) {
1691
3293
  });
1692
3294
  }
1693
3295
  function sessionEvent(socket, sessionId, type, data) {
3296
+ if (process.env.AGENTCONNECT_DEBUG?.trim()) {
3297
+ try {
3298
+ console.log(
3299
+ `[AgentConnect][Session ${sessionId}] ${type} ${JSON.stringify(data)}`
3300
+ );
3301
+ } catch {
3302
+ console.log(`[AgentConnect][Session ${sessionId}] ${type}`);
3303
+ }
3304
+ }
1694
3305
  send(socket, {
1695
3306
  jsonrpc: "2.0",
1696
3307
  method: "acp.session.event",
@@ -1705,7 +3316,14 @@ function buildProviderList(statuses) {
1705
3316
  name: provider.name,
1706
3317
  installed: info.installed ?? false,
1707
3318
  loggedIn: info.loggedIn ?? false,
1708
- version: info.version
3319
+ version: info.version,
3320
+ updateAvailable: info.updateAvailable,
3321
+ latestVersion: info.latestVersion,
3322
+ updateCheckedAt: info.updateCheckedAt,
3323
+ updateSource: info.updateSource,
3324
+ updateCommand: info.updateCommand,
3325
+ updateMessage: info.updateMessage,
3326
+ updateInProgress: info.updateInProgress
1709
3327
  };
1710
3328
  });
1711
3329
  }
@@ -1720,10 +3338,12 @@ function startDevHost({
1720
3338
  const wss = new WebSocketServer({ server });
1721
3339
  const sessions = /* @__PURE__ */ new Map();
1722
3340
  const activeRuns = /* @__PURE__ */ new Map();
3341
+ const updatingProviders = /* @__PURE__ */ new Map();
1723
3342
  const processTable = /* @__PURE__ */ new Map();
1724
3343
  const backendState = /* @__PURE__ */ new Map();
1725
3344
  const statusCache = /* @__PURE__ */ new Map();
1726
- const statusCacheTtlMs = 2e3;
3345
+ const statusCacheTtlMs = 8e3;
3346
+ const statusInFlight = /* @__PURE__ */ new Map();
1727
3347
  const basePath = appPath || process.cwd();
1728
3348
  const manifest = readManifest(basePath);
1729
3349
  const appId = manifest?.id || "agentconnect-dev-app";
@@ -1736,7 +3356,7 @@ function startDevHost({
1736
3356
  function resolveAppPathInternal(input) {
1737
3357
  if (!input) return basePath;
1738
3358
  const value = String(input);
1739
- return path5.isAbsolute(value) ? value : path5.resolve(basePath, value);
3359
+ return path6.isAbsolute(value) ? value : path6.resolve(basePath, value);
1740
3360
  }
1741
3361
  function mapFileType(stat) {
1742
3362
  if (stat.isFile()) return "file";
@@ -1774,7 +3394,7 @@ function startDevHost({
1774
3394
  }
1775
3395
  function readManifest(root) {
1776
3396
  try {
1777
- const raw = fs2.readFileSync(path5.join(root, "agentconnect.app.json"), "utf8");
3397
+ const raw = fs2.readFileSync(path6.join(root, "agentconnect.app.json"), "utf8");
1778
3398
  return JSON.parse(raw);
1779
3399
  } catch {
1780
3400
  return null;
@@ -1794,9 +3414,22 @@ function startDevHost({
1794
3414
  if (cached && now - cached.at < statusCacheTtlMs) {
1795
3415
  return cached.status;
1796
3416
  }
1797
- const status = await provider.status();
1798
- statusCache.set(provider.id, { status, at: now });
1799
- return status;
3417
+ const existing = statusInFlight.get(provider.id);
3418
+ if (existing) return existing;
3419
+ const startedAt = Date.now();
3420
+ const promise = provider.status().then((status) => {
3421
+ debugLog("Providers", "status-check", {
3422
+ providerId: provider.id,
3423
+ durationMs: Date.now() - startedAt,
3424
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
3425
+ });
3426
+ statusCache.set(provider.id, { status, at: Date.now() });
3427
+ return status;
3428
+ }).finally(() => {
3429
+ statusInFlight.delete(provider.id);
3430
+ });
3431
+ statusInFlight.set(provider.id, promise);
3432
+ return promise;
1800
3433
  }
1801
3434
  function invalidateStatus(providerId) {
1802
3435
  if (!providerId) return;
@@ -1844,7 +3477,11 @@ function startDevHost({
1844
3477
  })
1845
3478
  );
1846
3479
  const statuses = Object.fromEntries(statusEntries);
1847
- reply(socket, id, { providers: buildProviderList(statuses) });
3480
+ const list = buildProviderList(statuses).map((entry) => ({
3481
+ ...entry,
3482
+ updateInProgress: updatingProviders.has(entry.id)
3483
+ }));
3484
+ reply(socket, id, { providers: list });
1848
3485
  return;
1849
3486
  }
1850
3487
  if (method === "acp.providers.status") {
@@ -1861,11 +3498,66 @@ function startDevHost({
1861
3498
  name: provider.name,
1862
3499
  installed: status.installed,
1863
3500
  loggedIn: status.loggedIn,
1864
- version: status.version
3501
+ version: status.version,
3502
+ updateAvailable: status.updateAvailable,
3503
+ latestVersion: status.latestVersion,
3504
+ updateCheckedAt: status.updateCheckedAt,
3505
+ updateSource: status.updateSource,
3506
+ updateCommand: status.updateCommand,
3507
+ updateMessage: status.updateMessage,
3508
+ updateInProgress: updatingProviders.has(provider.id) || status.updateInProgress
1865
3509
  }
1866
3510
  });
1867
3511
  return;
1868
3512
  }
3513
+ if (method === "acp.providers.update") {
3514
+ const providerId = params.provider;
3515
+ const provider = providers[providerId];
3516
+ if (!provider) {
3517
+ replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
3518
+ return;
3519
+ }
3520
+ debugLog("Providers", "update-start", { providerId });
3521
+ if (!updatingProviders.has(providerId)) {
3522
+ const promise = provider.update().finally(() => {
3523
+ updatingProviders.delete(providerId);
3524
+ invalidateStatus(providerId);
3525
+ });
3526
+ updatingProviders.set(providerId, promise);
3527
+ }
3528
+ try {
3529
+ const status = await updatingProviders.get(providerId);
3530
+ debugLog("Providers", "update-complete", {
3531
+ providerId,
3532
+ updateAvailable: status.updateAvailable,
3533
+ latestVersion: status.latestVersion,
3534
+ updateMessage: status.updateMessage
3535
+ });
3536
+ reply(socket, id, {
3537
+ provider: {
3538
+ id: provider.id,
3539
+ name: provider.name,
3540
+ installed: status.installed,
3541
+ loggedIn: status.loggedIn,
3542
+ version: status.version,
3543
+ updateAvailable: status.updateAvailable,
3544
+ latestVersion: status.latestVersion,
3545
+ updateCheckedAt: status.updateCheckedAt,
3546
+ updateSource: status.updateSource,
3547
+ updateCommand: status.updateCommand,
3548
+ updateMessage: status.updateMessage,
3549
+ updateInProgress: false
3550
+ }
3551
+ });
3552
+ } catch (err) {
3553
+ debugLog("Providers", "update-error", {
3554
+ providerId,
3555
+ message: err instanceof Error ? err.message : String(err)
3556
+ });
3557
+ replyError(socket, id, "AC_ERR_INTERNAL", err?.message || "Update failed");
3558
+ }
3559
+ return;
3560
+ }
1869
3561
  if (method === "acp.providers.ensureInstalled") {
1870
3562
  const providerId = params.provider;
1871
3563
  const provider = providers[providerId];
@@ -1943,6 +3635,7 @@ function startDevHost({
1943
3635
  const reasoningEffort = params.reasoningEffort || null;
1944
3636
  const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : void 0;
1945
3637
  const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : void 0;
3638
+ const providerDetailLevel = params.providerDetailLevel || void 0;
1946
3639
  const providerId = resolveProviderForModel(model);
1947
3640
  recordModelCapability(model);
1948
3641
  sessions.set(sessionId, {
@@ -1952,7 +3645,8 @@ function startDevHost({
1952
3645
  providerSessionId: null,
1953
3646
  reasoningEffort,
1954
3647
  cwd,
1955
- repoRoot
3648
+ repoRoot,
3649
+ providerDetailLevel: providerDetailLevel === "raw" || providerDetailLevel === "minimal" ? providerDetailLevel : void 0
1956
3650
  });
1957
3651
  reply(socket, id, { sessionId });
1958
3652
  return;
@@ -1965,6 +3659,7 @@ function startDevHost({
1965
3659
  const reasoningEffort = params.reasoningEffort || null;
1966
3660
  const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : void 0;
1967
3661
  const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : void 0;
3662
+ const providerDetailLevel = params.providerDetailLevel || void 0;
1968
3663
  recordModelCapability(model);
1969
3664
  sessions.set(sessionId, {
1970
3665
  id: sessionId,
@@ -1973,7 +3668,8 @@ function startDevHost({
1973
3668
  providerSessionId: params.providerSessionId || null,
1974
3669
  reasoningEffort,
1975
3670
  cwd,
1976
- repoRoot
3671
+ repoRoot,
3672
+ providerDetailLevel: providerDetailLevel === "raw" || providerDetailLevel === "minimal" ? providerDetailLevel : void 0
1977
3673
  });
1978
3674
  } else {
1979
3675
  if (params.providerSessionId) {
@@ -1985,6 +3681,12 @@ function startDevHost({
1985
3681
  if (params.repoRoot) {
1986
3682
  existing.repoRoot = resolveAppPathInternal(params.repoRoot);
1987
3683
  }
3684
+ if (params.providerDetailLevel) {
3685
+ const level = String(params.providerDetailLevel);
3686
+ if (level === "raw" || level === "minimal") {
3687
+ existing.providerDetailLevel = level;
3688
+ }
3689
+ }
1988
3690
  recordModelCapability(existing.model);
1989
3691
  }
1990
3692
  reply(socket, id, { sessionId });
@@ -2004,6 +3706,10 @@ function startDevHost({
2004
3706
  replyError(socket, id, "AC_ERR_UNSUPPORTED", "Unknown provider");
2005
3707
  return;
2006
3708
  }
3709
+ if (updatingProviders.has(session.providerId)) {
3710
+ replyError(socket, id, "AC_ERR_BUSY", "Provider update in progress.");
3711
+ return;
3712
+ }
2007
3713
  const status = await provider.status();
2008
3714
  if (!status.installed) {
2009
3715
  const installed = await provider.ensureInstalled();
@@ -2015,6 +3721,7 @@ function startDevHost({
2015
3721
  const controller = new AbortController();
2016
3722
  const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : session.cwd || basePath;
2017
3723
  const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : session.repoRoot || basePath;
3724
+ const providerDetailLevel = params.providerDetailLevel === "raw" || params.providerDetailLevel === "minimal" ? params.providerDetailLevel : session.providerDetailLevel || "minimal";
2018
3725
  activeRuns.set(sessionId, controller);
2019
3726
  let sawError = false;
2020
3727
  provider.runPrompt({
@@ -2024,6 +3731,7 @@ function startDevHost({
2024
3731
  reasoningEffort: session.reasoningEffort,
2025
3732
  repoRoot,
2026
3733
  cwd,
3734
+ providerDetailLevel,
2027
3735
  signal: controller.signal,
2028
3736
  onEvent: (event) => {
2029
3737
  if (event.type === "error") {
@@ -2089,7 +3797,7 @@ function startDevHost({
2089
3797
  const filePath = resolveAppPathInternal(params.path);
2090
3798
  const encoding = params.encoding || "utf8";
2091
3799
  const content = params.content ?? "";
2092
- await fsp.mkdir(path5.dirname(filePath), { recursive: true });
3800
+ await fsp.mkdir(path6.dirname(filePath), { recursive: true });
2093
3801
  await fsp.writeFile(filePath, content, {
2094
3802
  encoding,
2095
3803
  mode: params.mode
@@ -2113,7 +3821,7 @@ function startDevHost({
2113
3821
  const entries = await fsp.readdir(dirPath, { withFileTypes: true });
2114
3822
  const results = [];
2115
3823
  for (const entry of entries) {
2116
- const entryPath = path5.join(dirPath, entry.name);
3824
+ const entryPath = path6.join(dirPath, entry.name);
2117
3825
  let size = 0;
2118
3826
  let type = "other";
2119
3827
  try {
@@ -2169,7 +3877,7 @@ function startDevHost({
2169
3877
  const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : basePath;
2170
3878
  const env = { ...process.env, ...params.env || {} };
2171
3879
  const useTty = Boolean(params.tty);
2172
- const child = spawn4(command2, args2, {
3880
+ const child = spawn5(command2, args2, {
2173
3881
  cwd,
2174
3882
  env,
2175
3883
  stdio: useTty ? "inherit" : ["pipe", "pipe", "pipe"]
@@ -2285,7 +3993,7 @@ function startDevHost({
2285
3993
  }
2286
3994
  const cwd = backendConfig.cwd ? resolveAppPathInternal(backendConfig.cwd) : basePath;
2287
3995
  const args2 = backendConfig.args || [];
2288
- const child = spawn4(backendConfig.command, args2, {
3996
+ const child = spawn5(backendConfig.command, args2, {
2289
3997
  cwd,
2290
3998
  env,
2291
3999
  stdio: ["ignore", "pipe", "pipe"]
@@ -2364,7 +4072,7 @@ function startDevHost({
2364
4072
  }
2365
4073
 
2366
4074
  // src/paths.ts
2367
- import path6 from "path";
4075
+ import path7 from "path";
2368
4076
  import { fileURLToPath } from "url";
2369
4077
  import { promises as fs3 } from "fs";
2370
4078
  async function exists(target) {
@@ -2376,20 +4084,20 @@ async function exists(target) {
2376
4084
  }
2377
4085
  }
2378
4086
  function resolveAppPath(input) {
2379
- const candidate = input ? path6.resolve(input) : process.cwd();
4087
+ const candidate = input ? path7.resolve(input) : process.cwd();
2380
4088
  return candidate;
2381
4089
  }
2382
4090
  async function findSchemaDir() {
2383
4091
  const envDir = process.env.AGENTCONNECT_SCHEMA_DIR;
2384
4092
  if (envDir && await exists(envDir)) return envDir;
2385
- const start = path6.dirname(fileURLToPath(import.meta.url));
4093
+ const start = path7.dirname(fileURLToPath(import.meta.url));
2386
4094
  const roots = [start, process.cwd()];
2387
4095
  for (const root of roots) {
2388
4096
  let current = root;
2389
4097
  for (let i = 0; i < 8; i += 1) {
2390
- const candidate = path6.join(current, "schemas");
4098
+ const candidate = path7.join(current, "schemas");
2391
4099
  if (await exists(candidate)) return candidate;
2392
- const parent = path6.dirname(current);
4100
+ const parent = path7.dirname(current);
2393
4101
  if (parent === current) break;
2394
4102
  current = parent;
2395
4103
  }
@@ -2399,13 +4107,13 @@ async function findSchemaDir() {
2399
4107
 
2400
4108
  // src/zip.ts
2401
4109
  import fs5 from "fs";
2402
- import path8 from "path";
4110
+ import path9 from "path";
2403
4111
  import { createHash } from "crypto";
2404
4112
  import yazl from "yazl";
2405
4113
  import yauzl from "yauzl";
2406
4114
 
2407
4115
  // src/fs-utils.ts
2408
- import path7 from "path";
4116
+ import path8 from "path";
2409
4117
  import { promises as fs4 } from "fs";
2410
4118
  async function collectFiles(root, options = {}) {
2411
4119
  const ignoreNames = new Set(options.ignoreNames || []);
@@ -2415,8 +4123,8 @@ async function collectFiles(root, options = {}) {
2415
4123
  const entries = await fs4.readdir(dir, { withFileTypes: true });
2416
4124
  for (const entry of entries) {
2417
4125
  if (ignoreNames.has(entry.name)) continue;
2418
- const fullPath = path7.join(dir, entry.name);
2419
- const rel = path7.relative(root, fullPath);
4126
+ const fullPath = path8.join(dir, entry.name);
4127
+ const rel = path8.relative(root, fullPath);
2420
4128
  if (ignorePaths.has(rel)) continue;
2421
4129
  if (entry.isDirectory()) {
2422
4130
  await walk(fullPath);
@@ -2433,7 +4141,7 @@ async function readJson(filePath) {
2433
4141
  return JSON.parse(raw);
2434
4142
  }
2435
4143
  async function writeJson(filePath, data) {
2436
- await fs4.mkdir(path7.dirname(filePath), { recursive: true });
4144
+ await fs4.mkdir(path8.dirname(filePath), { recursive: true });
2437
4145
  await fs4.writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
2438
4146
  }
2439
4147
  async function fileExists(filePath) {
@@ -2461,7 +4169,7 @@ async function zipDirectory({
2461
4169
  ignoreNames = [],
2462
4170
  ignorePaths = []
2463
4171
  }) {
2464
- await fs5.promises.mkdir(path8.dirname(outputPath), { recursive: true });
4172
+ await fs5.promises.mkdir(path9.dirname(outputPath), { recursive: true });
2465
4173
  const zipfile = new yazl.ZipFile();
2466
4174
  const files = await collectFiles(inputDir, { ignoreNames, ignorePaths });
2467
4175
  for (const file of files) {
@@ -2517,10 +4225,10 @@ async function readZipEntry(zipPath, entryName) {
2517
4225
  }
2518
4226
 
2519
4227
  // src/manifest.ts
2520
- import path9 from "path";
4228
+ import path10 from "path";
2521
4229
  import Ajv from "ajv";
2522
4230
  async function readManifestFromDir(appPath) {
2523
- const manifestPath = path9.join(appPath, "agentconnect.app.json");
4231
+ const manifestPath = path10.join(appPath, "agentconnect.app.json");
2524
4232
  if (!await fileExists(manifestPath)) {
2525
4233
  throw new Error("agentconnect.app.json not found in app directory.");
2526
4234
  }
@@ -2538,7 +4246,7 @@ async function loadManifestSchema() {
2538
4246
  if (!schemaDir) {
2539
4247
  throw new Error("Unable to locate schemas directory.");
2540
4248
  }
2541
- const schemaPath = path9.join(schemaDir, "app-manifest.json");
4249
+ const schemaPath = path10.join(schemaDir, "app-manifest.json");
2542
4250
  return readJson(schemaPath);
2543
4251
  }
2544
4252
  async function validateManifest(manifest) {
@@ -2550,9 +4258,9 @@ async function validateManifest(manifest) {
2550
4258
  }
2551
4259
 
2552
4260
  // src/registry.ts
2553
- import path10 from "path";
4261
+ import path11 from "path";
2554
4262
  import { promises as fs6 } from "fs";
2555
- function compareSemver(a, b) {
4263
+ function compareSemver4(a, b) {
2556
4264
  const parse = (v) => v.split("-")[0].split(".").map((n) => Number(n));
2557
4265
  const pa = parse(a);
2558
4266
  const pb = parse(b);
@@ -2574,26 +4282,26 @@ async function publishPackage({
2574
4282
  if (!appId || !version) {
2575
4283
  throw new Error("Manifest must include id and version.");
2576
4284
  }
2577
- const entryDir = path10.join(registryPath, "apps", appId, version);
4285
+ const entryDir = path11.join(registryPath, "apps", appId, version);
2578
4286
  await fs6.mkdir(entryDir, { recursive: true });
2579
- const targetZip = path10.join(entryDir, "app.zip");
4287
+ const targetZip = path11.join(entryDir, "app.zip");
2580
4288
  await fs6.copyFile(zipPath, targetZip);
2581
- const manifestPath = path10.join(entryDir, "manifest.json");
4289
+ const manifestPath = path11.join(entryDir, "manifest.json");
2582
4290
  await writeJson(manifestPath, resolvedManifest);
2583
4291
  let signatureOut = null;
2584
4292
  if (signaturePath) {
2585
- signatureOut = path10.join(entryDir, "signature.json");
4293
+ signatureOut = path11.join(entryDir, "signature.json");
2586
4294
  await fs6.copyFile(signaturePath, signatureOut);
2587
4295
  }
2588
4296
  const hash = await hashFile(zipPath);
2589
- const indexPath = path10.join(registryPath, "index.json");
4297
+ const indexPath = path11.join(registryPath, "index.json");
2590
4298
  const index = await fileExists(indexPath) ? await readJson(indexPath) : { apps: {} };
2591
4299
  if (!index.apps) index.apps = {};
2592
4300
  if (!index.apps[appId]) {
2593
4301
  index.apps[appId] = { latest: version, versions: {} };
2594
4302
  }
2595
4303
  index.apps[appId].versions[version] = {
2596
- path: path10.relative(registryPath, targetZip).replace(/\\/g, "/"),
4304
+ path: path11.relative(registryPath, targetZip).replace(/\\/g, "/"),
2597
4305
  manifest: resolvedManifest,
2598
4306
  signature: signatureOut ? {
2599
4307
  algorithm: "unknown",
@@ -2603,7 +4311,7 @@ async function publishPackage({
2603
4311
  hash
2604
4312
  };
2605
4313
  const currentLatest = index.apps[appId].latest;
2606
- if (!currentLatest || compareSemver(version, currentLatest) > 0) {
4314
+ if (!currentLatest || compareSemver4(version, currentLatest) > 0) {
2607
4315
  index.apps[appId].latest = version;
2608
4316
  }
2609
4317
  await writeJson(indexPath, index);
@@ -2611,9 +4319,9 @@ async function publishPackage({
2611
4319
  }
2612
4320
 
2613
4321
  // src/registry-validate.ts
2614
- import path11 from "path";
4322
+ import path12 from "path";
2615
4323
  import { createPublicKey, verify as verifySignature } from "crypto";
2616
- function compareSemver2(a, b) {
4324
+ function compareSemver5(a, b) {
2617
4325
  const parse = (v) => v.split("-")[0].split(".").map((n) => Number(n));
2618
4326
  const pa = parse(a);
2619
4327
  const pb = parse(b);
@@ -2625,7 +4333,7 @@ function compareSemver2(a, b) {
2625
4333
  }
2626
4334
  function resolveEntry(registryPath, entryPath) {
2627
4335
  if (!entryPath || typeof entryPath !== "string") return null;
2628
- return path11.resolve(registryPath, entryPath);
4336
+ return path12.resolve(registryPath, entryPath);
2629
4337
  }
2630
4338
  function normalizeSignatureAlg(signatureAlg) {
2631
4339
  const value = String(signatureAlg || "").toLowerCase();
@@ -2666,7 +4374,7 @@ async function validateRegistry({
2666
4374
  }) {
2667
4375
  const errors = [];
2668
4376
  const warnings = [];
2669
- const indexPath = path11.join(registryPath, "index.json");
4377
+ const indexPath = path12.join(registryPath, "index.json");
2670
4378
  if (!await fileExists(indexPath)) {
2671
4379
  return {
2672
4380
  valid: false,
@@ -2721,9 +4429,9 @@ async function validateRegistry({
2721
4429
  message: `App ${appId} latest (${latest}) not found in versions.`
2722
4430
  });
2723
4431
  }
2724
- const sorted = [...versionKeys].sort(compareSemver2);
4432
+ const sorted = [...versionKeys].sort(compareSemver5);
2725
4433
  const expectedLatest = sorted[sorted.length - 1];
2726
- if (latest && expectedLatest && compareSemver2(latest, expectedLatest) !== 0) {
4434
+ if (latest && expectedLatest && compareSemver5(latest, expectedLatest) !== 0) {
2727
4435
  warnings.push({
2728
4436
  path: `apps.${appId}`,
2729
4437
  message: `App ${appId} latest (${latest}) is not the newest (${expectedLatest}).`
@@ -2882,7 +4590,7 @@ async function main() {
2882
4590
  }
2883
4591
  if (command === "pack") {
2884
4592
  const appPath = resolveAppPath(getFlag("--app", "-a") ?? void 0);
2885
- const outPath = getFlag("--out", "-o") || path12.join(appPath, "dist", "app.zip");
4593
+ const outPath = getFlag("--out", "-o") || path13.join(appPath, "dist", "app.zip");
2886
4594
  const manifest = await readManifestFromDir(appPath);
2887
4595
  const validation = await validateManifest(manifest);
2888
4596
  if (!validation.valid) {
@@ -2892,7 +4600,7 @@ async function main() {
2892
4600
  }
2893
4601
  const ignoreNames = ["node_modules", ".git", ".DS_Store"];
2894
4602
  const ignorePaths = [];
2895
- const outputRel = path12.relative(appPath, outPath);
4603
+ const outputRel = path13.relative(appPath, outPath);
2896
4604
  if (!outputRel.startsWith("..") && outputRel !== "") {
2897
4605
  ignorePaths.push(outputRel);
2898
4606
  }
@@ -2950,7 +4658,7 @@ async function main() {
2950
4658
  console.error("Sign requires a zip file path.");
2951
4659
  return 1;
2952
4660
  }
2953
- const outPath = getFlag("--out", "-o") || path12.join(path12.dirname(appPath), "app.sig.json");
4661
+ const outPath = getFlag("--out", "-o") || path13.join(path13.dirname(appPath), "app.sig.json");
2954
4662
  const manifest = await readManifestFromZip(appPath);
2955
4663
  const hash = await hashFile(appPath);
2956
4664
  const hashBuffer = Buffer.from(hash, "hex");
@@ -2974,7 +4682,7 @@ async function main() {
2974
4682
  publicKey: publicKeyPem,
2975
4683
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
2976
4684
  };
2977
- await fs7.mkdir(path12.dirname(outPath), { recursive: true });
4685
+ await fs7.mkdir(path13.dirname(outPath), { recursive: true });
2978
4686
  await fs7.writeFile(outPath, JSON.stringify(signaturePayload, null, 2), "utf8");
2979
4687
  console.log(`Signature written to ${outPath}`);
2980
4688
  return 0;