@sweny-ai/core 0.1.142 → 0.1.144
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/dist/agent-env.d.ts +24 -0
- package/dist/agent-env.js +40 -7
- package/dist/cli/config-file.d.ts +1 -1
- package/dist/cli/config-file.js +1 -1
- package/dist/cli/config.d.ts +2 -0
- package/dist/cli/config.js +15 -7
- package/dist/cli/main.js +30 -4
- package/dist/cli/run-history.d.ts +2 -0
- package/dist/cli/run-history.js +2 -0
- package/dist/cli/run-output.d.ts +13 -2
- package/dist/cli/run-output.js +27 -2
- package/dist/executor.d.ts +12 -0
- package/dist/executor.js +127 -7
- package/dist/harness/abort.d.ts +17 -0
- package/dist/harness/abort.js +53 -0
- package/dist/harness/agents.d.ts +11 -0
- package/dist/harness/agents.js +15 -0
- package/dist/harness/capabilities.d.ts +23 -0
- package/dist/harness/capabilities.js +35 -0
- package/dist/harness/claude-code.d.ts +20 -43
- package/dist/harness/claude-code.js +58 -312
- package/dist/harness/codex.d.ts +175 -0
- package/dist/harness/codex.js +921 -0
- package/dist/harness/index.d.ts +11 -3
- package/dist/harness/index.js +8 -8
- package/dist/harness/parse.d.ts +49 -0
- package/dist/harness/parse.js +253 -0
- package/dist/harness/policy.d.ts +23 -1
- package/dist/harness/policy.js +73 -15
- package/dist/harness/prompts.d.ts +7 -1
- package/dist/harness/prompts.js +16 -0
- package/dist/harness/types.d.ts +13 -2
- package/dist/harness/types.js +2 -2
- package/dist/index.d.ts +8 -4
- package/dist/index.js +6 -3
- package/dist/node-policy.d.ts +31 -0
- package/dist/node-policy.js +42 -0
- package/dist/safe-outputs.d.ts +103 -0
- package/dist/safe-outputs.js +460 -0
- package/dist/schema.d.ts +501 -12
- package/dist/schema.js +210 -4
- package/dist/skills/github.js +18 -0
- package/dist/types.d.ts +107 -1
- package/dist/types.js +29 -0
- package/package.json +1 -1
package/dist/agent-env.d.ts
CHANGED
|
@@ -42,9 +42,33 @@ export declare const AGENT_ENV_PREFIXES: readonly string[];
|
|
|
42
42
|
export declare const AGENT_AUTH_VARS: readonly string[];
|
|
43
43
|
/** Split a comma/whitespace separated list, dropping empties. */
|
|
44
44
|
export declare function parseList(v: string | undefined): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Codex's own credentials and home (openai/codex rust-v0.159.2,
|
|
47
|
+
* login/src/auth/manager.rs): `codex exec` authenticates with `CODEX_API_KEY`
|
|
48
|
+
* (it does not read `OPENAI_API_KEY` for this; see {@link resolveCodexAuthEnv})
|
|
49
|
+
* or `CODEX_ACCESS_TOKEN`, else the login stored in `$CODEX_HOME/auth.json`.
|
|
50
|
+
*/
|
|
51
|
+
export declare const CODEX_AUTH_VARS: readonly string[];
|
|
52
|
+
/** Prefixes for a Codex run: locale only. `ANTHROPIC_*` / `CLAUDE_*` never reach Codex. */
|
|
53
|
+
export declare const CODEX_ENV_PREFIXES: readonly string[];
|
|
54
|
+
/**
|
|
55
|
+
* `codex exec` reads `CODEX_API_KEY`, not `OPENAI_API_KEY`. Copy the OpenAI
|
|
56
|
+
* key into `CODEX_API_KEY` when only the former is set, so either works.
|
|
57
|
+
* Pure: returns a copy.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveCodexAuthEnv(env: Record<string, string>): Record<string, string>;
|
|
60
|
+
/** A stored Codex login (`codex login`): `auth.json` under `CODEX_HOME`, default `~/.codex`. */
|
|
61
|
+
export declare function hasCodexLogin(env?: Record<string, string | undefined>, exists?: (p: string) => boolean): boolean;
|
|
45
62
|
export interface BuildAgentEnvOpts {
|
|
46
63
|
/** Per-call names to include (a node's declared skill env vars). */
|
|
47
64
|
extraVars?: readonly string[];
|
|
65
|
+
/**
|
|
66
|
+
* The harness's own credential names (Codex: {@link CODEX_AUTH_VARS}).
|
|
67
|
+
* Default: none beyond the Claude prefixes.
|
|
68
|
+
*/
|
|
69
|
+
authVars?: readonly string[];
|
|
70
|
+
/** Prefixes always kept. Default: {@link AGENT_ENV_PREFIXES} (Claude Code's). */
|
|
71
|
+
prefixes?: readonly string[];
|
|
48
72
|
/** Operator passthrough list (`env-passthrough`). `"*"` inherits everything. */
|
|
49
73
|
passthrough?: readonly string[];
|
|
50
74
|
logger?: Pick<Logger, "warn">;
|
package/dist/agent-env.js
CHANGED
|
@@ -138,6 +138,31 @@ export function parseList(v) {
|
|
|
138
138
|
.map((s) => s.trim())
|
|
139
139
|
.filter(Boolean);
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Codex's own credentials and home (openai/codex rust-v0.159.2,
|
|
143
|
+
* login/src/auth/manager.rs): `codex exec` authenticates with `CODEX_API_KEY`
|
|
144
|
+
* (it does not read `OPENAI_API_KEY` for this; see {@link resolveCodexAuthEnv})
|
|
145
|
+
* or `CODEX_ACCESS_TOKEN`, else the login stored in `$CODEX_HOME/auth.json`.
|
|
146
|
+
*/
|
|
147
|
+
export const CODEX_AUTH_VARS = ["CODEX_API_KEY", "CODEX_ACCESS_TOKEN", "CODEX_HOME"];
|
|
148
|
+
/** Prefixes for a Codex run: locale only. `ANTHROPIC_*` / `CLAUDE_*` never reach Codex. */
|
|
149
|
+
export const CODEX_ENV_PREFIXES = ["LC_"];
|
|
150
|
+
/**
|
|
151
|
+
* `codex exec` reads `CODEX_API_KEY`, not `OPENAI_API_KEY`. Copy the OpenAI
|
|
152
|
+
* key into `CODEX_API_KEY` when only the former is set, so either works.
|
|
153
|
+
* Pure: returns a copy.
|
|
154
|
+
*/
|
|
155
|
+
export function resolveCodexAuthEnv(env) {
|
|
156
|
+
const out = { ...env };
|
|
157
|
+
if (!out.CODEX_API_KEY && out.OPENAI_API_KEY)
|
|
158
|
+
out.CODEX_API_KEY = out.OPENAI_API_KEY;
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
/** A stored Codex login (`codex login`): `auth.json` under `CODEX_HOME`, default `~/.codex`. */
|
|
162
|
+
export function hasCodexLogin(env = process.env, exists = existsSync) {
|
|
163
|
+
const home = env.CODEX_HOME || path.join(homedir(), ".codex");
|
|
164
|
+
return exists(path.join(home, "auth.json"));
|
|
165
|
+
}
|
|
141
166
|
/**
|
|
142
167
|
* Whether the agent env is scoped to the allowlist. `SWENY_ENV_SCOPE` (or the
|
|
143
168
|
* explicit value) `on|true|1` / `off|false|0` wins everywhere; otherwise on
|
|
@@ -319,13 +344,21 @@ export function buildAgentEnv(source, opts = {}) {
|
|
|
319
344
|
opts.logger?.warn("[sweny] env-passthrough contains '*': the agent inherits the full environment, including every secret.");
|
|
320
345
|
return Object.fromEntries(Object.entries(source).filter((e) => e[1] != null));
|
|
321
346
|
}
|
|
322
|
-
const exact = new Set([
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
347
|
+
const exact = new Set([
|
|
348
|
+
...AGENT_ENV_ALLOWLIST,
|
|
349
|
+
...(opts.extraVars ?? []),
|
|
350
|
+
...(opts.authVars ?? []),
|
|
351
|
+
...passthrough,
|
|
352
|
+
]);
|
|
353
|
+
const prefixes = [...(opts.prefixes ?? AGENT_ENV_PREFIXES)];
|
|
354
|
+
// Bedrock / Vertex routing is a Claude Code setting; only its default prefixes carry it.
|
|
355
|
+
if (!opts.prefixes) {
|
|
356
|
+
if (truthy(source.CLAUDE_CODE_USE_BEDROCK))
|
|
357
|
+
prefixes.push(...BEDROCK_PREFIXES);
|
|
358
|
+
if (truthy(source.CLAUDE_CODE_USE_VERTEX))
|
|
359
|
+
for (const v of VERTEX_VARS)
|
|
360
|
+
exact.add(v);
|
|
361
|
+
}
|
|
329
362
|
const out = {};
|
|
330
363
|
for (const [k, v] of Object.entries(source)) {
|
|
331
364
|
if (v == null)
|
|
@@ -19,4 +19,4 @@ export type FileConfig = Record<string, string | string[] | Record<string, unkno
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function loadConfigFile(cwd?: string): FileConfig;
|
|
21
21
|
/** Starter config written by `sweny new`. */
|
|
22
|
-
export declare const STARTER_CONFIG = "# .sweny.yml \u2014 SWEny project configuration\n# Commit this file. Secrets (API keys, tokens) go in .env (gitignored).\n#\n# Every key matches a CLI flag: \"time-range: 4h\" is the same as \"--time-range 4h\".\n# CLI flags override this file; env vars override this file; this file overrides defaults.\n\n# \u2500\u2500 Providers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# observability-provider: datadog # datadog | sentry | cloudwatch | splunk | elastic | newrelic | loki | prometheus | pagerduty | heroku | opsgenie | vercel | supabase | netlify | fly | render | file\n# issue-tracker-provider: github-issues # github-issues | linear | jira\n# source-control-provider: github # github | gitlab\n# coding-agent-provider: claude # claude
|
|
22
|
+
export declare const STARTER_CONFIG = "# .sweny.yml \u2014 SWEny project configuration\n# Commit this file. Secrets (API keys, tokens) go in .env (gitignored).\n#\n# Every key matches a CLI flag: \"time-range: 4h\" is the same as \"--time-range 4h\".\n# CLI flags override this file; env vars override this file; this file overrides defaults.\n\n# \u2500\u2500 Providers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# observability-provider: datadog # datadog | sentry | cloudwatch | splunk | elastic | newrelic | loki | prometheus | pagerduty | heroku | opsgenie | vercel | supabase | netlify | fly | render | file\n# issue-tracker-provider: github-issues # github-issues | linear | jira\n# source-control-provider: github # github | gitlab\n# coding-agent-provider: claude # claude | codex\n# notification-provider: console # console | slack | teams | discord | email | webhook\n\n# \u2500\u2500 Investigation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# time-range: 24h\n# severity-focus: errors\n# service-filter: \"*\"\n# investigation-depth: standard # quick | standard | thorough\n\n# \u2500\u2500 PR / branch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# base-branch: main\n# pr-labels: agent,triage,needs-review\n\n# \u2500\u2500 Paths \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# service-map-path: .github/service-map.yml\n# log-file: ./logs/errors.json # required when observability-provider is \"file\"\n\n# \u2500\u2500 Cache \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# cache-dir: .sweny/cache\n# cache-ttl: 86400\n\n# \u2500\u2500 Run reporting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Opt-in and not currently available: token minting is not yet exposed, so\n# leaving this unset means no reporting request is ever made.\n# cloud-token: sweny_pk_...\n# Or set SWENY_CLOUD_TOKEN in your environment.\n\n# \u2500\u2500 Agent sandbox \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# See https://docs.sweny.ai/advanced/agent-sandbox/\n# sandbox: off # off (local default) | auto (CI default: sandbox if supported, else warn) | strict\n# sandbox-allowed-domains: [internal.example.com] # extra hosts agent commands may reach\n# env-scope: off # off (local default: full env) | on (CI default: allowlist)\n# env-passthrough: [NPM_TOKEN] # extra env vars the agent may see when scoped (\"*\" = all)\n\n# \u2500\u2500 MCP servers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Extend the coding agent with additional tools via MCP.\n# Value is a JSON object \u2014 each key is a server name you choose.\n# See https://docs.sweny.ai/advanced/mcp-servers/ for a full catalog with copy-paste configs.\n#\n# Example: GitHub MCP server (query PRs, issues, CI run logs)\n# mcp-servers-json: '{\"github\":{\"type\":\"stdio\",\"command\":\"npx\",\"args\":[\"-y\",\"@modelcontextprotocol/server-github@latest\"],\"env\":{\"GITHUB_PERSONAL_ACCESS_TOKEN\":\"ghp_...\"}}}'\n\n# \u2500\u2500 Local-only quick start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Uncomment to run without any external services (just an LLM API key):\n# observability-provider: file\n# log-file: ./sample-errors.json\n# issue-tracker-provider: file\n# source-control-provider: file\n# notification-provider: file\n# output-dir: .sweny/output\n\n# \u2500\u2500 Credentials (.env) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Copy the relevant block into your .env file and fill in the values.\n#\n# Claude (coding agent) \u2014 https://console.anthropic.com/settings/api-keys\n# ANTHROPIC_API_KEY=sk-ant-...\n#\n# GitHub (source control + issue tracker)\n# GITHUB_TOKEN=ghp_... # https://github.com/settings/tokens (repo + issues scopes)\n#\n# Datadog (observability) \u2014 https://app.datadoghq.com/organization-settings\n# DD_API_KEY=... # Organization Settings > API Keys\n# DD_APP_KEY=... # Organization Settings > Application Keys\n# DD_SITE=datadoghq.com # or datadoghq.eu, us3.datadoghq.com, etc.\n#\n# Sentry (observability) \u2014 https://sentry.io/settings/auth-tokens/\n# SENTRY_AUTH_TOKEN=sntrys_...\n# SENTRY_ORG=your-org-slug # from sentry.io/organizations/<slug>/\n# SENTRY_PROJECT=your-project # Project Settings > General > Project Slug\n#\n# Linear (issue tracker) \u2014 https://linear.app/settings/api\n# LINEAR_API_KEY=lin_api_...\n# LINEAR_TEAM_ID=... # Settings > Workspace > Teams > [team] > copy ID from URL\n# LINEAR_BUG_LABEL_ID=... # Settings > Labels > [label] > copy ID from URL\n#\n# Jira (issue tracker) \u2014 https://your-org.atlassian.net\n# JIRA_BASE_URL=https://your-org.atlassian.net\n# JIRA_EMAIL=you@company.com # your Atlassian account email\n# JIRA_API_TOKEN=... # https://id.atlassian.com/manage-profile/security/api-tokens\n#\n# Vercel (observability) \u2014 https://vercel.com/account/tokens\n# VERCEL_TOKEN=...\n# VERCEL_PROJECT_ID=prj_... # Project Settings > General > Project ID\n# VERCEL_TEAM_ID=team_... # optional, for team-owned projects\n#\n# Supabase (observability) \u2014 https://supabase.com/dashboard/account/tokens\n# SUPABASE_MANAGEMENT_KEY=...\n# SUPABASE_PROJECT_REF=... # Project Settings > General > Reference ID\n#\n# Netlify (observability) \u2014 https://app.netlify.com/user/applications#personal-access-tokens\n# NETLIFY_TOKEN=...\n# NETLIFY_SITE_ID=... # Site Settings > General > Site ID\n#\n# Fly.io (observability) \u2014 https://fly.io/user/personal_access_tokens\n# FLY_TOKEN=...\n# FLY_APP_NAME=... # the name of your Fly.io application\n#\n# Render (observability) \u2014 https://dashboard.render.com/u/settings\n# RENDER_API_KEY=...\n# RENDER_SERVICE_ID=srv-... # from your service's Settings page\n#\n# Prometheus (observability) \u2014 self-hosted or Grafana Cloud\n# PROMETHEUS_URL=http://prometheus.internal:9090\n# PROMETHEUS_TOKEN=... # optional, for secured instances\n#\n# PagerDuty (observability) \u2014 https://your-account.pagerduty.com/api_keys\n# PAGERDUTY_API_KEY=...\n#\n# Honeycomb (observability) \u2014 https://docs.honeycomb.io/api/\n# HONEYCOMB_API_KEY=...\n# HONEYCOMB_DATASET=... # dataset name (e.g. production)\n#\n# Heroku (observability) \u2014 https://devcenter.heroku.com/articles/platform-api-reference\n# HEROKU_API_KEY=... # https://dashboard.heroku.com/account\n# HEROKU_APP_NAME=... # the name of your Heroku application\n#\n# OpsGenie (observability) \u2014 https://support.atlassian.com/opsgenie/docs/api-key-management/\n# OPSGENIE_API_KEY=...\n# OPSGENIE_REGION=us # or eu for EU-hosted accounts\n#\n# Slack (notifications) \u2014 https://api.slack.com/apps\n# NOTIFICATION_WEBHOOK_URL=https://hooks.slack.com/services/...\n# # or use a bot token: SLACK_BOT_TOKEN=xoxb-...\n";
|
package/dist/cli/config-file.js
CHANGED
|
@@ -148,7 +148,7 @@ export const STARTER_CONFIG = `# .sweny.yml — SWEny project configuration
|
|
|
148
148
|
# observability-provider: datadog # datadog | sentry | cloudwatch | splunk | elastic | newrelic | loki | prometheus | pagerduty | heroku | opsgenie | vercel | supabase | netlify | fly | render | file
|
|
149
149
|
# issue-tracker-provider: github-issues # github-issues | linear | jira
|
|
150
150
|
# source-control-provider: github # github | gitlab
|
|
151
|
-
# coding-agent-provider: claude # claude
|
|
151
|
+
# coding-agent-provider: claude # claude | codex
|
|
152
152
|
# notification-provider: console # console | slack | teams | discord | email | webhook
|
|
153
153
|
|
|
154
154
|
# ── Investigation ────────────────────────────────────────────────────
|
package/dist/cli/config.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface CliConfig {
|
|
|
11
11
|
/** Auth precedence mode: auto (default), api-key, or oauth. */
|
|
12
12
|
swenyAuth: "auto" | "api-key" | "oauth";
|
|
13
13
|
openaiApiKey: string;
|
|
14
|
+
/** Codex's own credential: CODEX_API_KEY, else CODEX_ACCESS_TOKEN. */
|
|
15
|
+
codexApiKey?: string;
|
|
14
16
|
geminiApiKey: string;
|
|
15
17
|
observabilityProviders: string[];
|
|
16
18
|
observabilityCredentials: Record<string, Record<string, string>>;
|
package/dist/cli/config.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
+
import { hasCodexLogin } from "../agent-env.js";
|
|
4
|
+
import { unsupportedAgentError } from "../harness/agents.js";
|
|
3
5
|
export function registerTriageCommand(program) {
|
|
4
6
|
// NOTE: File-eligible options omit Commander defaults so parseCliInputs() can
|
|
5
7
|
// distinguish "user passed flag" from "not passed" and fall through to .sweny.yml.
|
|
6
8
|
return program
|
|
7
9
|
.command("triage")
|
|
8
10
|
.description("Run the SWEny triage workflow")
|
|
9
|
-
.option("--agent <provider>", "Coding agent: claude (
|
|
11
|
+
.option("--agent <provider>", "Coding agent: claude (default) or codex")
|
|
10
12
|
.option("--coding-agent-provider <provider>", "Coding agent provider (alias for --agent)")
|
|
11
13
|
.option("--observability-provider <provider>", "Observability provider (default: none)")
|
|
12
14
|
.option("--issue-tracker-provider <provider>", "Issue tracker provider (default: github-issues)")
|
|
@@ -107,6 +109,7 @@ export function parseCliInputs(options, fileConfig = {}) {
|
|
|
107
109
|
anthropicBaseUrl: env.ANTHROPIC_BASE_URL || f("anthropic-base-url") || "",
|
|
108
110
|
swenyAuth: normalizeSwenyAuth(env.SWENY_AUTH || f("sweny-auth")),
|
|
109
111
|
openaiApiKey: env.OPENAI_API_KEY || "",
|
|
112
|
+
codexApiKey: env.CODEX_API_KEY || env.CODEX_ACCESS_TOKEN || "",
|
|
110
113
|
geminiApiKey: env.GEMINI_API_KEY || env.GOOGLE_API_KEY || "",
|
|
111
114
|
observabilityProviders: obsProviders,
|
|
112
115
|
observabilityCredentials: obsCreds,
|
|
@@ -228,12 +231,17 @@ export function validateInputs(config) {
|
|
|
228
231
|
errors.push("Missing: ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN — get a key at https://console.anthropic.com");
|
|
229
232
|
}
|
|
230
233
|
break;
|
|
234
|
+
case "codex":
|
|
235
|
+
// codex exec authenticates with CODEX_API_KEY (OPENAI_API_KEY is mapped
|
|
236
|
+
// to it) or a stored `codex login` under CODEX_HOME.
|
|
237
|
+
if (!config.openaiApiKey && !config.codexApiKey && !hasCodexLogin(process.env)) {
|
|
238
|
+
errors.push("Missing: CODEX_API_KEY or OPENAI_API_KEY, or a Codex login (`codex login`), for --agent codex");
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
231
241
|
default:
|
|
232
|
-
// Honest --agent (#330):
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
errors.push(`Unsupported coding agent "${config.codingAgentProvider}": the only supported agent is "claude" ` +
|
|
236
|
-
`(headless Claude Code). Remove --agent / coding-agent-provider or set it to claude.`);
|
|
242
|
+
// Honest --agent (#330): only agents with an adapter are accepted.
|
|
243
|
+
// Anything else would silently run a different agent under that name.
|
|
244
|
+
errors.push(unsupportedAgentError(config.codingAgentProvider));
|
|
237
245
|
}
|
|
238
246
|
// Repository required unless all providers are file-based
|
|
239
247
|
const allLocal = config.issueTrackerProvider === "file" && config.sourceControlProvider === "file";
|
|
@@ -650,7 +658,7 @@ export function registerImplementCommand(program) {
|
|
|
650
658
|
return program
|
|
651
659
|
.command("implement <issueId>")
|
|
652
660
|
.description("Implement a fix for a specific issue and open a PR")
|
|
653
|
-
.option("--agent <provider>", "Coding agent: claude (
|
|
661
|
+
.option("--agent <provider>", "Coding agent: claude (default) or codex")
|
|
654
662
|
.option("--coding-agent-provider <provider>", "Coding agent provider (alias for --agent)")
|
|
655
663
|
.option("--issue-tracker-provider <provider>", "Issue tracker (linear|jira|github-issues|file)")
|
|
656
664
|
.option("--source-control-provider <provider>", "Source control (github|gitlab|file)")
|
package/dist/cli/main.js
CHANGED
|
@@ -10,6 +10,8 @@ import { execute } from "../executor.js";
|
|
|
10
10
|
import { triageWorkflow, implementWorkflow, seedContentWorkflow } from "../workflows/index.js";
|
|
11
11
|
import { consoleLogger } from "../types.js";
|
|
12
12
|
import { createHarness } from "../harness/index.js";
|
|
13
|
+
import { isSupportedAgent, unsupportedAgentError } from "../harness/agents.js";
|
|
14
|
+
import { resolveHarnessPolicy } from "../harness/policy.js";
|
|
13
15
|
import { builtinSkills, createSkillMap, validateWorkflowSkills } from "../skills/index.js";
|
|
14
16
|
import { formatMissingSkillLines, skillEnvWarnings } from "./skill-env.js";
|
|
15
17
|
import { configuredSkills, configuredSkillsWithDiagnostics } from "../skills/custom-loader.js";
|
|
@@ -175,6 +177,28 @@ async function resolveRulesAndContext(config) {
|
|
|
175
177
|
context: contextResult.resolved,
|
|
176
178
|
};
|
|
177
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Build the harness for `--agent` (#331). A non-Claude agent is checked before
|
|
182
|
+
* any node runs, so a missing or too-old CLI fails fast with the fix.
|
|
183
|
+
*/
|
|
184
|
+
async function harnessFor(agent, opts, harnessPolicy) {
|
|
185
|
+
if (!isSupportedAgent(agent)) {
|
|
186
|
+
console.error(chalk.red(`\n ${unsupportedAgentError(agent)}\n`));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
const harness = createHarness(agent, {
|
|
190
|
+
...opts,
|
|
191
|
+
policy: resolveHarnessPolicy(process.env, harnessPolicy, opts.logger),
|
|
192
|
+
});
|
|
193
|
+
if (agent !== "claude") {
|
|
194
|
+
const pre = await harness.preflight();
|
|
195
|
+
if (!pre.ok) {
|
|
196
|
+
console.error(chalk.red(`\n ${pre.reason}\n`));
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return harness;
|
|
201
|
+
}
|
|
178
202
|
// ── sweny triage ──────────────────────────────────────────────────────
|
|
179
203
|
const triageCmd = registerTriageCommand(program);
|
|
180
204
|
triageCmd.action(async (options) => {
|
|
@@ -203,7 +227,7 @@ triageCmd.action(async (options) => {
|
|
|
203
227
|
const skills = createSkillMap(triageSkillDiscovery.skills);
|
|
204
228
|
const mcpAutoConfig = buildMcpAutoConfig(config);
|
|
205
229
|
const mcpServers = buildAutoMcpServers(mcpAutoConfig);
|
|
206
|
-
const claude =
|
|
230
|
+
const claude = await harnessFor(config.codingAgentProvider, {
|
|
207
231
|
maxTurns: config.maxInvestigateTurns || 50,
|
|
208
232
|
cwd: process.cwd(),
|
|
209
233
|
logger: consoleLogger,
|
|
@@ -513,7 +537,7 @@ implementCmd.action(async (issueId, options) => {
|
|
|
513
537
|
const skills = createSkillMap(implementSkillDiscovery.skills);
|
|
514
538
|
const mcpAutoConfig = buildMcpAutoConfig(config);
|
|
515
539
|
const mcpServers = buildAutoMcpServers(mcpAutoConfig);
|
|
516
|
-
const claude =
|
|
540
|
+
const claude = await harnessFor(config.codingAgentProvider, {
|
|
517
541
|
maxTurns: config.maxImplementTurns || 40,
|
|
518
542
|
cwd: process.cwd(),
|
|
519
543
|
logger: consoleLogger,
|
|
@@ -761,14 +785,15 @@ export async function workflowRunAction(file, options) {
|
|
|
761
785
|
});
|
|
762
786
|
// Raw [info]/[debug] lines are verbose-only; warnings are rendered (#383).
|
|
763
787
|
const runLogger = createRunLogger({ verbose: Boolean(options.verbose), tty: isTTY });
|
|
764
|
-
|
|
788
|
+
// --agent picks the harness (#331): claude (default) or codex.
|
|
789
|
+
const claude = await harnessFor(config.codingAgentProvider, {
|
|
765
790
|
maxTurns: config.maxInvestigateTurns || 50,
|
|
766
791
|
cwd: process.cwd(),
|
|
767
792
|
logger: runLogger,
|
|
768
793
|
defaultMcpServers: mcpServers,
|
|
769
794
|
mcpServers: config.mcpServers,
|
|
770
795
|
model: workflow.model,
|
|
771
|
-
});
|
|
796
|
+
}, typeof options.harnessPolicy === "string" ? options.harnessPolicy : undefined);
|
|
772
797
|
// Track per-node entry time to compute elapsed on exit
|
|
773
798
|
const nodeEnterTimes = new Map();
|
|
774
799
|
const wfProgressObserver = isJson
|
|
@@ -939,6 +964,7 @@ export async function workflowRunAction(file, options) {
|
|
|
939
964
|
fileRoot: config.fileRoot || undefined,
|
|
940
965
|
signal,
|
|
941
966
|
max_steps: wfMaxSteps,
|
|
967
|
+
stageOutputs: options.stage === true,
|
|
942
968
|
}), wfTimeoutMs, `Workflow ${workflow.name}`);
|
|
943
969
|
const wfDurationMs = Date.now() - runStart;
|
|
944
970
|
const wfHasFailed = [...results.values()].some((r) => r.status === "failed");
|
|
@@ -53,6 +53,8 @@ export interface RunRecord {
|
|
|
53
53
|
id: string;
|
|
54
54
|
version: string;
|
|
55
55
|
};
|
|
56
|
+
/** Opinions the harness could not honor natively on some node, deduped. Absent when none. */
|
|
57
|
+
degraded?: string[];
|
|
56
58
|
}
|
|
57
59
|
/** sha256 (hex) of the workflow as canonical JSON: keys sorted, undefined dropped. */
|
|
58
60
|
export declare function hashWorkflow(workflow: Workflow): string;
|
package/dist/cli/run-history.js
CHANGED
|
@@ -92,6 +92,7 @@ export function buildRunRecord(i) {
|
|
|
92
92
|
};
|
|
93
93
|
});
|
|
94
94
|
const harness = [...i.results.values()].find((r) => r.harness)?.harness;
|
|
95
|
+
const degraded = [...new Set([...i.results.values()].flatMap((r) => r.degraded ?? []))];
|
|
95
96
|
return {
|
|
96
97
|
schema_version: RUN_HISTORY_SCHEMA_VERSION,
|
|
97
98
|
run_id: i.runId,
|
|
@@ -111,6 +112,7 @@ export function buildRunRecord(i) {
|
|
|
111
112
|
cost_usd: summary.costUsd ?? null,
|
|
112
113
|
},
|
|
113
114
|
...(harness ? { harness: { id: harness.id, version: harness.version } } : {}),
|
|
115
|
+
...(degraded.length > 0 ? { degraded } : {}),
|
|
114
116
|
};
|
|
115
117
|
}
|
|
116
118
|
// ── Write, prune, read ──────────────────────────────────────────
|
package/dist/cli/run-output.d.ts
CHANGED
|
@@ -18,7 +18,16 @@ export interface RunSummary {
|
|
|
18
18
|
tokens?: number;
|
|
19
19
|
/** Sum of SDK-reported cost. Undefined when the SDK reported none (never estimated). */
|
|
20
20
|
costUsd?: number;
|
|
21
|
+
/** Harness that ran the nodes, when a result was tagged. */
|
|
22
|
+
harness?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Opinions some node's harness could not honor natively, as short keys
|
|
25
|
+
* (`max_turns`, `egress allowlist`, `deny [write]`). Absent when none.
|
|
26
|
+
*/
|
|
27
|
+
degraded?: string[];
|
|
21
28
|
}
|
|
29
|
+
/** The short key of a `degraded` entry: the text before its first colon. */
|
|
30
|
+
export declare function degradedKey(entry: string): string;
|
|
22
31
|
/** Sum the per-node results into one run summary. */
|
|
23
32
|
export declare function summarizeRun(results: Map<string, NodeResult>, durationMs: number, crashed?: boolean): RunSummary;
|
|
24
33
|
export declare function formatReceiptDuration(ms: number): string;
|
|
@@ -26,8 +35,10 @@ export declare function formatTokenCount(n: number): string;
|
|
|
26
35
|
export declare function formatCost(usd: number): string;
|
|
27
36
|
/**
|
|
28
37
|
* One plain line: `✓ 3/3 nodes · 41 tool calls · 2m10s · 12k tokens · $0.18`.
|
|
29
|
-
* Token and cost segments are omitted when the SDK reported none.
|
|
30
|
-
*
|
|
38
|
+
* Token and cost segments are omitted when the SDK reported none. A harness
|
|
39
|
+
* other than Claude Code is named, and anything it could not honor natively
|
|
40
|
+
* is listed (`· codex · degraded: max_turns`). No ANSI; callers colorize only
|
|
41
|
+
* when writing to a TTY.
|
|
31
42
|
*/
|
|
32
43
|
export declare function formatReceipt(s: RunSummary): string;
|
|
33
44
|
/** Receipt for a terminal: colored only when `color` is true. */
|
package/dist/cli/run-output.js
CHANGED
|
@@ -9,6 +9,11 @@ import fs from "node:fs";
|
|
|
9
9
|
import chalk from "chalk";
|
|
10
10
|
import { consoleLogger } from "../types.js";
|
|
11
11
|
import { toMermaidBlock } from "../mermaid.js";
|
|
12
|
+
/** The short key of a `degraded` entry: the text before its first colon. */
|
|
13
|
+
export function degradedKey(entry) {
|
|
14
|
+
const i = entry.indexOf(":");
|
|
15
|
+
return (i === -1 ? entry : entry.slice(0, i)).trim();
|
|
16
|
+
}
|
|
12
17
|
/** Sum the per-node results into one run summary. */
|
|
13
18
|
export function summarizeRun(results, durationMs, crashed = false) {
|
|
14
19
|
let nodesOk = 0;
|
|
@@ -17,8 +22,13 @@ export function summarizeRun(results, durationMs, crashed = false) {
|
|
|
17
22
|
let toolCalls = 0;
|
|
18
23
|
let tokens;
|
|
19
24
|
let costUsd;
|
|
25
|
+
let harness;
|
|
26
|
+
const degraded = new Set();
|
|
20
27
|
const isNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
21
28
|
for (const r of results.values()) {
|
|
29
|
+
harness ??= r.harness?.id;
|
|
30
|
+
for (const d of r.degraded ?? [])
|
|
31
|
+
degraded.add(degradedKey(d));
|
|
22
32
|
if (r.status === "success")
|
|
23
33
|
nodesOk++;
|
|
24
34
|
else if (r.status === "skipped")
|
|
@@ -45,6 +55,8 @@ export function summarizeRun(results, durationMs, crashed = false) {
|
|
|
45
55
|
durationMs,
|
|
46
56
|
...(tokens !== undefined ? { tokens } : {}),
|
|
47
57
|
...(costUsd !== undefined ? { costUsd } : {}),
|
|
58
|
+
...(harness !== undefined ? { harness } : {}),
|
|
59
|
+
...(degraded.size > 0 ? { degraded: [...degraded] } : {}),
|
|
48
60
|
};
|
|
49
61
|
}
|
|
50
62
|
export function formatReceiptDuration(ms) {
|
|
@@ -71,8 +83,10 @@ export function formatCost(usd) {
|
|
|
71
83
|
}
|
|
72
84
|
/**
|
|
73
85
|
* One plain line: `✓ 3/3 nodes · 41 tool calls · 2m10s · 12k tokens · $0.18`.
|
|
74
|
-
* Token and cost segments are omitted when the SDK reported none.
|
|
75
|
-
*
|
|
86
|
+
* Token and cost segments are omitted when the SDK reported none. A harness
|
|
87
|
+
* other than Claude Code is named, and anything it could not honor natively
|
|
88
|
+
* is listed (`· codex · degraded: max_turns`). No ANSI; callers colorize only
|
|
89
|
+
* when writing to a TTY.
|
|
76
90
|
*/
|
|
77
91
|
export function formatReceipt(s) {
|
|
78
92
|
const parts = [
|
|
@@ -82,6 +96,8 @@ export function formatReceipt(s) {
|
|
|
82
96
|
formatReceiptDuration(s.durationMs),
|
|
83
97
|
...(s.tokens !== undefined ? [`${formatTokenCount(s.tokens)} tokens`] : []),
|
|
84
98
|
...(s.costUsd !== undefined ? [formatCost(s.costUsd)] : []),
|
|
99
|
+
...(s.harness !== undefined && s.harness !== "claude-code" ? [s.harness] : []),
|
|
100
|
+
...(s.degraded && s.degraded.length > 0 ? [`degraded: ${s.degraded.join(", ")}`] : []),
|
|
85
101
|
];
|
|
86
102
|
return parts.join(" · ");
|
|
87
103
|
}
|
|
@@ -182,6 +198,10 @@ export const WORKFLOW_RUN_OPTIONS = [
|
|
|
182
198
|
"--dry-run",
|
|
183
199
|
"Run with read-only tools only: write tools, external MCP servers, and shell/file-edit tools are withheld, so nothing is created, posted, or sent. Stops at the first conditional edge. To inspect nodes without running, use --list-nodes.",
|
|
184
200
|
],
|
|
201
|
+
[
|
|
202
|
+
"--stage",
|
|
203
|
+
"Run normally, but preview every safe output (nodes with outputs:) instead of writing it: print what would be written and write nothing",
|
|
204
|
+
],
|
|
185
205
|
["--list-nodes", "Validate, print nodes and skills, and exit without running"],
|
|
186
206
|
["--json", "Output result as JSON on stdout; suppress progress output"],
|
|
187
207
|
["--stream", "Stream NDJSON events to stdout (for Studio / automation)"],
|
|
@@ -195,4 +215,9 @@ export const WORKFLOW_RUN_OPTIONS = [
|
|
|
195
215
|
"Write a PR-comment markdown (run receipt, status-colored DAG, per-node table; metadata only) to <path> so any CI can post it",
|
|
196
216
|
],
|
|
197
217
|
["--input <json>", "JSON string of input data to pass to the workflow"],
|
|
218
|
+
["--agent <id>", "Coding agent that runs the nodes: claude (default) or codex"],
|
|
219
|
+
[
|
|
220
|
+
"--harness-policy <mode>",
|
|
221
|
+
"strict: refuse a node whose policy the agent cannot enforce; warn: run it and report what was not enforced (default: strict under GitHub Actions, warn elsewhere; env SWENY_HARNESS_POLICY)",
|
|
222
|
+
],
|
|
198
223
|
];
|
package/dist/executor.d.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { Workflow, Skill, Tool, Claude, Observer, Logger, ExecutionResult } from "./types.js";
|
|
16
16
|
import type { AgentHarness } from "./harness/types.js";
|
|
17
|
+
import { type ActorInfo } from "./safe-outputs.js";
|
|
17
18
|
export interface ExecuteOptions {
|
|
18
19
|
/** Registered skills (id → Skill) */
|
|
19
20
|
skills: Map<string, Skill>;
|
|
@@ -73,6 +74,17 @@ export interface ExecuteOptions {
|
|
|
73
74
|
* whole-run budget. Default: no timeout (back-compat).
|
|
74
75
|
*/
|
|
75
76
|
timeoutMs?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Safe outputs (#365): preview every write and apply none (CLI `--stage`).
|
|
79
|
+
* A dry run always stages. Default: false.
|
|
80
|
+
*/
|
|
81
|
+
stageOutputs?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Who triggered the run, for `safe_outputs.trusted_actors` /
|
|
84
|
+
* `trusted_associations`. Default: `GITHUB_ACTOR` and the author
|
|
85
|
+
* association in the `GITHUB_EVENT_PATH` payload.
|
|
86
|
+
*/
|
|
87
|
+
actor?: ActorInfo;
|
|
76
88
|
}
|
|
77
89
|
/**
|
|
78
90
|
* Default workflow-level step budget. Counts total node executions across the
|