@engineeros/connector 0.13.0 → 0.13.2
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 +100 -34
- package/package.json +1 -1
- package/src/acp-client.mjs +22 -20
- package/src/agent-harness.mjs +37 -10
- package/src/runner.mjs +198 -22
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. 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 or
|
|
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,16 +278,15 @@ async function connect() {
|
|
|
275
278
|
return;
|
|
276
279
|
}
|
|
277
280
|
if (message.type === "workspace.assessment") {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
assessments.push(message);
|
|
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
|
+
);
|
|
288
290
|
}
|
|
289
291
|
pump();
|
|
290
292
|
return;
|
|
@@ -337,8 +339,9 @@ async function connect() {
|
|
|
337
339
|
clearInterval(pingTimer);
|
|
338
340
|
for (const promptState of activePrompts.values())
|
|
339
341
|
void cancelPrompt(promptState);
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
+
if (active?.kind === "assessment") {
|
|
343
|
+
active.transportInterrupted = true;
|
|
344
|
+
if (event.code === 4001) void stopProcess(active.child);
|
|
342
345
|
}
|
|
343
346
|
if (connectionRejected) {
|
|
344
347
|
stopped = true;
|
|
@@ -463,13 +466,16 @@ function pump() {
|
|
|
463
466
|
if (active || socket.readyState !== WebSocket.OPEN) return;
|
|
464
467
|
const assessment = assessments.shift();
|
|
465
468
|
if (assessment) {
|
|
466
|
-
|
|
469
|
+
const assessmentState = {
|
|
467
470
|
kind: "assessment",
|
|
468
471
|
runId: assessment.assessment_id,
|
|
469
472
|
stage: assessment.stage,
|
|
470
473
|
child: null,
|
|
474
|
+
resumeAssignment: null,
|
|
475
|
+
transportInterrupted: false,
|
|
471
476
|
};
|
|
472
|
-
|
|
477
|
+
active = assessmentState;
|
|
478
|
+
void executeAssessment(assessment, assessmentState);
|
|
473
479
|
return;
|
|
474
480
|
}
|
|
475
481
|
const runId = available.shift();
|
|
@@ -579,24 +585,38 @@ async function cancelPrompt(promptState) {
|
|
|
579
585
|
await stopProcess(promptState.child);
|
|
580
586
|
}
|
|
581
587
|
|
|
582
|
-
async function executeAssessment(assignment) {
|
|
588
|
+
async function executeAssessment(assignment, assessmentState) {
|
|
583
589
|
const assessmentId = assignment.assessment_id;
|
|
584
590
|
const stage = assignment.stage;
|
|
585
591
|
console.log(`Agent is running assessment stage ${stage} (${assessmentId}).`);
|
|
586
|
-
|
|
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;
|
|
587
599
|
let outputBuffer = "";
|
|
588
600
|
let outputLength = 0;
|
|
589
601
|
let outputTimer;
|
|
590
|
-
|
|
602
|
+
let accepted = false;
|
|
603
|
+
let failureReported = false;
|
|
604
|
+
let inactivityFailure = null;
|
|
605
|
+
let lastReportedMilestone = null;
|
|
606
|
+
const creditedMilestones = new Set();
|
|
591
607
|
const reportProgress = (message, { milestone = true } = {}) => {
|
|
592
|
-
if (
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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;
|
|
600
620
|
socket.send(
|
|
601
621
|
JSON.stringify({
|
|
602
622
|
type: "workspace.assessment.progress",
|
|
@@ -615,8 +635,7 @@ async function executeAssessment(assignment) {
|
|
|
615
635
|
if (
|
|
616
636
|
!output ||
|
|
617
637
|
socket.readyState !== WebSocket.OPEN ||
|
|
618
|
-
active
|
|
619
|
-
active?.stage !== stage
|
|
638
|
+
active !== assessmentState
|
|
620
639
|
) {
|
|
621
640
|
return;
|
|
622
641
|
}
|
|
@@ -645,8 +664,27 @@ async function executeAssessment(assignment) {
|
|
|
645
664
|
};
|
|
646
665
|
reportProgress(`Starting ${stage} assessment stage`);
|
|
647
666
|
const heartbeat = setInterval(() => {
|
|
648
|
-
|
|
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;
|
|
649
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
|
+
}
|
|
650
688
|
}, 15_000);
|
|
651
689
|
try {
|
|
652
690
|
if (assignment.assessment_mode === "incremental") {
|
|
@@ -655,11 +693,16 @@ async function executeAssessment(assignment) {
|
|
|
655
693
|
}
|
|
656
694
|
const result = await executeWorkspaceAssessment(assignment, config, {
|
|
657
695
|
onProcess: (child) => {
|
|
658
|
-
if (active
|
|
659
|
-
|
|
696
|
+
if (active === assessmentState) {
|
|
697
|
+
assessmentState.child = child;
|
|
698
|
+
agentProcessStarted = true;
|
|
699
|
+
lastAgentActivityAt = Date.now();
|
|
700
|
+
reportProgress("Connected agent process started");
|
|
660
701
|
}
|
|
661
702
|
},
|
|
662
703
|
onEvent: (event) => {
|
|
704
|
+
lastAgentActivityAt = Date.now();
|
|
705
|
+
agentEventCount += 1;
|
|
663
706
|
reportOutput(assessmentStreamDelta(event));
|
|
664
707
|
const message = assessmentProgressMessage(event);
|
|
665
708
|
if (message) reportProgress(message);
|
|
@@ -682,11 +725,16 @@ async function executeAssessment(assignment) {
|
|
|
682
725
|
await describeRejectedResponse(response, "the assessment"),
|
|
683
726
|
);
|
|
684
727
|
}
|
|
728
|
+
accepted = true;
|
|
685
729
|
console.log(`Workspace assessment stage ${stage} was accepted by EngineerOS.`);
|
|
686
730
|
} catch (error) {
|
|
687
731
|
flushOutput();
|
|
688
|
-
const message = protocolFailureMessage(error);
|
|
689
|
-
if (
|
|
732
|
+
const message = inactivityFailure || protocolFailureMessage(error);
|
|
733
|
+
if (
|
|
734
|
+
!assessmentState.transportInterrupted &&
|
|
735
|
+
active === assessmentState &&
|
|
736
|
+
socket.readyState === WebSocket.OPEN
|
|
737
|
+
) {
|
|
690
738
|
socket.send(
|
|
691
739
|
JSON.stringify({
|
|
692
740
|
type: "workspace.assessment.failed",
|
|
@@ -695,16 +743,34 @@ async function executeAssessment(assignment) {
|
|
|
695
743
|
message,
|
|
696
744
|
}),
|
|
697
745
|
);
|
|
746
|
+
failureReported = true;
|
|
698
747
|
}
|
|
699
748
|
console.error(message);
|
|
700
749
|
} finally {
|
|
701
750
|
if (outputTimer) clearTimeout(outputTimer);
|
|
702
751
|
clearInterval(heartbeat);
|
|
703
|
-
active
|
|
704
|
-
|
|
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
|
+
}
|
|
705
764
|
}
|
|
706
765
|
}
|
|
707
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
|
+
|
|
708
774
|
async function execute(assignment) {
|
|
709
775
|
const runId = assignment.run_id;
|
|
710
776
|
console.log(`Running Goal ${runId} with ${codingAgent.name}.`);
|
package/package.json
CHANGED
package/src/acp-client.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import * as acp from "@agentclientprotocol/sdk";
|
|
|
5
5
|
const RUNTIME_IDLE_MS = 30 * 60 * 1_000;
|
|
6
6
|
const runtimes = new Map();
|
|
7
7
|
|
|
8
|
-
export function permissionOutcome(options = [], allow = false) {
|
|
8
|
+
export function permissionOutcome(options = [], allow = false) {
|
|
9
9
|
if (!allow) return { outcome: { outcome: "cancelled" } };
|
|
10
10
|
const selected =
|
|
11
11
|
options.find((option) => option.kind === "allow_once") ??
|
|
@@ -13,9 +13,9 @@ export function permissionOutcome(options = [], allow = false) {
|
|
|
13
13
|
return selected
|
|
14
14
|
? { outcome: { outcome: "selected", optionId: selected.optionId } }
|
|
15
15
|
: { outcome: { outcome: "cancelled" } };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function launchAcpAgent(
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function launchAcpAgent(
|
|
19
19
|
workspace,
|
|
20
20
|
prompt,
|
|
21
21
|
config,
|
|
@@ -39,7 +39,7 @@ export function launchAcpAgent(
|
|
|
39
39
|
if (runtimeKey) runtimes.set(runtimeKey, runtime);
|
|
40
40
|
}
|
|
41
41
|
const sessionKey = options.sessionKey || `isolated-${crypto.randomUUID()}`;
|
|
42
|
-
const completed = runtime
|
|
42
|
+
const completed = runtime
|
|
43
43
|
.prompt({
|
|
44
44
|
sessionKey,
|
|
45
45
|
prompt,
|
|
@@ -49,9 +49,10 @@ export function launchAcpAgent(
|
|
|
49
49
|
callbacks,
|
|
50
50
|
})
|
|
51
51
|
.finally(async () => {
|
|
52
|
-
if (!persistent) await runtime.dispose();
|
|
53
|
-
});
|
|
54
|
-
|
|
52
|
+
if (!persistent) await runtime.dispose();
|
|
53
|
+
});
|
|
54
|
+
runtime.child.engineerOsCancel = () => runtime.dispose();
|
|
55
|
+
return {
|
|
55
56
|
child: runtime.child,
|
|
56
57
|
completed,
|
|
57
58
|
cancel: () => runtime.cancel(sessionKey),
|
|
@@ -117,18 +118,19 @@ class AcpRuntime {
|
|
|
117
118
|
this.stderr = "";
|
|
118
119
|
this.disposed = false;
|
|
119
120
|
this.idleTimer = null;
|
|
120
|
-
this.child = spawn(
|
|
121
|
-
config.agent_command,
|
|
122
|
-
Array.isArray(config.agent_args) ? config.agent_args : [],
|
|
123
|
-
{
|
|
124
|
-
cwd: workspace,
|
|
125
|
-
env: { ...process.env, ...(config.agent_env || {}) },
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
121
|
+
this.child = spawn(
|
|
122
|
+
config.agent_command,
|
|
123
|
+
Array.isArray(config.agent_args) ? config.agent_args : [],
|
|
124
|
+
{
|
|
125
|
+
cwd: workspace,
|
|
126
|
+
env: { ...process.env, ...(config.agent_env || {}) },
|
|
127
|
+
windowsHide: true,
|
|
128
|
+
shell:
|
|
129
|
+
process.platform === "win32" &&
|
|
130
|
+
/\.(cmd|bat)$/i.test(config.agent_command),
|
|
131
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
132
|
+
},
|
|
133
|
+
);
|
|
132
134
|
this.child.stderr.setEncoding("utf8");
|
|
133
135
|
this.child.stderr.on("data", (chunk) => {
|
|
134
136
|
this.stderr = `${this.stderr}${chunk}`.slice(-4_000);
|
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"],
|
|
@@ -635,10 +656,23 @@ export function codexFailureMessage(output, code) {
|
|
|
635
656
|
return `Codex exited with code ${code}. ${output.slice(-1_000)}`;
|
|
636
657
|
}
|
|
637
658
|
|
|
638
|
-
export function assessmentProgressMessage(event) {
|
|
639
|
-
if (!event || typeof event !== "object") return null;
|
|
640
|
-
if (event.type === "
|
|
641
|
-
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";
|
|
642
676
|
if (event.type === "item.started") {
|
|
643
677
|
if (event.item?.type === "command_execution") {
|
|
644
678
|
return assessmentCommandMilestone(event.item.command);
|
|
@@ -652,8 +686,16 @@ export function assessmentProgressMessage(event) {
|
|
|
652
686
|
if (event.type === "item.completed" && event.item?.type === "agent_message") {
|
|
653
687
|
return "Synthesizing findings and highest-return actions";
|
|
654
688
|
}
|
|
655
|
-
return null;
|
|
656
|
-
}
|
|
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
|
+
}
|
|
657
699
|
|
|
658
700
|
export function promptProgressMessage(event) {
|
|
659
701
|
if (!event || typeof event !== "object") return null;
|
|
@@ -781,10 +823,141 @@ export function workspaceAssessmentExecution(assignment) {
|
|
|
781
823
|
return {
|
|
782
824
|
...connectorExecution(assignment, { requiredOutputHeading }),
|
|
783
825
|
requiredOutputHeading,
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
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 = {}) {
|
|
788
961
|
const rawPrompt = assignment?.prompt_markdown;
|
|
789
962
|
if (typeof rawPrompt !== "string" || !rawPrompt.trim()) {
|
|
790
963
|
throw new Error("EngineerOS assignment is missing prompt_markdown.");
|
|
@@ -834,10 +1007,13 @@ export function connectorExecution(assignment, options = {}) {
|
|
|
834
1007
|
};
|
|
835
1008
|
}
|
|
836
1009
|
|
|
837
|
-
export async function stopProcess(child) {
|
|
838
|
-
if (!child || child.exitCode !== null) return;
|
|
839
|
-
|
|
840
|
-
|
|
1010
|
+
export async function stopProcess(child) {
|
|
1011
|
+
if (!child || child.exitCode !== null) return;
|
|
1012
|
+
if (typeof child.engineerOsCancel === "function") {
|
|
1013
|
+
await child.engineerOsCancel();
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
if (process.platform === "win32") {
|
|
841
1017
|
await run(
|
|
842
1018
|
"taskkill",
|
|
843
1019
|
["/pid", String(child.pid), "/t", "/f"],
|