@the-open-engine/zeroshot 6.10.2 → 6.12.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/cli/commands/providers.js +13 -13
- package/cli/index.js +77 -35
- package/cli/lib/first-run.js +12 -11
- package/cli/lib/update-checker.js +517 -132
- package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/opencode.js +3 -0
- package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +1 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/cluster/client.cjs +146 -0
- package/lib/cluster/client.d.ts +44 -0
- package/lib/cluster/client.mjs +141 -0
- package/lib/cluster/connection.cjs +382 -0
- package/lib/cluster/connection.d.ts +41 -0
- package/lib/cluster/connection.mjs +378 -0
- package/lib/cluster/errors.cjs +44 -0
- package/lib/cluster/errors.d.ts +23 -0
- package/lib/cluster/errors.mjs +32 -0
- package/lib/cluster/frames.cjs +49 -0
- package/lib/cluster/frames.d.ts +12 -0
- package/lib/cluster/frames.mjs +45 -0
- package/lib/cluster/generated/protocol-schema.cjs +5 -0
- package/lib/cluster/generated/protocol-schema.d.ts +1 -0
- package/lib/cluster/generated/protocol-schema.mjs +2 -0
- package/lib/cluster/generated/protocol.cjs +22 -0
- package/lib/cluster/generated/protocol.d.ts +781 -0
- package/lib/cluster/generated/protocol.mjs +19 -0
- package/lib/cluster/index.cjs +40 -0
- package/lib/cluster/index.d.ts +10 -0
- package/lib/cluster/index.mjs +6 -0
- package/lib/cluster/queue.cjs +71 -0
- package/lib/cluster/queue.d.ts +15 -0
- package/lib/cluster/queue.mjs +67 -0
- package/lib/cluster/socket.cjs +15 -0
- package/lib/cluster/socket.d.ts +11 -0
- package/lib/cluster/socket.mjs +12 -0
- package/lib/cluster/subscriptions.cjs +270 -0
- package/lib/cluster/subscriptions.d.ts +69 -0
- package/lib/cluster/subscriptions.mjs +264 -0
- package/lib/cluster/validators.cjs +46 -0
- package/lib/cluster/validators.d.ts +3 -0
- package/lib/cluster/validators.mjs +42 -0
- package/lib/settings.js +195 -23
- package/lib/setup-apply.js +45 -32
- package/lib/setup-undo.js +25 -16
- package/package.json +25 -3
- package/scripts/build-cluster.js +43 -0
- package/scripts/generate-cluster-types.js +203 -0
- package/scripts/release-dry-run.js +6 -6
- package/scripts/rust-distribution.js +933 -0
- package/src/agent/agent-hook-executor.js +14 -6
- package/src/agent/agent-lifecycle.js +25 -8
- package/src/agent/agent-task-executor.js +711 -139
- package/src/agent/output-reformatter.js +54 -41
- package/src/agent/pr-verification.js +11 -3
- package/src/agent/task-execution-handle.js +373 -0
- package/src/agent-cli-provider/adapters/opencode.ts +3 -0
- package/src/agent-cli-provider/types.ts +1 -0
- package/src/agent-wrapper.js +5 -2
- package/src/cluster/client.ts +199 -0
- package/src/cluster/connection.ts +300 -0
- package/src/cluster/errors.ts +32 -0
- package/src/cluster/frames.ts +44 -0
- package/src/cluster/generated/protocol-schema.ts +2 -0
- package/src/cluster/generated/protocol.ts +183 -0
- package/src/cluster/index.ts +38 -0
- package/src/cluster/queue.ts +84 -0
- package/src/cluster/socket.ts +31 -0
- package/src/cluster/subscriptions.ts +256 -0
- package/src/cluster/validators.ts +56 -0
- package/src/cluster/ws.d.ts +7 -0
- package/src/isolation-manager.js +16 -0
- package/src/issue-providers/jira-provider.js +1 -1
- package/src/issue-providers/linear-provider.js +4 -4
- package/task-lib/runner.js +3 -0
package/lib/setup-apply.js
CHANGED
|
@@ -111,7 +111,7 @@ function convertDecisionValue({ decisionId, value, globalSettings, deps }) {
|
|
|
111
111
|
|
|
112
112
|
function defaultApplyDeps() {
|
|
113
113
|
const fs = require('fs');
|
|
114
|
-
const { loadSettings,
|
|
114
|
+
const { loadSettings, mutateSettings } = require('./settings');
|
|
115
115
|
const { readRepoSettings, writeRepoSettings } = require('./repo-settings');
|
|
116
116
|
const { checkGhAuth } = require('../src/preflight');
|
|
117
117
|
const { listProviders: listIssueProviders } = require('../src/issue-providers');
|
|
@@ -119,7 +119,7 @@ function defaultApplyDeps() {
|
|
|
119
119
|
return {
|
|
120
120
|
readFile: (filePath) => fs.readFileSync(filePath, 'utf8'),
|
|
121
121
|
loadSettings,
|
|
122
|
-
|
|
122
|
+
mutateSettings,
|
|
123
123
|
readRepoSettings,
|
|
124
124
|
writeRepoSettings,
|
|
125
125
|
checkGhAuth,
|
|
@@ -194,48 +194,62 @@ function applyWrite(write, { repoRoot, journal, deps }) {
|
|
|
194
194
|
return write.scope;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
function persistIfDirty({
|
|
198
|
-
globalDirty,
|
|
199
|
-
repoDirty,
|
|
200
|
-
globalSettings,
|
|
201
|
-
repoSettings,
|
|
202
|
-
repoRoot,
|
|
203
|
-
journal,
|
|
204
|
-
deps,
|
|
205
|
-
}) {
|
|
206
|
-
if (globalDirty) deps.saveSettings(globalSettings);
|
|
207
|
-
if (repoDirty) deps.writeRepoSettings(repoRoot, repoSettings);
|
|
208
|
-
if (globalDirty || repoDirty) saveJournal(journal);
|
|
209
|
-
}
|
|
210
197
|
|
|
211
|
-
// Phase 2:
|
|
212
|
-
//
|
|
213
|
-
// or targeted at a settings key no resolver consumes.
|
|
198
|
+
// Phase 2: resolve each global outcome against the locked, freshly read state.
|
|
199
|
+
// Repo-local settings remain intentionally outside the global settings lock.
|
|
214
200
|
function writeResolvedDecisions(
|
|
215
201
|
resolved,
|
|
216
|
-
{
|
|
202
|
+
{ repoSettings, repoRoot, journal, allowRiskyDefaults, deps }
|
|
217
203
|
) {
|
|
218
|
-
const
|
|
219
|
-
let globalDirty = false;
|
|
204
|
+
const resultsById = new Map();
|
|
220
205
|
let repoDirty = false;
|
|
206
|
+
let globalDirty = false;
|
|
207
|
+
const globalDecisions = resolved.filter((decision) => decision.target.scope === 'global');
|
|
208
|
+
|
|
209
|
+
if (globalDecisions.length > 0) {
|
|
210
|
+
const globalResults = deps.mutateSettings((freshGlobalSettings) => {
|
|
211
|
+
const results = [];
|
|
212
|
+
for (const decision of globalDecisions) {
|
|
213
|
+
const freshDecision = {
|
|
214
|
+
...decision,
|
|
215
|
+
writeValue: convertDecisionValue({
|
|
216
|
+
decisionId: decision.decisionId,
|
|
217
|
+
value: decision.inputValue,
|
|
218
|
+
globalSettings: freshGlobalSettings,
|
|
219
|
+
deps,
|
|
220
|
+
}),
|
|
221
|
+
};
|
|
222
|
+
const { result, write } = resolveDecisionOutcome(freshDecision, {
|
|
223
|
+
globalSettings: freshGlobalSettings,
|
|
224
|
+
repoSettings,
|
|
225
|
+
allowRiskyDefaults,
|
|
226
|
+
});
|
|
227
|
+
results.push(result);
|
|
228
|
+
if (write) {
|
|
229
|
+
applyWrite(write, { repoRoot, journal, deps });
|
|
230
|
+
globalDirty = true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return results;
|
|
234
|
+
});
|
|
235
|
+
for (const result of globalResults) resultsById.set(result.decisionId, result);
|
|
236
|
+
}
|
|
221
237
|
|
|
222
|
-
for (const decision of resolved) {
|
|
238
|
+
for (const decision of resolved.filter((item) => item.target.scope === 'repo')) {
|
|
223
239
|
const { result, write } = resolveDecisionOutcome(decision, {
|
|
224
|
-
globalSettings,
|
|
240
|
+
globalSettings: {},
|
|
225
241
|
repoSettings,
|
|
226
242
|
allowRiskyDefaults,
|
|
227
243
|
});
|
|
228
|
-
|
|
244
|
+
resultsById.set(result.decisionId, result);
|
|
229
245
|
if (!write) continue;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (scope === 'repo') repoDirty = true;
|
|
233
|
-
else globalDirty = true;
|
|
246
|
+
applyWrite(write, { repoRoot, journal, deps });
|
|
247
|
+
repoDirty = true;
|
|
234
248
|
}
|
|
235
249
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return
|
|
250
|
+
if (repoDirty) deps.writeRepoSettings(repoRoot, repoSettings);
|
|
251
|
+
if (globalDirty || repoDirty) saveJournal(journal);
|
|
252
|
+
return resolved.map((decision) => resultsById.get(decision.decisionId));
|
|
239
253
|
}
|
|
240
254
|
|
|
241
255
|
/**
|
|
@@ -268,7 +282,6 @@ function applyDecisions({ decisionsPath, cwd, allowRiskyDefaults = false, deps =
|
|
|
268
282
|
const resolved = resolveAndValidateDecisions(input, globalSettings, resolvedDeps);
|
|
269
283
|
|
|
270
284
|
const results = writeResolvedDecisions(resolved, {
|
|
271
|
-
globalSettings,
|
|
272
285
|
repoSettings,
|
|
273
286
|
repoRoot,
|
|
274
287
|
journal: loadJournal(),
|
package/lib/setup-undo.js
CHANGED
|
@@ -18,10 +18,10 @@ const {
|
|
|
18
18
|
} = require('./setup-journal');
|
|
19
19
|
|
|
20
20
|
function defaultUndoDeps() {
|
|
21
|
-
const {
|
|
21
|
+
const { mutateSettings } = require('./settings');
|
|
22
22
|
const { readRepoSettings, writeRepoSettings } = require('./repo-settings');
|
|
23
23
|
|
|
24
|
-
return {
|
|
24
|
+
return { mutateSettings, readRepoSettings, writeRepoSettings };
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -35,9 +35,7 @@ function undo({ deps = {} } = {}) {
|
|
|
35
35
|
const resolvedDeps = { ...defaultUndoDeps(), ...deps };
|
|
36
36
|
const journal = loadJournal();
|
|
37
37
|
|
|
38
|
-
const globalSettings = resolvedDeps.loadSettings();
|
|
39
38
|
const repoSettingsCache = new Map();
|
|
40
|
-
let globalDirty = false;
|
|
41
39
|
const dirtyRepoRoots = new Set();
|
|
42
40
|
|
|
43
41
|
function repoSettingsFor(repoRoot) {
|
|
@@ -48,41 +46,52 @@ function undo({ deps = {} } = {}) {
|
|
|
48
46
|
return repoSettingsCache.get(repoRoot);
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
const settingsObj = entry.scope === 'repo' ? repoSettingsFor(entry.repoRoot) : globalSettings;
|
|
49
|
+
function restoreEntry(entry, settingsObj) {
|
|
53
50
|
const current = getNestedValue(settingsObj, entry.path) ?? null;
|
|
54
|
-
|
|
55
51
|
if (deepEqual(current, entry.priorValue)) {
|
|
56
52
|
return { ...entry, status: 'already-restored' };
|
|
57
53
|
}
|
|
58
|
-
|
|
59
54
|
if (!deepEqual(current, entry.appliedValue)) {
|
|
60
55
|
return { ...entry, status: 'skipped-modified', current, wouldRestore: entry.priorValue };
|
|
61
56
|
}
|
|
62
|
-
|
|
63
57
|
if (entry.priorValue === null) {
|
|
64
58
|
deleteNestedKey(settingsObj, entry.path);
|
|
65
59
|
} else {
|
|
66
60
|
setNestedValue(settingsObj, entry.path, entry.priorValue);
|
|
67
61
|
}
|
|
62
|
+
return { ...entry, status: entry.priorValue === null ? 'deleted' : 'restored' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resultsByIndex = new Map();
|
|
66
|
+
const globalEntries = journal.entries
|
|
67
|
+
.map((entry, index) => ({ entry, index }))
|
|
68
|
+
.filter(({ entry }) => entry.scope === 'global');
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
if (globalEntries.length > 0) {
|
|
71
|
+
const globalResults = resolvedDeps.mutateSettings((freshGlobalSettings) =>
|
|
72
|
+
globalEntries.map(({ entry }) => restoreEntry(entry, freshGlobalSettings))
|
|
73
|
+
);
|
|
74
|
+
globalEntries.forEach(({ index }, resultIndex) => {
|
|
75
|
+
resultsByIndex.set(index, globalResults[resultIndex]);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
journal.entries.forEach((entry, index) => {
|
|
80
|
+
if (entry.scope !== 'repo') return;
|
|
81
|
+
const result = restoreEntry(entry, repoSettingsFor(entry.repoRoot));
|
|
82
|
+
resultsByIndex.set(index, result);
|
|
83
|
+
if (result.status === 'restored' || result.status === 'deleted') {
|
|
70
84
|
dirtyRepoRoots.add(entry.repoRoot);
|
|
71
|
-
} else {
|
|
72
|
-
globalDirty = true;
|
|
73
85
|
}
|
|
74
|
-
|
|
75
|
-
return { ...entry, status: entry.priorValue === null ? 'deleted' : 'restored' };
|
|
76
86
|
});
|
|
77
87
|
|
|
78
|
-
if (globalDirty) resolvedDeps.saveSettings(globalSettings);
|
|
79
88
|
for (const repoRoot of dirtyRepoRoots) {
|
|
80
89
|
resolvedDeps.writeRepoSettings(repoRoot, repoSettingsFor(repoRoot));
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
// Journal entries are left in place (not cleared) so a re-run reports
|
|
84
93
|
// 'already-restored' instead of finding nothing to undo.
|
|
85
|
-
return
|
|
94
|
+
return journal.entries.map((_, index) => resultsByIndex.get(index));
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
module.exports = { undo };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-open-engine/zeroshot",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.12.0",
|
|
4
4
|
"description": "Multi-agent orchestration engine for Claude, Codex, and Gemini",
|
|
5
5
|
"main": "src/orchestrator.js",
|
|
6
6
|
"bin": {
|
|
@@ -40,21 +40,27 @@
|
|
|
40
40
|
"test:coverage:report": "c8 --reporter=html npm run test:unit && echo 'Coverage report generated at coverage/index.html'",
|
|
41
41
|
"postinstall": "node scripts/fix-node-pty-permissions.js && node scripts/check-path.js",
|
|
42
42
|
"start": "node cli/index.js",
|
|
43
|
-
"typecheck": "tsc --noEmit",
|
|
43
|
+
"typecheck": "tsc --noEmit && npm run typecheck:cluster",
|
|
44
44
|
"typecheck:agent-cli-provider": "tsc --project tsconfig.agent-cli-provider.json",
|
|
45
|
+
"typecheck:cluster": "tsc --project tsconfig.cluster.json",
|
|
45
46
|
"lint:agent-cli-provider": "eslint \"src/agent-cli-provider/**/*.ts\" \"tests/agent-cli-provider/**/*.ts\"",
|
|
46
47
|
"build:agent-cli-provider": "tsc --project tsconfig.agent-cli-provider.build.json",
|
|
48
|
+
"build:cluster": "npm run protocol:generate && tsc --project tsconfig.cluster.cjs.json && tsc --project tsconfig.cluster.esm.json && node scripts/build-cluster.js",
|
|
47
49
|
"test:agent-cli-provider": "node --test tests/agent-cli-provider/*.test.js",
|
|
48
50
|
"test:providers:live": "npm run build:agent-cli-provider && node scripts/live-provider-smoke.js",
|
|
49
51
|
"check:agent-cli-provider": "npm run check:agent-cli-provider:ci",
|
|
50
52
|
"check:agent-cli-provider:ci": "npm run typecheck:agent-cli-provider && npm run lint:agent-cli-provider && npm run build:agent-cli-provider && npm run test:agent-cli-provider",
|
|
53
|
+
"test:cluster-client": "npm run build:cluster && node --test tests/cluster/client.test.js tests/cluster/parity.test.js tests/cluster/architecture.test.js tests/cluster/verifier-regressions.test.js",
|
|
54
|
+
"test:cluster-package": "npm run build:cluster && node --test tests/cluster/package.test.js",
|
|
51
55
|
"dev:link": "npm link",
|
|
52
56
|
"lint": "eslint .",
|
|
53
57
|
"lint:fix": "eslint . --fix",
|
|
54
58
|
"validate:templates": "node scripts/validate-templates.js",
|
|
55
59
|
"format": "prettier --write .",
|
|
56
60
|
"format:check": "prettier --check .",
|
|
57
|
-
"protocol:
|
|
61
|
+
"protocol:generate": "node scripts/generate-cluster-types.js",
|
|
62
|
+
"protocol:check": "cargo run -p openengine-cluster-testkit --bin generate-cluster-protocol -- --check && node scripts/generate-cluster-types.js --check",
|
|
63
|
+
"rust:distribution:check": "node scripts/rust-distribution.js check-repository",
|
|
58
64
|
"rust:check": "cargo fmt --all -- --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace",
|
|
59
65
|
"deadcode": "ts-prune --skip node_modules",
|
|
60
66
|
"deadcode:files": "unimported",
|
|
@@ -67,6 +73,7 @@
|
|
|
67
73
|
"release:preflight": "node scripts/release-preflight.js",
|
|
68
74
|
"release:recover": "node scripts/release-recovery.js",
|
|
69
75
|
"release:assert-published": "node scripts/assert-release-published.js",
|
|
76
|
+
"prepack": "npm run build:cluster",
|
|
70
77
|
"prepublishOnly": "npm run lint && npm run typecheck && npm run check:agent-cli-provider:ci",
|
|
71
78
|
"prepare": "npm run build:agent-cli-provider && husky"
|
|
72
79
|
},
|
|
@@ -114,6 +121,17 @@
|
|
|
114
121
|
"access": "public",
|
|
115
122
|
"registry": "https://registry.npmjs.org/"
|
|
116
123
|
},
|
|
124
|
+
"exports": {
|
|
125
|
+
".": "./src/orchestrator.js",
|
|
126
|
+
"./cluster": {
|
|
127
|
+
"types": "./lib/cluster/index.d.ts",
|
|
128
|
+
"import": "./lib/cluster/index.mjs",
|
|
129
|
+
"require": "./lib/cluster/index.cjs",
|
|
130
|
+
"default": "./lib/cluster/index.cjs"
|
|
131
|
+
},
|
|
132
|
+
"./package.json": "./package.json",
|
|
133
|
+
"./*": "./*"
|
|
134
|
+
},
|
|
117
135
|
"files": [
|
|
118
136
|
"src/",
|
|
119
137
|
"lib/",
|
|
@@ -141,6 +159,9 @@
|
|
|
141
159
|
"pidusage": "^4.0.1",
|
|
142
160
|
"proper-lockfile": "^4.1.2"
|
|
143
161
|
},
|
|
162
|
+
"optionalDependencies": {
|
|
163
|
+
"ws": "^8.18.3"
|
|
164
|
+
},
|
|
144
165
|
"devDependencies": {
|
|
145
166
|
"@commitlint/cli": "^19.8.1",
|
|
146
167
|
"@commitlint/config-conventional": "^19.8.1",
|
|
@@ -156,6 +177,7 @@
|
|
|
156
177
|
"eslint-plugin-sonarjs": "^3.0.5",
|
|
157
178
|
"eslint-plugin-unused-imports": "^4.3.0",
|
|
158
179
|
"husky": "^9.1.7",
|
|
180
|
+
"js-yaml": "^4.2.0",
|
|
159
181
|
"jscpd": "^3.5.10",
|
|
160
182
|
"lint-staged": "^16.2.7",
|
|
161
183
|
"mocha": "^11.7.5",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const root = path.resolve(__dirname, '..');
|
|
7
|
+
const buildRoot = path.join(root, '.cluster-build');
|
|
8
|
+
const outputRoot = path.join(root, 'lib/cluster');
|
|
9
|
+
|
|
10
|
+
function filesBelow(directory) {
|
|
11
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
12
|
+
return entries.flatMap((entry) => {
|
|
13
|
+
const absolute = path.join(directory, entry.name);
|
|
14
|
+
return entry.isDirectory() ? filesBelow(absolute) : [absolute];
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function copyBuild(sourceRoot, mode) {
|
|
19
|
+
for (const source of filesBelow(sourceRoot)) {
|
|
20
|
+
const relative = path.relative(sourceRoot, source);
|
|
21
|
+
const extension = path.extname(relative);
|
|
22
|
+
if (extension === '.js') {
|
|
23
|
+
const targetExtension = mode === 'cjs' ? '.cjs' : '.mjs';
|
|
24
|
+
const target = path.join(outputRoot, relative.slice(0, -3) + targetExtension);
|
|
25
|
+
let content = fs.readFileSync(source, 'utf8');
|
|
26
|
+
content = content.replace(
|
|
27
|
+
/(["'])(\.\.?\/[^"']+)\.js\1/g,
|
|
28
|
+
(_match, quote, specifier) => `${quote}${specifier}${targetExtension}${quote}`
|
|
29
|
+
);
|
|
30
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
31
|
+
fs.writeFileSync(target, content);
|
|
32
|
+
} else if (mode === 'cjs' && extension === '.ts' && relative.endsWith('.d.ts')) {
|
|
33
|
+
const target = path.join(outputRoot, relative);
|
|
34
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
35
|
+
fs.copyFileSync(source, target);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fs.rmSync(outputRoot, { recursive: true, force: true });
|
|
41
|
+
copyBuild(path.join(buildRoot, 'cjs'), 'cjs');
|
|
42
|
+
copyBuild(path.join(buildRoot, 'esm'), 'esm');
|
|
43
|
+
fs.rmSync(buildRoot, { recursive: true, force: true });
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
7
|
+
const OPENRPC_PATH = path.join(ROOT, 'protocol/openengine-cluster/v1/openrpc.json');
|
|
8
|
+
const SCHEMA_PATH = path.join(ROOT, 'protocol/openengine-cluster/v1/schema.json');
|
|
9
|
+
const PROTOCOL_RS = path.join(ROOT, 'crates/openengine-cluster-protocol/src/lib.rs');
|
|
10
|
+
const WATCH_RS = path.join(ROOT, 'crates/openengine-cluster-protocol/src/watch.rs');
|
|
11
|
+
const CLIENT_RS = path.join(ROOT, 'crates/openengine-cluster-client/src/lib.rs');
|
|
12
|
+
const OUTPUT_PATH = path.join(ROOT, 'src/cluster/generated/protocol.ts');
|
|
13
|
+
const SCHEMA_OUTPUT_PATH = path.join(ROOT, 'src/cluster/generated/protocol-schema.ts');
|
|
14
|
+
|
|
15
|
+
function readJson(file) {
|
|
16
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function rustString(source, name) {
|
|
20
|
+
const match = source.match(new RegExp(`pub const ${name}: &str = "([^"]+)";`));
|
|
21
|
+
if (!match) throw new Error(`missing Rust string constant ${name}`);
|
|
22
|
+
return match[1];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function rustNumber(source, name) {
|
|
26
|
+
const match = source.match(new RegExp(`(?:pub )?const ${name}: [^=]+ = (-?[0-9_]+);`));
|
|
27
|
+
if (!match) throw new Error(`missing Rust numeric constant ${name}`);
|
|
28
|
+
return Number(match[1].replaceAll('_', ''));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function refName(ref) {
|
|
32
|
+
const prefix = '#/$defs/';
|
|
33
|
+
if (!ref.startsWith(prefix)) throw new Error(`unsupported non-local schema reference ${ref}`);
|
|
34
|
+
return ref.slice(prefix.length);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function literal(value) {
|
|
38
|
+
return JSON.stringify(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function renderCombination(schema) {
|
|
42
|
+
for (const [keyword, operator] of [
|
|
43
|
+
['oneOf', ' | '],
|
|
44
|
+
['anyOf', ' | '],
|
|
45
|
+
['allOf', ' & '],
|
|
46
|
+
]) {
|
|
47
|
+
const branches = schema[keyword];
|
|
48
|
+
if (!Array.isArray(branches)) continue;
|
|
49
|
+
const combination = branches.map(schemaType).map(parenthesize).join(operator);
|
|
50
|
+
const hasBase =
|
|
51
|
+
schema.type !== undefined ||
|
|
52
|
+
schema.properties !== undefined ||
|
|
53
|
+
schema.additionalProperties !== undefined;
|
|
54
|
+
if (!hasBase) return combination;
|
|
55
|
+
const base = { ...schema };
|
|
56
|
+
delete base[keyword];
|
|
57
|
+
return `(${schemaType(base)}) & (${combination})`;
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function renderObject(schema) {
|
|
63
|
+
if (schema.properties) {
|
|
64
|
+
const required = new Set(schema.required ?? []);
|
|
65
|
+
const fields = Object.entries(schema.properties).map(
|
|
66
|
+
([name, value]) =>
|
|
67
|
+
`readonly ${JSON.stringify(name)}${required.has(name) ? '' : '?'}: ${schemaType(value)};`
|
|
68
|
+
);
|
|
69
|
+
if (schema.additionalProperties && schema.additionalProperties !== false) {
|
|
70
|
+
fields.push(`readonly [key: string]: ${schemaType(schema.additionalProperties)};`);
|
|
71
|
+
}
|
|
72
|
+
return `{ ${fields.join(' ')} }`;
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(schema.required) && schema.required.length > 0) {
|
|
75
|
+
const fields = schema.required.map((name) => `readonly ${JSON.stringify(name)}: unknown;`);
|
|
76
|
+
return `{ ${fields.join(' ')} }`;
|
|
77
|
+
}
|
|
78
|
+
if (schema.additionalProperties && schema.additionalProperties !== false) {
|
|
79
|
+
return `{ readonly [key: string]: ${schemaType(schema.additionalProperties)} }`;
|
|
80
|
+
}
|
|
81
|
+
return schema.type === 'object' ? 'Record<string, never>' : 'unknown';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function schemaType(schema) {
|
|
85
|
+
if (schema === true) return 'unknown';
|
|
86
|
+
if (schema === false) return 'never';
|
|
87
|
+
if (!schema || typeof schema !== 'object') throw new Error('invalid JSON schema');
|
|
88
|
+
if ('$ref' in schema) return refName(schema.$ref);
|
|
89
|
+
if ('const' in schema) return literal(schema.const);
|
|
90
|
+
if (Array.isArray(schema.enum)) return schema.enum.map(literal).join(' | ') || 'never';
|
|
91
|
+
const combination = renderCombination(schema);
|
|
92
|
+
if (combination !== undefined) return combination;
|
|
93
|
+
if (Array.isArray(schema.type)) {
|
|
94
|
+
return schema.type.map((type) => schemaType({ ...schema, type })).join(' | ');
|
|
95
|
+
}
|
|
96
|
+
switch (schema.type) {
|
|
97
|
+
case 'null':
|
|
98
|
+
return 'null';
|
|
99
|
+
case 'boolean':
|
|
100
|
+
return 'boolean';
|
|
101
|
+
case 'integer':
|
|
102
|
+
case 'number':
|
|
103
|
+
return 'number';
|
|
104
|
+
case 'string':
|
|
105
|
+
return 'string';
|
|
106
|
+
case 'array':
|
|
107
|
+
return `ReadonlyArray<${schemaType(schema.items ?? true)}>`;
|
|
108
|
+
case 'object':
|
|
109
|
+
case undefined:
|
|
110
|
+
return renderObject(schema);
|
|
111
|
+
default:
|
|
112
|
+
throw new Error(`unsupported JSON schema type ${String(schema.type)}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function parenthesize(value) {
|
|
117
|
+
return value.includes(' | ') || value.includes(' & ') ? `(${value})` : value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function generate() {
|
|
121
|
+
const openrpc = readJson(OPENRPC_PATH);
|
|
122
|
+
const schema = readJson(SCHEMA_PATH);
|
|
123
|
+
const protocolRust = fs.readFileSync(PROTOCOL_RS, 'utf8');
|
|
124
|
+
const watchRust = fs.readFileSync(WATCH_RS, 'utf8');
|
|
125
|
+
const clientRust = fs.readFileSync(CLIENT_RS, 'utf8');
|
|
126
|
+
const methods = openrpc.methods.map((method) => method.name);
|
|
127
|
+
const subscriptionMethods = new Set(['watch', 'logs', 'agent/attach']);
|
|
128
|
+
const unaryMethods = methods.filter((method) => !subscriptionMethods.has(method));
|
|
129
|
+
const resultDefinitions = Object.fromEntries(
|
|
130
|
+
openrpc.methods.map((method) => [method.name, method.result.schema.$ref.split('/').at(-1)])
|
|
131
|
+
);
|
|
132
|
+
const aliases = Object.entries(schema.$defs)
|
|
133
|
+
.map(([name, definition]) => `export type ${name} = ${schemaType(definition)};`)
|
|
134
|
+
.join('\n');
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
`// Generated by scripts/generate-cluster-types.js from the checked-in Cluster Protocol v1 artifacts.\n// Do not edit this file by hand. Run npm run protocol:generate.\n\n` +
|
|
138
|
+
`export const JSON_RPC_VERSION = ${literal(rustString(protocolRust, 'JSON_RPC_VERSION'))} as const;\n` +
|
|
139
|
+
`export const PROTOCOL_VERSION = ${literal(rustString(protocolRust, 'PROTOCOL_VERSION'))} as const;\n` +
|
|
140
|
+
`export const SUBSCRIPTION_QUEUE_CAPACITY = ${rustNumber(watchRust, 'DEFAULT_SUBSCRIPTION_QUEUE_CAPACITY')} as const;\n` +
|
|
141
|
+
`export const MAX_FRAME_BYTES = ${rustNumber(clientRust, 'MAX_FRAME_BYTES')} as const;\n` +
|
|
142
|
+
`export const CLUSTER_METHODS = ${JSON.stringify(methods)} as const;\n` +
|
|
143
|
+
`export type ClusterMethod = (typeof CLUSTER_METHODS)[number];\n\n` +
|
|
144
|
+
`export const UNARY_METHODS = ${JSON.stringify(unaryMethods)} as const;\n` +
|
|
145
|
+
`export type UnaryClusterMethod = (typeof UNARY_METHODS)[number];\n` +
|
|
146
|
+
`export const SUBSCRIPTION_METHODS = ${JSON.stringify([...subscriptionMethods])} as const;\n` +
|
|
147
|
+
`export type SubscriptionMethod = (typeof SUBSCRIPTION_METHODS)[number];\n` +
|
|
148
|
+
`export const METHOD_RESULT_DEFINITIONS = ${JSON.stringify(resultDefinitions)} as const;\n\n` +
|
|
149
|
+
`export const JSON_RPC_ERROR_CODES = {\n` +
|
|
150
|
+
` PARSE_ERROR: ${rustNumber(protocolRust, 'PARSE_ERROR')},\n` +
|
|
151
|
+
` INVALID_REQUEST: ${rustNumber(protocolRust, 'INVALID_REQUEST')},\n` +
|
|
152
|
+
` METHOD_NOT_FOUND: ${rustNumber(protocolRust, 'METHOD_NOT_FOUND')},\n` +
|
|
153
|
+
` INVALID_PARAMS: ${rustNumber(protocolRust, 'INVALID_PARAMS')},\n` +
|
|
154
|
+
` INTERNAL_ERROR: ${rustNumber(protocolRust, 'INTERNAL_ERROR')},\n` +
|
|
155
|
+
` APPLICATION_ERROR: ${rustNumber(protocolRust, 'APPLICATION_ERROR')},\n` +
|
|
156
|
+
`} as const;\n\n` +
|
|
157
|
+
`export const DOMAIN_ERROR_CODES = ${JSON.stringify([
|
|
158
|
+
rustString(watchRust, 'NOT_FOUND'),
|
|
159
|
+
rustString(watchRust, 'GONE'),
|
|
160
|
+
rustString(watchRust, 'SLOW_CONSUMER'),
|
|
161
|
+
rustString(protocolRust, 'UNSUPPORTED_PROTOCOL_VERSION'),
|
|
162
|
+
rustString(protocolRust, 'INTERNAL_ERROR_CODE'),
|
|
163
|
+
])} as const;\n\n` +
|
|
164
|
+
aliases +
|
|
165
|
+
'\n\n' +
|
|
166
|
+
`export interface ClusterMethodParams {\n` +
|
|
167
|
+
` readonly initialize: InitializeParams; readonly plan: PlanParams; readonly apply: ApplyParams;\n` +
|
|
168
|
+
` readonly update: UpdateParams; readonly stop: StopParams; readonly retry: RetryParams;\n` +
|
|
169
|
+
` readonly resubmit: ResubmitParams; readonly delete: DeleteParams; readonly get: GetParams;\n` +
|
|
170
|
+
` readonly watch: WatchParams; readonly logs: LogsParams; readonly 'agent/attach': AgentAttachParams;\n` +
|
|
171
|
+
`}\n\n` +
|
|
172
|
+
`export interface ClusterMethodResults {\n` +
|
|
173
|
+
` readonly initialize: InitializeResult; readonly plan: PlanResult; readonly apply: ApplyResult;\n` +
|
|
174
|
+
` readonly update: UpdateResult; readonly stop: StopResult; readonly retry: RetryResult;\n` +
|
|
175
|
+
` readonly resubmit: ResubmitResult; readonly delete: DeleteResult; readonly get: GetResult;\n` +
|
|
176
|
+
` readonly watch: WatchResult; readonly logs: LogsResult; readonly 'agent/attach': AgentAttachResult;\n` +
|
|
177
|
+
`}\n`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const expected = generate();
|
|
182
|
+
const schemaExpected = `// Generated from protocol/openengine-cluster/v1/schema.json. Do not edit.\nexport const CLUSTER_PROTOCOL_SCHEMA: unknown = ${JSON.stringify(readJson(SCHEMA_PATH))};\n`;
|
|
183
|
+
const check = process.argv.includes('--check');
|
|
184
|
+
const outputs = [
|
|
185
|
+
[OUTPUT_PATH, expected],
|
|
186
|
+
[SCHEMA_OUTPUT_PATH, schemaExpected],
|
|
187
|
+
];
|
|
188
|
+
if (check) {
|
|
189
|
+
for (const [outputPath, outputExpected] of outputs) {
|
|
190
|
+
const actual = fs.existsSync(outputPath) ? fs.readFileSync(outputPath, 'utf8') : '';
|
|
191
|
+
if (actual !== outputExpected) {
|
|
192
|
+
console.error(
|
|
193
|
+
`Generated cluster protocol output is out of date: ${path.relative(ROOT, outputPath)}`
|
|
194
|
+
);
|
|
195
|
+
process.exitCode = 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
for (const [outputPath, outputExpected] of outputs) {
|
|
200
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
201
|
+
fs.writeFileSync(outputPath, outputExpected);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -5,6 +5,7 @@ const { execFileSync } = require('child_process');
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const { pathToFileURL } = require('url');
|
|
8
9
|
|
|
9
10
|
const projectRoot = path.resolve(__dirname, '..');
|
|
10
11
|
|
|
@@ -55,7 +56,7 @@ async function main() {
|
|
|
55
56
|
{
|
|
56
57
|
...releaseConfig,
|
|
57
58
|
branches: [branch],
|
|
58
|
-
repositoryUrl: mirrorPath,
|
|
59
|
+
repositoryUrl: pathToFileURL(mirrorPath).href,
|
|
59
60
|
plugins: validationPlugins(releaseConfig),
|
|
60
61
|
dryRun: true,
|
|
61
62
|
ci: false,
|
|
@@ -66,12 +67,11 @@ async function main() {
|
|
|
66
67
|
}
|
|
67
68
|
);
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const version = result?.nextRelease.version || '';
|
|
71
|
+
if (process.env.GITHUB_OUTPUT) {
|
|
72
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\n`);
|
|
72
73
|
}
|
|
73
|
-
|
|
74
|
-
console.log(`RELEASE_DRY_RUN_RESULT=${result.nextRelease.version}`);
|
|
74
|
+
console.log(`RELEASE_DRY_RUN_RESULT=${version || 'no-release'}`);
|
|
75
75
|
} finally {
|
|
76
76
|
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
77
77
|
}
|