@engineeros/connector 0.13.4 → 0.14.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/src/runner.mjs CHANGED
@@ -28,10 +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";
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
35
  const EXCLUDED_DIRECTORIES = new Set([
36
36
  ".agents",
37
37
  ".claude",
@@ -395,94 +395,94 @@ export async function executeWorkspaceAssessment(
395
395
  execution.profile,
396
396
  );
397
397
  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;
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;
486
486
  }
487
487
 
488
488
  export async function workspaceChangeImpact(
@@ -695,23 +695,23 @@ export function codexFailureMessage(output, code) {
695
695
  return `Codex exited with code ${code}. ${output.slice(-1_000)}`;
696
696
  }
697
697
 
698
- export function assessmentProgressMessage(event) {
699
- if (!event || typeof event !== "object") return null;
698
+ export function assessmentProgressMessage(event) {
699
+ if (!event || typeof event !== "object") return null;
700
700
  if (event.type === "assessment.output_correction")
701
701
  return "Completing the required stage report structure";
702
702
  if (event.type === "agent.connected")
703
- return "Connected agent is ready to inspect repository evidence";
704
- if (event.type === "acp.plan")
705
- return "Organizing the repository assessment plan";
703
+ return "Connected agent is ready to inspect the workspace";
704
+ if (event.type === "acp.plan")
705
+ return "Organizing the repository assessment plan";
706
706
  if (event.type === "acp.agent_thought_chunk")
707
- return "Reasoning through repository evidence";
707
+ return "Reasoning through the current implementation";
708
708
  if (event.type === "acp.agent_message_chunk")
709
- return "Drafting the evidence-backed stage report";
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";
709
+ return "Drafting the stage report";
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";
715
715
  if (event.type === "item.started") {
716
716
  if (event.item?.type === "command_execution") {
717
717
  return assessmentCommandMilestone(event.item.command);
@@ -725,16 +725,16 @@ export function assessmentProgressMessage(event) {
725
725
  if (event.type === "item.completed" && event.item?.type === "agent_message") {
726
726
  return "Synthesizing findings and highest-return actions";
727
727
  }
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
- }
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
+ }
738
738
 
739
739
  export function promptProgressMessage(event) {
740
740
  if (!event || typeof event !== "object") return null;
@@ -824,7 +824,7 @@ function assessmentCommandMilestone(command) {
824
824
  ? command.join(" ")
825
825
  : String(command || "");
826
826
  const normalized = value.replace(/\s+/g, " ").trim().toLowerCase();
827
- if (!normalized) return "Inspecting repository evidence";
827
+ if (!normalized) return "Inspecting workspace source";
828
828
  if (/\bgit\s+(status|log|diff|show|rev-parse)\b/.test(normalized)) {
829
829
  return "Comparing Git history and workspace changes";
830
830
  }
@@ -843,194 +843,252 @@ function assessmentCommandMilestone(command) {
843
843
  return "Tracing architecture and code relationships";
844
844
  }
845
845
 
846
- export function workspaceAssessmentExecution(assignment) {
847
- const stage = assignment?.stage;
848
- const requiredOutputHeading =
849
- stage === "synthesis"
850
- ? "# Assessment Stage: Synthesis"
851
- : stage === "capability_catalog"
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
- ) {
860
- throw new Error("EngineerOS assessment assignment has an unsupported stage.");
861
- }
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
+ }
862
854
  return {
863
855
  ...connectorExecution(assignment, { requiredOutputHeading }),
864
856
  requiredOutputHeading,
865
- };
866
- }
867
-
868
- export async function recoverAssessmentStageOutput({
869
- completed,
870
- prompt,
871
- requiredOutputHeading,
872
- retry,
873
- }) {
874
- try {
875
- return {
876
- completed,
877
- correctionUsed: false,
878
- report: normalizeAgentStructuredOutput(
879
- completed.finalMessage,
880
- requiredOutputHeading,
881
- ),
882
- };
883
- } catch (error) {
884
- if (!recoverableStructuredOutputFailure(error)) throw error;
885
- }
886
-
887
- const corrected = await retry(
888
- assessmentStageCorrectionPrompt(prompt, requiredOutputHeading),
889
- completed.sessionId,
890
- );
891
- try {
892
- return {
893
- completed: {
894
- ...corrected,
895
- usage: mergeTokenUsage(completed.usage, corrected.usage),
896
- },
897
- correctionUsed: true,
898
- report: normalizeAgentStructuredOutput(
899
- corrected.finalMessage,
900
- requiredOutputHeading,
901
- ),
902
- };
903
- } catch (error) {
904
- throw new Error(
905
- `Agent did not return the required stage report after one automatic correction attempt. ${error.message}`,
906
- { cause: error },
907
- );
908
- }
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 context already inspected in the previous turn when it is available; inspect only what remains necessary.",
934
+ `The first non-whitespace line of the final answer must be exactly: ${requiredOutputHeading}`,
935
+ "Follow every section, inspection, and completeness rule in the Assignment.",
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;
909
1009
  }
910
1010
 
911
- export function assessmentRejectionCorrectionPrompt(
912
- validationMessage,
913
- requiredOutputHeading,
914
- ) {
915
- return [
916
- "## Rejected Stage Output Recovery",
917
- "",
918
- "EngineerOS rejected the previous stage report because it was incomplete or violated the structured Markdown contract:",
919
- "",
920
- String(validationMessage || "The stage report was rejected.").trim(),
921
- "",
922
- "Treat the previous stage report as the authoritative draft.",
923
- "Copy every valid section and block unchanged; repair only the incomplete or invalid entries identified by EngineerOS validation.",
924
- "Do not re-inspect the repository or replace valid evidence unless the validation error requires it.",
925
- "Return the entire corrected structured Markdown stage report so EngineerOS can validate it atomically, not only the repaired fragment or missing tail.",
926
- `The first non-whitespace line must be exactly: ${requiredOutputHeading}`,
927
- "Do not return progress commentary, an explanation of the correction, or a code fence.",
928
- ].join("\n");
1011
+ export function workspaceAssessmentWorkerLimit(protocol) {
1012
+ return protocol === "acp" ? 1 : 3;
929
1013
  }
930
1014
 
931
- export function assessmentStageCorrectionPrompt(
932
- originalPrompt,
933
- requiredOutputHeading,
1015
+ export function shouldReportAssessmentProgress(
1016
+ {
1017
+ message,
1018
+ progressPercent,
1019
+ lastMessage,
1020
+ lastProgressPercent,
1021
+ lastSentAt,
1022
+ },
1023
+ { now = Date.now(), minimumIntervalMs = 2_000 } = {},
934
1024
  ) {
935
- return [
936
- String(originalPrompt || "").trim(),
937
- "",
938
- "## Incomplete Stage Output Recovery",
939
- "",
940
- "The previous turn ended without a complete stage deliverable. Return the entire structured Markdown stage report now.",
941
- "Use repository evidence already inspected in the previous turn when it is available; inspect only what remains necessary.",
942
- `The first non-whitespace line of the final answer must be exactly: ${requiredOutputHeading}`,
943
- "Follow every section, evidence, and completeness rule in the Assignment.",
944
- "Do not return progress commentary, an explanation of the correction, or a code fence.",
945
- ].join("\n");
946
- }
947
-
948
- function recoverableStructuredOutputFailure(error) {
949
- const message = error instanceof Error ? error.message : "";
1025
+ if (!message) return false;
1026
+ if (message === lastMessage && progressPercent <= lastProgressPercent) {
1027
+ return false;
1028
+ }
950
1029
  return (
951
- message.startsWith("Agent completed") ||
952
- message.startsWith("Agent response")
1030
+ progressPercent > lastProgressPercent ||
1031
+ now - lastSentAt >= minimumIntervalMs
953
1032
  );
954
1033
  }
955
-
956
- function mergeTokenUsage(first, second) {
957
- const usages = [first, second].filter(
958
- (usage) => usage && typeof usage === "object",
959
- );
960
- if (!usages.length) return null;
961
- return Object.fromEntries(
962
- [
963
- "input_tokens",
964
- "output_tokens",
965
- "cache_read_tokens",
966
- "cache_write_tokens",
967
- "reasoning_tokens",
968
- "total_tokens",
969
- ].map((field) => [
970
- field,
971
- usages.reduce(
972
- (total, usage) =>
973
- total + (Number.isFinite(usage[field]) ? usage[field] : 0),
974
- 0,
975
- ),
976
- ]),
1034
+
1035
+ export function requeueInterruptedAssessment(
1036
+ queue,
1037
+ assessmentState,
1038
+ { accepted, failureReported },
1039
+ ) {
1040
+ if (accepted || failureReported || !assessmentState?.resumeAssignment) {
1041
+ return false;
1042
+ }
1043
+ return (
1044
+ enqueueWorkspaceAssessment(
1045
+ queue,
1046
+ null,
1047
+ assessmentState.resumeAssignment,
1048
+ { front: true },
1049
+ ) === "queued"
977
1050
  );
978
1051
  }
979
1052
 
980
- export function enqueueWorkspaceAssessment(
981
- queue,
982
- activeAssessment,
983
- assignment,
984
- { front = false } = {},
1053
+ export async function submitAssessmentResultWithRetry(
1054
+ payload,
1055
+ submit,
1056
+ {
1057
+ isActive = () => true,
1058
+ onRetry = () => {},
1059
+ wait = (milliseconds) =>
1060
+ new Promise((resolve) => setTimeout(resolve, milliseconds)),
1061
+ initialDelayMs = 1_000,
1062
+ maxDelayMs = 30_000,
1063
+ } = {},
985
1064
  ) {
986
- const matches = (candidate) =>
987
- candidate?.assessment_id === assignment?.assessment_id &&
988
- candidate?.stage === assignment?.stage;
989
- if (
990
- activeAssessment?.kind === "assessment" &&
991
- activeAssessment.runId === assignment?.assessment_id &&
992
- activeAssessment.stage === assignment?.stage
993
- ) {
994
- activeAssessment.resumeAssignment = assignment;
995
- return "deferred";
996
- }
997
- if (queue.some(matches)) return "duplicate";
998
- if (front) queue.unshift(assignment);
999
- else queue.push(assignment);
1000
- return "queued";
1001
- }
1002
-
1003
- export function takeWorkspaceAssessmentWave(queue, activeCount, limit = 3) {
1004
- const available = Math.max(0, limit - activeCount);
1005
- if (!available || !queue.length) return [];
1006
- if (!String(queue[0]?.stage || "").startsWith("capability:")) {
1007
- return activeCount === 0 ? queue.splice(0, 1) : [];
1008
- }
1009
- const wave = [];
1010
- while (
1011
- wave.length < available &&
1012
- String(queue[0]?.stage || "").startsWith("capability:")
1013
- ) {
1014
- wave.push(queue.shift());
1065
+ let retryDelayMs = initialDelayMs;
1066
+ while (isActive()) {
1067
+ let retryFailure;
1068
+ try {
1069
+ const response = await submit(payload);
1070
+ if (!isTransientAssessmentResultResponse(response)) return response;
1071
+ await response.body?.cancel?.();
1072
+ retryFailure = new Error(
1073
+ `EngineerOS temporarily rejected the completed assessment result (${response.status}).`,
1074
+ );
1075
+ } catch (error) {
1076
+ retryFailure = error;
1077
+ }
1078
+ if (!isActive()) break;
1079
+ onRetry(retryFailure, retryDelayMs);
1080
+ await wait(retryDelayMs);
1081
+ retryDelayMs = Math.min(maxDelayMs, retryDelayMs * 2);
1015
1082
  }
1016
- return wave;
1083
+ throw new Error("Assessment result delivery stopped before EngineerOS accepted it.");
1017
1084
  }
1018
1085
 
1019
- export function requeueInterruptedAssessment(
1020
- queue,
1021
- assessmentState,
1022
- { accepted, failureReported },
1023
- ) {
1024
- if (accepted || failureReported || !assessmentState?.resumeAssignment) {
1025
- return false;
1026
- }
1086
+ function isTransientAssessmentResultResponse(response) {
1027
1087
  return (
1028
- enqueueWorkspaceAssessment(
1029
- queue,
1030
- null,
1031
- assessmentState.resumeAssignment,
1032
- { front: true },
1033
- ) === "queued"
1088
+ response.status === 408 ||
1089
+ response.status === 425 ||
1090
+ response.status === 429 ||
1091
+ response.status >= 500
1034
1092
  );
1035
1093
  }
1036
1094
 
@@ -1084,13 +1142,13 @@ export function connectorExecution(assignment, options = {}) {
1084
1142
  };
1085
1143
  }
1086
1144
 
1087
- export async function stopProcess(child) {
1088
- if (!child || child.exitCode !== null) return;
1089
- if (typeof child.engineerOsCancel === "function") {
1090
- await child.engineerOsCancel();
1091
- return;
1092
- }
1093
- if (process.platform === "win32") {
1145
+ export async function stopProcess(child) {
1146
+ if (!child || child.exitCode !== null) return;
1147
+ if (typeof child.engineerOsCancel === "function") {
1148
+ await child.engineerOsCancel();
1149
+ return;
1150
+ }
1151
+ if (process.platform === "win32") {
1094
1152
  await run(
1095
1153
  "taskkill",
1096
1154
  ["/pid", String(child.pid), "/t", "/f"],