@psnext/slingcli 2.4.20260601-1 → 2.5.20260602-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 (96) hide show
  1. package/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler/package.json +4 -4
  2. package/node_modules/@aws-sdk/credential-provider-node/dist-cjs/index.js +12 -1
  3. package/node_modules/@aws-sdk/credential-provider-node/dist-es/runtime/memoize-chain.js +12 -1
  4. package/node_modules/@aws-sdk/credential-provider-node/package.json +1 -1
  5. package/node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler/package.json +4 -4
  6. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +81 -18
  7. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +1 -0
  8. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -0
  9. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +14 -1
  10. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  11. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +197 -190
  12. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +84 -21
  13. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +24 -14
  14. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +5 -9
  15. package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js +1 -1
  16. package/node_modules/@earendil-works/pi-ai/dist/providers/google.js +5 -3
  17. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +2 -3
  18. package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.js +2 -3
  19. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +130 -62
  20. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +18 -12
  21. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +4 -1
  22. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +5 -9
  23. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +1 -0
  24. package/node_modules/@earendil-works/pi-ai/dist/stream.js +14 -2
  25. package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.js +34 -0
  26. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +8 -7
  27. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +1 -1
  28. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -1
  29. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +179 -79
  30. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  31. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +27 -0
  32. package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +9 -1
  33. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +1 -0
  34. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +29 -5
  35. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +21 -3
  36. package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.js +29 -1
  37. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +65 -13
  38. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -1
  39. package/node_modules/@earendil-works/pi-coding-agent/dist/core/resolve-config-value.js +134 -11
  40. package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +12 -6
  41. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +167 -96
  42. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +14 -9
  43. package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js +0 -3
  44. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +7 -10
  45. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -7
  46. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +6 -7
  47. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/render-utils.js +17 -1
  48. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +5 -6
  49. package/node_modules/@earendil-works/pi-coding-agent/dist/index.js +2 -0
  50. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +69 -16
  51. package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.js +118 -1
  52. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js +1 -1
  53. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +2 -4
  54. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +61 -6
  55. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
  56. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.js +1 -0
  57. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +4 -1
  58. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/deprecation.js +13 -0
  59. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/json.js +7 -0
  60. package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +71 -56
  61. package/node_modules/@earendil-works/pi-coding-agent/package.json +7 -7
  62. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -53
  63. package/node_modules/@earendil-works/pi-tui/dist/components/input.js +6 -54
  64. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +34 -7
  65. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +172 -37
  66. package/node_modules/@earendil-works/pi-tui/dist/tui.js +166 -22
  67. package/node_modules/@earendil-works/pi-tui/dist/utils.js +8 -3
  68. package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js +96 -0
  69. package/node_modules/@earendil-works/pi-tui/package.json +2 -2
  70. package/node_modules/@mariozechner/clipboard/Cargo.toml +3 -3
  71. package/node_modules/@mariozechner/clipboard/index.d.ts +34 -20
  72. package/node_modules/@mariozechner/clipboard/index.js +546 -257
  73. package/node_modules/@mariozechner/clipboard/package.json +14 -14
  74. package/node_modules/@mariozechner/clipboard/src/lib.rs +4 -9
  75. package/node_modules/@mariozechner/clipboard-linux-x64-gnu/clipboard.linux-x64-gnu.node +0 -0
  76. package/node_modules/@mariozechner/clipboard-linux-x64-gnu/package.json +2 -2
  77. package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.browser.js +39 -22
  78. package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.js +39 -22
  79. package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/configurations.js +19 -6
  80. package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retryMiddleware.js +4 -5
  81. package/node_modules/@smithy/core/dist-es/submodules/retry/service-error-classification/constants.js +1 -1
  82. package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/ConfiguredRetryStrategy.js +4 -5
  83. package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/DefaultRetryToken.js +3 -0
  84. package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/StandardRetryStrategy.js +9 -5
  85. package/node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/configurations.d.ts +4 -1
  86. package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/ConfiguredRetryStrategy.d.ts +1 -2
  87. package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/DefaultRetryToken.d.ts +3 -0
  88. package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/StandardRetryStrategy.d.ts +1 -1
  89. package/node_modules/@smithy/core/package.json +2 -2
  90. package/node_modules/@smithy/credential-provider-imds/package.json +3 -3
  91. package/node_modules/@smithy/fetch-http-handler/package.json +4 -4
  92. package/node_modules/@smithy/signature-v4/package.json +3 -3
  93. package/node_modules/@smithy/types/dist-types/retry.d.ts +25 -0
  94. package/node_modules/@smithy/types/package.json +1 -1
  95. package/package.json +6 -6
  96. package/slingshot/index.js +231 -230
