@loucompanion/forge-bridge 0.1.1-dev.8dcbc6245e51 → 0.1.1-dev.9acab970e5d1

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.
Files changed (35) hide show
  1. package/dist/bridge/bridge.service.js +74 -9
  2. package/dist/bridge/bridge.types.d.ts +15 -0
  3. package/dist/cli/cli.service.d.ts +27 -2
  4. package/dist/cli/cli.service.js +406 -16
  5. package/dist/cli/local-config.d.ts +3 -0
  6. package/dist/cli/local-config.js +26 -3
  7. package/dist/session/index.d.ts +2 -0
  8. package/dist/session/index.js +1 -0
  9. package/dist/session/internals.d.ts +5 -3
  10. package/dist/session/internals.js +2 -1
  11. package/dist/session/phase-completion.d.ts +111 -0
  12. package/dist/session/phase-completion.js +2350 -0
  13. package/dist/session/provider-cli/index.d.ts +2 -2
  14. package/dist/session/provider-cli/index.js +1 -1
  15. package/dist/session/provider-cli/provider-cli.base.d.ts +2 -2
  16. package/dist/session/provider-cli/provider-cli.service.d.ts +48 -3
  17. package/dist/session/provider-cli/provider-cli.service.js +1870 -41
  18. package/dist/session/provider-cli/provider-cli.types.d.ts +2 -0
  19. package/dist/session/provider-cli/provider-usage.d.ts +25 -0
  20. package/dist/session/provider-cli/provider-usage.js +64 -0
  21. package/dist/session/qa-sidecar-validation.d.ts +3 -0
  22. package/dist/session/qa-sidecar-validation.js +44 -0
  23. package/dist/session/session.service.js +25 -11
  24. package/dist/session/session.types.d.ts +55 -0
  25. package/dist/session/session.types.js +119 -2
  26. package/dist/shared/cursor-agent/cursor-agent.launch.d.ts +9 -0
  27. package/dist/shared/cursor-agent/cursor-agent.launch.js +66 -0
  28. package/dist/version.d.ts +1 -1
  29. package/dist/version.js +1 -1
  30. package/dist/worktree/artifact-injector.d.ts +2 -1
  31. package/dist/worktree/artifact-injector.js +38 -9
  32. package/dist/worktree/worktree.service.d.ts +2 -0
  33. package/dist/worktree/worktree.service.js +111 -5
  34. package/dist/worktree/worktree.types.d.ts +2 -0
  35. package/package.json +1 -1
@@ -87,11 +87,20 @@ export class ForgeBridgeService {
87
87
  const normalized = this.normalizeDispatch(dispatch);
88
88
  const result = await this.sessions.runOnce(normalized, {
89
89
  emit: async (event) => {
90
- await this.invokeAck("DispatchEvent", {
91
- leaseId,
92
- fencingToken,
93
- event: redactSecrets(event, redactionSecrets)
94
- });
90
+ try {
91
+ await this.invokeAck("DispatchEvent", {
92
+ leaseId,
93
+ fencingToken,
94
+ event: redactSecrets(event, redactionSecrets)
95
+ });
96
+ }
97
+ catch (error) {
98
+ if (isDispatchNoLongerPending(error)) {
99
+ controller.abort(error instanceof Error ? error.message : "Dispatch is no longer pending.");
100
+ return;
101
+ }
102
+ throw error;
103
+ }
95
104
  }
96
105
  }, controller.signal);
97
106
  await this.sendResult(leaseId, fencingToken, redactSecrets(result, redactionSecrets));
@@ -123,6 +132,7 @@ export class ForgeBridgeService {
123
132
  const mapping = findRepoMapping(dispatch, this.config.repoMappings);
124
133
  const localPath = mapping?.localPath?.trim();
125
134
  const repoRoot = localPath ? path.dirname(path.resolve(localPath)) : this.config.defaultRepoRoot;
135
+ const qaLaunchTarget = normalizeQaAppLaunchTarget(mapping?.qaAppLaunchTarget) ?? dispatch.qaBrowser?.launchTarget ?? null;
126
136
  return {
127
137
  ...dispatch,
128
138
  repo: {
@@ -133,7 +143,13 @@ export class ForgeBridgeService {
133
143
  serverLocal: {
134
144
  repoRoot,
135
145
  cleanupWorktrees: this.config.cleanupWorktrees
136
- }
146
+ },
147
+ qaBrowser: dispatch.qaBrowser
148
+ ? {
149
+ ...dispatch.qaBrowser,
150
+ launchTarget: qaLaunchTarget
151
+ }
152
+ : dispatch.qaBrowser
137
153
  };
138
154
  }
139
155
  async sendResult(leaseId, fencingToken, result) {
@@ -142,7 +158,15 @@ export class ForgeBridgeService {
142
158
  fencingToken,
143
159
  result
144
160
  };
145
- await this.invokeAck("DispatchResult", payload);
161
+ try {
162
+ await this.invokeAck("DispatchResult", payload);
163
+ }
164
+ catch (error) {
165
+ if (isDispatchNoLongerPending(error)) {
166
+ return;
167
+ }
168
+ throw error;
169
+ }
146
170
  }
147
171
  startHeartbeat() {
148
172
  this.stopHeartbeat();
@@ -176,7 +200,10 @@ export class ForgeBridgeService {
176
200
  repoMappings: this.config.repoMappings,
177
201
  os: this.config.os,
178
202
  sandboxProfiles: this.config.sandboxProfiles,
179
- supportsApiSecretGrant: this.config.supportsApiSecretGrant
203
+ supportsApiSecretGrant: this.config.supportsApiSecretGrant,
204
+ supportsManagedWorktrees: this.config.supportsManagedWorktrees,
205
+ supportsManagedForgeArtifacts: this.config.supportsManagedForgeArtifacts,
206
+ supportsRunnerAuthoredPhaseArtifacts: this.config.supportsRunnerAuthoredPhaseArtifacts
180
207
  };
181
208
  }
182
209
  async invokeAck(methodName, payload) {
@@ -186,7 +213,7 @@ export class ForgeBridgeService {
186
213
  }
187
214
  const ack = await connection.invoke(methodName, payload);
188
215
  if (!ack.accepted) {
189
- throw new Error(ack.message ?? ack.code ?? `${methodName} callback was rejected.`);
216
+ throw new BridgeCallbackRejectedError(ack.code ?? null, ack.message ?? ack.code ?? `${methodName} callback was rejected.`);
190
217
  }
191
218
  return ack;
192
219
  }
@@ -204,6 +231,20 @@ export class ForgeBridgeService {
204
231
  }
205
232
  }
206
233
  }
