@engineeros/connector 0.13.3 → 0.14.0
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 +31 -16
- package/package.json +1 -1
- package/src/runner.mjs +20 -12
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
|
|
50
|
+
From **Project steering -> Workspace**, run the workspace assessment to use the connected agent subscription already authenticated on that computer. Select any combination of fifteen brownfield evidence domains, collectively covering 133 baseline checks from product and architecture through security, privacy, project-specific compliance, delivery, reliability, governance, and modernization planning. Every domain uses a detailed structured-Markdown contract. EngineerOS stores each completed stage, resumes unfinished work after a connector restart, retry, or temporary connection loss, and then synthesizes the selected results into System State. Connector `0.14.0` runs up to three dependency-ready read-only assessment workers concurrently; capability detail and project-specific compliance controls fan out after their catalogs, and final synthesis waits for every selected result. Compliance findings describe repository-verifiable engineering readiness and missing external evidence, never legal compliance or certification. Each result, live output, validation correction, and retry remains independently durable. The connector prints each worker's safe activity and elapsed time every 30 seconds, stops a worker that produces no agent activity for ten minutes instead of waiting indefinitely, and gives an incomplete report one bounded correction turn instead of repeatedly restarting it. 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
|
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
promptStreamEvent,
|
|
25
25
|
requeueInterruptedAssessment,
|
|
26
26
|
stopProcess,
|
|
27
|
+
takeWorkspaceAssessmentWave,
|
|
27
28
|
workspaceSnapshot,
|
|
28
29
|
} from "../src/runner.mjs";
|
|
29
30
|
import { disposeAcpRuntimes } from "../src/acp-client.mjs";
|
|
@@ -195,6 +196,8 @@ firstMessage.capabilities = capabilities;
|
|
|
195
196
|
|
|
196
197
|
let stopped = false;
|
|
197
198
|
let active = null;
|
|
199
|
+
const activeAssessments = new Map();
|
|
200
|
+
const ASSESSMENT_WORKER_LIMIT = 3;
|
|
198
201
|
const available = [];
|
|
199
202
|
const assessments = [];
|
|
200
203
|
const activePrompts = new Map();
|
|
@@ -212,7 +215,10 @@ process.on("SIGINT", async () => {
|
|
|
212
215
|
clearConnectionWatchdog?.();
|
|
213
216
|
clearTimeout(reconnectTimer);
|
|
214
217
|
clearInterval(pingTimer);
|
|
215
|
-
await
|
|
218
|
+
await Promise.all([
|
|
219
|
+
stopProcess(active?.child),
|
|
220
|
+
...[...activeAssessments.values()].map((state) => stopProcess(state.child)),
|
|
221
|
+
]);
|
|
216
222
|
await Promise.all([...activePrompts.values()].map(cancelPrompt));
|
|
217
223
|
await disposeAcpRuntimes();
|
|
218
224
|
socket?.close();
|
|
@@ -278,9 +284,10 @@ async function connect() {
|
|
|
278
284
|
return;
|
|
279
285
|
}
|
|
280
286
|
if (message.type === "workspace.assessment") {
|
|
287
|
+
const assessmentKey = `${message.assessment_id}:${message.stage}`;
|
|
281
288
|
const disposition = enqueueWorkspaceAssessment(
|
|
282
289
|
assessments,
|
|
283
|
-
|
|
290
|
+
activeAssessments.get(assessmentKey),
|
|
284
291
|
message,
|
|
285
292
|
);
|
|
286
293
|
if (disposition === "deferred") {
|
|
@@ -339,9 +346,9 @@ async function connect() {
|
|
|
339
346
|
clearInterval(pingTimer);
|
|
340
347
|
for (const promptState of activePrompts.values())
|
|
341
348
|
void cancelPrompt(promptState);
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if (event.code === 4001) void stopProcess(
|
|
349
|
+
for (const assessmentState of activeAssessments.values()) {
|
|
350
|
+
assessmentState.transportInterrupted = true;
|
|
351
|
+
if (event.code === 4001) void stopProcess(assessmentState.child);
|
|
345
352
|
}
|
|
346
353
|
if (connectionRejected) {
|
|
347
354
|
stopped = true;
|
|
@@ -463,21 +470,27 @@ function sendPromptEvent(promptId, event) {
|
|
|
463
470
|
}
|
|
464
471
|
|
|
465
472
|
function pump() {
|
|
466
|
-
if (
|
|
467
|
-
const
|
|
468
|
-
|
|
473
|
+
if (socket.readyState !== WebSocket.OPEN || active) return;
|
|
474
|
+
const wave = takeWorkspaceAssessmentWave(
|
|
475
|
+
assessments,
|
|
476
|
+
activeAssessments.size,
|
|
477
|
+
ASSESSMENT_WORKER_LIMIT,
|
|
478
|
+
);
|
|
479
|
+
for (const assessment of wave) {
|
|
480
|
+
const assessmentKey = `${assessment.assessment_id}:${assessment.stage}`;
|
|
469
481
|
const assessmentState = {
|
|
470
482
|
kind: "assessment",
|
|
483
|
+
key: assessmentKey,
|
|
471
484
|
runId: assessment.assessment_id,
|
|
472
485
|
stage: assessment.stage,
|
|
473
486
|
child: null,
|
|
474
487
|
resumeAssignment: null,
|
|
475
488
|
transportInterrupted: false,
|
|
476
489
|
};
|
|
477
|
-
|
|
490
|
+
activeAssessments.set(assessmentKey, assessmentState);
|
|
478
491
|
void executeAssessment(assessment, assessmentState);
|
|
479
|
-
return;
|
|
480
492
|
}
|
|
493
|
+
if (activeAssessments.size > 0 || assessments.length > 0) return;
|
|
481
494
|
const runId = available.shift();
|
|
482
495
|
if (!runId) return;
|
|
483
496
|
active = { kind: "goal", runId, child: null, cancelled: false };
|
|
@@ -604,8 +617,10 @@ async function executeAssessment(assignment, assessmentState) {
|
|
|
604
617
|
let inactivityFailure = null;
|
|
605
618
|
let lastReportedMilestone = null;
|
|
606
619
|
const creditedMilestones = new Set();
|
|
620
|
+
const isActiveAssessment = () =>
|
|
621
|
+
activeAssessments.get(assessmentState.key) === assessmentState;
|
|
607
622
|
const reportProgress = (message, { milestone = true } = {}) => {
|
|
608
|
-
if (
|
|
623
|
+
if (!isActiveAssessment()) return;
|
|
609
624
|
if (milestone && lastReportedMilestone === message) return;
|
|
610
625
|
if (milestone) {
|
|
611
626
|
lastReportedMilestone = message;
|
|
@@ -635,7 +650,7 @@ async function executeAssessment(assignment, assessmentState) {
|
|
|
635
650
|
if (
|
|
636
651
|
!output ||
|
|
637
652
|
socket.readyState !== WebSocket.OPEN ||
|
|
638
|
-
|
|
653
|
+
!isActiveAssessment()
|
|
639
654
|
) {
|
|
640
655
|
return;
|
|
641
656
|
}
|
|
@@ -693,7 +708,7 @@ async function executeAssessment(assignment, assessmentState) {
|
|
|
693
708
|
}
|
|
694
709
|
let result = await executeWorkspaceAssessment(assignment, config, {
|
|
695
710
|
onProcess: (child) => {
|
|
696
|
-
if (
|
|
711
|
+
if (isActiveAssessment()) {
|
|
697
712
|
assessmentState.child = child;
|
|
698
713
|
agentProcessStarted = true;
|
|
699
714
|
lastAgentActivityAt = Date.now();
|
|
@@ -747,7 +762,7 @@ async function executeAssessment(assignment, assessmentState) {
|
|
|
747
762
|
const message = inactivityFailure || protocolFailureMessage(error);
|
|
748
763
|
if (
|
|
749
764
|
!assessmentState.transportInterrupted &&
|
|
750
|
-
|
|
765
|
+
isActiveAssessment() &&
|
|
751
766
|
socket.readyState === WebSocket.OPEN
|
|
752
767
|
) {
|
|
753
768
|
socket.send(
|
|
@@ -764,8 +779,8 @@ async function executeAssessment(assignment, assessmentState) {
|
|
|
764
779
|
} finally {
|
|
765
780
|
if (outputTimer) clearTimeout(outputTimer);
|
|
766
781
|
clearInterval(heartbeat);
|
|
767
|
-
if (
|
|
768
|
-
|
|
782
|
+
if (isActiveAssessment()) {
|
|
783
|
+
activeAssessments.delete(assessmentState.key);
|
|
769
784
|
const replayQueued = requeueInterruptedAssessment(
|
|
770
785
|
assessments,
|
|
771
786
|
assessmentState,
|
package/package.json
CHANGED
package/src/runner.mjs
CHANGED
|
@@ -845,18 +845,10 @@ function assessmentCommandMilestone(command) {
|
|
|
845
845
|
|
|
846
846
|
export function workspaceAssessmentExecution(assignment) {
|
|
847
847
|
const stage = assignment?.stage;
|
|
848
|
-
const requiredOutputHeading =
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
? "# Assessment Stage: Capability Catalog"
|
|
853
|
-
: String(stage || "").startsWith("capability:")
|
|
854
|
-
? "# Assessment Stage: Capabilities"
|
|
855
|
-
: `# Assessment Stage: ${String(stage || "").replace(/^./, (value) => value.toUpperCase())}`;
|
|
856
|
-
if (
|
|
857
|
-
!new Set(["architecture", "capability_catalog", "quality", "synthesis"]).has(stage) &&
|
|
858
|
-
!String(stage || "").startsWith("capability:")
|
|
859
|
-
) {
|
|
848
|
+
const requiredOutputHeading = String(
|
|
849
|
+
assignment?.required_output_heading || "",
|
|
850
|
+
).trim();
|
|
851
|
+
if (!stage || !requiredOutputHeading.startsWith("# Assessment Stage: ")) {
|
|
860
852
|
throw new Error("EngineerOS assessment assignment has an unsupported stage.");
|
|
861
853
|
}
|
|
862
854
|
return {
|
|
@@ -1000,6 +992,22 @@ export function enqueueWorkspaceAssessment(
|
|
|
1000
992
|
return "queued";
|
|
1001
993
|
}
|
|
1002
994
|
|
|
995
|
+
export function takeWorkspaceAssessmentWave(queue, activeCount, limit = 3) {
|
|
996
|
+
const available = Math.max(0, limit - activeCount);
|
|
997
|
+
if (!available || !queue.length) return [];
|
|
998
|
+
if (queue[0]?.parallelizable !== true) {
|
|
999
|
+
return activeCount === 0 ? queue.splice(0, 1) : [];
|
|
1000
|
+
}
|
|
1001
|
+
const wave = [];
|
|
1002
|
+
while (
|
|
1003
|
+
wave.length < available &&
|
|
1004
|
+
queue[0]?.parallelizable === true
|
|
1005
|
+
) {
|
|
1006
|
+
wave.push(queue.shift());
|
|
1007
|
+
}
|
|
1008
|
+
return wave;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1003
1011
|
export function requeueInterruptedAssessment(
|
|
1004
1012
|
queue,
|
|
1005
1013
|
assessmentState,
|