@@ -70,6 +70,15 @@ function resolveBranchWithGitAsync(repoDir) {
70
70
  });
71
71
  });
72
72
  }
73
+ function isWslEnvironment() {
74
+ return process.platform === "linux" && !!(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);
75
+ }
76
+ function isWindowsMountedRepoPath(repoDir) {
77
+ return /^\/mnt\/[a-z](?:\/|$)/i.test(repoDir);
78
+ }
79
+ function shouldPollGitHead(repoDir) {
80
+ return isWslEnvironment() && isWindowsMountedRepoPath(repoDir);
81
+ }
73
82
  /**
74
83
  * Provides git branch and extension statuses - data not otherwise accessible to extensions.
75
84
  * Token stats, model info available via ctx.sessionManager and ctx.model.
@@ -81,6 +90,8 @@ export class FooterDataProvider {
81
90
  cachedBranch = undefined;
82
91
  gitPaths = undefined;
83
92
  headWatcher = null;
93
+ headWatchFilePath = null;
94
+ headWatchFileListener = null;
84
95
  reftableWatcher = null;
85
96
  reftableTablesListWatcher = null;
86
97
  reftableTablesListPath = null;
@@ -236,6 +247,11 @@ export class FooterDataProvider {
236
247
  clearGitWatchers() {
237
248
  closeWatcher(this.headWatcher);
238
249
  this.headWatcher = null;
250
+ if (this.headWatchFilePath && this.headWatchFileListener) {
251
+ unwatchFile(this.headWatchFilePath, this.headWatchFileListener);
252
+ this.headWatchFilePath = null;
253
+ this.headWatchFileListener = null;
254
+ }
239
255
  closeWatcher(this.reftableWatcher);
240
256
  this.reftableWatcher = null;
241
257
  closeWatcher(this.reftableTablesListWatcher);
@@ -266,6 +282,7 @@ export class FooterDataProvider {
266
282
  this.clearGitWatchers();
267
283
  if (!this.gitPaths)
268
284
  return;
285
+ const pollGitHead = shouldPollGitHead(this.gitPaths.repoDir);
269
286
  // Watch the directory containing HEAD, not HEAD itself.
270
287
  // Git uses atomic writes (write temp, rename over HEAD), which changes the inode.
271
288
  // fs.watch on a file stops working after the inode changes.
@@ -274,7 +291,18 @@ export class FooterDataProvider {
274
291
  this.scheduleRefresh();
275
292
  }
276
293
  }, () => this.handleGitWatcherError());
277
- if (!this.headWatcher) {
294
+ if (pollGitHead) {
295
+ this.headWatchFilePath = this.gitPaths.headPath;
296
+ this.headWatchFileListener = (current, previous) => {
297
+ if (current.mtimeMs !== previous.mtimeMs ||
298
+ current.ctimeMs !== previous.ctimeMs ||
299
+ current.size !== previous.size) {
300
+ this.scheduleRefresh();
301
+ }
302
+ };
303
+ watchFile(this.headWatchFilePath, { interval: 1000 }, this.headWatchFileListener);
304
+ }
305
+ if (!this.headWatcher && !pollGitHead) {
278
306
  return;
279
307
  }
280
308
  // In reftable repos, branch switches update files in the reftable directory
@@ -8,9 +8,11 @@ import { join } from "path";
8
8
  import { Type } from "typebox";
9
9
  import { Compile } from "typebox/compile";
10
10
  import { getAgentDir } from "../config.js";
11
+ import { warnDeprecation } from "../utils/deprecation.js";
12
+ import { stripJsonComments } from "../utils/json.js";
11
13
  import { normalizePath } from "../utils/paths.js";
12
14
  import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "./provider-display-names.js";
13
- import { clearConfigValueCache, resolveConfigValueOrThrow, resolveConfigValueUncached, resolveHeadersOrThrow, } from "./resolve-config-value.js";
15
+ import { clearConfigValueCache, getConfigValueEnvVarNames, isCommandConfigValue, isConfigValueConfigured, isLegacyEnvVarNameConfigValue, resolveConfigValueOrThrow, resolveConfigValueUncached, resolveHeadersOrThrow, } from "./resolve-config-value.js";
14
16
  // Schema for OpenRouter routing preferences
15
17
  const PercentileCutoffsSchema = Type.Object({
16
18
  p50: Type.Optional(Type.Number()),
@@ -166,11 +168,56 @@ function formatValidationPath(error) {
166
168
  const path = error.instancePath.replace(/^\//, "").replace(/\//g, ".");
167
169
  return path || "root";
168
170
  }
169
- /** Strip `//` line comments and trailing commas from JSON, leaving string literals untouched. */
170
- function stripJsonComments(input) {
171
- return input
172
- .replace(/"(?:\\.|[^"\\])*"|\/\/[^\n]*/g, (m) => (m[0] === '"' ? m : ""))
173
- .replace(/"(?:\\.|[^"\\])*"|,(\s*[}\]])/g, (m, tail) => tail ?? (m[0] === '"' ? m : ""));
171
+ function migrateLegacyRegisterProviderConfigValue(providerName, field, value) {
172
+ if (!isLegacyEnvVarNameConfigValue(value))
173
+ return value;
174
+ warnDeprecation(`registerProvider("${providerName}") ${field} value "${value}" is treated as a legacy environment variable reference. This will no longer be detected as an environment variable reference in a future release. Pass "$${value}" instead.`);
175
+ return `$${value}`;
176
+ }
177
+ function migrateLegacyRegisterProviderHeaders(providerName, field, headers) {
178
+ if (!headers)
179
+ return undefined;
180
+ let migratedHeaders;
181
+ for (const [key, value] of Object.entries(headers)) {
182
+ const migratedValue = migrateLegacyRegisterProviderConfigValue(providerName, `${field} header "${key}"`, value);
183
+ if (migratedValue === value)
184
+ continue;
185
+ migratedHeaders ??= { ...headers };
186
+ migratedHeaders[key] = migratedValue;
187
+ }
188
+ return migratedHeaders ?? headers;
189
+ }
190
+ function migrateLegacyRegisterProviderConfigValues(providerName, config) {
191
+ let migratedConfig;
192
+ const setMigratedConfigValue = (key, value) => {
193
+ migratedConfig ??= { ...config };
194
+ migratedConfig[key] = value;
195
+ };
196
+ if (config.apiKey) {
197
+ const apiKey = migrateLegacyRegisterProviderConfigValue(providerName, "apiKey", config.apiKey);
198
+ if (apiKey !== config.apiKey) {
199
+ setMigratedConfigValue("apiKey", apiKey);
200
+ }
201
+ }
202
+ const headers = migrateLegacyRegisterProviderHeaders(providerName, "headers", config.headers);
203
+ if (headers !== config.headers) {
204
+ setMigratedConfigValue("headers", headers);
205
+ }
206
+ if (config.models) {
207
+ let models;
208
+ for (let index = 0; index < config.models.length; index++) {
209
+ const model = config.models[index];
210
+ const modelHeaders = migrateLegacyRegisterProviderHeaders(providerName, `model "${model.id}" headers`, model.headers);
211
+ if (modelHeaders === model.headers)
212
+ continue;
213
+ models ??= [...config.models];
214
+ models[index] = { ...model, headers: modelHeaders };
215
+ }
216
+ if (models) {
217
+ setMigratedConfigValue("models", models);
218
+ }
219
+ }
220
+ return migratedConfig ?? config;
174
221
  }
