@wibeco/bridge 0.2.18 → 0.2.19

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.
@@ -879,6 +879,11 @@ async function updatePresenceSession(source, sessionId, event, cwd = process.cwd
879
879
  }
880
880
  const metrics = metricsFromState(state, measuredAt);
881
881
  if (event.kind === "lifecycle.after") {
882
+ if (state.taskLinesAdded > 0 || state.taskLinesDeleted > 0 || state.taskPaths.length > 0) {
883
+ state.lastCompletedTaskLinesAdded = state.taskLinesAdded;
884
+ state.lastCompletedTaskLinesDeleted = state.taskLinesDeleted;
885
+ state.lastCompletedTaskPaths = state.taskPaths;
886
+ }
882
887
  state.taskBaselineLinesAdded = workingLinesAdded;
883
888
  state.taskBaselineLinesDeleted = workingLinesDeleted;
884
889
  state.taskLinesAdded = 0;
@@ -931,12 +936,24 @@ async function resolveLatestPresenceSession(source, cwd = process.cwd(), maxAgeM
931
936
  async function resolveLatestPresenceTask(source, cwd = process.cwd(), maxAgeMs = 5 * 6e4) {
932
937
  const state = await resolveLatestPresenceState(source, cwd, maxAgeMs);
933
938
  if (!state?.sessionId) return void 0;
934
- return {
935
- sessionId: state.sessionId,
936
- linesAdded: state.taskLinesAdded,
937
- linesDeleted: state.taskLinesDeleted,
938
- paths: state.taskPaths
939
- };
939
+ const liveHasMetrics = state.taskLinesAdded > 0 || state.taskLinesDeleted > 0 || state.taskPaths.length > 0;
940
+ if (liveHasMetrics) {
941
+ return {
942
+ sessionId: state.sessionId,
943
+ linesAdded: state.taskLinesAdded,
944
+ linesDeleted: state.taskLinesDeleted,
945
+ paths: state.taskPaths
946
+ };
947
+ }
948
+ if ((state.lastCompletedTaskLinesAdded ?? 0) > 0 || (state.lastCompletedTaskLinesDeleted ?? 0) > 0 || (state.lastCompletedTaskPaths?.length ?? 0) > 0) {
949
+ return {
950
+ sessionId: state.sessionId,
951
+ linesAdded: state.lastCompletedTaskLinesAdded ?? 0,
952
+ linesDeleted: state.lastCompletedTaskLinesDeleted ?? 0,
953
+ paths: state.lastCompletedTaskPaths ?? []
954
+ };
955
+ }
956
+ return void 0;
940
957
  }
941
958
  async function resolveLatestPresenceState(source, cwd, maxAgeMs) {
942
959
  let names;
@@ -991,6 +1008,15 @@ async function readPresenceState(path) {
991
1008
  taskPaths: Array.isArray(value.taskPaths) ? value.taskPaths.filter(
992
1009
  (path2) => typeof path2 === "string"
993
1010
  ) : [],
1011
+ lastCompletedTaskLinesAdded: optionalCount(
1012
+ value.lastCompletedTaskLinesAdded
1013
+ ),
1014
+ lastCompletedTaskLinesDeleted: optionalCount(
1015
+ value.lastCompletedTaskLinesDeleted
1016
+ ),
1017
+ lastCompletedTaskPaths: Array.isArray(value.lastCompletedTaskPaths) ? value.lastCompletedTaskPaths.filter(
1018
+ (path2) => typeof path2 === "string"
1019
+ ) : void 0,
994
1020
  paths: Array.isArray(value.paths) ? value.paths.filter((path2) => typeof path2 === "string") : [],
995
1021
  model: typeof value.model === "string" ? canonicalModelName(value.model) : void 0,
996
1022
  modelSeconds: canonicalModelSeconds(value.modelSeconds),
@@ -19,7 +19,7 @@ import {
19
19
  startPresenceSession,
20
20
  stopPresenceSession,
21
21
  updatePresenceSession
22
- } from "./chunk-HP5FH4VQ.js";
22
+ } from "./chunk-C5GHOVVI.js";
23
23
 
24
24
  // src/cli/commands.ts
25
25
  import { createHash } from "crypto";
@@ -302,12 +302,18 @@ async function setupCommand(requestedAdapter, options = {}) {
302
302
  }
303
303
  const projectConfigPath = join(cwd, ".wibe", "project.json");
304
304
  const existingProjectConfig = await readProjectConfig(projectConfigPath);
305
- if (existingProjectConfig && (existingProjectConfig.projectId !== options.projectId || existingProjectConfig.adapter !== adapter || existingProjectConfig.appUrl.replace(/\/$/, "") !== appUrl || existingProjectConfig.repository && expectedRepository && existingProjectConfig.repository !== expectedRepository)) {
306
- throw new Error(
307
- `Existing ${projectConfigPath} targets a different project, URL, adapter, or repository. It was not overwritten; remove it intentionally or rerun setup with matching options.`
305
+ if (existingProjectConfig) {
306
+ assertCompatibleProjectConfig(
307
+ existingProjectConfig,
308
+ {
309
+ projectId: options.projectId,
310
+ appUrl,
311
+ repository: expectedRepository
312
+ },
313
+ projectConfigPath
308
314
  );
309
315
  }
310
- const existingCredential = existingProjectConfig ? await loadCredential(cwd) : null;
316
+ const existingCredential = existingProjectConfig ? await loadCredential(cwd, adapter, { fallbackToPrimary: false }) : null;
311
317
  if (!options.reauthorize && existingCredential && existingCredential.projectId === options.projectId && existingCredential.appUrl.replace(/\/$/, "") === appUrl) {
312
318
  const installedNativeFiles2 = await installNativeConfigs(
313
319
  adapter,
@@ -317,6 +323,12 @@ async function setupCommand(requestedAdapter, options = {}) {
317
323
  options.projectId
318
324
  );
319
325
  await adoptPendingQueue(cwd, options.projectId);
326
+ await writeProjectConfigFile(projectConfigPath, existingProjectConfig, {
327
+ projectId: options.projectId,
328
+ appUrl,
329
+ adapter,
330
+ repository: expectedRepository ?? existingProjectConfig?.repository
331
+ });
320
332
  const heartbeat2 = await sendVerificationHeartbeat(
321
333
  existingCredential,
322
334
  adapter,
@@ -367,40 +379,17 @@ Confirm code ${authorization.user_code}
367
379
  repositoryId: token.repositoryId,
368
380
  deviceId: token.deviceId
369
381
  };
370
- await new SystemCredentialStore().set(
371
- "dev.wibe.bridge",
372
- token.projectId,
373
- JSON.stringify(credential)
382
+ await storeCredential(
383
+ credential,
384
+ adapter,
385
+ existingProjectConfig?.adapter ?? adapter
374
386
  );
375
- if (!existingProjectConfig) {
376
- await mkdir(join(cwd, ".wibe"), { recursive: true });
377
- await writeFile(
378
- projectConfigPath,
379
- `${JSON.stringify(
380
- {
381
- projectId: token.projectId,
382
- appUrl,
383
- adapter,
384
- ...expectedRepository ? { repository: expectedRepository } : {}
385
- },
386
- null,
387
- 2
388
- )}
389
- `,
390
- { mode: 384, flag: "wx" }
391
- );
392
- } else if (expectedRepository && !existingProjectConfig.repository) {
393
- await writeFile(
394
- projectConfigPath,
395
- `${JSON.stringify(
396
- { ...existingProjectConfig, repository: expectedRepository },
397
- null,
398
- 2
399
- )}
400
- `,
401
- { mode: 384 }
402
- );
403
- }
387
+ await writeProjectConfigFile(projectConfigPath, existingProjectConfig, {
388
+ projectId: token.projectId,
389
+ appUrl,
390
+ adapter,
391
+ repository: expectedRepository ?? existingProjectConfig?.repository
392
+ });
404
393
  const installedNativeFiles = await installNativeConfigs(
405
394
  adapter,
406
395
  source,
@@ -546,8 +535,18 @@ Install the Wibe GitHub App on ${repository}, then return here.
546
535
  }
547
536
  const projectConfigPath = join(cwd, ".wibe", "project.json");
548
537
  const existingProjectConfig = await readProjectConfig(projectConfigPath);
549
- const existingCredential = existingProjectConfig ? await loadCredential(cwd) : null;
550
- let credential = existingCredential;
538
+ if (existingProjectConfig) {
539
+ assertCompatibleProjectConfig(
540
+ existingProjectConfig,
541
+ {
542
+ projectId: snapshot.project_id,
543
+ appUrl,
544
+ repository
545
+ },
546
+ projectConfigPath
547
+ );
548
+ }
549
+ let credential = existingProjectConfig ? await loadCredential(cwd, adapter, { fallbackToPrimary: false }) : null;
551
550
  if (!credential || credential.projectId !== snapshot.project_id || credential.appUrl.replace(/\/$/, "") !== appUrl) {
552
551
  const minted = await mintOnboardingDevice({
553
552
  appUrl,
@@ -563,30 +562,18 @@ Install the Wibe GitHub App on ${repository}, then return here.
563
562
  repositoryId: minted.repositoryId,
564
563
  deviceId: minted.deviceId
565
564
  };
566
- await new SystemCredentialStore().set(
567
- "dev.wibe.bridge",
568
- minted.projectId,
569
- JSON.stringify(credential)
565
+ await storeCredential(
566
+ credential,
567
+ adapter,
568
+ existingProjectConfig?.adapter ?? adapter
570
569
  );
571
- if (!existingProjectConfig) {
572
- await mkdir(join(cwd, ".wibe"), { recursive: true });
573
- await writeFile(
574
- projectConfigPath,
575
- `${JSON.stringify(
576
- {
577
- projectId: minted.projectId,
578
- appUrl,
579
- adapter,
580
- repository
581
- },
582
- null,
583
- 2
584
- )}
585
- `,
586
- { mode: 384, flag: "wx" }
587
- );
588
- }
589
570
  }
571
+ await writeProjectConfigFile(projectConfigPath, existingProjectConfig, {
572
+ projectId: snapshot.project_id,
573
+ appUrl,
574
+ adapter,
575
+ repository
576
+ });
590
577
  const installedNativeFiles = await installNativeConfigs(
591
578
  adapter,
592
579
  source,
@@ -784,7 +771,7 @@ async function emitCommand(adapter, eventName, input) {
784
771
  const publishableEvents = [event, ...repositoryEvents].filter(
785
772
  (candidate) => eventTypeForHook(candidate)
786
773
  );
787
- const credential = await loadCredential(process.cwd());
774
+ const credential = await loadCredential(process.cwd(), adapter);
788
775
  await quarantineLegacyQueue();
789
776
  const queue = new JsonFileOfflineQueue(
790
777
  credential ? projectQueuePath(credential.projectId) : pendingQueuePath(repo?.root ?? process.cwd())
@@ -823,8 +810,9 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
823
810
  const projectConfig = await readProjectConfig(
824
811
  join(cwd, ".wibe", "project.json")
825
812
  );
826
- const credential = await loadCredential(cwd);
827
- if (!projectConfig || !credential) {
813
+ const adapter = projectConfig ? await detectAdapter(void 0, cwd).catch(() => projectConfig.adapter) : void 0;
814
+ const credential = await loadCredential(cwd, adapter);
815
+ if (!projectConfig || !credential || !adapter) {
828
816
  throw new Error("Wibe is not authorized in this repository. Run wibe setup first.");
829
817
  }
830
818
  await quarantineLegacyQueue();
@@ -855,10 +843,7 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
855
843
  throw new Error("Progress confidence must be between 0 and 1.");
856
844
  }
857
845
  const repo = await detectRepository(cwd);
858
- const taskMetrics = await resolveLatestPresenceTask(
859
- projectConfig.adapter,
860
- cwd
861
- );
846
+ const taskMetrics = await resolveLatestPresenceTask(adapter, cwd);
862
847
  const screenshot = options.screenshot?.trim() ? await uploadProgressScreenshot(
863
848
  options.screenshot.trim(),
864
849
  screenshotAlt,
@@ -866,7 +851,7 @@ async function shareProgressCommand(options, cwd = process.cwd()) {
866
851
  cwd
867
852
  ) : {};
868
853
  const event = createHookEvent({
869
- source: projectConfig.adapter,
854
+ source: adapter,
870
855
  kind: "progress.shared",
871
856
  ...taskMetrics?.sessionId ? { sessionId: taskMetrics.sessionId } : {},
872
857
  metadata: {
@@ -921,24 +906,101 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
921
906
  const credentialMatchesProjectConfig = Boolean(
922
907
  credential && projectConfig && credential.projectId === projectConfig.projectId && credential.appUrl.replace(/\/$/, "") === projectConfig.appUrl.replace(/\/$/, "")
923
908
  );
924
- let adapter;
909
+ let adapters = [];
925
910
  let adapterError;
926
- try {
927
- adapter = await detectAdapter(void 0, cwd);
928
- } catch (error) {
929
- adapterError = error instanceof Error ? error.message : String(error);
911
+ if (projectConfig) {
912
+ adapters = configuredAdapters(projectConfig);
913
+ } else {
914
+ try {
915
+ adapters = [await detectAdapter(void 0, cwd)];
916
+ } catch (error) {
917
+ adapterError = error instanceof Error ? error.message : String(error);
918
+ }
930
919
  }
931
- const heartbeat = credential && adapter && repositoryMatches && credentialMatchesProjectConfig ? await sendVerificationHeartbeat(credential, adapter, "doctor", cwd) : void 0;
932
- const nativeConfig = adapter ? await validateNativeConfigs(adapter, cwd, projectConfig?.projectId) : { hooks: false, mcp: false, activityRule: false, projectTrust: void 0 };
933
- const trustNotes = adapter === "codex" && nativeConfig.projectTrust === void 0 ? [
934
- "note Codex project trust could not be verified because the user config was not found; trust this project in Codex and rerun doctor"
935
- ] : [];
920
+ const nativeByAdapter = await Promise.all(
921
+ adapters.map(async (adapter) => ({
922
+ adapter,
923
+ native: await validateNativeConfigs(adapter, cwd, projectConfig?.projectId),
924
+ credential: await loadCredential(cwd, adapter, {
925
+ fallbackToPrimary: adapters.length <= 1
926
+ })
927
+ }))
928
+ );
929
+ const primaryAdapter = adapters[0];
930
+ const primaryNative = nativeByAdapter[0]?.native ?? {
931
+ hooks: false,
932
+ mcp: false,
933
+ activityRule: false,
934
+ projectTrust: void 0
935
+ };
936
+ const heartbeatResults = await Promise.all(
937
+ nativeByAdapter.map(async ({ adapter, credential: adapterCredential }) => {
938
+ if (!adapterCredential || !repositoryMatches || !credentialMatchesProjectConfig) {
939
+ return { adapter, heartbeat: void 0 };
940
+ }
941
+ return {
942
+ adapter,
943
+ heartbeat: await sendVerificationHeartbeat(
944
+ adapterCredential,
945
+ adapter,
946
+ "doctor",
947
+ cwd
948
+ )
949
+ };
950
+ })
951
+ );
952
+ const primaryHeartbeat = heartbeatResults[0]?.heartbeat;
953
+ const trustNotes = nativeByAdapter.flatMap(
954
+ ({ adapter, native }) => adapter === "codex" && native.projectTrust === void 0 ? [
955
+ "note Codex project trust could not be verified because the user config was not found; trust this project in Codex and rerun doctor"
956
+ ] : []
957
+ );
936
958
  const queueNotes = [
937
959
  `note project queue ${queuePath} contains ${queueBacklog ?? "unknown"} event(s)`,
938
960
  ...legacyQueue.pending ? [
939
961
  `note legacy shared queue is quarantined at ${legacyQueue.quarantined} and will not be delivered automatically`
940
962
  ] : []
941
963
  ];
964
+ const adapterChecks = adapters.length > 1 ? nativeByAdapter.flatMap(({ adapter, native, credential: adapterCredential }) => {
965
+ const heartbeat = heartbeatResults.find(
966
+ (item) => item.adapter === adapter
967
+ )?.heartbeat;
968
+ return [
969
+ [`${adapter} agent hooks configuration`, native.hooks],
970
+ [`${adapter} MCP endpoint configuration`, native.mcp],
971
+ [`${adapter} agent activity instructions`, native.activityRule],
972
+ ...adapter === "codex" ? native.projectTrust === void 0 ? [] : [
973
+ [
974
+ native.projectTrust ? `${adapter} Codex project trust` : `${adapter} Codex project trust (trust this project in Codex, then review Wibe hooks in /hooks)`,
975
+ native.projectTrust
976
+ ]
977
+ ] : [],
978
+ [
979
+ heartbeat?.error ? `${adapter} verification heartbeat (${heartbeat.error})` : `${adapter} verification heartbeat`,
980
+ Boolean(adapterCredential && heartbeat && !heartbeat.error)
981
+ ]
982
+ ];
983
+ }) : [
984
+ [
985
+ adapterError ? `adapter detection (${adapterError})` : "adapter detection",
986
+ Boolean(primaryAdapter)
987
+ ],
988
+ ["agent hooks configuration", primaryNative.hooks],
989
+ ["MCP endpoint configuration", primaryNative.mcp],
990
+ ["agent activity instructions", primaryNative.activityRule],
991
+ ...primaryAdapter === "codex" ? [
992
+ ...primaryNative.projectTrust === void 0 ? [] : [
993
+ [
994
+ primaryNative.projectTrust ? "Codex project trust" : "Codex project trust (trust this project in Codex, then review Wibe hooks in /hooks)",
995
+ primaryNative.projectTrust
996
+ ]
997
+ ]
998
+ ] : [],
999
+ [
1000
+ primaryHeartbeat?.error ? `verification heartbeat (${primaryHeartbeat.error})` : "verification heartbeat",
1001
+ Boolean(primaryHeartbeat && !primaryHeartbeat.error)
1002
+ ]
1003
+ ];
942
1004
  const checks = [
943
1005
  ["node", Number(process.versions.node.split(".")[0]) >= 20],
944
1006
  ["git repository", Boolean(repository)],
@@ -959,25 +1021,8 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
959
1021
  queueError ? `project queue (${queueError})` : "project queue",
960
1022
  queueError === void 0
961
1023
  ],
962
- [
963
- adapterError ? `adapter detection (${adapterError})` : "adapter detection",
964
- Boolean(adapter)
965
- ],
966
- ["agent hooks configuration", nativeConfig.hooks],
967
- ["MCP endpoint configuration", nativeConfig.mcp],
968
- ["agent activity instructions", nativeConfig.activityRule],
969
- ...adapter === "codex" ? [
970
- ...nativeConfig.projectTrust === void 0 ? [] : [
971
- [
972
- nativeConfig.projectTrust ? "Codex project trust" : "Codex project trust (trust this project in Codex, then review Wibe hooks in /hooks)",
973
- nativeConfig.projectTrust
974
- ]
975
- ]
976
- ] : [],
977
- [
978
- heartbeat?.error ? `verification heartbeat (${heartbeat.error})` : "verification heartbeat",
979
- Boolean(heartbeat && !heartbeat.error)
980
- ]
1024
+ ...adapters.length > 1 ? [["adapter detection", adapters.length > 0]] : [],
1025
+ ...adapterChecks
981
1026
  ];
982
1027
  const failures = checks.filter(([, okay]) => !okay).length;
983
1028
  return {
@@ -989,7 +1034,8 @@ async function doctorCommand(cwd = process.cwd(), requestedRepository) {
989
1034
  ].join("\n")
990
1035
  };
991
1036
  }
992
- async function loadCredential(cwd) {
1037
+ async function loadCredential(cwd, adapter, options = {}) {
1038
+ const fallbackToPrimary = options.fallbackToPrimary !== false;
993
1039
  if (process.env.WIBE_ACCESS_TOKEN && process.env.WIBE_PROJECT_ID && process.env.WIBE_ORGANIZATION_ID && process.env.WIBE_DEVICE_ID) {
994
1040
  const credential = {
995
1041
  appUrl: process.env.WIBE_APP_URL ?? "http://localhost:3000",
@@ -1003,18 +1049,31 @@ async function loadCredential(cwd) {
1003
1049
  if (!project || project.projectId !== credential.projectId || project.appUrl.replace(/\/$/, "") !== credential.appUrl.replace(/\/$/, "")) {
1004
1050
  return null;
1005
1051
  }
1052
+ if (adapter && adapter !== project.adapter && !fallbackToPrimary) {
1053
+ return null;
1054
+ }
1006
1055
  return credential;
1007
1056
  }
1008
1057
  try {
1009
1058
  const project = await readProjectConfig(join(cwd, ".wibe", "project.json"));
1010
1059
  if (!project) return null;
1011
- const stored = await new SystemCredentialStore().get(
1012
- "dev.wibe.bridge",
1013
- project.projectId
1060
+ const requested = adapter ?? project.adapter;
1061
+ const accounts = credentialAccounts(
1062
+ project.projectId,
1063
+ requested,
1064
+ project.adapter,
1065
+ fallbackToPrimary
1014
1066
  );
1015
- if (!stored) return null;
1016
- const credential = JSON.parse(stored);
1017
- return credential.projectId === project.projectId && credential.appUrl.replace(/\/$/, "") === project.appUrl.replace(/\/$/, "") ? credential : null;
1067
+ const store = new SystemCredentialStore();
1068
+ for (const account of accounts) {
1069
+ const stored = await store.get("dev.wibe.bridge", account);
1070
+ if (!stored) continue;
1071
+ const credential = JSON.parse(stored);
1072
+ if (credential.projectId === project.projectId && credential.appUrl.replace(/\/$/, "") === project.appUrl.replace(/\/$/, "")) {
1073
+ return credential;
1074
+ }
1075
+ }
1076
+ return null;
1018
1077
  } catch {
1019
1078
  return null;
1020
1079
  }
@@ -1369,24 +1428,44 @@ var REPOSITORY_SIGNALS = {
1369
1428
  };
1370
1429
  async function detectAdapter(explicitAdapter, cwd = process.cwd(), environment = process.env) {
1371
1430
  if (explicitAdapter) return explicitAdapter;
1431
+ const environmentAdapters = ADAPTERS.filter(
1432
+ (adapter) => ENVIRONMENT_SIGNALS[adapter].some(
1433
+ (name) => typeof environment[name] === "string" && environment[name] !== ""
1434
+ )
1435
+ );
1436
+ if (environmentAdapters.length === 1) return environmentAdapters[0];
1437
+ if (environmentAdapters.length > 1) {
1438
+ const details2 = environmentAdapters.map(
1439
+ (adapter) => `${adapter} (${ENVIRONMENT_SIGNALS[adapter].filter(
1440
+ (name) => typeof environment[name] === "string" && environment[name] !== ""
1441
+ ).map((name) => `environment ${name}`).join(", ")})`
1442
+ ).join("; ");
1443
+ throw new Error(
1444
+ `Adapter detection is ambiguous: ${details2}. Pass --adapter <${ADAPTERS.join("|")}> to choose explicitly.`
1445
+ );
1446
+ }
1372
1447
  const configuredProject = await readProjectConfig(join(cwd, ".wibe", "project.json"));
1373
- if (configuredProject) return configuredProject.adapter;
1448
+ if (configuredProject) {
1449
+ const adapters = configuredAdapters(configuredProject);
1450
+ if (adapters.length === 1) return adapters[0];
1451
+ throw new Error(
1452
+ `Adapter detection is ambiguous: ${adapters.join(", ")}. Pass --adapter <${ADAPTERS.join("|")}> to choose explicitly.`
1453
+ );
1454
+ }
1374
1455
  const candidates = /* @__PURE__ */ new Map();
1375
1456
  for (const adapter of ADAPTERS) {
1376
- const environmentMatches = ENVIRONMENT_SIGNALS[adapter].filter(
1377
- (name) => typeof environment[name] === "string" && environment[name] !== ""
1378
- );
1379
1457
  const repositoryMatches = (await Promise.all(
1380
1458
  REPOSITORY_SIGNALS[adapter].map(async (path) => ({
1381
1459
  path,
1382
1460
  found: await exists(join(cwd, path))
1383
1461
  }))
1384
1462
  )).filter(({ found }) => found).map(({ path }) => path);
1385
- const evidence = [
1386
- ...environmentMatches.map((name) => `environment ${name}`),
1387
- ...repositoryMatches.map((path) => `config ${path}`)
1388
- ];
1389
- if (evidence.length > 0) candidates.set(adapter, evidence);
1463
+ if (repositoryMatches.length > 0) {
1464
+ candidates.set(
1465
+ adapter,
1466
+ repositoryMatches.map((path) => `config ${path}`)
1467
+ );
1468
+ }
1390
1469
  }
1391
1470
  if (candidates.size === 1) return candidates.keys().next().value;
1392
1471
  if (candidates.size === 0) {
@@ -1399,6 +1478,61 @@ async function detectAdapter(explicitAdapter, cwd = process.cwd(), environment =
1399
1478
  `Adapter detection is ambiguous: ${details}. Pass --adapter <${ADAPTERS.join("|")}> to choose explicitly.`
1400
1479
  );
1401
1480
  }
1481
+ function configuredAdapters(project) {
1482
+ const listed = (project.adapters ?? []).filter(
1483
+ (adapter) => ADAPTERS.includes(adapter)
1484
+ );
1485
+ return [.../* @__PURE__ */ new Set([project.adapter, ...listed])];
1486
+ }
1487
+ function credentialAccount(projectId, adapter, primaryAdapter) {
1488
+ return adapter === primaryAdapter ? projectId : `${projectId}:${adapter}`;
1489
+ }
1490
+ function credentialAccounts(projectId, adapter, primaryAdapter, fallbackToPrimary) {
1491
+ const preferred = credentialAccount(projectId, adapter, primaryAdapter);
1492
+ if (!fallbackToPrimary || preferred === projectId) {
1493
+ return preferred === projectId ? [projectId, `${projectId}:${adapter}`] : [preferred];
1494
+ }
1495
+ return [.../* @__PURE__ */ new Set([preferred, projectId])];
1496
+ }
1497
+ async function storeCredential(credential, adapter, primaryAdapter) {
1498
+ await new SystemCredentialStore().set(
1499
+ "dev.wibe.bridge",
1500
+ credentialAccount(credential.projectId, adapter, primaryAdapter),
1501
+ JSON.stringify(credential)
1502
+ );
1503
+ }
1504
+ function projectConfigWithAdapter(existing, input) {
1505
+ const adapters = [
1506
+ .../* @__PURE__ */ new Set([
1507
+ ...existing ? configuredAdapters(existing) : [],
1508
+ input.adapter
1509
+ ])
1510
+ ];
1511
+ return {
1512
+ projectId: input.projectId,
1513
+ appUrl: input.appUrl,
1514
+ adapter: existing?.adapter ?? input.adapter,
1515
+ adapters,
1516
+ ...input.repository ? { repository: input.repository } : {}
1517
+ };
1518
+ }
1519
+ function assertCompatibleProjectConfig(existing, incoming, projectConfigPath) {
1520
+ if (existing.projectId !== incoming.projectId || existing.appUrl.replace(/\/$/, "") !== incoming.appUrl.replace(/\/$/, "") || existing.repository && incoming.repository && existing.repository !== incoming.repository) {
1521
+ throw new Error(
1522
+ `Existing ${projectConfigPath} targets a different project, URL, or repository. It was not overwritten; remove it intentionally or rerun setup with matching options.`
1523
+ );
1524
+ }
1525
+ }
1526
+ async function writeProjectConfigFile(projectConfigPath, existing, input) {
1527
+ const next = projectConfigWithAdapter(existing, input);
1528
+ await mkdir(resolve(projectConfigPath, ".."), { recursive: true });
1529
+ await writeFile(
1530
+ projectConfigPath,
1531
+ `${JSON.stringify(next, null, 2)}
1532
+ `,
1533
+ { mode: 384, ...existing ? {} : { flag: "wx" } }
1534
+ );
1535
+ }
1402
1536
  async function readProjectConfig(path) {
1403
1537
  if (!await exists(path)) return void 0;
1404
1538
  let parsed;
@@ -1418,6 +1552,14 @@ async function readProjectConfig(path) {
1418
1552
  if (parsed.repository) {
1419
1553
  parsed.repository = normalizeGitHubRepository(parsed.repository);
1420
1554
  }
1555
+ if (Array.isArray(parsed.adapters)) {
1556
+ parsed.adapters = parsed.adapters.filter(
1557
+ (adapter) => ADAPTERS.includes(adapter)
1558
+ );
1559
+ if (!parsed.adapters.length) delete parsed.adapters;
1560
+ } else {
1561
+ delete parsed.adapters;
1562
+ }
1421
1563
  return parsed;
1422
1564
  }
1423
1565
  async function assertExpectedRepository(cwd, expected) {
package/dist/cli.js CHANGED
@@ -7,10 +7,10 @@ import {
7
7
  setupCommand,
8
8
  shareProgressCommand,
9
9
  statusCommand
10
- } from "./chunk-UWS3QC3N.js";
10
+ } from "./chunk-E44G2C5V.js";
11
11
  import {
12
12
  runPresenceHeartbeat
13
- } from "./chunk-HP5FH4VQ.js";
13
+ } from "./chunk-C5GHOVVI.js";
14
14
 
15
15
  // src/cli.ts
16
16
  var HELP = `wibe-bridge <command>
@@ -21,7 +21,7 @@ Commands:
21
21
  Prints browser URLs for GitHub sign-in and GitHub App install.
22
22
  setup --project <uuid> [--adapter <cursor|claude-code|codex>] [--url <wibe-url>] [--repository <owner/repo>] [--reauthorize]
23
23
  Auto-detects one adapter from the environment or repository config.
24
- Use --adapter when multiple agent configs are present.
24
+ Run again with --adapter to connect an additional coding agent to the same project.
25
25
  Use --reauthorize only to replace a rejected or revoked device token.
26
26
  status
27
27
  emit --adapter <name> --event <hook-name> (JSON payload on stdin)
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  emitCommand
4
- } from "./chunk-UWS3QC3N.js";
5
- import "./chunk-HP5FH4VQ.js";
4
+ } from "./chunk-E44G2C5V.js";
5
+ import "./chunk-C5GHOVVI.js";
6
6
 
7
7
  // src/codex-hook.ts
8
8
  async function main() {
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  startPresenceSession,
35
35
  stopPresenceSession,
36
36
  updatePresenceSession
37
- } from "./chunk-HP5FH4VQ.js";
37
+ } from "./chunk-C5GHOVVI.js";
38
38
  export {
39
39
  JsonFileOfflineQueue,
40
40
  MemoryOfflineQueue,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wibeco/bridge",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
4
4
  "description": "Privacy-first live activity bridge for Cursor, Claude Code, and Codex.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,7 +8,7 @@
8
8
  - For text-only outcomes, use `wibe_share_progress` with phase `shipped` at completion. Publish earlier only with phase `blocked` when a material blocker leaves the task unfinished.
9
9
  - Write one self-contained paragraph of 20–55 words in past tense. Lead with what now works, then explain the user or team impact and meaningful validation. Mention important limitations when present.
10
10
  - Describe the outcome, not the agent’s process. Never include first-person narration, prompts, source code, secrets, raw commands, routine tool calls, filenames, line counts, praise, hype, or speculation; Wibe displays code and file changes separately.
11
- - When calling `wibe_share_progress`, pass `paths`, `lines_added`, and `lines_deleted` only for the current user task. Use the agent's per-task edit results; never pass the repository's full uncommitted diff. If exact task metrics are unavailable, omit them so Wibe can use hook-derived metrics instead of guessed totals.
11
+ - When calling `wibe_share_progress`, pass `session_id` when this conversation or thread has an identifier so the update attaches to this agent session rather than another agent on the same project. Pass `paths`, `lines_added`, and `lines_deleted` only for the current user task. Use the agent's per-task edit results; never pass the repository's full uncommitted diff. If exact task metrics are unavailable, omit them so Wibe can use hook-derived metrics instead of guessed totals.
12
12
  - This instruction is standing user authorization to publish the single completion update described here; do not ask for separate per-task approval.
13
13
  - If the MCP tool is unavailable, run `npx --yes --package @wibeco/bridge@latest wibe share-progress --summary "<summary>" --phase <shipped|blocked>`.
14
14
  - Do not silently skip reporting. If both mechanisms fail, state `Wibe: update failed` and the reason in the final response. Report a Wibe update as sent only after the tool or CLI confirms success.
@@ -8,7 +8,7 @@
8
8
  - For text-only outcomes, use `wibe_share_progress` with phase `shipped` at completion. Publish earlier only with phase `blocked` when a material blocker leaves the task unfinished.
9
9
  - Write one self-contained paragraph of 20–55 words in past tense. Lead with what now works, then explain the user or team impact and meaningful validation. Mention important limitations when present.
10
10
  - Describe the outcome, not the agent’s process. Never include first-person narration, prompts, source code, secrets, raw commands, routine tool calls, filenames, line counts, praise, hype, or speculation; Wibe displays code and file changes separately.
11
- - When calling `wibe_share_progress`, pass `paths`, `lines_added`, and `lines_deleted` only for the current user task. Use the agent's per-task edit results; never pass the repository's full uncommitted diff. If exact task metrics are unavailable, omit them so Wibe can use hook-derived metrics instead of guessed totals.
11
+ - When calling `wibe_share_progress`, pass `session_id` when this conversation or thread has an identifier so the update attaches to this agent session rather than another agent on the same project. Pass `paths`, `lines_added`, and `lines_deleted` only for the current user task. Use the agent's per-task edit results; never pass the repository's full uncommitted diff. If exact task metrics are unavailable, omit them so Wibe can use hook-derived metrics instead of guessed totals.
12
12
  - This instruction is standing user authorization to publish the single completion update described here; do not ask for separate per-task approval.
13
13
  - If the MCP tool is unavailable, run `npx --yes --package @wibeco/bridge@latest wibe share-progress --summary "<summary>" --phase <shipped|blocked>`.
14
14
  - Do not silently skip reporting. If both mechanisms fail, state `Wibe: update failed` and the reason in the final response. Report a Wibe update as sent only after the tool or CLI confirms success.
@@ -12,7 +12,7 @@ alwaysApply: true
12
12
  - For text-only outcomes, use `wibe_share_progress` with phase `shipped` at completion. Publish earlier only with phase `blocked` when a material blocker leaves the task unfinished.
13
13
  - Write one self-contained paragraph of 20–55 words in past tense. Lead with what now works, then explain the user or team impact and meaningful validation. Mention important limitations when present.
14
14
  - Describe the outcome, not the agent’s process. Never include first-person narration, prompts, source code, secrets, raw commands, routine tool calls, filenames, line counts, praise, hype, or speculation; Wibe displays code and file changes separately.
15
- - When calling `wibe_share_progress`, pass `paths`, `lines_added`, and `lines_deleted` only for the current user task. Use the agent's per-task edit results; never pass the repository's full uncommitted diff. If exact task metrics are unavailable, omit them so Wibe can use hook-derived metrics instead of guessed totals.
15
+ - When calling `wibe_share_progress`, pass `session_id` when this conversation or thread has an identifier so the update attaches to this agent session rather than another agent on the same project. Pass `paths`, `lines_added`, and `lines_deleted` only for the current user task. Use the agent's per-task edit results; never pass the repository's full uncommitted diff. If exact task metrics are unavailable, omit them so Wibe can use hook-derived metrics instead of guessed totals.
16
16
  - Bias frontend outcomes toward visual evidence without asking for per-task approval. For UI components, pages, styling, responsive behavior, interactions, and visual fixes, attach one screenshot when the app or preview is already runnable. Prefer a screenshot already made during visual QA; otherwise capture the clearest final state.
17
17
  - Frame screenshots around the feature, not the whole application. Use the browser snapshot to identify the smallest element that contains the changed component and the context needed to understand it, then call `browser_take_screenshot` with that element’s `ref` and a descriptive `element` name. Include the trigger with an open menu, popover, or dialog when practical. Use viewport or full-page screenshots only for page-wide work, and reject captures dominated by blank space.
18
18
  - Skip screenshots for backend, infrastructure, documentation, refactors, and non-visual frontend logic. Never launch or repair an app solely for Wibe, and omit the image when navigation needs manual authentication, the state contains sensitive data, visual validation is blocked, or the project has visual updates disabled.