@loucompanion/forge-bridge 0.1.1-dev.32d4dd5874ee → 0.1.1-dev.440026b50fad
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/bridge/bridge.service.d.ts +1 -0
- package/dist/bridge/bridge.service.js +213 -25
- package/dist/bridge/bridge.types.d.ts +13 -2
- package/dist/cli/cli.service.js +225 -10
- package/dist/cli/local-config.d.ts +1 -0
- package/dist/cli/local-config.js +36 -4
- package/dist/config.js +1 -1
- package/dist/session/internals.d.ts +1 -1
- package/dist/session/phase-completion.d.ts +76 -4
- package/dist/session/phase-completion.js +2596 -100
- package/dist/session/provider-cli/provider-cli.base.d.ts +4 -3
- package/dist/session/provider-cli/provider-cli.service.d.ts +21 -5
- package/dist/session/provider-cli/provider-cli.service.js +2195 -222
- package/dist/session/provider-cli/provider-cli.types.d.ts +5 -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 +53 -15
- package/dist/session/session.types.d.ts +130 -0
- package/dist/session/session.types.js +261 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +1 -0
- package/dist/worktree/artifact-injector.js +117 -4
- package/dist/worktree/worktree.service.d.ts +1 -0
- package/dist/worktree/worktree.service.js +132 -6
- package/dist/worktree/worktree.types.d.ts +8 -1
- package/package.json +8 -3
|
@@ -15,6 +15,7 @@ export declare class ForgeBridgeService implements BridgeService {
|
|
|
15
15
|
run(signal?: AbortSignal): Promise<void>;
|
|
16
16
|
stop(): Promise<void>;
|
|
17
17
|
private handleDispatch;
|
|
18
|
+
private parseDispatchOrReport;
|
|
18
19
|
private handleCancel;
|
|
19
20
|
private abortActiveSessions;
|
|
20
21
|
private normalizeDispatch;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HubConnectionBuilder, LogLevel } from "@microsoft/signalr";
|
|
2
|
+
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { defaultForgeBridgeConfig } from "../config.js";
|
|
4
5
|
import { redactSecrets, redactText } from "../shared/redaction/redaction.js";
|
|
@@ -48,8 +49,14 @@ export class ForgeBridgeService {
|
|
|
48
49
|
while (!this.stopped && !signal?.aborted) {
|
|
49
50
|
try {
|
|
50
51
|
await this.connectOnce();
|
|
52
|
+
if (attempt > 0) {
|
|
53
|
+
logBridgeStatus("Connected to Forge. Waiting for dispatches...");
|
|
54
|
+
}
|
|
51
55
|
attempt = 0;
|
|
52
56
|
await this.waitForClose(signal);
|
|
57
|
+
if (!this.stopped && !signal?.aborted) {
|
|
58
|
+
logBridgeStatus("Connection closed. Reconnecting...");
|
|
59
|
+
}
|
|
53
60
|
}
|
|
54
61
|
catch (error) {
|
|
55
62
|
if (this.stopped || signal?.aborted) {
|
|
@@ -57,6 +64,7 @@ export class ForgeBridgeService {
|
|
|
57
64
|
}
|
|
58
65
|
const backoff = this.config.reconnectBackoffMs[Math.min(attempt, this.config.reconnectBackoffMs.length - 1)] ?? 5000;
|
|
59
66
|
attempt += 1;
|
|
67
|
+
logBridgeStatus(`Connect attempt ${attempt} failed: ${errorMessage(error)}. Retrying in ${formatDelay(backoff)}...`);
|
|
60
68
|
await delay(backoff, signal);
|
|
61
69
|
}
|
|
62
70
|
}
|
|
@@ -73,10 +81,14 @@ export class ForgeBridgeService {
|
|
|
73
81
|
this.abortActiveSessions("bridge stopping");
|
|
74
82
|
await this.stopConnection();
|
|
75
83
|
}
|
|
76
|
-
async handleDispatch(
|
|
84
|
+
async handleDispatch(rawDispatch) {
|
|
85
|
+
const dispatch = this.parseDispatchOrReport(rawDispatch);
|
|
86
|
+
if (!dispatch) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
77
89
|
const leaseId = dispatch.lease.id;
|
|
78
90
|
const fencingToken = dispatch.lease.fencingToken;
|
|
79
|
-
const redactionSecrets = exactSecrets(dispatch);
|
|
91
|
+
const redactionSecrets = exactSecrets(dispatch, this.config);
|
|
80
92
|
if (this.activeSessions.has(leaseId)) {
|
|
81
93
|
await this.sendResult(leaseId, fencingToken, failureResult("Dispatch is already active on this bridge.")).catch(() => undefined);
|
|
82
94
|
return;
|
|
@@ -87,11 +99,20 @@ export class ForgeBridgeService {
|
|
|
87
99
|
const normalized = this.normalizeDispatch(dispatch);
|
|
88
100
|
const result = await this.sessions.runOnce(normalized, {
|
|
89
101
|
emit: async (event) => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
try {
|
|
103
|
+
await this.invokeAck("DispatchEvent", {
|
|
104
|
+
leaseId,
|
|
105
|
+
fencingToken,
|
|
106
|
+
event: redactSecrets(event, redactionSecrets)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (isDispatchNoLongerPending(error)) {
|
|
111
|
+
controller.abort(error instanceof Error ? error.message : "Dispatch is no longer pending.");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
95
116
|
}
|
|
96
117
|
}, controller.signal);
|
|
97
118
|
await this.sendResult(leaseId, fencingToken, redactSecrets(result, redactionSecrets));
|
|
@@ -106,6 +127,27 @@ export class ForgeBridgeService {
|
|
|
106
127
|
this.activeSessions.delete(leaseId);
|
|
107
128
|
}
|
|
108
129
|
}
|
|
130
|
+
parseDispatchOrReport(rawDispatch) {
|
|
131
|
+
try {
|
|
132
|
+
return parseRunOnceDispatchPacket(rawDispatch);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
const identifiers = readDispatchIdentifiers(rawDispatch);
|
|
136
|
+
if (!identifiers) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const summary = invalidDispatchSummary(error);
|
|
140
|
+
void this.sendResult(identifiers.leaseId, identifiers.fencingToken, failureResult(summary, [
|
|
141
|
+
{
|
|
142
|
+
role: "runner",
|
|
143
|
+
kind: "error",
|
|
144
|
+
content: summary,
|
|
145
|
+
eventType: "invalid_dispatch"
|
|
146
|
+
}
|
|
147
|
+
])).catch(() => undefined);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
109
151
|
async handleCancel(request) {
|
|
110
152
|
const active = this.activeSessions.get(request.leaseId);
|
|
111
153
|
if (!active || active.fencingToken !== request.fencingToken) {
|
|
@@ -120,9 +162,10 @@ export class ForgeBridgeService {
|
|
|
120
162
|
this.activeSessions.clear();
|
|
121
163
|
}
|
|
122
164
|
normalizeDispatch(dispatch) {
|
|
123
|
-
const mapping = findRepoMapping(dispatch, this.config
|
|
165
|
+
const mapping = findRepoMapping(dispatch, repoMappingsFor(this.config));
|
|
124
166
|
const localPath = mapping?.localPath?.trim();
|
|
125
167
|
const repoRoot = localPath ? path.dirname(path.resolve(localPath)) : this.config.defaultRepoRoot;
|
|
168
|
+
const qaLaunchTarget = normalizeQaAppLaunchTarget(mapping?.qaAppLaunchTarget) ?? dispatch.qaBrowser?.launchTarget ?? null;
|
|
126
169
|
return {
|
|
127
170
|
...dispatch,
|
|
128
171
|
repo: {
|
|
@@ -133,7 +176,13 @@ export class ForgeBridgeService {
|
|
|
133
176
|
serverLocal: {
|
|
134
177
|
repoRoot,
|
|
135
178
|
cleanupWorktrees: this.config.cleanupWorktrees
|
|
136
|
-
}
|
|
179
|
+
},
|
|
180
|
+
qaBrowser: dispatch.qaBrowser
|
|
181
|
+
? {
|
|
182
|
+
...dispatch.qaBrowser,
|
|
183
|
+
launchTarget: qaLaunchTarget
|
|
184
|
+
}
|
|
185
|
+
: dispatch.qaBrowser
|
|
137
186
|
};
|
|
138
187
|
}
|
|
139
188
|
async sendResult(leaseId, fencingToken, result) {
|
|
@@ -142,7 +191,15 @@ export class ForgeBridgeService {
|
|
|
142
191
|
fencingToken,
|
|
143
192
|
result
|
|
144
193
|
};
|
|
145
|
-
|
|
194
|
+
try {
|
|
195
|
+
await this.invokeAck("DispatchResult", payload);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (isDispatchNoLongerPending(error)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
146
203
|
}
|
|
147
204
|
startHeartbeat() {
|
|
148
205
|
this.stopHeartbeat();
|
|
@@ -173,13 +230,14 @@ export class ForgeBridgeService {
|
|
|
173
230
|
cliProviders: this.config.cliProviders,
|
|
174
231
|
providerStatuses: this.config.providerStatuses,
|
|
175
232
|
projectLabels: this.config.projectLabels,
|
|
176
|
-
repoMappings: this.config
|
|
233
|
+
repoMappings: repoMappingsFor(this.config),
|
|
177
234
|
os: this.config.os,
|
|
178
235
|
sandboxProfiles: this.config.sandboxProfiles,
|
|
179
236
|
supportsApiSecretGrant: this.config.supportsApiSecretGrant,
|
|
180
237
|
supportsManagedWorktrees: this.config.supportsManagedWorktrees,
|
|
181
238
|
supportsManagedForgeArtifacts: this.config.supportsManagedForgeArtifacts,
|
|
182
|
-
supportsRunnerAuthoredPhaseArtifacts: this.config.supportsRunnerAuthoredPhaseArtifacts
|
|
239
|
+
supportsRunnerAuthoredPhaseArtifacts: this.config.supportsRunnerAuthoredPhaseArtifacts,
|
|
240
|
+
supportsDraftRunOnce: this.config.supportsDraftRunOnce
|
|
183
241
|
};
|
|
184
242
|
}
|
|
185
243
|
async invokeAck(methodName, payload) {
|
|
@@ -189,7 +247,7 @@ export class ForgeBridgeService {
|
|
|
189
247
|
}
|
|
190
248
|
const ack = await connection.invoke(methodName, payload);
|
|
191
249
|
if (!ack.accepted) {
|
|
192
|
-
throw new
|
|
250
|
+
throw new BridgeCallbackRejectedError(ack.code ?? null, ack.message ?? ack.code ?? `${methodName} callback was rejected.`);
|
|
193
251
|
}
|
|
194
252
|
return ack;
|
|
195
253
|
}
|
|
@@ -207,6 +265,20 @@ export class ForgeBridgeService {
|
|
|
207
265
|
}
|
|
208
266
|
}
|
|
209
267
|
}
|
|
268
|
+
class BridgeCallbackRejectedError extends Error {
|
|
269
|
+
code;
|
|
270
|
+
constructor(code, message) {
|
|
271
|
+
super(message);
|
|
272
|
+
this.code = code;
|
|
273
|
+
this.name = "BridgeCallbackRejectedError";
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function isDispatchNoLongerPending(error) {
|
|
277
|
+
if (error instanceof BridgeCallbackRejectedError) {
|
|
278
|
+
return error.code === "bridge.dispatch_not_pending" || error.code === "bridge.stale_dispatch";
|
|
279
|
+
}
|
|
280
|
+
return error instanceof Error && error.message === "Dispatch is no longer pending.";
|
|
281
|
+
}
|
|
210
282
|
export class SignalRBridgeConnectionFactory {
|
|
211
283
|
create(config) {
|
|
212
284
|
return new SignalRBridgeConnection(config);
|
|
@@ -237,14 +309,7 @@ export class SignalRBridgeConnection {
|
|
|
237
309
|
}
|
|
238
310
|
onDispatch(handler) {
|
|
239
311
|
this.connection.on("Dispatch", (raw) => {
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
dispatch = parseRunOnceDispatchPacket(raw);
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
void handler(dispatch).catch(() => undefined);
|
|
312
|
+
void handler(raw).catch(() => undefined);
|
|
248
313
|
});
|
|
249
314
|
}
|
|
250
315
|
onCancel(handler) {
|
|
@@ -271,11 +336,12 @@ export function defaultBridgeServiceConfig(overrides = {}) {
|
|
|
271
336
|
projectLabels: splitCsv(process.env.FORGE_PROJECT_LABELS ?? ""),
|
|
272
337
|
repoMappings: [],
|
|
273
338
|
os: process.platform,
|
|
274
|
-
sandboxProfiles:
|
|
339
|
+
sandboxProfiles: overrides.sandboxProfiles,
|
|
275
340
|
supportsApiSecretGrant: true,
|
|
276
341
|
supportsManagedWorktrees: true,
|
|
277
342
|
supportsManagedForgeArtifacts: true,
|
|
278
343
|
supportsRunnerAuthoredPhaseArtifacts: true,
|
|
344
|
+
supportsDraftRunOnce: true,
|
|
279
345
|
version: process.env.FORGE_BRIDGE_VERSION ?? forgeBridgeVersion,
|
|
280
346
|
protocolVersion: 1,
|
|
281
347
|
minServerVersion: null,
|
|
@@ -285,6 +351,7 @@ export function defaultBridgeServiceConfig(overrides = {}) {
|
|
|
285
351
|
};
|
|
286
352
|
return {
|
|
287
353
|
...config,
|
|
354
|
+
sandboxProfiles: config.sandboxProfiles ?? defaultSandboxProfiles(config.cliProviders),
|
|
288
355
|
heartbeatIntervalMs: positiveFiniteNumber(config.heartbeatIntervalMs, 30_000)
|
|
289
356
|
};
|
|
290
357
|
}
|
|
@@ -303,6 +370,14 @@ function positiveFiniteNumber(value, fallback) {
|
|
|
303
370
|
function providerStatusesFor(providers) {
|
|
304
371
|
return Object.fromEntries(providers.map((provider) => [provider, { signedIn: true, version: "", models: configuredModelsFor(provider) }]));
|
|
305
372
|
}
|
|
373
|
+
function defaultSandboxProfiles(providers, platform = process.platform) {
|
|
374
|
+
if (!providers.some((provider) => provider.toLowerCase() === "codex")) {
|
|
375
|
+
return ["none"];
|
|
376
|
+
}
|
|
377
|
+
return platform === "win32"
|
|
378
|
+
? ["danger-full-access"]
|
|
379
|
+
: ["provider-workspace-write"];
|
|
380
|
+
}
|
|
306
381
|
function configuredModelsFor(provider) {
|
|
307
382
|
const envName = `FORGE_${provider.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase()}_MODELS`;
|
|
308
383
|
return splitCsv(process.env[envName] ?? "");
|
|
@@ -313,6 +388,62 @@ function splitCsv(value) {
|
|
|
313
388
|
.map((entry) => entry.trim())
|
|
314
389
|
.filter((entry) => entry.length > 0);
|
|
315
390
|
}
|
|
391
|
+
function repoMappingsFor(config) {
|
|
392
|
+
const explicit = [...config.repoMappings];
|
|
393
|
+
const discovered = discoverRepoRootMappings(config.defaultRepoRoot);
|
|
394
|
+
if (explicit.length === 0) {
|
|
395
|
+
return discovered;
|
|
396
|
+
}
|
|
397
|
+
const explicitKeys = new Set(explicit.flatMap(mappingKeys));
|
|
398
|
+
return [
|
|
399
|
+
...explicit,
|
|
400
|
+
...discovered.filter((mapping) => !mappingKeys(mapping).some((key) => explicitKeys.has(key)))
|
|
401
|
+
];
|
|
402
|
+
}
|
|
403
|
+
function discoverRepoRootMappings(defaultRepoRoot) {
|
|
404
|
+
const root = defaultRepoRoot.trim();
|
|
405
|
+
if (!root) {
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
const resolvedRoot = path.resolve(root);
|
|
409
|
+
const mappings = [];
|
|
410
|
+
if (isGitCheckout(resolvedRoot)) {
|
|
411
|
+
mappings.push({ name: path.basename(resolvedRoot), localPath: resolvedRoot });
|
|
412
|
+
}
|
|
413
|
+
let entries;
|
|
414
|
+
try {
|
|
415
|
+
entries = fs.readdirSync(resolvedRoot, { withFileTypes: true });
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
return mappings;
|
|
419
|
+
}
|
|
420
|
+
for (const entry of entries) {
|
|
421
|
+
if (!entry.isDirectory()) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const localPath = path.join(resolvedRoot, entry.name);
|
|
425
|
+
if (isGitCheckout(localPath)) {
|
|
426
|
+
mappings.push({ name: entry.name, localPath });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return mappings.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
430
|
+
}
|
|
431
|
+
function isGitCheckout(localPath) {
|
|
432
|
+
try {
|
|
433
|
+
return fs.existsSync(path.join(localPath, ".git"));
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function mappingKeys(mapping) {
|
|
440
|
+
return [
|
|
441
|
+
mapping.repoId ? `id:${mapping.repoId.toLowerCase()}` : null,
|
|
442
|
+
mapping.name ? `name:${mapping.name.trim().toLowerCase()}` : null,
|
|
443
|
+
mapping.remoteUrl ? `remote:${mapping.remoteUrl.trim().toLowerCase()}` : null,
|
|
444
|
+
mapping.localPath ? `path:${path.resolve(mapping.localPath).toLowerCase()}` : null
|
|
445
|
+
].filter((key) => Boolean(key));
|
|
446
|
+
}
|
|
316
447
|
function findRepoMapping(dispatch, mappings) {
|
|
317
448
|
return mappings.find((mapping) => {
|
|
318
449
|
if (!mapping.localPath?.trim()) {
|
|
@@ -323,16 +454,70 @@ function findRepoMapping(dispatch, mappings) {
|
|
|
323
454
|
(!!mapping.remoteUrl?.trim() && equalsIgnoreCase(mapping.remoteUrl, dispatch.repo.remoteUrl)));
|
|
324
455
|
});
|
|
325
456
|
}
|
|
457
|
+
function normalizeQaAppLaunchTarget(target) {
|
|
458
|
+
if (!target) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
const routes = [...new Set((target.routes ?? []).map((route) => route.trim()).filter(Boolean))];
|
|
462
|
+
const normalized = {
|
|
463
|
+
url: cleanString(target.url),
|
|
464
|
+
command: cleanString(target.command),
|
|
465
|
+
working_directory: cleanString(target.working_directory),
|
|
466
|
+
ready_url: cleanString(target.ready_url),
|
|
467
|
+
routes,
|
|
468
|
+
notes: cleanString(target.notes)
|
|
469
|
+
};
|
|
470
|
+
return normalized.url || normalized.command || normalized.ready_url || routes.length > 0
|
|
471
|
+
? normalized
|
|
472
|
+
: null;
|
|
473
|
+
}
|
|
474
|
+
function cleanString(value) {
|
|
475
|
+
const clean = value?.trim();
|
|
476
|
+
return clean ? clean : null;
|
|
477
|
+
}
|
|
326
478
|
function equalsIgnoreCase(left, right) {
|
|
327
479
|
return !!left?.trim() && !!right?.trim() && left.trim().toLowerCase() === right.trim().toLowerCase();
|
|
328
480
|
}
|
|
481
|
+
function readDispatchIdentifiers(rawDispatch) {
|
|
482
|
+
if (!rawDispatch || typeof rawDispatch !== "object" || Array.isArray(rawDispatch)) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
const packet = rawDispatch;
|
|
486
|
+
const rawLease = packet.lease ?? packet.Lease;
|
|
487
|
+
if (!rawLease || typeof rawLease !== "object" || Array.isArray(rawLease)) {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
const lease = rawLease;
|
|
491
|
+
const leaseId = readString(lease, "id", "Id");
|
|
492
|
+
const fencingToken = readString(lease, "fencingToken", "FencingToken");
|
|
493
|
+
return leaseId && fencingToken ? { leaseId, fencingToken } : null;
|
|
494
|
+
}
|
|
495
|
+
function readString(source, camelKey, pascalKey) {
|
|
496
|
+
const value = source[camelKey] ?? source[pascalKey];
|
|
497
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
498
|
+
}
|
|
499
|
+
function invalidDispatchSummary(error) {
|
|
500
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
501
|
+
return `Invalid bridge dispatch packet: ${detail}`;
|
|
502
|
+
}
|
|
503
|
+
function errorMessage(error) {
|
|
504
|
+
return error instanceof Error ? error.message : String(error);
|
|
505
|
+
}
|
|
506
|
+
function formatDelay(ms) {
|
|
507
|
+
if (ms >= 1000 && ms % 1000 === 0) {
|
|
508
|
+
return `${ms / 1000}s`;
|
|
509
|
+
}
|
|
510
|
+
return `${ms}ms`;
|
|
511
|
+
}
|
|
512
|
+
function logBridgeStatus(message) {
|
|
513
|
+
process.stderr.write(`[${new Date().toISOString()}] ${message}\n`);
|
|
514
|
+
}
|
|
329
515
|
function delay(ms, signal) {
|
|
330
516
|
if (signal?.aborted) {
|
|
331
517
|
return Promise.resolve();
|
|
332
518
|
}
|
|
333
519
|
return new Promise((resolve) => {
|
|
334
520
|
const timeout = setTimeout(resolve, ms);
|
|
335
|
-
timeout.unref?.();
|
|
336
521
|
signal?.addEventListener("abort", () => {
|
|
337
522
|
clearTimeout(timeout);
|
|
338
523
|
resolve();
|
|
@@ -348,6 +533,9 @@ function abortPromise(signal) {
|
|
|
348
533
|
}
|
|
349
534
|
return new Promise((resolve) => signal.addEventListener("abort", () => resolve(), { once: true }));
|
|
350
535
|
}
|
|
351
|
-
function exactSecrets(dispatch) {
|
|
352
|
-
return
|
|
536
|
+
function exactSecrets(dispatch, config) {
|
|
537
|
+
return [
|
|
538
|
+
dispatch.apiKeySecretGrant?.secret,
|
|
539
|
+
config.deviceToken
|
|
540
|
+
].filter((secret) => typeof secret === "string" && secret.length > 0);
|
|
353
541
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RunOnceResultPacket, RunnerDispatchEvent } from "../session/session.types.js";
|
|
2
2
|
export interface BridgeServiceConfig {
|
|
3
3
|
readonly serverUrl: string;
|
|
4
4
|
readonly runnerId: string;
|
|
@@ -16,6 +16,7 @@ export interface BridgeServiceConfig {
|
|
|
16
16
|
readonly supportsManagedWorktrees: boolean;
|
|
17
17
|
readonly supportsManagedForgeArtifacts: boolean;
|
|
18
18
|
readonly supportsRunnerAuthoredPhaseArtifacts: boolean;
|
|
19
|
+
readonly supportsDraftRunOnce: boolean;
|
|
19
20
|
readonly version: string;
|
|
20
21
|
readonly protocolVersion: number;
|
|
21
22
|
readonly minServerVersion?: string | null;
|
|
@@ -27,6 +28,15 @@ export interface BridgeRepoMapping {
|
|
|
27
28
|
readonly name?: string | null;
|
|
28
29
|
readonly remoteUrl?: string | null;
|
|
29
30
|
readonly localPath?: string | null;
|
|
31
|
+
readonly qaAppLaunchTarget?: QaAppLaunchTarget | null;
|
|
32
|
+
}
|
|
33
|
+
export interface QaAppLaunchTarget {
|
|
34
|
+
readonly url?: string | null;
|
|
35
|
+
readonly command?: string | null;
|
|
36
|
+
readonly working_directory?: string | null;
|
|
37
|
+
readonly ready_url?: string | null;
|
|
38
|
+
readonly routes?: readonly string[] | null;
|
|
39
|
+
readonly notes?: string | null;
|
|
30
40
|
}
|
|
31
41
|
export interface BridgeProviderStatus {
|
|
32
42
|
readonly signedIn?: boolean | null;
|
|
@@ -46,6 +56,7 @@ export interface BridgeCapabilities {
|
|
|
46
56
|
readonly supportsManagedWorktrees?: boolean | null;
|
|
47
57
|
readonly supportsManagedForgeArtifacts?: boolean | null;
|
|
48
58
|
readonly supportsRunnerAuthoredPhaseArtifacts?: boolean | null;
|
|
59
|
+
readonly supportsDraftRunOnce?: boolean | null;
|
|
49
60
|
}
|
|
50
61
|
export interface BridgeHelloRequest {
|
|
51
62
|
readonly version: string;
|
|
@@ -79,5 +90,5 @@ export interface BridgeCancelRequest {
|
|
|
79
90
|
readonly fencingToken: string;
|
|
80
91
|
readonly reason?: string | null;
|
|
81
92
|
}
|
|
82
|
-
export type BridgeDispatchHandler = (dispatch:
|
|
93
|
+
export type BridgeDispatchHandler = (dispatch: unknown) => Promise<void>;
|
|
83
94
|
export type BridgeCancelHandler = (request: BridgeCancelRequest) => Promise<void>;
|