@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 +10 -0
- package/bin/engineeros-connector.mjs +45 -7
- package/package.json +4 -2
- package/src/agent-harness.mjs +85 -10
- package/src/runner.mjs +41 -10
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
|
|
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.
|
|
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"
|
package/src/agent-harness.mjs
CHANGED
|
@@ -45,7 +45,12 @@ export function agentHarnessCapabilities() {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export function buildAgentHarnessPrompt({
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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 {
|
|
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("
|
|
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
|
|
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:
|
|
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 =
|
|
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 =
|
|
346
|
-
|
|
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("
|
|
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
|
|
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
|
-
"
|
|
947
|
+
"Agent completed without announcing a resumable session id.",
|
|
917
948
|
),
|
|
918
949
|
);
|
|
919
950
|
else reject(new Error(codexFailureMessage(output, code)));
|