@engineeros/connector 0.12.5 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/engineeros-connector.mjs +109 -28
- package/package.json +1 -1
- package/src/agent-harness.mjs +37 -10
- package/src/runner.mjs +210 -25
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ npx --yes @engineeros/connector@latest pair PAIRING-CODE --url https://your-engi
|
|
|
47
47
|
|
|
48
48
|
The connector uploads a bounded ZIP snapshot for a safe file inventory, then stays online for deep assessments, rescans, and Goal Runs. Inventory never executes repository code. It excludes known secrets, dependency directories, build output, compiled binaries, files larger than 5 MB, agent-tool caches, Git metadata, and connector state before upload.
|
|
49
49
|
|
|
50
|
-
From **Project steering -> Workspace**, run the workspace assessment to use the connected agent subscription already authenticated on that computer. EngineerOS stores the
|
|
50
|
+
From **Project steering -> Workspace**, run the workspace assessment to use the connected agent subscription already authenticated on that computer. Select Architecture, Capabilities, Quality, or any combination when only part of the workspace needs reassessment. EngineerOS stores every completed assessment stage before starting the next one, resumes the unfinished stage after a connector restart, retry, or temporary connection loss, and then synthesizes the selected results into System State. The connector prints the current safe activity and elapsed time every 30 seconds, stops a stage that produces no agent activity for ten minutes instead of waiting indefinitely, and gives an incomplete final report one bounded correction turn instead of repeatedly restarting the stage. Capabilities are assessed one discovered capability at a time so a large repository does not depend on one unbounded agent turn. Assessment cannot modify the workspace.
|
|
51
51
|
|
|
52
52
|
After onboarding, every project prompt is routed to this connection. Copilot, shaping, planning, architecture, and experience generation use the connected agent subscription and workspace context. Interactive prompts run independently from assessments and Goal scheduling. Prompt runs are read-only; only an explicitly registered Goal Run receives workspace-write access. If the connector is offline, EngineerOS asks the user to reconnect instead of silently switching models.
|
|
53
53
|
|
|
@@ -12,14 +12,17 @@ import {
|
|
|
12
12
|
workspaceUrl,
|
|
13
13
|
} from "../src/config.mjs";
|
|
14
14
|
import {
|
|
15
|
+
assessmentInactivityFailure,
|
|
15
16
|
assessmentProgressMessage,
|
|
16
17
|
assessmentStreamDelta,
|
|
17
18
|
applyAcceptedChange,
|
|
19
|
+
enqueueWorkspaceAssessment,
|
|
18
20
|
executeAssignment,
|
|
19
21
|
executeConnectedPrompt,
|
|
20
22
|
executeWorkspaceAssessment,
|
|
21
23
|
inspectCodingAgent,
|
|
22
24
|
promptStreamEvent,
|
|
25
|
+
requeueInterruptedAssessment,
|
|
23
26
|
stopProcess,
|
|
24
27
|
workspaceSnapshot,
|
|
25
28
|
} from "../src/runner.mjs";
|
|
@@ -275,13 +278,15 @@ async function connect() {
|
|
|
275
278
|
return;
|
|
276
279
|
}
|
|
277
280
|
if (message.type === "workspace.assessment") {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
) {
|
|
284
|
-
|
|
281
|
+
const disposition = enqueueWorkspaceAssessment(
|
|
282
|
+
assessments,
|
|
283
|
+
active,
|
|
284
|
+
message,
|
|
285
|
+
);
|
|
286
|
+
if (disposition === "deferred") {
|
|
287
|
+
console.log(
|
|
288
|
+
`Assessment stage ${message.stage} is still active after reconnect; preserving the replay until it finishes.`,
|
|
289
|
+
);
|
|
285
290
|
}
|
|
286
291
|
pump();
|
|
287
292
|
return;
|
|
@@ -334,8 +339,9 @@ async function connect() {
|
|
|
334
339
|
clearInterval(pingTimer);
|
|
335
340
|
for (const promptState of activePrompts.values())
|
|
336
341
|
void cancelPrompt(promptState);
|
|
337
|
-
if (
|
|
338
|
-
|
|
342
|
+
if (active?.kind === "assessment") {
|
|
343
|
+
active.transportInterrupted = true;
|
|
344
|
+
if (event.code === 4001) void stopProcess(active.child);
|
|
339
345
|
}
|
|
340
346
|
if (connectionRejected) {
|
|
341
347
|
stopped = true;
|
|
@@ -460,12 +466,16 @@ function pump() {
|
|
|
460
466
|
if (active || socket.readyState !== WebSocket.OPEN) return;
|
|
461
467
|
const assessment = assessments.shift();
|
|
462
468
|
if (assessment) {
|
|
463
|
-
|
|
469
|
+
const assessmentState = {
|
|
464
470
|
kind: "assessment",
|
|
465
471
|
runId: assessment.assessment_id,
|
|
472
|
+
stage: assessment.stage,
|
|
466
473
|
child: null,
|
|
474
|
+
resumeAssignment: null,
|
|
475
|
+
transportInterrupted: false,
|
|
467
476
|
};
|
|
468
|
-
|
|
477
|
+
active = assessmentState;
|
|
478
|
+
void executeAssessment(assessment, assessmentState);
|
|
469
479
|
return;
|
|
470
480
|
}
|
|
471
481
|
const runId = available.shift();
|
|
@@ -575,23 +585,43 @@ async function cancelPrompt(promptState) {
|
|
|
575
585
|
await stopProcess(promptState.child);
|
|
576
586
|
}
|
|
577
587
|
|
|
578
|
-
async function executeAssessment(assignment) {
|
|
588
|
+
async function executeAssessment(assignment, assessmentState) {
|
|
579
589
|
const assessmentId = assignment.assessment_id;
|
|
580
|
-
|
|
581
|
-
|
|
590
|
+
const stage = assignment.stage;
|
|
591
|
+
console.log(`Agent is running assessment stage ${stage} (${assessmentId}).`);
|
|
592
|
+
const stageStartedAt = Date.now();
|
|
593
|
+
let lastAgentActivityAt = stageStartedAt;
|
|
594
|
+
let lastAgentStatus = "Starting the connected agent";
|
|
595
|
+
let agentEventCount = 0;
|
|
596
|
+
let agentProcessStarted = false;
|
|
597
|
+
let statusTicks = 0;
|
|
598
|
+
let progress = 5;
|
|
582
599
|
let outputBuffer = "";
|
|
583
600
|
let outputLength = 0;
|
|
584
601
|
let outputTimer;
|
|
585
|
-
|
|
602
|
+
let accepted = false;
|
|
603
|
+
let failureReported = false;
|
|
604
|
+
let inactivityFailure = null;
|
|
605
|
+
let lastReportedMilestone = null;
|
|
606
|
+
const creditedMilestones = new Set();
|
|
586
607
|
const reportProgress = (message, { milestone = true } = {}) => {
|
|
587
|
-
if (
|
|
588
|
-
|
|
589
|
-
if (milestone
|
|
590
|
-
|
|
608
|
+
if (active !== assessmentState) return;
|
|
609
|
+
if (milestone && lastReportedMilestone === message) return;
|
|
610
|
+
if (milestone) {
|
|
611
|
+
lastReportedMilestone = message;
|
|
612
|
+
if (!creditedMilestones.has(message)) {
|
|
613
|
+
creditedMilestones.add(message);
|
|
614
|
+
progress = Math.min(90, progress + 10);
|
|
615
|
+
}
|
|
616
|
+
lastAgentStatus = message;
|
|
617
|
+
console.log(`[assessment:${stage}] ${message}.`);
|
|
618
|
+
}
|
|
619
|
+
if (socket.readyState !== WebSocket.OPEN) return;
|
|
591
620
|
socket.send(
|
|
592
621
|
JSON.stringify({
|
|
593
622
|
type: "workspace.assessment.progress",
|
|
594
623
|
assessment_id: assessmentId,
|
|
624
|
+
stage,
|
|
595
625
|
progress_percent: progress,
|
|
596
626
|
message: String(message).slice(0, 500),
|
|
597
627
|
}),
|
|
@@ -605,7 +635,7 @@ async function executeAssessment(assignment) {
|
|
|
605
635
|
if (
|
|
606
636
|
!output ||
|
|
607
637
|
socket.readyState !== WebSocket.OPEN ||
|
|
608
|
-
active
|
|
638
|
+
active !== assessmentState
|
|
609
639
|
) {
|
|
610
640
|
return;
|
|
611
641
|
}
|
|
@@ -614,6 +644,7 @@ async function executeAssessment(assignment) {
|
|
|
614
644
|
JSON.stringify({
|
|
615
645
|
type: "workspace.assessment.output",
|
|
616
646
|
assessment_id: assessmentId,
|
|
647
|
+
stage,
|
|
617
648
|
delta: output.slice(offset, offset + 50_000),
|
|
618
649
|
}),
|
|
619
650
|
);
|
|
@@ -631,10 +662,29 @@ async function executeAssessment(assignment) {
|
|
|
631
662
|
outputTimer = setTimeout(flushOutput, 500);
|
|
632
663
|
}
|
|
633
664
|
};
|
|
634
|
-
reportProgress(
|
|
665
|
+
reportProgress(`Starting ${stage} assessment stage`);
|
|
635
666
|
const heartbeat = setInterval(() => {
|
|
636
|
-
|
|
667
|
+
const now = Date.now();
|
|
668
|
+
const inactiveMs = now - lastAgentActivityAt;
|
|
669
|
+
const nextInactivityFailure = agentProcessStarted
|
|
670
|
+
? assessmentInactivityFailure(stage, inactiveMs)
|
|
671
|
+
: null;
|
|
672
|
+
if (!inactivityFailure && nextInactivityFailure) {
|
|
673
|
+
inactivityFailure = nextInactivityFailure;
|
|
674
|
+
console.error(`[assessment:${stage}] ${inactivityFailure}`);
|
|
675
|
+
void stopProcess(assessmentState.child);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (inactivityFailure) return;
|
|
637
679
|
reportProgress("Assessment in progress", { milestone: false });
|
|
680
|
+
statusTicks += 1;
|
|
681
|
+
if (statusTicks % 2 === 0) {
|
|
682
|
+
console.log(
|
|
683
|
+
`[assessment:${stage}] ${formatAssessmentDuration(now - stageStartedAt)} elapsed · ` +
|
|
684
|
+
`${lastAgentStatus} · last agent activity ${formatAssessmentDuration(inactiveMs)} ago ` +
|
|
685
|
+
`(${agentEventCount.toLocaleString()} events).`,
|
|
686
|
+
);
|
|
687
|
+
}
|
|
638
688
|
}, 15_000);
|
|
639
689
|
try {
|
|
640
690
|
if (assignment.assessment_mode === "incremental") {
|
|
@@ -643,9 +693,16 @@ async function executeAssessment(assignment) {
|
|
|
643
693
|
}
|
|
644
694
|
const result = await executeWorkspaceAssessment(assignment, config, {
|
|
645
695
|
onProcess: (child) => {
|
|
646
|
-
if (active
|
|
696
|
+
if (active === assessmentState) {
|
|
697
|
+
assessmentState.child = child;
|
|
698
|
+
agentProcessStarted = true;
|
|
699
|
+
lastAgentActivityAt = Date.now();
|
|
700
|
+
reportProgress("Connected agent process started");
|
|
701
|
+
}
|
|
647
702
|
},
|
|
648
703
|
onEvent: (event) => {
|
|
704
|
+
lastAgentActivityAt = Date.now();
|
|
705
|
+
agentEventCount += 1;
|
|
649
706
|
reportOutput(assessmentStreamDelta(event));
|
|
650
707
|
const message = assessmentProgressMessage(event);
|
|
651
708
|
if (message) reportProgress(message);
|
|
@@ -668,28 +725,52 @@ async function executeAssessment(assignment) {
|
|
|
668
725
|
await describeRejectedResponse(response, "the assessment"),
|
|
669
726
|
);
|
|
670
727
|
}
|
|
671
|
-
|
|
728
|
+
accepted = true;
|
|
729
|
+
console.log(`Workspace assessment stage ${stage} was accepted by EngineerOS.`);
|
|
672
730
|
} catch (error) {
|
|
673
731
|
flushOutput();
|
|
674
|
-
const message = protocolFailureMessage(error);
|
|
675
|
-
if (
|
|
732
|
+
const message = inactivityFailure || protocolFailureMessage(error);
|
|
733
|
+
if (
|
|
734
|
+
!assessmentState.transportInterrupted &&
|
|
735
|
+
active === assessmentState &&
|
|
736
|
+
socket.readyState === WebSocket.OPEN
|
|
737
|
+
) {
|
|
676
738
|
socket.send(
|
|
677
739
|
JSON.stringify({
|
|
678
740
|
type: "workspace.assessment.failed",
|
|
679
741
|
assessment_id: assessmentId,
|
|
742
|
+
stage,
|
|
680
743
|
message,
|
|
681
744
|
}),
|
|
682
745
|
);
|
|
746
|
+
failureReported = true;
|
|
683
747
|
}
|
|
684
748
|
console.error(message);
|
|
685
749
|
} finally {
|
|
686
750
|
if (outputTimer) clearTimeout(outputTimer);
|
|
687
751
|
clearInterval(heartbeat);
|
|
688
|
-
active
|
|
689
|
-
|
|
752
|
+
if (active === assessmentState) {
|
|
753
|
+
active = null;
|
|
754
|
+
const replayQueued = requeueInterruptedAssessment(
|
|
755
|
+
assessments,
|
|
756
|
+
assessmentState,
|
|
757
|
+
{ accepted, failureReported },
|
|
758
|
+
);
|
|
759
|
+
if (replayQueued) {
|
|
760
|
+
console.log(`Restarting interrupted assessment stage ${stage}.`);
|
|
761
|
+
}
|
|
762
|
+
pump();
|
|
763
|
+
}
|
|
690
764
|
}
|
|
691
765
|
}
|
|
692
766
|
|
|
767
|
+
function formatAssessmentDuration(milliseconds) {
|
|
768
|
+
const totalSeconds = Math.max(0, Math.floor(milliseconds / 1_000));
|
|
769
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
770
|
+
const seconds = totalSeconds % 60;
|
|
771
|
+
return minutes ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
772
|
+
}
|
|
773
|
+
|
|
693
774
|
async function execute(assignment) {
|
|
694
775
|
const runId = assignment.run_id;
|
|
695
776
|
console.log(`Running Goal ${runId} with ${codingAgent.name}.`);
|
package/package.json
CHANGED
package/src/agent-harness.mjs
CHANGED
|
@@ -141,10 +141,15 @@ export function normalizeAgentStructuredOutput(
|
|
|
141
141
|
if (!content) {
|
|
142
142
|
throw new Error("Agent completed without returning a response.");
|
|
143
143
|
}
|
|
144
|
-
const firstSection = {
|
|
145
|
-
"# Workspace Assessment": "## Executive Summary",
|
|
146
|
-
"# Workspace Assessment Delta": "## Updated Executive Summary",
|
|
147
|
-
|
|
144
|
+
const firstSection = {
|
|
145
|
+
"# Workspace Assessment": "## Executive Summary",
|
|
146
|
+
"# Workspace Assessment Delta": "## Updated Executive Summary",
|
|
147
|
+
"# Assessment Stage: Architecture": "## Architecture Summary",
|
|
148
|
+
"# Assessment Stage: Capability Catalog": "## Capability Catalog",
|
|
149
|
+
"# Assessment Stage: Capabilities": "## Observed Capabilities",
|
|
150
|
+
"# Assessment Stage: Quality": "## Quality Summary",
|
|
151
|
+
"# Assessment Stage: Synthesis": "## Executive Summary",
|
|
152
|
+
}[outputHeading];
|
|
148
153
|
if (
|
|
149
154
|
firstSection &&
|
|
150
155
|
(content.startsWith(firstSection) ||
|
|
@@ -152,12 +157,34 @@ export function normalizeAgentStructuredOutput(
|
|
|
152
157
|
) {
|
|
153
158
|
return `${outputHeading}\n\n${content}`;
|
|
154
159
|
}
|
|
155
|
-
const lines = content.split(/\r?\n/);
|
|
156
|
-
const headingIndex = lines.findIndex(
|
|
157
|
-
(line) => line.trim() === outputHeading,
|
|
158
|
-
);
|
|
159
|
-
const
|
|
160
|
-
|
|
160
|
+
const lines = content.split(/\r?\n/);
|
|
161
|
+
const headingIndex = lines.findIndex(
|
|
162
|
+
(line) => line.trim() === outputHeading,
|
|
163
|
+
);
|
|
164
|
+
const firstSectionIndex = firstSection
|
|
165
|
+
? lines.findIndex((line) => line.trim() === firstSection)
|
|
166
|
+
: -1;
|
|
167
|
+
if (
|
|
168
|
+
headingIndex < 0 &&
|
|
169
|
+
outputHeading.startsWith("# Assessment Stage:") &&
|
|
170
|
+
firstSectionIndex >= 0
|
|
171
|
+
) {
|
|
172
|
+
const preamble = lines.slice(0, firstSectionIndex).join("\n");
|
|
173
|
+
if (preamble.length > 2_000) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Agent response contains too much text before the required section '${firstSection}'.`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const normalized = lines.slice(firstSectionIndex).join("\n").trim();
|
|
179
|
+
if (/```/.test(preamble) || /(?:^|\n)```\s*$/.test(normalized)) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Agent response must return '${outputHeading}' as plain Markdown, without a code fence.`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return `${outputHeading}\n\n${normalized}`;
|
|
185
|
+
}
|
|
186
|
+
const preamble =
|
|
187
|
+
headingIndex >= 0 ? lines.slice(0, headingIndex).join("\n") : content;
|
|
161
188
|
if (headingIndex < 0) {
|
|
162
189
|
throw new Error(
|
|
163
190
|
`Agent response is missing the required heading '${outputHeading}'.`,
|
package/src/runner.mjs
CHANGED
|
@@ -28,9 +28,10 @@ const MAX_ARCHIVE_BYTES = 25_000_000;
|
|
|
28
28
|
const MAX_EVIDENCE_BYTES = 24_000_000;
|
|
29
29
|
const MAX_SHAREABLE_FILE_BYTES = 5 * 1024 * 1024;
|
|
30
30
|
const MAX_EVIDENCE_FILES = 5_000;
|
|
31
|
-
const MAX_INVENTORY_FILES = 100_000;
|
|
32
|
-
const MAX_COMPRESSED_INVENTORY_BYTES = 10_000_000;
|
|
33
|
-
const EVIDENCE_POLICY_VERSION = "workspace-evidence-v1";
|
|
31
|
+
const MAX_INVENTORY_FILES = 100_000;
|
|
32
|
+
const MAX_COMPRESSED_INVENTORY_BYTES = 10_000_000;
|
|
33
|
+
const EVIDENCE_POLICY_VERSION = "workspace-evidence-v1";
|
|
34
|
+
export const ASSESSMENT_INACTIVITY_TIMEOUT_MS = 10 * 60 * 1_000;
|
|
34
35
|
const EXCLUDED_DIRECTORIES = new Set([
|
|
35
36
|
".agents",
|
|
36
37
|
".claude",
|
|
@@ -394,11 +395,31 @@ export async function executeWorkspaceAssessment(
|
|
|
394
395
|
execution.profile,
|
|
395
396
|
);
|
|
396
397
|
callbacks.onProcess?.(controller.child);
|
|
397
|
-
|
|
398
|
-
const
|
|
399
|
-
completed
|
|
400
|
-
|
|
401
|
-
|
|
398
|
+
let completed = await controller.completed;
|
|
399
|
+
const recovered = await recoverAssessmentStageOutput({
|
|
400
|
+
completed,
|
|
401
|
+
prompt,
|
|
402
|
+
requiredOutputHeading: execution.requiredOutputHeading,
|
|
403
|
+
retry: async (correctionPrompt, previousSessionId) => {
|
|
404
|
+
callbacks.onEvent?.({
|
|
405
|
+
type: "assessment.output_correction",
|
|
406
|
+
message: "The Agent is completing the required stage report structure",
|
|
407
|
+
});
|
|
408
|
+
const correction = launchAgentProcess(
|
|
409
|
+
config.workspace,
|
|
410
|
+
correctionPrompt,
|
|
411
|
+
execution.sandboxMode,
|
|
412
|
+
config,
|
|
413
|
+
callbacks,
|
|
414
|
+
execution.profile,
|
|
415
|
+
previousSessionId,
|
|
416
|
+
);
|
|
417
|
+
callbacks.onProcess?.(correction.child);
|
|
418
|
+
return correction.completed;
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
completed = recovered.completed;
|
|
422
|
+
const report = recovered.report;
|
|
402
423
|
const endingRevision = await run(
|
|
403
424
|
"git",
|
|
404
425
|
["rev-parse", "HEAD"],
|
|
@@ -414,8 +435,9 @@ export async function executeWorkspaceAssessment(
|
|
|
414
435
|
"The Git commit changed during assessment. Refresh the workspace inventory and assess the new commit.",
|
|
415
436
|
);
|
|
416
437
|
}
|
|
417
|
-
return {
|
|
418
|
-
|
|
438
|
+
return {
|
|
439
|
+
stage: assignment.stage,
|
|
440
|
+
report_markdown: report,
|
|
419
441
|
observed_head_revision: endingHead,
|
|
420
442
|
changed_files: changeImpact.changedFiles,
|
|
421
443
|
change_impact_markdown: changeImpact.markdown,
|
|
@@ -634,10 +656,23 @@ export function codexFailureMessage(output, code) {
|
|
|
634
656
|
return `Codex exited with code ${code}. ${output.slice(-1_000)}`;
|
|
635
657
|
}
|
|
636
658
|
|
|
637
|
-
export function assessmentProgressMessage(event) {
|
|
638
|
-
if (!event || typeof event !== "object") return null;
|
|
639
|
-
if (event.type === "
|
|
640
|
-
return "
|
|
659
|
+
export function assessmentProgressMessage(event) {
|
|
660
|
+
if (!event || typeof event !== "object") return null;
|
|
661
|
+
if (event.type === "assessment.output_correction")
|
|
662
|
+
return "Completing the required stage report structure";
|
|
663
|
+
if (event.type === "agent.connected")
|
|
664
|
+
return "Connected agent is ready to inspect repository evidence";
|
|
665
|
+
if (event.type === "acp.plan")
|
|
666
|
+
return "Organizing the repository assessment plan";
|
|
667
|
+
if (event.type === "acp.agent_thought_chunk")
|
|
668
|
+
return "Reasoning through repository evidence";
|
|
669
|
+
if (event.type === "acp.agent_message_chunk")
|
|
670
|
+
return "Drafting the evidence-backed stage report";
|
|
671
|
+
if (event.type === "acp.tool_call" || event.type === "acp.tool_call_update") {
|
|
672
|
+
return assessmentCommandMilestone(event.update?.title);
|
|
673
|
+
}
|
|
674
|
+
if (event.type === "turn.started")
|
|
675
|
+
return "Reviewing repository structure and current Git state";
|
|
641
676
|
if (event.type === "item.started") {
|
|
642
677
|
if (event.item?.type === "command_execution") {
|
|
643
678
|
return assessmentCommandMilestone(event.item.command);
|
|
@@ -651,8 +686,16 @@ export function assessmentProgressMessage(event) {
|
|
|
651
686
|
if (event.type === "item.completed" && event.item?.type === "agent_message") {
|
|
652
687
|
return "Synthesizing findings and highest-return actions";
|
|
653
688
|
}
|
|
654
|
-
return null;
|
|
655
|
-
}
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
export function assessmentInactivityFailure(stage, inactiveMs) {
|
|
693
|
+
if (inactiveMs < ASSESSMENT_INACTIVITY_TIMEOUT_MS) return null;
|
|
694
|
+
return (
|
|
695
|
+
`The connected agent produced no activity for 10 minutes during ${stage}. ` +
|
|
696
|
+
"The stage was stopped instead of waiting indefinitely. Retry it after checking the agent terminal."
|
|
697
|
+
);
|
|
698
|
+
}
|
|
656
699
|
|
|
657
700
|
export function promptProgressMessage(event) {
|
|
658
701
|
if (!event || typeof event !== "object") return null;
|
|
@@ -761,18 +804,160 @@ function assessmentCommandMilestone(command) {
|
|
|
761
804
|
return "Tracing architecture and code relationships";
|
|
762
805
|
}
|
|
763
806
|
|
|
764
|
-
export function workspaceAssessmentExecution(assignment) {
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
807
|
+
export function workspaceAssessmentExecution(assignment) {
|
|
808
|
+
const stage = assignment?.stage;
|
|
809
|
+
const requiredOutputHeading =
|
|
810
|
+
stage === "synthesis"
|
|
811
|
+
? "# Assessment Stage: Synthesis"
|
|
812
|
+
: stage === "capability_catalog"
|
|
813
|
+
? "# Assessment Stage: Capability Catalog"
|
|
814
|
+
: String(stage || "").startsWith("capability:")
|
|
815
|
+
? "# Assessment Stage: Capabilities"
|
|
816
|
+
: `# Assessment Stage: ${String(stage || "").replace(/^./, (value) => value.toUpperCase())}`;
|
|
817
|
+
if (
|
|
818
|
+
!new Set(["architecture", "capability_catalog", "quality", "synthesis"]).has(stage) &&
|
|
819
|
+
!String(stage || "").startsWith("capability:")
|
|
820
|
+
) {
|
|
821
|
+
throw new Error("EngineerOS assessment assignment has an unsupported stage.");
|
|
822
|
+
}
|
|
769
823
|
return {
|
|
770
824
|
...connectorExecution(assignment, { requiredOutputHeading }),
|
|
771
825
|
requiredOutputHeading,
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
export function
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
export async function recoverAssessmentStageOutput({
|
|
830
|
+
completed,
|
|
831
|
+
prompt,
|
|
832
|
+
requiredOutputHeading,
|
|
833
|
+
retry,
|
|
834
|
+
}) {
|
|
835
|
+
try {
|
|
836
|
+
return {
|
|
837
|
+
completed,
|
|
838
|
+
report: normalizeAgentStructuredOutput(
|
|
839
|
+
completed.finalMessage,
|
|
840
|
+
requiredOutputHeading,
|
|
841
|
+
),
|
|
842
|
+
};
|
|
843
|
+
} catch (error) {
|
|
844
|
+
if (!recoverableStructuredOutputFailure(error)) throw error;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const corrected = await retry(
|
|
848
|
+
assessmentStageCorrectionPrompt(prompt, requiredOutputHeading),
|
|
849
|
+
completed.sessionId,
|
|
850
|
+
);
|
|
851
|
+
try {
|
|
852
|
+
return {
|
|
853
|
+
completed: {
|
|
854
|
+
...corrected,
|
|
855
|
+
usage: mergeTokenUsage(completed.usage, corrected.usage),
|
|
856
|
+
},
|
|
857
|
+
report: normalizeAgentStructuredOutput(
|
|
858
|
+
corrected.finalMessage,
|
|
859
|
+
requiredOutputHeading,
|
|
860
|
+
),
|
|
861
|
+
};
|
|
862
|
+
} catch (error) {
|
|
863
|
+
throw new Error(
|
|
864
|
+
`Agent did not return the required stage report after one automatic correction attempt. ${error.message}`,
|
|
865
|
+
{ cause: error },
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
export function assessmentStageCorrectionPrompt(
|
|
871
|
+
originalPrompt,
|
|
872
|
+
requiredOutputHeading,
|
|
873
|
+
) {
|
|
874
|
+
return [
|
|
875
|
+
String(originalPrompt || "").trim(),
|
|
876
|
+
"",
|
|
877
|
+
"## Incomplete Stage Output Recovery",
|
|
878
|
+
"",
|
|
879
|
+
"The previous turn ended without a complete stage deliverable. Return the entire structured Markdown stage report now.",
|
|
880
|
+
"Use repository evidence already inspected in the previous turn when it is available; inspect only what remains necessary.",
|
|
881
|
+
`The first non-whitespace line of the final answer must be exactly: ${requiredOutputHeading}`,
|
|
882
|
+
"Follow every section, evidence, and completeness rule in the Assignment.",
|
|
883
|
+
"Do not return progress commentary, an explanation of the correction, or a code fence.",
|
|
884
|
+
].join("\n");
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function recoverableStructuredOutputFailure(error) {
|
|
888
|
+
const message = error instanceof Error ? error.message : "";
|
|
889
|
+
return (
|
|
890
|
+
message.startsWith("Agent completed") ||
|
|
891
|
+
message.startsWith("Agent response")
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function mergeTokenUsage(first, second) {
|
|
896
|
+
const usages = [first, second].filter(
|
|
897
|
+
(usage) => usage && typeof usage === "object",
|
|
898
|
+
);
|
|
899
|
+
if (!usages.length) return null;
|
|
900
|
+
return Object.fromEntries(
|
|
901
|
+
[
|
|
902
|
+
"input_tokens",
|
|
903
|
+
"output_tokens",
|
|
904
|
+
"cache_read_tokens",
|
|
905
|
+
"cache_write_tokens",
|
|
906
|
+
"reasoning_tokens",
|
|
907
|
+
"total_tokens",
|
|
908
|
+
].map((field) => [
|
|
909
|
+
field,
|
|
910
|
+
usages.reduce(
|
|
911
|
+
(total, usage) =>
|
|
912
|
+
total + (Number.isFinite(usage[field]) ? usage[field] : 0),
|
|
913
|
+
0,
|
|
914
|
+
),
|
|
915
|
+
]),
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
export function enqueueWorkspaceAssessment(
|
|
920
|
+
queue,
|
|
921
|
+
activeAssessment,
|
|
922
|
+
assignment,
|
|
923
|
+
{ front = false } = {},
|
|
924
|
+
) {
|
|
925
|
+
const matches = (candidate) =>
|
|
926
|
+
candidate?.assessment_id === assignment?.assessment_id &&
|
|
927
|
+
candidate?.stage === assignment?.stage;
|
|
928
|
+
if (
|
|
929
|
+
activeAssessment?.kind === "assessment" &&
|
|
930
|
+
activeAssessment.runId === assignment?.assessment_id &&
|
|
931
|
+
activeAssessment.stage === assignment?.stage
|
|
932
|
+
) {
|
|
933
|
+
activeAssessment.resumeAssignment = assignment;
|
|
934
|
+
return "deferred";
|
|
935
|
+
}
|
|
936
|
+
if (queue.some(matches)) return "duplicate";
|
|
937
|
+
if (front) queue.unshift(assignment);
|
|
938
|
+
else queue.push(assignment);
|
|
939
|
+
return "queued";
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
export function requeueInterruptedAssessment(
|
|
943
|
+
queue,
|
|
944
|
+
assessmentState,
|
|
945
|
+
{ accepted, failureReported },
|
|
946
|
+
) {
|
|
947
|
+
if (accepted || failureReported || !assessmentState?.resumeAssignment) {
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
return (
|
|
951
|
+
enqueueWorkspaceAssessment(
|
|
952
|
+
queue,
|
|
953
|
+
null,
|
|
954
|
+
assessmentState.resumeAssignment,
|
|
955
|
+
{ front: true },
|
|
956
|
+
) === "queued"
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
export function connectorExecution(assignment, options = {}) {
|
|
776
961
|
const rawPrompt = assignment?.prompt_markdown;
|
|
777
962
|
if (typeof rawPrompt !== "string" || !rawPrompt.trim()) {
|
|
778
963
|
throw new Error("EngineerOS assignment is missing prompt_markdown.");
|