@ryanfw/prompt-orchestration-pipeline 1.3.1 → 1.3.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/docs/pop-task-guide.md +1 -0
- package/package.json +1 -2
- package/src/core/__tests__/agent-step.test.ts +322 -3
- package/src/core/__tests__/task-runner.test.ts +57 -1
- package/src/core/agent-step.ts +231 -14
- package/src/core/agent-types.ts +3 -0
- package/src/core/file-io.ts +8 -74
- package/src/core/pipeline-runner.ts +54 -3
- package/src/core/status-writer.ts +44 -26
- package/src/core/task-runner.ts +27 -3
- package/src/harness/__tests__/discovery.test.ts +60 -8
- package/src/harness/discovery.ts +16 -6
- package/src/ui/client/hooks/useJobListWithUpdates.ts +16 -1
- package/src/ui/dist/assets/{index-CbS3OsW7.js → index--RH3sAt3.js} +14 -1
- package/src/ui/dist/assets/{index-CbS3OsW7.js.map → index--RH3sAt3.js.map} +1 -1
- package/src/ui/dist/assets/style-CSSKuMOe.css +2 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/embedded-assets.js +6 -6
- package/src/ui/server/__tests__/gate-endpoints.test.ts +90 -0
- package/src/ui/server/__tests__/job-control-endpoints.test.ts +188 -0
- package/src/ui/server/__tests__/path-containment.test.ts +54 -0
- package/src/ui/server/__tests__/status-corruption.test.ts +55 -0
- package/src/ui/server/config-bridge.ts +1 -0
- package/src/ui/server/endpoints/__tests__/upload-endpoints.test.ts +77 -0
- package/src/ui/server/endpoints/gate-endpoints.ts +17 -1
- package/src/ui/server/endpoints/job-control-endpoints.ts +36 -2
- package/src/ui/server/endpoints/upload-endpoints.ts +13 -3
- package/src/ui/server/utils/http-utils.ts +6 -1
- package/src/ui/server/utils/path-containment.ts +18 -0
- package/src/ui/server/utils/status-corruption.ts +27 -0
- package/src/ui/dist/assets/style-BUFg3Sth.css +0 -2
|
@@ -25,14 +25,8 @@ describe("healedPath", () => {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it("does not duplicate dirs already on the base PATH", () => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const testDir = join(home, ".local/bin");
|
|
31
|
-
// Only test if that dir actually exists on this machine
|
|
32
|
-
const result = healedPath(`${testDir}:/usr/bin`);
|
|
33
|
-
const count = result.split(":").filter((d) => d === testDir).length;
|
|
34
|
-
// Should be 0 or 1 — if dir exists it appears once, if not it appears zero times
|
|
35
|
-
expect(count).toBeLessThanOrEqual(1);
|
|
28
|
+
const result = healedPath(`${existingDir}:/usr/bin`, [existingDir]);
|
|
29
|
+
expect(result.split(":").filter((d) => d === existingDir)).toHaveLength(1);
|
|
36
30
|
});
|
|
37
31
|
|
|
38
32
|
it("defaults to process.env.PATH when basePath is undefined", () => {
|
|
@@ -180,4 +174,62 @@ describe("applyHarnessDiscovery", () => {
|
|
|
180
174
|
expect(env.POP_CODEX_BIN).toBeUndefined();
|
|
181
175
|
expect(env.LLM_ADAPTER_CODEX_BIN).toBeUndefined();
|
|
182
176
|
});
|
|
177
|
+
|
|
178
|
+
it("bridges existing POP_* binary overrides to adapter env vars when discovery misses", () => {
|
|
179
|
+
const env: Record<string, string | undefined> = {
|
|
180
|
+
PATH: "/usr/bin",
|
|
181
|
+
POP_OPENCODE_BIN: "/custom/opencode",
|
|
182
|
+
};
|
|
183
|
+
const statuses: Record<HarnessName, HarnessStatus> = {
|
|
184
|
+
opencode: makeStatus({ name: "opencode", installed: false }),
|
|
185
|
+
claude: makeStatus({ name: "claude", installed: false }),
|
|
186
|
+
codex: makeStatus({ name: "codex", installed: false }),
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const messages = applyHarnessDiscovery(statuses, env);
|
|
190
|
+
|
|
191
|
+
expect(env.POP_OPENCODE_BIN).toBe("/custom/opencode");
|
|
192
|
+
expect(env.LLM_ADAPTER_OPENCODE_BIN).toBe("/custom/opencode");
|
|
193
|
+
expect(messages.find((m) => m.message.includes('"opencode"'))).toMatchObject({
|
|
194
|
+
level: "info",
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("bridges existing adapter binary overrides back to POP env vars when discovery misses", () => {
|
|
199
|
+
const env: Record<string, string | undefined> = {
|
|
200
|
+
PATH: "/usr/bin",
|
|
201
|
+
LLM_ADAPTER_CODEX_BIN: "/custom/codex",
|
|
202
|
+
};
|
|
203
|
+
const statuses: Record<HarnessName, HarnessStatus> = {
|
|
204
|
+
opencode: makeStatus({ name: "opencode", installed: false }),
|
|
205
|
+
claude: makeStatus({ name: "claude", installed: false }),
|
|
206
|
+
codex: makeStatus({ name: "codex", installed: false }),
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
applyHarnessDiscovery(statuses, env);
|
|
210
|
+
|
|
211
|
+
expect(env.LLM_ADAPTER_CODEX_BIN).toBe("/custom/codex");
|
|
212
|
+
expect(env.POP_CODEX_BIN).toBe("/custom/codex");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("keeps POP_* binary overrides ahead of discovered PATH binaries", () => {
|
|
216
|
+
const env: Record<string, string | undefined> = {
|
|
217
|
+
PATH: "/usr/bin",
|
|
218
|
+
POP_CODEX_BIN: "/custom/codex",
|
|
219
|
+
};
|
|
220
|
+
const statuses: Record<HarnessName, HarnessStatus> = {
|
|
221
|
+
opencode: makeStatus({ name: "opencode", installed: false }),
|
|
222
|
+
claude: makeStatus({ name: "claude", installed: false }),
|
|
223
|
+
codex: makeStatus({ name: "codex", installed: true, binPath: "/path/codex", authenticated: true }),
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const messages = applyHarnessDiscovery(statuses, env);
|
|
227
|
+
|
|
228
|
+
expect(env.POP_CODEX_BIN).toBe("/custom/codex");
|
|
229
|
+
expect(env.LLM_ADAPTER_CODEX_BIN).toBe("/custom/codex");
|
|
230
|
+
expect(messages.find((m) => m.message.includes('"codex"'))).toMatchObject({
|
|
231
|
+
level: "info",
|
|
232
|
+
message: 'harness "codex": using configured binary override /custom/codex',
|
|
233
|
+
});
|
|
234
|
+
});
|
|
183
235
|
});
|
package/src/harness/discovery.ts
CHANGED
|
@@ -32,11 +32,11 @@ function standardBinDirs(): string[] {
|
|
|
32
32
|
* Build a PATH with known bin dirs prepended — keeping only dirs that exist
|
|
33
33
|
* and are not already present.
|
|
34
34
|
*/
|
|
35
|
-
export function healedPath(basePath?: string): string {
|
|
35
|
+
export function healedPath(basePath?: string, candidateDirs = standardBinDirs()): string {
|
|
36
36
|
const current = (basePath ?? process.env.PATH ?? "").split(":").filter(Boolean);
|
|
37
37
|
const seen = new Set(current);
|
|
38
38
|
const prepend: string[] = [];
|
|
39
|
-
for (const dir of
|
|
39
|
+
for (const dir of candidateDirs) {
|
|
40
40
|
if (dir && !seen.has(dir) && existsSync(dir)) {
|
|
41
41
|
prepend.push(dir);
|
|
42
42
|
seen.add(dir);
|
|
@@ -68,12 +68,22 @@ export function applyHarnessDiscovery(
|
|
|
68
68
|
|
|
69
69
|
const messages: PreflightMessage[] = [];
|
|
70
70
|
for (const status of Object.values(statuses) as HarnessStatus[]) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
const adapterEnv = adapterBinEnvVar(status.name);
|
|
72
|
+
const popEnv = popBinEnvVar(status.name);
|
|
73
|
+
const explicitBin = env[popEnv] ?? env[adapterEnv];
|
|
74
|
+
const configuredBin = explicitBin ?? status.binPath;
|
|
75
|
+
|
|
76
|
+
if (configuredBin) {
|
|
77
|
+
env[adapterEnv] = configuredBin;
|
|
78
|
+
env[popEnv] = configuredBin;
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
if (
|
|
81
|
+
if (explicitBin) {
|
|
82
|
+
messages.push({
|
|
83
|
+
level: "info",
|
|
84
|
+
message: `harness "${status.name}": using configured binary override ${configuredBin}`,
|
|
85
|
+
});
|
|
86
|
+
} else if (!status.installed && !configuredBin) {
|
|
77
87
|
messages.push({
|
|
78
88
|
level: "warn",
|
|
79
89
|
message: `harness "${status.name}": CLI not found on PATH or known install dirs; agent steps using it will fail until it is installed`,
|
|
@@ -33,8 +33,23 @@ function sortNormalizedJobs(jobs: NormalizedJobSummary[]): NormalizedJobSummary[
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function completedCountFromTasks(job: NormalizedJobSummary): number {
|
|
37
|
+
return Object.values(job.tasks).filter((task) => task.state === "done" || task.state === "skipped").length;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function comparableJob(job: NormalizedJobSummary): Record<string, unknown> {
|
|
41
|
+
const comparable: Record<string, unknown> = { ...job };
|
|
42
|
+
for (const [key, value] of Object.entries(comparable)) {
|
|
43
|
+
if (value === undefined) delete comparable[key];
|
|
44
|
+
}
|
|
45
|
+
if (comparable["completedCount"] === completedCountFromTasks(job)) {
|
|
46
|
+
delete comparable["completedCount"];
|
|
47
|
+
}
|
|
48
|
+
return comparable;
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
function jobsEqual(left: NormalizedJobSummary[], right: NormalizedJobSummary[]): boolean {
|
|
37
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
52
|
+
return JSON.stringify(left.map(comparableJob)) === JSON.stringify(right.map(comparableJob));
|
|
38
53
|
}
|
|
39
54
|
|
|
40
55
|
function mergeJob(current: NormalizedJobSummary, incoming: NormalizedJobSummary): NormalizedJobSummary {
|
|
@@ -78431,8 +78431,21 @@ function sortNormalizedJobs(jobs) {
|
|
|
78431
78431
|
return left.jobId.localeCompare(right.jobId);
|
|
78432
78432
|
});
|
|
78433
78433
|
}
|
|
78434
|
+
function completedCountFromTasks(job) {
|
|
78435
|
+
return Object.values(job.tasks).filter((task) => task.state === "done" || task.state === "skipped").length;
|
|
78436
|
+
}
|
|
78437
|
+
function comparableJob(job) {
|
|
78438
|
+
const comparable = { ...job };
|
|
78439
|
+
for (const [key, value] of Object.entries(comparable)) {
|
|
78440
|
+
if (value === void 0) delete comparable[key];
|
|
78441
|
+
}
|
|
78442
|
+
if (comparable["completedCount"] === completedCountFromTasks(job)) {
|
|
78443
|
+
delete comparable["completedCount"];
|
|
78444
|
+
}
|
|
78445
|
+
return comparable;
|
|
78446
|
+
}
|
|
78434
78447
|
function jobsEqual(left, right) {
|
|
78435
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
78448
|
+
return JSON.stringify(left.map(comparableJob)) === JSON.stringify(right.map(comparableJob));
|
|
78436
78449
|
}
|
|
78437
78450
|
function mergeJob(current, incoming) {
|
|
78438
78451
|
return {
|