175
222
  function emptyCustomModelsResult(error) {
176
223
  return { models: [], overrides: new Map(), modelOverrides: new Map(), error };
@@ -491,8 +538,9 @@ export class ModelRegistry {
491
538
  * Get API key for a model.
492
539
  */
493
540
  hasConfiguredAuth(model) {
541
+ const providerApiKey = this.providerRequestConfigs.get(model.provider)?.apiKey;
494
542
  return (this.authStorage.hasAuth(model.provider) ||
495
- this.providerRequestConfigs.get(model.provider)?.apiKey !== undefined);
543
+ (providerApiKey !== undefined && isConfigValueConfigured(providerApiKey)));
496
544
  }
497
545
  getModelRequestKey(provider, modelId) {
498
546
  return `${provider}:${modelId}`;
@@ -563,11 +611,14 @@ export class ModelRegistry {
563
611
  if (!providerApiKey) {
564
612
  return authStatus;
565
613
  }
566
- if (providerApiKey.startsWith("!")) {
614
+ if (isCommandConfigValue(providerApiKey)) {
567
615
  return { configured: true, source: "models_json_command" };
568
616
  }
569
- if (process.env[providerApiKey]) {
570
- return { configured: true, source: "environment", label: providerApiKey };
617
+ const envVarNames = getConfigValueEnvVarNames(providerApiKey);
618
+ if (envVarNames.length > 0) {
619
+ return isConfigValueConfigured(providerApiKey)
620
+ ? { configured: true, source: "environment", label: envVarNames.join(", ") }
621
+ : { configured: false };
571
622
  }
572
623
  return { configured: true, source: "models_json_key" };
573
624
  }
@@ -609,9 +660,10 @@ export class ModelRegistry {
609
660
  * If provider has oauth: registers OAuth provider for /login support.
610
661
  */
611
662
  registerProvider(providerName, config) {
612
- this.validateProviderConfig(providerName, config);
613
- this.applyProviderConfig(providerName, config);
614
- this.upsertRegisteredProvider(providerName, config);
663
+ const migratedConfig = migrateLegacyRegisterProviderConfigValues(providerName, config);
664
+ this.validateProviderConfig(providerName, migratedConfig);
665
+ this.applyProviderConfig(providerName, migratedConfig);
666
+ this.upsertRegisteredProvider(providerName, migratedConfig);
615
667
  }
616
668
  /**
617
669
  * Unregister a previously registered provider.
@@ -9,7 +9,7 @@ import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
9
9
  /** Default model IDs for each known provider */
10
10
  export const defaultModelPerProvider = {
11
11
  "amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
12
- anthropic: "claude-opus-4-7",
12
+ anthropic: "claude-opus-4-8",
13
13
  openai: "gpt-5.4",
14
14
  "azure-openai-responses": "gpt-5.4",
15
15
  "openai-codex": "gpt-5.5",
@@ -6,17 +6,130 @@ import { execSync, spawnSync } from "child_process";
6
6
  import { getShellConfig } from "../utils/shell.js";
7
7
  // Cache for shell command results (persists for process lifetime)
8
8
  const commandResultCache = new Map();
9
+ const ENV_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
10
+ const ENV_VAR_NAME_PREFIX_RE = /^[A-Za-z_][A-Za-z0-9_]*/;
11
+ const LEGACY_ENV_VAR_NAME_RE = /^[A-Z_][A-Z0-9_]*$/;
12
+ function appendLiteral(parts, value) {
13
+ if (!value)
14
+ return;
15
+ const previousPart = parts[parts.length - 1];
16
+ if (previousPart?.type === "literal") {
17
+ previousPart.value += value;
18
+ return;
19
+ }
20
+ parts.push({ type: "literal", value });
21
+ }
22
+ function parseConfigValueTemplate(config) {
23
+ const parts = [];
24
+ let index = 0;
25
+ while (index < config.length) {
26
+ const dollarIndex = config.indexOf("$", index);
27
+ if (dollarIndex < 0) {
28
+ appendLiteral(parts, config.slice(index));
29
+ break;
30
+ }
31
+ appendLiteral(parts, config.slice(index, dollarIndex));
32
+ const nextChar = config[dollarIndex + 1];
33
+ if (nextChar === "$" || nextChar === "!") {
34
+ appendLiteral(parts, nextChar);
35
+ index = dollarIndex + 2;
36
+ continue;
37
+ }
38
+ if (nextChar === "{") {
39
+ const endIndex = config.indexOf("}", dollarIndex + 2);
40
+ if (endIndex < 0) {
41
+ appendLiteral(parts, "$");
42
+ index = dollarIndex + 1;
43
+ continue;
44
+ }
45
+ const name = config.slice(dollarIndex + 2, endIndex);
46
+ if (ENV_VAR_NAME_RE.test(name)) {
47
+ parts.push({ type: "env", name });
48
+ }
49
+ else {
50
+ appendLiteral(parts, config.slice(dollarIndex, endIndex + 1));
51
+ }
52
+ index = endIndex + 1;
53
+ continue;
54
+ }
55
+ const match = config.slice(dollarIndex + 1).match(ENV_VAR_NAME_PREFIX_RE);
56
+ if (match) {
57
+ parts.push({ type: "env", name: match[0] });
58
+ index = dollarIndex + 1 + match[0].length;
59
+ continue;
60
+ }
61
+ appendLiteral(parts, "$");
62
+ index = dollarIndex + 1;
63
+ }
64
+ return parts;
65
+ }
66
+ function parseConfigValueReference(config) {
67
+ if (config.startsWith("!")) {
68
+ return { type: "command", config };
69
+ }
70
+ return { type: "template", parts: parseConfigValueTemplate(config) };
71
+ }
72
+ function resolveEnvConfigValue(name) {
73
+ return process.env[name] || undefined;
74
+ }
75
+ function getTemplateEnvVarNames(parts) {
76
+ const names = [];
77
+ for (const part of parts) {
78
+ if (part.type !== "env" || names.includes(part.name))
79
+ continue;
80
+ names.push(part.name);
81
+ }
82
+ return names;
83
+ }
84
+ function resolveTemplate(parts) {
85
+ let resolved = "";
86
+ for (const part of parts) {
87
+ if (part.type === "literal") {
88
+ resolved += part.value;
89
+ continue;
90
+ }
91
+ const envValue = resolveEnvConfigValue(part.name);
92
+ if (envValue === undefined)
93
+ return undefined;
94
+ resolved += envValue;
95
+ }
96
+ return resolved;
97
+ }
98
+ export function getConfigValueEnvVarName(config) {
99
+ const reference = parseConfigValueReference(config);
100
+ if (reference.type !== "template")
101
+ return undefined;
102
+ return reference.parts.length === 1 && reference.parts[0]?.type === "env" ? reference.parts[0].name : undefined;
103
+ }
104
+ export function getConfigValueEnvVarNames(config) {
105
+ const reference = parseConfigValueReference(config);
106
+ return reference.type === "template" ? getTemplateEnvVarNames(reference.parts) : [];
107
+ }
108
+ export function getMissingConfigValueEnvVarNames(config) {
109
+ return getConfigValueEnvVarNames(config).filter((name) => resolveEnvConfigValue(name) === undefined);
110
+ }
111
+ export function isCommandConfigValue(config) {
112
+ return parseConfigValueReference(config).type === "command";
113
+ }
114
+ export function isConfigValueConfigured(config) {
115
+ return getMissingConfigValueEnvVarNames(config).length === 0;
116
+ }
117
+ export function isLegacyEnvVarNameConfigValue(config) {
118
+ return LEGACY_ENV_VAR_NAME_RE.test(config);
119
+ }
9
120
  /**
10
121
  * Resolve a config value (API key, header value, etc.) to an actual value.
11
122
  * - If starts with "!", executes the rest as a shell command and uses stdout (cached)
12
- * - Otherwise checks environment variable first, then treats as literal (not cached)
123
+ * - Interpolates "$ENV_VAR" or "${ENV_VAR}" references with the named environment variable
124
+ * - In non-command values, "$$" escapes a literal "$" and "$!" escapes a literal "!"
125
+ * - Otherwise treats the value as a literal
13
126
  */
14
127
  export function resolveConfigValue(config) {
15
- if (config.startsWith("!")) {
16
- return executeCommand(config);
128
+ const reference = parseConfigValueReference(config);
129
+ if (reference.type === "command") {
130
+ return executeCommand(reference.config);
17
131
  }
18
- const envValue = process.env[config];
19
- return envValue || config;
132
+ return resolveTemplate(reference.parts);
20
133
  }
21
134
  function executeWithConfiguredShell(command) {
22
135
  try {
@@ -79,19 +192,29 @@ function executeCommand(commandConfig) {
79
192
  * Resolve all header values using the same resolution logic as API keys.
80
193
  */
81
194
  export function resolveConfigValueUncached(config) {
82
- if (config.startsWith("!")) {
83
- return executeCommandUncached(config);
195
+ const reference = parseConfigValueReference(config);
196
+ if (reference.type === "command") {
197
+ return executeCommandUncached(reference.config);
84
198
  }
85
- const envValue = process.env[config];
86
- return envValue || config;
199
+ return resolveTemplate(reference.parts);
87
200
  }
88
201
  export function resolveConfigValueOrThrow(config, description) {
89
202
  const resolvedValue = resolveConfigValueUncached(config);
90
203
  if (resolvedValue !== undefined) {
91
204
  return resolvedValue;
92
205
  }
93
- if (config.startsWith("!")) {
94
- throw new Error(`Failed to resolve ${description} from shell command: ${config.slice(1)}`);
206
+ const reference = parseConfigValueReference(config);
207
+ if (reference.type === "command") {
208
+ throw new Error(`Failed to resolve ${description} from shell command: ${reference.config.slice(1)}`);
209
+ }
210
+ if (reference.type === "template") {
211
+ const missingEnvVars = getMissingConfigValueEnvVarNames(config);
212
+ if (missingEnvVars.length === 1) {
213
+ throw new Error(`Failed to resolve ${description} from environment variable: ${missingEnvVars[0]}`);
214
+ }
215
+ if (missingEnvVars.length > 1) {
216
+ throw new Error(`Failed to resolve ${description} from environment variables: ${missingEnvVars.join(", ")}`);
217
+ }
95
218
  }
96
219
  throw new Error(`Failed to resolve ${description}`);
97
220
  }
@@ -155,11 +155,9 @@ export async function createAgentSession(options = {}) {
155
155
  }
156
156
  const defaultActiveToolNames = ["read", "bash", "edit", "write"];
157
157
  const allowedToolNames = options.tools ?? (options.noTools === "all" ? [] : undefined);
158
- const initialActiveToolNames = options.tools
159
- ? [...options.tools]
160
- : options.noTools
161
- ? []
162
- : defaultActiveToolNames;
158
+ const excludedToolNames = options.excludeTools;
159
+ const excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;
160
+ const initialActiveToolNames = (options.tools ? [...options.tools] : options.noTools ? [] : defaultActiveToolNames).filter((name) => !excludedToolNameSet?.has(name));
163
161
  let agent;
164
162
  // Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
165
163
  const convertToLlmWithBlockImages = (messages) => {
@@ -206,11 +204,18 @@ export async function createAgentSession(options = {}) {
206
204
  throw new Error(auth.error);
207
205
  }
208
206
  const providerRetrySettings = settingsManager.getProviderRetrySettings();
207
+ const httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();
208
+ // SDKs treat timeout=0 as 0ms (immediate timeout), not "no timeout".
209
+ // Use max int32 to effectively disable the timeout.
210
+ const effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;
211
+ const timeoutMs = options?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;
212
+ const websocketConnectTimeoutMs = options?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();
209
213
  const attributionHeaders = getAttributionHeaders(model, settingsManager, options?.sessionId);
210
214
  return streamSimple(model, context, {
211
215
  ...options,
212
216
  apiKey: auth.apiKey,
213
- timeoutMs: options?.timeoutMs ?? providerRetrySettings.timeoutMs,
217
+ timeoutMs,
218
+ websocketConnectTimeoutMs,
214
219
  maxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,
215
220
  maxRetryDelayMs: options?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,
216
221
  headers: attributionHeaders || auth.headers || options?.headers
@@ -274,6 +279,7 @@ export async function createAgentSession(options = {}) {
274
279
  modelRegistry,
275
280
  initialActiveToolNames,
276
281
  allowedToolNames,
282
+ excludedToolNames,
277
283
  extensionRunnerRef,
278
284
  sessionStartEvent: options.sessionStartEvent,
279
285
  });