@sweny-ai/core 0.1.125 → 0.1.126

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.
@@ -58,11 +58,48 @@ export interface BuildAgentEnvOpts {
58
58
  export declare function resolveEnvScope(env: Record<string, string | undefined>, explicit?: boolean, logger?: Pick<Logger, "warn">): boolean;
59
59
  /** Max names listed in the withheld-vars warning before "and N more". */
60
60
  export declare const WITHHELD_WARNING_CAP = 30;
61
+ /**
62
+ * Runner baseline: exact names set by common CI images (GitHub-hosted runner
63
+ * image, Actions runner context, Debian-based containers). They are present on
64
+ * every run, so listing them as "withheld" is noise. Names only; none of these
65
+ * is a credential the operator supplied. Secrets such as `GITHUB_TOKEN`,
66
+ * `NPM_TOKEN`, or `AWS_*` are deliberately absent, so withholding them still
67
+ * warns.
68
+ */
69
+ export declare const RUNNER_BASELINE_VARS: ReadonlySet<string>;
70
+ /**
71
+ * Runner baseline: name patterns for families the CI images set in bulk
72
+ * (Actions runner internals, Android/Java/.NET/Go/Python toolchains, browsers
73
+ * and webdrivers, Azure CLI, vcpkg, ghcup, ...). `AWS_*`, `NPM_*` and
74
+ * `GITHUB_TOKEN`-style names are intentionally not matched.
75
+ */
76
+ export declare const RUNNER_BASELINE_PATTERNS: readonly RegExp[];
77
+ /** True when `name` is set by the CI image itself, not by the workflow author. */
78
+ export declare function isRunnerBaselineVar(name: string): boolean;
79
+ /** Split withheld names into the runner baseline and everything else. */
80
+ export declare function classifyWithheld(withheld: readonly string[]): {
81
+ baseline: string[];
82
+ other: string[];
83
+ };
61
84
  /**
62
85
  * One-line warning naming the withheld variables. Names only, never values;
63
86
  * sorted; capped at {@link WITHHELD_WARNING_CAP}.
64
87
  */
65
88
  export declare function formatWithheldWarning(withheld: readonly string[]): string;
89
+ /** Plain-log summary: counts only, no names. */
90
+ export declare function formatScopeSummary(total: number, baseline: number): string;
91
+ /** Test seam: forget that the withheld notice was already emitted. */
92
+ export declare function resetWithheldReport(): void;
93
+ /**
94
+ * Report withheld env names, once per process (not per node or per client).
95
+ *
96
+ * - always: one plain `info` line with counts (not a `::warning::` annotation);
97
+ * - only when non-baseline names were withheld: a `warn` listing just those
98
+ * (capped), as a `::warning title=SWEny agent env::` annotation under GitHub
99
+ * Actions;
100
+ * - `debug` (shown with `--verbose`): every withheld name.
101
+ */
102
+ export declare function reportWithheldEnv(withheld: readonly string[], logger: Pick<Logger, "info" | "warn" | "debug">, env?: Record<string, string | undefined>): void;
66
103
  /**
67
104
  * Scope the agent env and report which variable names were withheld.
68
105
  * `withheld` holds names only; values never leave `source`.
package/dist/agent-env.js CHANGED
@@ -159,6 +159,100 @@ export function resolveEnvScope(env, explicit, logger) {
159
159
  }
160
160
  /** Max names listed in the withheld-vars warning before "and N more". */
161
161
  export const WITHHELD_WARNING_CAP = 30;
162
+ /**
163
+ * Runner baseline: exact names set by common CI images (GitHub-hosted runner
164
+ * image, Actions runner context, Debian-based containers). They are present on
165
+ * every run, so listing them as "withheld" is noise. Names only; none of these
166
+ * is a credential the operator supplied. Secrets such as `GITHUB_TOKEN`,
167
+ * `NPM_TOKEN`, or `AWS_*` are deliberately absent, so withholding them still
168
+ * warns.
169
+ */
170
+ export const RUNNER_BASELINE_VARS = new Set([
171
+ // image / OS
172
+ "ImageOS",
173
+ "ImageVersion",
174
+ "ACCEPT_EULA",
175
+ "DEBIAN_FRONTEND",
176
+ "AGENT_TOOLSDIRECTORY",
177
+ "CONDA",
178
+ "SWIFT_PATH",
179
+ "LEIN_HOME",
180
+ "LEIN_JAR",
181
+ "ANT_HOME",
182
+ "GRADLE_HOME",
183
+ "M2_HOME",
184
+ "SELENIUM_JAR_PATH",
185
+ "ENABLE_RUNNER_TRACING",
186
+ "INVOCATION_ID",
187
+ "JOURNAL_STREAM",
188
+ "SYSTEMD_EXEC_PID",
189
+ "MANAGERPID",
190
+ "OLDPWD",
191
+ "PWD",
192
+ "SHLVL",
193
+ "_",
194
+ // GitHub Actions context (never GITHUB_TOKEN)
195
+ "GITHUB_ACTION",
196
+ "GITHUB_ACTION_PATH",
197
+ "GITHUB_ACTION_REF",
198
+ "GITHUB_ACTION_REPOSITORY",
199
+ "GITHUB_ACTOR",
200
+ "GITHUB_ACTOR_ID",
201
+ "GITHUB_ENV",
202
+ "GITHUB_EVENT_PATH",
203
+ "GITHUB_GRAPHQL_URL",
204
+ "GITHUB_JOB",
205
+ "GITHUB_OUTPUT",
206
+ "GITHUB_PATH",
207
+ "GITHUB_REF_PROTECTED",
208
+ "GITHUB_REF_TYPE",
209
+ "GITHUB_REPOSITORY_ID",
210
+ "GITHUB_REPOSITORY_OWNER_ID",
211
+ "GITHUB_RETENTION_DAYS",
212
+ "GITHUB_RUN_ATTEMPT",
213
+ "GITHUB_STATE",
214
+ "GITHUB_STEP_SUMMARY",
215
+ "GITHUB_TRIGGERING_ACTOR",
216
+ "GITHUB_WORKFLOW",
217
+ "GITHUB_WORKFLOW_REF",
218
+ "GITHUB_WORKFLOW_SHA",
219
+ ]);
220
+ /**
221
+ * Runner baseline: name patterns for families the CI images set in bulk
222
+ * (Actions runner internals, Android/Java/.NET/Go/Python toolchains, browsers
223
+ * and webdrivers, Azure CLI, vcpkg, ghcup, ...). `AWS_*`, `NPM_*` and
224
+ * `GITHUB_TOKEN`-style names are intentionally not matched.
225
+ */
226
+ export const RUNNER_BASELINE_PATTERNS = [
227
+ /^ACTIONS_/,
228
+ /^RUNNER_/,
229
+ /^ANDROID_/,
230
+ /^JAVA_HOME(_|$)/,
231
+ /^CHROME/,
232
+ /WEBDRIVER$/,
233
+ /^DOTNET_/,
234
+ /^GOROOT(_|$)/,
235
+ /^PIPX_/,
236
+ /^POWERSHELL_/,
237
+ /^VCPKG_/,
238
+ /^GHCUP_/,
239
+ /^BOOTSTRAP_HASKELL_/,
240
+ /^AZURE_(EXTENSION_DIR|HTTP_USER_AGENT|CONFIG_DIR)$/,
241
+ /^HOMEBREW_/,
242
+ /^STATS_/,
243
+ ];
244
+ /** True when `name` is set by the CI image itself, not by the workflow author. */
245
+ export function isRunnerBaselineVar(name) {
246
+ return RUNNER_BASELINE_VARS.has(name) || RUNNER_BASELINE_PATTERNS.some((re) => re.test(name));
247
+ }
248
+ /** Split withheld names into the runner baseline and everything else. */
249
+ export function classifyWithheld(withheld) {
250
+ const baseline = [];
251
+ const other = [];
252
+ for (const n of withheld)
253
+ (isRunnerBaselineVar(n) ? baseline : other).push(n);
254
+ return { baseline: baseline.sort(), other: other.sort() };
255
+ }
162
256
  /**
163
257
  * One-line warning naming the withheld variables. Names only, never values;
164
258
  * sorted; capped at {@link WITHHELD_WARNING_CAP}.
@@ -171,6 +265,37 @@ export function formatWithheldWarning(withheld) {
171
265
  `If a node's commands need any of them, add the names to env-passthrough ` +
172
266
  `(SWENY_ENV_PASSTHROUGH), or set env-scope: off (SWENY_ENV_SCOPE=off).`);
173
267
  }
268
+ /** Plain-log summary: counts only, no names. */
269
+ export function formatScopeSummary(total, baseline) {
270
+ return (`sweny: agent env scoped (${total} withheld, ${baseline} from the CI image). ` +
271
+ `Add names to env-passthrough if a node needs them; --verbose lists them.`);
272
+ }
273
+ let withheldReported = false;
274
+ /** Test seam: forget that the withheld notice was already emitted. */
275
+ export function resetWithheldReport() {
276
+ withheldReported = false;
277
+ }
278
+ /**
279
+ * Report withheld env names, once per process (not per node or per client).
280
+ *
281
+ * - always: one plain `info` line with counts (not a `::warning::` annotation);
282
+ * - only when non-baseline names were withheld: a `warn` listing just those
283
+ * (capped), as a `::warning title=SWEny agent env::` annotation under GitHub
284
+ * Actions;
285
+ * - `debug` (shown with `--verbose`): every withheld name.
286
+ */
287
+ export function reportWithheldEnv(withheld, logger, env = process.env) {
288
+ if (withheld.length === 0 || withheldReported)
289
+ return;
290
+ withheldReported = true;
291
+ const { baseline, other } = classifyWithheld(withheld);
292
+ logger.info(formatScopeSummary(withheld.length, baseline.length));
293
+ if (other.length > 0) {
294
+ const prefix = env.GITHUB_ACTIONS === "true" ? "::warning title=SWEny agent env::" : "";
295
+ logger.warn(`${prefix}${formatWithheldWarning(other)}`);
296
+ }
297
+ logger.debug(`sweny: agent env withheld: ${[...withheld].sort().join(", ")}`);
298
+ }
174
299
  /**
175
300
  * Scope the agent env and report which variable names were withheld.
176
301
  * `withheld` holds names only; values never leave `source`.
package/dist/claude.d.ts CHANGED
@@ -129,7 +129,6 @@ export declare class ClaudeClient implements Claude {
129
129
  private sandboxProbe;
130
130
  private sandboxWarned;
131
131
  private envScope;
132
- private envWarned;
133
132
  constructor(opts?: ClaudeClientOptions);
134
133
  /**
135
134
  * Build env for the Claude Code subprocess (#360). Applies auth precedence
@@ -137,8 +136,8 @@ export declare class ClaudeClient implements Claude {
137
136
  * `SWENY_AUTH`). When env scoping is on (default in CI, see
138
137
  * {@link resolveEnvScope}) the result is narrowed to the allowlist plus
139
138
  * `extraVars` (the node's declared skill env vars) and the operator
140
- * passthrough list, and the withheld names (never values) are warned once
141
- * per client. When off, the full env passes through as before.
139
+ * passthrough list, and the withheld names (never values) are reported once
140
+ * per process ({@link reportWithheldEnv}). When off, the full env passes through as before.
142
141
  */
143
142
  private buildEnv;
144
143
  run(opts: {
package/dist/claude.js CHANGED
@@ -11,7 +11,7 @@
11
11
  import { query, createSdkMcpServer, tool as sdkTool } from "@anthropic-ai/claude-agent-sdk";
12
12
  import { z } from "zod";
13
13
  import { consoleLogger } from "./types.js";
14
- import { formatWithheldWarning, parseList, resolveEnvScope, scopeAgentEnv, resolveAgentSandbox, } from "./agent-env.js";
14
+ import { reportWithheldEnv, parseList, resolveEnvScope, scopeAgentEnv, resolveAgentSandbox, } from "./agent-env.js";
15
15
  import { fenceUntrustedJson } from "./untrusted.js";
16
16
  const SYSTEM_PROMPT = `You are a step in an automated workflow. Execute the instruction precisely using the tools available to you. Be thorough but concise. When you're done, summarize your findings and results.`;
17
17
  /**
@@ -217,7 +217,6 @@ export class ClaudeClient {
217
217
  sandboxProbe;
218
218
  sandboxWarned = false;
219
219
  envScope;
220
- envWarned = false;
221
220
  constructor(opts = {}) {
222
221
  this.model = opts.model;
223
222
  this.maxTurns = opts.maxTurns ?? 20;
@@ -237,8 +236,8 @@ export class ClaudeClient {
237
236
  * `SWENY_AUTH`). When env scoping is on (default in CI, see
238
237
  * {@link resolveEnvScope}) the result is narrowed to the allowlist plus
239
238
  * `extraVars` (the node's declared skill env vars) and the operator
240
- * passthrough list, and the withheld names (never values) are warned once
241
- * per client. When off, the full env passes through as before.
239
+ * passthrough list, and the withheld names (never values) are reported once
240
+ * per process ({@link reportWithheldEnv}). When off, the full env passes through as before.
242
241
  */
243
242
  buildEnv(extraVars = []) {
244
243
  const full = Object.fromEntries(Object.entries(process.env).filter((e) => e[1] != null));
@@ -250,11 +249,7 @@ export class ClaudeClient {
250
249
  passthrough: this.envPassthrough ?? parseList(process.env.SWENY_ENV_PASSTHROUGH),
251
250
  logger: this.logger,
252
251
  });
253
- if (withheld.length > 0 && !this.envWarned) {
254
- this.envWarned = true;
255
- const prefix = process.env.GITHUB_ACTIONS === "true" ? "::warning title=SWEny agent env::" : "";
256
- this.logger.warn(`${prefix}${formatWithheldWarning(withheld)}`);
257
- }
252
+ reportWithheldEnv(withheld, this.logger);
258
253
  return env;
259
254
  }
260
255
  async run(opts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.125",
3
+ "version": "0.1.126",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"