234
+ class BridgeCallbackRejectedError extends Error {
235
+ code;
236
+ constructor(code, message) {
237
+ super(message);
238
+ this.code = code;
239
+ this.name = "BridgeCallbackRejectedError";
240
+ }
241
+ }
242
+ function isDispatchNoLongerPending(error) {
243
+ if (error instanceof BridgeCallbackRejectedError) {
244
+ return error.code === "bridge.dispatch_not_pending" || error.code === "bridge.stale_dispatch";
245
+ }
246
+ return error instanceof Error && error.message === "Dispatch is no longer pending.";
247
+ }
207
248
  export class SignalRBridgeConnectionFactory {
208
249
  create(config) {
209
250
  return new SignalRBridgeConnection(config);
@@ -270,6 +311,9 @@ export function defaultBridgeServiceConfig(overrides = {}) {
270
311
  os: process.platform,
271
312
  sandboxProfiles: ["default"],
272
313
  supportsApiSecretGrant: true,
314
+ supportsManagedWorktrees: true,
315
+ supportsManagedForgeArtifacts: true,
316
+ supportsRunnerAuthoredPhaseArtifacts: true,
273
317
  version: process.env.FORGE_BRIDGE_VERSION ?? forgeBridgeVersion,
274
318
  protocolVersion: 1,
275
319
  minServerVersion: null,
@@ -317,6 +361,27 @@ function findRepoMapping(dispatch, mappings) {
317
361
  (!!mapping.remoteUrl?.trim() && equalsIgnoreCase(mapping.remoteUrl, dispatch.repo.remoteUrl)));
318
362
  });
319
363
  }
364
+ function normalizeQaAppLaunchTarget(target) {
365
+ if (!target) {
366
+ return null;
367
+ }
368
+ const routes = [...new Set((target.routes ?? []).map((route) => route.trim()).filter(Boolean))];
369
+ const normalized = {
370
+ url: cleanString(target.url),
371
+ command: cleanString(target.command),
372
+ working_directory: cleanString(target.working_directory),
373
+ ready_url: cleanString(target.ready_url),
374
+ routes,
375
+ notes: cleanString(target.notes)
376
+ };
377
+ return normalized.url || normalized.command || normalized.ready_url || routes.length > 0
378
+ ? normalized
379
+ : null;
380
+ }
381
+ function cleanString(value) {
382
+ const clean = value?.trim();
383
+ return clean ? clean : null;
384
+ }
320
385
  function equalsIgnoreCase(left, right) {
321
386
  return !!left?.trim() && !!right?.trim() && left.trim().toLowerCase() === right.trim().toLowerCase();
322
387
  }
