@loucompanion/forge-bridge 0.1.1-dev.ec3c3e74a648 → 0.1.1-dev.f599637d804e
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 +130 -22
- package/dist/bridge/bridge.types.d.ts +11 -2
- package/dist/cli/cli.service.js +13 -9
- package/dist/cli/local-config.js +18 -1
- package/dist/session/internals.d.ts +1 -1
- package/dist/session/phase-completion.d.ts +63 -4
- package/dist/session/phase-completion.js +2105 -94
- package/dist/session/provider-cli/provider-cli.base.d.ts +2 -2
- package/dist/session/provider-cli/provider-cli.service.d.ts +7 -4
- package/dist/session/provider-cli/provider-cli.service.js +1673 -152
- 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 +36 -15
- package/dist/session/session.types.d.ts +119 -0
- package/dist/session/session.types.js +238 -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 +78 -1
- 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;
|
|
@@ -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
|
}
|
|
@@ -73,10 +73,14 @@ export class ForgeBridgeService {
|
|
|
73
73
|
this.abortActiveSessions("bridge stopping");
|
|
74
74
|
await this.stopConnection();
|
|
75
75
|
}
|
|
76
|
-
async handleDispatch(
|
|
76
|
+
async handleDispatch(rawDispatch) {
|
|
77
|
+
const dispatch = this.parseDispatchOrReport(rawDispatch);
|
|
78
|
+
if (!dispatch) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
77
81
|
const leaseId = dispatch.lease.id;
|
|
78
82
|
const fencingToken = dispatch.lease.fencingToken;
|
|
79
|
-
const redactionSecrets = exactSecrets(dispatch);
|
|
83
|
+
const redactionSecrets = exactSecrets(dispatch, this.config);
|
|
80
84
|
if (this.activeSessions.has(leaseId)) {
|
|
81
85
|
await this.sendResult(leaseId, fencingToken, failureResult("Dispatch is already active on this bridge.")).catch(() => undefined);
|
|
82
86
|
return;
|
|
@@ -87,11 +91,20 @@ export class ForgeBridgeService {
|
|
|
87
91
|
const normalized = this.normalizeDispatch(dispatch);
|
|
88
92
|
const result = await this.sessions.runOnce(normalized, {
|
|
89
93
|
emit: async (event) => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
try {
|
|
95
|
+
await this.invokeAck("DispatchEvent", {
|
|
96
|
+
leaseId,
|
|
97
|
+
fencingToken,
|
|
98
|
+
event: redactSecrets(event, redactionSecrets)
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (isDispatchNoLongerPending(error)) {
|
|
103
|
+
controller.abort(error instanceof Error ? error.message : "Dispatch is no longer pending.");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
95
108
|
}
|
|
96
109
|
}, controller.signal);
|
|
97
110
|
await this.sendResult(leaseId, fencingToken, redactSecrets(result, redactionSecrets));
|
|
@@ -106,6 +119,27 @@ export class ForgeBridgeService {
|
|
|
106
119
|
this.activeSessions.delete(leaseId);
|
|
107
120
|
}
|
|
108
121
|
}
|
|
122
|
+
parseDispatchOrReport(rawDispatch) {
|
|
123
|
+
try {
|
|
124
|
+
return parseRunOnceDispatchPacket(rawDispatch);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const identifiers = readDispatchIdentifiers(rawDispatch);
|
|
128
|
+
if (!identifiers) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const summary = invalidDispatchSummary(error);
|
|
132
|
+
void this.sendResult(identifiers.leaseId, identifiers.fencingToken, failureResult(summary, [
|
|
133
|
+
{
|
|
134
|
+
role: "runner",
|
|
135
|
+
kind: "error",
|
|
136
|
+
content: summary,
|
|
137
|
+
eventType: "invalid_dispatch"
|
|
138
|
+
}
|
|
139
|
+
])).catch(() => undefined);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
109
143
|
async handleCancel(request) {
|
|
110
144
|
const active = this.activeSessions.get(request.leaseId);
|
|
111
145
|
if (!active || active.fencingToken !== request.fencingToken) {
|
|
@@ -123,6 +157,7 @@ export class ForgeBridgeService {
|
|
|
123
157
|
const mapping = findRepoMapping(dispatch, this.config.repoMappings);
|
|
124
158
|
const localPath = mapping?.localPath?.trim();
|
|
125
159
|
const repoRoot = localPath ? path.dirname(path.resolve(localPath)) : this.config.defaultRepoRoot;
|
|
160
|
+
const qaLaunchTarget = normalizeQaAppLaunchTarget(mapping?.qaAppLaunchTarget) ?? dispatch.qaBrowser?.launchTarget ?? null;
|
|
126
161
|
return {
|
|
127
162
|
...dispatch,
|
|
128
163
|
repo: {
|
|
@@ -133,7 +168,13 @@ export class ForgeBridgeService {
|
|
|
133
168
|
serverLocal: {
|
|
134
169
|
repoRoot,
|
|
135
170
|
cleanupWorktrees: this.config.cleanupWorktrees
|
|
136
|
-
}
|
|
171
|
+
},
|
|
172
|
+
qaBrowser: dispatch.qaBrowser
|
|
173
|
+
? {
|
|
174
|
+
...dispatch.qaBrowser,
|
|
175
|
+
launchTarget: qaLaunchTarget
|
|
176
|
+
}
|
|
177
|
+
: dispatch.qaBrowser
|
|
137
178
|
};
|
|
138
179
|
}
|
|
139
180
|
async sendResult(leaseId, fencingToken, result) {
|
|
@@ -142,7 +183,15 @@ export class ForgeBridgeService {
|
|
|
142
183
|
fencingToken,
|
|
143
184
|
result
|
|
144
185
|
};
|
|
145
|
-
|
|
186
|
+
try {
|
|
187
|
+
await this.invokeAck("DispatchResult", payload);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
if (isDispatchNoLongerPending(error)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
146
195
|
}
|
|
147
196
|
startHeartbeat() {
|
|
148
197
|
this.stopHeartbeat();
|
|
@@ -189,7 +238,7 @@ export class ForgeBridgeService {
|
|
|
189
238
|
}
|
|
190
239
|
const ack = await connection.invoke(methodName, payload);
|
|
191
240
|
if (!ack.accepted) {
|
|
192
|
-
throw new
|
|
241
|
+
throw new BridgeCallbackRejectedError(ack.code ?? null, ack.message ?? ack.code ?? `${methodName} callback was rejected.`);
|
|
193
242
|
}
|
|
194
243
|
return ack;
|
|
195
244
|
}
|
|
@@ -207,6 +256,20 @@ export class ForgeBridgeService {
|
|
|
207
256
|
}
|
|
208
257
|
}
|
|
209
258
|
}
|
|
259
|
+
class BridgeCallbackRejectedError extends Error {
|
|
260
|
+
code;
|
|
261
|
+
constructor(code, message) {
|
|
262
|
+
super(message);
|
|
263
|
+
this.code = code;
|
|
264
|
+
this.name = "BridgeCallbackRejectedError";
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function isDispatchNoLongerPending(error) {
|
|
268
|
+
if (error instanceof BridgeCallbackRejectedError) {
|
|
269
|
+
return error.code === "bridge.dispatch_not_pending" || error.code === "bridge.stale_dispatch";
|
|
270
|
+
}
|
|
271
|
+
return error instanceof Error && error.message === "Dispatch is no longer pending.";
|
|
272
|
+
}
|
|
210
273
|
export class SignalRBridgeConnectionFactory {
|
|
211
274
|
create(config) {
|
|
212
275
|
return new SignalRBridgeConnection(config);
|
|
@@ -237,14 +300,7 @@ export class SignalRBridgeConnection {
|
|
|
237
300
|
}
|
|
238
301
|
onDispatch(handler) {
|
|
239
302
|
this.connection.on("Dispatch", (raw) => {
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
dispatch = parseRunOnceDispatchPacket(raw);
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
void handler(dispatch).catch(() => undefined);
|
|
303
|
+
void handler(raw).catch(() => undefined);
|
|
248
304
|
});
|
|
249
305
|
}
|
|
250
306
|
onCancel(handler) {
|
|
@@ -271,7 +327,7 @@ export function defaultBridgeServiceConfig(overrides = {}) {
|
|
|
271
327
|
projectLabels: splitCsv(process.env.FORGE_PROJECT_LABELS ?? ""),
|
|
272
328
|
repoMappings: [],
|
|
273
329
|
os: process.platform,
|
|
274
|
-
sandboxProfiles:
|
|
330
|
+
sandboxProfiles: overrides.sandboxProfiles,
|
|
275
331
|
supportsApiSecretGrant: true,
|
|
276
332
|
supportsManagedWorktrees: true,
|
|
277
333
|
supportsManagedForgeArtifacts: true,
|
|
@@ -285,6 +341,7 @@ export function defaultBridgeServiceConfig(overrides = {}) {
|
|
|
285
341
|
};
|
|
286
342
|
return {
|
|
287
343
|
...config,
|
|
344
|
+
sandboxProfiles: config.sandboxProfiles ?? defaultSandboxProfiles(config.cliProviders),
|
|
288
345
|
heartbeatIntervalMs: positiveFiniteNumber(config.heartbeatIntervalMs, 30_000)
|
|
289
346
|
};
|
|
290
347
|
}
|
|
@@ -303,6 +360,11 @@ function positiveFiniteNumber(value, fallback) {
|
|
|
303
360
|
function providerStatusesFor(providers) {
|
|
304
361
|
return Object.fromEntries(providers.map((provider) => [provider, { signedIn: true, version: "", models: configuredModelsFor(provider) }]));
|
|
305
362
|
}
|
|
363
|
+
function defaultSandboxProfiles(providers) {
|
|
364
|
+
return providers.some((provider) => provider.toLowerCase() === "codex")
|
|
365
|
+
? ["provider-workspace-write"]
|
|
366
|
+
: ["none"];
|
|
367
|
+
}
|
|
306
368
|
function configuredModelsFor(provider) {
|
|
307
369
|
const envName = `FORGE_${provider.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase()}_MODELS`;
|
|
308
370
|
return splitCsv(process.env[envName] ?? "");
|
|
@@ -323,9 +385,52 @@ function findRepoMapping(dispatch, mappings) {
|
|
|
323
385
|
(!!mapping.remoteUrl?.trim() && equalsIgnoreCase(mapping.remoteUrl, dispatch.repo.remoteUrl)));
|
|
324
386
|
});
|
|
325
387
|
}
|
|
388
|
+
function normalizeQaAppLaunchTarget(target) {
|
|
389
|
+
if (!target) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
const routes = [...new Set((target.routes ?? []).map((route) => route.trim()).filter(Boolean))];
|
|
393
|
+
const normalized = {
|
|
394
|
+
url: cleanString(target.url),
|
|
395
|
+
command: cleanString(target.command),
|
|
396
|
+
working_directory: cleanString(target.working_directory),
|
|
397
|
+
ready_url: cleanString(target.ready_url),
|
|
398
|
+
routes,
|
|
399
|
+
notes: cleanString(target.notes)
|
|
400
|
+
};
|
|
401
|
+
return normalized.url || normalized.command || normalized.ready_url || routes.length > 0
|
|
402
|
+
? normalized
|
|
403
|
+
: null;
|
|
404
|
+
}
|
|
405
|
+
function cleanString(value) {
|
|
406
|
+
const clean = value?.trim();
|
|
407
|
+
return clean ? clean : null;
|
|
408
|
+
}
|
|
326
409
|
function equalsIgnoreCase(left, right) {
|
|
327
410
|
return !!left?.trim() && !!right?.trim() && left.trim().toLowerCase() === right.trim().toLowerCase();
|
|
328
411
|
}
|
|
412
|
+
function readDispatchIdentifiers(rawDispatch) {
|
|
413
|
+
if (!rawDispatch || typeof rawDispatch !== "object" || Array.isArray(rawDispatch)) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
const packet = rawDispatch;
|
|
417
|
+
const rawLease = packet.lease ?? packet.Lease;
|
|
418
|
+
if (!rawLease || typeof rawLease !== "object" || Array.isArray(rawLease)) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
const lease = rawLease;
|
|
422
|
+
const leaseId = readString(lease, "id", "Id");
|
|
423
|
+
const fencingToken = readString(lease, "fencingToken", "FencingToken");
|
|
424
|
+
return leaseId && fencingToken ? { leaseId, fencingToken } : null;
|
|
425
|
+
}
|
|
426
|
+
function readString(source, camelKey, pascalKey) {
|
|
427
|
+
const value = source[camelKey] ?? source[pascalKey];
|
|
428
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
429
|
+
}
|
|
430
|
+
function invalidDispatchSummary(error) {
|
|
431
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
432
|
+
return `Invalid bridge dispatch packet: ${detail}`;
|
|
433
|
+
}
|
|
329
434
|
function delay(ms, signal) {
|
|
330
435
|
if (signal?.aborted) {
|
|
331
436
|
return Promise.resolve();
|
|
@@ -348,6 +453,9 @@ function abortPromise(signal) {
|
|
|
348
453
|
}
|
|
349
454
|
return new Promise((resolve) => signal.addEventListener("abort", () => resolve(), { once: true }));
|
|
350
455
|
}
|
|
351
|
-
function exactSecrets(dispatch) {
|
|
352
|
-
return
|
|
456
|
+
function exactSecrets(dispatch, config) {
|
|
457
|
+
return [
|
|
458
|
+
dispatch.apiKeySecretGrant?.secret,
|
|
459
|
+
config.deviceToken
|
|
460
|
+
].filter((secret) => typeof secret === "string" && secret.length > 0);
|
|
353
461
|
}
|
|
@@ -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;
|
|
@@ -27,6 +27,15 @@ export interface BridgeRepoMapping {
|
|
|
27
27
|
readonly name?: string | null;
|
|
28
28
|
readonly remoteUrl?: string | null;
|
|
29
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;
|
|
30
39
|
}
|
|
31
40
|
export interface BridgeProviderStatus {
|
|
32
41
|
readonly signedIn?: boolean | null;
|
|
@@ -79,5 +88,5 @@ export interface BridgeCancelRequest {
|
|
|
79
88
|
readonly fencingToken: string;
|
|
80
89
|
readonly reason?: string | null;
|
|
81
90
|
}
|
|
82
|
-
export type BridgeDispatchHandler = (dispatch:
|
|
91
|
+
export type BridgeDispatchHandler = (dispatch: unknown) => Promise<void>;
|
|
83
92
|
export type BridgeCancelHandler = (request: BridgeCancelRequest) => Promise<void>;
|
package/dist/cli/cli.service.js
CHANGED
|
@@ -176,19 +176,23 @@ function parseEnvSecretGrant(raw) {
|
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
178
|
function exactSecrets(dispatch) {
|
|
179
|
-
return dispatch.apiKeySecretGrant?.secret
|
|
179
|
+
return exactSecretValues(dispatch.apiKeySecretGrant?.secret, process.env.FORGE_DEVICE_TOKEN);
|
|
180
180
|
}
|
|
181
181
|
function readEnvSecretValuesForRedaction() {
|
|
182
|
+
const secrets = [];
|
|
182
183
|
const raw = process.env[apiKeySecretGrantEnv];
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return [];
|
|
184
|
+
if (raw) {
|
|
185
|
+
try {
|
|
186
|
+
secrets.push(parseEnvSecretGrant(raw).secret);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// Ignore malformed grant env here; command handling will report the parse error.
|
|
190
|
+
}
|
|
191
191
|
}
|
|
192
|
+
return exactSecretValues(...secrets, process.env.FORGE_DEVICE_TOKEN);
|
|
193
|
+
}
|
|
194
|
+
function exactSecretValues(...values) {
|
|
195
|
+
return values.filter((value) => typeof value === "string" && value.length > 0);
|
|
192
196
|
}
|
|
193
197
|
export function parseArgs(argv) {
|
|
194
198
|
if (argv[0] === "connect") {
|
package/dist/cli/local-config.js
CHANGED
|
@@ -159,7 +159,8 @@ function normalizeRepoMappings(values) {
|
|
|
159
159
|
repoId: cleanString(mapping.repoId),
|
|
160
160
|
name: cleanString(mapping.name) ?? label,
|
|
161
161
|
remoteUrl: cleanString(mapping.remoteUrl),
|
|
162
|
-
localPath: path.resolve(mapping.localPath)
|
|
162
|
+
localPath: path.resolve(mapping.localPath),
|
|
163
|
+
qaAppLaunchTarget: normalizeQaAppLaunchTarget(mapping.qaAppLaunchTarget)
|
|
163
164
|
};
|
|
164
165
|
})
|
|
165
166
|
.sort((left, right) => left.label.localeCompare(right.label));
|
|
@@ -171,6 +172,22 @@ function normalizeLabel(value) {
|
|
|
171
172
|
}
|
|
172
173
|
return label;
|
|
173
174
|
}
|
|
175
|
+
function normalizeQaAppLaunchTarget(value) {
|
|
176
|
+
if (!value)
|
|
177
|
+
return undefined;
|
|
178
|
+
const routes = cleanList(value.routes);
|
|
179
|
+
const normalized = {
|
|
180
|
+
url: cleanString(value.url),
|
|
181
|
+
command: cleanString(value.command),
|
|
182
|
+
working_directory: cleanString(value.working_directory),
|
|
183
|
+
ready_url: cleanString(value.ready_url),
|
|
184
|
+
routes,
|
|
185
|
+
notes: cleanString(value.notes)
|
|
186
|
+
};
|
|
187
|
+
return normalized.url || normalized.command || normalized.ready_url || routes?.length
|
|
188
|
+
? normalized
|
|
189
|
+
: undefined;
|
|
190
|
+
}
|
|
174
191
|
function cleanString(value) {
|
|
175
192
|
const clean = value?.trim();
|
|
176
193
|
return clean ? clean : undefined;
|
|
@@ -2,7 +2,7 @@ export { parseRunOnceDispatchPacket, failureResult } from "./session.types.js";
|
|
|
2
2
|
export { buildCompletionToolServerArgs, codexMcpConfigArgs, completionPhaseFromDispatch, expectedCompletionToolName, prepareCompletionToolRuntime, readCompletionArtifact, writeCompletionArtifact } from "./phase-completion.js";
|
|
3
3
|
export type { CompletionArtifactReference, CompletionPhase, CompletionToolRuntime } from "./phase-completion.js";
|
|
4
4
|
export type { SessionEventSink, SessionService } from "./session.base.js";
|
|
5
|
-
export type { ApiKeySecretGrant, DispatchCycleRun, DispatchExecution, DispatchLease, DispatchSession, DispatchRepo, DispatchServerLocalConfig, DispatchWorkItem, RunOnceDispatchPacket, RunOnceResultPacket, RunnerDispatchArtifact, RunnerDispatchEvent, RunnerDispatchSpec, RunnerEventLine } from "./session.types.js";
|
|
5
|
+
export type { ApiKeySecretGrant, DispatchCycleRun, DispatchExecution, DispatchLease, DispatchPullRequestContext, DispatchSession, DispatchRepo, DispatchServerLocalConfig, DispatchWorkItem, RunOnceDispatchPacket, RunOnceResultPacket, RunnerDispatchArtifact, RunnerDispatchEvent, RunnerDispatchSpec, RunnerEventLine } from "./session.types.js";
|
|
6
6
|
export { DeterministicProviderCliService, ProcessBackedProviderCliService, RoutingProviderCliService, buildProviderCliCommand } from "./provider-cli/provider-cli.service.js";
|
|
7
7
|
export type { ProviderCliService } from "./provider-cli/provider-cli.base.js";
|
|
8
8
|
export type { ProviderCliCommand, ProviderProcessRunner, ProviderProcessRunRequest, ProviderProcessRunResult } from "./provider-cli/provider-cli.service.js";
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { RunOnceDispatchPacket, RunnerDispatchArtifact } from "./session.types.js";
|
|
2
2
|
import type { WorktreeHandle } from "../worktree/worktree.types.js";
|
|
3
|
-
export type
|
|
3
|
+
export type DevelopCompletionPhase = "implementation" | "self_review" | "validation";
|
|
4
|
+
export type DebugCompletionPhase = "debug_repro" | "debug_root_cause" | "debug_fix" | "debug_regression";
|
|
5
|
+
export type ArchitectCompletionPhase = "architect";
|
|
6
|
+
export type CompletionPhase = DevelopCompletionPhase | "qa" | DebugCompletionPhase | ArchitectCompletionPhase;
|
|
7
|
+
export type QaCompletionGate = "qa_plan" | "qa_regression" | "qa_api" | "qa_manual_web" | "qa_bug_repro" | "qa_findings" | "qa_verdict";
|
|
4
8
|
export interface CompletionToolRuntime {
|
|
5
9
|
readonly serverName: string;
|
|
6
10
|
readonly command: string;
|
|
@@ -17,13 +21,38 @@ export interface CompletionToolRuntimeOptions {
|
|
|
17
21
|
export interface CompletionToolServerConfig {
|
|
18
22
|
readonly worktreePath: string;
|
|
19
23
|
readonly workItemId: string;
|
|
24
|
+
readonly ownerType?: string;
|
|
25
|
+
readonly ownerId?: string;
|
|
20
26
|
readonly phase: CompletionPhase;
|
|
21
27
|
readonly producedBy?: string;
|
|
22
28
|
readonly redactionSecrets?: readonly string[];
|
|
29
|
+
readonly developCycle?: boolean;
|
|
30
|
+
readonly qaCycle?: boolean;
|
|
31
|
+
readonly debugCycle?: boolean;
|
|
32
|
+
readonly architectCycle?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface CompletionToolCallResult {
|
|
35
|
+
readonly content: readonly {
|
|
36
|
+
readonly type: "text";
|
|
37
|
+
readonly text: string;
|
|
38
|
+
}[];
|
|
39
|
+
readonly structuredContent: CompletionArtifactReference & {
|
|
40
|
+
readonly gate: {
|
|
41
|
+
readonly phase: CompletionPhase;
|
|
42
|
+
readonly qaGate?: QaCompletionGate;
|
|
43
|
+
readonly status: string;
|
|
44
|
+
readonly allowed: boolean;
|
|
45
|
+
readonly reason: string;
|
|
46
|
+
};
|
|
47
|
+
readonly nextPhase: CompletionPhase | "dev_test_ready" | "qa_ready" | "sign_off_ready";
|
|
48
|
+
readonly nextInstruction: string;
|
|
49
|
+
};
|
|
23
50
|
}
|
|
24
51
|
export interface CompletionArtifactReference {
|
|
25
52
|
readonly phase: CompletionPhase;
|
|
26
53
|
readonly workItemId: string;
|
|
54
|
+
readonly ownerType?: string;
|
|
55
|
+
readonly ownerId?: string;
|
|
27
56
|
readonly repoPath: string;
|
|
28
57
|
readonly path: string;
|
|
29
58
|
readonly reportPath: string;
|
|
@@ -31,20 +60,50 @@ export interface CompletionArtifactReference {
|
|
|
31
60
|
readonly status: string;
|
|
32
61
|
readonly producedBy: string;
|
|
33
62
|
}
|
|
63
|
+
export declare const debugCompletionPhaseOrder: readonly DebugCompletionPhase[];
|
|
64
|
+
export declare const qaCompletionGateOrder: readonly QaCompletionGate[];
|
|
34
65
|
export declare function completionPhaseFromDispatch(dispatch: RunOnceDispatchPacket): CompletionPhase;
|
|
35
66
|
export declare function normalizeCompletionPhase(value: string | null | undefined): CompletionPhase;
|
|
67
|
+
export declare function usesDevelopCycleCompletion(dispatch: RunOnceDispatchPacket): boolean;
|
|
68
|
+
export declare function usesQaCycleCompletion(dispatch: RunOnceDispatchPacket): boolean;
|
|
69
|
+
export declare function usesDebugCycleCompletion(dispatch: RunOnceDispatchPacket): boolean;
|
|
70
|
+
export declare function usesArchitectCycleCompletion(dispatch: RunOnceDispatchPacket): boolean;
|
|
36
71
|
export declare function completionToolNamesForPhase(phase: CompletionPhase): readonly string[];
|
|
72
|
+
export declare function completionToolNamesForQaGate(gate: QaCompletionGate): readonly string[];
|
|
73
|
+
export declare function completionToolNamesForDebugGate(gate: DebugCompletionPhase): readonly string[];
|
|
37
74
|
export declare function expectedCompletionToolName(phase: CompletionPhase): string;
|
|
75
|
+
export declare function expectedQaCompletionToolName(gate: QaCompletionGate): string;
|
|
76
|
+
export declare function expectedDebugCompletionToolName(gate: DebugCompletionPhase): string;
|
|
38
77
|
export declare function artifactRootFor(worktreePath: string, workItemId: string): string;
|
|
39
|
-
export declare function
|
|
40
|
-
export declare function
|
|
78
|
+
export declare function artifactRootForOwner(worktreePath: string, ownerType: string, ownerId: string): string;
|
|
79
|
+
export declare function completionArtifactJsonPath(worktreePath: string, workItemId: string, phase: CompletionPhase, ownerType?: string | null, ownerId?: string | null): string;
|
|
80
|
+
export declare function completionArtifactReportPath(worktreePath: string, workItemId: string, phase: CompletionPhase, ownerType?: string | null, ownerId?: string | null): string;
|
|
41
81
|
export declare function prepareCompletionToolRuntime(options: CompletionToolRuntimeOptions): Promise<CompletionToolRuntime>;
|
|
42
82
|
export declare function buildCompletionToolServerArgs(config: CompletionToolServerConfig): readonly string[];
|
|
43
83
|
export declare function codexMcpConfigArgs(runtime: CompletionToolRuntime): readonly string[];
|
|
44
84
|
export declare function cursorMcpServerConfig(runtime: CompletionToolRuntime): unknown;
|
|
45
85
|
export declare function completionPromptForPhase(phase: CompletionPhase): string;
|
|
86
|
+
export declare function completionPromptForDevelopCycle(phase: CompletionPhase): string;
|
|
87
|
+
export declare function completionPromptForQaCycle(currentGate?: QaCompletionGate): string;
|
|
88
|
+
export declare function completionPromptForDebugCycle(currentGate?: DebugCompletionPhase): string;
|
|
89
|
+
export declare function completionToolNamesForServer(config: CompletionToolServerConfig): readonly string[];
|
|
90
|
+
export declare class CompletionToolServerSession {
|
|
91
|
+
private readonly config;
|
|
92
|
+
private currentPhase;
|
|
93
|
+
private currentQaGate;
|
|
94
|
+
private completed;
|
|
95
|
+
constructor(config: CompletionToolServerConfig);
|
|
96
|
+
get instructions(): string;
|
|
97
|
+
toolDefinitions(): readonly unknown[];
|
|
98
|
+
callTool(toolName: string, input: unknown): Promise<CompletionToolCallResult>;
|
|
99
|
+
private callDebugGateTool;
|
|
100
|
+
private callQaGateTool;
|
|
101
|
+
private advanceAfterPass;
|
|
102
|
+
private advanceAfterQaGate;
|
|
103
|
+
private advanceAfterDebugGate;
|
|
104
|
+
}
|
|
46
105
|
export declare function writeCompletionArtifact(config: CompletionToolServerConfig, toolName: string, input: unknown): Promise<CompletionArtifactReference>;
|
|
47
|
-
export declare function readCompletionArtifact(worktreePath: string, workItemId: string, phase: CompletionPhase): Promise<RunnerDispatchArtifact | null>;
|
|
106
|
+
export declare function readCompletionArtifact(worktreePath: string, workItemId: string, phase: CompletionPhase, ownerType?: string | null, ownerId?: string | null): Promise<RunnerDispatchArtifact | null>;
|
|
48
107
|
export declare function runCompletionMcpServer(config: CompletionToolServerConfig): Promise<void>;
|
|
49
108
|
export declare function parseCompletionMcpArgs(argv: readonly string[]): {
|
|
50
109
|
readonly ok: true;
|