@the-open-engine/zeroshot 6.7.1 → 6.8.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/index.js +114 -16
- package/lib/agent-cli-provider/adapters/common.js +1 -1
- package/lib/agent-cli-provider/adapters/common.js.map +1 -1
- package/lib/agent-cli-provider/adapters/opencode-models.d.ts +7 -0
- package/lib/agent-cli-provider/adapters/opencode-models.d.ts.map +1 -0
- package/lib/agent-cli-provider/adapters/opencode-models.js +87 -0
- package/lib/agent-cli-provider/adapters/opencode-models.js.map +1 -0
- package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/opencode.js +6 -58
- package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +27 -13
- package/lib/agent-cli-provider/single-agent-runtime.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/git-remote-utils.js +90 -7
- package/package.json +6 -15
- package/protocol/openengine-cluster/v1/worker.schema.json +47 -0
- package/scripts/assert-release-published.js +161 -2
- package/scripts/release-dry-run.js +83 -0
- package/scripts/release-preflight.js +24 -12
- package/scripts/release-recovery.js +149 -0
- package/scripts/semantic-release-notes.js +44 -0
- package/scripts/setup-merge-queue.sh +104 -118
- package/src/agent/agent-task-executor.js +18 -9
- package/src/agent-cli-provider/adapters/common.ts +1 -1
- package/src/agent-cli-provider/adapters/opencode-models.ts +100 -0
- package/src/agent-cli-provider/adapters/opencode.ts +8 -65
- package/src/agent-cli-provider/single-agent-runtime.ts +51 -33
- package/src/agent-cli-provider/types.ts +1 -0
- package/src/agent-wrapper.js +14 -2
- package/src/agents/git-pusher-template.js +94 -19
- package/src/attach/socket-discovery.js +9 -19
- package/src/attach/socket-paths.js +85 -0
- package/src/claude-task-runner.js +121 -37
- package/src/config-validator.js +2 -2
- package/src/isolation-manager.js +164 -74
- package/src/orchestrator.js +83 -34
- package/src/providers/index.js +5 -0
- package/src/task-run-model-args.js +155 -0
- package/task-lib/attachable-watcher.js +3 -9
- package/task-lib/commands/run.js +17 -2
- package/task-lib/runner.js +87 -29
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
function run(command, args, options = {}) {
|
|
8
|
+
return execFileSync(command, args, {
|
|
9
|
+
encoding: 'utf8',
|
|
10
|
+
stdio: options.stdio || ['ignore', 'pipe', 'pipe'],
|
|
11
|
+
...options,
|
|
12
|
+
}).trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseReleaseTag(tag) {
|
|
16
|
+
const match = String(tag || '').match(/^v(\d+\.\d+\.\d+)$/);
|
|
17
|
+
if (!match) throw new Error('release_tag must match vX.Y.Z');
|
|
18
|
+
return match[1];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function validateCommit(commit) {
|
|
22
|
+
if (!/^[0-9a-f]{40}$/.test(String(commit || ''))) {
|
|
23
|
+
throw new Error('release_commit must be a full lowercase commit SHA');
|
|
24
|
+
}
|
|
25
|
+
return commit;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function verifyImmutableSource(tag, commit) {
|
|
29
|
+
run('git', ['show-ref', '--verify', `refs/tags/${tag}`]);
|
|
30
|
+
const head = run('git', ['rev-parse', 'HEAD']);
|
|
31
|
+
const tagCommit = run('git', ['rev-list', '-n', '1', `refs/tags/${tag}`]);
|
|
32
|
+
if (head !== commit || tagCommit !== commit) {
|
|
33
|
+
throw new Error('checkout, protected tag, and release_commit must reference the same commit');
|
|
34
|
+
}
|
|
35
|
+
run('git', ['merge-base', '--is-ancestor', commit, 'origin/main']);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function npmPackageMetadata(name, version) {
|
|
39
|
+
const result = spawnSync(
|
|
40
|
+
'npm',
|
|
41
|
+
['view', `${name}@${version}`, 'version', 'gitHead', 'dist.attestations', '--json'],
|
|
42
|
+
{ encoding: 'utf8' }
|
|
43
|
+
);
|
|
44
|
+
if (result.status === 0) return JSON.parse(result.stdout);
|
|
45
|
+
if (/\bE404\b|is not in this registry|No match found/i.test(result.stderr || '')) return null;
|
|
46
|
+
throw new Error(`npm registry lookup failed: ${(result.stderr || result.stdout).trim()}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function verifyExistingNpmVersion(metadata, version, commit) {
|
|
50
|
+
if (metadata.version !== version) {
|
|
51
|
+
throw new Error(`npm returned ${metadata.version}; expected ${version}`);
|
|
52
|
+
}
|
|
53
|
+
if (metadata.gitHead !== commit) {
|
|
54
|
+
throw new Error(`npm gitHead ${metadata.gitHead || '(missing)'} does not match ${commit}`);
|
|
55
|
+
}
|
|
56
|
+
if (!metadata['dist.attestations']?.provenance) {
|
|
57
|
+
throw new Error('npm package exists without a provenance attestation');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function recoverNpm(name, version, commit) {
|
|
62
|
+
const existing = npmPackageMetadata(name, version);
|
|
63
|
+
if (existing) {
|
|
64
|
+
verifyExistingNpmVersion(existing, version, commit);
|
|
65
|
+
console.log(`${name}@${version} already exists with matching provenance; nothing to recover`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
run('npm', ['version', version, '--no-git-tag-version', '--allow-same-version']);
|
|
70
|
+
run('npm', ['publish', '--access', 'public', '--provenance'], { stdio: 'inherit' });
|
|
71
|
+
|
|
72
|
+
const published = npmPackageMetadata(name, version);
|
|
73
|
+
if (!published) throw new Error(`${name}@${version} was not visible after publication`);
|
|
74
|
+
verifyExistingNpmVersion(published, version, commit);
|
|
75
|
+
console.log(`Recovered ${name}@${version}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function releaseExists(tag) {
|
|
79
|
+
const result = spawnSync('gh', ['release', 'view', tag], { encoding: 'utf8' });
|
|
80
|
+
if (result.status === 0) return true;
|
|
81
|
+
if (/release not found|HTTP 404/i.test(`${result.stdout}\n${result.stderr}`)) return false;
|
|
82
|
+
throw new Error(`GitHub Release lookup failed: ${(result.stderr || result.stdout).trim()}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function recoverGithubRelease(tag, commit, version) {
|
|
86
|
+
if (releaseExists(tag)) {
|
|
87
|
+
console.log(`GitHub Release ${tag} already exists; nothing to recover`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const notesPath = path.join(process.cwd(), 'docs', 'releases', `${tag}.md`);
|
|
92
|
+
const args = ['release', 'create', tag, '--verify-tag', '--target', commit, '--title', tag];
|
|
93
|
+
if (fs.existsSync(notesPath)) {
|
|
94
|
+
args.push('--notes-file', notesPath);
|
|
95
|
+
} else {
|
|
96
|
+
args.push('--generate-notes');
|
|
97
|
+
const tags = run('git', [
|
|
98
|
+
'tag',
|
|
99
|
+
'--list',
|
|
100
|
+
'v[0-9]*',
|
|
101
|
+
'--merged',
|
|
102
|
+
`${commit}^`,
|
|
103
|
+
'--sort=-version:refname',
|
|
104
|
+
])
|
|
105
|
+
.split(/\r?\n/)
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
if (tags[0]) args.push('--notes-start-tag', tags[0]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
run('gh', args, { stdio: 'inherit' });
|
|
111
|
+
if (!releaseExists(tag)) throw new Error(`GitHub Release ${tag} was not created`);
|
|
112
|
+
console.log(`Recovered GitHub Release ${tag} for ${version}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function main() {
|
|
116
|
+
const action = process.env.RECOVERY_ACTION;
|
|
117
|
+
if (!['recover-npm', 'recover-github-release'].includes(action)) {
|
|
118
|
+
throw new Error('RECOVERY_ACTION must be recover-npm or recover-github-release');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const tag = process.env.RELEASE_TAG;
|
|
122
|
+
const commit = validateCommit(process.env.RELEASE_COMMIT);
|
|
123
|
+
const version = parseReleaseTag(tag);
|
|
124
|
+
verifyImmutableSource(tag, commit);
|
|
125
|
+
|
|
126
|
+
const packageJson = require('../package.json');
|
|
127
|
+
if (action === 'recover-npm') {
|
|
128
|
+
recoverNpm(packageJson.name, version, commit);
|
|
129
|
+
} else {
|
|
130
|
+
recoverGithubRelease(tag, commit, version);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (require.main === module) {
|
|
135
|
+
try {
|
|
136
|
+
main();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(`Release recovery failed: ${error.message}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
npmPackageMetadata,
|
|
145
|
+
parseReleaseTag,
|
|
146
|
+
releaseExists,
|
|
147
|
+
validateCommit,
|
|
148
|
+
verifyExistingNpmVersion,
|
|
149
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function curatedNotesPath(cwd, version) {
|
|
5
|
+
return path.join(cwd, 'docs', 'releases', `v${version}.md`);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function readCuratedNotes(cwd, version) {
|
|
9
|
+
const notesPath = curatedNotesPath(cwd, version);
|
|
10
|
+
if (!fs.existsSync(notesPath)) return null;
|
|
11
|
+
|
|
12
|
+
const notes = fs.readFileSync(notesPath, 'utf8').trim();
|
|
13
|
+
if (!notes) {
|
|
14
|
+
throw new Error(`Curated release notes are empty: ${notesPath}`);
|
|
15
|
+
}
|
|
16
|
+
return `${notes}\n`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function conventionalNotes(pluginConfig, context) {
|
|
20
|
+
const generator = await import('@semantic-release/release-notes-generator');
|
|
21
|
+
return generator.generateNotes(pluginConfig.conventional || {}, context);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function generateNotesWithFallback(pluginConfig, context, fallback = conventionalNotes) {
|
|
25
|
+
const version = context.nextRelease?.version;
|
|
26
|
+
if (!version) throw new Error('nextRelease.version is required to generate release notes');
|
|
27
|
+
|
|
28
|
+
const curated = readCuratedNotes(context.cwd || process.cwd(), version);
|
|
29
|
+
if (curated) return curated;
|
|
30
|
+
const generated = await fallback(pluginConfig, context);
|
|
31
|
+
return generated;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function generateNotes(pluginConfig, context) {
|
|
35
|
+
const notes = await generateNotesWithFallback(pluginConfig, context);
|
|
36
|
+
return notes;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
curatedNotesPath,
|
|
41
|
+
generateNotes,
|
|
42
|
+
generateNotesWithFallback,
|
|
43
|
+
readCuratedNotes,
|
|
44
|
+
};
|
|
@@ -1,75 +1,79 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Setup GitHub merge queue and branch protection for zeroshot
|
|
3
|
-
# Run once after creating the repo or to update settings
|
|
4
2
|
|
|
5
|
-
set -
|
|
3
|
+
set -euo pipefail
|
|
6
4
|
|
|
7
5
|
REPO="the-open-engine/zeroshot"
|
|
6
|
+
MAIN_RULESET_NAME="Protect main trunk"
|
|
7
|
+
TAG_RULESET_NAME="Make release tags immutable"
|
|
8
8
|
|
|
9
|
-
echo "╔═══════════════════════════════════════════════════════════════════╗"
|
|
10
|
-
echo "║ Setting up merge queue for $REPO ║"
|
|
11
|
-
echo "╚═══════════════════════════════════════════════════════════════════╝"
|
|
12
|
-
echo ""
|
|
13
|
-
|
|
14
|
-
# Check gh is authenticated
|
|
15
9
|
if ! gh auth status &>/dev/null; then
|
|
16
|
-
echo "
|
|
17
|
-
echo " Run: gh auth login"
|
|
10
|
+
echo "ERROR: Authenticate GitHub CLI with: gh auth login"
|
|
18
11
|
exit 1
|
|
19
12
|
fi
|
|
20
13
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
echo "❌ ERROR: You need admin access to $REPO"
|
|
14
|
+
if [[ "$(gh api "repos/$REPO" --jq '.permissions.admin')" != "true" ]]; then
|
|
15
|
+
echo "ERROR: Repository admin access is required"
|
|
24
16
|
exit 1
|
|
25
17
|
fi
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
--input - <<EOF
|
|
38
|
-
{
|
|
39
|
-
"required_status_checks": {
|
|
40
|
-
"strict": true,
|
|
41
|
-
"contexts": ["check", "install-matrix (ubuntu-latest, 20)", "install-matrix (macos-latest, 20)"]
|
|
42
|
-
},
|
|
43
|
-
"enforce_admins": false,
|
|
44
|
-
"required_pull_request_reviews": null,
|
|
45
|
-
"restrictions": null,
|
|
46
|
-
"allow_force_pushes": false,
|
|
47
|
-
"allow_deletions": false,
|
|
48
|
-
"required_linear_history": true,
|
|
49
|
-
"required_conversation_resolution": false
|
|
19
|
+
upsert_ruleset() {
|
|
20
|
+
local name="$1"
|
|
21
|
+
local payload="$2"
|
|
22
|
+
local id
|
|
23
|
+
id="$(gh api "repos/$REPO/rulesets" --jq ".[] | select(.name == \"$name\") | .id" | head -1)"
|
|
24
|
+
if [[ -n "$id" ]]; then
|
|
25
|
+
gh api --method PUT "repos/$REPO/rulesets/$id" --input "$payload" >/dev/null
|
|
26
|
+
else
|
|
27
|
+
gh api --method POST "repos/$REPO/rulesets" --input "$payload" >/dev/null
|
|
28
|
+
fi
|
|
50
29
|
}
|
|
51
|
-
EOF
|
|
52
|
-
|
|
53
|
-
echo "✓ 'dev' branch protection configured"
|
|
54
30
|
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
main_payload="$(mktemp)"
|
|
32
|
+
tag_payload="$(mktemp)"
|
|
33
|
+
trap 'rm -f "$main_payload" "$tag_payload"' EXIT
|
|
57
34
|
|
|
58
|
-
|
|
59
|
-
# Using the ruleset API which supports merge queue
|
|
60
|
-
gh api --method POST "repos/$REPO/rulesets" \
|
|
61
|
-
--input - <<EOF 2>/dev/null || echo " (ruleset may already exist)"
|
|
35
|
+
cat > "$main_payload" <<'JSON'
|
|
62
36
|
{
|
|
63
|
-
"name": "
|
|
37
|
+
"name": "Protect main trunk",
|
|
64
38
|
"target": "branch",
|
|
65
39
|
"enforcement": "active",
|
|
66
40
|
"conditions": {
|
|
67
41
|
"ref_name": {
|
|
68
|
-
"include": ["refs/heads/
|
|
42
|
+
"include": ["refs/heads/main"],
|
|
69
43
|
"exclude": []
|
|
70
44
|
}
|
|
71
45
|
},
|
|
72
46
|
"rules": [
|
|
47
|
+
{"type": "deletion"},
|
|
48
|
+
{"type": "non_fast_forward"},
|
|
49
|
+
{"type": "required_linear_history"},
|
|
50
|
+
{
|
|
51
|
+
"type": "pull_request",
|
|
52
|
+
"parameters": {
|
|
53
|
+
"required_approving_review_count": 0,
|
|
54
|
+
"dismiss_stale_reviews_on_push": true,
|
|
55
|
+
"required_reviewers": [],
|
|
56
|
+
"require_code_owner_review": false,
|
|
57
|
+
"dismissal_restriction": {
|
|
58
|
+
"enabled": false,
|
|
59
|
+
"allowed_actors": []
|
|
60
|
+
},
|
|
61
|
+
"require_last_push_approval": false,
|
|
62
|
+
"required_review_thread_resolution": true,
|
|
63
|
+
"allowed_merge_methods": ["squash"]
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"type": "required_status_checks",
|
|
68
|
+
"parameters": {
|
|
69
|
+
"strict_required_status_checks_policy": true,
|
|
70
|
+
"do_not_enforce_on_create": false,
|
|
71
|
+
"required_status_checks": [
|
|
72
|
+
{"context": "required", "integration_id": 15368},
|
|
73
|
+
{"context": "semantic", "integration_id": 15368}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
73
77
|
{
|
|
74
78
|
"type": "merge_queue",
|
|
75
79
|
"parameters": {
|
|
@@ -82,49 +86,34 @@ gh api --method POST "repos/$REPO/rulesets" \
|
|
|
82
86
|
"min_entries_to_merge_wait_minutes": 1
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
|
-
]
|
|
89
|
+
],
|
|
90
|
+
"bypass_actors": []
|
|
86
91
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
echo "✓ Merge queue enabled for 'dev'"
|
|
92
|
+
JSON
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
# Configure 'main' branch protection (release branch)
|
|
93
|
-
# ============================================================================
|
|
94
|
-
|
|
95
|
-
echo "→ Configuring 'main' branch protection..."
|
|
96
|
-
|
|
97
|
-
gh api --method PUT "repos/$REPO/branches/main/protection" \
|
|
98
|
-
--input - <<EOF
|
|
94
|
+
cat > "$tag_payload" <<'JSON'
|
|
99
95
|
{
|
|
100
|
-
"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"require_code_owner_reviews": false
|
|
96
|
+
"name": "Make release tags immutable",
|
|
97
|
+
"target": "tag",
|
|
98
|
+
"enforcement": "active",
|
|
99
|
+
"conditions": {
|
|
100
|
+
"ref_name": {
|
|
101
|
+
"include": ["refs/tags/v*"],
|
|
102
|
+
"exclude": []
|
|
103
|
+
}
|
|
109
104
|
},
|
|
110
|
-
"
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"
|
|
105
|
+
"rules": [
|
|
106
|
+
{"type": "deletion"},
|
|
107
|
+
{"type": "non_fast_forward"}
|
|
108
|
+
],
|
|
109
|
+
"bypass_actors": []
|
|
115
110
|
}
|
|
116
|
-
|
|
111
|
+
JSON
|
|
117
112
|
|
|
118
|
-
|
|
113
|
+
upsert_ruleset "$MAIN_RULESET_NAME" "$main_payload"
|
|
114
|
+
upsert_ruleset "$TAG_RULESET_NAME" "$tag_payload"
|
|
119
115
|
|
|
120
|
-
|
|
121
|
-
# Configure repository settings
|
|
122
|
-
# ============================================================================
|
|
123
|
-
|
|
124
|
-
echo "→ Configuring repository settings..."
|
|
125
|
-
|
|
126
|
-
gh api --method PATCH "repos/$REPO" \
|
|
127
|
-
--input - <<EOF
|
|
116
|
+
gh api --method PATCH "repos/$REPO" --input - <<'JSON' >/dev/null
|
|
128
117
|
{
|
|
129
118
|
"allow_squash_merge": true,
|
|
130
119
|
"allow_merge_commit": false,
|
|
@@ -134,37 +123,34 @@ gh api --method PATCH "repos/$REPO" \
|
|
|
134
123
|
"delete_branch_on_merge": true,
|
|
135
124
|
"allow_auto_merge": true
|
|
136
125
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
echo "
|
|
168
|
-
echo " gh pr create --base main --head dev --title \"Release\""
|
|
169
|
-
echo " → CI passes → merge → semantic-release publishes"
|
|
170
|
-
echo ""
|
|
126
|
+
JSON
|
|
127
|
+
|
|
128
|
+
gh api --method PUT "repos/$REPO/environments/release" --input - <<'JSON' >/dev/null
|
|
129
|
+
{
|
|
130
|
+
"deployment_branch_policy": {
|
|
131
|
+
"protected_branches": false,
|
|
132
|
+
"custom_branch_policies": true
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
JSON
|
|
136
|
+
|
|
137
|
+
if ! gh api "repos/$REPO/environments/release/deployment-branch-policies" \
|
|
138
|
+
--jq '.branch_policies[] | select(.name == "main") | .id' | grep -q .; then
|
|
139
|
+
gh api --method POST "repos/$REPO/environments/release/deployment-branch-policies" \
|
|
140
|
+
-f name=main -f type=branch >/dev/null
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
gh variable set RELEASE_AUTOMATION_ENABLED --repo "$REPO" --body false
|
|
144
|
+
|
|
145
|
+
if gh api "repos/$REPO/branches/main/protection" >/dev/null 2>&1; then
|
|
146
|
+
gh api --method DELETE "repos/$REPO/branches/main/protection"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
while IFS= read -r ruleset_id; do
|
|
150
|
+
[[ -n "$ruleset_id" ]] && gh api --method DELETE "repos/$REPO/rulesets/$ruleset_id"
|
|
151
|
+
done < <(
|
|
152
|
+
gh api "repos/$REPO/rulesets" \
|
|
153
|
+
--jq '.[] | select(.name == "dev-merge-queue" or .name == "main-merge-queue") | .id'
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
echo "Configured protected main trunk, merge queue, immutable release tags, and disabled release automation."
|
|
@@ -23,6 +23,10 @@ const {
|
|
|
23
23
|
prepareClaudeConfigDir,
|
|
24
24
|
resolveRepoMcpConfigPath,
|
|
25
25
|
} = require('../worktree-claude-config.js');
|
|
26
|
+
const {
|
|
27
|
+
appendTaskRunModelArgs,
|
|
28
|
+
wrapTaskRunWithIsolatedSettings,
|
|
29
|
+
} = require('../task-run-model-args.js');
|
|
26
30
|
const { buildRawLogOnlyMetadata } = require('./context-replay-policy');
|
|
27
31
|
|
|
28
32
|
function runCommandWithTimeout(command, args, options = {}, callback = null) {
|
|
@@ -722,14 +726,10 @@ function resolveOutputFormatConfig(agent) {
|
|
|
722
726
|
|
|
723
727
|
function buildTaskRunArgs({ agent, providerName, modelSpec, runOutputFormat }) {
|
|
724
728
|
const args = ['task', 'run', '--output-format', runOutputFormat, '--provider', providerName];
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if (modelSpec?.reasoningEffort) {
|
|
731
|
-
args.push('--reasoning-effort', modelSpec.reasoningEffort);
|
|
732
|
-
}
|
|
729
|
+
const modelSpecSource = agent._resolveModelSpecSource
|
|
730
|
+
? agent._resolveModelSpecSource()
|
|
731
|
+
: 'direct';
|
|
732
|
+
appendTaskRunModelArgs(args, modelSpec, modelSpecSource);
|
|
733
733
|
|
|
734
734
|
// Add verification mode flag if configured
|
|
735
735
|
if (agent.config.verificationMode) {
|
|
@@ -1509,11 +1509,14 @@ async function spawnClaudeTaskIsolated(agent, context) {
|
|
|
1509
1509
|
const { manager, clusterId } = agent.isolation;
|
|
1510
1510
|
const providerName = agent._resolveProvider ? agent._resolveProvider() : 'claude';
|
|
1511
1511
|
const modelSpec = resolveAgentModelSpec(agent);
|
|
1512
|
+
const modelSpecSource = agent._resolveModelSpecSource
|
|
1513
|
+
? agent._resolveModelSpecSource()
|
|
1514
|
+
: 'direct';
|
|
1512
1515
|
|
|
1513
1516
|
agent._log(`📦 Agent ${agent.id}: Running task in isolated container using zeroshot task run...`);
|
|
1514
1517
|
|
|
1515
1518
|
const { desiredOutputFormat, runOutputFormat } = resolveOutputFormatConfig(agent);
|
|
1516
|
-
|
|
1519
|
+
let command = [
|
|
1517
1520
|
'zeroshot',
|
|
1518
1521
|
...buildTaskRunArgs({
|
|
1519
1522
|
agent,
|
|
@@ -1531,6 +1534,12 @@ async function spawnClaudeTaskIsolated(agent, context) {
|
|
|
1531
1534
|
});
|
|
1532
1535
|
|
|
1533
1536
|
command.push(finalContext);
|
|
1537
|
+
command = wrapTaskRunWithIsolatedSettings(command, {
|
|
1538
|
+
providerName,
|
|
1539
|
+
settings: loadSettings(),
|
|
1540
|
+
modelSpecSource,
|
|
1541
|
+
modelSpec,
|
|
1542
|
+
});
|
|
1534
1543
|
|
|
1535
1544
|
// STEP 1: Spawn task and extract task ID (same as non-isolated mode)
|
|
1536
1545
|
// Timeout for spawn phase - if CLI hangs during init (e.g., opencode 429 bug), kill it
|
|
@@ -99,7 +99,7 @@ export function validateModelIdFromCatalog(
|
|
|
99
99
|
modelId: string | null | undefined
|
|
100
100
|
): string | null | undefined {
|
|
101
101
|
if (!modelId) return modelId;
|
|
102
|
-
if (catalog
|
|
102
|
+
if (Object.prototype.hasOwnProperty.call(catalog, modelId)) return modelId;
|
|
103
103
|
const validModels = Object.keys(catalog).join(', ');
|
|
104
104
|
throw new InvalidProviderModelError(
|
|
105
105
|
`Invalid model "${modelId}" for provider "${provider}". Valid models: ${validModels}.`
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { unknownToMessage } from '../json';
|
|
2
|
+
import {
|
|
3
|
+
InvalidProviderModelError,
|
|
4
|
+
type LevelModelSpec,
|
|
5
|
+
type LevelOverrides,
|
|
6
|
+
type ModelCatalogEntry,
|
|
7
|
+
type ModelLevel,
|
|
8
|
+
type ResolvedModelSpec,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import { validateModelIdFromCatalog } from './common';
|
|
11
|
+
|
|
12
|
+
export const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {
|
|
13
|
+
'opencode/big-pickle': { rank: 1 },
|
|
14
|
+
'opencode/glm-4.7-free': { rank: 1 },
|
|
15
|
+
'opencode/gpt-5-nano': { rank: 1 },
|
|
16
|
+
'opencode/grok-code': { rank: 1 },
|
|
17
|
+
'opencode/minimax-m2.1-free': { rank: 1 },
|
|
18
|
+
'google/gemini-1.5-flash': { rank: 1 },
|
|
19
|
+
'google/gemini-1.5-flash-8b': { rank: 1 },
|
|
20
|
+
'google/gemini-1.5-pro': { rank: 1 },
|
|
21
|
+
'google/gemini-2.0-flash': { rank: 1 },
|
|
22
|
+
'google/gemini-2.0-flash-lite': { rank: 1 },
|
|
23
|
+
'google/gemini-2.5-flash': { rank: 1 },
|
|
24
|
+
'google/gemini-2.5-flash-image': { rank: 1 },
|
|
25
|
+
'google/gemini-2.5-flash-image-preview': { rank: 1 },
|
|
26
|
+
'google/gemini-2.5-flash-lite': { rank: 1 },
|
|
27
|
+
'google/gemini-2.5-flash-lite-preview-06-17': { rank: 1 },
|
|
28
|
+
'google/gemini-2.5-flash-lite-preview-09-2025': { rank: 1 },
|
|
29
|
+
'google/gemini-2.5-flash-preview-04-17': { rank: 1 },
|
|
30
|
+
'google/gemini-2.5-flash-preview-05-20': { rank: 1 },
|
|
31
|
+
'google/gemini-2.5-flash-preview-09-2025': { rank: 1 },
|
|
32
|
+
'google/gemini-2.5-flash-preview-tts': { rank: 1 },
|
|
33
|
+
'google/gemini-2.5-pro': { rank: 1 },
|
|
34
|
+
'google/gemini-2.5-pro-preview-05-06': { rank: 1 },
|
|
35
|
+
'google/gemini-2.5-pro-preview-06-05': { rank: 1 },
|
|
36
|
+
'google/gemini-2.5-pro-preview-tts': { rank: 1 },
|
|
37
|
+
'google/gemini-3-flash-preview': { rank: 1 },
|
|
38
|
+
'google/gemini-3-pro-preview': { rank: 1 },
|
|
39
|
+
'google/gemini-embedding-001': { rank: 1 },
|
|
40
|
+
'google/gemini-flash-latest': { rank: 1 },
|
|
41
|
+
'google/gemini-flash-lite-latest': { rank: 1 },
|
|
42
|
+
'google/gemini-live-2.5-flash': { rank: 1 },
|
|
43
|
+
'google/gemini-live-2.5-flash-preview-native-audio': { rank: 1 },
|
|
44
|
+
'openai/gpt-5.1-codex-max': { rank: 1 },
|
|
45
|
+
'openai/gpt-5.1-codex-mini': { rank: 1 },
|
|
46
|
+
'openai/gpt-5.2': { rank: 1 },
|
|
47
|
+
'openai/gpt-5.2-codex': { rank: 1 },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
|
|
51
|
+
level1: { rank: 1, model: null, reasoningEffort: 'low' },
|
|
52
|
+
level2: { rank: 2, model: null, reasoningEffort: 'medium' },
|
|
53
|
+
level3: { rank: 3, model: null, reasoningEffort: 'high' },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function containsControlCharacter(value: string): boolean {
|
|
57
|
+
return Array.from(value).some((character) => {
|
|
58
|
+
const codePoint = character.codePointAt(0);
|
|
59
|
+
return codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): ResolvedModelSpec {
|
|
64
|
+
const base = LEVEL_MAPPING[level] ?? LEVEL_MAPPING.level2;
|
|
65
|
+
const override = overrides?.[level];
|
|
66
|
+
const selectedModel = override?.model ?? base.model;
|
|
67
|
+
return {
|
|
68
|
+
level,
|
|
69
|
+
model:
|
|
70
|
+
override?.model === undefined || override.model === null
|
|
71
|
+
? (validateModelId(selectedModel) ?? null)
|
|
72
|
+
: (validateConfiguredModelId(selectedModel) ?? null),
|
|
73
|
+
reasoningEffort: override?.reasoningEffort ?? base.reasoningEffort,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function validateConfiguredModelId(
|
|
78
|
+
modelId: string | null | undefined
|
|
79
|
+
): string | null | undefined {
|
|
80
|
+
if (modelId && Object.prototype.hasOwnProperty.call(MODEL_CATALOG, modelId)) return modelId;
|
|
81
|
+
if (typeof modelId !== 'string') return validateModelId(modelId);
|
|
82
|
+
const segments = modelId.split('/');
|
|
83
|
+
if (
|
|
84
|
+
segments.length >= 2 &&
|
|
85
|
+
segments.every(Boolean) &&
|
|
86
|
+
!/\s/u.test(modelId) &&
|
|
87
|
+
!containsControlCharacter(modelId)
|
|
88
|
+
) {
|
|
89
|
+
return modelId;
|
|
90
|
+
}
|
|
91
|
+
throw new InvalidProviderModelError(
|
|
92
|
+
`Invalid configured model "${unknownToMessage(
|
|
93
|
+
modelId
|
|
94
|
+
)}" for provider "opencode". Expected "provider/model" with no whitespace, control characters, or empty path segments.`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function validateModelId(modelId: string | null | undefined): string | null | undefined {
|
|
99
|
+
return validateModelIdFromCatalog('opencode', MODEL_CATALOG, modelId);
|
|
100
|
+
}
|