@sweny-ai/core 0.1.180 → 0.1.182
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 +45 -1
- package/dist/agent-env.js +100 -18
- package/dist/cli/config-file.d.ts +1 -1
- package/dist/cli/config-file.js +0 -8
- package/dist/cli/config.d.ts +4 -23
- package/dist/cli/config.js +8 -60
- package/dist/cli/main.js +1 -9
- package/dist/cli/setup.js +1 -8
- package/dist/harness/agents.js +3 -2
- package/dist/harness/claude-code.d.ts +1 -1
- package/dist/harness/sandbox-wrapper.d.ts +3 -1
- package/dist/harness/sandbox-wrapper.js +6 -10
- package/dist/inputs.d.ts +9 -28
- package/dist/inputs.js +1 -1
- package/dist/schema.d.ts +669 -3069
- package/dist/schema.js +7 -7
- package/dist/sources.d.ts +4 -18
- package/package.json +2 -2
package/dist/agent-env.d.ts
CHANGED
|
@@ -355,11 +355,55 @@ export declare function resolveAgentAccess(skillIds: readonly string[], skills:
|
|
|
355
355
|
export type SandboxMode = "off" | "auto" | "strict";
|
|
356
356
|
/**
|
|
357
357
|
* Parse the sandbox mode from the explicit `mode` or `SWENY_SANDBOX`.
|
|
358
|
-
* Accepts `off|false|0`, `auto`, `strict|on|true|1` (`on` is an alias for
|
|
358
|
+
* Accepts `off|false|0|no`, `auto`, `strict|on|true|1` (`on` is an alias for
|
|
359
359
|
* `strict`). Unset or unknown (with a warning) uses the default: `auto` when
|
|
360
360
|
* `CI` is truthy, `off` otherwise. An explicit value always wins.
|
|
361
361
|
*/
|
|
362
362
|
export declare function resolveSandboxMode(env: Record<string, string | undefined>, mode?: string, logger?: Pick<Logger, "warn">): SandboxMode;
|
|
363
|
+
/**
|
|
364
|
+
* The fix-it hint for a sandbox start that failed on a user namespace. Ubuntu
|
|
365
|
+
* 23.10+ restricts unprivileged user namespaces (the sysctl), and Ubuntu 26.04
|
|
366
|
+
* also loads the `bwrap-userns-restrict` AppArmor profile, which denies every
|
|
367
|
+
* capability to bwrap's children, so the seccomp helper of
|
|
368
|
+
* @anthropic-ai/sandbox-runtime cannot nest its own user namespace
|
|
369
|
+
* (`apply-seccomp: write /proc/self/setgroups ... Permission denied`,
|
|
370
|
+
* anthropics/sandbox-runtime#429). Empty when `stderr` is not a namespace error.
|
|
371
|
+
*/
|
|
372
|
+
export declare function usernsHint(stderr: string): string;
|
|
373
|
+
/**
|
|
374
|
+
* Where the host probes look for `bwrap` and `unshare`. Never PATH: a probe runs
|
|
375
|
+
* a real binary, and a directory an agent can write may be on PATH.
|
|
376
|
+
*/
|
|
377
|
+
export declare const SYSTEM_BIN_DIRS: readonly ["/usr/bin", "/bin", "/usr/local/bin", "/usr/sbin"];
|
|
378
|
+
/** Absolute path of `name` in {@link SYSTEM_BIN_DIRS} (first match), never from PATH. */
|
|
379
|
+
export declare function systemBin(name: string, exists?: (p: string) => boolean): string | undefined;
|
|
380
|
+
export interface ProbeResult {
|
|
381
|
+
/** Exit status; null when the process could not start or was killed. */
|
|
382
|
+
status: number | null;
|
|
383
|
+
stdout: string;
|
|
384
|
+
stderr: string;
|
|
385
|
+
/** Spawn error (ENOENT, timeout). */
|
|
386
|
+
error?: Error;
|
|
387
|
+
}
|
|
388
|
+
/** Run a probe binary (an absolute path) and collect its result. */
|
|
389
|
+
export type ProbeRunner = (file: string, args: string[]) => ProbeResult;
|
|
390
|
+
/** One line of a probe's output for a warning: the first line, control characters masked, capped. */
|
|
391
|
+
export declare function probeLine(text: string): string;
|
|
392
|
+
/**
|
|
393
|
+
* Functional bubblewrap check: can it actually create a sandbox on this host?
|
|
394
|
+
* The same runs as the Action's `scripts/sandbox-userns.sh probe`: a plain
|
|
395
|
+
* `bwrap ... true`, then a user namespace nested inside bwrap, which the SDK
|
|
396
|
+
* sandbox's seccomp helper needs. On Ubuntu 26.04 the first passes and the
|
|
397
|
+
* second does not, so a plain probe would report a sandbox that then fails
|
|
398
|
+
* every command.
|
|
399
|
+
*
|
|
400
|
+
* `bwrap` and `unshare` come only from {@link SYSTEM_BIN_DIRS}, run by absolute
|
|
401
|
+
* path with {@link PROBE_ENV}, and at most one sanitized line of their stderr
|
|
402
|
+
* reaches the warning. The nested run is skipped (not failed) when there is no
|
|
403
|
+
* system `unshare` or it has no `--map-root-user` (busybox): an unusable probe
|
|
404
|
+
* tool is not a restricted host.
|
|
405
|
+
*/
|
|
406
|
+
export declare function bwrapWorks(run?: ProbeRunner, findBin?: (name: string) => string | undefined): string | undefined;
|
|
363
407
|
/**
|
|
364
408
|
* Probe whether the SDK sandbox can start on this host. Returns a reason
|
|
365
409
|
* string when it cannot, undefined when it looks supported.
|
package/dist/agent-env.js
CHANGED
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
* SWENY_SANDBOX / sandbox auto | strict | off (default: auto in CI, off locally)
|
|
26
26
|
* SWENY_SANDBOX_ALLOWED_DOMAINS / sandbox-allowed-domains extra hosts for sandboxed commands
|
|
27
27
|
*/
|
|
28
|
-
import {
|
|
29
|
-
import { existsSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
28
|
+
import { spawnSync } from "node:child_process";
|
|
29
|
+
import { accessSync, constants as fsConstants, existsSync, mkdirSync, mkdtempSync, statSync, writeFileSync, } from "node:fs";
|
|
30
30
|
import { homedir, tmpdir } from "node:os";
|
|
31
31
|
import * as path from "node:path";
|
|
32
32
|
import { runStateRoot } from "./journal.js";
|
|
@@ -872,13 +872,13 @@ export function resolveAgentAccess(skillIds, skills, grant = []) {
|
|
|
872
872
|
}
|
|
873
873
|
/**
|
|
874
874
|
* Parse the sandbox mode from the explicit `mode` or `SWENY_SANDBOX`.
|
|
875
|
-
* Accepts `off|false|0`, `auto`, `strict|on|true|1` (`on` is an alias for
|
|
875
|
+
* Accepts `off|false|0|no`, `auto`, `strict|on|true|1` (`on` is an alias for
|
|
876
876
|
* `strict`). Unset or unknown (with a warning) uses the default: `auto` when
|
|
877
877
|
* `CI` is truthy, `off` otherwise. An explicit value always wins.
|
|
878
878
|
*/
|
|
879
879
|
export function resolveSandboxMode(env, mode, logger) {
|
|
880
880
|
const raw = (mode ?? env.SWENY_SANDBOX ?? "").trim().toLowerCase();
|
|
881
|
-
if (raw === "off" || raw === "false" || raw === "0")
|
|
881
|
+
if (raw === "off" || raw === "false" || raw === "0" || raw === "no")
|
|
882
882
|
return "off";
|
|
883
883
|
if (raw === "strict" || raw === "on" || raw === "true" || raw === "1")
|
|
884
884
|
return "strict";
|
|
@@ -889,23 +889,105 @@ export function resolveSandboxMode(env, mode, logger) {
|
|
|
889
889
|
logger?.warn(`SWENY_SANDBOX="${raw}" is not one of off|auto|strict; using ${fallback}`);
|
|
890
890
|
return fallback;
|
|
891
891
|
}
|
|
892
|
-
/**
|
|
893
|
-
|
|
892
|
+
/**
|
|
893
|
+
* The fix-it hint for a sandbox start that failed on a user namespace. Ubuntu
|
|
894
|
+
* 23.10+ restricts unprivileged user namespaces (the sysctl), and Ubuntu 26.04
|
|
895
|
+
* also loads the `bwrap-userns-restrict` AppArmor profile, which denies every
|
|
896
|
+
* capability to bwrap's children, so the seccomp helper of
|
|
897
|
+
* @anthropic-ai/sandbox-runtime cannot nest its own user namespace
|
|
898
|
+
* (`apply-seccomp: write /proc/self/setgroups ... Permission denied`,
|
|
899
|
+
* anthropics/sandbox-runtime#429). Empty when `stderr` is not a namespace error.
|
|
900
|
+
*/
|
|
901
|
+
export function usernsHint(stderr) {
|
|
902
|
+
return /user namespace|userns|uid map|uid_map|setgroups|setting up uid|Operation not permitted|Permission denied/i.test(stderr)
|
|
903
|
+
? "; unprivileged user namespaces look restricted (Ubuntu 23.10+: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0; " +
|
|
904
|
+
"Ubuntu 26.04 also: sudo apparmor_parser -R /etc/apparmor.d/bwrap-userns-restrict)"
|
|
905
|
+
: "";
|
|
906
|
+
}
|
|
907
|
+
const BWRAP_PROBE = ["--ro-bind", "/", "/", "--dev", "/dev", "--proc", "/proc"];
|
|
908
|
+
/**
|
|
909
|
+
* Where the host probes look for `bwrap` and `unshare`. Never PATH: a probe runs
|
|
910
|
+
* a real binary, and a directory an agent can write may be on PATH.
|
|
911
|
+
*/
|
|
912
|
+
export const SYSTEM_BIN_DIRS = ["/usr/bin", "/bin", "/usr/local/bin", "/usr/sbin"];
|
|
913
|
+
/** The whole environment a probe runs with: no parent variables, no secrets. */
|
|
914
|
+
const PROBE_ENV = { PATH: "/usr/bin:/bin" };
|
|
915
|
+
/** Longest probe stderr echoed into a warning. */
|
|
916
|
+
const PROBE_ECHO_MAX = 200;
|
|
917
|
+
function isExecutableFile(p) {
|
|
894
918
|
try {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
919
|
+
if (!statSync(p).isFile())
|
|
920
|
+
return false;
|
|
921
|
+
accessSync(p, fsConstants.X_OK);
|
|
922
|
+
return true;
|
|
923
|
+
}
|
|
924
|
+
catch {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/** Absolute path of `name` in {@link SYSTEM_BIN_DIRS} (first match), never from PATH. */
|
|
929
|
+
export function systemBin(name, exists = isExecutableFile) {
|
|
930
|
+
for (const dir of SYSTEM_BIN_DIRS) {
|
|
931
|
+
const p = path.posix.join(dir, name);
|
|
932
|
+
if (exists(p))
|
|
933
|
+
return p;
|
|
900
934
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
935
|
+
return undefined;
|
|
936
|
+
}
|
|
937
|
+
const runProbe = (file, args) => {
|
|
938
|
+
const r = spawnSync(file, args, {
|
|
939
|
+
env: PROBE_ENV,
|
|
940
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
941
|
+
encoding: "utf8",
|
|
942
|
+
timeout: 10_000,
|
|
943
|
+
maxBuffer: 64 * 1024,
|
|
944
|
+
});
|
|
945
|
+
return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "", error: r.error };
|
|
946
|
+
};
|
|
947
|
+
/** One line of a probe's output for a warning: the first line, control characters masked, capped. */
|
|
948
|
+
export function probeLine(text) {
|
|
949
|
+
const line = text.trim().split(/\r?\n/)[0] ?? "";
|
|
950
|
+
return line.replace(/[\u0000-\u001f\u007f-\u009f]/g, "?").slice(0, PROBE_ECHO_MAX) || "unknown error";
|
|
951
|
+
}
|
|
952
|
+
function probeEcho(r) {
|
|
953
|
+
return probeLine(r.stderr.trim() || r.error?.message || (r.status === null ? "killed" : `exit ${r.status}`));
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Functional bubblewrap check: can it actually create a sandbox on this host?
|
|
957
|
+
* The same runs as the Action's `scripts/sandbox-userns.sh probe`: a plain
|
|
958
|
+
* `bwrap ... true`, then a user namespace nested inside bwrap, which the SDK
|
|
959
|
+
* sandbox's seccomp helper needs. On Ubuntu 26.04 the first passes and the
|
|
960
|
+
* second does not, so a plain probe would report a sandbox that then fails
|
|
961
|
+
* every command.
|
|
962
|
+
*
|
|
963
|
+
* `bwrap` and `unshare` come only from {@link SYSTEM_BIN_DIRS}, run by absolute
|
|
964
|
+
* path with {@link PROBE_ENV}, and at most one sanitized line of their stderr
|
|
965
|
+
* reaches the warning. The nested run is skipped (not failed) when there is no
|
|
966
|
+
* system `unshare` or it has no `--map-root-user` (busybox): an unusable probe
|
|
967
|
+
* tool is not a restricted host.
|
|
968
|
+
*/
|
|
969
|
+
export function bwrapWorks(run = runProbe, findBin = (name) => systemBin(name)) {
|
|
970
|
+
const ok = (r) => r.status === 0 && !r.error;
|
|
971
|
+
const bwrap = findBin("bwrap");
|
|
972
|
+
if (!bwrap) {
|
|
973
|
+
const dirs = SYSTEM_BIN_DIRS.slice(0, -1).join(", ") + " or " + SYSTEM_BIN_DIRS[SYSTEM_BIN_DIRS.length - 1];
|
|
974
|
+
return `bwrap is not installed in ${dirs} (the sandbox probe does not search PATH)`;
|
|
975
|
+
}
|
|
976
|
+
const plain = run(bwrap, [...BWRAP_PROBE, "true"]);
|
|
977
|
+
if (!ok(plain))
|
|
978
|
+
return `bwrap cannot create a sandbox (${probeEcho(plain)})${usernsHint(plain.stderr)}`;
|
|
979
|
+
const unshare = findBin("unshare");
|
|
980
|
+
if (!unshare)
|
|
981
|
+
return undefined;
|
|
982
|
+
const help = run(unshare, ["--help"]);
|
|
983
|
+
if (!`${help.stdout}\n${help.stderr}`.includes("--map-root-user"))
|
|
984
|
+
return undefined;
|
|
985
|
+
const nested = run(bwrap, [...BWRAP_PROBE, unshare, "--user", "--map-root-user", "true"]);
|
|
986
|
+
if (!ok(nested)) {
|
|
987
|
+
return (`bwrap cannot nest a user namespace, which the sandbox's seccomp helper needs (${probeEcho(nested)})` +
|
|
988
|
+
usernsHint("user namespace"));
|
|
908
989
|
}
|
|
990
|
+
return undefined;
|
|
909
991
|
}
|
|
910
992
|
/**
|
|
911
993
|
* Probe whether the SDK sandbox can start on this host. Returns a reason
|
|
@@ -54,4 +54,4 @@ export type FileConfig = Record<string, string | string[] | Record<string, unkno
|
|
|
54
54
|
*/
|
|
55
55
|
export declare function loadConfigFile(cwd?: string): FileConfig;
|
|
56
56
|
/** Starter config written by `sweny new`. */
|
|
57
|
-
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#\n# Decision model (optional) for nodes with route_by: decider. https://docs.sweny.ai/advanced/decision-models/\n# Local Ollama: set it here (a URL in this file must be loopback and never gets a key):\n# decider:\n# url: http://localhost:11434\n# model: nimble\n# Remote server: set it in the CI or shell environment only, never in this file or .env:\n# SWENY_DECIDER_URL=https://... SWENY_DECIDER_MODEL=... SWENY_DECIDER_API_KEY=...\n";
|
|
57
|
+
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# log-file: ./logs/errors.json # required when observability-provider is \"file\"\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\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#\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#\n# Decision model (optional) for nodes with route_by: decider. https://docs.sweny.ai/advanced/decision-models/\n# Local Ollama: set it here (a URL in this file must be loopback and never gets a key):\n# decider:\n# url: http://localhost:11434\n# model: nimble\n# Remote server: set it in the CI or shell environment only, never in this file or .env:\n# SWENY_DECIDER_URL=https://... SWENY_DECIDER_MODEL=... SWENY_DECIDER_API_KEY=...\n";
|
package/dist/cli/config-file.js
CHANGED
|
@@ -221,13 +221,8 @@ export const STARTER_CONFIG = `# .sweny.yml — SWEny project configuration
|
|
|
221
221
|
# pr-labels: agent,triage,needs-review
|
|
222
222
|
|
|
223
223
|
# ── Paths ─────────────────────────────────────────────────────────────
|
|
224
|
-
# service-map-path: .github/service-map.yml
|
|
225
224
|
# log-file: ./logs/errors.json # required when observability-provider is "file"
|
|
226
225
|
|
|
227
|
-
# ── Cache ─────────────────────────────────────────────────────────────
|
|
228
|
-
# cache-dir: .sweny/cache
|
|
229
|
-
# cache-ttl: 86400
|
|
230
|
-
|
|
231
226
|
# ── Run reporting ─────────────────────────────────────────────────────
|
|
232
227
|
# Opt-in and not currently available: token minting is not yet exposed, so
|
|
233
228
|
# leaving this unset means no reporting request is ever made.
|
|
@@ -256,7 +251,6 @@ export const STARTER_CONFIG = `# .sweny.yml — SWEny project configuration
|
|
|
256
251
|
# issue-tracker-provider: file
|
|
257
252
|
# source-control-provider: file
|
|
258
253
|
# notification-provider: file
|
|
259
|
-
# output-dir: .sweny/output
|
|
260
254
|
|
|
261
255
|
# ── Credentials (.env) ───────────────────────────────────────────────
|
|
262
256
|
# Copy the relevant block into your .env file and fill in the values.
|
|
@@ -279,8 +273,6 @@ export const STARTER_CONFIG = `# .sweny.yml — SWEny project configuration
|
|
|
279
273
|
#
|
|
280
274
|
# Linear (issue tracker) — https://linear.app/settings/api
|
|
281
275
|
# LINEAR_API_KEY=lin_api_...
|
|
282
|
-
# LINEAR_TEAM_ID=... # Settings > Workspace > Teams > [team] > copy ID from URL
|
|
283
|
-
# LINEAR_BUG_LABEL_ID=... # Settings > Labels > [label] > copy ID from URL
|
|
284
276
|
#
|
|
285
277
|
# Jira (issue tracker) — https://your-org.atlassian.net
|
|
286
278
|
# JIRA_BASE_URL=https://your-org.atlassian.net
|
package/dist/cli/config.d.ts
CHANGED
|
@@ -18,12 +18,6 @@ export interface CliConfig {
|
|
|
18
18
|
observabilityCredentials: Record<string, Record<string, string>>;
|
|
19
19
|
issueTrackerProvider: string;
|
|
20
20
|
linearApiKey: string;
|
|
21
|
-
linearTeamId: string;
|
|
22
|
-
linearBugLabelId: string;
|
|
23
|
-
linearTriageLabelId: string;
|
|
24
|
-
linearStateBacklog: string;
|
|
25
|
-
linearStateInProgress: string;
|
|
26
|
-
linearStatePeerReview: string;
|
|
27
21
|
timeRange: string;
|
|
28
22
|
severityFocus: string;
|
|
29
23
|
serviceFilter: string;
|
|
@@ -38,7 +32,6 @@ export interface CliConfig {
|
|
|
38
32
|
noveltyMode: boolean;
|
|
39
33
|
issueOverride: string;
|
|
40
34
|
additionalInstructions: string;
|
|
41
|
-
serviceMapPath: string;
|
|
42
35
|
githubToken: string;
|
|
43
36
|
botToken: string;
|
|
44
37
|
cloudToken: string;
|
|
@@ -48,7 +41,6 @@ export interface CliConfig {
|
|
|
48
41
|
jiraApiToken: string;
|
|
49
42
|
gitlabToken: string;
|
|
50
43
|
gitlabProjectId: string;
|
|
51
|
-
gitlabBaseUrl: string;
|
|
52
44
|
notificationProvider: string;
|
|
53
45
|
notificationWebhookUrl: string;
|
|
54
46
|
sendgridApiKey: string;
|
|
@@ -66,10 +58,6 @@ export interface CliConfig {
|
|
|
66
58
|
*/
|
|
67
59
|
verbose: boolean;
|
|
68
60
|
bell: boolean;
|
|
69
|
-
cacheDir: string;
|
|
70
|
-
cacheTtl: number;
|
|
71
|
-
noCache: boolean;
|
|
72
|
-
outputDir: string;
|
|
73
61
|
mcpServers: Record<string, McpServerConfig>;
|
|
74
62
|
workspaceTools: string[];
|
|
75
63
|
rules: string[];
|
|
@@ -118,17 +106,10 @@ export declare function normalizeSwenyAuth(raw: unknown): "auto" | "api-key" | "
|
|
|
118
106
|
* `0` and breaking downstream invariants.
|
|
119
107
|
*
|
|
120
108
|
* Why zero is rejected: every current caller (max-investigate-turns,
|
|
121
|
-
* max-implement-turns
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* use a sibling helper (parseNonNegativeInt) rather than weakening this
|
|
126
|
-
* one's contract.
|
|
109
|
+
* max-implement-turns) uses the result as a positive turn count. There is
|
|
110
|
+
* no "zero means disabled" semantics anywhere. If a future flag wants
|
|
111
|
+
* "0 = disabled", use a sibling helper (parseNonNegativeInt) rather than
|
|
112
|
+
* weakening this one's contract.
|
|
127
113
|
*/
|
|
128
114
|
export declare function parsePositiveInt(raw: unknown, fallback: number): number;
|
|
129
|
-
/**
|
|
130
|
-
* Returns non-fatal warnings about the configuration.
|
|
131
|
-
* These are surfaced before the spinner starts but do not abort the run.
|
|
132
|
-
*/
|
|
133
|
-
export declare function validateWarnings(config: Pick<CliConfig, "serviceMapPath">): string[];
|
|
134
115
|
export declare function registerImplementCommand(program: Command): Command;
|
package/dist/cli/config.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
2
|
import { hasCodexLogin } from "../agent-env.js";
|
|
4
3
|
import { parseAcpAgent, unsupportedAgentError } from "../harness/agents.js";
|
|
5
4
|
export function registerTriageCommand(program) {
|
|
@@ -18,8 +17,8 @@ export function registerTriageCommand(program) {
|
|
|
18
17
|
.option("--severity-focus <focus>", "Severity level focus (default: errors)")
|
|
19
18
|
.option("--service-filter <filter>", "Service filter pattern (default: *)")
|
|
20
19
|
.option("--investigation-depth <depth>", "Investigation depth (default: standard)")
|
|
21
|
-
.option("--max-investigate-turns <n>", "Max
|
|
22
|
-
.option("--max-implement-turns <n>", "Max
|
|
20
|
+
.option("--max-investigate-turns <n>", "Max coding agent turns for investigation (default: 50)")
|
|
21
|
+
.option("--max-implement-turns <n>", "Max coding agent turns for implementation (default: 30)")
|
|
23
22
|
.option("--base-branch <branch>", "Base branch for PRs (default: main)")
|
|
24
23
|
.option("--pr-labels <labels>", "Comma-separated PR labels (default: agent,triage,needs-review)")
|
|
25
24
|
.option("--dry-run", "Analyze only, do not create issues or PRs", false)
|
|
@@ -27,16 +26,9 @@ export function registerTriageCommand(program) {
|
|
|
27
26
|
.option("--review-mode <mode>", "PR merge behavior: auto (merge when CI passes) | review (human approval, default)", "review")
|
|
28
27
|
.option("--no-novelty-mode", "Disable novelty mode (allow +1 on existing issues)")
|
|
29
28
|
.option("--issue-override <issue>", "Work on a specific existing issue")
|
|
30
|
-
.option("--additional-instructions <text>", "Extra instructions for the
|
|
31
|
-
.option("--service-map-path <path>", "Path to service map YAML (default: .github/service-map.yml)")
|
|
29
|
+
.option("--additional-instructions <text>", "Extra instructions for the coding agent")
|
|
32
30
|
.option("--repository <owner/repo>", "Repository (auto-detected from git remote)")
|
|
33
|
-
.option("--linear-team-id <id>", "Linear team ID")
|
|
34
|
-
.option("--linear-bug-label-id <id>", "Linear bug label ID")
|
|
35
|
-
.option("--linear-triage-label-id <id>", "Linear triage label ID")
|
|
36
31
|
.option("--issue-labels <labels>", "Comma-separated extra labels applied to every agent-created issue (e.g. agent UUID for Linear, 'agent' for GitHub Issues)")
|
|
37
|
-
.option("--linear-state-backlog <name>", "Linear backlog state name")
|
|
38
|
-
.option("--linear-state-in-progress <name>", "Linear in-progress state name")
|
|
39
|
-
.option("--linear-state-peer-review <name>", "Linear peer-review state name")
|
|
40
32
|
.option("--log-file <path>", "Path to JSON log file (use with --observability-provider file)")
|
|
41
33
|
.option("--dd-site <site>", "Datadog site (default: datadoghq.com)")
|
|
42
34
|
.option("--sentry-org <org>", "Sentry organization slug")
|
|
@@ -56,15 +48,10 @@ export function registerTriageCommand(program) {
|
|
|
56
48
|
.option("--axiom-org-id <id>", "Axiom org ID (required for multi-org Axiom accounts)")
|
|
57
49
|
.option("--betterstack-source-id <id>", "Better Stack log source ID (used to auto-discover ClickHouse table)")
|
|
58
50
|
.option("--betterstack-table-name <name>", 'Better Stack ClickHouse table name, e.g. "t273774.my_source"')
|
|
59
|
-
.option("--gitlab-base-url <url>", "GitLab base URL (default: https://gitlab.com)")
|
|
60
51
|
.option("--json", "Output results as JSON", false)
|
|
61
52
|
.option("--stream", "Stream NDJSON events to stdout (for Studio / automation)", false)
|
|
62
53
|
.option("--verbose", "Print each tool call's input and output inline (human-readable, truncated). Use --stream for full untruncated NDJSON.", false)
|
|
63
54
|
.option("--bell", "Ring terminal bell on completion", false)
|
|
64
|
-
.option("--cache-dir <path>", "Step cache directory (default: .sweny/cache)")
|
|
65
|
-
.option("--cache-ttl <seconds>", "Cache TTL in seconds, 1 to 31536000 (default: 86400)")
|
|
66
|
-
.option("--no-cache", "Disable step cache")
|
|
67
|
-
.option("--output-dir <path>", "Output directory for file providers (default: .sweny/output)")
|
|
68
55
|
.option("--workspace-tools <tools>", "Comma-separated workspace tool integrations to enable (slack, notion, pagerduty, monday)")
|
|
69
56
|
.option("--offline", "Skip URL Sources and fail fast if any are referenced", false)
|
|
70
57
|
.option("--file-root [dir]", "Sandbox file: Sources to this directory tree (defaults to the working directory when the flag is given without a value)");
|
|
@@ -106,7 +93,7 @@ export function parseCliInputs(options, fileConfig = {}) {
|
|
|
106
93
|
anthropicApiKey: env.ANTHROPIC_API_KEY || "",
|
|
107
94
|
anthropicAuthToken: env.ANTHROPIC_AUTH_TOKEN || "",
|
|
108
95
|
claudeOauthToken: env.CLAUDE_CODE_OAUTH_TOKEN || "",
|
|
109
|
-
// Non-secret config: env or .sweny config file
|
|
96
|
+
// Non-secret config: env or .sweny config file
|
|
110
97
|
anthropicBaseUrl: env.ANTHROPIC_BASE_URL || f("anthropic-base-url") || "",
|
|
111
98
|
swenyAuth: normalizeSwenyAuth(env.SWENY_AUTH || f("sweny-auth")),
|
|
112
99
|
openaiApiKey: env.OPENAI_API_KEY || "",
|
|
@@ -116,12 +103,6 @@ export function parseCliInputs(options, fileConfig = {}) {
|
|
|
116
103
|
observabilityCredentials: obsCreds,
|
|
117
104
|
issueTrackerProvider: options.issueTrackerProvider || f("issue-tracker-provider") || "github-issues",
|
|
118
105
|
linearApiKey: env.LINEAR_API_KEY || "",
|
|
119
|
-
linearTeamId: options.linearTeamId || env.LINEAR_TEAM_ID || f("linear-team-id") || "",
|
|
120
|
-
linearBugLabelId: options.linearBugLabelId || env.LINEAR_BUG_LABEL_ID || f("linear-bug-label-id") || "",
|
|
121
|
-
linearTriageLabelId: options.linearTriageLabelId || env.LINEAR_TRIAGE_LABEL_ID || f("linear-triage-label-id") || "",
|
|
122
|
-
linearStateBacklog: options.linearStateBacklog || env.LINEAR_STATE_BACKLOG || f("linear-state-backlog") || "",
|
|
123
|
-
linearStateInProgress: options.linearStateInProgress || env.LINEAR_STATE_IN_PROGRESS || f("linear-state-in-progress") || "",
|
|
124
|
-
linearStatePeerReview: options.linearStatePeerReview || env.LINEAR_STATE_PEER_REVIEW || f("linear-state-peer-review") || "",
|
|
125
106
|
timeRange: options.timeRange || f("time-range") || "24h",
|
|
126
107
|
severityFocus: options.severityFocus || f("severity-focus") || "errors",
|
|
127
108
|
serviceFilter: options.serviceFilter || f("service-filter") || "*",
|
|
@@ -142,7 +123,6 @@ export function parseCliInputs(options, fileConfig = {}) {
|
|
|
142
123
|
noveltyMode: options.noveltyMode !== false,
|
|
143
124
|
issueOverride: options.issueOverride || "",
|
|
144
125
|
additionalInstructions: options.additionalInstructions || "",
|
|
145
|
-
serviceMapPath: options.serviceMapPath || f("service-map-path") || ".github/service-map.yml",
|
|
146
126
|
githubToken: env.GITHUB_TOKEN || "",
|
|
147
127
|
botToken: env.BOT_TOKEN || "",
|
|
148
128
|
cloudToken: (() => {
|
|
@@ -160,7 +140,6 @@ export function parseCliInputs(options, fileConfig = {}) {
|
|
|
160
140
|
jiraApiToken: env.JIRA_API_TOKEN || "",
|
|
161
141
|
gitlabToken: env.GITLAB_TOKEN || "",
|
|
162
142
|
gitlabProjectId: env.GITLAB_PROJECT_ID || "",
|
|
163
|
-
gitlabBaseUrl: options.gitlabBaseUrl || env.GITLAB_BASE_URL || f("gitlab-base-url") || "https://gitlab.com",
|
|
164
143
|
notificationProvider: options.notificationProvider || f("notification-provider") || "console",
|
|
165
144
|
// Secrets: env only
|
|
166
145
|
notificationWebhookUrl: env.NOTIFICATION_WEBHOOK_URL || "",
|
|
@@ -175,10 +154,6 @@ export function parseCliInputs(options, fileConfig = {}) {
|
|
|
175
154
|
stream: Boolean(options.stream),
|
|
176
155
|
verbose: Boolean(options.verbose),
|
|
177
156
|
bell: Boolean(options.bell),
|
|
178
|
-
cacheDir: options.cacheDir || env.SWENY_CACHE_DIR || f("cache-dir") || ".sweny/cache",
|
|
179
|
-
cacheTtl: parsePositiveInt(options.cacheTtl ?? f("cache-ttl"), 86400),
|
|
180
|
-
noCache: options.cache === false,
|
|
181
|
-
outputDir: options.outputDir || env.SWENY_OUTPUT_DIR || f("output-dir") || ".sweny/output",
|
|
182
157
|
mcpServers: parseMcpServers(env.SWENY_MCP_SERVERS || f("mcp-servers-json") || ""),
|
|
183
158
|
workspaceTools: (options.workspaceTools || env.SWENY_WORKSPACE_TOOLS || f("workspace-tools") || "")
|
|
184
159
|
.split(",")
|
|
@@ -379,8 +354,6 @@ export function validateInputs(config) {
|
|
|
379
354
|
case "linear":
|
|
380
355
|
if (!config.linearApiKey)
|
|
381
356
|
errors.push("Missing: LINEAR_API_KEY - find API keys at https://linear.app/settings/api");
|
|
382
|
-
if (!config.linearTeamId)
|
|
383
|
-
errors.push("Missing: LINEAR_TEAM_ID - find it in Linear > Settings > Workspace > Teams > [your team] > copy the ID from the URL");
|
|
384
357
|
break;
|
|
385
358
|
case "jira":
|
|
386
359
|
if (!config.jiraBaseUrl)
|
|
@@ -459,9 +432,6 @@ export function validateInputs(config) {
|
|
|
459
432
|
// bounds check let junk through).
|
|
460
433
|
validateIntegerBound(errors, "--max-investigate-turns", config.maxInvestigateTurns, 1, 500);
|
|
461
434
|
validateIntegerBound(errors, "--max-implement-turns", config.maxImplementTurns, 1, 500);
|
|
462
|
-
// cacheTtl is parsed with parsePositiveInt (junk → NaN); bound it so a
|
|
463
|
-
// malformed --cache-ttl / cache-ttl: surfaces a field error instead of NaN.
|
|
464
|
-
validateIntegerBound(errors, "--cache-ttl", config.cacheTtl, 1, 31_536_000);
|
|
465
435
|
return errors;
|
|
466
436
|
}
|
|
467
437
|
function validateIntegerBound(errors, flag, value, min, max) {
|
|
@@ -498,12 +468,10 @@ export function normalizeSwenyAuth(raw) {
|
|
|
498
468
|
* `0` and breaking downstream invariants.
|
|
499
469
|
*
|
|
500
470
|
* Why zero is rejected: every current caller (max-investigate-turns,
|
|
501
|
-
* max-implement-turns
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
* use a sibling helper (parseNonNegativeInt) rather than weakening this
|
|
506
|
-
* one's contract.
|
|
471
|
+
* max-implement-turns) uses the result as a positive turn count. There is
|
|
472
|
+
* no "zero means disabled" semantics anywhere. If a future flag wants
|
|
473
|
+
* "0 = disabled", use a sibling helper (parseNonNegativeInt) rather than
|
|
474
|
+
* weakening this one's contract.
|
|
507
475
|
*/
|
|
508
476
|
export function parsePositiveInt(raw, fallback) {
|
|
509
477
|
if (raw === undefined || raw === null || raw === "")
|
|
@@ -524,22 +492,6 @@ export function parsePositiveInt(raw, fallback) {
|
|
|
524
492
|
const truncated = Math.trunc(parsed);
|
|
525
493
|
return truncated >= 1 ? truncated : Number.NaN;
|
|
526
494
|
}
|
|
527
|
-
const DEFAULT_SERVICE_MAP_PATH = ".github/service-map.yml";
|
|
528
|
-
/**
|
|
529
|
-
* Returns non-fatal warnings about the configuration.
|
|
530
|
-
* These are surfaced before the spinner starts but do not abort the run.
|
|
531
|
-
*/
|
|
532
|
-
export function validateWarnings(config) {
|
|
533
|
-
const warnings = [];
|
|
534
|
-
// Warn when an explicitly configured serviceMapPath doesn't exist on disk.
|
|
535
|
-
// We skip the default path to avoid noise for users who never set it up.
|
|
536
|
-
if (config.serviceMapPath && config.serviceMapPath !== DEFAULT_SERVICE_MAP_PATH) {
|
|
537
|
-
if (!fs.existsSync(config.serviceMapPath)) {
|
|
538
|
-
warnings.push(`Service map file not found: "${config.serviceMapPath}". Cross-repo routing will be disabled.`);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return warnings;
|
|
542
|
-
}
|
|
543
495
|
function parseObservabilityCredentials(provider, options, fileConfig = {}) {
|
|
544
496
|
const env = process.env;
|
|
545
497
|
const f = (key) => {
|
|
@@ -675,10 +627,6 @@ export function registerImplementCommand(program) {
|
|
|
675
627
|
.option("--max-implement-turns <n>", "Max coding agent turns (default: 40)")
|
|
676
628
|
.option("--base-branch <branch>", "Base branch for PRs (default: main)")
|
|
677
629
|
.option("--repository <owner/repo>", "Repository (auto-detected from git remote)")
|
|
678
|
-
.option("--linear-team-id <id>", "Linear team ID")
|
|
679
|
-
.option("--linear-state-in-progress <name>", "Linear in-progress state name")
|
|
680
|
-
.option("--linear-state-peer-review <name>", "Linear peer-review state name")
|
|
681
|
-
.option("--output-dir <path>", "Output directory for file providers (default: .sweny/output)")
|
|
682
630
|
.option("--workspace-tools <tools>", "Comma-separated workspace tool integrations to enable (slack, notion, pagerduty, monday)")
|
|
683
631
|
.option("--review-mode <mode>", "PR merge behavior: auto (GitHub auto-merge when CI passes) | review (human approval, default)", "review")
|
|
684
632
|
.option("--additional-instructions <text>", "Extra instructions for the coding agent")
|
package/dist/cli/main.js
CHANGED
|
@@ -47,7 +47,7 @@ import { writeReceipt } from "./ticket.js";
|
|
|
47
47
|
import { createNodeProgress, toolCallsText } from "./progress.js";
|
|
48
48
|
import { applyNoColor, createPaint } from "./style.js";
|
|
49
49
|
import { colorEnabled, glyphsFor, richOutput, spinnerFramesFor, supportsUnicode } from "./terminal.js";
|
|
50
|
-
import { registerTriageCommand, registerImplementCommand, parseCliInputs, validateInputs,
|
|
50
|
+
import { registerTriageCommand, registerImplementCommand, parseCliInputs, validateInputs, parsePositiveInt, } from "./config.js";
|
|
51
51
|
import { c, formatBanner, getStepDetails, formatStepLine, formatDagResultHuman, formatDagResultMarkdown, writeResultJson, formatValidationErrors, formatCrashError, formatCheckResults, } from "./output.js";
|
|
52
52
|
import { checkProviderConnectivity, detectClaudeCodeLogin, discoverWorkflowSkillIds, validateCheckInputs, } from "./check.js";
|
|
53
53
|
import { registerSetupCommand } from "./setup.js";
|
|
@@ -262,10 +262,6 @@ triageCmd.action(async (options) => {
|
|
|
262
262
|
console.error(c.subtle("\n Run sweny triage --help for usage information.\n"));
|
|
263
263
|
process.exit(1);
|
|
264
264
|
}
|
|
265
|
-
// Non-fatal warnings (e.g. missing service map file)
|
|
266
|
-
for (const warning of validateWarnings(config)) {
|
|
267
|
-
console.warn(chalk.yellow(` \u26A0 ${warning}`));
|
|
268
|
-
}
|
|
269
265
|
// Banner
|
|
270
266
|
if (!config.json) {
|
|
271
267
|
console.log(formatBanner(config, version));
|
|
@@ -572,10 +568,6 @@ implementCmd.action(async (issueId, options) => {
|
|
|
572
568
|
maxImplementTurns: parsePositiveInt(options.maxImplementTurns ?? fileConfig["max-implement-turns"], 40),
|
|
573
569
|
baseBranch: options.baseBranch || fileConfig["base-branch"] || "main",
|
|
574
570
|
repository: options.repository || process.env.GITHUB_REPOSITORY || "",
|
|
575
|
-
outputDir: options.outputDir ||
|
|
576
|
-
process.env.SWENY_OUTPUT_DIR ||
|
|
577
|
-
fileConfig["output-dir"] ||
|
|
578
|
-
".sweny/output",
|
|
579
571
|
};
|
|
580
572
|
// Validate (parity with triage): surface the curated "Missing: ..." errors
|
|
581
573
|
// and reject malformed integer flags before any execution.
|
package/dist/cli/setup.js
CHANGED
|
@@ -165,24 +165,17 @@ export async function setupLinear(apiKey, teamId) {
|
|
|
165
165
|
const bugLabel = byName.get("bug") ?? (await ensure(BUG_LABEL));
|
|
166
166
|
const bugId = bugLabel.id;
|
|
167
167
|
// ── Print config snippet ─────────────────────────────────────────────────
|
|
168
|
-
const triageId = workTypeIds["triage"] ?? byName.get("triage")?.id ?? "";
|
|
169
168
|
console.log(`
|
|
170
169
|
${chalk.bold(" Done.")} ${created.length} created, ${skipped.length} already existed.
|
|
171
170
|
|
|
172
171
|
${chalk.bold(" Add these to your .sweny.yml:")}
|
|
173
172
|
|
|
174
173
|
${chalk.cyan(` issue-tracker-provider: linear
|
|
175
|
-
linear-team-id: ${teamId}
|
|
176
|
-
linear-triage-label-id: ${triageId}
|
|
177
|
-
linear-bug-label-id: ${bugId}
|
|
178
174
|
issue-labels: ${agentLabel.id}`)}
|
|
179
175
|
|
|
180
176
|
${chalk.bold(" Or as environment variables / GitHub Actions secrets:")}
|
|
181
177
|
|
|
182
|
-
${chalk.cyan(`
|
|
183
|
-
LINEAR_TRIAGE_LABEL_ID=${triageId}
|
|
184
|
-
LINEAR_BUG_LABEL_ID=${bugId}
|
|
185
|
-
SWENY_ISSUE_LABELS=${agentLabel.id}`)}
|
|
178
|
+
${chalk.cyan(` SWENY_ISSUE_LABELS=${agentLabel.id}`)}
|
|
186
179
|
|
|
187
180
|
${chalk.dim(" Full label inventory:")}
|
|
188
181
|
${chalk.dim(` agent ${agentLabel.id}`)}
|
package/dist/harness/agents.js
CHANGED
|
@@ -17,6 +17,7 @@ export function isSupportedAgent(id) {
|
|
|
17
17
|
}
|
|
18
18
|
/** The error for an agent id with no adapter. */
|
|
19
19
|
export function unsupportedAgentError(id) {
|
|
20
|
-
return (`Unsupported coding agent "${id}": supported agents are "claude" (headless Claude Code) ` +
|
|
21
|
-
`
|
|
20
|
+
return (`Unsupported coding agent "${id}": supported agents are "claude" (headless Claude Code), ` +
|
|
21
|
+
`"codex" (Codex CLI), "pi" (pi coding agent, experimental), or "acp:<command>" (any Agent Client Protocol agent, ` +
|
|
22
|
+
`experimental, for example "acp:opencode acp"). Remove --agent / coding-agent-provider or set it to one of them.`);
|
|
22
23
|
}
|
|
@@ -276,4 +276,4 @@ export type ClaudeClient = ClaudeCodeHarness;
|
|
|
276
276
|
*
|
|
277
277
|
* @internal Exported for the tool bridge parity test (#414).
|
|
278
278
|
*/
|
|
279
|
-
export declare function coreToolToSdkTool(coreTool: Tool, defaultCtx: ToolContext): import("@anthropic-ai/claude-agent-sdk").SdkMcpToolDefinition<Record<string, import("zod").ZodType<
|
|
279
|
+
export declare function coreToolToSdkTool(coreTool: Tool, defaultCtx: ToolContext): import("@anthropic-ai/claude-agent-sdk").SdkMcpToolDefinition<Record<string, import("zod").ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>>>;
|
|
@@ -35,7 +35,9 @@
|
|
|
35
35
|
* socat for step 1: install it with `npm i -g @anthropic-ai/sandbox-runtime`, or
|
|
36
36
|
* point `SWENY_SRT_PATH` at the binary. Linux also needs bubblewrap, socat and
|
|
37
37
|
* ripgrep, and unprivileged user namespaces (Ubuntu 23.10+:
|
|
38
|
-
* `sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
|
38
|
+
* `sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0`; Ubuntu 26.04
|
|
39
|
+
* also `sudo apparmor_parser -R /etc/apparmor.d/bwrap-userns-restrict`, see
|
|
40
|
+
* {@link usernsHint}).
|
|
39
41
|
*
|
|
40
42
|
* Modes are the step 1 modes, unchanged (`SWENY_SANDBOX`, default `auto` in CI
|
|
41
43
|
* and `off` locally): `off` never wraps; `auto` wraps when a wrapper is
|