@engineeros/connector 0.14.0 → 0.14.4
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 +965 -913
- package/package.json +41 -41
- package/src/acp-client.mjs +87 -68
- package/src/agent-harness.mjs +37 -37
- package/src/codex-app-server.mjs +40 -40
- package/src/connection.mjs +74 -74
- package/src/runner.mjs +378 -308
package/src/runner.mjs
CHANGED
|
@@ -28,10 +28,13 @@ 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";
|
|
34
|
-
export const ASSESSMENT_INACTIVITY_TIMEOUT_MS = 10 * 60 * 1_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";
|
|
34
|
+
export const ASSESSMENT_INACTIVITY_TIMEOUT_MS = 10 * 60 * 1_000;
|
|
35
|
+
export const ASSESSMENT_OUTPUT_FLUSH_INTERVAL_MS = 15_000;
|
|
36
|
+
export const ASSESSMENT_RESULT_TIMEOUT_MS = 120_000;
|
|
37
|
+
export const MAX_ASSESSMENT_WORKERS = 3;
|
|
35
38
|
const EXCLUDED_DIRECTORIES = new Set([
|
|
36
39
|
".agents",
|
|
37
40
|
".claude",
|
|
@@ -395,94 +398,94 @@ export async function executeWorkspaceAssessment(
|
|
|
395
398
|
execution.profile,
|
|
396
399
|
);
|
|
397
400
|
callbacks.onProcess?.(controller.child);
|
|
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 buildResult = async (turn, report) => {
|
|
423
|
-
const endingRevision = await run(
|
|
424
|
-
"git",
|
|
425
|
-
["rev-parse", "HEAD"],
|
|
426
|
-
config.workspace,
|
|
427
|
-
{ allowFailure: true },
|
|
428
|
-
);
|
|
429
|
-
const endingHead =
|
|
430
|
-
endingRevision.code === 0
|
|
431
|
-
? endingRevision.stdout.trim().slice(0, 128)
|
|
432
|
-
: null;
|
|
433
|
-
if (startingHead !== endingHead) {
|
|
434
|
-
throw new Error(
|
|
435
|
-
"The Git commit changed during assessment. Refresh the workspace inventory and assess the new commit.",
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
return {
|
|
439
|
-
stage: assignment.stage,
|
|
440
|
-
report_markdown: report,
|
|
441
|
-
observed_head_revision: endingHead,
|
|
442
|
-
changed_files: changeImpact.changedFiles,
|
|
443
|
-
change_impact_markdown: changeImpact.markdown,
|
|
444
|
-
model: turn.model ?? config.agent_protocol ?? "coding-agent",
|
|
445
|
-
usage: turn.usage ?? null,
|
|
446
|
-
};
|
|
447
|
-
};
|
|
448
|
-
const result = await buildResult(completed, recovered.report);
|
|
449
|
-
if (!recovered.correctionUsed) {
|
|
450
|
-
Object.defineProperty(result, "correctAfterRejection", {
|
|
451
|
-
enumerable: false,
|
|
452
|
-
value: async (validationMessage) => {
|
|
453
|
-
callbacks.onEvent?.({
|
|
454
|
-
type: "assessment.output_correction",
|
|
455
|
-
message: "The Agent is correcting the rejected stage report",
|
|
456
|
-
});
|
|
457
|
-
const correction = launchAgentProcess(
|
|
458
|
-
config.workspace,
|
|
459
|
-
assessmentRejectionCorrectionPrompt(
|
|
460
|
-
validationMessage,
|
|
461
|
-
execution.requiredOutputHeading,
|
|
462
|
-
),
|
|
463
|
-
execution.sandboxMode,
|
|
464
|
-
config,
|
|
465
|
-
callbacks,
|
|
466
|
-
execution.profile,
|
|
467
|
-
completed.sessionId,
|
|
468
|
-
);
|
|
469
|
-
callbacks.onProcess?.(correction.child);
|
|
470
|
-
const corrected = await correction.completed;
|
|
471
|
-
const correctedReport = normalizeAgentStructuredOutput(
|
|
472
|
-
corrected.finalMessage,
|
|
473
|
-
execution.requiredOutputHeading,
|
|
474
|
-
);
|
|
475
|
-
return buildResult(
|
|
476
|
-
{
|
|
477
|
-
...corrected,
|
|
478
|
-
usage: mergeTokenUsage(completed.usage, corrected.usage),
|
|
479
|
-
},
|
|
480
|
-
correctedReport,
|
|
481
|
-
);
|
|
482
|
-
},
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
return result;
|
|
401
|
+
let completed = await controller.completed;
|
|
402
|
+
const recovered = await recoverAssessmentStageOutput({
|
|
403
|
+
completed,
|
|
404
|
+
prompt,
|
|
405
|
+
requiredOutputHeading: execution.requiredOutputHeading,
|
|
406
|
+
retry: async (correctionPrompt, previousSessionId) => {
|
|
407
|
+
callbacks.onEvent?.({
|
|
408
|
+
type: "assessment.output_correction",
|
|
409
|
+
message: "The Agent is completing the required stage report structure",
|
|
410
|
+
});
|
|
411
|
+
const correction = launchAgentProcess(
|
|
412
|
+
config.workspace,
|
|
413
|
+
correctionPrompt,
|
|
414
|
+
execution.sandboxMode,
|
|
415
|
+
config,
|
|
416
|
+
callbacks,
|
|
417
|
+
execution.profile,
|
|
418
|
+
previousSessionId,
|
|
419
|
+
);
|
|
420
|
+
callbacks.onProcess?.(correction.child);
|
|
421
|
+
return correction.completed;
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
completed = recovered.completed;
|
|
425
|
+
const buildResult = async (turn, report) => {
|
|
426
|
+
const endingRevision = await run(
|
|
427
|
+
"git",
|
|
428
|
+
["rev-parse", "HEAD"],
|
|
429
|
+
config.workspace,
|
|
430
|
+
{ allowFailure: true },
|
|
431
|
+
);
|
|
432
|
+
const endingHead =
|
|
433
|
+
endingRevision.code === 0
|
|
434
|
+
? endingRevision.stdout.trim().slice(0, 128)
|
|
435
|
+
: null;
|
|
436
|
+
if (startingHead !== endingHead) {
|
|
437
|
+
throw new Error(
|
|
438
|
+
"The Git commit changed during assessment. Refresh the workspace inventory and assess the new commit.",
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
stage: assignment.stage,
|
|
443
|
+
report_markdown: report,
|
|
444
|
+
observed_head_revision: endingHead,
|
|
445
|
+
changed_files: changeImpact.changedFiles,
|
|
446
|
+
change_impact_markdown: changeImpact.markdown,
|
|
447
|
+
model: turn.model ?? config.agent_protocol ?? "coding-agent",
|
|
448
|
+
usage: turn.usage ?? null,
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
const result = await buildResult(completed, recovered.report);
|
|
452
|
+
if (!recovered.correctionUsed) {
|
|
453
|
+
Object.defineProperty(result, "correctAfterRejection", {
|
|
454
|
+
enumerable: false,
|
|
455
|
+
value: async (validationMessage) => {
|
|
456
|
+
callbacks.onEvent?.({
|
|
457
|
+
type: "assessment.output_correction",
|
|
458
|
+
message: "The Agent is correcting the rejected stage report",
|
|
459
|
+
});
|
|
460
|
+
const correction = launchAgentProcess(
|
|
461
|
+
config.workspace,
|
|
462
|
+
assessmentRejectionCorrectionPrompt(
|
|
463
|
+
validationMessage,
|
|
464
|
+
execution.requiredOutputHeading,
|
|
465
|
+
),
|
|
466
|
+
execution.sandboxMode,
|
|
467
|
+
config,
|
|
468
|
+
callbacks,
|
|
469
|
+
execution.profile,
|
|
470
|
+
completed.sessionId,
|
|
471
|
+
);
|
|
472
|
+
callbacks.onProcess?.(correction.child);
|
|
473
|
+
const corrected = await correction.completed;
|
|
474
|
+
const correctedReport = normalizeAgentStructuredOutput(
|
|
475
|
+
corrected.finalMessage,
|
|
476
|
+
execution.requiredOutputHeading,
|
|
477
|
+
);
|
|
478
|
+
return buildResult(
|
|
479
|
+
{
|
|
480
|
+
...corrected,
|
|
481
|
+
usage: mergeTokenUsage(completed.usage, corrected.usage),
|
|
482
|
+
},
|
|
483
|
+
correctedReport,
|
|
484
|
+
);
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
return result;
|
|
486
489
|
}
|
|
487
490
|
|
|
488
491
|
export async function workspaceChangeImpact(
|
|
@@ -695,23 +698,23 @@ export function codexFailureMessage(output, code) {
|
|
|
695
698
|
return `Codex exited with code ${code}. ${output.slice(-1_000)}`;
|
|
696
699
|
}
|
|
697
700
|
|
|
698
|
-
export function assessmentProgressMessage(event) {
|
|
699
|
-
if (!event || typeof event !== "object") return null;
|
|
700
|
-
if (event.type === "assessment.output_correction")
|
|
701
|
-
return "Completing the required stage report structure";
|
|
702
|
-
if (event.type === "agent.connected")
|
|
703
|
-
return "Connected agent is ready to inspect
|
|
704
|
-
if (event.type === "acp.plan")
|
|
705
|
-
return "Organizing the repository assessment plan";
|
|
706
|
-
if (event.type === "acp.agent_thought_chunk")
|
|
707
|
-
return "Reasoning through
|
|
708
|
-
if (event.type === "acp.agent_message_chunk")
|
|
709
|
-
return "Drafting the
|
|
710
|
-
if (event.type === "acp.tool_call" || event.type === "acp.tool_call_update") {
|
|
711
|
-
return assessmentCommandMilestone(event.update?.title);
|
|
712
|
-
}
|
|
713
|
-
if (event.type === "turn.started")
|
|
714
|
-
return "Reviewing repository structure and current Git state";
|
|
701
|
+
export function assessmentProgressMessage(event) {
|
|
702
|
+
if (!event || typeof event !== "object") return null;
|
|
703
|
+
if (event.type === "assessment.output_correction")
|
|
704
|
+
return "Completing the required stage report structure";
|
|
705
|
+
if (event.type === "agent.connected")
|
|
706
|
+
return "Connected agent is ready to inspect the workspace";
|
|
707
|
+
if (event.type === "acp.plan")
|
|
708
|
+
return "Organizing the repository assessment plan";
|
|
709
|
+
if (event.type === "acp.agent_thought_chunk")
|
|
710
|
+
return "Reasoning through the current implementation";
|
|
711
|
+
if (event.type === "acp.agent_message_chunk")
|
|
712
|
+
return "Drafting the stage report";
|
|
713
|
+
if (event.type === "acp.tool_call" || event.type === "acp.tool_call_update") {
|
|
714
|
+
return assessmentCommandMilestone(event.update?.title);
|
|
715
|
+
}
|
|
716
|
+
if (event.type === "turn.started")
|
|
717
|
+
return "Reviewing repository structure and current Git state";
|
|
715
718
|
if (event.type === "item.started") {
|
|
716
719
|
if (event.item?.type === "command_execution") {
|
|
717
720
|
return assessmentCommandMilestone(event.item.command);
|
|
@@ -725,16 +728,16 @@ export function assessmentProgressMessage(event) {
|
|
|
725
728
|
if (event.type === "item.completed" && event.item?.type === "agent_message") {
|
|
726
729
|
return "Synthesizing findings and highest-return actions";
|
|
727
730
|
}
|
|
728
|
-
return null;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
export function assessmentInactivityFailure(stage, inactiveMs) {
|
|
732
|
-
if (inactiveMs < ASSESSMENT_INACTIVITY_TIMEOUT_MS) return null;
|
|
733
|
-
return (
|
|
734
|
-
`The connected agent produced no activity for 10 minutes during ${stage}. ` +
|
|
735
|
-
"The stage was stopped instead of waiting indefinitely. Retry it after checking the agent terminal."
|
|
736
|
-
);
|
|
737
|
-
}
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
export function assessmentInactivityFailure(stage, inactiveMs) {
|
|
735
|
+
if (inactiveMs < ASSESSMENT_INACTIVITY_TIMEOUT_MS) return null;
|
|
736
|
+
return (
|
|
737
|
+
`The connected agent produced no activity for 10 minutes during ${stage}. ` +
|
|
738
|
+
"The stage was stopped instead of waiting indefinitely. Retry it after checking the agent terminal."
|
|
739
|
+
);
|
|
740
|
+
}
|
|
738
741
|
|
|
739
742
|
export function promptProgressMessage(event) {
|
|
740
743
|
if (!event || typeof event !== "object") return null;
|
|
@@ -824,7 +827,7 @@ function assessmentCommandMilestone(command) {
|
|
|
824
827
|
? command.join(" ")
|
|
825
828
|
: String(command || "");
|
|
826
829
|
const normalized = value.replace(/\s+/g, " ").trim().toLowerCase();
|
|
827
|
-
if (!normalized) return "Inspecting
|
|
830
|
+
if (!normalized) return "Inspecting workspace source";
|
|
828
831
|
if (/\bgit\s+(status|log|diff|show|rev-parse)\b/.test(normalized)) {
|
|
829
832
|
return "Comparing Git history and workspace changes";
|
|
830
833
|
}
|
|
@@ -843,190 +846,257 @@ function assessmentCommandMilestone(command) {
|
|
|
843
846
|
return "Tracing architecture and code relationships";
|
|
844
847
|
}
|
|
845
848
|
|
|
846
|
-
export function workspaceAssessmentExecution(assignment) {
|
|
847
|
-
const stage = assignment?.stage;
|
|
848
|
-
const requiredOutputHeading = String(
|
|
849
|
-
assignment?.required_output_heading || "",
|
|
850
|
-
).trim();
|
|
851
|
-
if (!stage || !requiredOutputHeading.startsWith("# Assessment Stage: ")) {
|
|
852
|
-
throw new Error("EngineerOS assessment assignment has an unsupported stage.");
|
|
853
|
-
}
|
|
849
|
+
export function workspaceAssessmentExecution(assignment) {
|
|
850
|
+
const stage = assignment?.stage;
|
|
851
|
+
const requiredOutputHeading = String(
|
|
852
|
+
assignment?.required_output_heading || "",
|
|
853
|
+
).trim();
|
|
854
|
+
if (!stage || !requiredOutputHeading.startsWith("# Assessment Stage: ")) {
|
|
855
|
+
throw new Error("EngineerOS assessment assignment has an unsupported stage.");
|
|
856
|
+
}
|
|
854
857
|
return {
|
|
855
858
|
...connectorExecution(assignment, { requiredOutputHeading }),
|
|
856
859
|
requiredOutputHeading,
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
export async function recoverAssessmentStageOutput({
|
|
861
|
-
completed,
|
|
862
|
-
prompt,
|
|
863
|
-
requiredOutputHeading,
|
|
864
|
-
retry,
|
|
865
|
-
}) {
|
|
866
|
-
try {
|
|
867
|
-
return {
|
|
868
|
-
completed,
|
|
869
|
-
correctionUsed: false,
|
|
870
|
-
report: normalizeAgentStructuredOutput(
|
|
871
|
-
completed.finalMessage,
|
|
872
|
-
requiredOutputHeading,
|
|
873
|
-
),
|
|
874
|
-
};
|
|
875
|
-
} catch (error) {
|
|
876
|
-
if (!recoverableStructuredOutputFailure(error)) throw error;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
const corrected = await retry(
|
|
880
|
-
assessmentStageCorrectionPrompt(prompt, requiredOutputHeading),
|
|
881
|
-
completed.sessionId,
|
|
882
|
-
);
|
|
883
|
-
try {
|
|
884
|
-
return {
|
|
885
|
-
completed: {
|
|
886
|
-
...corrected,
|
|
887
|
-
usage: mergeTokenUsage(completed.usage, corrected.usage),
|
|
888
|
-
},
|
|
889
|
-
correctionUsed: true,
|
|
890
|
-
report: normalizeAgentStructuredOutput(
|
|
891
|
-
corrected.finalMessage,
|
|
892
|
-
requiredOutputHeading,
|
|
893
|
-
),
|
|
894
|
-
};
|
|
895
|
-
} catch (error) {
|
|
896
|
-
throw new Error(
|
|
897
|
-
`Agent did not return the required stage report after one automatic correction attempt. ${error.message}`,
|
|
898
|
-
{ cause: error },
|
|
899
|
-
);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
export function assessmentRejectionCorrectionPrompt(
|
|
904
|
-
validationMessage,
|
|
905
|
-
requiredOutputHeading,
|
|
906
|
-
) {
|
|
907
|
-
return [
|
|
908
|
-
"## Rejected Stage Output Recovery",
|
|
909
|
-
"",
|
|
910
|
-
"EngineerOS rejected the previous stage report because it was incomplete or violated the structured Markdown contract:",
|
|
911
|
-
"",
|
|
912
|
-
String(validationMessage || "The stage report was rejected.").trim(),
|
|
913
|
-
"",
|
|
914
|
-
"Treat the previous stage report as the authoritative draft.",
|
|
915
|
-
"Copy every valid section and block unchanged; repair only the incomplete or invalid entries identified by EngineerOS validation.",
|
|
916
|
-
"Do not re-inspect the repository or replace valid evidence unless the validation error requires it.",
|
|
917
|
-
"Return the entire corrected structured Markdown stage report so EngineerOS can validate it atomically, not only the repaired fragment or missing tail.",
|
|
918
|
-
`The first non-whitespace line must be exactly: ${requiredOutputHeading}`,
|
|
919
|
-
"Do not return progress commentary, an explanation of the correction, or a code fence.",
|
|
920
|
-
].join("\n");
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
export function assessmentStageCorrectionPrompt(
|
|
924
|
-
originalPrompt,
|
|
925
|
-
requiredOutputHeading,
|
|
926
|
-
) {
|
|
927
|
-
return [
|
|
928
|
-
String(originalPrompt || "").trim(),
|
|
929
|
-
"",
|
|
930
|
-
"## Incomplete Stage Output Recovery",
|
|
931
|
-
"",
|
|
932
|
-
"The previous turn ended without a complete stage deliverable. Return the entire structured Markdown stage report now.",
|
|
933
|
-
"Use repository
|
|
934
|
-
`The first non-whitespace line of the final answer must be exactly: ${requiredOutputHeading}`,
|
|
935
|
-
"Follow every section,
|
|
936
|
-
"Do not return progress commentary, an explanation of the correction, or a code fence.",
|
|
937
|
-
].join("\n");
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
function recoverableStructuredOutputFailure(error) {
|
|
941
|
-
const message = error instanceof Error ? error.message : "";
|
|
942
|
-
return (
|
|
943
|
-
message.startsWith("Agent completed") ||
|
|
944
|
-
message.startsWith("Agent response")
|
|
945
|
-
);
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
function mergeTokenUsage(first, second) {
|
|
949
|
-
const usages = [first, second].filter(
|
|
950
|
-
(usage) => usage && typeof usage === "object",
|
|
951
|
-
);
|
|
952
|
-
if (!usages.length) return null;
|
|
953
|
-
return Object.fromEntries(
|
|
954
|
-
[
|
|
955
|
-
"input_tokens",
|
|
956
|
-
"output_tokens",
|
|
957
|
-
"cache_read_tokens",
|
|
958
|
-
"cache_write_tokens",
|
|
959
|
-
"reasoning_tokens",
|
|
960
|
-
"total_tokens",
|
|
961
|
-
].map((field) => [
|
|
962
|
-
field,
|
|
963
|
-
usages.reduce(
|
|
964
|
-
(total, usage) =>
|
|
965
|
-
total + (Number.isFinite(usage[field]) ? usage[field] : 0),
|
|
966
|
-
0,
|
|
967
|
-
),
|
|
968
|
-
]),
|
|
969
|
-
);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
export function enqueueWorkspaceAssessment(
|
|
973
|
-
queue,
|
|
974
|
-
activeAssessment,
|
|
975
|
-
assignment,
|
|
976
|
-
{ front = false } = {},
|
|
977
|
-
) {
|
|
978
|
-
const matches = (candidate) =>
|
|
979
|
-
candidate?.assessment_id === assignment?.assessment_id &&
|
|
980
|
-
candidate?.stage === assignment?.stage;
|
|
981
|
-
if (
|
|
982
|
-
activeAssessment?.kind === "assessment" &&
|
|
983
|
-
activeAssessment.runId === assignment?.assessment_id &&
|
|
984
|
-
activeAssessment.stage === assignment?.stage
|
|
985
|
-
) {
|
|
986
|
-
activeAssessment.resumeAssignment = assignment;
|
|
987
|
-
return "deferred";
|
|
988
|
-
}
|
|
989
|
-
if (queue.some(matches)) return "duplicate";
|
|
990
|
-
if (front) queue.unshift(assignment);
|
|
991
|
-
else queue.push(assignment);
|
|
992
|
-
return "queued";
|
|
993
|
-
}
|
|
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
|
-
|
|
1011
|
-
export function
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
) {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
export async function recoverAssessmentStageOutput({
|
|
864
|
+
completed,
|
|
865
|
+
prompt,
|
|
866
|
+
requiredOutputHeading,
|
|
867
|
+
retry,
|
|
868
|
+
}) {
|
|
869
|
+
try {
|
|
870
|
+
return {
|
|
871
|
+
completed,
|
|
872
|
+
correctionUsed: false,
|
|
873
|
+
report: normalizeAgentStructuredOutput(
|
|
874
|
+
completed.finalMessage,
|
|
875
|
+
requiredOutputHeading,
|
|
876
|
+
),
|
|
877
|
+
};
|
|
878
|
+
} catch (error) {
|
|
879
|
+
if (!recoverableStructuredOutputFailure(error)) throw error;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const corrected = await retry(
|
|
883
|
+
assessmentStageCorrectionPrompt(prompt, requiredOutputHeading),
|
|
884
|
+
completed.sessionId,
|
|
885
|
+
);
|
|
886
|
+
try {
|
|
887
|
+
return {
|
|
888
|
+
completed: {
|
|
889
|
+
...corrected,
|
|
890
|
+
usage: mergeTokenUsage(completed.usage, corrected.usage),
|
|
891
|
+
},
|
|
892
|
+
correctionUsed: true,
|
|
893
|
+
report: normalizeAgentStructuredOutput(
|
|
894
|
+
corrected.finalMessage,
|
|
895
|
+
requiredOutputHeading,
|
|
896
|
+
),
|
|
897
|
+
};
|
|
898
|
+
} catch (error) {
|
|
899
|
+
throw new Error(
|
|
900
|
+
`Agent did not return the required stage report after one automatic correction attempt. ${error.message}`,
|
|
901
|
+
{ cause: error },
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
export function assessmentRejectionCorrectionPrompt(
|
|
907
|
+
validationMessage,
|
|
908
|
+
requiredOutputHeading,
|
|
909
|
+
) {
|
|
910
|
+
return [
|
|
911
|
+
"## Rejected Stage Output Recovery",
|
|
912
|
+
"",
|
|
913
|
+
"EngineerOS rejected the previous stage report because it was incomplete or violated the structured Markdown contract:",
|
|
914
|
+
"",
|
|
915
|
+
String(validationMessage || "The stage report was rejected.").trim(),
|
|
916
|
+
"",
|
|
917
|
+
"Treat the previous stage report as the authoritative draft.",
|
|
918
|
+
"Copy every valid section and block unchanged; repair only the incomplete or invalid entries identified by EngineerOS validation.",
|
|
919
|
+
"Do not re-inspect the repository or replace valid evidence unless the validation error requires it.",
|
|
920
|
+
"Return the entire corrected structured Markdown stage report so EngineerOS can validate it atomically, not only the repaired fragment or missing tail.",
|
|
921
|
+
`The first non-whitespace line must be exactly: ${requiredOutputHeading}`,
|
|
922
|
+
"Do not return progress commentary, an explanation of the correction, or a code fence.",
|
|
923
|
+
].join("\n");
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
export function assessmentStageCorrectionPrompt(
|
|
927
|
+
originalPrompt,
|
|
928
|
+
requiredOutputHeading,
|
|
929
|
+
) {
|
|
930
|
+
return [
|
|
931
|
+
String(originalPrompt || "").trim(),
|
|
932
|
+
"",
|
|
933
|
+
"## Incomplete Stage Output Recovery",
|
|
934
|
+
"",
|
|
935
|
+
"The previous turn ended without a complete stage deliverable. Return the entire structured Markdown stage report now.",
|
|
936
|
+
"Use repository context already inspected in the previous turn when it is available; inspect only what remains necessary.",
|
|
937
|
+
`The first non-whitespace line of the final answer must be exactly: ${requiredOutputHeading}`,
|
|
938
|
+
"Follow every section, inspection, and completeness rule in the Assignment.",
|
|
939
|
+
"Do not return progress commentary, an explanation of the correction, or a code fence.",
|
|
940
|
+
].join("\n");
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function recoverableStructuredOutputFailure(error) {
|
|
944
|
+
const message = error instanceof Error ? error.message : "";
|
|
945
|
+
return (
|
|
946
|
+
message.startsWith("Agent completed") ||
|
|
947
|
+
message.startsWith("Agent response")
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function mergeTokenUsage(first, second) {
|
|
952
|
+
const usages = [first, second].filter(
|
|
953
|
+
(usage) => usage && typeof usage === "object",
|
|
954
|
+
);
|
|
955
|
+
if (!usages.length) return null;
|
|
956
|
+
return Object.fromEntries(
|
|
957
|
+
[
|
|
958
|
+
"input_tokens",
|
|
959
|
+
"output_tokens",
|
|
960
|
+
"cache_read_tokens",
|
|
961
|
+
"cache_write_tokens",
|
|
962
|
+
"reasoning_tokens",
|
|
963
|
+
"total_tokens",
|
|
964
|
+
].map((field) => [
|
|
965
|
+
field,
|
|
966
|
+
usages.reduce(
|
|
967
|
+
(total, usage) =>
|
|
968
|
+
total + (Number.isFinite(usage[field]) ? usage[field] : 0),
|
|
969
|
+
0,
|
|
970
|
+
),
|
|
971
|
+
]),
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
export function enqueueWorkspaceAssessment(
|
|
976
|
+
queue,
|
|
977
|
+
activeAssessment,
|
|
978
|
+
assignment,
|
|
979
|
+
{ front = false } = {},
|
|
980
|
+
) {
|
|
981
|
+
const matches = (candidate) =>
|
|
982
|
+
candidate?.assessment_id === assignment?.assessment_id &&
|
|
983
|
+
candidate?.stage === assignment?.stage;
|
|
984
|
+
if (
|
|
985
|
+
activeAssessment?.kind === "assessment" &&
|
|
986
|
+
activeAssessment.runId === assignment?.assessment_id &&
|
|
987
|
+
activeAssessment.stage === assignment?.stage
|
|
988
|
+
) {
|
|
989
|
+
activeAssessment.resumeAssignment = assignment;
|
|
990
|
+
return "deferred";
|
|
991
|
+
}
|
|
992
|
+
if (queue.some(matches)) return "duplicate";
|
|
993
|
+
if (front) queue.unshift(assignment);
|
|
994
|
+
else queue.push(assignment);
|
|
995
|
+
return "queued";
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
export function takeWorkspaceAssessmentWave(queue, activeCount, limit = 3) {
|
|
999
|
+
const available = Math.max(0, limit - activeCount);
|
|
1000
|
+
if (!available || !queue.length) return [];
|
|
1001
|
+
if (queue[0]?.parallelizable !== true) {
|
|
1002
|
+
return activeCount === 0 ? queue.splice(0, 1) : [];
|
|
1003
|
+
}
|
|
1004
|
+
const wave = [];
|
|
1005
|
+
while (
|
|
1006
|
+
wave.length < available &&
|
|
1007
|
+
queue[0]?.parallelizable === true
|
|
1008
|
+
) {
|
|
1009
|
+
wave.push(queue.shift());
|
|
1010
|
+
}
|
|
1011
|
+
return wave;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
export function workspaceAssessmentWorkerLimit() {
|
|
1015
|
+
return MAX_ASSESSMENT_WORKERS;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
export function assessmentWorkerSnapshots(activeAssessments) {
|
|
1019
|
+
return [...activeAssessments.values()]
|
|
1020
|
+
.slice(0, MAX_ASSESSMENT_WORKERS)
|
|
1021
|
+
.map((state) => ({
|
|
1022
|
+
assessment_id: String(state.runId),
|
|
1023
|
+
stage: String(state.stage).slice(0, 96),
|
|
1024
|
+
phase: state.phase,
|
|
1025
|
+
progress_percent: Math.max(
|
|
1026
|
+
0,
|
|
1027
|
+
Math.min(95, Number(state.progressPercent) || 0),
|
|
1028
|
+
),
|
|
1029
|
+
message: String(state.lastMessage || "Assessment worker is starting").slice(
|
|
1030
|
+
0,
|
|
1031
|
+
500,
|
|
1032
|
+
),
|
|
1033
|
+
started_at: new Date(state.startedAt).toISOString(),
|
|
1034
|
+
last_activity_at: new Date(state.lastActivityAt).toISOString(),
|
|
1035
|
+
event_count: Math.max(0, Number(state.eventCount) || 0),
|
|
1036
|
+
}));
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
export function requeueInterruptedAssessment(
|
|
1040
|
+
queue,
|
|
1041
|
+
assessmentState,
|
|
1042
|
+
{ accepted, failureReported },
|
|
1043
|
+
) {
|
|
1044
|
+
if (accepted || failureReported || !assessmentState?.resumeAssignment) {
|
|
1045
|
+
return false;
|
|
1046
|
+
}
|
|
1047
|
+
return (
|
|
1048
|
+
enqueueWorkspaceAssessment(
|
|
1049
|
+
queue,
|
|
1050
|
+
null,
|
|
1051
|
+
assessmentState.resumeAssignment,
|
|
1052
|
+
{ front: true },
|
|
1053
|
+
) === "queued"
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
export async function submitAssessmentResultWithRetry(
|
|
1058
|
+
payload,
|
|
1059
|
+
submit,
|
|
1060
|
+
{
|
|
1061
|
+
isActive = () => true,
|
|
1062
|
+
onRetry = () => {},
|
|
1063
|
+
wait = (milliseconds) =>
|
|
1064
|
+
new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
1065
|
+
initialDelayMs = 1_000,
|
|
1066
|
+
maxDelayMs = 30_000,
|
|
1067
|
+
} = {},
|
|
1068
|
+
) {
|
|
1069
|
+
let retryDelayMs = initialDelayMs;
|
|
1070
|
+
while (isActive()) {
|
|
1071
|
+
let retryFailure;
|
|
1072
|
+
try {
|
|
1073
|
+
const response = await submit(payload);
|
|
1074
|
+
if (!isTransientAssessmentResultResponse(response)) return response;
|
|
1075
|
+
await response.body?.cancel?.();
|
|
1076
|
+
retryFailure = new Error(
|
|
1077
|
+
`EngineerOS temporarily rejected the completed assessment result (${response.status}).`,
|
|
1078
|
+
);
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
retryFailure = error;
|
|
1081
|
+
}
|
|
1082
|
+
if (!isActive()) break;
|
|
1083
|
+
onRetry(retryFailure, retryDelayMs);
|
|
1084
|
+
await wait(retryDelayMs);
|
|
1085
|
+
retryDelayMs = Math.min(maxDelayMs, retryDelayMs * 2);
|
|
1086
|
+
}
|
|
1087
|
+
throw new Error("Assessment result delivery stopped before EngineerOS accepted it.");
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function isTransientAssessmentResultResponse(response) {
|
|
1091
|
+
return (
|
|
1092
|
+
response.status === 408 ||
|
|
1093
|
+
response.status === 425 ||
|
|
1094
|
+
response.status === 429 ||
|
|
1095
|
+
response.status >= 500
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
export function connectorExecution(assignment, options = {}) {
|
|
1030
1100
|
const rawPrompt = assignment?.prompt_markdown;
|
|
1031
1101
|
if (typeof rawPrompt !== "string" || !rawPrompt.trim()) {
|
|
1032
1102
|
throw new Error("EngineerOS assignment is missing prompt_markdown.");
|
|
@@ -1076,13 +1146,13 @@ export function connectorExecution(assignment, options = {}) {
|
|
|
1076
1146
|
};
|
|
1077
1147
|
}
|
|
1078
1148
|
|
|
1079
|
-
export async function stopProcess(child) {
|
|
1080
|
-
if (!child || child.exitCode !== null) return;
|
|
1081
|
-
if (typeof child.engineerOsCancel === "function") {
|
|
1082
|
-
await child.engineerOsCancel();
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
if (process.platform === "win32") {
|
|
1149
|
+
export async function stopProcess(child) {
|
|
1150
|
+
if (!child || child.exitCode !== null) return;
|
|
1151
|
+
if (typeof child.engineerOsCancel === "function") {
|
|
1152
|
+
await child.engineerOsCancel();
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
if (process.platform === "win32") {
|
|
1086
1156
|
await run(
|
|
1087
1157
|
"taskkill",
|
|
1088
1158
|
["/pid", String(child.pid), "/t", "/f"],
|