agency-lang 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/lib/agents/agency-agent/agent.agency +169 -28
  2. package/dist/lib/agents/agency-agent/agent.js +579 -152
  3. package/dist/lib/agents/agency-agent/lib/attachments.js +2 -2
  4. package/dist/lib/agents/agency-agent/lib/config.agency +5 -0
  5. package/dist/lib/agents/agency-agent/lib/config.js +11 -0
  6. package/dist/lib/agents/agency-agent/lib/defaultPolicy.agency +106 -0
  7. package/dist/lib/agents/agency-agent/lib/defaultPolicy.js +764 -3
  8. package/dist/lib/agents/agency-agent/lib/modelFilters.js +1 -1
  9. package/dist/lib/agents/agency-agent/lib/resolution.js +4 -4
  10. package/dist/lib/agents/agency-agent/lib/search.js +3 -3
  11. package/dist/lib/agents/agency-agent/lib/settings.js +1 -1
  12. package/dist/lib/agents/agency-agent/lib/utils.js +1 -1
  13. package/dist/lib/agents/agency-agent/shared.agency +22 -0
  14. package/dist/lib/agents/agency-agent/shared.js +160 -3
  15. package/dist/lib/agents/agency-agent/subagents/code.js +1 -1
  16. package/dist/lib/agents/agency-agent/subagents/explorer.js +1 -1
  17. package/dist/lib/agents/agency-agent/subagents/oracle.js +1 -1
  18. package/dist/lib/agents/agency-agent/subagents/research.js +1 -1
  19. package/dist/lib/agents/agency-agent/subagents/review.js +4 -4
  20. package/dist/lib/agents/agency-agent/tests/agentTurn.js +1 -1
  21. package/dist/lib/agents/agency-agent/tests/attachments.js +1 -1
  22. package/dist/lib/agents/agency-agent/tests/attachmentsTurn.js +2 -2
  23. package/dist/lib/agents/agency-agent/tests/gitPolicy.js +3 -3
  24. package/dist/lib/agents/agency-agent/tests/oneShotRounds.agency +20 -0
  25. package/dist/lib/agents/agency-agent/tests/oneShotRounds.js +555 -0
  26. package/dist/lib/agents/agency-agent/tests/oneShotRounds.test.json +9 -0
  27. package/dist/lib/agents/review/agent.js +3 -3
  28. package/dist/lib/cli/doctor.d.ts +8 -0
  29. package/dist/lib/cli/doctor.js +21 -13
  30. package/dist/lib/cli/doctor.test.js +34 -0
  31. package/dist/lib/cli/help.js +1 -5
  32. package/dist/lib/cli/runBundledAgent.d.ts +17 -1
  33. package/dist/lib/cli/runBundledAgent.js +73 -2
  34. package/dist/lib/cli/runBundledAgent.test.d.ts +1 -0
  35. package/dist/lib/cli/runBundledAgent.test.js +96 -0
  36. package/dist/lib/config.d.ts +42 -33
  37. package/dist/lib/config.js +108 -6
  38. package/dist/lib/config.test.js +121 -2
  39. package/dist/lib/ir/prettyPrint.js +25 -4
  40. package/dist/lib/parseCache.js +8 -13
  41. package/dist/lib/parseCache.test.js +5 -8
  42. package/dist/lib/parser.js +5 -12
  43. package/dist/lib/preprocessors/typescriptPreprocessor.d.ts +0 -28
  44. package/dist/lib/preprocessors/typescriptPreprocessor.js +0 -245
  45. package/dist/lib/runtime/configOverrides.d.ts +17 -16
  46. package/dist/lib/runtime/configOverrides.js +27 -16
  47. package/dist/lib/runtime/configOverrides.test.js +116 -2
  48. package/dist/lib/runtime/prompt.js +7 -3
  49. package/dist/lib/runtime/runner.d.ts +2 -2
  50. package/dist/lib/runtime/runner.js +26 -7
  51. package/dist/lib/runtime/state/context.js +9 -2
  52. package/dist/lib/stdlib/llm.d.ts +1 -0
  53. package/dist/lib/stdlib/version.d.ts +1 -1
  54. package/dist/lib/stdlib/version.js +1 -1
  55. package/dist/scripts/agency.d.ts +2 -0
  56. package/dist/scripts/agency.js +55 -12
  57. package/dist/scripts/agency.test.js +73 -1
  58. package/package.json +1 -1
  59. package/stdlib/agency/eval.js +1 -1
  60. package/stdlib/agency.js +1 -1
  61. package/stdlib/agent.agency +9 -9
  62. package/stdlib/agent.js +5 -5
  63. package/stdlib/data/tech/hackernews.js +1 -1
  64. package/stdlib/index.js +15 -15
  65. package/stdlib/llm.agency +2 -1
  66. package/stdlib/llm.js +3 -3
  67. package/stdlib/markdown.js +1 -1
  68. package/stdlib/memory.js +2 -2
  69. package/stdlib/object.js +5 -5
  70. package/stdlib/policy.agency +21 -7
  71. package/stdlib/policy.js +45 -16
  72. package/stdlib/skills.js +1 -1
  73. package/stdlib/strategy.js +3 -3
  74. package/stdlib/thread.js +3 -3
  75. package/stdlib/ui/chart.js +2 -2
  76. package/stdlib/ui/cli.agency +1 -1
  77. package/stdlib/ui/cli.js +1 -1
  78. package/stdlib/ui/layout.js +3 -3
  79. package/stdlib/ui/table.js +2 -2
  80. package/stdlib/ui.js +3 -3
  81. package/dist/lib/agents/docs/cli/cli/agent.md +0 -16
  82. package/dist/lib/agents/docs/cli/cli/compile.md +0 -21
  83. package/dist/lib/agents/docs/cli/cli/coverage.md +0 -112
  84. package/dist/lib/agents/docs/cli/cli/debug.md +0 -41
  85. package/dist/lib/agents/docs/cli/cli/doc.md +0 -98
  86. package/dist/lib/agents/docs/cli/cli/editor-integration.md +0 -44
  87. package/dist/lib/agents/docs/cli/cli/eval-judge.md +0 -72
  88. package/dist/lib/agents/docs/cli/cli/eval.md +0 -194
  89. package/dist/lib/agents/docs/cli/cli/format.md +0 -23
  90. package/dist/lib/agents/docs/cli/cli/index.md +0 -7
  91. package/dist/lib/agents/docs/cli/cli/local.md +0 -92
  92. package/dist/lib/agents/docs/cli/cli/models.md +0 -63
  93. package/dist/lib/agents/docs/cli/cli/optimize.md +0 -338
  94. package/dist/lib/agents/docs/cli/cli/pack.md +0 -56
  95. package/dist/lib/agents/docs/cli/cli/policy.md +0 -23
  96. package/dist/lib/agents/docs/cli/cli/preprocess-and-ast.md +0 -21
  97. package/dist/lib/agents/docs/cli/cli/review.md +0 -16
  98. package/dist/lib/agents/docs/cli/cli/run.md +0 -79
  99. package/dist/lib/agents/docs/cli/cli/schedule.md +0 -89
  100. package/dist/lib/agents/docs/cli/cli/serve.md +0 -52
  101. package/dist/lib/agents/docs/cli/cli/test.md +0 -44
  102. package/dist/lib/agents/docs/cli/cli/trace-and-bundle.md +0 -75
  103. package/dist/lib/agents/docs/cli/cli/typecheck.md +0 -19
  104. package/dist/lib/agents/docs/guide/guide/advanced-types.md +0 -26
  105. package/dist/lib/agents/docs/guide/guide/agency-config-file.md +0 -212
  106. package/dist/lib/agents/docs/guide/guide/agency-packages.md +0 -54
  107. package/dist/lib/agents/docs/guide/guide/agency-stdlib.md +0 -14
  108. package/dist/lib/agents/docs/guide/guide/agency-vs-typescript.md +0 -33
  109. package/dist/lib/agents/docs/guide/guide/attachments.md +0 -56
  110. package/dist/lib/agents/docs/guide/guide/basic-syntax.md +0 -350
  111. package/dist/lib/agents/docs/guide/guide/blocks.md +0 -134
  112. package/dist/lib/agents/docs/guide/guide/build-integration.md +0 -99
  113. package/dist/lib/agents/docs/guide/guide/builtins.md +0 -84
  114. package/dist/lib/agents/docs/guide/guide/callbacks.md +0 -169
  115. package/dist/lib/agents/docs/guide/guide/checkpointing.md +0 -101
  116. package/dist/lib/agents/docs/guide/guide/cli-args.md +0 -190
  117. package/dist/lib/agents/docs/guide/guide/common-functions.md +0 -36
  118. package/dist/lib/agents/docs/guide/guide/compiling-and-running.md +0 -62
  119. package/dist/lib/agents/docs/guide/guide/concurrency.md +0 -108
  120. package/dist/lib/agents/docs/guide/guide/cross-thread-context.md +0 -226
  121. package/dist/lib/agents/docs/guide/guide/custom-providers.md +0 -195
  122. package/dist/lib/agents/docs/guide/guide/debugging.md +0 -67
  123. package/dist/lib/agents/docs/guide/guide/developer-tools.md +0 -90
  124. package/dist/lib/agents/docs/guide/guide/effects-and-raises.md +0 -137
  125. package/dist/lib/agents/docs/guide/guide/effects.md +0 -100
  126. package/dist/lib/agents/docs/guide/guide/error-handling.md +0 -161
  127. package/dist/lib/agents/docs/guide/guide/exercise-1.md +0 -32
  128. package/dist/lib/agents/docs/guide/guide/functions.md +0 -94
  129. package/dist/lib/agents/docs/guide/guide/getting-started.md +0 -35
  130. package/dist/lib/agents/docs/guide/guide/global-var-initialization.md +0 -70
  131. package/dist/lib/agents/docs/guide/guide/global-vs-static.md +0 -76
  132. package/dist/lib/agents/docs/guide/guide/guards.md +0 -134
  133. package/dist/lib/agents/docs/guide/guide/handlers.md +0 -196
  134. package/dist/lib/agents/docs/guide/guide/image-generation.md +0 -113
  135. package/dist/lib/agents/docs/guide/guide/imports-and-packages.md +0 -100
  136. package/dist/lib/agents/docs/guide/guide/interrupts-from-typescript.md +0 -84
  137. package/dist/lib/agents/docs/guide/guide/interrupts-part-2.md +0 -54
  138. package/dist/lib/agents/docs/guide/guide/interrupts.md +0 -139
  139. package/dist/lib/agents/docs/guide/guide/llm-part-2.md +0 -111
  140. package/dist/lib/agents/docs/guide/guide/llm.md +0 -109
  141. package/dist/lib/agents/docs/guide/guide/mcp.md +0 -295
  142. package/dist/lib/agents/docs/guide/guide/memory.md +0 -401
  143. package/dist/lib/agents/docs/guide/guide/message-threads.md +0 -111
  144. package/dist/lib/agents/docs/guide/guide/nodes.md +0 -50
  145. package/dist/lib/agents/docs/guide/guide/observability.md +0 -99
  146. package/dist/lib/agents/docs/guide/guide/partial-application.md +0 -96
  147. package/dist/lib/agents/docs/guide/guide/pattern-matching.md +0 -366
  148. package/dist/lib/agents/docs/guide/guide/policies.md +0 -67
  149. package/dist/lib/agents/docs/guide/guide/schema-parameter-injection.md +0 -101
  150. package/dist/lib/agents/docs/guide/guide/schemas.md +0 -99
  151. package/dist/lib/agents/docs/guide/guide/serving.md +0 -165
  152. package/dist/lib/agents/docs/guide/guide/state-isolation.md +0 -139
  153. package/dist/lib/agents/docs/guide/guide/streaming.md +0 -28
  154. package/dist/lib/agents/docs/guide/guide/tags.md +0 -72
  155. package/dist/lib/agents/docs/guide/guide/testing.md +0 -187
  156. package/dist/lib/agents/docs/guide/guide/troubleshooting.md +0 -86
  157. package/dist/lib/agents/docs/guide/guide/ts-helpers.md +0 -559
  158. package/dist/lib/agents/docs/guide/guide/ts-interop.md +0 -72
  159. package/dist/lib/agents/docs/guide/guide/type-validation.md +0 -187
  160. package/dist/lib/agents/docs/guide/guide/types.md +0 -181
  161. package/dist/lib/agents/docs/guide/guide/unused.md +0 -178
  162. package/dist/lib/agents/docs/guide/guide/unused2.md +0 -258
  163. package/dist/lib/agents/docs/guide/guide/value-parameterized-types.md +0 -186
  164. package/dist/lib/agents/docs/guide/guide/vscode-extension.md +0 -33
  165. package/dist/lib/agents/docs/guide/guide/why-agency.md +0 -323
  166. package/dist/lib/preprocessors/typescriptPreprocessor.config.test.js +0 -327
  167. package/stdlib/browser.js +0 -421
  168. package/stdlib/chart.js +0 -983
  169. package/stdlib/cli.js +0 -1116
  170. package/stdlib/email.js +0 -1104
  171. package/stdlib/imessage.js +0 -377
  172. package/stdlib/keyring.js +0 -803
  173. package/stdlib/layout.js +0 -3657
  174. package/stdlib/oauth.js +0 -862
  175. package/stdlib/schemas.js +0 -223
  176. package/stdlib/search.js +0 -629
  177. package/stdlib/sms.js +0 -419
  178. package/stdlib/table.js +0 -1419
  179. package/stdlib/threads.js +0 -1041
  180. package/stdlib/types.js +0 -222
  181. package/stdlib/validators.js +0 -1606
  182. /package/dist/lib/{preprocessors/typescriptPreprocessor.config.test.d.ts → cli/doctor.test.d.ts} +0 -0
