@loucompanion/forge-bridge 0.1.3-dev.bd0c67bbf352 → 0.1.3-dev.d1ca67076aa9
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.js +8 -2
- package/dist/cli/cli.help.d.ts +2 -0
- package/dist/cli/cli.help.js +171 -0
- package/dist/cli/cli.service.js +5 -183
- package/dist/session/phase-completion.artifacts.d.ts +10 -0
- package/dist/session/phase-completion.artifacts.js +599 -0
- package/dist/session/phase-completion.d.ts +32 -44
- package/dist/session/phase-completion.helpers.d.ts +112 -0
- package/dist/session/phase-completion.helpers.js +660 -0
- package/dist/session/phase-completion.js +28 -3501
- package/dist/session/phase-completion.prompts.d.ts +14 -0
- package/dist/session/phase-completion.prompts.js +220 -0
- package/dist/session/phase-completion.runtime.d.ts +36 -0
- package/dist/session/phase-completion.runtime.js +609 -0
- package/dist/session/phase-completion.schemas.d.ts +3 -0
- package/dist/session/phase-completion.schemas.js +707 -0
- package/dist/session/phase-completion.validation.d.ts +34 -0
- package/dist/session/phase-completion.validation.js +724 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +93 -0
- package/dist/session/provider-cli/provider-cli.artifacts.js +754 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
- package/dist/session/provider-cli/provider-cli.deterministic.d.ts +12 -0
- package/dist/session/provider-cli/provider-cli.deterministic.js +318 -0
- package/dist/session/provider-cli/provider-cli.process.d.ts +31 -0
- package/dist/session/provider-cli/provider-cli.process.js +265 -0
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +33 -0
- package/dist/session/provider-cli/provider-cli.prompts.js +411 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +65 -0
- package/dist/session/provider-cli/provider-cli.runtime.d.ts +9 -0
- package/dist/session/provider-cli/provider-cli.runtime.js +40 -0
- package/dist/session/provider-cli/provider-cli.service.d.ts +6 -53
- package/dist/session/provider-cli/provider-cli.service.js +11 -2529
- package/dist/session/provider-cli/provider-output-state.d.ts +20 -0
- package/dist/session/provider-cli/provider-output-state.js +445 -0
- package/dist/session/session.types.d.ts +37 -0
- package/dist/session/session.types.js +53 -0
- package/dist/shared/process/windows-launch.d.ts +8 -0
- package/dist/shared/process/windows-launch.js +38 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RunnerDispatchEvent } from "../session.types.js";
|
|
2
|
+
import { type ProviderUsage } from "./provider-usage.js";
|
|
3
|
+
export declare function messageFromError(error: unknown): string;
|
|
4
|
+
export declare function clip(value: string, maxLength: number): string;
|
|
5
|
+
export interface ProviderOutputState {
|
|
6
|
+
readonly sessionLabel: string | null;
|
|
7
|
+
finalMessage: string | null;
|
|
8
|
+
/** Accumulated provider-reported token usage for this run, or null when none was reported. */
|
|
9
|
+
readonly usage: ProviderUsage | null;
|
|
10
|
+
consume(line: string): readonly RunnerDispatchEvent[];
|
|
11
|
+
}
|
|
12
|
+
export declare function createProviderOutputState(provider: string): ProviderOutputState;
|
|
13
|
+
export declare function parseJsonLine(line: string): Record<string, unknown> | null;
|
|
14
|
+
export declare function formatCodexMcpToolCallEvents(type: string, event: Record<string, unknown>): readonly RunnerDispatchEvent[];
|
|
15
|
+
export declare function objectField(source: Record<string, unknown>, key: string): Record<string, unknown>;
|
|
16
|
+
export declare function stringField(source: Record<string, unknown>, key: string): string | null;
|
|
17
|
+
export declare function numericField(source: Record<string, unknown>, key: string): number | null;
|
|
18
|
+
export declare function stripUndefined(value: Record<string, unknown>): Record<string, unknown>;
|
|
19
|
+
export declare function normalizedString(value: string): string;
|
|
20
|
+
export declare function isNodeError(error: unknown): error is NodeJS.ErrnoException;
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { addUsage, emptyUsage, readUsageNumber } from "./provider-usage.js";
|
|
2
|
+
export function messageFromError(error) {
|
|
3
|
+
return error instanceof Error ? error.message : String(error);
|
|
4
|
+
}
|
|
5
|
+
export function clip(value, maxLength) {
|
|
6
|
+
if (maxLength <= 0) {
|
|
7
|
+
return "";
|
|
8
|
+
}
|
|
9
|
+
if (value.length <= maxLength) {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
const ellipsis = "...";
|
|
13
|
+
if (maxLength <= ellipsis.length) {
|
|
14
|
+
return ellipsis.slice(0, maxLength);
|
|
15
|
+
}
|
|
16
|
+
return `${value.slice(0, maxLength - ellipsis.length)}${ellipsis}`;
|
|
17
|
+
}
|
|
18
|
+
export function createProviderOutputState(provider) {
|
|
19
|
+
return provider.trim().toLowerCase() === "cursor"
|
|
20
|
+
? new CursorStreamJsonState()
|
|
21
|
+
: new CodexJsonState();
|
|
22
|
+
}
|
|
23
|
+
class CursorStreamJsonState {
|
|
24
|
+
sessionId = null;
|
|
25
|
+
finalMessage = null;
|
|
26
|
+
emittedSession = false;
|
|
27
|
+
accumulatedUsage = null;
|
|
28
|
+
get sessionLabel() {
|
|
29
|
+
return this.sessionId ? `session ${this.sessionId}` : null;
|
|
30
|
+
}
|
|
31
|
+
get usage() {
|
|
32
|
+
return this.accumulatedUsage;
|
|
33
|
+
}
|
|
34
|
+
consume(line) {
|
|
35
|
+
const event = parseJsonLine(line);
|
|
36
|
+
if (!event) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
const type = stringField(event, "type");
|
|
40
|
+
if (type === "system" && stringField(event, "subtype") === "init") {
|
|
41
|
+
this.sessionId = stringField(event, "session_id");
|
|
42
|
+
if (this.sessionId && !this.emittedSession) {
|
|
43
|
+
this.emittedSession = true;
|
|
44
|
+
return [{
|
|
45
|
+
role: "runner",
|
|
46
|
+
kind: "checkpoint",
|
|
47
|
+
content: `Cursor session started: ${this.sessionId}.`,
|
|
48
|
+
eventType: "provider_thread_started"
|
|
49
|
+
}];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (type === "assistant") {
|
|
53
|
+
const text = assistantMessageText(event);
|
|
54
|
+
if (text) {
|
|
55
|
+
this.finalMessage = text;
|
|
56
|
+
return [{
|
|
57
|
+
role: "assistant",
|
|
58
|
+
kind: "message",
|
|
59
|
+
content: clip(text, 4000),
|
|
60
|
+
eventType: "provider_message"
|
|
61
|
+
}];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (type === "tool_call") {
|
|
65
|
+
const subtype = stringField(event, "subtype");
|
|
66
|
+
const toolCall = objectField(event, "tool_call");
|
|
67
|
+
if (subtype === "started") {
|
|
68
|
+
const summary = formatCursorToolCallStarted(toolCall);
|
|
69
|
+
if (summary) {
|
|
70
|
+
return [{
|
|
71
|
+
role: "worker",
|
|
72
|
+
kind: "checkpoint",
|
|
73
|
+
content: summary,
|
|
74
|
+
eventType: "provider_tool_call"
|
|
75
|
+
}];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (subtype === "completed") {
|
|
79
|
+
const completed = formatCursorToolCallCompleted(toolCall);
|
|
80
|
+
if (completed) {
|
|
81
|
+
return [{
|
|
82
|
+
role: "worker",
|
|
83
|
+
kind: completed.kind,
|
|
84
|
+
content: completed.content,
|
|
85
|
+
eventType: completed.kind === "file_edit" ? "provider_file_edit" : "provider_tool_call"
|
|
86
|
+
}];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (type === "result" && stringField(event, "subtype") === "success") {
|
|
91
|
+
const result = stringField(event, "result");
|
|
92
|
+
if (result) {
|
|
93
|
+
this.finalMessage = result;
|
|
94
|
+
}
|
|
95
|
+
this.accumulateUsage(event);
|
|
96
|
+
}
|
|
97
|
+
if (type === "error" || (type === "result" && event.is_error === true)) {
|
|
98
|
+
return [{
|
|
99
|
+
role: "runner",
|
|
100
|
+
kind: "error",
|
|
101
|
+
content: clip(line.trim(), 1000),
|
|
102
|
+
eventType: "provider_cli_error"
|
|
103
|
+
}];
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
accumulateUsage(event) {
|
|
108
|
+
const usage = objectField(event, "usage");
|
|
109
|
+
const durationMs = readUsageNumber(event, "duration_ms") || readUsageNumber(event, "duration_api_ms");
|
|
110
|
+
if (Object.keys(usage).length === 0 && durationMs === 0) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.accumulatedUsage = addUsage(this.accumulatedUsage ?? emptyUsage("provider_reported"), {
|
|
114
|
+
inputTokens: readUsageNumber(usage, "inputTokens"),
|
|
115
|
+
outputTokens: readUsageNumber(usage, "outputTokens"),
|
|
116
|
+
cacheReadTokens: readUsageNumber(usage, "cacheReadTokens"),
|
|
117
|
+
cacheCreationTokens: readUsageNumber(usage, "cacheWriteTokens"),
|
|
118
|
+
providerDurationMs: durationMs > 0 ? durationMs : null,
|
|
119
|
+
source: "provider_reported"
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function formatCursorToolCallStarted(toolCall) {
|
|
124
|
+
const readArgs = objectField(objectField(toolCall, "readToolCall"), "args");
|
|
125
|
+
const readPath = stringField(readArgs, "path");
|
|
126
|
+
if (readPath) {
|
|
127
|
+
return `Reading ${readPath}`;
|
|
128
|
+
}
|
|
129
|
+
const writeArgs = objectField(objectField(toolCall, "writeToolCall"), "args");
|
|
130
|
+
const writePath = stringField(writeArgs, "path");
|
|
131
|
+
if (writePath) {
|
|
132
|
+
return `Writing ${writePath}`;
|
|
133
|
+
}
|
|
134
|
+
const shellArgs = objectField(objectField(toolCall, "shellToolCall"), "args");
|
|
135
|
+
const command = stringField(shellArgs, "command");
|
|
136
|
+
if (command) {
|
|
137
|
+
return `Shell command started: ${clip(command, 500)}`;
|
|
138
|
+
}
|
|
139
|
+
const fn = objectField(toolCall, "function");
|
|
140
|
+
const name = stringField(fn, "name");
|
|
141
|
+
if (name) {
|
|
142
|
+
return `Tool call started: ${name}`;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function formatCursorToolCallCompleted(toolCall) {
|
|
147
|
+
const writeArgs = objectField(objectField(toolCall, "writeToolCall"), "args");
|
|
148
|
+
const writePath = stringField(writeArgs, "path");
|
|
149
|
+
const writeResult = objectField(objectField(objectField(toolCall, "writeToolCall"), "result"), "success");
|
|
150
|
+
const writtenPath = stringField(writeResult, "path") ?? writePath;
|
|
151
|
+
if (writtenPath) {
|
|
152
|
+
return {
|
|
153
|
+
kind: "file_edit",
|
|
154
|
+
content: `Wrote ${writtenPath}`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const readArgs = objectField(objectField(toolCall, "readToolCall"), "args");
|
|
158
|
+
const readPath = stringField(readArgs, "path");
|
|
159
|
+
if (readPath) {
|
|
160
|
+
const readResult = objectField(objectField(objectField(toolCall, "readToolCall"), "result"), "success");
|
|
161
|
+
const totalLines = readResult.totalLines;
|
|
162
|
+
const lineSummary = typeof totalLines === "number" ? ` (${totalLines} lines)` : "";
|
|
163
|
+
return {
|
|
164
|
+
kind: "checkpoint",
|
|
165
|
+
content: `Read ${readPath}${lineSummary}`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const shellArgs = objectField(objectField(toolCall, "shellToolCall"), "args");
|
|
169
|
+
const command = stringField(shellArgs, "command");
|
|
170
|
+
if (command) {
|
|
171
|
+
return {
|
|
172
|
+
kind: "checkpoint",
|
|
173
|
+
content: `Shell command completed: ${clip(command, 500)}`
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const fn = objectField(toolCall, "function");
|
|
177
|
+
const name = stringField(fn, "name");
|
|
178
|
+
if (name) {
|
|
179
|
+
return {
|
|
180
|
+
kind: "checkpoint",
|
|
181
|
+
content: `Tool call completed: ${name}`
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function assistantMessageText(event) {
|
|
187
|
+
const message = objectField(event, "message");
|
|
188
|
+
const content = message.content;
|
|
189
|
+
if (!Array.isArray(content)) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const parts = content
|
|
193
|
+
.map((entry) => {
|
|
194
|
+
if (!entry || typeof entry !== "object") {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const block = entry;
|
|
198
|
+
return stringField(block, "text");
|
|
199
|
+
})
|
|
200
|
+
.filter((text) => Boolean(text));
|
|
201
|
+
if (parts.length === 0) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
return parts.join("");
|
|
205
|
+
}
|
|
206
|
+
export function parseJsonLine(line) {
|
|
207
|
+
const trimmed = line.trim();
|
|
208
|
+
if (!trimmed.startsWith("{")) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
let parsed;
|
|
212
|
+
try {
|
|
213
|
+
parsed = JSON.parse(trimmed);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
219
|
+
? parsed
|
|
220
|
+
: null;
|
|
221
|
+
}
|
|
222
|
+
class CodexJsonState {
|
|
223
|
+
threadId = null;
|
|
224
|
+
finalMessage = null;
|
|
225
|
+
emittedThread = false;
|
|
226
|
+
accumulatedUsage = null;
|
|
227
|
+
get sessionLabel() {
|
|
228
|
+
return this.threadId ? `thread ${this.threadId}` : null;
|
|
229
|
+
}
|
|
230
|
+
get usage() {
|
|
231
|
+
return this.accumulatedUsage;
|
|
232
|
+
}
|
|
233
|
+
consume(line) {
|
|
234
|
+
const event = parseJsonLine(line);
|
|
235
|
+
if (!event) {
|
|
236
|
+
return [];
|
|
237
|
+
}
|
|
238
|
+
const type = stringField(event, "type") ?? "";
|
|
239
|
+
if (type === "thread.started") {
|
|
240
|
+
this.threadId = stringField(event, "thread_id");
|
|
241
|
+
if (this.threadId && !this.emittedThread) {
|
|
242
|
+
this.emittedThread = true;
|
|
243
|
+
return [{
|
|
244
|
+
role: "runner",
|
|
245
|
+
kind: "checkpoint",
|
|
246
|
+
content: `Codex thread started: ${this.threadId}.`,
|
|
247
|
+
eventType: "provider_thread_started"
|
|
248
|
+
}];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (type === "turn.completed") {
|
|
252
|
+
this.accumulateUsage(event);
|
|
253
|
+
return [{
|
|
254
|
+
role: "runner",
|
|
255
|
+
kind: "checkpoint",
|
|
256
|
+
content: "Codex turn completed.",
|
|
257
|
+
eventType: "provider_turn_completed"
|
|
258
|
+
}];
|
|
259
|
+
}
|
|
260
|
+
if (type === "turn.failed" || type === "error") {
|
|
261
|
+
return [{
|
|
262
|
+
role: "runner",
|
|
263
|
+
kind: "error",
|
|
264
|
+
content: clip(line.trim(), 1000),
|
|
265
|
+
eventType: "provider_cli_error"
|
|
266
|
+
}];
|
|
267
|
+
}
|
|
268
|
+
if (type === "item.completed" || type === "item.updated") {
|
|
269
|
+
const item = objectField(event, "item");
|
|
270
|
+
const itemType = stringField(item, "type");
|
|
271
|
+
if (itemType === "agent_message") {
|
|
272
|
+
const text = stringField(item, "text");
|
|
273
|
+
if (text) {
|
|
274
|
+
this.finalMessage = text;
|
|
275
|
+
return [{
|
|
276
|
+
role: "assistant",
|
|
277
|
+
kind: "message",
|
|
278
|
+
content: clip(text, 4000),
|
|
279
|
+
eventType: "provider_message"
|
|
280
|
+
}];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (itemType === "command_execution") {
|
|
284
|
+
const command = stringField(item, "command");
|
|
285
|
+
const status = stringField(item, "status");
|
|
286
|
+
if (command) {
|
|
287
|
+
return [{
|
|
288
|
+
role: "worker",
|
|
289
|
+
kind: "checkpoint",
|
|
290
|
+
content: `Codex command ${status || "completed"}: ${clip(command, 500)}`,
|
|
291
|
+
eventType: "provider_command"
|
|
292
|
+
}];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const mcpEvents = formatCodexMcpToolCallEvents(type, event);
|
|
297
|
+
if (mcpEvents.length > 0) {
|
|
298
|
+
return mcpEvents;
|
|
299
|
+
}
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
accumulateUsage(event) {
|
|
303
|
+
const usage = objectField(event, "usage");
|
|
304
|
+
if (Object.keys(usage).length === 0) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
this.accumulatedUsage = addUsage(this.accumulatedUsage ?? emptyUsage("provider_reported"), {
|
|
308
|
+
inputTokens: readUsageNumber(usage, "input_tokens"),
|
|
309
|
+
outputTokens: readUsageNumber(usage, "output_tokens"),
|
|
310
|
+
cacheReadTokens: readUsageNumber(usage, "cached_input_tokens"),
|
|
311
|
+
reasoningOutputTokens: readUsageNumber(usage, "reasoning_output_tokens"),
|
|
312
|
+
source: "provider_reported"
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
export function formatCodexMcpToolCallEvents(type, event) {
|
|
317
|
+
if (type !== "item.started" && type !== "item.updated" && type !== "item.completed") {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
const item = objectField(event, "item");
|
|
321
|
+
const itemType = stringField(item, "type") ?? "";
|
|
322
|
+
const serverName = mcpServerName(item);
|
|
323
|
+
const toolName = mcpToolName(item);
|
|
324
|
+
const isMcpItem = itemType.toLowerCase().includes("mcp")
|
|
325
|
+
|| Boolean(serverName)
|
|
326
|
+
|| (toolName ? isForgeCompletionTool(toolName) || toolName.toLowerCase().includes("mcp") : false);
|
|
327
|
+
if (!isMcpItem || !toolName) {
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
const status = normalizedString(stringField(item, "status")
|
|
331
|
+
?? stringField(event, "status")
|
|
332
|
+
?? (type === "item.started" ? "started" : type === "item.completed" ? "completed" : "updated"));
|
|
333
|
+
const failed = status === "failed" || status === "error" || status === "cancelled" || Boolean(item.error);
|
|
334
|
+
const completed = failed || type === "item.completed" || status === "completed" || status === "succeeded" || status === "success";
|
|
335
|
+
const started = type === "item.started" || status === "started" || status === "running" || status === "in_progress";
|
|
336
|
+
const data = safeProviderMcpData(item, serverName ?? "unknown", toolName, status);
|
|
337
|
+
const label = `${serverName ?? "unknown"}.${toolName}`;
|
|
338
|
+
if (failed) {
|
|
339
|
+
const summary = mcpErrorSummary(item) ?? "MCP tool call failed.";
|
|
340
|
+
const events = [{
|
|
341
|
+
role: "tool",
|
|
342
|
+
kind: "error",
|
|
343
|
+
content: `MCP tool call failed: ${label} - ${summary}`,
|
|
344
|
+
eventType: "provider_mcp_call_failed",
|
|
345
|
+
data
|
|
346
|
+
}];
|
|
347
|
+
if (isForgeCompletionTool(toolName)) {
|
|
348
|
+
events.push({
|
|
349
|
+
role: "tool",
|
|
350
|
+
kind: "error",
|
|
351
|
+
content: `Forge completion tool validation failed: ${label} - ${summary}`,
|
|
352
|
+
eventType: "completion_tool_validation_failed",
|
|
353
|
+
data
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return events;
|
|
357
|
+
}
|
|
358
|
+
if (completed) {
|
|
359
|
+
return [{
|
|
360
|
+
role: "tool",
|
|
361
|
+
kind: "checkpoint",
|
|
362
|
+
content: `MCP tool call completed: ${label}.`,
|
|
363
|
+
eventType: "provider_mcp_call_completed",
|
|
364
|
+
data
|
|
365
|
+
}];
|
|
366
|
+
}
|
|
367
|
+
if (started) {
|
|
368
|
+
return [{
|
|
369
|
+
role: "tool",
|
|
370
|
+
kind: "checkpoint",
|
|
371
|
+
content: `MCP tool call started: ${label}.`,
|
|
372
|
+
eventType: "provider_mcp_call_started",
|
|
373
|
+
data
|
|
374
|
+
}];
|
|
375
|
+
}
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
function mcpServerName(item) {
|
|
379
|
+
return stringField(item, "server")
|
|
380
|
+
?? stringField(item, "server_name")
|
|
381
|
+
?? stringField(item, "mcp_server")
|
|
382
|
+
?? stringField(objectField(item, "mcp"), "server")
|
|
383
|
+
?? stringField(objectField(item, "mcp"), "server_name");
|
|
384
|
+
}
|
|
385
|
+
function mcpToolName(item) {
|
|
386
|
+
return stringField(item, "tool")
|
|
387
|
+
?? stringField(item, "tool_name")
|
|
388
|
+
?? stringField(item, "name")
|
|
389
|
+
?? stringField(objectField(item, "function"), "name")
|
|
390
|
+
?? stringField(objectField(item, "mcp"), "tool")
|
|
391
|
+
?? stringField(objectField(item, "mcp"), "tool_name");
|
|
392
|
+
}
|
|
393
|
+
function mcpErrorSummary(item) {
|
|
394
|
+
const direct = stringField(item, "error")
|
|
395
|
+
?? stringField(item, "error_message")
|
|
396
|
+
?? stringField(item, "message");
|
|
397
|
+
if (direct) {
|
|
398
|
+
return clip(direct, 600);
|
|
399
|
+
}
|
|
400
|
+
const error = objectField(item, "error");
|
|
401
|
+
return stringField(error, "message")
|
|
402
|
+
?? stringField(error, "summary")
|
|
403
|
+
?? stringField(objectField(item, "result"), "error")
|
|
404
|
+
?? null;
|
|
405
|
+
}
|
|
406
|
+
function safeProviderMcpData(item, serverName, toolName, status) {
|
|
407
|
+
const elapsedMs = numericField(item, "elapsed_ms") ?? numericField(item, "duration_ms");
|
|
408
|
+
return JSON.stringify(stripUndefined({
|
|
409
|
+
server_name: serverName,
|
|
410
|
+
tool_name: toolName,
|
|
411
|
+
status,
|
|
412
|
+
elapsed_ms: elapsedMs,
|
|
413
|
+
error_summary: mcpErrorSummary(item)
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
function isForgeCompletionTool(toolName) {
|
|
417
|
+
const normalized = toolName.trim().toLowerCase();
|
|
418
|
+
return normalized.startsWith("forge.complete_")
|
|
419
|
+
|| normalized.startsWith("complete_")
|
|
420
|
+
|| normalized.includes("forge.complete_")
|
|
421
|
+
|| normalized.includes("complete_architect_plan");
|
|
422
|
+
}
|
|
423
|
+
export function objectField(source, key) {
|
|
424
|
+
const value = source[key];
|
|
425
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
426
|
+
? value
|
|
427
|
+
: {};
|
|
428
|
+
}
|
|
429
|
+
export function stringField(source, key) {
|
|
430
|
+
const value = source[key];
|
|
431
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
432
|
+
}
|
|
433
|
+
export function numericField(source, key) {
|
|
434
|
+
const value = source[key];
|
|
435
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
436
|
+
}
|
|
437
|
+
export function stripUndefined(value) {
|
|
438
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
439
|
+
}
|
|
440
|
+
export function normalizedString(value) {
|
|
441
|
+
return value.trim().toLowerCase().replace(/-/g, "_");
|
|
442
|
+
}
|
|
443
|
+
export function isNodeError(error) {
|
|
444
|
+
return error instanceof Error && "code" in error;
|
|
445
|
+
}
|
|
@@ -19,6 +19,7 @@ export interface RunOnceDispatchPacket {
|
|
|
19
19
|
readonly architectDependencies?: DispatchArchitectDependencyContext | null;
|
|
20
20
|
readonly storyDevelop?: DispatchStoryDevelopContext | null;
|
|
21
21
|
readonly storyQa?: DispatchStoryQaContext | null;
|
|
22
|
+
readonly designerRefs?: DispatchDesignerRefHandoffContext | null;
|
|
22
23
|
readonly draft?: DispatchDraftPrompt | null;
|
|
23
24
|
}
|
|
24
25
|
export interface DispatchDraftPrompt {
|
|
@@ -142,6 +143,42 @@ export interface DispatchStoryQaDependencyContext {
|
|
|
142
143
|
readonly fromWorkItemId: string;
|
|
143
144
|
readonly toWorkItemId: string;
|
|
144
145
|
}
|
|
146
|
+
export interface DispatchDesignerRefHandoffContext {
|
|
147
|
+
readonly schemaVersion: number;
|
|
148
|
+
readonly kind: string;
|
|
149
|
+
readonly ownerType: string;
|
|
150
|
+
readonly ownerId: string;
|
|
151
|
+
readonly projectId: string;
|
|
152
|
+
readonly repoId: string;
|
|
153
|
+
readonly title: string;
|
|
154
|
+
readonly refs: readonly DispatchDesignerRefAttachment[];
|
|
155
|
+
}
|
|
156
|
+
export interface DispatchDesignerRefAttachment {
|
|
157
|
+
readonly ref: DispatchDesignerRef;
|
|
158
|
+
readonly reason: string;
|
|
159
|
+
readonly required: boolean;
|
|
160
|
+
readonly source: string;
|
|
161
|
+
readonly lineageKind: string;
|
|
162
|
+
readonly inheritedFromItemType?: string | null;
|
|
163
|
+
readonly inheritedFromItemId?: string | null;
|
|
164
|
+
readonly inheritedFromDesignerRefId?: string | null;
|
|
165
|
+
readonly addedByLane: string;
|
|
166
|
+
readonly sortOrder: number;
|
|
167
|
+
readonly createdAt: string;
|
|
168
|
+
}
|
|
169
|
+
export interface DispatchDesignerRef {
|
|
170
|
+
readonly id: string;
|
|
171
|
+
readonly projectId: string;
|
|
172
|
+
readonly repoId: string;
|
|
173
|
+
readonly refKey: string;
|
|
174
|
+
readonly kind: string;
|
|
175
|
+
readonly path: string;
|
|
176
|
+
readonly anchor?: string | null;
|
|
177
|
+
readonly title: string;
|
|
178
|
+
readonly contentHash: string;
|
|
179
|
+
readonly commitSha: string;
|
|
180
|
+
readonly createdAt: string;
|
|
181
|
+
}
|
|
145
182
|
export interface DispatchPullRequestContext {
|
|
146
183
|
readonly number: number;
|
|
147
184
|
readonly provider: string;
|
|
@@ -26,6 +26,7 @@ export function parseRunOnceDispatchPacket(value) {
|
|
|
26
26
|
architectDependencies: parseArchitectDependencies(packet.architectDependencies),
|
|
27
27
|
storyDevelop: parseStoryDevelop(packet.storyDevelop),
|
|
28
28
|
storyQa: parseStoryQa(packet.storyQa),
|
|
29
|
+
designerRefs: parseDesignerRefs(packet.designerRefs ?? packet.designer_refs),
|
|
29
30
|
draft: parseDraftPrompt(packet.draft)
|
|
30
31
|
};
|
|
31
32
|
}
|
|
@@ -416,6 +417,58 @@ function parseStoryQaDependencies(value) {
|
|
|
416
417
|
};
|
|
417
418
|
});
|
|
418
419
|
}
|
|
420
|
+
function parseDesignerRefs(value) {
|
|
421
|
+
if (value === undefined || value === null) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
const context = asObject(value, "designerRefs");
|
|
425
|
+
const refs = context.refs;
|
|
426
|
+
if (!Array.isArray(refs)) {
|
|
427
|
+
throw new Error("designerRefs.refs must be an array.");
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
schemaVersion: requiredNumber(context, "schemaVersion"),
|
|
431
|
+
kind: requiredString(context, "kind"),
|
|
432
|
+
ownerType: requiredString(context, "ownerType"),
|
|
433
|
+
ownerId: requiredString(context, "ownerId"),
|
|
434
|
+
projectId: requiredString(context, "projectId"),
|
|
435
|
+
repoId: requiredString(context, "repoId"),
|
|
436
|
+
title: requiredString(context, "title"),
|
|
437
|
+
refs: refs.map((entry, index) => parseDesignerRefAttachment(entry, index))
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function parseDesignerRefAttachment(value, index) {
|
|
441
|
+
const attachment = asObject(value, `designerRefs.refs[${index}]`);
|
|
442
|
+
return {
|
|
443
|
+
ref: parseDesignerRef(attachment.ref, index),
|
|
444
|
+
reason: optionalString(attachment, "reason") ?? "",
|
|
445
|
+
required: requiredBoolean(attachment, "required"),
|
|
446
|
+
source: optionalString(attachment, "source") ?? "",
|
|
447
|
+
lineageKind: optionalString(attachment, "lineageKind") ?? optionalString(attachment, "lineage_kind") ?? "",
|
|
448
|
+
inheritedFromItemType: optionalString(attachment, "inheritedFromItemType") ?? optionalString(attachment, "inherited_from_item_type"),
|
|
449
|
+
inheritedFromItemId: optionalString(attachment, "inheritedFromItemId") ?? optionalString(attachment, "inherited_from_item_id"),
|
|
450
|
+
inheritedFromDesignerRefId: optionalString(attachment, "inheritedFromDesignerRefId") ?? optionalString(attachment, "inherited_from_designer_ref_id"),
|
|
451
|
+
addedByLane: optionalString(attachment, "addedByLane") ?? optionalString(attachment, "added_by_lane") ?? "",
|
|
452
|
+
sortOrder: requiredNumber(attachment, "sortOrder"),
|
|
453
|
+
createdAt: requiredString(attachment, "createdAt")
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function parseDesignerRef(value, index) {
|
|
457
|
+
const designerRef = asObject(value, `designerRefs.refs[${index}].ref`);
|
|
458
|
+
return {
|
|
459
|
+
id: requiredString(designerRef, "id"),
|
|
460
|
+
projectId: requiredString(designerRef, "projectId"),
|
|
461
|
+
repoId: requiredString(designerRef, "repoId"),
|
|
462
|
+
refKey: requiredString(designerRef, "refKey"),
|
|
463
|
+
kind: requiredString(designerRef, "kind"),
|
|
464
|
+
path: requiredString(designerRef, "path"),
|
|
465
|
+
anchor: optionalString(designerRef, "anchor"),
|
|
466
|
+
title: optionalString(designerRef, "title") ?? "",
|
|
467
|
+
contentHash: requiredString(designerRef, "contentHash"),
|
|
468
|
+
commitSha: requiredString(designerRef, "commitSha"),
|
|
469
|
+
createdAt: requiredString(designerRef, "createdAt")
|
|
470
|
+
};
|
|
471
|
+
}
|
|
419
472
|
function parseStringArray(value, label) {
|
|
420
473
|
if (!Array.isArray(value)) {
|
|
421
474
|
throw new Error(`${label} must be an array.`);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface LaunchCommand {
|
|
2
|
+
readonly command: string;
|
|
3
|
+
readonly args: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function buildWindowsCmdLaunch(command: string, args: readonly string[]): LaunchCommand;
|
|
6
|
+
export declare function buildWindowsLaunchCommand(commandPath: string, args: readonly string[]): LaunchCommand;
|
|
7
|
+
export declare function windowsCommandLine(command: string, args: readonly string[]): string;
|
|
8
|
+
export declare function quoteWindowsCommandLineArg(value: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export function buildWindowsCmdLaunch(command, args) {
|
|
3
|
+
return {
|
|
4
|
+
command: process.env.ComSpec ?? "cmd.exe",
|
|
5
|
+
args: ["/d", "/s", "/v:off", "/c", `"${windowsCommandLine(command, args)}"`]
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function buildWindowsLaunchCommand(commandPath, args) {
|
|
9
|
+
const extension = path.extname(commandPath).toLowerCase();
|
|
10
|
+
if (extension === ".ps1") {
|
|
11
|
+
return {
|
|
12
|
+
command: powershellExecutable(),
|
|
13
|
+
args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", commandPath, ...args]
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (extension === ".cmd" || extension === ".bat") {
|
|
17
|
+
return buildWindowsCmdLaunch(commandPath, args);
|
|
18
|
+
}
|
|
19
|
+
return { command: commandPath, args };
|
|
20
|
+
}
|
|
21
|
+
export function windowsCommandLine(command, args) {
|
|
22
|
+
return [command, ...args].map(quoteWindowsCommandLineArg).join(" ");
|
|
23
|
+
}
|
|
24
|
+
export function quoteWindowsCommandLineArg(value) {
|
|
25
|
+
const escaped = value
|
|
26
|
+
.replace(/\^/g, "^^")
|
|
27
|
+
.replace(/%/g, "^%");
|
|
28
|
+
if (escaped.length > 0 && !/[\s"&|<>()^%!]/.test(escaped)) {
|
|
29
|
+
return escaped;
|
|
30
|
+
}
|
|
31
|
+
return `"${escaped.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/g, "$1$1")}"`;
|
|
32
|
+
}
|
|
33
|
+
function powershellExecutable() {
|
|
34
|
+
const systemRoot = process.env.SystemRoot;
|
|
35
|
+
return systemRoot
|
|
36
|
+
? path.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
|
|
37
|
+
: "powershell.exe";
|
|
38
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const forgeBridgeVersion = "0.1.3-dev.
|
|
1
|
+
export declare const forgeBridgeVersion = "0.1.3-dev.d1ca67076aa9";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const forgeBridgeVersion = "0.1.3-dev.
|
|
1
|
+
export const forgeBridgeVersion = "0.1.3-dev.d1ca67076aa9";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loucompanion/forge-bridge",
|
|
3
|
-
"version": "0.1.3-dev.
|
|
3
|
+
"version": "0.1.3-dev.d1ca67076aa9",
|
|
4
4
|
"description": "Forge runner bridge CLI for connecting local developer machines to Forge dispatch.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|