@@ -13,6 +13,9 @@ export interface BridgeServiceConfig {
13
13
  readonly os: string;
14
14
  readonly sandboxProfiles: readonly string[];
15
15
  readonly supportsApiSecretGrant: boolean;
16
+ readonly supportsManagedWorktrees: boolean;
17
+ readonly supportsManagedForgeArtifacts: boolean;
18
+ readonly supportsRunnerAuthoredPhaseArtifacts: boolean;
16
19
  readonly version: string;
17
20
  readonly protocolVersion: number;
18
21
  readonly minServerVersion?: string | null;
@@ -24,6 +27,15 @@ export interface BridgeRepoMapping {
24
27
  readonly name?: string | null;
25
28
  readonly remoteUrl?: string | null;
26
29
  readonly localPath?: string | null;
30
+ readonly qaAppLaunchTarget?: QaAppLaunchTarget | null;
31
+ }
32
+ export interface QaAppLaunchTarget {
33
+ readonly url?: string | null;
34
+ readonly command?: string | null;
35
+ readonly working_directory?: string | null;
36
+ readonly ready_url?: string | null;
37
+ readonly routes?: readonly string[] | null;
38
+ readonly notes?: string | null;
27
39
  }
28
40
  export interface BridgeProviderStatus {
29
41
  readonly signedIn?: boolean | null;
@@ -40,6 +52,9 @@ export interface BridgeCapabilities {
40
52
  readonly os?: string | null;
41
53
  readonly sandboxProfiles?: readonly string[] | null;
42
54
  readonly supportsApiSecretGrant?: boolean | null;
55
+ readonly supportsManagedWorktrees?: boolean | null;
56
+ readonly supportsManagedForgeArtifacts?: boolean | null;
57
+ readonly supportsRunnerAuthoredPhaseArtifacts?: boolean | null;
43
58
  }
44
59
  export interface BridgeHelloRequest {
45
60
  readonly version: string;
@@ -5,7 +5,8 @@ import type { CliIo, CliService } from "./cli.base.js";
5
5
  export declare class ForgeBridgeCliService implements CliService {
6
6
  private readonly sessions;
7
7
  private readonly bridgeFactory;
8
- constructor(sessions?: SessionService, bridgeFactory?: (config: BridgeServiceConfig) => BridgeService);
8
+ private readonly pairRunner;
9
+ constructor(sessions?: SessionService, bridgeFactory?: (config: BridgeServiceConfig) => BridgeService, pairRunner?: PairRunnerClient);
9
10
  run(argv: readonly string[], io?: CliIo): Promise<number>;
10
11
  }
11
12
  export declare function parseArgs(argv: readonly string[]): {
@@ -13,11 +14,18 @@ export declare function parseArgs(argv: readonly string[]): {
13
14
  readonly command: "run-once";
14
15
  readonly dispatchPath: string;
15
16
  readonly resultPath: string;
17
+ } | {
18
+ readonly ok: true;
19
+ readonly command: "mcp";
20
+ readonly argv: readonly string[];
16
21
  } | {
17
22
  readonly ok: true;
18
23
  readonly command: "connect";
19
24
  readonly config: BridgeServiceConfig;
20
- } | {
25
+ } | ({
26
+ readonly ok: true;
27
+ readonly command: "pair";
28
+ } & PairArgs) | {
21
29
  readonly ok: true;
22
30
  readonly command: "add-repo";
23
31
  readonly label: string;
@@ -41,5 +49,22 @@ export declare function parseArgs(argv: readonly string[]): {
41
49
  readonly ok: false;
42
50
  readonly error: string;
43
51
  };
52
+ interface PairArgs {
53
+ readonly serverUrl: string;
54
+ readonly code?: string;
55
+ readonly name: string;
56
+ readonly fingerprint: string;
57
+ readonly repoRoot?: string;
58
+ readonly cleanupWorktrees: boolean;
59
+ readonly cliProviders: readonly string[];
60
+ readonly projectLabels: readonly string[];
61
+ }
62
+ interface PairRunnerClaim {
63
+ readonly runnerId: string;
64
+ readonly deviceToken: string;
65
+ }
66
+ type PairRunnerClient = (args: PairArgs) => Promise<PairRunnerClaim>;
67
+ export declare function parseCodexModelCatalog(raw: string): string[];
68
+ export declare function parseCursorModelList(raw: string): string[];
44
69
  type CleanupMode = "stale" | "merged" | "all";
45
70
  export {};