@@ -27,11 +27,7 @@ Flags:
27
27
  Config File (agency.json):
28
28
  {
29
29
  "verbose": false, Enable verbose logging by default
30
- "outDir": "./dist", Default output directory for compiled files
31
- "excludeNodeTypes": ["comment"], Node types to exclude from code generation
32
- "excludeBuiltinFunctions": ["write"], Builtin functions to exclude
33
- "allowedFetchDomains": ["api.example.com"], Whitelist for fetch domains
34
- "disallowedFetchDomains": ["blocked.com"] Blacklist for fetch domains
30
+ "outDir": "./dist" Default output directory for compiled files
35
31
  }
36
32
 
37
33
  Examples:
@@ -1,2 +1,18 @@
1
1
  import { AgencyConfig } from "../config.js";
2
- export declare function runBundledAgent(config: AgencyConfig, agentName: string, args?: string[]): void;
2
+ import { spawn as realSpawn } from "child_process";
3
+ /**
4
+ * Extract the debug flags (`--trace`, `--log-file`) from a bundled agent's
5
+ * forwarded argv, as a config override. This is the ONE place these flags are
6
+ * handled — every bundled agent gets them for free by going through
7
+ * `runBundledAgent`; no agent writes flag code.
8
+ *
9
+ * Tokenization is `node:util.parseArgs` (which `std::args` is itself built on)
10
+ * after the same bare-flag normalization std::args applies, so `--flag=value`,
11
+ * the `--` terminator, last-wins-on-repeat, AND a following-flag-is-not-a-value
12
+ * all match the agent's own parser. An empty/bare `--trace` maps to a per-run
13
+ * trace file in cwd; the flag→config meaning lives in `applyCliFlags`.
14
+ */
15
+ export declare function agentConfigOverride(args: string[]): Partial<AgencyConfig>;
16
+ export declare function runBundledAgent(config: AgencyConfig, agentName: string, args?: string[], deps?: {
17
+ spawn?: typeof realSpawn;
18
+ }): void;
@@ -1,9 +1,72 @@
1
+ import { applyCliFlags, CONFIG_OVERRIDES_ENV, serializeConfigOverrides, } from "../config.js";
1
2
  import { compile, compiledOutputNodeArgs } from "./commands.js";
