@engineeros/connector 0.4.6 → 0.5.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/runner.mjs +127 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engineeros/connector",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
4
4
  "description": "Connect a local Codex CLI workspace to EngineerOS over an outbound WebSocket.",
5
5
  "private": false,
6
6
  "type": "module",
package/src/runner.mjs CHANGED
@@ -1,4 +1,3 @@
1
- import { createHash } from "node:crypto";
2
1
  import { spawn } from "node:child_process";
3
2
  import {
4
3
  lstat,
@@ -106,21 +105,117 @@ export async function executeAssignment(assignment, config, callbacks) {
106
105
  const changedFiles = await changedFilePaths(runWorkspace);
107
106
  if (!changedFiles.length)
108
107
  throw new Error("Codex completed without changing any files.");
109
- const diffPatch = await boundedDiff(runWorkspace, changedFiles);
110
- const archive = await repositoryArchive(runWorkspace);
111
- const revision = `worktree-${createHash("sha256").update(diffPatch).digest("hex").slice(0, 55)}`;
108
+ const committed = await commitRunChange(
109
+ runWorkspace,
110
+ assignment.base_revision,
111
+ assignment.run_id,
112
+ );
113
+ const verifier = launchCodexProcess(
114
+ runWorkspace,
115
+ verificationPrompt(assignment, committed.revision, committed.changedFiles),
116
+ "read-only",
117
+ callbacks,
118
+ );
119
+ callbacks.onProcess?.(verifier.child);
120
+ const verification = await verifier.completed;
121
+ const proofEvidence = parseVerificationReport(
122
+ verification.finalMessage,
123
+ assignment.proof_checks,
124
+ config.connector_id,
125
+ assignment.run_id,
126
+ );
112
127
  return {
113
- head_revision: revision,
128
+ head_revision: committed.revision,
114
129
  repository_locator: assignment.repository_locator,
115
130
  external_reference: `connector:${config.connector_id}/run:${assignment.run_id}`,
116
- diff_patch: diffPatch,
117
- changed_files: changedFiles,
118
- repository_archive_base64: archive.toString("base64"),
119
- repository_archive_media_type: "application/zip",
120
- proof_evidence: [],
131
+ diff_patch: committed.diffPatch,
132
+ changed_files: committed.changedFiles,
133
+ proof_evidence: proofEvidence,
134
+ verification_report: verification.finalMessage,
135
+ };
136
+ }
137
+
138
+ async function commitRunChange(workspace, baseRevision, runId) {
139
+ await run("git", ["add", "-A"], workspace);
140
+ await run(
141
+ "git",
142
+ [
143
+ "-c",
144
+ "user.name=EngineerOS Codex",
145
+ "-c",
146
+ "user.email=codex@engineeros.local",
147
+ "commit",
148
+ "-m",
149
+ `EngineerOS Goal Run ${runId}`,
150
+ ],
151
+ workspace,
152
+ );
153
+ const head = await run("git", ["rev-parse", "HEAD"], workspace);
154
+ const revision = head.stdout.trim();
155
+ const diff = await run(
156
+ "git",
157
+ ["diff", "--binary", baseRevision, revision, "--"],
158
+ workspace,
159
+ );
160
+ const paths = await run(
161
+ "git",
162
+ ["diff", "--name-only", "-z", baseRevision, revision, "--"],
163
+ workspace,
164
+ );
165
+ return {
166
+ revision,
167
+ diffPatch: diff.stdout,
168
+ changedFiles: gitPathList(paths.stdout).sort(),
121
169
  };
122
170
  }
123
171
 
172
+ export function verificationPrompt(assignment, revision, changedFiles) {
173
+ const checks = (assignment.proof_checks ?? [])
174
+ .map(
175
+ (proof, index) =>
176
+ `## Proof ${index + 1}\n- Check: ${proof.check ?? ""}\n- Expected: ${proof.expected ?? ""}`,
177
+ )
178
+ .join("\n\n");
179
+ return `# EngineerOS Connected Verification
180
+
181
+ Independently verify the frozen Goal at Git commit \`${revision}\`. Do not modify files. Run the commands or inspections needed for every Proof item, and check the Goal boundaries and constraints in the supplied packet.
182
+
183
+ Changed files:
184
+ ${changedFiles.map((item) => `- \`${item}\``).join("\n")}
185
+
186
+ ${checks}
187
+
188
+ Return structured Markdown only, with exactly one section per Proof:
189
+
190
+ ## Proof 1
191
+ - Status: passed or failed
192
+ - Exit code: integer or none
193
+ - Evidence: concise observed output and command
194
+
195
+ Do not claim a Proof passed unless you observed it directly.`;
196
+ }
197
+
198
+ export function parseVerificationReport(report, proofChecks, connectorId, runId) {
199
+ if (!report?.trim()) throw new Error("Codex returned no connected verification report.");
200
+ return (proofChecks ?? []).map((_, index) => {
201
+ const start = new RegExp(`^## Proof ${index + 1}\\s*$`, "im").exec(report);
202
+ const tail = start ? report.slice(start.index + start[0].length) : "";
203
+ const section = tail.split(/^## Proof \d+\s*$/im)[0] ?? "";
204
+ const status = /^- Status:\s*(passed|failed)\s*$/im.exec(section)?.[1] ?? "failed";
205
+ const exitValue = /^- Exit code:\s*(\d+|none)\s*$/im.exec(section)?.[1] ?? "none";
206
+ const evidence = /^- Evidence:\s*(.+)$/im.exec(section)?.[1]?.trim();
207
+ return {
208
+ proof_index: index,
209
+ status,
210
+ verifier_type: "agent_reported",
211
+ verifier_identity: "Connected Codex CLI verifier",
212
+ locator: `connector:${connectorId}/run:${runId}#proof-${index + 1}`,
213
+ output_excerpt: evidence || "The connected verifier did not provide evidence for this Proof.",
214
+ exit_code: exitValue === "none" ? null : Number(exitValue),
215
+ };
216
+ });
217
+ }
218
+
124
219
  export async function executeWorkspaceAssessment(
125
220
  assignment,
126
221
  config,
@@ -585,20 +680,17 @@ function runCodexCommand(command, args, cwd) {
585
680
  async function changedFilePaths(workspace) {
586
681
  const tracked = await run(
587
682
  "git",
588
- ["diff", "--name-only", "HEAD", "--"],
683
+ ["diff", "--name-only", "-z", "HEAD", "--"],
589
684
  workspace,
590
685
  );
591
686
  const untracked = await run(
592
687
  "git",
593
- ["ls-files", "--others", "--exclude-standard"],
688
+ ["ls-files", "-z", "--others", "--exclude-standard"],
594
689
  workspace,
595
690
  );
596
691
  return [
597
692
  ...new Set(
598
- `${tracked.stdout}\n${untracked.stdout}`
599
- .split(/\r?\n/)
600
- .map(normalizePath)
601
- .filter(Boolean),
693
+ [...gitPathList(tracked.stdout), ...gitPathList(untracked.stdout)],
602
694
  ),
603
695
  ].sort();
604
696
  }
@@ -610,16 +702,15 @@ async function boundedDiff(workspace, changedFiles) {
610
702
  workspace,
611
703
  );
612
704
  const untracked = new Set(
613
- (
614
- await run(
615
- "git",
616
- ["ls-files", "--others", "--exclude-standard"],
617
- workspace,
618
- )
619
- ).stdout
620
- .split(/\r?\n/)
621
- .map(normalizePath)
622
- .filter(Boolean),
705
+ gitPathList(
706
+ (
707
+ await run(
708
+ "git",
709
+ ["ls-files", "-z", "--others", "--exclude-standard"],
710
+ workspace,
711
+ )
712
+ ).stdout,
713
+ ),
623
714
  );
624
715
  const parts = [tracked.stdout];
625
716
  for (const relative of changedFiles.filter((item) => untracked.has(item))) {
@@ -638,15 +729,13 @@ async function boundedDiff(workspace, changedFiles) {
638
729
  return diff;
639
730
  }
640
731
 
641
- async function repositoryArchive(workspace) {
732
+ export async function repositoryArchive(workspace) {
642
733
  const listed = await run(
643
734
  "git",
644
- ["ls-files", "-co", "--exclude-standard"],
735
+ ["ls-files", "-z", "-co", "--exclude-standard"],
645
736
  workspace,
646
737
  );
647
- const files = [
648
- ...new Set(listed.stdout.split(/\r?\n/).map(normalizePath).filter(Boolean)),
649
- ].sort();
738
+ const files = [...new Set(gitPathList(listed.stdout))].sort();
650
739
  return createZip(
651
740
  await Promise.all(
652
741
  files.map(async (relative) => ({
@@ -801,6 +890,13 @@ function normalizePath(value) {
801
890
  .replace(/^\.\//, "");
802
891
  }
803
892
 
893
+ function gitPathList(value) {
894
+ return String(value ?? "")
895
+ .split("\0")
896
+ .map(normalizePath)
897
+ .filter(Boolean);
898
+ }
899
+
804
900
  function run(command, args, cwd, options = {}) {
805
901
  return new Promise((resolve, reject) => {
806
902
  const child = spawn(command, args, {