@stage5/lumine 0.2.40 → 0.2.42
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 +78 -13
- package/lib/admin-news.js +236 -0
- package/lib/admin-workflows.js +434 -0
- package/lib/admin.js +644 -45
- package/lib/agent/mcp-server.js +138 -0
- package/lib/agent/providers/claude-code.js +214 -0
- package/lib/agent/providers/codex.js +522 -0
- package/lib/agent/providers/environment.js +40 -0
- package/lib/agent/providers/index.js +38 -0
- package/lib/agent/tool-session.js +301 -0
- package/lib/agent/trace.js +138 -0
- package/lib/agent.js +453 -0
- package/lib/api.js +81 -1
- package/lib/build-review.js +493 -0
- package/lib/commands.js +86 -9
- package/lib/constants.js +2 -0
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +24 -3
- package/sdk/LUMINE_ADMIN.md +271 -32
package/lib/agent.js
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
loadBuildMetadata,
|
|
8
|
+
loadExternalAgentRuntime,
|
|
9
|
+
validateExternalAgentCandidate,
|
|
10
|
+
} from "./api.js";
|
|
11
|
+
import { assertAuthScope, resolveAuth } from "./auth.js";
|
|
12
|
+
import {
|
|
13
|
+
assertLocalProjectCanBeSaved,
|
|
14
|
+
assertProjectFilesWithinLimits,
|
|
15
|
+
collectProjectFilesFromDir,
|
|
16
|
+
findLocalProjectMetadata,
|
|
17
|
+
isIndexHtmlPath,
|
|
18
|
+
} from "./workspace.js";
|
|
19
|
+
import { resolveExternalAgentProvider } from "./agent/providers/index.js";
|
|
20
|
+
import {
|
|
21
|
+
createExternalAgentToolSession,
|
|
22
|
+
replaceWorkspaceProjectFiles,
|
|
23
|
+
} from "./agent/tool-session.js";
|
|
24
|
+
import {
|
|
25
|
+
appendAgentTrace,
|
|
26
|
+
createAgentRunArtifacts,
|
|
27
|
+
readAgentTrace,
|
|
28
|
+
summarizeAgentTraceForReview,
|
|
29
|
+
writeAgentFeedback,
|
|
30
|
+
writeAgentRuntimeFile,
|
|
31
|
+
} from "./agent/trace.js";
|
|
32
|
+
|
|
33
|
+
const CLI_PATH = fileURLToPath(new URL("../bin/lumine.js", import.meta.url));
|
|
34
|
+
const MAX_AGENT_REQUEST_LENGTH = 20_000;
|
|
35
|
+
const LOOP_REVIEW_OUTPUT_SCHEMA = {
|
|
36
|
+
type: "object",
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
required: ["summary", "observations"],
|
|
39
|
+
properties: {
|
|
40
|
+
summary: { type: "string", maxLength: 500 },
|
|
41
|
+
observations: {
|
|
42
|
+
type: "array",
|
|
43
|
+
maxItems: 6,
|
|
44
|
+
items: {
|
|
45
|
+
type: "object",
|
|
46
|
+
additionalProperties: false,
|
|
47
|
+
required: ["area", "severity", "evidence", "improvement"],
|
|
48
|
+
properties: {
|
|
49
|
+
area: {
|
|
50
|
+
type: "string",
|
|
51
|
+
enum: [
|
|
52
|
+
"prompt",
|
|
53
|
+
"tool_contract",
|
|
54
|
+
"validation",
|
|
55
|
+
"adapter",
|
|
56
|
+
"workflow",
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
severity: {
|
|
60
|
+
type: "string",
|
|
61
|
+
enum: ["low", "medium", "high"],
|
|
62
|
+
},
|
|
63
|
+
evidence: { type: "string", maxLength: 800 },
|
|
64
|
+
improvement: { type: "string", maxLength: 800 },
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export async function agentCommand(options, { saveWorkspace }) {
|
|
72
|
+
const userMessage = String(options.agentPrompt || "").trim();
|
|
73
|
+
if (!userMessage) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
'Pass the Build request, for example: lumine agent --provider codex "Add keyboard controls".',
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (userMessage.length > MAX_AGENT_REQUEST_LENGTH) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`The Build request must be at most ${MAX_AGENT_REQUEST_LENGTH.toLocaleString("en-US")} characters.`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const provider = resolveExternalAgentProvider(options.provider);
|
|
84
|
+
const auth = await resolveAuth(options);
|
|
85
|
+
await assertAuthScope({ options, auth, scope: "build:read" });
|
|
86
|
+
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
87
|
+
const workspace = await loadExternalAgentWorkspace({ options, auth });
|
|
88
|
+
const runtimeResult = await loadExternalAgentRuntime({
|
|
89
|
+
options,
|
|
90
|
+
auth,
|
|
91
|
+
buildId: workspace.buildId,
|
|
92
|
+
userMessage,
|
|
93
|
+
projectFiles: workspace.files,
|
|
94
|
+
baseFilesHash: workspace.baseFilesHash,
|
|
95
|
+
});
|
|
96
|
+
if (runtimeResult.runtime?.billing?.usesTwinkleAiEnergy !== false) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
"Twinkle did not return the zero-energy external-agent runtime contract.",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const artifacts = await createAgentRunArtifacts({
|
|
103
|
+
dir: workspace.dir,
|
|
104
|
+
provider: provider.id,
|
|
105
|
+
userMessage,
|
|
106
|
+
runtime: runtimeResult.runtime,
|
|
107
|
+
});
|
|
108
|
+
const isolationDir = await fs.mkdtemp(
|
|
109
|
+
path.join(os.tmpdir(), "lumine-agent-isolation-"),
|
|
110
|
+
);
|
|
111
|
+
let runtime = runtimeResult.runtime;
|
|
112
|
+
let currentFiles = workspace.files;
|
|
113
|
+
let scopeBaseFiles = workspace.files;
|
|
114
|
+
let finalText = "";
|
|
115
|
+
let scopeRepairAttempts = 0;
|
|
116
|
+
let validationRepairAttempts = 0;
|
|
117
|
+
|
|
118
|
+
console.log(
|
|
119
|
+
`Running ${provider.label} through Lumine's workspace loop (Twinkle AI Energy: 0).`,
|
|
120
|
+
);
|
|
121
|
+
console.log(`Build: ${workspace.build.title || workspace.buildId}`);
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// Each failed validation pass receives the same canonical repair prompt and
|
|
125
|
+
// a fresh tool read-set, matching the hosted Lumine repair boundary.
|
|
126
|
+
for (;;) {
|
|
127
|
+
await writeAgentRuntimeFile({
|
|
128
|
+
runtimeFile: artifacts.runtimeFile,
|
|
129
|
+
runtime,
|
|
130
|
+
runId: artifacts.runId,
|
|
131
|
+
provider: provider.id,
|
|
132
|
+
userMessage,
|
|
133
|
+
});
|
|
134
|
+
const toolSession =
|
|
135
|
+
provider.id === "codex"
|
|
136
|
+
? await createExternalAgentToolSession({
|
|
137
|
+
options,
|
|
138
|
+
auth,
|
|
139
|
+
buildId: workspace.buildId,
|
|
140
|
+
baseFilesHash: workspace.baseFilesHash,
|
|
141
|
+
dir: workspace.dir,
|
|
142
|
+
runtime,
|
|
143
|
+
traceFile: artifacts.traceFile,
|
|
144
|
+
provider: provider.id,
|
|
145
|
+
initialFiles: currentFiles,
|
|
146
|
+
})
|
|
147
|
+
: null;
|
|
148
|
+
const providerResult = await provider.runPass({
|
|
149
|
+
options,
|
|
150
|
+
runtime,
|
|
151
|
+
toolSession,
|
|
152
|
+
isolationDir,
|
|
153
|
+
mcp: {
|
|
154
|
+
cliPath: CLI_PATH,
|
|
155
|
+
dir: workspace.dir,
|
|
156
|
+
runtimeFile: artifacts.runtimeFile,
|
|
157
|
+
traceFile: artifacts.traceFile,
|
|
158
|
+
buildId: workspace.buildId,
|
|
159
|
+
baseFilesHash: workspace.baseFilesHash,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
finalText = String(providerResult?.finalText || "").trim() || finalText;
|
|
163
|
+
await appendAgentTrace(artifacts.traceFile, {
|
|
164
|
+
type: "provider_completed",
|
|
165
|
+
provider: provider.id,
|
|
166
|
+
phase: runtime.phase,
|
|
167
|
+
ok: true,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const candidateFiles = await collectAgentProjectFiles(workspace.dir);
|
|
171
|
+
const validation = await validateExternalAgentCandidate({
|
|
172
|
+
options,
|
|
173
|
+
auth,
|
|
174
|
+
buildId: workspace.buildId,
|
|
175
|
+
baseFilesHash: workspace.baseFilesHash,
|
|
176
|
+
userMessage,
|
|
177
|
+
phase: runtime.phase,
|
|
178
|
+
validationBaseFiles: workspace.files,
|
|
179
|
+
scopeBaseFiles,
|
|
180
|
+
candidateFiles,
|
|
181
|
+
});
|
|
182
|
+
if (validation.ok) {
|
|
183
|
+
// Validation returns the canonical normalized snapshot. Make the
|
|
184
|
+
// workspace converge on that response before the ordinary guarded save
|
|
185
|
+
// collects files from disk.
|
|
186
|
+
await replaceWorkspaceWithSnapshot({
|
|
187
|
+
dir: workspace.dir,
|
|
188
|
+
currentFiles: candidateFiles,
|
|
189
|
+
snapshotFiles: validation.projectFiles,
|
|
190
|
+
});
|
|
191
|
+
currentFiles = validation.projectFiles;
|
|
192
|
+
await appendAgentTrace(artifacts.traceFile, {
|
|
193
|
+
type: "validation_passed",
|
|
194
|
+
provider: provider.id,
|
|
195
|
+
phase: runtime.phase,
|
|
196
|
+
ok: true,
|
|
197
|
+
});
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await appendAgentTrace(artifacts.traceFile, {
|
|
202
|
+
type: "validation_failed",
|
|
203
|
+
provider: provider.id,
|
|
204
|
+
phase: runtime.phase,
|
|
205
|
+
ok: false,
|
|
206
|
+
result: validation.kind,
|
|
207
|
+
reason: String(validation.reason || "").slice(0, 1000),
|
|
208
|
+
});
|
|
209
|
+
console.error(`Lumine validation: ${validation.reason}`);
|
|
210
|
+
|
|
211
|
+
if (validation.kind === "scope") {
|
|
212
|
+
if (runtime.phase === "validation_repair") {
|
|
213
|
+
// A validation-repair pass is intentionally allowed only a tiny
|
|
214
|
+
// corrective diff. If it exceeds that scope, retire its local file
|
|
215
|
+
// mutations before returning the failure; otherwise a rejected
|
|
216
|
+
// candidate could remain in the workspace and be saved manually
|
|
217
|
+
// after Lumine explicitly refused it.
|
|
218
|
+
await replaceWorkspaceWithSnapshot({
|
|
219
|
+
dir: workspace.dir,
|
|
220
|
+
currentFiles: candidateFiles,
|
|
221
|
+
snapshotFiles: scopeBaseFiles,
|
|
222
|
+
});
|
|
223
|
+
currentFiles = scopeBaseFiles;
|
|
224
|
+
throw new Error(
|
|
225
|
+
`The validation repair exceeded Lumine's repair scope: ${validation.reason}`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (scopeRepairAttempts >= 1) {
|
|
229
|
+
await replaceWorkspaceWithSnapshot({
|
|
230
|
+
dir: workspace.dir,
|
|
231
|
+
currentFiles: candidateFiles,
|
|
232
|
+
snapshotFiles: scopeBaseFiles,
|
|
233
|
+
});
|
|
234
|
+
throw new Error(
|
|
235
|
+
`The external agent still exceeded Lumine's run scope after repair. The oversized pass was not saved.`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
scopeRepairAttempts += 1;
|
|
239
|
+
await replaceWorkspaceWithSnapshot({
|
|
240
|
+
dir: workspace.dir,
|
|
241
|
+
currentFiles: candidateFiles,
|
|
242
|
+
snapshotFiles: scopeBaseFiles,
|
|
243
|
+
});
|
|
244
|
+
currentFiles = scopeBaseFiles;
|
|
245
|
+
} else {
|
|
246
|
+
if (validationRepairAttempts >= 2) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
"The external agent could not pass Lumine validation after two repair attempts. Its local candidate remains unsaved for inspection.",
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
validationRepairAttempts += 1;
|
|
252
|
+
scopeBaseFiles = candidateFiles;
|
|
253
|
+
currentFiles = candidateFiles;
|
|
254
|
+
}
|
|
255
|
+
runtime = validation.runtime;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const changedPaths = listChangedPaths(workspace.files, currentFiles);
|
|
259
|
+
if (changedPaths.length > 0) {
|
|
260
|
+
await saveWorkspace({
|
|
261
|
+
...options,
|
|
262
|
+
dir: workspace.dir,
|
|
263
|
+
publish: false,
|
|
264
|
+
externalAgentProvider: provider.id,
|
|
265
|
+
summary:
|
|
266
|
+
options.summary ||
|
|
267
|
+
`External ${provider.label} agent: ${userMessage.slice(0, 160)}`,
|
|
268
|
+
});
|
|
269
|
+
await appendAgentTrace(artifacts.traceFile, {
|
|
270
|
+
type: "save_completed",
|
|
271
|
+
provider: provider.id,
|
|
272
|
+
phase: runtime.phase,
|
|
273
|
+
ok: true,
|
|
274
|
+
changedPaths,
|
|
275
|
+
});
|
|
276
|
+
} else {
|
|
277
|
+
console.log("No project files changed; nothing was saved.");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log("");
|
|
281
|
+
console.log(finalText || "The Lumine pass completed.");
|
|
282
|
+
let feedback = null;
|
|
283
|
+
if (options.reviewLoop) {
|
|
284
|
+
feedback = await reviewLoopSafely({
|
|
285
|
+
options,
|
|
286
|
+
provider,
|
|
287
|
+
isolationDir,
|
|
288
|
+
artifacts,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
console.log(`\nLoop trace: ${artifacts.traceFile}`);
|
|
292
|
+
await appendAgentTrace(artifacts.traceFile, {
|
|
293
|
+
type: "run_completed",
|
|
294
|
+
provider: provider.id,
|
|
295
|
+
phase: runtime.phase,
|
|
296
|
+
ok: true,
|
|
297
|
+
changedPaths,
|
|
298
|
+
feedbackObservationCount: feedback?.observations?.length || 0,
|
|
299
|
+
});
|
|
300
|
+
return {
|
|
301
|
+
provider: provider.id,
|
|
302
|
+
buildId: workspace.buildId,
|
|
303
|
+
changedPaths,
|
|
304
|
+
finalText,
|
|
305
|
+
traceFile: artifacts.traceFile,
|
|
306
|
+
feedbackFile: feedback ? artifacts.feedbackFile : null,
|
|
307
|
+
};
|
|
308
|
+
} catch (error) {
|
|
309
|
+
await appendAgentTrace(artifacts.traceFile, {
|
|
310
|
+
type: "run_failed",
|
|
311
|
+
provider: provider.id,
|
|
312
|
+
phase: runtime?.phase || null,
|
|
313
|
+
ok: false,
|
|
314
|
+
error: String(error?.message || error).slice(0, 1000),
|
|
315
|
+
}).catch(() => undefined);
|
|
316
|
+
error.message = `${error.message}\nLoop trace: ${artifacts.traceFile}`;
|
|
317
|
+
throw error;
|
|
318
|
+
} finally {
|
|
319
|
+
await fs.rm(isolationDir, { recursive: true, force: true });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function loadExternalAgentWorkspace({ options, auth }) {
|
|
324
|
+
const localProject = await findLocalProjectMetadata(
|
|
325
|
+
path.resolve(options.dir || process.cwd()),
|
|
326
|
+
);
|
|
327
|
+
assertLocalProjectCanBeSaved(localProject);
|
|
328
|
+
const buildId =
|
|
329
|
+
Number(localProject?.metadata?.buildId || 0) ||
|
|
330
|
+
Number(localProject?.metadata?.build?.id || 0);
|
|
331
|
+
if (!buildId || !localProject?.rootDir) {
|
|
332
|
+
throw new Error(
|
|
333
|
+
"Run `lumine pull` first, then start the external agent from that pulled workspace.",
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
const baseFilesHash = String(localProject.metadata?.filesHash || "").trim();
|
|
337
|
+
if (!baseFilesHash) {
|
|
338
|
+
throw new Error(
|
|
339
|
+
"This workspace has no server filesHash. Run `lumine pull` before starting the external agent.",
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
const build = await loadBuildMetadata({ options, auth, buildId });
|
|
343
|
+
if (build?.canWrite === false) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
"This checkout is read-only. Pull your contribution branch or an owned Build before starting the external agent.",
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
const files = await collectAgentProjectFiles(localProject.rootDir);
|
|
349
|
+
if (!files.some((file) => isIndexHtmlPath(file.path))) {
|
|
350
|
+
throw new Error("Project files must include /index.html or /index.htm.");
|
|
351
|
+
}
|
|
352
|
+
assertProjectFilesWithinLimits(files, build?.projectLimits);
|
|
353
|
+
return {
|
|
354
|
+
localProject,
|
|
355
|
+
buildId,
|
|
356
|
+
build,
|
|
357
|
+
dir: localProject.rootDir,
|
|
358
|
+
baseFilesHash,
|
|
359
|
+
files,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function collectAgentProjectFiles(dir) {
|
|
364
|
+
const root = path.resolve(dir);
|
|
365
|
+
const files = [];
|
|
366
|
+
await collectProjectFilesFromDir({ root, dir: root, files });
|
|
367
|
+
return files.sort((left, right) => left.path.localeCompare(right.path));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function replaceWorkspaceWithSnapshot({
|
|
371
|
+
dir,
|
|
372
|
+
currentFiles,
|
|
373
|
+
snapshotFiles,
|
|
374
|
+
}) {
|
|
375
|
+
await replaceWorkspaceProjectFiles({
|
|
376
|
+
dir,
|
|
377
|
+
previousFiles: currentFiles,
|
|
378
|
+
nextFiles: snapshotFiles,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function listChangedPaths(beforeFiles, afterFiles) {
|
|
383
|
+
const before = new Map(beforeFiles.map((file) => [file.path, file.content]));
|
|
384
|
+
const after = new Map(afterFiles.map((file) => [file.path, file.content]));
|
|
385
|
+
return Array.from(new Set([...before.keys(), ...after.keys()]))
|
|
386
|
+
.filter((filePath) => before.get(filePath) !== after.get(filePath))
|
|
387
|
+
.sort();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function reviewLoopSafely({
|
|
391
|
+
options,
|
|
392
|
+
provider,
|
|
393
|
+
isolationDir,
|
|
394
|
+
artifacts,
|
|
395
|
+
}) {
|
|
396
|
+
try {
|
|
397
|
+
const events = await readAgentTrace(artifacts.traceFile);
|
|
398
|
+
const traceSummary = summarizeAgentTraceForReview(events);
|
|
399
|
+
const feedback = await provider.reviewLoop({
|
|
400
|
+
options,
|
|
401
|
+
isolationDir,
|
|
402
|
+
systemPrompt:
|
|
403
|
+
"You are reviewing the observable trace of a Lumine tool loop powered by this same local provider. Identify only evidence-backed runtime friction. Do not infer hidden reasoning, critique the app implementation, or suggest generic features. If the loop was clean, return an empty observations array.",
|
|
404
|
+
prompt: `Review this sanitized Lumine runtime trace and return structured feedback.\n\n${JSON.stringify(traceSummary)}`,
|
|
405
|
+
outputSchema: LOOP_REVIEW_OUTPUT_SCHEMA,
|
|
406
|
+
});
|
|
407
|
+
const normalizedFeedback = normalizeLoopFeedback(feedback);
|
|
408
|
+
await writeAgentFeedback(artifacts.feedbackFile, normalizedFeedback);
|
|
409
|
+
console.log("\nLumine loop review:");
|
|
410
|
+
console.log(normalizedFeedback.summary || "No runtime friction observed.");
|
|
411
|
+
for (const observation of normalizedFeedback.observations) {
|
|
412
|
+
console.log(
|
|
413
|
+
`- [${observation.severity}] ${observation.improvement} (evidence: ${observation.evidence})`,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
console.log(`Feedback: ${artifacts.feedbackFile}`);
|
|
417
|
+
return normalizedFeedback;
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error(
|
|
420
|
+
`Lumine loop review skipped: ${String(error?.message || error)}`,
|
|
421
|
+
);
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function normalizeLoopFeedback(value) {
|
|
427
|
+
const allowedAreas = new Set([
|
|
428
|
+
"prompt",
|
|
429
|
+
"tool_contract",
|
|
430
|
+
"validation",
|
|
431
|
+
"adapter",
|
|
432
|
+
"workflow",
|
|
433
|
+
]);
|
|
434
|
+
const allowedSeverities = new Set(["low", "medium", "high"]);
|
|
435
|
+
return {
|
|
436
|
+
summary: String(value?.summary || "").trim().slice(0, 500),
|
|
437
|
+
observations: (Array.isArray(value?.observations) ? value.observations : [])
|
|
438
|
+
.slice(0, 6)
|
|
439
|
+
.map((observation) => ({
|
|
440
|
+
area: allowedAreas.has(observation?.area)
|
|
441
|
+
? observation.area
|
|
442
|
+
: "workflow",
|
|
443
|
+
severity: allowedSeverities.has(observation?.severity)
|
|
444
|
+
? observation.severity
|
|
445
|
+
: "low",
|
|
446
|
+
evidence: String(observation?.evidence || "").trim().slice(0, 800),
|
|
447
|
+
improvement: String(observation?.improvement || "")
|
|
448
|
+
.trim()
|
|
449
|
+
.slice(0, 800),
|
|
450
|
+
}))
|
|
451
|
+
.filter((observation) => observation.evidence && observation.improvement),
|
|
452
|
+
};
|
|
453
|
+
}
|
package/lib/api.js
CHANGED
|
@@ -83,6 +83,79 @@ export async function loadBuildFiles({ options, auth, buildId, includeContent })
|
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
export async function loadExternalAgentRuntime({
|
|
87
|
+
options,
|
|
88
|
+
auth,
|
|
89
|
+
buildId,
|
|
90
|
+
userMessage,
|
|
91
|
+
projectFiles,
|
|
92
|
+
baseFilesHash,
|
|
93
|
+
}) {
|
|
94
|
+
return await requestJson({
|
|
95
|
+
method: "POST",
|
|
96
|
+
url: `${options.apiUrl}/cli/build/${buildId}/agent/runtime`,
|
|
97
|
+
authToken: auth.token,
|
|
98
|
+
body: {
|
|
99
|
+
userMessage,
|
|
100
|
+
projectFiles,
|
|
101
|
+
baseFilesHash,
|
|
102
|
+
},
|
|
103
|
+
timeoutMs: options.timeoutMs,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function executeExternalAgentTool({
|
|
108
|
+
options,
|
|
109
|
+
auth,
|
|
110
|
+
buildId,
|
|
111
|
+
baseFilesHash,
|
|
112
|
+
projectFiles,
|
|
113
|
+
readPaths,
|
|
114
|
+
name,
|
|
115
|
+
arguments: toolArguments,
|
|
116
|
+
}) {
|
|
117
|
+
return await requestJson({
|
|
118
|
+
method: "POST",
|
|
119
|
+
url: `${options.apiUrl}/cli/build/${buildId}/agent/tool`,
|
|
120
|
+
authToken: auth.token,
|
|
121
|
+
body: {
|
|
122
|
+
baseFilesHash,
|
|
123
|
+
projectFiles,
|
|
124
|
+
readPaths,
|
|
125
|
+
name,
|
|
126
|
+
arguments: toolArguments,
|
|
127
|
+
},
|
|
128
|
+
timeoutMs: options.timeoutMs,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function validateExternalAgentCandidate({
|
|
133
|
+
options,
|
|
134
|
+
auth,
|
|
135
|
+
buildId,
|
|
136
|
+
baseFilesHash,
|
|
137
|
+
userMessage,
|
|
138
|
+
phase,
|
|
139
|
+
validationBaseFiles,
|
|
140
|
+
scopeBaseFiles,
|
|
141
|
+
candidateFiles,
|
|
142
|
+
}) {
|
|
143
|
+
return await requestJson({
|
|
144
|
+
method: "POST",
|
|
145
|
+
url: `${options.apiUrl}/cli/build/${buildId}/agent/validate`,
|
|
146
|
+
authToken: auth.token,
|
|
147
|
+
body: {
|
|
148
|
+
baseFilesHash,
|
|
149
|
+
userMessage,
|
|
150
|
+
phase,
|
|
151
|
+
validationBaseFiles,
|
|
152
|
+
scopeBaseFiles,
|
|
153
|
+
candidateFiles,
|
|
154
|
+
},
|
|
155
|
+
timeoutMs: options.timeoutMs,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
86
159
|
export async function loadBuildVersions({ options, auth, buildId, limit }) {
|
|
87
160
|
const url = new URL(`${options.apiUrl}/cli/build/${buildId}/versions`);
|
|
88
161
|
if (limit) url.searchParams.set("limit", String(limit));
|
|
@@ -213,10 +286,17 @@ export function detectLumineAgentEnvironment(environment = process.env) {
|
|
|
213
286
|
|
|
214
287
|
export function createLumineSaveClientContext(options, environment = process.env) {
|
|
215
288
|
const version = String(options?.lumineCli?.version || "").trim();
|
|
289
|
+
const selectedExternalAgent =
|
|
290
|
+
options?.externalAgentProvider === "claude-code"
|
|
291
|
+
? "claude_code"
|
|
292
|
+
: options?.externalAgentProvider === "codex"
|
|
293
|
+
? "codex"
|
|
294
|
+
: "";
|
|
216
295
|
return {
|
|
217
296
|
source: "lumine_cli",
|
|
218
297
|
version: version || null,
|
|
219
|
-
agentEnvironment:
|
|
298
|
+
agentEnvironment:
|
|
299
|
+
selectedExternalAgent || detectLumineAgentEnvironment(environment),
|
|
220
300
|
};
|
|
221
301
|
}
|
|
222
302
|
|