@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 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 source-cited findings in System State and promotes the highest-return corrective action in Steering. Assessment cannot modify the workspace.
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) => candidate.assessment_id === message.assessment_id,
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
- `EngineerOS rejected the workspace (${response.status}): ${await response.text()}`,
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: ${error instanceof Error ? error.message : String(error)}`,
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: error instanceof Error ? error.message : String(error),
560
+ message,
554
561
  }),
555
562
  );
556
563
  }
557
564
  if (!promptState.cancelled) {
558
- console.error(error instanceof Error ? error.message : String(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
- console.log(`Agent is assessing the workspace (${assessmentId}).`);
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 (socket.readyState !== WebSocket.OPEN || active?.runId !== assessmentId)
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("Starting read-only repository assessment");
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) active.child = child;
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
- `EngineerOS rejected the assessment (${response.status}): ${await response.text()}`,
682
+ await describeRejectedResponse(response, "the assessment"),
666
683
  );
667
684
  }
668
- console.log("Workspace assessment is current in EngineerOS.");
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
- message: error instanceof Error ? error.message : String(error),
694
+ stage,
695
+ message,
677
696
  }),
678
697
  );
679
698
  }
680
- console.error(error instanceof Error ? error.message : String(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
- `EngineerOS rejected the result (${response.status}): ${await response.text()}`,
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: error instanceof Error ? error.message : String(error),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engineeros/connector",
3
- "version": "0.12.4",
3
+ "version": "0.13.0",
4
4
  "description": "Connect a local coding agent to EngineerOS, using ACP when supported.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -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
- report_markdown: report,
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 requiredOutputHeading =
766
- assignment?.assessment_mode === "incremental"
767
- ? "# Workspace Assessment Delta"
768
- : "# Workspace Assessment";
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,