@engineeros/connector 0.9.0 → 0.9.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 CHANGED
@@ -92,3 +92,13 @@ npm install -g @openai/codex@latest
92
92
  codex --version
93
93
  npx @engineeros/connector start --workspace .
94
94
  ```
95
+
96
+ ## Publish the connector
97
+
98
+ Run the release workflow from this package instead of calling `npm publish` directly:
99
+
100
+ ```sh
101
+ npm run release:patch
102
+ ```
103
+
104
+ The workflow checks the versions already present on npm, keeps the current version when it is unpublished, or advances to the next unused patch version when necessary. It then runs the connector tests, syntax checks, and package dry run before publishing. A direct `npm publish` now stops early with the corrective command when its version already exists.
@@ -13,6 +13,7 @@ import {
13
13
  } from "../src/config.mjs";
14
14
  import {
15
15
  assessmentProgressMessage,
16
+ assessmentStreamDelta,
16
17
  applyAcceptedChange,
17
18
  executeAssignment,
18
19
  executeConnectedPrompt,
@@ -396,7 +397,7 @@ async function submitWorkspaceSnapshot() {
396
397
  `Sent ${snapshot.file_count} safe file(s); ${snapshot.excluded_file_count} sensitive or generated path(s) excluded.`,
397
398
  );
398
399
  console.log(
399
- `Workspace assessed as ${result.workspace_kind}. EngineerOS System State is current.`,
400
+ `Workspace inventory registered as ${result.workspace_kind}. Assessment queued.`,
400
401
  );
401
402
  snapshotInFlight = false;
402
403
  } catch (error) {
@@ -553,10 +554,11 @@ async function cancelPrompt(promptState) {
553
554
 
554
555
  async function executeAssessment(assignment) {
555
556
  const assessmentId = assignment.assessment_id;
556
- console.log(
557
- `Assessing workspace with ${codingAgent.name} (${assessmentId}).`,
558
- );
557
+ console.log(`Agent is assessing the workspace (${assessmentId}).`);
559
558
  let progress = 10;
559
+ let outputBuffer = "";
560
+ let outputLength = 0;
561
+ let outputTimer;
560
562
  const reportedMilestones = new Set();
561
563
  const reportProgress = (message, { milestone = true } = {}) => {
562
564
  if (socket.readyState !== WebSocket.OPEN || active?.runId !== assessmentId)
@@ -572,6 +574,40 @@ async function executeAssessment(assignment) {
572
574
  }),
573
575
  );
574
576
  };
577
+ const flushOutput = () => {
578
+ if (outputTimer) clearTimeout(outputTimer);
579
+ outputTimer = undefined;
580
+ const output = outputBuffer;
581
+ outputBuffer = "";
582
+ if (
583
+ !output ||
584
+ socket.readyState !== WebSocket.OPEN ||
585
+ active?.runId !== assessmentId
586
+ ) {
587
+ return;
588
+ }
589
+ for (let offset = 0; offset < output.length; offset += 50_000) {
590
+ socket.send(
591
+ JSON.stringify({
592
+ type: "workspace.assessment.output",
593
+ assessment_id: assessmentId,
594
+ delta: output.slice(offset, offset + 50_000),
595
+ }),
596
+ );
597
+ }
598
+ };
599
+ const reportOutput = (delta) => {
600
+ const remaining = 120_000 - outputLength;
601
+ if (!delta || remaining <= 0) return;
602
+ const bounded = String(delta).slice(0, remaining);
603
+ outputBuffer += bounded;
604
+ outputLength += bounded.length;
605
+ if (outputBuffer.length >= 50_000) {
606
+ flushOutput();
607
+ } else if (!outputTimer) {
608
+ outputTimer = setTimeout(flushOutput, 500);
609
+ }
610
+ };
575
611
  reportProgress("Starting read-only repository assessment");
576
612
  const heartbeat = setInterval(() => {
577
613
  progress = Math.min(90, progress + 5);
@@ -587,10 +623,12 @@ async function executeAssessment(assignment) {
587
623
  if (active?.runId === assessmentId) active.child = child;
588
624
  },
589
625
  onEvent: (event) => {
626
+ reportOutput(assessmentStreamDelta(event));
590
627
  const message = assessmentProgressMessage(event);
591
628
  if (message) reportProgress(message);
592
629
  },
593
630
  });
631
+ flushOutput();
594
632
  const response = await fetch(
595
633
  assessmentResultUrl(config.server_url, config.connector_id, assessmentId),
596
634
  {
@@ -607,10 +645,9 @@ async function executeAssessment(assignment) {
607
645
  `EngineerOS rejected the assessment (${response.status}): ${await response.text()}`,
608
646
  );
609
647
  }
610
- console.log(
611
- `${codingAgent.name} workspace assessment is current in EngineerOS.`,
612
- );
648
+ console.log("Workspace assessment is current in EngineerOS.");
613
649
  } catch (error) {
650
+ flushOutput();
614
651
  if (socket.readyState === WebSocket.OPEN) {
615
652
  socket.send(
616
653
  JSON.stringify({
@@ -622,6 +659,7 @@ async function executeAssessment(assignment) {
622
659
  }
623
660
  console.error(error instanceof Error ? error.message : String(error));
624
661
  } finally {
662
+ if (outputTimer) clearTimeout(outputTimer);
625
663
  clearInterval(heartbeat);
626
664
  active = null;
627
665
  pump();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engineeros/connector",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Connect a local coding agent to EngineerOS, using ACP when supported.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -15,8 +15,10 @@
15
15
  },
16
16
  "scripts": {
17
17
  "start": "node ./bin/engineeros-connector.mjs",
18
+ "prepublishOnly": "node ./scripts/release.mjs check",
19
+ "release:patch": "node ./scripts/release.mjs patch",
18
20
  "test": "node --test --test-concurrency=1",
19
- "type-check": "node --check ./bin/engineeros-connector.mjs && node --check ./src/acp-client.mjs && node --check ./src/agent-harness.mjs && node --check ./src/agent-registry.mjs && node --check ./src/capabilities.mjs && node --check ./src/cli-args.mjs && node --check ./src/config.mjs && node --check ./src/connection.mjs && node --check ./src/mcp-server.mjs && node --check ./src/runner.mjs"
21
+ "type-check": "node --check ./bin/engineeros-connector.mjs && node --check ./scripts/release.mjs && node --check ./src/acp-client.mjs && node --check ./src/agent-harness.mjs && node --check ./src/agent-registry.mjs && node --check ./src/capabilities.mjs && node --check ./src/cli-args.mjs && node --check ./src/config.mjs && node --check ./src/connection.mjs && node --check ./src/mcp-server.mjs && node --check ./src/runner.mjs"
20
22
  },
21
23
  "engines": {
22
24
  "node": ">=22"
@@ -45,7 +45,12 @@ export function agentHarnessCapabilities() {
45
45
  };
46
46
  }
47
47
 
48
- export function buildAgentHarnessPrompt({ agentRole, prompt, sandboxMode }) {
48
+ export function buildAgentHarnessPrompt({
49
+ agentRole,
50
+ prompt,
51
+ sandboxMode,
52
+ requiredOutputHeading,
53
+ }) {
49
54
  const role = ROLE_DEFINITIONS[agentRole];
50
55
  if (!role) {
51
56
  throw new Error(
@@ -61,8 +66,9 @@ export function buildAgentHarnessPrompt({ agentRole, prompt, sandboxMode }) {
61
66
  if (!assignment) {
62
67
  throw new Error("EngineerOS assignment is missing prompt_markdown.");
63
68
  }
69
+ const outputHeading = normalizedOutputHeading(requiredOutputHeading);
64
70
 
65
- return [
71
+ const sections = [
66
72
  "# EngineerOS Agent Assignment",
67
73
  "",
68
74
  "## Active Role",
@@ -71,13 +77,20 @@ export function buildAgentHarnessPrompt({ agentRole, prompt, sandboxMode }) {
71
77
  `- Access: ${role.sandboxMode}`,
72
78
  `- Responsibility: ${role.instruction}`,
73
79
  "- User-facing language: refer to yourself neutrally as the Agent. Do not expose internal role, harness, provider, or coding-agent terminology unless the user asks.",
74
- "",
75
- "## Interaction Contract",
76
- "",
77
- "- Infer the current project situation from the assignment and workspace evidence before responding.",
78
- "- Lead with the useful answer or outcome. Do not narrate routine searches, tool calls, or internal work.",
79
- "- When one next activity clearly follows and would help, offer exactly one short, concrete suggestion. Do not force a next step when none is useful.",
80
- "- Ask a question only when a consequential choice cannot be resolved from available evidence.",
80
+ ];
81
+ if (!outputHeading) {
82
+ sections.push(
83
+ "",
84
+ "## Interaction Contract",
85
+ "",
86
+ "- Infer the current project situation from the assignment and workspace evidence before responding.",
87
+ "- Lead with the useful answer or outcome. Do not narrate routine searches, tool calls, or internal work.",
88
+ "- When one next activity clearly follows and would help, offer exactly one short, concrete suggestion. Do not force a next step when none is useful.",
89
+ "- Ask a question only when a consequential choice cannot be resolved from available evidence.",
90
+ "- If the Assignment defines a response format, follow it exactly instead of adding conversational guidance.",
91
+ );
92
+ }
93
+ sections.push(
81
94
  "",
82
95
  "## Active Skill",
83
96
  "",
@@ -86,7 +99,58 @@ export function buildAgentHarnessPrompt({ agentRole, prompt, sandboxMode }) {
86
99
  "## Assignment",
87
100
  "",
88
101
  assignment,
89
- ].join("\n");
102
+ );
103
+ if (outputHeading) {
104
+ sections.push(
105
+ "",
106
+ "## Final Response Contract",
107
+ "",
108
+ "- Return only the structured Markdown required by the Assignment.",
109
+ `- The first non-whitespace line must be exactly: ${outputHeading}`,
110
+ "- Do not add a preamble, status message, commentary, or code fence.",
111
+ "- Do not append a conversational summary or next activity.",
112
+ );
113
+ }
114
+ return sections.join("\n");
115
+ }
116
+
117
+ export function normalizeAgentStructuredOutput(
118
+ response,
119
+ requiredOutputHeading,
120
+ ) {
121
+ const outputHeading = normalizedOutputHeading(requiredOutputHeading);
122
+ if (!outputHeading) {
123
+ throw new Error(
124
+ "EngineerOS structured output requires a heading contract.",
125
+ );
126
+ }
127
+ const content = String(response || "").trim();
128
+ if (!content) {
129
+ throw new Error("Agent completed without returning a response.");
130
+ }
131
+ const lines = content.split(/\r?\n/);
132
+ const headingIndex = lines.findIndex(
133
+ (line, index) => index < 20 && line.trim() === outputHeading,
134
+ );
135
+ const preamble =
136
+ headingIndex >= 0 ? lines.slice(0, headingIndex).join("\n") : content;
137
+ if (headingIndex < 0) {
138
+ throw new Error(
139
+ `Agent response is missing the required heading '${outputHeading}'.`,
140
+ );
141
+ }
142
+ if (preamble.length > 2_000) {
143
+ throw new Error(
144
+ `Agent response contains too much text before the required heading '${outputHeading}'.`,
145
+ );
146
+ }
147
+ const normalized = lines.slice(headingIndex).join("\n").trim();
148
+ if (/```/.test(preamble) || /(?:^|\n)```\s*$/.test(normalized)) {
149
+ throw new Error(
150
+ `Agent response must return '${outputHeading}' as plain Markdown, without a code fence.`,
151
+ );
152
+ }
153
+ return normalized;
90
154
  }
91
155
 
92
156
  function bundledSkill(skillName) {
@@ -106,3 +170,14 @@ function bundledSkill(skillName) {
106
170
  );
107
171
  }
108
172
  }
173
+
174
+ function normalizedOutputHeading(value) {
175
+ if (value === undefined || value === null) return null;
176
+ const heading = String(value).trim();
177
+ if (!/^# [^\r\n]+$/.test(heading)) {
178
+ throw new Error(
179
+ "EngineerOS requiredOutputHeading must be one level-one Markdown heading.",
180
+ );
181
+ }
182
+ return heading;
183
+ }
package/src/runner.mjs CHANGED
@@ -12,7 +12,10 @@ import path from "node:path";
12
12
  import { promisify } from "node:util";
13
13
  import { deflateRaw } from "node:zlib";
14
14
  import { launchAcpAgent } from "./acp-client.mjs";
15
- import { buildAgentHarnessPrompt } from "./agent-harness.mjs";
15
+ import {
16
+ buildAgentHarnessPrompt,
17
+ normalizeAgentStructuredOutput,
18
+ } from "./agent-harness.mjs";
16
19
 
17
20
  const deflate = promisify(deflateRaw);
18
21
  const MAX_ARCHIVE_BYTES = 25 * 1024 * 1024;
@@ -110,12 +113,13 @@ export async function executeAssignment(assignment, config, callbacks) {
110
113
  await controller.completed;
111
114
  const changedFiles = await changedFilePaths(runWorkspace);
112
115
  if (!changedFiles.length)
113
- throw new Error("Codex completed without changing any files.");
116
+ throw new Error("Agent completed without changing any files.");
114
117
  const committed = await commitRunChange(
115
118
  runWorkspace,
116
119
  assignment.base_revision,
117
120
  assignment.run_id,
118
121
  );
122
+ const verificationHeading = "# Verification Report";
119
123
  const verifier = launchAgentProcess(
120
124
  runWorkspace,
121
125
  buildAgentHarnessPrompt({
@@ -126,6 +130,7 @@ export async function executeAssignment(assignment, config, callbacks) {
126
130
  committed.changedFiles,
127
131
  ),
128
132
  sandboxMode: "read-only",
133
+ requiredOutputHeading: verificationHeading,
129
134
  }),
130
135
  "read-only",
131
136
  config,
@@ -134,8 +139,12 @@ export async function executeAssignment(assignment, config, callbacks) {
134
139
  );
135
140
  callbacks.onProcess?.(verifier.child);
136
141
  const verification = await verifier.completed;
137
- const proofEvidence = parseVerificationReport(
142
+ const verificationReport = normalizeAgentStructuredOutput(
138
143
  verification.finalMessage,
144
+ verificationHeading,
145
+ );
146
+ const proofEvidence = parseVerificationReport(
147
+ verificationReport,
139
148
  assignment.proof_checks,
140
149
  config.connector_id,
141
150
  assignment.run_id,
@@ -147,7 +156,7 @@ export async function executeAssignment(assignment, config, callbacks) {
147
156
  diff_patch: committed.diffPatch,
148
157
  changed_files: committed.changedFiles,
149
158
  proof_evidence: proofEvidence,
150
- verification_report: verification.finalMessage,
159
+ verification_report: verificationReport,
151
160
  };
152
161
  }
153
162
 
@@ -299,7 +308,7 @@ export async function executeWorkspaceAssessment(
299
308
  config,
300
309
  callbacks,
301
310
  ) {
302
- const execution = connectorExecution(assignment);
311
+ const execution = workspaceAssessmentExecution(assignment);
303
312
  const startingRevision = await run(
304
313
  "git",
305
314
  ["rev-parse", "HEAD"],
@@ -342,8 +351,10 @@ export async function executeWorkspaceAssessment(
342
351
  );
343
352
  callbacks.onProcess?.(controller.child);
344
353
  const completed = await controller.completed;
345
- const report = completed.finalMessage.trim();
346
- if (!report) throw new Error("Codex completed without returning a response.");
354
+ const report = normalizeAgentStructuredOutput(
355
+ completed.finalMessage,
356
+ execution.requiredOutputHeading,
357
+ );
347
358
  const endingRevision = await run(
348
359
  "git",
349
360
  ["rev-parse", "HEAD"],
@@ -504,7 +515,7 @@ export async function executeConnectedPrompt(assignment, config, callbacks) {
504
515
  const completed = await controller.completed;
505
516
  const content = completed.finalMessage.trim();
506
517
  if (!content) {
507
- throw new Error("Codex completed without returning a response.");
518
+ throw new Error("Agent completed without returning a response.");
508
519
  }
509
520
  return {
510
521
  content,
@@ -640,6 +651,14 @@ export function promptStreamEvent(event) {
640
651
  return message ? { kind: "status", message } : null;
641
652
  }
642
653
 
654
+ export function assessmentStreamDelta(event) {
655
+ const streamEvent = promptStreamEvent(event);
656
+ if (streamEvent?.kind !== "message" || !streamEvent.delta) return null;
657
+ return event.type === "item.completed"
658
+ ? `${streamEvent.delta.trim()}\n\n`
659
+ : streamEvent.delta;
660
+ }
661
+
643
662
  function assessmentCommandMilestone(command) {
644
663
  const value = Array.isArray(command)
645
664
  ? command.join(" ")
@@ -664,7 +683,18 @@ function assessmentCommandMilestone(command) {
664
683
  return "Tracing architecture and code relationships";
665
684
  }
666
685
 
667
- export function connectorExecution(assignment) {
686
+ export function workspaceAssessmentExecution(assignment) {
687
+ const requiredOutputHeading =
688
+ assignment?.assessment_mode === "incremental"
689
+ ? "# Workspace Assessment Delta"
690
+ : "# Workspace Assessment";
691
+ return {
692
+ ...connectorExecution(assignment, { requiredOutputHeading }),
693
+ requiredOutputHeading,
694
+ };
695
+ }
696
+
697
+ export function connectorExecution(assignment, options = {}) {
668
698
  const rawPrompt = assignment?.prompt_markdown;
669
699
  if (typeof rawPrompt !== "string" || !rawPrompt.trim()) {
670
700
  throw new Error("EngineerOS assignment is missing prompt_markdown.");
@@ -690,6 +720,7 @@ export function connectorExecution(assignment) {
690
720
  agentRole,
691
721
  prompt: rawPrompt,
692
722
  sandboxMode,
723
+ requiredOutputHeading: options.requiredOutputHeading,
693
724
  });
694
725
  return {
695
726
  prompt,
@@ -913,7 +944,7 @@ function launchCodexProcess(
913
944
  else if (code === 0)
914
945
  reject(
915
946
  new Error(
916
- "Codex completed without announcing a resumable session id.",
947
+ "Agent completed without announcing a resumable session id.",
917
948
  ),
918
949
  );
919
950
  else reject(new Error(codexFailureMessage(output, code)));