@the-open-engine/zeroshot 6.7.0 → 6.7.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/cli/commands/providers.js +4 -1
- package/cli/index.js +131 -27
- package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/claude.js +23 -10
- package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +4 -0
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/adapters/common.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/common.js +2 -1
- package/lib/agent-cli-provider/adapters/common.js.map +1 -1
- package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
- package/lib/agent-cli-provider/contract-options.js +2 -1
- package/lib/agent-cli-provider/contract-options.js.map +1 -1
- package/lib/agent-cli-provider/index.d.ts +1 -1
- package/lib/agent-cli-provider/index.d.ts.map +1 -1
- package/lib/agent-cli-provider/index.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +1 -1
- package/lib/agent-cli-provider/provider-registry.js +1 -1
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +6 -2
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +3 -1
- 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/lib/settings.js +1 -0
- package/package.json +6 -15
- package/protocol/openengine-cluster/v1/worker.schema.json +47 -0
- package/scripts/assert-release-published.js +128 -0
- package/scripts/release-dry-run.js +83 -0
- package/scripts/release-preflight.js +24 -12
- package/scripts/release-recovery.js +149 -0
- package/scripts/run-lint-staged-no-stash.js +68 -0
- package/scripts/semantic-release-notes.js +44 -0
- package/scripts/setup-merge-queue.sh +104 -118
- package/src/agent/agent-lifecycle.js +368 -91
- package/src/agent/agent-task-executor.js +384 -101
- package/src/agent-cli-provider/adapters/claude.ts +29 -11
- package/src/agent-cli-provider/adapters/codex.ts +4 -0
- package/src/agent-cli-provider/adapters/common.ts +2 -1
- package/src/agent-cli-provider/contract-options.ts +2 -1
- package/src/agent-cli-provider/index.ts +1 -0
- package/src/agent-cli-provider/provider-registry.ts +1 -1
- package/src/agent-cli-provider/single-agent-runtime.ts +8 -2
- package/src/agent-cli-provider/types.ts +3 -1
- package/src/agent-wrapper.js +9 -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/config-validator.js +10 -11
- package/src/isolation-manager.js +164 -74
- package/src/orchestrator.js +101 -40
- package/task-lib/attachable-watcher.js +13 -10
- package/task-lib/commands/kill.js +34 -9
- package/task-lib/commands/run.js +17 -2
- package/task-lib/commands/status.js +13 -3
- package/task-lib/process-termination.js +202 -0
- package/task-lib/runner.js +29 -23
- package/task-lib/store.js +28 -6
- package/task-lib/watcher.js +14 -2
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
type ClaudeCliFeatures,
|
|
5
5
|
type CommandSpec,
|
|
6
6
|
type ErrorClassification,
|
|
7
|
-
InvalidProviderModelError,
|
|
8
7
|
type LevelModelSpec,
|
|
9
8
|
type LevelOverrides,
|
|
10
9
|
type ModelCatalogEntry,
|
|
@@ -28,8 +27,23 @@ import { parseClaudeEvent } from './claude-parser';
|
|
|
28
27
|
|
|
29
28
|
const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {
|
|
30
29
|
haiku: { rank: 1 },
|
|
30
|
+
'claude-haiku-4-5': { rank: 1 },
|
|
31
|
+
'claude-haiku-4-5-20251001': { rank: 1 },
|
|
31
32
|
sonnet: { rank: 2 },
|
|
33
|
+
'claude-sonnet-4-5': { rank: 2 },
|
|
34
|
+
'claude-sonnet-4-5-20250929': { rank: 2 },
|
|
35
|
+
'claude-sonnet-4-6': { rank: 2 },
|
|
36
|
+
'claude-sonnet-5': { rank: 2 },
|
|
32
37
|
opus: { rank: 3 },
|
|
38
|
+
'claude-opus-4-5': { rank: 3 },
|
|
39
|
+
'claude-opus-4-5-20251101': { rank: 3 },
|
|
40
|
+
'claude-opus-4-6': { rank: 3 },
|
|
41
|
+
'claude-opus-4-7': { rank: 3 },
|
|
42
|
+
'claude-opus-4-8': { rank: 3 },
|
|
43
|
+
fable: { rank: 3 },
|
|
44
|
+
'claude-fable-5': { rank: 3 },
|
|
45
|
+
'claude-mythos-5': { rank: 3 },
|
|
46
|
+
'claude-mythos-preview': { rank: 3 },
|
|
33
47
|
};
|
|
34
48
|
|
|
35
49
|
const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
|
|
@@ -50,6 +64,7 @@ function detectCliFeatures(helpText?: string | null): ClaudeCliFeatures {
|
|
|
50
64
|
supportsIncludePartials: unknown ? true : /--include-partial-messages/.test(help),
|
|
51
65
|
supportsVerbose: unknown ? true : /--verbose/.test(help),
|
|
52
66
|
supportsModel: unknown ? true : /--model/.test(help),
|
|
67
|
+
supportsEffort: unknown ? true : /--effort/.test(help),
|
|
53
68
|
unknown,
|
|
54
69
|
};
|
|
55
70
|
}
|
|
@@ -86,6 +101,9 @@ function addModelArgs(args: string[], options: BuildProviderCommandOptions): voi
|
|
|
86
101
|
if (options.modelSpec?.model && features.supportsModel !== false) {
|
|
87
102
|
args.push('--model', options.modelSpec.model);
|
|
88
103
|
}
|
|
104
|
+
if (options.modelSpec?.reasoningEffort && features.supportsEffort !== false) {
|
|
105
|
+
args.push('--effort', options.modelSpec.reasoningEffort);
|
|
106
|
+
}
|
|
89
107
|
}
|
|
90
108
|
|
|
91
109
|
function addAutoApproveArgs(args: string[], options: BuildProviderCommandOptions): void {
|
|
@@ -139,6 +157,15 @@ function collectWarnings(options: BuildProviderCommandOptions): WarningMetadata[
|
|
|
139
157
|
)
|
|
140
158
|
);
|
|
141
159
|
}
|
|
160
|
+
if (options.modelSpec?.reasoningEffort && features.supportsEffort === false) {
|
|
161
|
+
warnings.push(
|
|
162
|
+
warning(
|
|
163
|
+
'claude',
|
|
164
|
+
'claude-reasoning',
|
|
165
|
+
'Claude CLI does not support --effort; skipping reasoningEffort.'
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
}
|
|
142
169
|
return warnings;
|
|
143
170
|
}
|
|
144
171
|
|
|
@@ -176,16 +203,7 @@ function resolveModelSpec(level: ModelLevel, overrides?: LevelOverrides): Resolv
|
|
|
176
203
|
}
|
|
177
204
|
|
|
178
205
|
function validateModelId(modelId: string | null | undefined): string | null | undefined {
|
|
179
|
-
|
|
180
|
-
return validateModelIdFromCatalog('claude', MODEL_CATALOG, modelId);
|
|
181
|
-
} catch (error) {
|
|
182
|
-
if (modelId && (modelId === 'opus-4.6' || modelId === 'claude-opus-4-6')) {
|
|
183
|
-
throw new InvalidProviderModelError(
|
|
184
|
-
`Invalid model "${modelId}" for provider "claude". Use canonical model ids: haiku, sonnet, opus.`
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
throw error;
|
|
188
|
-
}
|
|
206
|
+
return validateModelIdFromCatalog('claude', MODEL_CATALOG, modelId);
|
|
189
207
|
}
|
|
190
208
|
|
|
191
209
|
function classifyError(error: unknown): ErrorClassification {
|
|
@@ -27,6 +27,10 @@ import { parseCodexEvent } from './codex-parser';
|
|
|
27
27
|
const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {
|
|
28
28
|
'gpt-5.4': { rank: 2 },
|
|
29
29
|
'gpt-5.5': { rank: 3 },
|
|
30
|
+
'gpt-5.6': { rank: 3 },
|
|
31
|
+
'gpt-5.6-sol': { rank: 3 },
|
|
32
|
+
'gpt-5.6-terra': { rank: 2 },
|
|
33
|
+
'gpt-5.6-luna': { rank: 1 },
|
|
30
34
|
};
|
|
31
35
|
|
|
32
36
|
const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
|
|
@@ -100,8 +100,9 @@ export function validateModelIdFromCatalog(
|
|
|
100
100
|
): string | null | undefined {
|
|
101
101
|
if (!modelId) return modelId;
|
|
102
102
|
if (catalog[modelId] !== undefined) return modelId;
|
|
103
|
+
const validModels = Object.keys(catalog).join(', ');
|
|
103
104
|
throw new InvalidProviderModelError(
|
|
104
|
-
`Invalid model "${modelId}" for provider "${provider}".
|
|
105
|
+
`Invalid model "${modelId}" for provider "${provider}". Valid models: ${validModels}.`
|
|
105
106
|
);
|
|
106
107
|
}
|
|
107
108
|
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
|
|
14
14
|
const OUTPUT_FORMATS: readonly OutputFormat[] = ['text', 'json', 'stream-json'];
|
|
15
15
|
const MODEL_LEVELS: readonly ModelLevel[] = ['level1', 'level2', 'level3'];
|
|
16
|
-
const REASONING_EFFORTS: readonly ReasoningEffort[] = ['low', 'medium', 'high', 'xhigh'];
|
|
16
|
+
const REASONING_EFFORTS: readonly ReasoningEffort[] = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
17
17
|
const CLI_FEATURE_FIELDS = [
|
|
18
18
|
'supportsOutputFormat',
|
|
19
19
|
'supportsStreamJson',
|
|
@@ -22,6 +22,7 @@ const CLI_FEATURE_FIELDS = [
|
|
|
22
22
|
'supportsIncludePartials',
|
|
23
23
|
'supportsVerbose',
|
|
24
24
|
'supportsModel',
|
|
25
|
+
'supportsEffort',
|
|
25
26
|
'supportsJson',
|
|
26
27
|
'supportsOutputSchema',
|
|
27
28
|
'supportsDir',
|
|
@@ -61,7 +61,7 @@ type MutableModelSpec = {
|
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
const MODEL_LEVELS: readonly ModelLevel[] = ['level1', 'level2', 'level3'];
|
|
64
|
-
const REASONING_EFFORTS: readonly ReasoningEffort[] = ['low', 'medium', 'high', 'xhigh'];
|
|
64
|
+
const REASONING_EFFORTS: readonly ReasoningEffort[] = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
65
65
|
const settingsModule: unknown = require('../../lib/settings');
|
|
66
66
|
const providerDetectionModule: unknown = require('../../lib/provider-detection');
|
|
67
67
|
const claudeAuthModule: unknown = require('../../lib/settings/claude-auth');
|
|
@@ -420,7 +420,13 @@ function optionalModelLevel(value: unknown, field: string): ModelLevel | undefin
|
|
|
420
420
|
|
|
421
421
|
function optionalReasoningEffort(value: unknown, field: string): ReasoningEffort | undefined {
|
|
422
422
|
if (value === undefined) return undefined;
|
|
423
|
-
if (
|
|
423
|
+
if (
|
|
424
|
+
value === 'low' ||
|
|
425
|
+
value === 'medium' ||
|
|
426
|
+
value === 'high' ||
|
|
427
|
+
value === 'xhigh' ||
|
|
428
|
+
value === 'max'
|
|
429
|
+
) {
|
|
424
430
|
return value;
|
|
425
431
|
}
|
|
426
432
|
throw new Error(`${field} must be one of: ${REASONING_EFFORTS.join(', ')}.`);
|
|
@@ -15,7 +15,7 @@ export type ProviderId = (typeof providerIds)[number];
|
|
|
15
15
|
export type ProviderAlias = (typeof providerAliases)[number];
|
|
16
16
|
export type KnownProviderName = ProviderId | ProviderAlias;
|
|
17
17
|
export type ModelLevel = 'level1' | 'level2' | 'level3';
|
|
18
|
-
export type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh';
|
|
18
|
+
export type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
19
19
|
export type OutputFormat = 'text' | 'json' | 'stream-json';
|
|
20
20
|
export type {
|
|
21
21
|
ProviderCapabilities,
|
|
@@ -95,6 +95,7 @@ export interface ClaudeCliFeatures extends BaseCliFeatures {
|
|
|
95
95
|
readonly supportsIncludePartials: boolean;
|
|
96
96
|
readonly supportsVerbose: boolean;
|
|
97
97
|
readonly supportsModel: boolean;
|
|
98
|
+
readonly supportsEffort: boolean;
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
export interface CodexCliFeatures extends BaseCliFeatures {
|
|
@@ -186,6 +187,7 @@ export interface CliFeatureOverrides {
|
|
|
186
187
|
readonly supportsIncludePartials?: boolean;
|
|
187
188
|
readonly supportsVerbose?: boolean;
|
|
188
189
|
readonly supportsModel?: boolean;
|
|
190
|
+
readonly supportsEffort?: boolean;
|
|
189
191
|
readonly supportsJson?: boolean;
|
|
190
192
|
readonly supportsOutputSchema?: boolean;
|
|
191
193
|
readonly supportsDir?: boolean;
|
package/src/agent-wrapper.js
CHANGED
|
@@ -81,8 +81,15 @@ class AgentWrapper {
|
|
|
81
81
|
// LIVENESS DETECTION - Track output freshness to detect stuck agents
|
|
82
82
|
/** @type {number | null} */
|
|
83
83
|
this.lastOutputTime = null; // Timestamp of last output received
|
|
84
|
+
/** @type {number | null} */
|
|
85
|
+
this.taskStartedAt = null; // Timestamp used for absolute task timeout
|
|
84
86
|
/** @type {NodeJS.Timeout | null} */
|
|
85
87
|
this.livenessCheckInterval = null; // Interval for health checks
|
|
88
|
+
this.consecutiveStaleWarnings = 0;
|
|
89
|
+
this.livenessTerminationStarted = false;
|
|
90
|
+
this.livenessTerminationContext = null;
|
|
91
|
+
this.livenessTerminationAttempts = 0;
|
|
92
|
+
this.livenessTerminationRetryAt = 0;
|
|
86
93
|
this.staleDuration = normalizedConfig.staleDuration;
|
|
87
94
|
this.enableLivenessCheck = normalizedConfig.enableLivenessCheck;
|
|
88
95
|
|
|
@@ -499,8 +506,8 @@ class AgentWrapper {
|
|
|
499
506
|
* Kill current task
|
|
500
507
|
* @private
|
|
501
508
|
*/
|
|
502
|
-
_killTask() {
|
|
503
|
-
return killTask(this);
|
|
509
|
+
_killTask(reason) {
|
|
510
|
+
return killTask(this, reason);
|
|
504
511
|
}
|
|
505
512
|
|
|
506
513
|
/**
|
|
@@ -275,6 +275,7 @@ const hasSufficientEvidence = latestValidatorMessages.every((r) => {
|
|
|
275
275
|
return hasSufficientEvidence;`;
|
|
276
276
|
|
|
277
277
|
const { readRepoSettings } = require('../../lib/repo-settings');
|
|
278
|
+
const { normalizeGitRemoteName, quoteShellArgument } = require('../../lib/git-remote-utils');
|
|
278
279
|
const { resolveRequiredQualityGates } = require('../quality-gates');
|
|
279
280
|
|
|
280
281
|
function getSafeBranchName(value) {
|
|
@@ -313,6 +314,35 @@ function normalizeCloseIssueMode(value) {
|
|
|
313
314
|
return null;
|
|
314
315
|
}
|
|
315
316
|
|
|
317
|
+
function normalizeIssueNumber(value) {
|
|
318
|
+
const candidate = typeof value === 'number' ? String(value) : value;
|
|
319
|
+
if (typeof candidate !== 'string') return 'unknown';
|
|
320
|
+
const trimmed = candidate.trim();
|
|
321
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(trimmed) ? trimmed : 'unknown';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function normalizeIssueTitle(value) {
|
|
325
|
+
if (typeof value !== 'string' || value.trim() === '') return 'Implementation';
|
|
326
|
+
const normalized = [...value]
|
|
327
|
+
.map((character) => {
|
|
328
|
+
const codePoint = character.codePointAt(0);
|
|
329
|
+
return codePoint < 0x20 || codePoint === 0x7f ? ' ' : character;
|
|
330
|
+
})
|
|
331
|
+
.join('')
|
|
332
|
+
.trim();
|
|
333
|
+
return normalized || 'Implementation';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function resolveIssueContext(options) {
|
|
337
|
+
const issueNumber = normalizeIssueNumber(options.issueNumber);
|
|
338
|
+
const issueTitle = normalizeIssueTitle(options.issueTitle);
|
|
339
|
+
const issueReference =
|
|
340
|
+
options.includeIssueReference === false || issueNumber === 'unknown'
|
|
341
|
+
? ''
|
|
342
|
+
: `Closes #${issueNumber}`;
|
|
343
|
+
return { issueNumber, issueTitle, issueReference };
|
|
344
|
+
}
|
|
345
|
+
|
|
316
346
|
/**
|
|
317
347
|
* Resolve GitHub configuration from CLI options and repo settings.
|
|
318
348
|
* Priority: CLI options > repo settings (.zeroshot/settings.json) > defaults
|
|
@@ -321,12 +351,20 @@ function normalizeCloseIssueMode(value) {
|
|
|
321
351
|
* @param {string} [options.prBase] - Target branch for PRs
|
|
322
352
|
* @param {boolean} [options.mergeQueue] - Use GitHub merge queue
|
|
323
353
|
* @param {string} [options.closeIssue] - When to close issue: auto|always|never
|
|
354
|
+
* @param {string} [options.gitRemote=origin] - Remote to push the implementation branch to
|
|
355
|
+
* @param {string|number} [options.issueNumber] - Typed issue identifier for prompt commands
|
|
356
|
+
* @param {string} [options.issueTitle] - Typed issue title for prompt commands
|
|
357
|
+
* @param {boolean} [options.includeIssueReference] - Include the closing reference in PR text
|
|
324
358
|
* @returns {Object} Resolved configuration
|
|
325
359
|
*/
|
|
326
360
|
function resolveGitHubConfig(options = {}) {
|
|
327
361
|
const repoSettingsResult = readRepoSettings(options.cwd || process.cwd());
|
|
328
362
|
const repoSettings = repoSettingsResult.settings || {};
|
|
329
363
|
const repoGithub = repoSettings.github || {};
|
|
364
|
+
const gitRemote = normalizeGitRemoteName(options.gitRemote ?? 'origin');
|
|
365
|
+
if (!gitRemote) {
|
|
366
|
+
throw new Error(`Invalid git remote name '${options.gitRemote}'`);
|
|
367
|
+
}
|
|
330
368
|
|
|
331
369
|
// CLI options override repo settings
|
|
332
370
|
const prBase = getSafeBranchName(options.prBase) || getSafeBranchName(repoGithub.prBase);
|
|
@@ -347,7 +385,14 @@ function resolveGitHubConfig(options = {}) {
|
|
|
347
385
|
options.autoMerge === true ||
|
|
348
386
|
(options.autoMerge !== false && parseBool(repoGithub.autoMerge) === true);
|
|
349
387
|
|
|
350
|
-
return {
|
|
388
|
+
return {
|
|
389
|
+
prBase,
|
|
390
|
+
useMergeQueue,
|
|
391
|
+
closeIssueMode,
|
|
392
|
+
autoMerge,
|
|
393
|
+
gitRemote,
|
|
394
|
+
issueContext: resolveIssueContext(options),
|
|
395
|
+
};
|
|
351
396
|
}
|
|
352
397
|
|
|
353
398
|
/**
|
|
@@ -358,13 +403,16 @@ function resolveGitHubConfig(options = {}) {
|
|
|
358
403
|
* @returns {Object|null} Platform configuration or null if unsupported
|
|
359
404
|
*/
|
|
360
405
|
function getPlatformConfig(platform, config = {}) {
|
|
361
|
-
const { prBase, useMergeQueue, closeIssueMode, autoMerge } = config;
|
|
406
|
+
const { prBase, useMergeQueue, closeIssueMode, autoMerge, gitRemote } = config;
|
|
407
|
+
const issueContext = config.issueContext || resolveIssueContext({});
|
|
408
|
+
const issueTitleArgument = quoteShellArgument(`feat: ${issueContext.issueTitle}`);
|
|
409
|
+
const issueReferenceArgument = quoteShellArgument(issueContext.issueReference);
|
|
362
410
|
|
|
363
411
|
const PLATFORM_CONFIGS = {
|
|
364
412
|
github: {
|
|
365
413
|
prName: 'PR',
|
|
366
414
|
prNameLower: 'pull request',
|
|
367
|
-
createCmd: `gh pr create${prBase ? ` --base ${prBase}` : ''} --title
|
|
415
|
+
createCmd: `gh pr create${prBase ? ` --base ${prBase}` : ''} --title ${issueTitleArgument} --body ${issueReferenceArgument}`,
|
|
368
416
|
mergeCmd: useMergeQueue
|
|
369
417
|
? `PR_ID="$(timeout 30 gh pr view --json id --jq .id)"
|
|
370
418
|
gh api graphql -f query='mutation($id:ID!){enqueuePullRequest(input:{pullRequestId:$id}){mergeQueueEntry{state}}}' -f id="$PR_ID"
|
|
@@ -380,24 +428,26 @@ for i in $(seq 1 90); do if timeout 30 gh pr view --json mergedAt --jq .mergedAt
|
|
|
380
428
|
usesMergeQueue: useMergeQueue,
|
|
381
429
|
closeIssueMode: closeIssueMode || 'never',
|
|
382
430
|
autoMerge: Boolean(autoMerge),
|
|
431
|
+
gitRemote,
|
|
432
|
+
issueContext,
|
|
383
433
|
},
|
|
384
434
|
gitlab: {
|
|
385
435
|
prName: 'MR',
|
|
386
436
|
prNameLower: 'merge request',
|
|
387
|
-
createCmd:
|
|
388
|
-
'glab mr create --title "feat: {{issue_title}}" --description "Closes #{{issue_number}}"',
|
|
437
|
+
createCmd: `glab mr create --title ${issueTitleArgument} --description ${issueReferenceArgument}`,
|
|
389
438
|
mergeCmd: 'glab mr merge --auto-merge',
|
|
390
439
|
mergeFallbackCmd: 'glab mr merge',
|
|
391
440
|
prUrlExample: 'https://gitlab.com/owner/repo/-/merge_requests/123',
|
|
392
441
|
outputFields: { urlField: 'mr_url', numberField: 'mr_number', mergedField: 'merged' },
|
|
393
442
|
closeIssueMode: closeIssueMode || 'never',
|
|
394
443
|
autoMerge: Boolean(autoMerge),
|
|
444
|
+
gitRemote,
|
|
445
|
+
issueContext,
|
|
395
446
|
},
|
|
396
447
|
'azure-devops': {
|
|
397
448
|
prName: 'PR',
|
|
398
449
|
prNameLower: 'pull request',
|
|
399
|
-
createCmd:
|
|
400
|
-
'az repos pr create --title "feat: {{issue_title}}" --description "Closes #{{issue_number}}"',
|
|
450
|
+
createCmd: `az repos pr create --title ${issueTitleArgument} --description ${issueReferenceArgument}`,
|
|
401
451
|
mergeCmd: 'az repos pr update --id <PR_ID> --auto-complete true',
|
|
402
452
|
mergeFallbackCmd: 'az repos pr update --id <PR_ID> --status completed',
|
|
403
453
|
prUrlExample: 'https://dev.azure.com/org/project/_git/repo/pullrequest/123',
|
|
@@ -411,6 +461,8 @@ for i in $(seq 1 90); do if timeout 30 gh pr view --json mergedAt --jq .mergedAt
|
|
|
411
461
|
requiresPrIdExtraction: true,
|
|
412
462
|
closeIssueMode: closeIssueMode || 'never',
|
|
413
463
|
autoMerge: Boolean(autoMerge),
|
|
464
|
+
gitRemote,
|
|
465
|
+
issueContext,
|
|
414
466
|
},
|
|
415
467
|
};
|
|
416
468
|
|
|
@@ -430,8 +482,20 @@ const SUPPORTED_PLATFORMS = ['github', 'gitlab', 'azure-devops'];
|
|
|
430
482
|
* @returns {string} The complete review-mode prompt
|
|
431
483
|
*/
|
|
432
484
|
function generateReviewModePrompt(config) {
|
|
433
|
-
const {
|
|
434
|
-
|
|
485
|
+
const {
|
|
486
|
+
prName,
|
|
487
|
+
prNameLower,
|
|
488
|
+
createCmd,
|
|
489
|
+
prUrlExample,
|
|
490
|
+
outputFields,
|
|
491
|
+
requiresPrIdExtraction,
|
|
492
|
+
gitRemote,
|
|
493
|
+
issueContext,
|
|
494
|
+
} = config;
|
|
495
|
+
const gitRemoteArgument = quoteShellArgument(gitRemote);
|
|
496
|
+
const commitMessageArgument = quoteShellArgument(
|
|
497
|
+
`feat: implement #${issueContext.issueNumber} - ${issueContext.issueTitle}`
|
|
498
|
+
);
|
|
435
499
|
|
|
436
500
|
return `CRITICAL: ALL VALIDATORS APPROVED. YOU ARE A TRANSPORT-ONLY GIT PUSHER.
|
|
437
501
|
|
|
@@ -467,13 +531,13 @@ Run this. If nothing to commit, output JSON with ${outputFields.urlField}: null
|
|
|
467
531
|
|
|
468
532
|
### STEP 3: Commit the changes (MANDATORY if there are changes)
|
|
469
533
|
\`\`\`bash
|
|
470
|
-
git commit -m
|
|
534
|
+
git commit -m ${commitMessageArgument}
|
|
471
535
|
\`\`\`
|
|
472
536
|
Run this command. Do not skip it.
|
|
473
537
|
|
|
474
|
-
### STEP 4: Push to
|
|
538
|
+
### STEP 4: Push to ${gitRemote} (MANDATORY)
|
|
475
539
|
\`\`\`bash
|
|
476
|
-
git push -u
|
|
540
|
+
git push -u -- ${gitRemoteArgument} HEAD
|
|
477
541
|
\`\`\`
|
|
478
542
|
Run this. If it fails, do not edit files, rebase, or resolve conflicts. Output blocked JSON with the failure summary.
|
|
479
543
|
|
|
@@ -543,7 +607,14 @@ function generatePrompt(config) {
|
|
|
543
607
|
usesMergeQueue,
|
|
544
608
|
closeIssueMode,
|
|
545
609
|
autoMerge,
|
|
610
|
+
gitRemote,
|
|
611
|
+
issueContext,
|
|
546
612
|
} = config;
|
|
613
|
+
const gitRemoteArgument = quoteShellArgument(gitRemote);
|
|
614
|
+
const issueNumberArgument = quoteShellArgument(issueContext.issueNumber);
|
|
615
|
+
const commitMessageArgument = quoteShellArgument(
|
|
616
|
+
`feat: implement #${issueContext.issueNumber} - ${issueContext.issueTitle}`
|
|
617
|
+
);
|
|
547
618
|
|
|
548
619
|
if (!autoMerge) {
|
|
549
620
|
return generateReviewModePrompt(config);
|
|
@@ -627,13 +698,13 @@ Run this. If nothing to commit, output JSON with ${outputFields.urlField}: null
|
|
|
627
698
|
|
|
628
699
|
### STEP 3: Commit the changes (MANDATORY if there are changes)
|
|
629
700
|
\`\`\`bash
|
|
630
|
-
git commit -m
|
|
701
|
+
git commit -m ${commitMessageArgument}
|
|
631
702
|
\`\`\`
|
|
632
703
|
Run this command. Do not skip it.
|
|
633
704
|
|
|
634
|
-
### STEP 4: Push to
|
|
705
|
+
### STEP 4: Push to ${gitRemote} (MANDATORY)
|
|
635
706
|
\`\`\`bash
|
|
636
|
-
git push -u
|
|
707
|
+
git push -u -- ${gitRemoteArgument} HEAD
|
|
637
708
|
\`\`\`
|
|
638
709
|
Run this. If it fails, do not edit files, rebase, or resolve conflicts. Output blocked JSON with the failure summary.
|
|
639
710
|
|
|
@@ -665,8 +736,8 @@ ${
|
|
|
665
736
|
closeIssueMode !== 'never'
|
|
666
737
|
? `### STEP 7: Close the issue (MANDATORY)
|
|
667
738
|
\`\`\`bash
|
|
668
|
-
if [
|
|
669
|
-
ISSUE_STATE="$(gh issue view {
|
|
739
|
+
if [ ${issueNumberArgument} != "unknown" ]; then
|
|
740
|
+
ISSUE_STATE="$(gh issue view ${issueNumberArgument} --json state --jq .state 2>/dev/null || true)"
|
|
670
741
|
if [ "$ISSUE_STATE" = "OPEN" ]; then
|
|
671
742
|
BASE_BRANCH="${rebaseBranch || 'main'}"
|
|
672
743
|
DEFAULT_BRANCH="$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name 2>/dev/null || true)"
|
|
@@ -682,9 +753,9 @@ if [ "{{issue_number}}" != "unknown" ]; then
|
|
|
682
753
|
if [ "$SHOULD_CLOSE" = "1" ]; then
|
|
683
754
|
PR_URL="$(gh pr view --json url --jq .url 2>/dev/null || true)"
|
|
684
755
|
if [ -n "$PR_URL" ]; then
|
|
685
|
-
gh issue close {
|
|
756
|
+
gh issue close ${issueNumberArgument} --comment "Implemented in $PR_URL"
|
|
686
757
|
else
|
|
687
|
-
gh issue close {
|
|
758
|
+
gh issue close ${issueNumberArgument} --comment "Implemented"
|
|
688
759
|
fi
|
|
689
760
|
fi
|
|
690
761
|
fi
|
|
@@ -728,6 +799,10 @@ If blocked before creating a ${prName}, output:
|
|
|
728
799
|
* @param {string} [options.prBase] - Target branch for PRs
|
|
729
800
|
* @param {boolean} [options.mergeQueue] - Use GitHub merge queue
|
|
730
801
|
* @param {string} [options.closeIssue] - When to close issue: auto|always|never
|
|
802
|
+
* @param {string} [options.gitRemote=origin] - Remote to push the implementation branch to
|
|
803
|
+
* @param {string|number} [options.issueNumber] - Typed issue identifier for prompt commands
|
|
804
|
+
* @param {string} [options.issueTitle] - Typed issue title for prompt commands
|
|
805
|
+
* @param {boolean} [options.includeIssueReference] - Include the closing reference in PR text
|
|
731
806
|
* @param {Array} [options.requiredQualityGates] - Required handoff quality gates
|
|
732
807
|
* @param {boolean} [options.autoMerge] - Merge the PR (--ship). False stops after PR creation (--pr).
|
|
733
808
|
* @returns {Object} Agent configuration object
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Socket Discovery - Utilities for socket path management
|
|
3
3
|
*
|
|
4
|
-
* Socket locations
|
|
5
|
-
*
|
|
6
|
-
* - Clusters: ~/.zeroshot/sockets/cluster-<id>.sock (cluster-level, future)
|
|
7
|
-
* - Agents: ~/.zeroshot/sockets/cluster-<id>/<agent-id>.sock
|
|
4
|
+
* Socket locations are allocated by socket-paths.js under a short, per-user
|
|
5
|
+
* runtime directory so long HOME paths cannot exceed Unix socket limits.
|
|
8
6
|
*/
|
|
9
7
|
|
|
10
8
|
const path = require('path');
|
|
11
9
|
const fs = require('fs');
|
|
12
|
-
const os = require('os');
|
|
13
10
|
const net = require('net');
|
|
14
11
|
const { readClustersFileSync } = require('../../lib/clusters-registry');
|
|
12
|
+
const socketPaths = require('./socket-paths');
|
|
15
13
|
|
|
16
|
-
const ZEROSHOT_DIR = path.join(
|
|
17
|
-
const SOCKET_DIR =
|
|
14
|
+
const ZEROSHOT_DIR = path.join(socketPaths.resolveHomeDir(), '.zeroshot');
|
|
15
|
+
const SOCKET_DIR = socketPaths.getSocketDir();
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
18
|
* Check if an ID is a known cluster by looking up clusters.json
|
|
@@ -35,9 +33,7 @@ function isKnownCluster(id) {
|
|
|
35
33
|
* Ensure socket directory exists
|
|
36
34
|
*/
|
|
37
35
|
function ensureSocketDir() {
|
|
38
|
-
|
|
39
|
-
fs.mkdirSync(SOCKET_DIR, { recursive: true });
|
|
40
|
-
}
|
|
36
|
+
socketPaths.ensureSocketDir();
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
/**
|
|
@@ -46,8 +42,7 @@ function ensureSocketDir() {
|
|
|
46
42
|
* @returns {string} - Socket path
|
|
47
43
|
*/
|
|
48
44
|
function getTaskSocketPath(taskId) {
|
|
49
|
-
|
|
50
|
-
return path.join(SOCKET_DIR, `${taskId}.sock`);
|
|
45
|
+
return socketPaths.getTaskSocketPath(taskId);
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
/**
|
|
@@ -57,11 +52,7 @@ function getTaskSocketPath(taskId) {
|
|
|
57
52
|
* @returns {string} - Socket path
|
|
58
53
|
*/
|
|
59
54
|
function getAgentSocketPath(clusterId, agentId) {
|
|
60
|
-
|
|
61
|
-
if (!fs.existsSync(clusterDir)) {
|
|
62
|
-
fs.mkdirSync(clusterDir, { recursive: true });
|
|
63
|
-
}
|
|
64
|
-
return path.join(clusterDir, `${agentId}.sock`);
|
|
55
|
+
return socketPaths.getAgentSocketPath(clusterId, agentId);
|
|
65
56
|
}
|
|
66
57
|
|
|
67
58
|
/**
|
|
@@ -81,8 +72,7 @@ function getSocketPath(id, agentId = null) {
|
|
|
81
72
|
return getAgentSocketPath(id, agentId);
|
|
82
73
|
}
|
|
83
74
|
// Cluster-level socket (future use)
|
|
84
|
-
|
|
85
|
-
return path.join(SOCKET_DIR, `${id}.sock`);
|
|
75
|
+
return socketPaths.getClusterSocketPath(id);
|
|
86
76
|
}
|
|
87
77
|
// Unknown format, treat as task
|
|
88
78
|
return getTaskSocketPath(id);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic attach socket paths.
|
|
3
|
+
*
|
|
4
|
+
* Unix-domain sockets have a small path budget (104 bytes on macOS). Keep the
|
|
5
|
+
* live socket namespace independent from HOME while retaining one namespace
|
|
6
|
+
* per OS user and Zeroshot home.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const SOCKET_ROOT = process.platform === 'win32' ? null : '/tmp';
|
|
15
|
+
const SOCKET_DIR_MODE = 0o700;
|
|
16
|
+
|
|
17
|
+
function shortHash(value) {
|
|
18
|
+
return crypto.createHash('sha256').update(value).digest('hex').slice(0, 16);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function userNamespace() {
|
|
22
|
+
if (typeof process.getuid === 'function') {
|
|
23
|
+
return String(process.getuid());
|
|
24
|
+
}
|
|
25
|
+
return shortHash(os.userInfo().username);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveHomeDir(env = process.env) {
|
|
29
|
+
return env.ZEROSHOT_HOME || env.HOME || env.USERPROFILE || os.homedir();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getSocketDir(homeDir = resolveHomeDir()) {
|
|
33
|
+
if (process.platform === 'win32') {
|
|
34
|
+
return path.join(homeDir, '.zeroshot', 'sockets');
|
|
35
|
+
}
|
|
36
|
+
return path.join(SOCKET_ROOT, `zeroshot-${userNamespace()}-${shortHash(homeDir)}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function assertSafeSocketDir(socketDir) {
|
|
40
|
+
const stat = fs.lstatSync(socketDir);
|
|
41
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
42
|
+
throw new Error(`Attach socket path is not a directory: ${socketDir}`);
|
|
43
|
+
}
|
|
44
|
+
if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) {
|
|
45
|
+
throw new Error(`Attach socket directory is not owned by the current user: ${socketDir}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ensureOwnedDirectory(socketDir) {
|
|
50
|
+
fs.mkdirSync(socketDir, { recursive: true, mode: SOCKET_DIR_MODE });
|
|
51
|
+
assertSafeSocketDir(socketDir);
|
|
52
|
+
if (process.platform !== 'win32') {
|
|
53
|
+
fs.chmodSync(socketDir, SOCKET_DIR_MODE);
|
|
54
|
+
}
|
|
55
|
+
return socketDir;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ensureSocketDir(homeDir = resolveHomeDir()) {
|
|
59
|
+
const socketDir = getSocketDir(homeDir);
|
|
60
|
+
return ensureOwnedDirectory(socketDir);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getTaskSocketPath(taskId, homeDir = resolveHomeDir()) {
|
|
64
|
+
return path.join(ensureSocketDir(homeDir), `${taskId}.sock`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getAgentSocketPath(clusterId, agentId, homeDir = resolveHomeDir()) {
|
|
68
|
+
const clusterDir = path.join(ensureSocketDir(homeDir), clusterId);
|
|
69
|
+
ensureOwnedDirectory(clusterDir);
|
|
70
|
+
return path.join(clusterDir, `${agentId}.sock`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getClusterSocketPath(clusterId, homeDir = resolveHomeDir()) {
|
|
74
|
+
return path.join(ensureSocketDir(homeDir), `${clusterId}.sock`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
SOCKET_DIR_MODE,
|
|
79
|
+
resolveHomeDir,
|
|
80
|
+
getSocketDir,
|
|
81
|
+
ensureSocketDir,
|
|
82
|
+
getTaskSocketPath,
|
|
83
|
+
getAgentSocketPath,
|
|
84
|
+
getClusterSocketPath,
|
|
85
|
+
};
|
package/src/config-validator.js
CHANGED
|
@@ -21,6 +21,9 @@ const { getProvider } = require('./providers');
|
|
|
21
21
|
const { CAPABILITIES } = require('./providers/capabilities');
|
|
22
22
|
const { GUIDANCE_TOPICS } = require('./guidance-topics');
|
|
23
23
|
|
|
24
|
+
const REASONING_EFFORTS = Object.freeze(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
25
|
+
const REASONING_EFFORT_LABEL = REASONING_EFFORTS.join('|');
|
|
26
|
+
|
|
24
27
|
const HOOK_ACTION_TOPIC_CONTRACTS = Object.freeze({
|
|
25
28
|
verify_pull_request: Object.freeze([
|
|
26
29
|
{ topic: 'CLUSTER_COMPLETE', keys: null },
|
|
@@ -2054,14 +2057,13 @@ function validateProviderSettings(provider, providerSettings) {
|
|
|
2054
2057
|
providerModule.validateModelId(override.model);
|
|
2055
2058
|
}
|
|
2056
2059
|
if (override?.reasoningEffort && !providerSupportsCapability(provider, 'reasoningEffort')) {
|
|
2057
|
-
throw new Error(
|
|
2060
|
+
throw new Error(
|
|
2061
|
+
`reasoningEffort overrides are only supported for Claude, Codex, and Opencode`
|
|
2062
|
+
);
|
|
2058
2063
|
}
|
|
2059
|
-
if (
|
|
2060
|
-
override?.reasoningEffort &&
|
|
2061
|
-
!['low', 'medium', 'high', 'xhigh'].includes(override.reasoningEffort)
|
|
2062
|
-
) {
|
|
2064
|
+
if (override?.reasoningEffort && !REASONING_EFFORTS.includes(override.reasoningEffort)) {
|
|
2063
2065
|
throw new Error(
|
|
2064
|
-
`Invalid reasoningEffort "${override.reasoningEffort}" (
|
|
2066
|
+
`Invalid reasoningEffort "${override.reasoningEffort}" (${REASONING_EFFORT_LABEL})`
|
|
2065
2067
|
);
|
|
2066
2068
|
}
|
|
2067
2069
|
}
|
|
@@ -2172,12 +2174,9 @@ function validateModelRulesSupport(agent, provider, providerModule, levels, warn
|
|
|
2172
2174
|
function validateReasoningEffortSupport(agent, provider, warnings) {
|
|
2173
2175
|
if (agent.reasoningEffort && !providerSupportsCapability(provider, 'reasoningEffort')) {
|
|
2174
2176
|
warnings.push(`Agent "${agent.id}" sets reasoningEffort but ${provider} does not support it`);
|
|
2175
|
-
} else if (
|
|
2176
|
-
agent.reasoningEffort &&
|
|
2177
|
-
!['low', 'medium', 'high', 'xhigh'].includes(agent.reasoningEffort)
|
|
2178
|
-
) {
|
|
2177
|
+
} else if (agent.reasoningEffort && !REASONING_EFFORTS.includes(agent.reasoningEffort)) {
|
|
2179
2178
|
warnings.push(
|
|
2180
|
-
`Agent "${agent.id}" has invalid reasoningEffort "${agent.reasoningEffort}" (
|
|
2179
|
+
`Agent "${agent.id}" has invalid reasoningEffort "${agent.reasoningEffort}" (${REASONING_EFFORT_LABEL})`
|
|
2181
2180
|
);
|
|
2182
2181
|
}
|
|
2183
2182
|
}
|