2
- import { spawn } from "child_process";
3
+ import { spawn as realSpawn } from "child_process";
3
4
  import * as fs from "fs";
4
5
  import * as path from "path";
6
+ import { parseArgs } from "node:util";
5
7
  const currentDir = path.dirname(new URL(import.meta.url).pathname);
6
- export function runBundledAgent(config, agentName, args = []) {
8
+ // The debug flags every bundled agent understands. Same names the agent
9
+ // declares (for --help) and that `agency run` accepts.
10
+ const AGENT_DEBUG_FLAGS = ["--trace", "--log-file"];
11
+ /**
12
+ * Rewrite a bare debug flag to its empty attached form (`--trace` → `--trace=`)
13
+ * when its next token is absent or starts with `-`. This replicates exactly
14
+ * what `std::args` does before parsing (lib/stdlib/args.ts:499-505), so the
15
+ * agent's own parser and this pre-scan agree: `--trace --print` and `--trace
16
+ * -p` are BOTH bare here, not a trace file named "--print" / "-p". Stops at the
17
+ * `--` terminator (everything after is positional).
18
+ */
19
+ function normalizeBareDebugFlags(args) {
20
+ const out = [];
21
+ for (let i = 0; i < args.length; i++) {
22
+ const token = args[i];
23
+ if (token === "--") {
24
+ out.push(...args.slice(i));
25
+ break;
26
+ }
27
+ if (AGENT_DEBUG_FLAGS.includes(token)) {
28
+ const next = args[i + 1];
29
+ const bare = next === undefined || next === "--" || next.startsWith("-");
30
+ out.push(bare ? `${token}=` : token);
31
+ }
32
+ else {
33
+ out.push(token);
34
+ }
35
+ }
36
+ return out;
37
+ }
38
+ /**
39
+ * Extract the debug flags (`--trace`, `--log-file`) from a bundled agent's
40
+ * forwarded argv, as a config override. This is the ONE place these flags are
41
+ * handled — every bundled agent gets them for free by going through
42
+ * `runBundledAgent`; no agent writes flag code.
43
+ *
44
+ * Tokenization is `node:util.parseArgs` (which `std::args` is itself built on)
45
+ * after the same bare-flag normalization std::args applies, so `--flag=value`,
46
+ * the `--` terminator, last-wins-on-repeat, AND a following-flag-is-not-a-value
47
+ * all match the agent's own parser. An empty/bare `--trace` maps to a per-run
48
+ * trace file in cwd; the flag→config meaning lives in `applyCliFlags`.
49
+ */
50
+ export function agentConfigOverride(args) {
51
+ const { values } = parseArgs({
52
+ args: normalizeBareDebugFlags(args),
53
+ options: { trace: { type: "string" }, "log-file": { type: "string" } },
54
+ strict: false,
55
+ allowPositionals: true,
56
+ });
57
+ const flags = {};
58
+ // After normalization a present --trace is always a string ("" when bare).
59
+ if (typeof values.trace === "string") {
60
+ flags.trace = values.trace;
61
+ }
62
+ // --log-file requires a value; a bare (now "") --log-file is ignored.
63
+ if (typeof values["log-file"] === "string" && values["log-file"] !== "") {
64
+ flags.logFile = values["log-file"];
65
+ }
66
+ return applyCliFlags({}, flags);
67
+ }
68
+ export function runBundledAgent(config, agentName, args = [], deps = {}) {
69
+ const spawn = deps.spawn ?? realSpawn;
7
70
  const agentDir = path.resolve(currentDir, `../agents/${agentName}`);
8
71
  const agencyFile = path.join(agentDir, "agent.agency");
9
72
  const precompiledFile = path.join(agentDir, "agent.js");
@@ -21,9 +84,17 @@ export function runBundledAgent(config, agentName, args = []) {
21
84
  console.error(`Failed to compile agent ${agentName}.`);
22
85
  process.exit(1);
23
86
  }
87
+ const overrides = agentConfigOverride(args);
88
+ const env = Object.keys(overrides).length > 0
89
+ ? {
90
+ ...process.env,
91
+ [CONFIG_OVERRIDES_ENV]: serializeConfigOverrides(overrides),
92
+ }
93
+ : process.env;
24
94
  const nodeProcess = spawn(process.execPath, [...compiledOutputNodeArgs(), runFile, ...args], {
25
95
  stdio: "inherit",
26
96
  shell: false,
97
+ env,
27
98
  });
28
99
  nodeProcess.on("error", (error) => {
29
100
  console.error(`Failed to run ${agentName}:`, error);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,96 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ // Force the precompiled-file branch so runBundledAgent never invokes the
3
+ // compiler; spawn is injected, so nothing actually launches.
4
+ vi.mock("fs", async (importOriginal) => {
5
+ const actual = await importOriginal();
6
+ return { ...actual, existsSync: () => true };
7
+ });
8
+ import { CONFIG_OVERRIDES_ENV } from "../config.js";
9
+ import { agentConfigOverride, runBundledAgent } from "./runBundledAgent.js";
10
+ describe("agentConfigOverride", () => {
11
+ it("--trace <file> → traceFile", () => {
12
+ expect(agentConfigOverride(["--trace", "out.agencytrace"])).toEqual({
13
+ trace: true,
14
+ traceFile: "out.agencytrace",
15
+ });
16
+ });
17
+ it("--trace=<file> attached form", () => {
18
+ expect(agentConfigOverride(["--trace=x.trace"])).toEqual({
19
+ trace: true,
20
+ traceFile: "x.trace",
21
+ });
22
+ });
23
+ it("bare --trace → per-run dir (traceDir)", () => {
24
+ expect(agentConfigOverride(["--trace"])).toEqual({ trace: true, traceDir: "." });
25
+ });
26
+ it("empty --trace= behaves identically to bare --trace (the divergence bug)", () => {
27
+ expect(agentConfigOverride(["--trace="])).toEqual({ trace: true, traceDir: "." });
28
+ });
29
+ it("a following flag is NOT consumed as the --trace value (matches std::args)", () => {
30
+ // Both are bare on the agent side, so they must be bare here too — not a
31
+ // trace file named "--print" / "-p".
32
+ expect(agentConfigOverride(["--trace", "--print", "hi"])).toEqual({
33
+ trace: true,
34
+ traceDir: ".",
35
+ });
36
+ expect(agentConfigOverride(["--trace", "-p"])).toEqual({ trace: true, traceDir: "." });
37
+ });
38
+ it("bare --log-file (or one followed by a flag) is ignored, not a file named --print", () => {
39
+ expect(agentConfigOverride(["--log-file"])).toEqual({});
40
+ expect(agentConfigOverride(["--log-file", "--print"])).toEqual({});
41
+ });
42
+ it("--log-file <path> → log.logFile + observability, space and attached forms", () => {
43
+ const expected = { log: { logFile: "l.jsonl" }, observability: true };
44
+ expect(agentConfigOverride(["--log-file", "l.jsonl"])).toEqual(expected);
45
+ expect(agentConfigOverride(["--log-file=l.jsonl"])).toEqual(expected);
46
+ });
47
+ it("stops at the -- terminator", () => {
48
+ expect(agentConfigOverride(["--", "--trace", "x"])).toEqual({});
49
+ });
50
+ it("last --trace wins on repeats", () => {
51
+ expect(agentConfigOverride(["--trace", "a", "--trace", "b"])).toEqual({
52
+ trace: true,
53
+ traceFile: "b",
54
+ });
55
+ });
56
+ it("combines trace + log and ignores the agent's own flags/positionals", () => {
57
+ expect(agentConfigOverride(["--model", "gpt", "hi", "--trace", "t", "--log-file", "l"])).toEqual({
58
+ trace: true,
59
+ traceFile: "t",
60
+ log: { logFile: "l" },
61
+ observability: true,
62
+ });
63
+ });
64
+ it("empty when neither flag is present", () => {
65
+ expect(agentConfigOverride(["--print", "do it"])).toEqual({});
66
+ });
67
+ });
68
+ describe("runBundledAgent passes config overrides to the child via env", () => {
69
+ afterEach(() => {
70
+ vi.restoreAllMocks();
71
+ });
72
+ it("serializes the override into AGENCY_CONFIG_OVERRIDES", () => {
73
+ const onMock = vi.fn();
74
+ const spawnMock = vi.fn((..._args) => ({ on: onMock }));
75
+ runBundledAgent({}, "agency-agent", ["--trace", "t.trace", "--log-file", "l.jsonl"], {
76
+ spawn: spawnMock,
77
+ });
78
+ expect(spawnMock).toHaveBeenCalledTimes(1);
79
+ const opts = spawnMock.mock.calls[0][2];
80
+ const overrides = JSON.parse(opts.env[CONFIG_OVERRIDES_ENV]);
81
+ expect(overrides).toEqual({
82
+ trace: true,
83
+ traceFile: "t.trace",
84
+ log: { logFile: "l.jsonl" },
85
+ observability: true,
86
+ });
87
+ // Parent env is preserved (spread), so PATH survives.
88
+ expect(opts.env.PATH).toBe(process.env.PATH);
89
+ });
90
+ it("does not set the env var when no debug flags are present", () => {
91
+ const spawnMock = vi.fn((..._args) => ({ on: vi.fn() }));
92
+ runBundledAgent({}, "agency-agent", ["--print", "hi"], { spawn: spawnMock });
93
+ const opts = spawnMock.mock.calls[0][2];
94
+ expect(opts.env[CONFIG_OVERRIDES_ENV]).toBeUndefined();
95
+ });
96
+ });
@@ -24,34 +24,6 @@ export interface AgencyConfig {
24
24
  verbose?: boolean;
25
25
  logLevel?: LogLevel;
26
26
  outDir?: string;
27
- /**
28
- * Array of AST node types to exclude from code generation
29
- * Example: ["comment", "typeAlias"]
30
- */
31
- excludeNodeTypes?: string[];
32
- /**
33
- * Array of built-in function names to exclude from code generation
34
- * Example: ["fetch", "write"]
35
- */
36
- excludeBuiltinFunctions?: string[];
37
- /**
38
- * Array of domains allowed for fetch operations
39
- * If specified, only these domains can be fetched
40
- * Example: ["api.example.com", "data.mysite.com"]
41
- */
42
- allowedFetchDomains?: string[];
43
- /**
44
- * Array of domains disallowed for fetch operations
45
- * These domains will throw an error if fetch is attempted
46
- * If both allowed and disallowed are set, takes the intersection
47
- * (only allowed domains, minus disallowed ones)
48
- * Example: ["malicious.com", "blocked.site.com"]
49
- */
50
- disallowedFetchDomains?: string[];
51
- /**
52
- * Optionally specify a custom host for tarsec trace collection
53
- */
54
- tarsecTraceHost?: string;
55
27
  /**
56
28
  * Number of times the LLM can go back and forth between calling tools
57
29
  * and responding to their outputs before halting execution to prevent infinite loops.
@@ -339,11 +311,6 @@ export declare const AgencyConfigSchema: z.ZodObject<{
339
311
  error: "error";
340
312
  }>>;
341
313
  outDir: z.ZodOptional<z.ZodString>;
342
- excludeNodeTypes: z.ZodOptional<z.ZodArray<z.ZodString>>;
343
- excludeBuiltinFunctions: z.ZodOptional<z.ZodArray<z.ZodString>>;
344
- allowedFetchDomains: z.ZodOptional<z.ZodArray<z.ZodString>>;
345
- disallowedFetchDomains: z.ZodOptional<z.ZodArray<z.ZodString>>;
346
- tarsecTraceHost: z.ZodOptional<z.ZodString>;
347
314
  maxToolCallRounds: z.ZodOptional<z.ZodNumber>;
348
315
  observability: z.ZodOptional<z.ZodBoolean>;
349
316
  log: z.ZodOptional<z.ZodObject<{
@@ -507,3 +474,45 @@ export declare function loadConfigSafe(configPath: string): {
507
474
  * Returns the directory containing agency.json, or null if not found.
508
475
  */
509
476
  export declare function findProjectRoot(startPath: string): string | null;
477
+ /** Per-invocation flags accepted by `agency run`/`compile` and forwarded to the
478
+ * bundled agents. Mapped onto AgencyConfig by applyCliFlags. `trace` is
479
+ * `string` for `--trace <file>`, `true` for a bare `--trace`. */
480
+ export type CliFlags = {
481
+ trace?: string | true;
482
+ logFile?: string;
483
+ observability?: boolean;
484
+ strict?: boolean;
485
+ maxToolCallRounds?: number;
486
+ maxToolResultChars?: number;
487
+ };
488
+ /**
489
+ * Fold per-invocation CLI flags onto a config COPY (never mutates the input).
490
+ * The single definition of what each debug flag means:
491
+ * --trace <file> → trace + traceFile=<file>
492
+ * --trace (bare) → trace + traceFile=<input>.trace when an input path is
493
+ * known (agency run), else traceDir="." (a bundled agent
494
+ * with no input file → a per-run file in cwd)
495
+ * --log-file <p> → log.logFile=<p> and observability=true
496
+ * --observability → observability=true
497
+ * --strict → typechecker.strict + strictTypes (the compile-path gate
498
+ * never runs the checker on strictTypes alone)
499
+ * --max-tool-call-rounds <n> → maxToolCallRounds=<n> (baked into runPrompt at
500
+ * compile time; overrides agency.json for this run)
501
+ * --max-tool-result-chars <n> → client.maxToolResultChars=<n> (0 disables the
502
+ * cap; overrides agency.json for this run)
503
+ */
504
+ export declare function applyCliFlags(config: AgencyConfig, flags: CliFlags, input?: string): AgencyConfig;
505
+ /** The one env var carrying a JSON Partial<AgencyConfig> into an already-compiled
506
+ * process (see the source-of-truth note above, source 3). */
507
+ export declare const CONFIG_OVERRIDES_ENV = "AGENCY_CONFIG_OVERRIDES";
508
+ /** Serialize config overrides for a child process's AGENCY_CONFIG_OVERRIDES. */
509
+ export declare function serializeConfigOverrides(overrides: Partial<AgencyConfig>): string;
510
+ /** Read + validate AGENCY_CONFIG_OVERRIDES. Returns {} when the var is absent,
511
+ * unparseable, or fails schema validation, so a malformed value can never
512
+ * brick startup. */
513
+ export declare function readConfigOverrides(env?: NodeJS.ProcessEnv): Partial<AgencyConfig>;
514
+ /** Return a deep copy of `config` with secret-bearing fields masked, for
515
+ * human-facing output (`agency config show`). Masks every `apiKey` — the
516
+ * top-level `log.apiKey` string and each key under `client.apiKey` /
517
+ * `client.statelog.apiKey` — to `•••<last4>`. */
518
+ export declare function redactConfigSecrets(config: AgencyConfig): AgencyConfig;
@@ -29,12 +29,10 @@ export const AgencyConfigSchema = z
29
29
  verbose: z.boolean(),
30
30
  logLevel: z.enum(["debug", "info", "warn", "error"]),
31
31
  outDir: z.string(),
32
- excludeNodeTypes: z.array(z.string()),
33
- excludeBuiltinFunctions: z.array(z.string()),
34
- allowedFetchDomains: z.array(z.string()),
35
- disallowedFetchDomains: z.array(z.string()),
36
- tarsecTraceHost: z.string(),
37
- maxToolCallRounds: z.number(),
32
+ // Positive integer: the generated code does `maxToolCallRounds || 10`, so a
33
+ // 0 would silently mean 10 and a float/negative is meaningless — reject at
34
+ // load rather than surprise. Mirrors maxCallDepth.
35
+ maxToolCallRounds: z.number().int().positive(),
38
36
  observability: z.boolean(),
39
37
  log: z
40
38
  .object({
@@ -228,3 +226,107 @@ export function findProjectRoot(startPath) {
228
226
  current = parent;
229
227
  }
230
228
  }
229
+ /**
230
+ * Fold per-invocation CLI flags onto a config COPY (never mutates the input).
231
+ * The single definition of what each debug flag means:
232
+ * --trace <file> → trace + traceFile=<file>
233
+ * --trace (bare) → trace + traceFile=<input>.trace when an input path is
234
+ * known (agency run), else traceDir="." (a bundled agent
235
+ * with no input file → a per-run file in cwd)
236
+ * --log-file <p> → log.logFile=<p> and observability=true
237
+ * --observability → observability=true
238
+ * --strict → typechecker.strict + strictTypes (the compile-path gate
239
+ * never runs the checker on strictTypes alone)
240
+ * --max-tool-call-rounds <n> → maxToolCallRounds=<n> (baked into runPrompt at
241
+ * compile time; overrides agency.json for this run)
242
+ * --max-tool-result-chars <n> → client.maxToolResultChars=<n> (0 disables the
243
+ * cap; overrides agency.json for this run)
244
+ */
245
+ export function applyCliFlags(config, flags, input) {
246
+ const next = { ...config };
247
+ if (flags.trace !== undefined) {
248
+ next.trace = true;
249
+ const explicitFile = typeof flags.trace === "string" && flags.trace !== "";
250
+ if (explicitFile) {
251
+ next.traceFile = flags.trace;
252
+ }
253
+ else if (input) {
254
+ next.traceFile = input.replace(/\.agency$/, ".trace");
255
+ }
256
+ else {
257
+ next.traceDir = ".";
258
+ }
259
+ }
260
+ if (flags.logFile) {
261
+ next.log = { ...next.log, logFile: flags.logFile };
262
+ next.observability = true;
263
+ }
264
+ if (flags.observability) {
265
+ next.observability = true;
266
+ }
267
+ if (flags.strict) {
268
+ next.typechecker = { ...next.typechecker, strict: true, strictTypes: true };
269
+ }
270
+ if (flags.maxToolCallRounds !== undefined) {
271
+ next.maxToolCallRounds = flags.maxToolCallRounds;
272
+ }
273
+ if (flags.maxToolResultChars !== undefined) {
274
+ next.client = { ...next.client, maxToolResultChars: flags.maxToolResultChars };
275
+ }
276
+ return next;
277
+ }
278
+ /** The one env var carrying a JSON Partial<AgencyConfig> into an already-compiled
279
+ * process (see the source-of-truth note above, source 3). */
280
+ export const CONFIG_OVERRIDES_ENV = "AGENCY_CONFIG_OVERRIDES";
281
+ /** Serialize config overrides for a child process's AGENCY_CONFIG_OVERRIDES. */
282
+ export function serializeConfigOverrides(overrides) {
283
+ return JSON.stringify(overrides);
284
+ }
285
+ /** Read + validate AGENCY_CONFIG_OVERRIDES. Returns {} when the var is absent,
286
+ * unparseable, or fails schema validation, so a malformed value can never
287
+ * brick startup. */
288
+ export function readConfigOverrides(env = process.env) {
289
+ const raw = env[CONFIG_OVERRIDES_ENV];
290
+ if (!raw)
291
+ return {};
292
+ try {
293
+ const result = AgencyConfigSchema.safeParse(JSON.parse(raw));
294
+ if (!result.success) {
295
+ // Don't brick startup, but never fail silently — a typo'd override that
296
+ // makes --trace/--log-file quietly do nothing is the worst failure mode.
297
+ console.error(`Ignoring invalid ${CONFIG_OVERRIDES_ENV}: ${result.error.issues
298
+ .map((i) => i.path.join("."))
299
+ .join(", ")}`);
300
+ return {};
301
+ }
302
+ return result.data;
303
+ }
304
+ catch (err) {
305
+ console.error(`Ignoring unparseable ${CONFIG_OVERRIDES_ENV}: ${err instanceof Error ? err.message : String(err)}`);
306
+ return {};
307
+ }
308
+ }
309
+ /** Return a deep copy of `config` with secret-bearing fields masked, for
310
+ * human-facing output (`agency config show`). Masks every `apiKey` — the
311
+ * top-level `log.apiKey` string and each key under `client.apiKey` /
312
+ * `client.statelog.apiKey` — to `•••<last4>`. */
313
+ export function redactConfigSecrets(config) {
314
+ const mask = (value) => value.length <= 4 ? "•••" : `•••${value.slice(-4)}`;
315
+ const clone = JSON.parse(JSON.stringify(config));
316
+ const redactKeyMap = (obj) => {
317
+ if (!obj)
318
+ return;
319
+ for (const key of Object.keys(obj)) {
320
+ if (typeof obj[key] === "string")
321
+ obj[key] = mask(obj[key]);
322
+ }
323
+ };
324
+ if (clone.log && typeof clone.log.apiKey === "string") {
325
+ clone.log.apiKey = mask(clone.log.apiKey);
326
+ }
327
+ redactKeyMap(clone.client?.apiKey);
328
+ if (clone.client?.statelog && typeof clone.client.statelog.apiKey === "string") {
329
+ clone.client.statelog.apiKey = mask(clone.client.statelog.apiKey);
330
+ }
331
+ return clone;
332
+ }
@@ -1,5 +1,8 @@
1
- import { describe, it, expect } from "vitest";
2
- import { AgencyConfigSchema } from "./config.js";
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import * as fs from "fs";
3
+ import * as os from "os";
4
+ import * as path from "path";
5
+ import { AgencyConfigSchema, applyCliFlags, CONFIG_OVERRIDES_ENV, loadConfigSafe, readConfigOverrides, redactConfigSecrets, serializeConfigOverrides, } from "./config.js";
3
6
  describe("AgencyConfigSchema", () => {
4
7
  it("should accept an empty config", () => {
5
8
  const result = AgencyConfigSchema.safeParse({});
@@ -79,6 +82,16 @@ describe("AgencyConfig maxCallDepth key", () => {
79
82
  expect(AgencyConfigSchema.safeParse({ maxCallDepth: 12.5 }).success).toBe(false);
80
83
  });
81
84
  });
85
+ describe("AgencyConfig maxToolCallRounds key", () => {
86
+ it("accepts a positive integer", () => {
87
+ expect(AgencyConfigSchema.safeParse({ maxToolCallRounds: 20 }).success).toBe(true);
88
+ });
89
+ it("rejects 0, negative, and non-integer (0 would silently mean 10 via `|| 10`)", () => {
90
+ expect(AgencyConfigSchema.safeParse({ maxToolCallRounds: 0 }).success).toBe(false);
91
+ expect(AgencyConfigSchema.safeParse({ maxToolCallRounds: -1 }).success).toBe(false);
92
+ expect(AgencyConfigSchema.safeParse({ maxToolCallRounds: 1.5 }).success).toBe(false);
93
+ });
94
+ });
82
95
  describe("AgencyConfig typechecker key", () => {
83
96
  it("accepts the new typechecker object", () => {
84
97
  const result = AgencyConfigSchema.safeParse({
@@ -102,3 +115,109 @@ describe("AgencyConfig typechecker key", () => {
102
115
  expect(result.success).toBe(true);
103
116
  });
104
117
  });
118
+ describe("loadConfigSafe — removed options are ignored, not rejected", () => {
119
+ it("loads an agency.json with the removed keys, and a stale wrong-typed key, without error", () => {
120
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "cfg-removed-"));
121
+ const cfgPath = path.join(dir, "agency.json");
122
+ fs.writeFileSync(cfgPath, JSON.stringify({
123
+ excludeNodeTypes: "oops-was-string-array", // stale + wrong type: Zod error before removal, ignored after
124
+ excludeBuiltinFunctions: ["write"],
125
+ allowedFetchDomains: ["api.example.com"],
126
+ disallowedFetchDomains: ["blocked.com"],
127
+ outDir: "./dist",
128
+ }));
129
+ const { config, error } = loadConfigSafe(cfgPath);
130
+ expect(error).toBeUndefined();
131
+ expect(config.outDir).toBe("./dist");
132
+ fs.rmSync(dir, { recursive: true, force: true });
133
+ });
134
+ });
135
+ describe("applyCliFlags", () => {
136
+ it("--trace <file> sets trace + traceFile", () => {
137
+ const out = applyCliFlags({}, { trace: "out.trace" });
138
+ expect(out.trace).toBe(true);
139
+ expect(out.traceFile).toBe("out.trace");
140
+ expect(out.traceDir).toBeUndefined();
141
+ });
142
+ it("bare --trace with an input derives <input>.trace (agency run)", () => {
143
+ const out = applyCliFlags({}, { trace: true }, "prog.agency");
144
+ expect(out.traceFile).toBe("prog.trace");
145
+ });
146
+ it("bare or empty --trace with no input uses traceDir='.' (bundled agent)", () => {
147
+ expect(applyCliFlags({}, { trace: true }).traceDir).toBe(".");
148
+ // Empty attached (--trace=) must behave like bare, not set traceFile="".
149
+ expect(applyCliFlags({}, { trace: "" }).traceDir).toBe(".");
150
+ expect(applyCliFlags({}, { trace: "" }).traceFile).toBeUndefined();
151
+ });
152
+ it("--log-file sets log.logFile and enables observability, preserving log.host", () => {
153
+ const out = applyCliFlags({ log: { host: "https://h" } }, { logFile: "x" });
154
+ expect(out.log).toEqual({ host: "https://h", logFile: "x" });
155
+ expect(out.observability).toBe(true);
156
+ });
157
+ it("--strict sets strict AND strictTypes", () => {
158
+ expect(applyCliFlags({}, { strict: true }).typechecker).toEqual({
159
+ strict: true,
160
+ strictTypes: true,
161
+ });
162
+ });
163
+ it("--max-tool-call-rounds sets the top-level maxToolCallRounds", () => {
164
+ expect(applyCliFlags({}, { maxToolCallRounds: 20 }).maxToolCallRounds).toBe(20);
165
+ });
166
+ it("--max-tool-result-chars sets client.maxToolResultChars, preserving other client fields", () => {
167
+ const out = applyCliFlags({ client: { defaultModel: "gpt" } }, { maxToolResultChars: 50000 });
168
+ expect(out.client).toEqual({ defaultModel: "gpt", maxToolResultChars: 50000 });
169
+ });
170
+ it("keeps maxToolResultChars=0 (0 disables the cap, not falsy-skipped)", () => {
171
+ expect(applyCliFlags({}, { maxToolResultChars: 0 }).client?.maxToolResultChars).toBe(0);
172
+ });
173
+ it("does not mutate the input config", () => {
174
+ const input = {};
175
+ applyCliFlags(input, { strict: true, logFile: "x", trace: "t", maxToolCallRounds: 5 });
176
+ expect(input).toEqual({});
177
+ });
178
+ });
179
+ describe("config overrides env round-trip", () => {
180
+ it("serialize → read yields the same Partial<AgencyConfig>", () => {
181
+ const overrides = { trace: true, traceDir: ".", observability: true, log: { logFile: "l.jsonl" } };
182
+ const env = { [CONFIG_OVERRIDES_ENV]: serializeConfigOverrides(overrides) };
183
+ expect(readConfigOverrides(env)).toEqual(overrides);
184
+ });
185
+ it("returns {} for an absent value without warning", () => {
186
+ const spy = vi.spyOn(console, "error").mockImplementation(() => { });
187
+ expect(readConfigOverrides({})).toEqual({});
188
+ expect(spy).not.toHaveBeenCalled();
189
+ spy.mockRestore();
190
+ });
191
+ it("returns {} and warns (never throws) for unparseable JSON", () => {
192
+ const spy = vi.spyOn(console, "error").mockImplementation(() => { });
193
+ expect(readConfigOverrides({ [CONFIG_OVERRIDES_ENV]: "not json" })).toEqual({});
194
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining("unparseable"));
195
+ spy.mockRestore();
196
+ });
197
+ it("returns {} and warns for a schema-violating field", () => {
198
+ const spy = vi.spyOn(console, "error").mockImplementation(() => { });
199
+ expect(readConfigOverrides({ [CONFIG_OVERRIDES_ENV]: '{"maxCallDepth":-1}' })).toEqual({});
200
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining("invalid"));
201
+ spy.mockRestore();
202
+ });
203
+ });
204
+ describe("redactConfigSecrets", () => {
205
+ it("masks log.apiKey and client.apiKey.* while keeping other fields", () => {
206
+ const redacted = redactConfigSecrets({
207
+ outDir: "./dist",
208
+ log: { host: "https://h", apiKey: "sk-secret-1234" },
209
+ client: { defaultModel: "gpt", apiKey: { openAi: "sk-openai-abcd", anthropic: "x" } },
210
+ });
211
+ expect(redacted.outDir).toBe("./dist");
212
+ expect(redacted.log?.host).toBe("https://h");
213
+ expect(redacted.log?.apiKey).toBe("•••1234");
214
+ expect(redacted.client?.apiKey?.openAi).toBe("•••abcd");
215
+ expect(redacted.client?.apiKey?.anthropic).toBe("•••"); // <=4 chars fully masked
216
+ expect(redacted.client?.defaultModel).toBe("gpt");
217
+ });
218
+ it("does not mutate the input", () => {
219
+ const input = { log: { apiKey: "sk-secret-1234" } };
220
+ redactConfigSecrets(input);
221
+ expect(input.log.apiKey).toBe("sk-secret-1234");
222
+ });
223
+ });
@@ -326,8 +326,16 @@ export function printTs(node, indent = 0) {
326
326
  // Always pass an opts object so Runner.thread has a single,
327
327
  // uniform signature: legacy callers see `{}` and the
328
328
  // hook-firing path can treat opts as required.
329
- const optsArg = optsParts.length === 0 ? "{}" : `{ ${optsParts.join(", ")} }`;
330
- return `await runner.thread(${node.id}, "${node.method}", ${optsArg}, async (runner) => {\n${body}\n${ind(indent)}});`;
329
+ //
330
+ // The opts object is emitted as a thunk (`async () => (<opts>)`) so its
331
+ // value expressions (`label`, `session`, ...) are evaluated inside
332
+ // `runner.thread`, AFTER its halt/skip guards. An early `return` earlier
333
+ // in the function halts the runner and skips the steps that assign the
334
+ // locals those expressions reference; evaluating them eagerly here would
335
+ // then dereference an unset local and throw. Deferring lets the early
336
+ // return win — same fix as `runnerLoop`.
337
+ const optsObj = optsParts.length === 0 ? "{}" : `{ ${optsParts.join(", ")} }`;
338
+ return `await runner.thread(${node.id}, "${node.method}", async () => (${optsObj}), async (runner) => {\n${body}\n${ind(indent)}});`;
331
339
  }
332
340
  case "runnerHandle": {
333
341
  const handler = printTs(node.handler, indent);
@@ -392,10 +400,23 @@ export function printTs(node, indent = 0) {
392
400
  return `runner.exitMatch(${node.matchId}, ${value});\nreturn;`;
393
401
  }
394
402
  case "runnerLoop": {
395
- const items = printTs(node.items, indent);
403
+ // The iterable is emitted as a thunk (`async () => (<expr>)`) so its
404
+ // expression is evaluated inside `runner.loop`, AFTER the halt/skip
405
+ // guards. An early `return` earlier in the function halts the runner and
406
+ // skips the steps that assign the iterable's backing locals; evaluating
407
+ // the iterable eagerly here would then dereference an unset local and
408
+ // throw. Deferring it lets the early return win. Mirrors how
409
+ // `runnerWhileLoop` wraps its condition.
410
+ //
411
+ // The expression MUST be parenthesized: a bare `async () => ${items}`
412
+ // breaks when the iterable prints as an object literal (record
413
+ // iteration over a literal, e.g. `for (k in { a: 1 })`) — `async () => {
414
+ // a: 1 }` parses as a function BODY block, not an object. The parens
415
+ // force expression context. (Same reason `runnerThread` wraps its opts.)
416
+ const items = printTs(node.items, indent + 1);
396
417
  const idxVar = node.indexVar ?? "_";
397
418
  const body = node.body.map((n) => printTs(n, indent + 1)).join("\n");
398
- return `await runner.loop(${node.id}, ${items}, async (${node.itemVar}, ${idxVar}, runner) => {\n${body}\n${ind(indent)}});`;
419
+ return `await runner.loop(${node.id}, async () => (${items}), async (${node.itemVar}, ${idxVar}, runner) => {\n${body}\n${ind(indent)}});`;
399
420
  }
400
421
  case "runnerWhileLoop": {
401
422
  const cond = printTs(node.condition, indent + 1);
@@ -27,23 +27,18 @@ export function parseAgencyFileCached(absPath, config = {}, applyTemplate = !isN
27
27
  rest: "",
28
28
  };
29
29
  }
30
- const bypass = !!config.tarsecTraceHost;
31
30
  const key = `${applyTemplate ? "t" : "r"}:${absPath}`;
32
- if (!bypass) {
33
- const entry = cache[key];
34
- if (entry && entry.mtimeMs === stat.mtimeMs && entry.size === stat.size) {
35
- stats.hits++;
36
- return {
37
- success: true,
38
- result: structuredClone(entry.program),
39
- rest: "",
40
- };
41
- }
31
+ const entry = cache[key];
32
+ if (entry && entry.mtimeMs === stat.mtimeMs && entry.size === stat.size) {
33
+ stats.hits++;
34
+ return {
35
+ success: true,
36
+ result: structuredClone(entry.program),
37
+ rest: "",
38
+ };
42
39
  }
43
40
  const contents = fs.readFileSync(absPath, "utf-8");
44
41
  const result = parseAgency(contents, config, applyTemplate);
45
- if (bypass)
46
- return result;
47
42
  stats.misses++;
48
43
  if (result.success) {
49
44
  cache[key] = {