@engineeros/connector 0.15.0 → 0.15.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 +3 -1
- package/bin/engineeros-connector.mjs +93 -56
- package/package.json +1 -1
- package/src/assessment-spool.mjs +1 -0
- package/src/runner.mjs +131 -23
package/README.md
CHANGED
|
@@ -47,7 +47,9 @@ 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 any combination of fifteen brownfield assessment 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. Connector `0.15.
|
|
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 assessment 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. Connector `0.15.1` uses half of the locally available CPU parallelism by default, capped at eight assessment workers, and supports an explicit `--assessment-workers` override from 1 to 32. It atomically spools each completed Markdown report under `.engineeros/assessments/<run-id>` before delivery. ACP agents such as OpenCode use isolated stage sessions within one persistent coding-agent process. The connector owns the worker pool and sends one aggregate heartbeat containing every active worker's phase, progress, last activity, and event count; report content is no longer streamed over the WebSocket or written into backend assessment JSON. The backend validates stage checkpoints so dependency fan-out remains authoritative, while the connector retains the reports and reuses them after reconnect without rerunning completed agents. Capability detail and project-specific compliance controls fan out after their catalogs. Final synthesis reads the connector-local Markdown files, then one completed bundle is revalidated, persisted, and published as canonical artifacts. The connector removes its spool only after EngineerOS acknowledges the complete bundle. Compliance findings describe repository-verifiable engineering readiness and missing external context, never legal compliance or certification. 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 applies separate bounded structure, semantic, and corrected-response formatting repairs before failing a stage. Assessment cannot modify tracked workspace source.
|
|
51
|
+
|
|
52
|
+
Every backend `422` content-validation response is returned to the stage agent with the exact failed item. Distinct validation failures continue through correction and resubmission until the complete stage is accepted. Three repetitions of the same unresolved validation error stop the automatic loop while preserving the latest local report for retry. Authentication, cancellation, source drift, and transport failures remain operational errors rather than agent-correction prompts.
|
|
51
53
|
|
|
52
54
|
Override the automatic assessment worker count when pairing (saved for that workspace) or starting the connector (for that process only):
|
|
53
55
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "../src/config.mjs";
|
|
14
14
|
import {
|
|
15
15
|
ASSESSMENT_RESULT_TIMEOUT_MS,
|
|
16
|
+
attachAssessmentRejectionCorrection,
|
|
16
17
|
assessmentWorkerSnapshots,
|
|
17
18
|
assessmentInactivityFailure,
|
|
18
19
|
assessmentProgressMessage,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
requeueInterruptedAssessment,
|
|
27
28
|
stopProcess,
|
|
28
29
|
submitAssessmentResultWithRetry,
|
|
30
|
+
submitAssessmentWithValidationRepair,
|
|
29
31
|
takeWorkspaceAssessmentWave,
|
|
30
32
|
workspaceAssessmentWorkerLimit,
|
|
31
33
|
workspaceSnapshot,
|
|
@@ -720,6 +722,46 @@ async function executeAssessment(assignment, assessmentState) {
|
|
|
720
722
|
);
|
|
721
723
|
}
|
|
722
724
|
}, 15_000);
|
|
725
|
+
const assessmentAgentCallbacks = {
|
|
726
|
+
onController: (controller) => {
|
|
727
|
+
if (isActiveAssessment()) assessmentState.controller = controller;
|
|
728
|
+
},
|
|
729
|
+
onProcess: (child) => {
|
|
730
|
+
if (isActiveAssessment()) {
|
|
731
|
+
assessmentState.child = child;
|
|
732
|
+
agentProcessStarted = true;
|
|
733
|
+
if (assessmentState.phase !== "correcting") {
|
|
734
|
+
assessmentState.phase = "running";
|
|
735
|
+
reportProgress("Connected agent process started");
|
|
736
|
+
}
|
|
737
|
+
assessmentState.lastActivityAt = Date.now();
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
onEvent: (event) => {
|
|
741
|
+
assessmentState.lastActivityAt = Date.now();
|
|
742
|
+
assessmentState.eventCount += 1;
|
|
743
|
+
const message = assessmentProgressMessage(event);
|
|
744
|
+
if (message) reportProgress(message);
|
|
745
|
+
},
|
|
746
|
+
};
|
|
747
|
+
const attachStoredCorrection = (candidate, preparedAssignment) =>
|
|
748
|
+
attachAssessmentRejectionCorrection(
|
|
749
|
+
candidate,
|
|
750
|
+
preparedAssignment,
|
|
751
|
+
config,
|
|
752
|
+
assessmentAgentCallbacks,
|
|
753
|
+
{
|
|
754
|
+
draftPath: path
|
|
755
|
+
.join(
|
|
756
|
+
".engineeros",
|
|
757
|
+
"assessments",
|
|
758
|
+
String(assessmentId),
|
|
759
|
+
candidate.report_file,
|
|
760
|
+
)
|
|
761
|
+
.split(path.sep)
|
|
762
|
+
.join("/"),
|
|
763
|
+
},
|
|
764
|
+
);
|
|
723
765
|
try {
|
|
724
766
|
if (assignment.assessment_mode === "incremental") {
|
|
725
767
|
progress = 15;
|
|
@@ -735,28 +777,14 @@ async function executeAssessment(assignment, assessmentState) {
|
|
|
735
777
|
stage,
|
|
736
778
|
);
|
|
737
779
|
if (result) {
|
|
780
|
+
attachStoredCorrection(result, preparedAssignment);
|
|
738
781
|
reportProgress("Recovered completed stage report from connector storage");
|
|
739
782
|
} else {
|
|
740
|
-
result = await executeWorkspaceAssessment(
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
if (isActiveAssessment()) {
|
|
746
|
-
assessmentState.child = child;
|
|
747
|
-
agentProcessStarted = true;
|
|
748
|
-
assessmentState.phase = "running";
|
|
749
|
-
assessmentState.lastActivityAt = Date.now();
|
|
750
|
-
reportProgress("Connected agent process started");
|
|
751
|
-
}
|
|
752
|
-
},
|
|
753
|
-
onEvent: (event) => {
|
|
754
|
-
assessmentState.lastActivityAt = Date.now();
|
|
755
|
-
assessmentState.eventCount += 1;
|
|
756
|
-
const message = assessmentProgressMessage(event);
|
|
757
|
-
if (message) reportProgress(message);
|
|
758
|
-
},
|
|
759
|
-
});
|
|
783
|
+
result = await executeWorkspaceAssessment(
|
|
784
|
+
preparedAssignment,
|
|
785
|
+
config,
|
|
786
|
+
assessmentAgentCallbacks,
|
|
787
|
+
);
|
|
760
788
|
await persistAssessmentStageResult(
|
|
761
789
|
config.workspace,
|
|
762
790
|
assessmentId,
|
|
@@ -801,44 +829,53 @@ async function executeAssessment(assignment, assessmentState) {
|
|
|
801
829
|
);
|
|
802
830
|
},
|
|
803
831
|
});
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
832
|
+
const repairedDelivery = await submitAssessmentWithValidationRepair(
|
|
833
|
+
result,
|
|
834
|
+
{
|
|
835
|
+
submit: deliverResult,
|
|
836
|
+
payloadFor: (candidate) =>
|
|
837
|
+
stage === "synthesis"
|
|
838
|
+
? assessmentCompletionBundle(
|
|
839
|
+
config.workspace,
|
|
840
|
+
assessmentId,
|
|
841
|
+
candidate,
|
|
842
|
+
)
|
|
843
|
+
: assessmentCheckpointPayload(candidate),
|
|
844
|
+
rejectionFor: (response) =>
|
|
845
|
+
describeRejectedResponse(response, "the assessment"),
|
|
846
|
+
correct: async (candidate, rejection) => {
|
|
847
|
+
agentCompleted = false;
|
|
848
|
+
assessmentState.phase = "correcting";
|
|
849
|
+
assessmentState.lastMessage = "Correcting a rejected stage result";
|
|
850
|
+
if (!candidate.correctAfterRejection) {
|
|
851
|
+
throw new Error(
|
|
852
|
+
`${rejection} The connector could not start the required agent correction.`,
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
return candidate.correctAfterRejection(rejection);
|
|
856
|
+
},
|
|
857
|
+
onCorrected: async (candidate) => {
|
|
858
|
+
await removeAssessmentStageResult(
|
|
807
859
|
config.workspace,
|
|
808
860
|
assessmentId,
|
|
809
|
-
|
|
810
|
-
)
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
result,
|
|
828
|
-
);
|
|
829
|
-
agentCompleted = true;
|
|
830
|
-
assessmentState.phase = "delivering";
|
|
831
|
-
assessmentState.lastMessage = "Delivering corrected stage result";
|
|
832
|
-
response = await deliverResult(
|
|
833
|
-
stage === "synthesis"
|
|
834
|
-
? await assessmentCompletionBundle(
|
|
835
|
-
config.workspace,
|
|
836
|
-
assessmentId,
|
|
837
|
-
result,
|
|
838
|
-
)
|
|
839
|
-
: assessmentCheckpointPayload(result),
|
|
840
|
-
);
|
|
841
|
-
}
|
|
861
|
+
stage,
|
|
862
|
+
);
|
|
863
|
+
const persisted = await persistAssessmentStageResult(
|
|
864
|
+
config.workspace,
|
|
865
|
+
assessmentId,
|
|
866
|
+
preparedAssignment,
|
|
867
|
+
candidate,
|
|
868
|
+
);
|
|
869
|
+
attachStoredCorrection(persisted, preparedAssignment);
|
|
870
|
+
agentCompleted = true;
|
|
871
|
+
assessmentState.phase = "delivering";
|
|
872
|
+
assessmentState.lastMessage = "Delivering corrected stage result";
|
|
873
|
+
return persisted;
|
|
874
|
+
},
|
|
875
|
+
},
|
|
876
|
+
);
|
|
877
|
+
result = repairedDelivery.result;
|
|
878
|
+
const response = repairedDelivery.response;
|
|
842
879
|
if (!response.ok) {
|
|
843
880
|
throw new Error(
|
|
844
881
|
await describeRejectedResponse(response, "the assessment"),
|
package/package.json
CHANGED
package/src/assessment-spool.mjs
CHANGED
|
@@ -43,6 +43,7 @@ export async function persistAssessmentStageResult(
|
|
|
43
43
|
change_impact_markdown: result.change_impact_markdown ?? null,
|
|
44
44
|
model: result.model ?? null,
|
|
45
45
|
usage: result.usage ?? null,
|
|
46
|
+
agent_session_id: result.agent_session_id ?? null,
|
|
46
47
|
};
|
|
47
48
|
await atomicWrite(path.join(directory, reportName), `${report}\n`);
|
|
48
49
|
await atomicWrite(
|
package/src/runner.mjs
CHANGED
|
@@ -33,6 +33,7 @@ const MAX_COMPRESSED_INVENTORY_BYTES = 10_000_000;
|
|
|
33
33
|
const EVIDENCE_POLICY_VERSION = "workspace-evidence-v1";
|
|
34
34
|
export const ASSESSMENT_INACTIVITY_TIMEOUT_MS = 10 * 60 * 1_000;
|
|
35
35
|
export const ASSESSMENT_RESULT_TIMEOUT_MS = 120_000;
|
|
36
|
+
export const MAX_REPEATED_ASSESSMENT_VALIDATION_FAILURES = 3;
|
|
36
37
|
export const MAX_ASSESSMENT_WORKERS = 32;
|
|
37
38
|
export const MAX_AUTOMATIC_ASSESSMENT_WORKERS = 8;
|
|
38
39
|
const EXCLUDED_DIRECTORIES = new Set([
|
|
@@ -462,39 +463,116 @@ export async function executeWorkspaceAssessment(
|
|
|
462
463
|
change_impact_markdown: changeImpact.markdown,
|
|
463
464
|
model: turn.model ?? config.agent_protocol ?? "coding-agent",
|
|
464
465
|
usage: turn.usage ?? null,
|
|
466
|
+
agent_session_id: turn.sessionId ?? null,
|
|
465
467
|
};
|
|
466
468
|
};
|
|
467
469
|
const result = await buildResult(completed, recovered.report);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
470
|
+
attachAssessmentRejectionCorrection(result, assignment, config, callbacks, {
|
|
471
|
+
previousSessionId: completed.sessionId,
|
|
472
|
+
buildResult,
|
|
473
|
+
});
|
|
474
|
+
return result;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export function attachAssessmentRejectionCorrection(
|
|
478
|
+
result,
|
|
479
|
+
assignment,
|
|
480
|
+
config,
|
|
481
|
+
callbacks,
|
|
482
|
+
{ previousSessionId = result.agent_session_id, draftPath, buildResult } = {},
|
|
483
|
+
) {
|
|
484
|
+
const execution = workspaceAssessmentExecution(assignment);
|
|
485
|
+
const assessmentAcpContext =
|
|
486
|
+
config.agent_protocol === "acp"
|
|
487
|
+
? {
|
|
488
|
+
persistentAcp: true,
|
|
489
|
+
sessionKey: `assessment:${assignment.assessment_id}:${assignment.stage}`,
|
|
490
|
+
}
|
|
491
|
+
: undefined;
|
|
492
|
+
Object.defineProperty(result, "correctAfterRejection", {
|
|
493
|
+
enumerable: false,
|
|
494
|
+
value: async (validationMessage) => {
|
|
495
|
+
callbacks.onEvent?.({
|
|
496
|
+
type: "assessment.output_correction",
|
|
497
|
+
message: "The Agent is correcting the rejected stage report",
|
|
498
|
+
});
|
|
499
|
+
const draftInstruction = draftPath
|
|
500
|
+
? [
|
|
501
|
+
"",
|
|
502
|
+
`The authoritative draft is stored at \`${draftPath}\`. Read that file before making the requested correction.`,
|
|
503
|
+
].join("\n")
|
|
504
|
+
: "";
|
|
505
|
+
const correctionPrompt = `${assessmentRejectionCorrectionPrompt(validationMessage, execution.requiredOutputHeading)}${draftInstruction}`;
|
|
506
|
+
const launchCorrection = (prompt, sessionId) => {
|
|
507
|
+
const controller = launchAgentProcess(
|
|
508
|
+
config.workspace,
|
|
509
|
+
prompt,
|
|
510
|
+
execution.sandboxMode,
|
|
511
|
+
config,
|
|
512
|
+
callbacks,
|
|
513
|
+
execution.profile,
|
|
514
|
+
sessionId,
|
|
515
|
+
assessmentAcpContext,
|
|
487
516
|
);
|
|
517
|
+
callbacks.onController?.(controller);
|
|
518
|
+
callbacks.onProcess?.(controller.child);
|
|
519
|
+
return controller;
|
|
520
|
+
};
|
|
521
|
+
let corrected = await launchCorrection(
|
|
522
|
+
correctionPrompt,
|
|
523
|
+
previousSessionId,
|
|
524
|
+
).completed;
|
|
525
|
+
const recovered = await recoverAssessmentStageOutput({
|
|
526
|
+
completed: corrected,
|
|
527
|
+
prompt: correctionPrompt,
|
|
528
|
+
requiredOutputHeading: execution.requiredOutputHeading,
|
|
529
|
+
retry: async (formatPrompt, sessionId) => {
|
|
530
|
+
callbacks.onEvent?.({
|
|
531
|
+
type: "assessment.output_correction",
|
|
532
|
+
message: "The Agent is formatting the corrected stage report",
|
|
533
|
+
});
|
|
534
|
+
return launchCorrection(formatPrompt, sessionId).completed;
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
corrected = recovered.completed;
|
|
538
|
+
const correctedReport = recovered.report;
|
|
539
|
+
if (buildResult) {
|
|
488
540
|
return buildResult(
|
|
489
541
|
{
|
|
490
542
|
...corrected,
|
|
491
|
-
usage: mergeTokenUsage(
|
|
543
|
+
usage: mergeTokenUsage(result.usage, corrected.usage),
|
|
492
544
|
},
|
|
493
545
|
correctedReport,
|
|
494
546
|
);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
|
|
547
|
+
}
|
|
548
|
+
const endingRevision = await run(
|
|
549
|
+
"git",
|
|
550
|
+
["rev-parse", "HEAD"],
|
|
551
|
+
config.workspace,
|
|
552
|
+
{ allowFailure: true },
|
|
553
|
+
);
|
|
554
|
+
const endingHead =
|
|
555
|
+
endingRevision.code === 0
|
|
556
|
+
? endingRevision.stdout.trim().slice(0, 128)
|
|
557
|
+
: null;
|
|
558
|
+
if (
|
|
559
|
+
assignment.target_head_revision &&
|
|
560
|
+
endingHead !== assignment.target_head_revision
|
|
561
|
+
) {
|
|
562
|
+
throw new Error(
|
|
563
|
+
"The Git commit changed before assessment correction. Refresh the workspace inventory and assess the new commit.",
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
...result,
|
|
568
|
+
report_markdown: correctedReport,
|
|
569
|
+
observed_head_revision: endingHead,
|
|
570
|
+
model: corrected.model ?? result.model,
|
|
571
|
+
usage: mergeTokenUsage(result.usage, corrected.usage),
|
|
572
|
+
agent_session_id: corrected.sessionId ?? previousSessionId ?? null,
|
|
573
|
+
};
|
|
574
|
+
},
|
|
575
|
+
});
|
|
498
576
|
return result;
|
|
499
577
|
}
|
|
500
578
|
|
|
@@ -1112,6 +1190,36 @@ export async function submitAssessmentResultWithRetry(
|
|
|
1112
1190
|
);
|
|
1113
1191
|
}
|
|
1114
1192
|
|
|
1193
|
+
export async function submitAssessmentWithValidationRepair(
|
|
1194
|
+
initialResult,
|
|
1195
|
+
{
|
|
1196
|
+
submit,
|
|
1197
|
+
payloadFor,
|
|
1198
|
+
rejectionFor,
|
|
1199
|
+
correct,
|
|
1200
|
+
onCorrected = async () => {},
|
|
1201
|
+
maxRepeatedFailures = MAX_REPEATED_ASSESSMENT_VALIDATION_FAILURES,
|
|
1202
|
+
},
|
|
1203
|
+
) {
|
|
1204
|
+
let result = initialResult;
|
|
1205
|
+
let response = await submit(await payloadFor(result));
|
|
1206
|
+
const rejectionCounts = new Map();
|
|
1207
|
+
while (response.status === 422) {
|
|
1208
|
+
const rejection = await rejectionFor(response);
|
|
1209
|
+
const rejectionCount = (rejectionCounts.get(rejection) || 0) + 1;
|
|
1210
|
+
rejectionCounts.set(rejection, rejectionCount);
|
|
1211
|
+
if (rejectionCount > maxRepeatedFailures) {
|
|
1212
|
+
throw new Error(
|
|
1213
|
+
`${rejection} The Agent repeated this unresolved validation failure ${maxRepeatedFailures} times; the connector retained the latest local report for retry.`,
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
result = await correct(result, rejection);
|
|
1217
|
+
result = (await onCorrected(result, rejection)) || result;
|
|
1218
|
+
response = await submit(await payloadFor(result));
|
|
1219
|
+
}
|
|
1220
|
+
return { result, response };
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1115
1223
|
function isTransientAssessmentResultResponse(response) {
|
|
1116
1224
|
return (
|
|
1117
1225
|
response.status === 408 ||
|