@engineeros/connector 0.12.4 → 0.13.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 +40 -22
- package/package.json +1 -1
- package/src/connection.mjs +48 -0
- package/src/runner.mjs +19 -7
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 or retry, and then synthesizes the selected results into System State. 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
|
|
|
@@ -31,7 +31,9 @@ import {
|
|
|
31
31
|
registeredAgents,
|
|
32
32
|
} from "../src/agent-registry.mjs";
|
|
33
33
|
import {
|
|
34
|
+
describeRejectedResponse,
|
|
34
35
|
describeWebSocketError,
|
|
36
|
+
protocolFailureMessage,
|
|
35
37
|
startConnectionWatchdog,
|
|
36
38
|
} from "../src/connection.mjs";
|
|
37
39
|
import { advertisedCapabilities } from "../src/capabilities.mjs";
|
|
@@ -274,9 +276,12 @@ async function connect() {
|
|
|
274
276
|
}
|
|
275
277
|
if (message.type === "workspace.assessment") {
|
|
276
278
|
if (
|
|
277
|
-
active?.runId !== message.assessment_id
|
|
279
|
+
(active?.runId !== message.assessment_id ||
|
|
280
|
+
active?.stage !== message.stage) &&
|
|
278
281
|
!assessments.some(
|
|
279
|
-
(candidate) =>
|
|
282
|
+
(candidate) =>
|
|
283
|
+
candidate.assessment_id === message.assessment_id &&
|
|
284
|
+
candidate.stage === message.stage,
|
|
280
285
|
)
|
|
281
286
|
) {
|
|
282
287
|
assessments.push(message);
|
|
@@ -387,7 +392,7 @@ async function submitWorkspaceSnapshot() {
|
|
|
387
392
|
);
|
|
388
393
|
if (!response.ok) {
|
|
389
394
|
throw new Error(
|
|
390
|
-
|
|
395
|
+
await describeRejectedResponse(response, "the workspace"),
|
|
391
396
|
);
|
|
392
397
|
}
|
|
393
398
|
const result = await response.json();
|
|
@@ -422,7 +427,7 @@ async function submitWorkspaceSnapshot() {
|
|
|
422
427
|
} catch (error) {
|
|
423
428
|
snapshotInFlight = false;
|
|
424
429
|
console.error(
|
|
425
|
-
`Workspace assessment failed: ${
|
|
430
|
+
`Workspace assessment failed: ${protocolFailureMessage(error)}`,
|
|
426
431
|
);
|
|
427
432
|
}
|
|
428
433
|
}
|
|
@@ -461,6 +466,7 @@ function pump() {
|
|
|
461
466
|
active = {
|
|
462
467
|
kind: "assessment",
|
|
463
468
|
runId: assessment.assessment_id,
|
|
469
|
+
stage: assessment.stage,
|
|
464
470
|
child: null,
|
|
465
471
|
};
|
|
466
472
|
void executeAssessment(assessment);
|
|
@@ -545,17 +551,18 @@ async function executePrompt(assignment) {
|
|
|
545
551
|
}),
|
|
546
552
|
);
|
|
547
553
|
} catch (error) {
|
|
554
|
+
const message = protocolFailureMessage(error);
|
|
548
555
|
if (!promptState.cancelled && socket.readyState === WebSocket.OPEN) {
|
|
549
556
|
socket.send(
|
|
550
557
|
JSON.stringify({
|
|
551
558
|
type: "prompt.failed",
|
|
552
559
|
prompt_id: promptId,
|
|
553
|
-
message
|
|
560
|
+
message,
|
|
554
561
|
}),
|
|
555
562
|
);
|
|
556
563
|
}
|
|
557
564
|
if (!promptState.cancelled) {
|
|
558
|
-
console.error(
|
|
565
|
+
console.error(message);
|
|
559
566
|
}
|
|
560
567
|
} finally {
|
|
561
568
|
if (activePrompts.get(promptId) === promptState)
|
|
@@ -574,14 +581,19 @@ async function cancelPrompt(promptState) {
|
|
|
574
581
|
|
|
575
582
|
async function executeAssessment(assignment) {
|
|
576
583
|
const assessmentId = assignment.assessment_id;
|
|
577
|
-
|
|
584
|
+
const stage = assignment.stage;
|
|
585
|
+
console.log(`Agent is running assessment stage ${stage} (${assessmentId}).`);
|
|
578
586
|
let progress = 10;
|
|
579
587
|
let outputBuffer = "";
|
|
580
588
|
let outputLength = 0;
|
|
581
589
|
let outputTimer;
|
|
582
590
|
const reportedMilestones = new Set();
|
|
583
591
|
const reportProgress = (message, { milestone = true } = {}) => {
|
|
584
|
-
if (
|
|
592
|
+
if (
|
|
593
|
+
socket.readyState !== WebSocket.OPEN ||
|
|
594
|
+
active?.runId !== assessmentId ||
|
|
595
|
+
active?.stage !== stage
|
|
596
|
+
)
|
|
585
597
|
return;
|
|
586
598
|
if (milestone && reportedMilestones.has(message)) return;
|
|
587
599
|
if (milestone) reportedMilestones.add(message);
|
|
@@ -589,6 +601,7 @@ async function executeAssessment(assignment) {
|
|
|
589
601
|
JSON.stringify({
|
|
590
602
|
type: "workspace.assessment.progress",
|
|
591
603
|
assessment_id: assessmentId,
|
|
604
|
+
stage,
|
|
592
605
|
progress_percent: progress,
|
|
593
606
|
message: String(message).slice(0, 500),
|
|
594
607
|
}),
|
|
@@ -602,7 +615,8 @@ async function executeAssessment(assignment) {
|
|
|
602
615
|
if (
|
|
603
616
|
!output ||
|
|
604
617
|
socket.readyState !== WebSocket.OPEN ||
|
|
605
|
-
active?.runId !== assessmentId
|
|
618
|
+
active?.runId !== assessmentId ||
|
|
619
|
+
active?.stage !== stage
|
|
606
620
|
) {
|
|
607
621
|
return;
|
|
608
622
|
}
|
|
@@ -611,6 +625,7 @@ async function executeAssessment(assignment) {
|
|
|
611
625
|
JSON.stringify({
|
|
612
626
|
type: "workspace.assessment.output",
|
|
613
627
|
assessment_id: assessmentId,
|
|
628
|
+
stage,
|
|
614
629
|
delta: output.slice(offset, offset + 50_000),
|
|
615
630
|
}),
|
|
616
631
|
);
|
|
@@ -628,7 +643,7 @@ async function executeAssessment(assignment) {
|
|
|
628
643
|
outputTimer = setTimeout(flushOutput, 500);
|
|
629
644
|
}
|
|
630
645
|
};
|
|
631
|
-
reportProgress(
|
|
646
|
+
reportProgress(`Starting ${stage} assessment stage`);
|
|
632
647
|
const heartbeat = setInterval(() => {
|
|
633
648
|
progress = Math.min(90, progress + 5);
|
|
634
649
|
reportProgress("Assessment in progress", { milestone: false });
|
|
@@ -640,7 +655,9 @@ async function executeAssessment(assignment) {
|
|
|
640
655
|
}
|
|
641
656
|
const result = await executeWorkspaceAssessment(assignment, config, {
|
|
642
657
|
onProcess: (child) => {
|
|
643
|
-
if (active?.runId === assessmentId
|
|
658
|
+
if (active?.runId === assessmentId && active?.stage === stage) {
|
|
659
|
+
active.child = child;
|
|
660
|
+
}
|
|
644
661
|
},
|
|
645
662
|
onEvent: (event) => {
|
|
646
663
|
reportOutput(assessmentStreamDelta(event));
|
|
@@ -662,22 +679,24 @@ async function executeAssessment(assignment) {
|
|
|
662
679
|
);
|
|
663
680
|
if (!response.ok) {
|
|
664
681
|
throw new Error(
|
|
665
|
-
|
|
682
|
+
await describeRejectedResponse(response, "the assessment"),
|
|
666
683
|
);
|
|
667
684
|
}
|
|
668
|
-
console.log(
|
|
685
|
+
console.log(`Workspace assessment stage ${stage} was accepted by EngineerOS.`);
|
|
669
686
|
} catch (error) {
|
|
670
687
|
flushOutput();
|
|
688
|
+
const message = protocolFailureMessage(error);
|
|
671
689
|
if (socket.readyState === WebSocket.OPEN) {
|
|
672
690
|
socket.send(
|
|
673
691
|
JSON.stringify({
|
|
674
692
|
type: "workspace.assessment.failed",
|
|
675
693
|
assessment_id: assessmentId,
|
|
676
|
-
|
|
694
|
+
stage,
|
|
695
|
+
message,
|
|
677
696
|
}),
|
|
678
697
|
);
|
|
679
698
|
}
|
|
680
|
-
console.error(
|
|
699
|
+
console.error(message);
|
|
681
700
|
} finally {
|
|
682
701
|
if (outputTimer) clearTimeout(outputTimer);
|
|
683
702
|
clearInterval(heartbeat);
|
|
@@ -735,10 +754,9 @@ async function execute(assignment) {
|
|
|
735
754
|
body: JSON.stringify(result),
|
|
736
755
|
},
|
|
737
756
|
);
|
|
738
|
-
if (!response.ok)
|
|
739
|
-
throw new Error(
|
|
740
|
-
|
|
741
|
-
);
|
|
757
|
+
if (!response.ok) {
|
|
758
|
+
throw new Error(await describeRejectedResponse(response, "the result"));
|
|
759
|
+
}
|
|
742
760
|
const integration = await applyAcceptedChange(
|
|
743
761
|
config.workspace,
|
|
744
762
|
assignment.base_revision,
|
|
@@ -757,17 +775,17 @@ async function execute(assignment) {
|
|
|
757
775
|
);
|
|
758
776
|
}
|
|
759
777
|
} catch (error) {
|
|
778
|
+
const message = protocolFailureMessage(error);
|
|
760
779
|
if (!active?.cancelled && socket.readyState === WebSocket.OPEN) {
|
|
761
780
|
socket.send(
|
|
762
781
|
JSON.stringify({
|
|
763
782
|
type: "run.failed",
|
|
764
783
|
run_id: runId,
|
|
765
|
-
message
|
|
784
|
+
message,
|
|
766
785
|
}),
|
|
767
786
|
);
|
|
768
787
|
}
|
|
769
|
-
if (!active?.cancelled)
|
|
770
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
788
|
+
if (!active?.cancelled) console.error(message);
|
|
771
789
|
} finally {
|
|
772
790
|
clearInterval(heartbeat);
|
|
773
791
|
active = null;
|
package/package.json
CHANGED
package/src/connection.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const PROTOCOL_FAILURE_MESSAGE_MAX_LENGTH = 2_000;
|
|
2
|
+
const TRUNCATED_FAILURE_SUFFIX = "\n… [truncated by EngineerOS connector]";
|
|
3
|
+
|
|
1
4
|
export function describeWebSocketError(event) {
|
|
2
5
|
return (
|
|
3
6
|
event?.error?.message ||
|
|
@@ -6,6 +9,26 @@ export function describeWebSocketError(event) {
|
|
|
6
9
|
);
|
|
7
10
|
}
|
|
8
11
|
|
|
12
|
+
export function protocolFailureMessage(error) {
|
|
13
|
+
const raw = error instanceof Error ? error.message : String(error ?? "");
|
|
14
|
+
const message =
|
|
15
|
+
raw.trim() || "The connector failed without providing an error detail.";
|
|
16
|
+
const characters = [...message];
|
|
17
|
+
if (characters.length <= PROTOCOL_FAILURE_MESSAGE_MAX_LENGTH) return message;
|
|
18
|
+
const suffix = [...TRUNCATED_FAILURE_SUFFIX];
|
|
19
|
+
return `${characters
|
|
20
|
+
.slice(0, PROTOCOL_FAILURE_MESSAGE_MAX_LENGTH - suffix.length)
|
|
21
|
+
.join("")}${TRUNCATED_FAILURE_SUFFIX}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function describeRejectedResponse(response, subject) {
|
|
25
|
+
const responseBody = await response.text();
|
|
26
|
+
const detail = responseDetail(responseBody);
|
|
27
|
+
return protocolFailureMessage(
|
|
28
|
+
`EngineerOS rejected ${subject} (${response.status})${detail ? `: ${detail}` : "."}`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
9
32
|
export function startConnectionWatchdog(
|
|
10
33
|
socket,
|
|
11
34
|
url,
|
|
@@ -24,3 +47,28 @@ export function startConnectionWatchdog(
|
|
|
24
47
|
|
|
25
48
|
return () => clearTimeout(timer);
|
|
26
49
|
}
|
|
50
|
+
|
|
51
|
+
function responseDetail(responseBody) {
|
|
52
|
+
const body = String(responseBody || "").trim();
|
|
53
|
+
if (!body) return "";
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(body);
|
|
56
|
+
if (typeof parsed?.detail === "string") return parsed.detail;
|
|
57
|
+
if (Array.isArray(parsed?.detail)) {
|
|
58
|
+
return parsed.detail.map(validationIssue).filter(Boolean).join("; ");
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Plain-text server errors are already useful to the operator.
|
|
62
|
+
}
|
|
63
|
+
return body;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function validationIssue(issue) {
|
|
67
|
+
if (typeof issue === "string") return issue;
|
|
68
|
+
if (!issue || typeof issue !== "object") return "";
|
|
69
|
+
const location = Array.isArray(issue.loc) ? issue.loc.join(".") : "";
|
|
70
|
+
const message = typeof issue.msg === "string" ? issue.msg : "";
|
|
71
|
+
const type = typeof issue.type === "string" ? ` (${issue.type})` : "";
|
|
72
|
+
if (!location && !message) return "";
|
|
73
|
+
return `${location ? `${location}: ` : ""}${message}${type}`;
|
|
74
|
+
}
|
package/src/runner.mjs
CHANGED
|
@@ -414,8 +414,9 @@ export async function executeWorkspaceAssessment(
|
|
|
414
414
|
"The Git commit changed during assessment. Refresh the workspace inventory and assess the new commit.",
|
|
415
415
|
);
|
|
416
416
|
}
|
|
417
|
-
return {
|
|
418
|
-
|
|
417
|
+
return {
|
|
418
|
+
stage: assignment.stage,
|
|
419
|
+
report_markdown: report,
|
|
419
420
|
observed_head_revision: endingHead,
|
|
420
421
|
changed_files: changeImpact.changedFiles,
|
|
421
422
|
change_impact_markdown: changeImpact.markdown,
|
|
@@ -761,11 +762,22 @@ function assessmentCommandMilestone(command) {
|
|
|
761
762
|
return "Tracing architecture and code relationships";
|
|
762
763
|
}
|
|
763
764
|
|
|
764
|
-
export function workspaceAssessmentExecution(assignment) {
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
765
|
+
export function workspaceAssessmentExecution(assignment) {
|
|
766
|
+
const stage = assignment?.stage;
|
|
767
|
+
const requiredOutputHeading =
|
|
768
|
+
stage === "synthesis"
|
|
769
|
+
? "# Assessment Stage: Synthesis"
|
|
770
|
+
: stage === "capability_catalog"
|
|
771
|
+
? "# Assessment Stage: Capability Catalog"
|
|
772
|
+
: String(stage || "").startsWith("capability:")
|
|
773
|
+
? "# Assessment Stage: Capabilities"
|
|
774
|
+
: `# Assessment Stage: ${String(stage || "").replace(/^./, (value) => value.toUpperCase())}`;
|
|
775
|
+
if (
|
|
776
|
+
!new Set(["architecture", "capability_catalog", "quality", "synthesis"]).has(stage) &&
|
|
777
|
+
!String(stage || "").startsWith("capability:")
|
|
778
|
+
) {
|
|
779
|
+
throw new Error("EngineerOS assessment assignment has an unsupported stage.");
|
|
780
|
+
}
|
|
769
781
|
return {
|
|
770
782
|
...connectorExecution(assignment, { requiredOutputHeading }),
|
|
771
783
|
requiredOutputHeading,
|