@loucompanion/forge-bridge 0.1.1-dev.7d43998c4521 → 0.1.1-dev.895607f4402a
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/README.md +1 -0
- package/dist/bridge/bridge.service.js +84 -12
- package/dist/bridge/bridge.types.d.ts +16 -0
- package/dist/cli/cli.service.d.ts +27 -2
- package/dist/cli/cli.service.js +406 -16
- package/dist/cli/local-config.d.ts +4 -1
- package/dist/cli/local-config.js +60 -8
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +1 -0
- package/dist/session/internals.d.ts +5 -3
- package/dist/session/internals.js +2 -1
- package/dist/session/phase-completion.d.ts +111 -0
- package/dist/session/phase-completion.js +2350 -0
- package/dist/session/provider-cli/index.d.ts +2 -2
- package/dist/session/provider-cli/index.js +1 -1
- package/dist/session/provider-cli/provider-cli.base.d.ts +2 -2
- package/dist/session/provider-cli/provider-cli.service.d.ts +48 -3
- package/dist/session/provider-cli/provider-cli.service.js +1807 -48
- package/dist/session/provider-cli/provider-cli.types.d.ts +2 -0
- package/dist/session/provider-cli/provider-usage.d.ts +25 -0
- package/dist/session/provider-cli/provider-usage.js +64 -0
- package/dist/session/qa-sidecar-validation.d.ts +3 -0
- package/dist/session/qa-sidecar-validation.js +44 -0
- package/dist/session/session.service.js +57 -12
- package/dist/session/session.types.d.ts +66 -0
- package/dist/session/session.types.js +165 -2
- package/dist/shared/cursor-agent/cursor-agent.launch.d.ts +9 -0
- package/dist/shared/cursor-agent/cursor-agent.launch.js +66 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +12 -0
- package/dist/worktree/artifact-injector.js +133 -0
- package/dist/worktree/dependency-warmer.d.ts +32 -0
- package/dist/worktree/dependency-warmer.js +297 -0
- package/dist/worktree/index.d.ts +2 -0
- package/dist/worktree/index.js +1 -0
- package/dist/worktree/worktree.service.d.ts +4 -0
- package/dist/worktree/worktree.service.js +175 -11
- package/dist/worktree/worktree.types.d.ts +7 -1
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -31,6 +31,7 @@ The bridge can also read connection settings from environment variables:
|
|
|
31
31
|
- `FORGE_DEVICE_NAME`
|
|
32
32
|
- `FORGE_REPO_ROOT`
|
|
33
33
|
- `FORGE_CLI_PROVIDERS`
|
|
34
|
+
- `FORGE_CODEX_MODELS` / `FORGE_CURSOR_MODELS` (optional comma-separated model ids advertised in runner capabilities)
|
|
34
35
|
|
|
35
36
|
## Repo-local development
|
|
36
37
|
|
|
@@ -51,7 +51,7 @@ export class ForgeBridgeService {
|
|
|
51
51
|
attempt = 0;
|
|
52
52
|
await this.waitForClose(signal);
|
|
53
53
|
}
|
|
54
|
-
catch
|
|
54
|
+
catch {
|
|
55
55
|
if (this.stopped || signal?.aborted) {
|
|
56
56
|
break;
|
|
57
57
|
}
|
|
@@ -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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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();
|
|
@@ -168,7 +192,7 @@ export class ForgeBridgeService {
|
|
|
168
192
|
}
|
|
169
193
|
capabilities() {
|
|
170
194
|
return {
|
|
171
|
-
schemaVersion:
|
|
195
|
+
schemaVersion: 2,
|
|
172
196
|
activeSessionCount: this.activeSessions.size,
|
|
173
197
|
cliProviders: this.config.cliProviders,
|
|
174
198
|
providerStatuses: this.config.providerStatuses,
|
|
@@ -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
|
|
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);
|
|
@@ -264,12 +305,15 @@ export function defaultBridgeServiceConfig(overrides = {}) {
|
|
|
264
305
|
defaultRepoRoot: process.env.FORGE_REPO_ROOT ?? defaults.defaultRepoRoot,
|
|
265
306
|
cleanupWorktrees: process.env.FORGE_CLEANUP_WORKTREES !== "false" && defaults.cleanupWorktrees,
|
|
266
307
|
cliProviders,
|
|
267
|
-
providerStatuses:
|
|
308
|
+
providerStatuses: providerStatusesFor(cliProviders),
|
|
268
309
|
projectLabels: splitCsv(process.env.FORGE_PROJECT_LABELS ?? ""),
|
|
269
310
|
repoMappings: [],
|
|
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,
|
|
@@ -294,6 +338,13 @@ function normalizeServerUrl(serverUrl) {
|
|
|
294
338
|
function positiveFiniteNumber(value, fallback) {
|
|
295
339
|
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
296
340
|
}
|
|
341
|
+
function providerStatusesFor(providers) {
|
|
342
|
+
return Object.fromEntries(providers.map((provider) => [provider, { signedIn: true, version: "", models: configuredModelsFor(provider) }]));
|
|
343
|
+
}
|
|
344
|
+
function configuredModelsFor(provider) {
|
|
345
|
+
const envName = `FORGE_${provider.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase()}_MODELS`;
|
|
346
|
+
return splitCsv(process.env[envName] ?? "");
|
|
347
|
+
}
|
|
297
348
|
function splitCsv(value) {
|
|
298
349
|
return value
|
|
299
350
|
.split(",")
|
|
@@ -310,6 +361,27 @@ function findRepoMapping(dispatch, mappings) {
|
|
|
310
361
|
(!!mapping.remoteUrl?.trim() && equalsIgnoreCase(mapping.remoteUrl, dispatch.repo.remoteUrl)));
|
|
311
362
|
});
|
|
312
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
|
+
}
|
|
313
385
|
function equalsIgnoreCase(left, right) {
|
|
314
386
|
return !!left?.trim() && !!right?.trim() && left.trim().toLowerCase() === right.trim().toLowerCase();
|
|
315
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,10 +27,20 @@ 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;
|
|
30
42
|
readonly version?: string | null;
|
|
43
|
+
readonly models?: readonly string[] | null;
|
|
31
44
|
}
|
|
32
45
|
export interface BridgeCapabilities {
|
|
33
46
|
readonly schemaVersion?: number | null;
|
|
@@ -39,6 +52,9 @@ export interface BridgeCapabilities {
|
|
|
39
52
|
readonly os?: string | null;
|
|
40
53
|
readonly sandboxProfiles?: readonly string[] | null;
|
|
41
54
|
readonly supportsApiSecretGrant?: boolean | null;
|
|
55
|
+
readonly supportsManagedWorktrees?: boolean | null;
|
|
56
|
+
readonly supportsManagedForgeArtifacts?: boolean | null;
|
|
57
|
+
readonly supportsRunnerAuthoredPhaseArtifacts?: boolean | null;
|
|
42
58
|
}
|
|
43
59
|
export interface BridgeHelloRequest {
|
|
44
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
|
-
|
|
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 {};
|