@sweny-ai/core 0.1.146 → 0.1.148
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 +9 -0
- package/dist/agent-env.js +61 -0
- package/dist/cli/config-file.d.ts +1 -1
- package/dist/cli/config-file.js +1 -1
- package/dist/cli/config.js +6 -2
- package/dist/cli/run-output.js +1 -1
- package/dist/harness/agents.d.ts +1 -1
- package/dist/harness/agents.js +2 -2
- package/dist/harness/capabilities.d.ts +28 -0
- package/dist/harness/capabilities.js +40 -0
- package/dist/harness/claude-code.d.ts +2 -1
- package/dist/harness/claude-code.js +6 -3
- package/dist/harness/index.d.ts +5 -1
- package/dist/harness/index.js +4 -0
- package/dist/harness/pi.d.ts +202 -0
- package/dist/harness/pi.js +966 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/safe-outputs.d.ts +1 -1
- package/dist/safe-outputs.js +34 -5
- package/dist/skills/linear.js +1 -1
- package/package.json +1 -1
package/dist/agent-env.d.ts
CHANGED
|
@@ -57,6 +57,15 @@ export declare const CODEX_ENV_PREFIXES: readonly string[];
|
|
|
57
57
|
* Pure: returns a copy.
|
|
58
58
|
*/
|
|
59
59
|
export declare function resolveCodexAuthEnv(env: Record<string, string>): Record<string, string>;
|
|
60
|
+
/**
|
|
61
|
+
* pi's provider credentials (badlogic/pi-mono v0.99.2, docs/providers.md).
|
|
62
|
+
* sweny has no model opinion, so a pi run may need any provider's key; only
|
|
63
|
+
* these names (never the rest of the env) reach the pi process. A stored
|
|
64
|
+
* `auth.json` is not used: pi runs with a scratch `PI_CODING_AGENT_DIR`.
|
|
65
|
+
*/
|
|
66
|
+
export declare const PI_AUTH_VARS: readonly string[];
|
|
67
|
+
/** Prefixes for a pi run: locale only. `ANTHROPIC_*` / `CLAUDE_*` beyond the names above never reach pi. */
|
|
68
|
+
export declare const PI_ENV_PREFIXES: readonly string[];
|
|
60
69
|
/** A stored Codex login (`codex login`): `auth.json` under `CODEX_HOME`, default `~/.codex`. */
|
|
61
70
|
export declare function hasCodexLogin(env?: Record<string, string | undefined>, exists?: (p: string) => boolean): boolean;
|
|
62
71
|
export interface BuildAgentEnvOpts {
|
package/dist/agent-env.js
CHANGED
|
@@ -158,6 +158,67 @@ export function resolveCodexAuthEnv(env) {
|
|
|
158
158
|
out.CODEX_API_KEY = out.OPENAI_API_KEY;
|
|
159
159
|
return out;
|
|
160
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* pi's provider credentials (badlogic/pi-mono v0.99.2, docs/providers.md).
|
|
163
|
+
* sweny has no model opinion, so a pi run may need any provider's key; only
|
|
164
|
+
* these names (never the rest of the env) reach the pi process. A stored
|
|
165
|
+
* `auth.json` is not used: pi runs with a scratch `PI_CODING_AGENT_DIR`.
|
|
166
|
+
*/
|
|
167
|
+
export const PI_AUTH_VARS = [
|
|
168
|
+
"ANTHROPIC_API_KEY",
|
|
169
|
+
"ANTHROPIC_OAUTH_TOKEN",
|
|
170
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
171
|
+
"OPENAI_API_KEY",
|
|
172
|
+
"GEMINI_API_KEY",
|
|
173
|
+
"DEEPSEEK_API_KEY",
|
|
174
|
+
"MISTRAL_API_KEY",
|
|
175
|
+
"GROQ_API_KEY",
|
|
176
|
+
"CEREBRAS_API_KEY",
|
|
177
|
+
"XAI_API_KEY",
|
|
178
|
+
"OPENROUTER_API_KEY",
|
|
179
|
+
"AI_GATEWAY_API_KEY",
|
|
180
|
+
"ZAI_API_KEY",
|
|
181
|
+
"ZAI_CODING_CN_API_KEY",
|
|
182
|
+
"OPENCODE_API_KEY",
|
|
183
|
+
"RADIUS_API_KEY",
|
|
184
|
+
"TYPESAFE_API_KEY",
|
|
185
|
+
"HF_TOKEN",
|
|
186
|
+
"FIREWORKS_API_KEY",
|
|
187
|
+
"TOGETHER_API_KEY",
|
|
188
|
+
"BASETEN_API_KEY",
|
|
189
|
+
"KIMI_API_KEY",
|
|
190
|
+
"META_API_KEY",
|
|
191
|
+
"MINIMAX_API_KEY",
|
|
192
|
+
"MINIMAX_CN_API_KEY",
|
|
193
|
+
"MOONSHOT_API_KEY",
|
|
194
|
+
"NVIDIA_API_KEY",
|
|
195
|
+
"ANT_LING_API_KEY",
|
|
196
|
+
"QWEN_TOKEN_PLAN_API_KEY",
|
|
197
|
+
"QWEN_TOKEN_PLAN_CN_API_KEY",
|
|
198
|
+
"XIAOMI_API_KEY",
|
|
199
|
+
"XIAOMI_TOKEN_PLAN_CN_API_KEY",
|
|
200
|
+
"XIAOMI_TOKEN_PLAN_AMS_API_KEY",
|
|
201
|
+
"XIAOMI_TOKEN_PLAN_SGP_API_KEY",
|
|
202
|
+
"COPILOT_GITHUB_TOKEN",
|
|
203
|
+
"AZURE_OPENAI_API_KEY",
|
|
204
|
+
"AZURE_OPENAI_BASE_URL",
|
|
205
|
+
"AZURE_OPENAI_RESOURCE_NAME",
|
|
206
|
+
"CLOUDFLARE_API_KEY",
|
|
207
|
+
"CLOUDFLARE_ACCOUNT_ID",
|
|
208
|
+
"CLOUDFLARE_GATEWAY_ID",
|
|
209
|
+
"GOOGLE_CLOUD_API_KEY",
|
|
210
|
+
"GOOGLE_CLOUD_PROJECT",
|
|
211
|
+
"GCLOUD_PROJECT",
|
|
212
|
+
"GOOGLE_CLOUD_LOCATION",
|
|
213
|
+
"AWS_ACCESS_KEY_ID",
|
|
214
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
215
|
+
"AWS_SESSION_TOKEN",
|
|
216
|
+
"AWS_BEARER_TOKEN_BEDROCK",
|
|
217
|
+
"AWS_REGION",
|
|
218
|
+
"AWS_DEFAULT_REGION",
|
|
219
|
+
];
|
|
220
|
+
/** Prefixes for a pi run: locale only. `ANTHROPIC_*` / `CLAUDE_*` beyond the names above never reach pi. */
|
|
221
|
+
export const PI_ENV_PREFIXES = ["LC_"];
|
|
161
222
|
/** A stored Codex login (`codex login`): `auth.json` under `CODEX_HOME`, default `~/.codex`. */
|
|
162
223
|
export function hasCodexLogin(env = process.env, exists = existsSync) {
|
|
163
224
|
const home = env.CODEX_HOME || path.join(homedir(), ".codex");
|
|
@@ -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 | 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";
|
|
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 | pi\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 | codex
|
|
151
|
+
# coding-agent-provider: claude # claude | codex | pi
|
|
152
152
|
# notification-provider: console # console | slack | teams | discord | email | webhook
|
|
153
153
|
|
|
154
154
|
# ── Investigation ────────────────────────────────────────────────────
|
package/dist/cli/config.js
CHANGED
|
@@ -8,7 +8,7 @@ export function registerTriageCommand(program) {
|
|
|
8
8
|
return program
|
|
9
9
|
.command("triage")
|
|
10
10
|
.description("Run the SWEny triage workflow")
|
|
11
|
-
.option("--agent <provider>", "Coding agent: claude (default) or
|
|
11
|
+
.option("--agent <provider>", "Coding agent: claude (default), codex, or pi (experimental)")
|
|
12
12
|
.option("--coding-agent-provider <provider>", "Coding agent provider (alias for --agent)")
|
|
13
13
|
.option("--observability-provider <provider>", "Observability provider (default: none)")
|
|
14
14
|
.option("--issue-tracker-provider <provider>", "Issue tracker provider (default: github-issues)")
|
|
@@ -238,6 +238,10 @@ export function validateInputs(config) {
|
|
|
238
238
|
errors.push("Missing: CODEX_API_KEY or OPENAI_API_KEY, or a Codex login (`codex login`), for --agent codex");
|
|
239
239
|
}
|
|
240
240
|
break;
|
|
241
|
+
case "pi":
|
|
242
|
+
// pi takes any provider's key, or none (a local model in models.json), so
|
|
243
|
+
// there is no single credential to check here; pi reports its own auth error.
|
|
244
|
+
break;
|
|
241
245
|
default:
|
|
242
246
|
// Honest --agent (#330): only agents with an adapter are accepted.
|
|
243
247
|
// Anything else would silently run a different agent under that name.
|
|
@@ -658,7 +662,7 @@ export function registerImplementCommand(program) {
|
|
|
658
662
|
return program
|
|
659
663
|
.command("implement <issueId>")
|
|
660
664
|
.description("Implement a fix for a specific issue and open a PR")
|
|
661
|
-
.option("--agent <provider>", "Coding agent: claude (default) or
|
|
665
|
+
.option("--agent <provider>", "Coding agent: claude (default), codex, or pi (experimental)")
|
|
662
666
|
.option("--coding-agent-provider <provider>", "Coding agent provider (alias for --agent)")
|
|
663
667
|
.option("--issue-tracker-provider <provider>", "Issue tracker (linear|jira|github-issues|file)")
|
|
664
668
|
.option("--source-control-provider <provider>", "Source control (github|gitlab|file)")
|
package/dist/cli/run-output.js
CHANGED
|
@@ -215,7 +215,7 @@ export const WORKFLOW_RUN_OPTIONS = [
|
|
|
215
215
|
"Write a PR-comment markdown (run receipt, status-colored DAG, per-node table; metadata only) to <path> so any CI can post it",
|
|
216
216
|
],
|
|
217
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
|
|
218
|
+
["--agent <id>", "Coding agent that runs the nodes: claude (default), codex, or pi (experimental)"],
|
|
219
219
|
[
|
|
220
220
|
"--harness-policy <mode>",
|
|
221
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)",
|
package/dist/harness/agents.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* runs one agent under another's name. No runtime imports (the CLI config and
|
|
5
5
|
* the harness factory share it).
|
|
6
6
|
*/
|
|
7
|
-
export declare const SUPPORTED_AGENTS: readonly ["claude", "codex"];
|
|
7
|
+
export declare const SUPPORTED_AGENTS: readonly ["claude", "codex", "pi"];
|
|
8
8
|
export type SupportedAgent = (typeof SUPPORTED_AGENTS)[number];
|
|
9
9
|
export declare function isSupportedAgent(id: string): id is SupportedAgent;
|
|
10
10
|
/** The error for an agent id with no adapter. */
|
package/dist/harness/agents.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* runs one agent under another's name. No runtime imports (the CLI config and
|
|
5
5
|
* the harness factory share it).
|
|
6
6
|
*/
|
|
7
|
-
export const SUPPORTED_AGENTS = ["claude", "codex"];
|
|
7
|
+
export const SUPPORTED_AGENTS = ["claude", "codex", "pi"];
|
|
8
8
|
export function isSupportedAgent(id) {
|
|
9
9
|
return SUPPORTED_AGENTS.includes(id);
|
|
10
10
|
}
|
|
11
11
|
/** The error for an agent id with no adapter. */
|
|
12
12
|
export function unsupportedAgentError(id) {
|
|
13
13
|
return (`Unsupported coding agent "${id}": supported agents are "claude" (headless Claude Code) ` +
|
|
14
|
-
`and "codex" (Codex CLI). Remove --agent / coding-agent-provider or set it to one of them.`);
|
|
14
|
+
`and "codex" (Codex CLI), or "pi" (pi coding agent, experimental). Remove --agent / coding-agent-provider or set it to one of them.`);
|
|
15
15
|
}
|
|
@@ -28,3 +28,31 @@ export declare const CLAUDE_CODE_CAPABILITIES: HarnessCapabilities;
|
|
|
28
28
|
* - usage tokens only: `turn.completed.usage`; Codex reports no cost.
|
|
29
29
|
*/
|
|
30
30
|
export declare const CODEX_CAPABILITIES: HarnessCapabilities;
|
|
31
|
+
/**
|
|
32
|
+
* pi (`pi --mode rpc`), declared from the pi coding-agent docs and source at
|
|
33
|
+
* badlogic/pi-mono v0.99.2 (`packages/coding-agent/docs/{cli,rpc,rpc-commands,json,mcp,
|
|
34
|
+
* security,message-types}.md`, `src/core/agent-session.ts`, `src/extensions/mcp/`)
|
|
35
|
+
* and probed by the contract suite against the scripted fake:
|
|
36
|
+
* - structuredOutput prompt: pi has no output-schema flag; sweny prompts for
|
|
37
|
+
* the JSON, parses and validates it.
|
|
38
|
+
* - toolTrace full: `tool_execution_start|end` events carry `toolCallId`,
|
|
39
|
+
* `toolName`, `args`, `isError` for built-in and MCP tools alike.
|
|
40
|
+
* - builtinDeny shell-only, plus write, edit and subagent: `--exclude-tools` and
|
|
41
|
+
* `--tools` take tool names (`bash`, `write`, `edit`), and `-ne` leaves no
|
|
42
|
+
* extension, so no subagent tool exists. pi has no built-in net tool, so `net`
|
|
43
|
+
* is not deniable as a class (`bash` can still curl): it degrades or, in
|
|
44
|
+
* strict, refuses.
|
|
45
|
+
* - mcp home-isolation: MCP is a built-in extension that reads `mcp.json` from
|
|
46
|
+
* `$PI_CODING_AGENT_DIR`. sweny points that at a scratch dir holding only the
|
|
47
|
+
* generated `mcp.json`, and passes `-na` so a project `.pi/mcp.json` never loads.
|
|
48
|
+
* - sandbox none: "no built-in sandbox" (docs/security.md). The process wrapper
|
|
49
|
+
* (sandbox-wrapper.ts) is the only containment; strict refuses without it.
|
|
50
|
+
* - readOnly tool-allowlist: `--tools read,grep,find,ls` plus sweny's own MCP
|
|
51
|
+
* tools; no shell, write or edit tool exists in the run.
|
|
52
|
+
* - turnLimit watchdog: pi has no turn limit flag; sweny counts
|
|
53
|
+
* `tool_execution_start` events and aborts.
|
|
54
|
+
* - usage tokens and cost: `get_session_stats` returns tokens and cost for the
|
|
55
|
+
* session. Read once at the end, so `live` is false.
|
|
56
|
+
* - cancel rpc: the `abort` command, then SIGTERM and SIGKILL.
|
|
57
|
+
*/
|
|
58
|
+
export declare const PI_CAPABILITIES: HarnessCapabilities;
|
|
@@ -50,3 +50,43 @@ export const CODEX_CAPABILITIES = {
|
|
|
50
50
|
cancel: "kill",
|
|
51
51
|
resume: false,
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* pi (`pi --mode rpc`), declared from the pi coding-agent docs and source at
|
|
55
|
+
* badlogic/pi-mono v0.99.2 (`packages/coding-agent/docs/{cli,rpc,rpc-commands,json,mcp,
|
|
56
|
+
* security,message-types}.md`, `src/core/agent-session.ts`, `src/extensions/mcp/`)
|
|
57
|
+
* and probed by the contract suite against the scripted fake:
|
|
58
|
+
* - structuredOutput prompt: pi has no output-schema flag; sweny prompts for
|
|
59
|
+
* the JSON, parses and validates it.
|
|
60
|
+
* - toolTrace full: `tool_execution_start|end` events carry `toolCallId`,
|
|
61
|
+
* `toolName`, `args`, `isError` for built-in and MCP tools alike.
|
|
62
|
+
* - builtinDeny shell-only, plus write, edit and subagent: `--exclude-tools` and
|
|
63
|
+
* `--tools` take tool names (`bash`, `write`, `edit`), and `-ne` leaves no
|
|
64
|
+
* extension, so no subagent tool exists. pi has no built-in net tool, so `net`
|
|
65
|
+
* is not deniable as a class (`bash` can still curl): it degrades or, in
|
|
66
|
+
* strict, refuses.
|
|
67
|
+
* - mcp home-isolation: MCP is a built-in extension that reads `mcp.json` from
|
|
68
|
+
* `$PI_CODING_AGENT_DIR`. sweny points that at a scratch dir holding only the
|
|
69
|
+
* generated `mcp.json`, and passes `-na` so a project `.pi/mcp.json` never loads.
|
|
70
|
+
* - sandbox none: "no built-in sandbox" (docs/security.md). The process wrapper
|
|
71
|
+
* (sandbox-wrapper.ts) is the only containment; strict refuses without it.
|
|
72
|
+
* - readOnly tool-allowlist: `--tools read,grep,find,ls` plus sweny's own MCP
|
|
73
|
+
* tools; no shell, write or edit tool exists in the run.
|
|
74
|
+
* - turnLimit watchdog: pi has no turn limit flag; sweny counts
|
|
75
|
+
* `tool_execution_start` events and aborts.
|
|
76
|
+
* - usage tokens and cost: `get_session_stats` returns tokens and cost for the
|
|
77
|
+
* session. Read once at the end, so `live` is false.
|
|
78
|
+
* - cancel rpc: the `abort` command, then SIGTERM and SIGKILL.
|
|
79
|
+
*/
|
|
80
|
+
export const PI_CAPABILITIES = {
|
|
81
|
+
structuredOutput: "prompt",
|
|
82
|
+
toolTrace: "full",
|
|
83
|
+
builtinDeny: "shell-only",
|
|
84
|
+
denyClasses: ["shell", "write", "edit", "subagent"],
|
|
85
|
+
mcp: { inject: true, exclusive: "home-isolation" },
|
|
86
|
+
sandbox: { fs: false, network: false },
|
|
87
|
+
readOnly: "tool-allowlist",
|
|
88
|
+
turnLimit: "watchdog",
|
|
89
|
+
usage: { tokens: true, costUsd: true, live: false },
|
|
90
|
+
cancel: "rpc",
|
|
91
|
+
resume: false,
|
|
92
|
+
};
|
|
@@ -175,7 +175,8 @@ export declare class ClaudeCodeHarness implements Claude, AgentHarness {
|
|
|
175
175
|
* is a read-only run; `policy.deny` classes become native `disallowedTools`
|
|
176
176
|
* names, merged with `nativeDeny` and the legacy `disallowedTools`; and
|
|
177
177
|
* `policy.strict` makes MCP exclusive (`strictMcpConfig`) even for a
|
|
178
|
-
* write-capable node.
|
|
178
|
+
* write-capable node. Per-request sandbox mode overrides the client default,
|
|
179
|
+
* and policy egress supplies the node hosts without dropping scoped env access.
|
|
179
180
|
*/
|
|
180
181
|
run(req: HarnessRunRequest): Promise<HarnessRunResult>;
|
|
181
182
|
private runQuery;
|
|
@@ -272,7 +272,8 @@ export class ClaudeCodeHarness {
|
|
|
272
272
|
* is a read-only run; `policy.deny` classes become native `disallowedTools`
|
|
273
273
|
* names, merged with `nativeDeny` and the legacy `disallowedTools`; and
|
|
274
274
|
* `policy.strict` makes MCP exclusive (`strictMcpConfig`) even for a
|
|
275
|
-
* write-capable node.
|
|
275
|
+
* write-capable node. Per-request sandbox mode overrides the client default,
|
|
276
|
+
* and policy egress supplies the node hosts without dropping scoped env access.
|
|
276
277
|
*/
|
|
277
278
|
async run(req) {
|
|
278
279
|
const policy = req.policy ?? {
|
|
@@ -301,6 +302,8 @@ export class ClaudeCodeHarness {
|
|
|
301
302
|
readOnly,
|
|
302
303
|
disallowedTools: disallowedTools.length > 0 ? disallowedTools : undefined,
|
|
303
304
|
strictMcp: readOnly || policy.strict,
|
|
305
|
+
sandboxMode: policy.sandbox,
|
|
306
|
+
agentAccess: { envVars: req.agentAccess?.envVars ?? [], domains: policy.egress },
|
|
304
307
|
});
|
|
305
308
|
return { ...result, harness: this.info(), degraded: gate.degraded };
|
|
306
309
|
}
|
|
@@ -318,7 +321,7 @@ export class ClaudeCodeHarness {
|
|
|
318
321
|
// `strict` fails the node here instead.
|
|
319
322
|
const sandbox = resolveAgentSandbox({
|
|
320
323
|
env: process.env,
|
|
321
|
-
mode: this.sandboxMode,
|
|
324
|
+
mode: opts.sandboxMode ?? this.sandboxMode,
|
|
322
325
|
allowedDomains: this.sandboxAllowedDomains,
|
|
323
326
|
nodeDomains: agentAccess?.domains,
|
|
324
327
|
probe: this.sandboxProbe,
|
|
@@ -326,7 +329,7 @@ export class ClaudeCodeHarness {
|
|
|
326
329
|
});
|
|
327
330
|
if (sandbox.error) {
|
|
328
331
|
this.logger.error(sandbox.error);
|
|
329
|
-
return { status: "failed", data: { error: sandbox.error }, toolCalls: [] };
|
|
332
|
+
return { status: "failed", data: { error: sandbox.error, refused: true }, toolCalls: [] };
|
|
330
333
|
}
|
|
331
334
|
if (sandbox.warning && !this.sandboxWarned) {
|
|
332
335
|
this.sandboxWarned = true;
|
package/dist/harness/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { ClaudeCodeHarness, type ClaudeCodeHarnessOptions } from "./claude-code.js";
|
|
6
6
|
import { CodexHarness, type CodexHarnessOptions } from "./codex.js";
|
|
7
|
+
import { PiHarness, type PiHarnessOptions } from "./pi.js";
|
|
7
8
|
export { claudeCompat, asClaude } from "./compat.js";
|
|
8
9
|
export type { AgentHarness, HarnessCapabilities, HarnessCompleteRequest, HarnessId, HarnessInfo, HarnessRunRequest, HarnessRunResult, NodePolicy, PolicyGateResult, PolicyWrappers, ToolClass, } from "./types.js";
|
|
9
10
|
export { policyGate, nativeDenyClasses, resolveHarnessPolicy, isToolClass, TOOL_CLASSES } from "./policy.js";
|
|
@@ -13,6 +14,8 @@ export { ClaudeCodeHarness, CLAUDE_CODE_CAPABILITIES } from "./claude-code.js";
|
|
|
13
14
|
export type { ClaudeCodeHarnessOptions } from "./claude-code.js";
|
|
14
15
|
export { CodexHarness, CODEX_CAPABILITIES, MIN_CODEX_VERSION } from "./codex.js";
|
|
15
16
|
export type { CodexHarnessOptions } from "./codex.js";
|
|
17
|
+
export { PiHarness, PI_CAPABILITIES, MIN_PI_VERSION } from "./pi.js";
|
|
18
|
+
export type { PiHarnessOptions } from "./pi.js";
|
|
16
19
|
export { startToolBridge } from "./tool-bridge/server.js";
|
|
17
20
|
export type { ToolBridge, ToolBridgeOptions } from "./tool-bridge/server.js";
|
|
18
21
|
export { SUPPORTED_AGENTS, isSupportedAgent, unsupportedAgentError } from "./agents.js";
|
|
@@ -24,4 +27,5 @@ export type { SupportedAgent } from "./agents.js";
|
|
|
24
27
|
*/
|
|
25
28
|
export declare function createHarness(id: "claude" | "claude-code", opts?: ClaudeCodeHarnessOptions): ClaudeCodeHarness;
|
|
26
29
|
export declare function createHarness(id: "codex", opts?: CodexHarnessOptions): CodexHarness;
|
|
27
|
-
export declare function createHarness(id:
|
|
30
|
+
export declare function createHarness(id: "pi", opts?: PiHarnessOptions): PiHarness;
|
|
31
|
+
export declare function createHarness(id: string, opts?: ClaudeCodeHarnessOptions & CodexHarnessOptions & PiHarnessOptions): ClaudeCodeHarness | CodexHarness | PiHarness;
|
package/dist/harness/index.js
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { ClaudeCodeHarness } from "./claude-code.js";
|
|
6
6
|
import { CodexHarness } from "./codex.js";
|
|
7
|
+
import { PiHarness } from "./pi.js";
|
|
7
8
|
import { unsupportedAgentError } from "./agents.js";
|
|
8
9
|
export { claudeCompat, asClaude } from "./compat.js";
|
|
9
10
|
export { policyGate, nativeDenyClasses, resolveHarnessPolicy, isToolClass, TOOL_CLASSES } from "./policy.js";
|
|
10
11
|
export { ask, evaluate, buildAskPrompt, buildEvaluatePrompt } from "./prompts.js";
|
|
11
12
|
export { ClaudeCodeHarness, CLAUDE_CODE_CAPABILITIES } from "./claude-code.js";
|
|
12
13
|
export { CodexHarness, CODEX_CAPABILITIES, MIN_CODEX_VERSION } from "./codex.js";
|
|
14
|
+
export { PiHarness, PI_CAPABILITIES, MIN_PI_VERSION } from "./pi.js";
|
|
13
15
|
export { startToolBridge } from "./tool-bridge/server.js";
|
|
14
16
|
export { SUPPORTED_AGENTS, isSupportedAgent, unsupportedAgentError } from "./agents.js";
|
|
15
17
|
export function createHarness(id, opts = {}) {
|
|
@@ -19,6 +21,8 @@ export function createHarness(id, opts = {}) {
|
|
|
19
21
|
return new ClaudeCodeHarness(opts);
|
|
20
22
|
case "codex":
|
|
21
23
|
return new CodexHarness(opts);
|
|
24
|
+
case "pi":
|
|
25
|
+
return new PiHarness(opts);
|
|
22
26
|
default:
|
|
23
27
|
throw new Error(unsupportedAgentError(id));
|
|
24
28
|
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi harness (#415): runs a node on the pi coding agent (bring any model)
|
|
3
|
+
* through `pi --mode rpc`.
|
|
4
|
+
*
|
|
5
|
+
* Interfaces used, all read from badlogic/pi-mono at v0.99.2
|
|
6
|
+
* (`packages/coding-agent`, now also published as earendil-works/pi):
|
|
7
|
+
* - `docs/cli.md`: `--mode rpc`, `--no-session`, `--tools` (allowlist over
|
|
8
|
+
* built-in, extension and MCP tools), `--exclude-tools`, `--no-tools`,
|
|
9
|
+
* `--no-extensions` with explicit `-e builtin:mcp`, `--no-context-files`,
|
|
10
|
+
* `--no-skills`, `--no-prompt-templates`, `--no-themes`, `--no-approve`,
|
|
11
|
+
* `--model`, `--append-system-prompt`.
|
|
12
|
+
* - `docs/rpc.md`, `docs/rpc-commands.md`: JSONL framing on LF only, the
|
|
13
|
+
* `prompt`, `abort` and `get_session_stats` commands, `agent_settled`.
|
|
14
|
+
* - `docs/json.md`, `docs/message-types.md`: `message_end` (assistant message
|
|
15
|
+
* with `stopReason` and `errorMessage`), `tool_execution_start|end`,
|
|
16
|
+
* `auto_retry_end`.
|
|
17
|
+
* - `docs/mcp.md`, `src/extensions/mcp/`: `mcp.json` in `$PI_CODING_AGENT_DIR`,
|
|
18
|
+
* `exposure: "direct"` so tools are declared to the model, `${VAR}` in
|
|
19
|
+
* `env`, tool names `mcp__<server>__<tool>`.
|
|
20
|
+
* - `docs/security.md`: "no built-in sandbox", so the whole process runs
|
|
21
|
+
* inside the sweny process wrapper (sandbox-wrapper.ts) and strict refuses
|
|
22
|
+
* without it.
|
|
23
|
+
* - `src/core/agent-session.ts` `_refreshToolRegistry`: a `--tools` allowlist
|
|
24
|
+
* filters every registered tool, MCP tools included, by exact name.
|
|
25
|
+
*
|
|
26
|
+
* Every sweny opinion in the pi column of the enforcement matrix is enforced
|
|
27
|
+
* natively, kept by sweny (wrapper, tool bridge, prompt + parse + validate,
|
|
28
|
+
* watchdog), reported in `degraded`, or refused under strict policy by
|
|
29
|
+
* `policyGate`. Nothing is dropped silently.
|
|
30
|
+
*/
|
|
31
|
+
import type { Logger, McpServerConfig, NodeUsage, ToolContext, Tool } from "../types.js";
|
|
32
|
+
import { type SandboxMode } from "../agent-env.js";
|
|
33
|
+
import type { AgentHarness, HarnessCapabilities, HarnessCompleteRequest, HarnessInfo, HarnessRunRequest, HarnessRunResult, ToolClass } from "./types.js";
|
|
34
|
+
import { PI_CAPABILITIES } from "./capabilities.js";
|
|
35
|
+
import { type HarnessPolicyMode } from "./policy.js";
|
|
36
|
+
import { type SandboxWrapper } from "./sandbox-wrapper.js";
|
|
37
|
+
export { PI_CAPABILITIES };
|
|
38
|
+
/**
|
|
39
|
+
* Oldest pi this adapter is written against. The docs and source were read at
|
|
40
|
+
* v0.99.2; older releases are unverified, so they are refused.
|
|
41
|
+
*/
|
|
42
|
+
export declare const MIN_PI_VERSION = "0.99.2";
|
|
43
|
+
/** Name of the MCP server that carries sweny's skill tools (the tool bridge). */
|
|
44
|
+
export declare const PI_BRIDGE_SERVER = "sweny-core";
|
|
45
|
+
/**
|
|
46
|
+
* Hosts pi itself needs through the sandbox wrapper: the API host of every
|
|
47
|
+
* provider whose key is in the env, and of the provider named in `--model`
|
|
48
|
+
* (`provider/id`). Other providers (custom endpoints in `models.json`, cloud
|
|
49
|
+
* providers) are added with `SWENY_SANDBOX_ALLOWED_DOMAINS`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function piBackendHosts(env?: Record<string, string | undefined>, model?: string): string[];
|
|
52
|
+
export interface PiHarnessOptions {
|
|
53
|
+
/** Model passed to `--model`. Free text (`provider/id`, `id:thinking`); sweny has no model opinion. */
|
|
54
|
+
model?: string;
|
|
55
|
+
/** Tool-call budget the watchdog enforces when a node sets no `max_turns` (default: 20). */
|
|
56
|
+
maxTurns?: number;
|
|
57
|
+
/** Working directory (default: process.cwd()). */
|
|
58
|
+
cwd?: string;
|
|
59
|
+
logger?: Logger;
|
|
60
|
+
/** Default tool context for standalone usage (not via the executor). */
|
|
61
|
+
defaultContext?: ToolContext;
|
|
62
|
+
/** Catalog defaults, overridden by per-run skill servers and explicit mcpServers. */
|
|
63
|
+
defaultMcpServers?: Record<string, McpServerConfig>;
|
|
64
|
+
/** Explicit external MCP servers, overriding defaults and per-run skill servers. */
|
|
65
|
+
mcpServers?: Record<string, McpServerConfig>;
|
|
66
|
+
/** Extra env var names passed through (see agent-env.ts). Default: `SWENY_ENV_PASSTHROUGH`. */
|
|
67
|
+
envPassthrough?: string[];
|
|
68
|
+
/** Scope the pi env to the allowlist. Default: `SWENY_ENV_SCOPE`, else on in CI. */
|
|
69
|
+
envScope?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Process sandbox mode (`SWENY_SANDBOX`): `strict` refuses the node without
|
|
72
|
+
* the process wrapper, `auto` wraps when a wrapper is available and otherwise
|
|
73
|
+
* degrades, `off` never wraps. Default: `SWENY_SANDBOX`, else `auto` in CI
|
|
74
|
+
* and `off` locally.
|
|
75
|
+
*/
|
|
76
|
+
sandbox?: SandboxMode;
|
|
77
|
+
/**
|
|
78
|
+
* The host's process sandbox wrapper (#360 step 2). `undefined` = detect
|
|
79
|
+
* srt once per process; `null` = none (test seam).
|
|
80
|
+
*/
|
|
81
|
+
sandboxWrapper?: SandboxWrapper | null;
|
|
82
|
+
/** `strict` refuses a node with unenforceable opinions. Default: {@link resolveHarnessPolicy}. */
|
|
83
|
+
policy?: HarnessPolicyMode;
|
|
84
|
+
/** The pi CLI. Default: `SWENY_PI_PATH`, else `pi` on PATH. Test seam for the fake. */
|
|
85
|
+
piCommand?: {
|
|
86
|
+
command: string;
|
|
87
|
+
args: string[];
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* A `models.json` (custom providers and models) copied into pi's scratch
|
|
91
|
+
* agent dir. Default: `SWENY_PI_MODELS_JSON`. Without it pi sees only its
|
|
92
|
+
* built-in model catalog; the operator's own `~/.pi/agent` is never read.
|
|
93
|
+
*/
|
|
94
|
+
modelsFile?: string;
|
|
95
|
+
/** Shim command override for the tool bridge (test seam). */
|
|
96
|
+
toolBridgeShim?: {
|
|
97
|
+
command: string;
|
|
98
|
+
args: string[];
|
|
99
|
+
};
|
|
100
|
+
/** How long to wait for pi to stop after the `abort` command before SIGTERM (default: 3000 ms). */
|
|
101
|
+
abortGraceMs?: number;
|
|
102
|
+
/** Grace period between SIGTERM and SIGKILL when stopping pi (default: 2000 ms). */
|
|
103
|
+
killGraceMs?: number;
|
|
104
|
+
/** How long to wait for `get_session_stats` after the run settles (default: 5000 ms). */
|
|
105
|
+
statsTimeoutMs?: number;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Split `disallowed_tools` names into portable classes pi can act on, pi tools
|
|
109
|
+
* it can exclude by name, and the names it has no tool for.
|
|
110
|
+
*/
|
|
111
|
+
export declare function translateDenyNames(names?: readonly string[]): {
|
|
112
|
+
classes: ToolClass[];
|
|
113
|
+
piTools: string[];
|
|
114
|
+
unknown: string[];
|
|
115
|
+
};
|
|
116
|
+
/** The name pi registers an MCP tool under (`src/extensions/mcp/tools.ts` createMcpToolName). */
|
|
117
|
+
export declare function piMcpToolName(server: string, tool: string, isTaken?: (name: string) => boolean): string;
|
|
118
|
+
/** pi registered names of the bridge's tools, and the way back to the sweny names. */
|
|
119
|
+
export declare function bridgeToolNames(tools: readonly Tool[]): {
|
|
120
|
+
names: string[];
|
|
121
|
+
back: Map<string, string>;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* `mcp.json` entries for the external servers. Returns what pi cannot load
|
|
125
|
+
* (reported in `degraded`). Values using pi's `!command` or `${VAR}` syntax are
|
|
126
|
+
* refused, so a sweny config value can never run a command inside pi.
|
|
127
|
+
*/
|
|
128
|
+
export declare function piMcpServers(servers: Record<string, McpServerConfig>): {
|
|
129
|
+
entries: Record<string, Record<string, unknown>>;
|
|
130
|
+
unsupported: string[];
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* `get_session_stats` data onto NodeUsage. pi reports 0 for what a provider did
|
|
134
|
+
* not report (and for a model it has no price for), so only positive values are
|
|
135
|
+
* kept: absent stays absent, never a guessed 0 or a fake free run.
|
|
136
|
+
*/
|
|
137
|
+
export declare function mapStats(data: unknown): NodeUsage | undefined;
|
|
138
|
+
export declare class PiHarness implements AgentHarness {
|
|
139
|
+
readonly id: "pi";
|
|
140
|
+
readonly capabilities: HarnessCapabilities;
|
|
141
|
+
/**
|
|
142
|
+
* Empty on purpose: judges use the model pi would use anyway. Leaving this
|
|
143
|
+
* undefined would fall back to the Claude judge model, which pi may not have.
|
|
144
|
+
*/
|
|
145
|
+
readonly defaultJudgeModel = "";
|
|
146
|
+
readonly logger: Logger;
|
|
147
|
+
private model;
|
|
148
|
+
private maxTurns;
|
|
149
|
+
private cwd;
|
|
150
|
+
private defaultContext;
|
|
151
|
+
private mcpServers;
|
|
152
|
+
private defaultMcpServers;
|
|
153
|
+
private envPassthrough;
|
|
154
|
+
private envScope;
|
|
155
|
+
private sandboxMode;
|
|
156
|
+
private sandboxWrapper;
|
|
157
|
+
private policyMode;
|
|
158
|
+
private piCommand;
|
|
159
|
+
private modelsFile;
|
|
160
|
+
private toolBridgeShim;
|
|
161
|
+
private abortGraceMs;
|
|
162
|
+
private killGraceMs;
|
|
163
|
+
private statsTimeoutMs;
|
|
164
|
+
private egressWarned;
|
|
165
|
+
private preflightResult;
|
|
166
|
+
private version;
|
|
167
|
+
constructor(opts?: PiHarnessOptions);
|
|
168
|
+
info(): HarnessInfo;
|
|
169
|
+
/** `pi --version`, checked against {@link MIN_PI_VERSION}. Cached per instance. */
|
|
170
|
+
preflight(): Promise<{
|
|
171
|
+
ok: true;
|
|
172
|
+
version: string;
|
|
173
|
+
} | {
|
|
174
|
+
ok: false;
|
|
175
|
+
reason: string;
|
|
176
|
+
}>;
|
|
177
|
+
/** Env for the pi process: scoped like Claude Code's, with pi's provider credentials instead of Anthropic's. */
|
|
178
|
+
private buildEnv;
|
|
179
|
+
/** The node policy after `disallowed_tools` names are translated into pi classes. */
|
|
180
|
+
private compilePolicy;
|
|
181
|
+
/** argv shared by run() and complete(); the prompt travels as an RPC command, never in argv. */
|
|
182
|
+
private baseArgs;
|
|
183
|
+
/** Spawn pi in rpc mode, send the prompt, and read events until it settles and exits. */
|
|
184
|
+
private exec;
|
|
185
|
+
/** Write pi's scratch agent dir: generated `mcp.json` only (plus an operator-supplied `models.json`). */
|
|
186
|
+
private writeAgentDir;
|
|
187
|
+
/**
|
|
188
|
+
* Run a node. The gate runs first: in strict mode a node whose opinions pi
|
|
189
|
+
* cannot honor is refused before pi starts. pi has no native sandbox, so the
|
|
190
|
+
* whole process runs inside the host's process sandbox wrapper (#360 step 2)
|
|
191
|
+
* when the sandbox mode is not off; `SWENY_SANDBOX=strict` refuses without one.
|
|
192
|
+
*/
|
|
193
|
+
run(req: HarnessRunRequest): Promise<HarnessRunResult>;
|
|
194
|
+
/** Map one rpc outcome onto a NodeResult with Claude Code's semantics. */
|
|
195
|
+
private toResult;
|
|
196
|
+
/**
|
|
197
|
+
* One completion: no tools (`--no-tools`), no MCP, no extensions, an empty
|
|
198
|
+
* scratch agent dir. Nothing can act, so no process wrapper is needed.
|
|
199
|
+
* null when it failed (callers fail closed).
|
|
200
|
+
*/
|
|
201
|
+
complete(req: HarnessCompleteRequest): Promise<string | null>;
|
|
202
|
+
}
|