@hasna/assistants 0.6.54 → 0.6.55

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/index.js CHANGED
@@ -280,7 +280,34 @@ function substituteVariables(template, args, env = {}) {
280
280
  });
281
281
  return result;
282
282
  }
283
- var init_utils = () => {};
283
+ function getRandomLoadingWord() {
284
+ return LOADING_WORDS[Math.floor(Math.random() * LOADING_WORDS.length)];
285
+ }
286
+ var LOADING_WORDS;
287
+ var init_utils = __esm(() => {
288
+ LOADING_WORDS = [
289
+ "Metamorphosing",
290
+ "Conjuring",
291
+ "Transmuting",
292
+ "Alchemizing",
293
+ "Manifesting",
294
+ "Brewing",
295
+ "Summoning",
296
+ "Cogitating",
297
+ "Ruminating",
298
+ "Percolating",
299
+ "Incubating",
300
+ "Gestating",
301
+ "Synthesizing",
302
+ "Crystallizing",
303
+ "Catalyzing",
304
+ "Distilling",
305
+ "Fermenting",
306
+ "Marinating",
307
+ "Simmering",
308
+ "Concocting"
309
+ ];
310
+ });
284
311
 
285
312
  // packages/shared/src/index.ts
286
313
  var init_src2 = __esm(() => {
@@ -9662,8 +9689,8 @@ var require_pattern2 = __commonJS((exports) => {
9662
9689
  }
9663
9690
  exports.endsWithSlashGlobStar = endsWithSlashGlobStar;
9664
9691
  function isAffectDepthOfReadingPattern(pattern) {
9665
- const basename = path.basename(pattern);
9666
- return endsWithSlashGlobStar(pattern) || isStaticPattern(basename);
9692
+ const basename2 = path.basename(pattern);
9693
+ return endsWithSlashGlobStar(pattern) || isStaticPattern(basename2);
9667
9694
  }
9668
9695
  exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
9669
9696
  function expandPatternsWithBraceExpansion(patterns) {
@@ -113535,6 +113562,118 @@ function validateBashCommand(command) {
113535
113562
  return { valid: true };
113536
113563
  }
113537
113564
 
113565
+ // packages/core/src/security/network-validator.ts
113566
+ import { lookup as dnsLookup } from "dns/promises";
113567
+ var lookupFn = dnsLookup;
113568
+ async function isPrivateHostOrResolved(hostname) {
113569
+ if (isPrivateHost(hostname)) {
113570
+ return true;
113571
+ }
113572
+ const host = normalizeHostname(hostname);
113573
+ if (host === "" || isIpLiteral(host)) {
113574
+ return isPrivateHost(host);
113575
+ }
113576
+ try {
113577
+ const results = await lookupFn(host, { all: true });
113578
+ for (const result of results) {
113579
+ if (isPrivateHost(result.address)) {
113580
+ return true;
113581
+ }
113582
+ }
113583
+ return false;
113584
+ } catch {
113585
+ return true;
113586
+ }
113587
+ }
113588
+ function isIpLiteral(host) {
113589
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host))
113590
+ return true;
113591
+ if (host.includes(":"))
113592
+ return true;
113593
+ return false;
113594
+ }
113595
+ function normalizeHostname(hostname) {
113596
+ let host = hostname.toLowerCase().trim();
113597
+ if (host.startsWith("[")) {
113598
+ const idx = host.indexOf("]:");
113599
+ if (idx > 0)
113600
+ host = host.slice(1, idx);
113601
+ else if (host.endsWith("]"))
113602
+ host = host.slice(1, -1);
113603
+ } else {
113604
+ const idx = host.lastIndexOf(":");
113605
+ const beforeColon = host.slice(0, idx);
113606
+ if (idx > 0 && !beforeColon.includes(":")) {
113607
+ host = beforeColon;
113608
+ }
113609
+ }
113610
+ host = host.replace(/\.$/, "");
113611
+ return host;
113612
+ }
113613
+ function isPrivateHost(hostname) {
113614
+ let host = normalizeHostname(hostname);
113615
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local"))
113616
+ return true;
113617
+ if (host === "127.0.0.1" || host === "::1" || host === "::" || host === "0:0:0:0:0:0:0:0")
113618
+ return true;
113619
+ if (/^\d+$/.test(host))
113620
+ return true;
113621
+ if (host.startsWith("::ffff:")) {
113622
+ const mapped = host.slice("::ffff:".length);
113623
+ if (mapped.includes(".")) {
113624
+ return isPrivateHost(mapped);
113625
+ }
113626
+ const hexMatch = mapped.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
113627
+ if (hexMatch) {
113628
+ const high = Number.parseInt(hexMatch[1], 16);
113629
+ const low = Number.parseInt(hexMatch[2], 16);
113630
+ const octets2 = [
113631
+ high >> 8 & 255,
113632
+ high & 255,
113633
+ low >> 8 & 255,
113634
+ low & 255
113635
+ ];
113636
+ return isPrivateIPv4(octets2);
113637
+ }
113638
+ return false;
113639
+ }
113640
+ if (host.includes(":")) {
113641
+ return host === "::1" || host === "::" || host === "0:0:0:0:0:0:0:0";
113642
+ }
113643
+ const match = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
113644
+ if (!match)
113645
+ return false;
113646
+ const octets = [];
113647
+ for (let i = 1;i <= 4; i++) {
113648
+ const value = Number.parseInt(match[i], 10);
113649
+ if (Number.isNaN(value))
113650
+ return false;
113651
+ octets.push(value);
113652
+ }
113653
+ return isPrivateIPv4(octets);
113654
+ }
113655
+ function isPrivateIPv4(octets) {
113656
+ if (octets[0] === 0)
113657
+ return true;
113658
+ if (octets[0] === 10)
113659
+ return true;
113660
+ if (octets[0] === 100 && octets[1] >= 64 && octets[1] <= 127)
113661
+ return true;
113662
+ if (octets[0] === 169 && octets[1] === 254)
113663
+ return true;
113664
+ if (octets[0] === 192 && octets[1] === 168)
113665
+ return true;
113666
+ if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31)
113667
+ return true;
113668
+ if (octets[0] === 127)
113669
+ return true;
113670
+ if (octets[0] >= 224 && octets[0] <= 239)
113671
+ return true;
113672
+ if (octets[0] >= 240)
113673
+ return true;
113674
+ return false;
113675
+ }
113676
+
113538
113677
  // packages/core/src/tools/bash.ts
113539
113678
  function killProcess(proc) {
113540
113679
  proc.kill();
@@ -113664,7 +113803,8 @@ class BashTool {
113664
113803
  "node --version",
113665
113804
  "bun --version",
113666
113805
  "npm --version",
113667
- "pnpm --version"
113806
+ "pnpm --version",
113807
+ "jq"
113668
113808
  ];
113669
113809
  static BLOCKED_PATTERNS = [
113670
113810
  /\brm\b/,
@@ -113695,8 +113835,7 @@ class BashTool {
113695
113835
  /\|\s*(bash|sh|zsh|fish)\b/,
113696
113836
  /curl.*\|\s*(bash|sh)/,
113697
113837
  /wget.*\|\s*(bash|sh)/,
113698
- /[;&]/,
113699
- /[|]/,
113838
+ /;/,
113700
113839
  /[\r\n]/,
113701
113840
  />\s*[^|]/,
113702
113841
  />>/,
@@ -113716,6 +113855,110 @@ class BashTool {
113716
113855
  /\bcmake\b/,
113717
113856
  /\bdocker\s+(run|exec|build|push)\b/
113718
113857
  ];
113858
+ static isCommandPartAllowed(commandPart, allowlist) {
113859
+ const trimmed = commandPart.trim().toLowerCase();
113860
+ for (const allowed of allowlist) {
113861
+ if (trimmed.startsWith(allowed.toLowerCase())) {
113862
+ return true;
113863
+ }
113864
+ }
113865
+ return false;
113866
+ }
113867
+ static areAllCommandPartsAllowed(command, allowlist) {
113868
+ const parts = this.splitCommandByOperators(command);
113869
+ if (parts.length === 0)
113870
+ return false;
113871
+ for (const part of parts) {
113872
+ if (!this.isCommandPartAllowed(part, allowlist)) {
113873
+ return false;
113874
+ }
113875
+ }
113876
+ return true;
113877
+ }
113878
+ static extractCurlUrls(command) {
113879
+ const urls = [];
113880
+ const curlMatch = command.match(/^curl\s+(.+)$/i);
113881
+ if (!curlMatch)
113882
+ return urls;
113883
+ const args = curlMatch[1];
113884
+ const urlMatches = args.match(/https?:\/\/[^\s'"]+/gi);
113885
+ if (urlMatches) {
113886
+ for (const url of urlMatches) {
113887
+ const cleanUrl = url.replace(/[;|&<>]+$/, "");
113888
+ if (cleanUrl)
113889
+ urls.push(cleanUrl);
113890
+ }
113891
+ }
113892
+ return urls;
113893
+ }
113894
+ static async validateCurlSsrf(command) {
113895
+ const trimmed = command.trim().toLowerCase();
113896
+ if (!trimmed.startsWith("curl ")) {
113897
+ return { valid: true };
113898
+ }
113899
+ const urls = this.extractCurlUrls(command.trim());
113900
+ for (const urlStr of urls) {
113901
+ try {
113902
+ const url = new URL(urlStr);
113903
+ if (await isPrivateHostOrResolved(url.hostname)) {
113904
+ return { valid: false, blockedUrl: urlStr };
113905
+ }
113906
+ } catch {
113907
+ continue;
113908
+ }
113909
+ }
113910
+ return { valid: true };
113911
+ }
113912
+ static splitCommandByOperators(command) {
113913
+ const parts = [];
113914
+ let current = "";
113915
+ let quote = null;
113916
+ let escaped = false;
113917
+ let i = 0;
113918
+ while (i < command.length) {
113919
+ const char = command[i];
113920
+ if (quote === '"' && !escaped && char === "\\") {
113921
+ escaped = true;
113922
+ current += char;
113923
+ i++;
113924
+ continue;
113925
+ }
113926
+ if (!quote && (char === '"' || char === "'")) {
113927
+ quote = char;
113928
+ current += char;
113929
+ i++;
113930
+ continue;
113931
+ }
113932
+ if (quote && !escaped && char === quote) {
113933
+ quote = null;
113934
+ current += char;
113935
+ i++;
113936
+ continue;
113937
+ }
113938
+ escaped = false;
113939
+ if (!quote) {
113940
+ if (char === "&" && command[i + 1] === "&" || char === "|" && command[i + 1] === "|") {
113941
+ if (current.trim())
113942
+ parts.push(current.trim());
113943
+ current = "";
113944
+ i += 2;
113945
+ continue;
113946
+ }
113947
+ if (char === "|") {
113948
+ if (current.trim())
113949
+ parts.push(current.trim());
113950
+ current = "";
113951
+ i++;
113952
+ continue;
113953
+ }
113954
+ }
113955
+ current += char;
113956
+ i++;
113957
+ }
113958
+ if (current.trim())
113959
+ parts.push(current.trim());
113960
+ return parts;
113961
+ }
113719
113962
  static executor = async (input) => {
113720
113963
  const command = input.command;
113721
113964
  const cwd = input.cwd || process.cwd();
@@ -113799,14 +114042,8 @@ class BashTool {
113799
114042
  suggestion: "Enable validation.perTool.bash.allowEnv to allow env/printenv."
113800
114043
  });
113801
114044
  }
113802
- let isAllowed = false;
113803
114045
  const allowlist = allowEnv ? this.ALLOWED_COMMANDS : this.ALLOWED_COMMANDS.filter((allowed) => allowed !== "env" && allowed !== "printenv");
113804
- for (const allowed of allowlist) {
113805
- if (commandTrimmed.startsWith(allowed.toLowerCase())) {
113806
- isAllowed = true;
113807
- break;
113808
- }
113809
- }
114046
+ const isAllowed = this.areAllCommandPartsAllowed(commandForChecks, allowlist);
113810
114047
  if (!isAllowed) {
113811
114048
  getSecurityLogger().log({
113812
114049
  eventType: "blocked_command",
@@ -113827,6 +114064,27 @@ class BashTool {
113827
114064
  suggestion: "Use a permitted read-only command."
113828
114065
  });
113829
114066
  }
114067
+ const ssrfCheck = await this.validateCurlSsrf(commandForChecks);
114068
+ if (!ssrfCheck.valid) {
114069
+ getSecurityLogger().log({
114070
+ eventType: "blocked_command",
114071
+ severity: "high",
114072
+ details: {
114073
+ tool: "bash",
114074
+ command,
114075
+ reason: `SSRF protection: curl to private/internal network blocked (${ssrfCheck.blockedUrl})`
114076
+ },
114077
+ sessionId: input.sessionId || "unknown"
114078
+ });
114079
+ throw new ToolExecutionError(`Cannot fetch from local/private network addresses for security reasons: ${ssrfCheck.blockedUrl}`, {
114080
+ toolName: "bash",
114081
+ toolInput: input,
114082
+ code: ErrorCodes.TOOL_PERMISSION_DENIED,
114083
+ recoverable: false,
114084
+ retryable: false,
114085
+ suggestion: "Use a public URL instead of localhost or internal network addresses."
114086
+ });
114087
+ }
113830
114088
  try {
113831
114089
  const runtime = getRuntime();
113832
114090
  const isWindows = process.platform === "win32";
@@ -113937,22 +114195,86 @@ function isWithinPath(target, base) {
113937
114195
 
113938
114196
  // packages/core/src/security/path-validator.ts
113939
114197
  import { homedir as homedir4 } from "os";
113940
- import { resolve as resolve2, relative as relative2, isAbsolute as isAbsolute2 } from "path";
114198
+ import { resolve as resolve2, relative as relative2, isAbsolute as isAbsolute2, basename } from "path";
113941
114199
  import { lstat as lstat2, realpath as realpath2 } from "fs/promises";
113942
114200
  var PROTECTED_PATHS = [
113943
114201
  "/etc/passwd",
113944
114202
  "/etc/shadow",
113945
114203
  "/etc/sudoers",
114204
+ "/etc/master.passwd",
114205
+ "/etc/security",
113946
114206
  "~/.secrets",
113947
114207
  "~/.ssh",
113948
114208
  "~/.gnupg",
114209
+ "~/.pgp",
114210
+ "~/.gpg",
113949
114211
  "~/.aws/credentials",
113950
- "~/.kube/config"
113951
- ];
114212
+ "~/.aws/config",
114213
+ "~/.azure/credentials",
114214
+ "~/.config/gcloud",
114215
+ "~/.kube/config",
114216
+ "~/.docker/config.json",
114217
+ "~/.npmrc",
114218
+ "~/.yarnrc",
114219
+ "~/.pypirc",
114220
+ "~/.gem/credentials",
114221
+ "~/.bash_history",
114222
+ "~/.zsh_history",
114223
+ "~/.history",
114224
+ "~/.pgpass",
114225
+ "~/.my.cnf",
114226
+ "~/.mongocli.yaml",
114227
+ "~/.git-credentials",
114228
+ "~/.gitconfig",
114229
+ "~/.netrc",
114230
+ "~/.authinfo",
114231
+ "~/.config/gh/hosts.yml",
114232
+ "~/.config/hub",
114233
+ "~/.password-store",
114234
+ "~/.vault-token",
114235
+ "~/.anthropic",
114236
+ "~/.openai"
114237
+ ];
114238
+ var PROTECTED_FILENAME_PATTERNS = [
114239
+ /^\.env($|\..*)/,
114240
+ /^\.secret(s)?($|\..*)/,
114241
+ /^credentials(\..*)?$/i,
114242
+ /^secrets?(\..*)?$/i,
114243
+ /^.*_credentials(\..*)?$/i,
114244
+ /^.*_secret(s)?(\..*)?$/i,
114245
+ /^\.?id_rsa(\.pub)?$/,
114246
+ /^\.?id_ed25519(\.pub)?$/,
114247
+ /^\.?id_ecdsa(\.pub)?$/,
114248
+ /^\.?id_dsa(\.pub)?$/,
114249
+ /^authorized_keys$/,
114250
+ /^known_hosts$/,
114251
+ /^.*\.pem$/,
114252
+ /^.*\.key$/,
114253
+ /^.*\.p12$/,
114254
+ /^.*\.pfx$/,
114255
+ /^.*\.keystore$/
114256
+ ];
114257
+ function isProtectedFilename(filename) {
114258
+ for (const pattern of PROTECTED_FILENAME_PATTERNS) {
114259
+ if (pattern.test(filename)) {
114260
+ return true;
114261
+ }
114262
+ }
114263
+ return false;
114264
+ }
114265
+ function isInAllowedPaths(resolved, allowedPaths) {
114266
+ for (const allowedPath of allowedPaths) {
114267
+ if (isWithinPath2(resolved, allowedPath)) {
114268
+ return true;
114269
+ }
114270
+ }
114271
+ return false;
114272
+ }
113952
114273
  async function isPathSafe(targetPath, operation, options = {}) {
113953
114274
  const expandedTarget = expandHome2(targetPath);
113954
114275
  const resolved = resolve2(expandedTarget);
113955
114276
  const home = homedir4();
114277
+ const filename = basename(resolved);
113956
114278
  for (const protectedPath of PROTECTED_PATHS) {
113957
114279
  const expanded = protectedPath.replace("~", home);
113958
114280
  if (isWithinPath2(resolved, expanded)) {
@@ -113962,15 +114284,48 @@ async function isPathSafe(targetPath, operation, options = {}) {
113962
114284
  };
113963
114285
  }
113964
114286
  }
114287
+ if (isProtectedFilename(filename)) {
114288
+ return {
114289
+ safe: false,
114290
+ reason: `Cannot ${operation} file with protected name pattern: ${filename}`
114291
+ };
114292
+ }
114293
+ const enforceAllowlist = options.enforceAllowlist ?? operation === "read";
114294
+ if (enforceAllowlist) {
114295
+ const cwd = options.cwd ? resolve2(options.cwd) : process.cwd();
114296
+ const allowedPaths = [cwd, ...(options.allowedPaths || []).map((p) => resolve2(p))];
114297
+ if (!isInAllowedPaths(resolved, allowedPaths)) {
114298
+ return {
114299
+ safe: false,
114300
+ reason: `Cannot ${operation} files outside project directory. Path must be within: ${cwd}`
114301
+ };
114302
+ }
114303
+ }
113965
114304
  try {
113966
114305
  const stat = await lstat2(resolved);
113967
114306
  if (stat.isSymbolicLink()) {
113968
114307
  const target = await realpath2(resolved);
113969
114308
  const root = options.cwd ? resolve2(options.cwd) : process.cwd();
113970
- if (!target.startsWith(root)) {
114309
+ const allowedTargets = [root, ...(options.allowedPaths || []).map((p) => resolve2(p))];
114310
+ if (!isInAllowedPaths(target, allowedTargets)) {
113971
114311
  return {
113972
114312
  safe: false,
113973
- reason: "Symlink points outside working directory"
114313
+ reason: "Symlink points outside allowed directories"
114314
+ };
114315
+ }
114316
+ for (const protectedPath of PROTECTED_PATHS) {
114317
+ const expanded = protectedPath.replace("~", home);
114318
+ if (isWithinPath2(target, expanded)) {
114319
+ return {
114320
+ safe: false,
114321
+ reason: `Symlink points to protected path: ${protectedPath}`
114322
+ };
114323
+ }
114324
+ }
114325
+ if (isProtectedFilename(basename(target))) {
114326
+ return {
114327
+ safe: false,
114328
+ reason: `Symlink points to file with protected name pattern`
113974
114329
  };
113975
114330
  }
113976
114331
  }
@@ -114611,10 +114966,48 @@ class FilesystemTools {
114611
114966
 
114612
114967
  // packages/core/src/tools/web.ts
114613
114968
  init_errors();
114614
- import { lookup as dnsLookup } from "dns/promises";
114969
+ import { lookup as dnsLookup2 } from "dns/promises";
114615
114970
  function abortController(controller) {
114616
114971
  controller.abort();
114617
114972
  }
114973
+ var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
114974
+ async function readResponseWithLimit(response, maxBytes = MAX_RESPONSE_BYTES) {
114975
+ const reader = response.body?.getReader();
114976
+ if (!reader) {
114977
+ return { text: "", truncated: false };
114978
+ }
114979
+ const decoder = new TextDecoder("utf-8", { fatal: false });
114980
+ const chunks = [];
114981
+ let totalBytes = 0;
114982
+ let truncated = false;
114983
+ try {
114984
+ while (true) {
114985
+ const { done, value } = await reader.read();
114986
+ if (done)
114987
+ break;
114988
+ totalBytes += value.byteLength;
114989
+ if (totalBytes > maxBytes) {
114990
+ const excess = totalBytes - maxBytes;
114991
+ const trimmed = value.slice(0, value.byteLength - excess);
114992
+ chunks.push(decoder.decode(trimmed, { stream: false }));
114993
+ truncated = true;
114994
+ break;
114995
+ }
114996
+ chunks.push(decoder.decode(value, { stream: true }));
114997
+ }
114998
+ if (!truncated) {
114999
+ chunks.push(decoder.decode());
115000
+ }
115001
+ } finally {
115002
+ reader.releaseLock();
115003
+ if (truncated && response.body) {
115004
+ try {
115005
+ await response.body.cancel();
115006
+ } catch {}
115007
+ }
115008
+ }
115009
+ return { text: chunks.join(""), truncated };
115010
+ }
114618
115011
 
114619
115012
  class WebFetchTool {
114620
115013
  static tool = {
@@ -114663,7 +115056,7 @@ class WebFetchTool {
114663
115056
  });
114664
115057
  }
114665
115058
  const hostname = parsedUrl.hostname;
114666
- if (await isPrivateHostOrResolved(hostname)) {
115059
+ if (await isPrivateHostOrResolved2(hostname)) {
114667
115060
  throw new ToolExecutionError("Cannot fetch from local/private network addresses for security reasons", {
114668
115061
  toolName: "web_fetch",
114669
115062
  toolInput: input,
@@ -114727,9 +115120,21 @@ class WebFetchTool {
114727
115120
  const contentType = response.headers.get("content-type") || "";
114728
115121
  if (extractType === "json") {
114729
115122
  try {
114730
- const json = await response.json();
115123
+ const { text: jsonText, truncated } = await readResponseWithLimit(response);
115124
+ if (truncated) {
115125
+ throw new ToolExecutionError("JSON response exceeds size limit", {
115126
+ toolName: "web_fetch",
115127
+ toolInput: input,
115128
+ code: ErrorCodes.TOOL_EXECUTION_FAILED,
115129
+ recoverable: false,
115130
+ retryable: false
115131
+ });
115132
+ }
115133
+ const json = JSON.parse(jsonText);
114731
115134
  return JSON.stringify(json, null, 2);
114732
- } catch {
115135
+ } catch (e) {
115136
+ if (e instanceof ToolExecutionError)
115137
+ throw e;
114733
115138
  throw new ToolExecutionError("Response is not valid JSON", {
114734
115139
  toolName: "web_fetch",
114735
115140
  toolInput: input,
@@ -114739,13 +115144,18 @@ class WebFetchTool {
114739
115144
  });
114740
115145
  }
114741
115146
  }
114742
- const html = await response.text();
115147
+ const { text: html, truncated: htmlTruncated } = await readResponseWithLimit(response);
114743
115148
  if (extractType === "html") {
114744
115149
  const maxLength2 = 50000;
114745
115150
  if (html.length > maxLength2) {
114746
115151
  return html.slice(0, maxLength2) + `
114747
115152
 
114748
115153
  [Content truncated...]`;
115154
+ }
115155
+ if (htmlTruncated) {
115156
+ return html + `
115157
+
115158
+ [Content truncated due to size limit...]`;
114749
115159
  }
114750
115160
  return html;
114751
115161
  }
@@ -114755,6 +115165,11 @@ class WebFetchTool {
114755
115165
  return text.slice(0, maxLength) + `
114756
115166
 
114757
115167
  [Content truncated...]`;
115168
+ }
115169
+ if (htmlTruncated) {
115170
+ return (text || "No readable content found on page") + `
115171
+
115172
+ [Content truncated due to size limit...]`;
114758
115173
  }
114759
115174
  return text || "No readable content found on page";
114760
115175
  } catch (error) {
@@ -114848,7 +115263,7 @@ class WebSearchTool {
114848
115263
  retryable: false
114849
115264
  });
114850
115265
  }
114851
- const html = await response.text();
115266
+ const { text: html } = await readResponseWithLimit(response, 2 * 1024 * 1024);
114852
115267
  const results = parseDuckDuckGoResults(html, maxResults);
114853
115268
  if (results.length === 0) {
114854
115269
  return `No results found for "${query}"`;
@@ -114998,7 +115413,7 @@ class CurlTool {
114998
115413
  });
114999
115414
  }
115000
115415
  const hostname = parsedUrl.hostname;
115001
- if (await isPrivateHostOrResolved(hostname)) {
115416
+ if (await isPrivateHostOrResolved2(hostname)) {
115002
115417
  throw new ToolExecutionError("Cannot fetch from local/private network addresses for security reasons", {
115003
115418
  toolName: "curl",
115004
115419
  toolInput: input,
@@ -115063,15 +115478,18 @@ class CurlTool {
115063
115478
  }
115064
115479
  const contentType = response.headers.get("content-type") || "";
115065
115480
  let responseBody;
115481
+ let truncatedBySize = false;
115482
+ const { text: rawBody, truncated } = await readResponseWithLimit(response);
115483
+ truncatedBySize = truncated;
115066
115484
  if (contentType.includes("application/json")) {
115067
115485
  try {
115068
- const json = await response.json();
115486
+ const json = JSON.parse(rawBody);
115069
115487
  responseBody = JSON.stringify(json, null, 2);
115070
115488
  } catch {
115071
- responseBody = await response.text();
115489
+ responseBody = rawBody;
115072
115490
  }
115073
115491
  } else {
115074
- responseBody = await response.text();
115492
+ responseBody = rawBody;
115075
115493
  if (contentType.includes("text/html")) {
115076
115494
  responseBody = extractReadableText(responseBody);
115077
115495
  }
@@ -115081,6 +115499,10 @@ class CurlTool {
115081
115499
  responseBody = responseBody.slice(0, maxLength) + `
115082
115500
 
115083
115501
  [Content truncated...]`;
115502
+ } else if (truncatedBySize) {
115503
+ responseBody = responseBody + `
115504
+
115505
+ [Content truncated due to size limit...]`;
115084
115506
  }
115085
115507
  const statusLine = `HTTP ${response.status} ${response.statusText}`;
115086
115508
  return `${statusLine}
@@ -115126,33 +115548,35 @@ class WebTools {
115126
115548
  registry.register(CurlTool.tool, CurlTool.executor);
115127
115549
  }
115128
115550
  }
115129
- var lookupFn = dnsLookup;
115130
- async function isPrivateHostOrResolved(hostname) {
115131
- if (isPrivateHost(hostname)) {
115551
+ var lookupFn2 = dnsLookup2;
115552
+ async function isPrivateHostOrResolved2(hostname) {
115553
+ if (isPrivateHost2(hostname)) {
115132
115554
  return true;
115133
115555
  }
115134
- const host = normalizeHostname(hostname);
115135
- if (host === "" || isIpLiteral(host)) {
115556
+ const host = normalizeHostname2(hostname);
115557
+ if (host === "" || isIpLiteral2(host)) {
115136
115558
  return false;
115137
115559
  }
115138
115560
  try {
115139
- const results = await lookupFn(host, { all: true });
115561
+ const results = await lookupFn2(host, { all: true });
115140
115562
  for (const result of results) {
115141
- if (isPrivateHost(result.address)) {
115563
+ if (isPrivateHost2(result.address)) {
115142
115564
  return true;
115143
115565
  }
115144
115566
  }
115145
- } catch {}
115146
- return false;
115567
+ return false;
115568
+ } catch {
115569
+ return true;
115570
+ }
115147
115571
  }
115148
- function isIpLiteral(hostname) {
115572
+ function isIpLiteral2(hostname) {
115149
115573
  if (hostname.includes(":"))
115150
115574
  return true;
115151
115575
  if (/^\d+$/.test(hostname))
115152
115576
  return true;
115153
115577
  return /^\d+\.\d+\.\d+\.\d+$/.test(hostname);
115154
115578
  }
115155
- function normalizeHostname(hostname) {
115579
+ function normalizeHostname2(hostname) {
115156
115580
  let host = hostname.toLowerCase();
115157
115581
  if (host.startsWith("[") && host.endsWith("]")) {
115158
115582
  host = host.slice(1, -1);
@@ -115164,8 +115588,8 @@ function normalizeHostname(hostname) {
115164
115588
  host = host.replace(/\.$/, "");
115165
115589
  return host;
115166
115590
  }
115167
- function isPrivateHost(hostname) {
115168
- let host = normalizeHostname(hostname);
115591
+ function isPrivateHost2(hostname) {
115592
+ let host = normalizeHostname2(hostname);
115169
115593
  if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local"))
115170
115594
  return true;
115171
115595
  if (host === "127.0.0.1" || host === "::1" || host === "::" || host === "0:0:0:0:0:0:0:0")
@@ -115175,7 +115599,7 @@ function isPrivateHost(hostname) {
115175
115599
  if (host.startsWith("::ffff:")) {
115176
115600
  const mapped = host.slice("::ffff:".length);
115177
115601
  if (mapped.includes(".")) {
115178
- return isPrivateHost(mapped);
115602
+ return isPrivateHost2(mapped);
115179
115603
  }
115180
115604
  const hexMatch = mapped.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
115181
115605
  if (hexMatch) {
@@ -115187,7 +115611,7 @@ function isPrivateHost(hostname) {
115187
115611
  low >> 8 & 255,
115188
115612
  low & 255
115189
115613
  ];
115190
- return isPrivateIPv4(octets2);
115614
+ return isPrivateIPv42(octets2);
115191
115615
  }
115192
115616
  return false;
115193
115617
  }
@@ -115207,9 +115631,9 @@ function isPrivateHost(hostname) {
115207
115631
  return false;
115208
115632
  octets.push(value);
115209
115633
  }
115210
- return isPrivateIPv4(octets);
115634
+ return isPrivateIPv42(octets);
115211
115635
  }
115212
- function isPrivateIPv4(octets) {
115636
+ function isPrivateIPv42(octets) {
115213
115637
  if (octets[0] === 0)
115214
115638
  return true;
115215
115639
  if (octets[0] === 10)
@@ -115561,8 +115985,37 @@ function computeNextRun(schedule, fromTime) {
115561
115985
  return;
115562
115986
  return getNextCronRun(schedule.schedule.cron, fromTime, validTimezone);
115563
115987
  }
115988
+ if (schedule.schedule.kind === "random") {
115989
+ return computeRandomNextRun(schedule.schedule, fromTime);
115990
+ }
115991
+ if (schedule.schedule.kind === "interval") {
115992
+ return computeIntervalNextRun(schedule.schedule, fromTime);
115993
+ }
115564
115994
  return;
115565
115995
  }
115996
+ function computeRandomNextRun(schedule, fromTime) {
115997
+ const { minInterval, maxInterval, unit = "minutes" } = schedule;
115998
+ if (!minInterval || !maxInterval || minInterval <= 0 || maxInterval <= 0) {
115999
+ return;
116000
+ }
116001
+ if (minInterval > maxInterval) {
116002
+ return;
116003
+ }
116004
+ const multiplier = unit === "seconds" ? 1000 : unit === "hours" ? 3600000 : 60000;
116005
+ const minMs = minInterval * multiplier;
116006
+ const maxMs = maxInterval * multiplier;
116007
+ const randomDelay = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
116008
+ return fromTime + randomDelay;
116009
+ }
116010
+ function computeIntervalNextRun(schedule, fromTime) {
116011
+ const { interval, unit = "minutes" } = schedule;
116012
+ if (!interval || interval <= 0) {
116013
+ return;
116014
+ }
116015
+ const multiplier = unit === "seconds" ? 1000 : unit === "hours" ? 3600000 : 60000;
116016
+ const intervalMs = interval * multiplier;
116017
+ return fromTime + intervalMs;
116018
+ }
115566
116019
  async function getDueSchedules(cwd, nowTime) {
115567
116020
  const schedules = await listSchedules(cwd);
115568
116021
  return schedules.filter((schedule) => {
@@ -115734,7 +116187,7 @@ function getTimeZoneOffsetMs(date, timeZone) {
115734
116187
  class SchedulerTool {
115735
116188
  static tool = {
115736
116189
  name: "schedule",
115737
- description: "Create and manage scheduled commands (once or cron).",
116190
+ description: "Create and manage scheduled commands (once, cron, or random interval).",
115738
116191
  parameters: {
115739
116192
  type: "object",
115740
116193
  properties: {
@@ -115755,6 +116208,23 @@ class SchedulerTool {
115755
116208
  type: "string",
115756
116209
  description: "Cron expression for recurring schedule (5 fields)"
115757
116210
  },
116211
+ minInterval: {
116212
+ type: "number",
116213
+ description: "Minimum interval for random scheduling (e.g., 1)"
116214
+ },
116215
+ maxInterval: {
116216
+ type: "number",
116217
+ description: "Maximum interval for random scheduling (e.g., 20)"
116218
+ },
116219
+ unit: {
116220
+ type: "string",
116221
+ description: "Time unit for interval/random scheduling: seconds, minutes, or hours",
116222
+ enum: ["seconds", "minutes", "hours"]
116223
+ },
116224
+ every: {
116225
+ type: "number",
116226
+ description: 'Fixed interval for recurring schedule (e.g., every: 15 with unit: "seconds" = every 15 seconds). Minimum 1 second.'
116227
+ },
115758
116228
  timezone: {
115759
116229
  type: "string",
115760
116230
  description: "IANA timezone name (optional, best-effort)"
@@ -115763,6 +116233,15 @@ class SchedulerTool {
115763
116233
  type: "string",
115764
116234
  description: "Optional description for this schedule"
115765
116235
  },
116236
+ actionType: {
116237
+ type: "string",
116238
+ description: 'Type of action: "command" runs the command, "message" injects custom message into agent session. Default: "command"',
116239
+ enum: ["command", "message"]
116240
+ },
116241
+ message: {
116242
+ type: "string",
116243
+ description: 'Custom message to inject when actionType is "message"'
116244
+ },
115766
116245
  sessionId: {
115767
116246
  type: "string",
115768
116247
  description: "Session id to scope the schedule to"
@@ -115789,7 +116268,15 @@ class SchedulerTool {
115789
116268
  return "No schedules found.";
115790
116269
  const rows = schedules.sort((a, b) => (a.nextRunAt || 0) - (b.nextRunAt || 0)).map((s) => {
115791
116270
  const next = s.nextRunAt ? new Date(s.nextRunAt).toISOString() : "n/a";
115792
- return `- ${s.id} [${s.status}] ${s.command} (next: ${next})`;
116271
+ let scheduleInfo = "";
116272
+ if (s.schedule.kind === "interval" && s.schedule.interval) {
116273
+ scheduleInfo = ` (every ${s.schedule.interval} ${s.schedule.unit || "minutes"})`;
116274
+ } else if (s.schedule.kind === "random" && s.schedule.minInterval && s.schedule.maxInterval) {
116275
+ scheduleInfo = ` (random: ${s.schedule.minInterval}-${s.schedule.maxInterval} ${s.schedule.unit || "minutes"})`;
116276
+ } else if (s.schedule.kind === "cron" && s.schedule.cron) {
116277
+ scheduleInfo = ` (cron: ${s.schedule.cron})`;
116278
+ }
116279
+ return `- ${s.id} [${s.status}] ${s.command}${scheduleInfo} (next: ${next})`;
115793
116280
  });
115794
116281
  return rows.join(`
115795
116282
  `);
@@ -115800,26 +116287,69 @@ class SchedulerTool {
115800
116287
  return "Error: command is required.";
115801
116288
  const at = input.at;
115802
116289
  const cron = input.cron;
115803
- if (!at && !cron)
115804
- return "Error: provide either at (ISO time) or cron.";
116290
+ const minInterval = input.minInterval;
116291
+ const maxInterval = input.maxInterval;
116292
+ const every = input.every;
116293
+ const unit = input.unit;
116294
+ const hasRandom = minInterval !== undefined && maxInterval !== undefined;
116295
+ const hasInterval = every !== undefined;
116296
+ if (!at && !cron && !hasRandom && !hasInterval) {
116297
+ return "Error: provide at (ISO time), cron, every (fixed interval), or minInterval+maxInterval for random scheduling.";
116298
+ }
116299
+ if (hasInterval) {
116300
+ if (every <= 0) {
116301
+ return "Error: every must be a positive number.";
116302
+ }
116303
+ const effectiveUnit = unit || "minutes";
116304
+ const intervalSeconds = effectiveUnit === "seconds" ? every : effectiveUnit === "hours" ? every * 3600 : every * 60;
116305
+ if (intervalSeconds < 1) {
116306
+ return "Error: minimum interval is 1 second.";
116307
+ }
116308
+ }
116309
+ if (hasRandom) {
116310
+ if (minInterval <= 0 || maxInterval <= 0) {
116311
+ return "Error: minInterval and maxInterval must be positive numbers.";
116312
+ }
116313
+ if (minInterval > maxInterval) {
116314
+ return "Error: minInterval cannot be greater than maxInterval.";
116315
+ }
116316
+ }
115805
116317
  const timezone = input.timezone;
115806
116318
  if (timezone && !isValidTimeZone(timezone)) {
115807
116319
  return `Error: invalid timezone "${timezone}".`;
115808
116320
  }
116321
+ const actionType = input.actionType;
116322
+ const message = input.message;
116323
+ let scheduleKind;
116324
+ if (hasInterval) {
116325
+ scheduleKind = "interval";
116326
+ } else if (hasRandom) {
116327
+ scheduleKind = "random";
116328
+ } else if (cron) {
116329
+ scheduleKind = "cron";
116330
+ } else {
116331
+ scheduleKind = "once";
116332
+ }
115809
116333
  const schedule = {
115810
116334
  id: generateId(),
115811
116335
  createdAt: now2,
115812
116336
  updatedAt: now2,
115813
116337
  createdBy: "agent",
115814
116338
  sessionId: typeof input.sessionId === "string" ? input.sessionId : undefined,
116339
+ actionType: actionType || "command",
115815
116340
  command,
116341
+ message: actionType === "message" ? message : undefined,
115816
116342
  description: input.description,
115817
116343
  status: "active",
115818
116344
  schedule: {
115819
- kind: cron ? "cron" : "once",
116345
+ kind: scheduleKind,
115820
116346
  at,
115821
116347
  cron,
115822
- timezone
116348
+ timezone,
116349
+ minInterval: hasRandom ? minInterval : undefined,
116350
+ maxInterval: hasRandom ? maxInterval : undefined,
116351
+ unit: hasRandom || hasInterval ? unit || "minutes" : undefined,
116352
+ interval: hasInterval ? every : undefined
115823
116353
  }
115824
116354
  };
115825
116355
  schedule.nextRunAt = computeNextRun(schedule, now2);
@@ -115827,6 +116357,12 @@ class SchedulerTool {
115827
116357
  return "Error: unable to compute next run for schedule.";
115828
116358
  }
115829
116359
  await saveSchedule(cwd, schedule);
116360
+ if (scheduleKind === "interval") {
116361
+ return `Scheduled ${schedule.command} (${schedule.id}) every ${every} ${unit || "minutes"}, next run: ${new Date(schedule.nextRunAt).toISOString()}`;
116362
+ }
116363
+ if (scheduleKind === "random") {
116364
+ return `Scheduled ${schedule.command} (${schedule.id}) randomly every ${minInterval}-${maxInterval} ${unit || "minutes"}, next run: ${new Date(schedule.nextRunAt).toISOString()}`;
116365
+ }
115830
116366
  return `Scheduled ${schedule.command} (${schedule.id}) for ${new Date(schedule.nextRunAt).toISOString()}`;
115831
116367
  }
115832
116368
  if (action === "delete") {
@@ -115870,6 +116406,8 @@ import { existsSync as existsSync3, writeFileSync as writeFileSync3, unlinkSync
115870
116406
  import { tmpdir } from "os";
115871
116407
  import { join as join7 } from "path";
115872
116408
  import { homedir as homedir6 } from "os";
116409
+ var FETCH_TIMEOUT_MS = 30000;
116410
+ var MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024;
115873
116411
  async function getViuPath() {
115874
116412
  const runtime = getRuntime();
115875
116413
  const explicitPath = process.env.ASSISTANTS_VIU_PATH || process.env.VIU_PATH;
@@ -115937,7 +116475,18 @@ class ImageDisplayTool {
115937
116475
  let tempFile = null;
115938
116476
  if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
115939
116477
  try {
115940
- const response = await fetch(imagePath);
116478
+ const url = new URL(imagePath);
116479
+ if (await isPrivateHostOrResolved(url.hostname)) {
116480
+ return "Error: Cannot fetch from local/private network addresses for security reasons";
116481
+ }
116482
+ const controller = new AbortController;
116483
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
116484
+ let response;
116485
+ try {
116486
+ response = await fetch(imagePath, { signal: controller.signal });
116487
+ } finally {
116488
+ clearTimeout(timeoutId);
116489
+ }
115941
116490
  if (!response.ok) {
115942
116491
  return `Error: Failed to fetch image: HTTP ${response.status}`;
115943
116492
  }
@@ -115945,12 +116494,47 @@ class ImageDisplayTool {
115945
116494
  if (!contentType.startsWith("image/")) {
115946
116495
  return `Error: URL does not point to an image (content-type: ${contentType})`;
115947
116496
  }
115948
- const buffer = await response.arrayBuffer();
116497
+ const contentLength = response.headers.get("content-length");
116498
+ if (contentLength) {
116499
+ const size = parseInt(contentLength, 10);
116500
+ if (!isNaN(size) && size > MAX_IMAGE_SIZE_BYTES) {
116501
+ return `Error: Image too large (${Math.round(size / 1024 / 1024)}MB exceeds ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024}MB limit)`;
116502
+ }
116503
+ }
116504
+ const chunks = [];
116505
+ let totalSize = 0;
116506
+ const reader = response.body?.getReader();
116507
+ if (!reader) {
116508
+ return "Error: Failed to read image response";
116509
+ }
116510
+ try {
116511
+ while (true) {
116512
+ const { done, value } = await reader.read();
116513
+ if (done)
116514
+ break;
116515
+ totalSize += value.length;
116516
+ if (totalSize > MAX_IMAGE_SIZE_BYTES) {
116517
+ return `Error: Image too large (exceeds ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024}MB limit)`;
116518
+ }
116519
+ chunks.push(value);
116520
+ }
116521
+ } finally {
116522
+ reader.releaseLock();
116523
+ }
116524
+ const buffer = new Uint8Array(totalSize);
116525
+ let offset = 0;
116526
+ for (const chunk of chunks) {
116527
+ buffer.set(chunk, offset);
116528
+ offset += chunk.length;
116529
+ }
115949
116530
  const ext = contentType.split("/")[1]?.split(";")[0] || "png";
115950
116531
  tempFile = join7(tmpdir(), `assistants-image-${generateId()}.${ext}`);
115951
- writeFileSync3(tempFile, Buffer.from(buffer));
116532
+ writeFileSync3(tempFile, buffer);
115952
116533
  localPath = tempFile;
115953
116534
  } catch (error) {
116535
+ if (error instanceof Error && error.name === "AbortError") {
116536
+ return `Error: Image fetch timed out after ${FETCH_TIMEOUT_MS / 1000} seconds`;
116537
+ }
115954
116538
  return `Error: Failed to fetch image: ${error instanceof Error ? error.message : String(error)}`;
115955
116539
  }
115956
116540
  }
@@ -116592,7 +117176,7 @@ Respond with ALLOW or DENY on the first line, followed by a short reason.`,
116592
117176
  // packages/core/src/skills/loader.ts
116593
117177
  init_src2();
116594
117178
  var import_fast_glob = __toESM(require_out4(), 1);
116595
- import { join as join9, basename, dirname as dirname5 } from "path";
117179
+ import { join as join9, basename as basename2, dirname as dirname5 } from "path";
116596
117180
  import { homedir as homedir7 } from "os";
116597
117181
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
116598
117182
 
@@ -116649,7 +117233,7 @@ class SkillLoader {
116649
117233
  const content = await readFile2(filePath, "utf-8");
116650
117234
  const { frontmatter, content: markdownContent } = parseFrontmatter(content);
116651
117235
  const includeContent = options.includeContent ?? true;
116652
- const dirName = basename(dirname5(filePath));
117236
+ const dirName = basename2(dirname5(filePath));
116653
117237
  const name = frontmatter.name || dirName;
116654
117238
  let description = frontmatter.description || "";
116655
117239
  if (!description && markdownContent) {
@@ -117590,7 +118174,7 @@ function createScopeVerificationHook() {
117590
118174
  }
117591
118175
  // packages/core/src/commands/loader.ts
117592
118176
  import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
117593
- import { join as join11, basename as basename2, extname as extname2 } from "path";
118177
+ import { join as join11, basename as basename3, extname as extname2 } from "path";
117594
118178
  import { homedir as homedir8 } from "os";
117595
118179
  class CommandLoader {
117596
118180
  commands = new Map;
@@ -117630,7 +118214,7 @@ class CommandLoader {
117630
118214
  const runtime = getRuntime();
117631
118215
  const content = await runtime.file(filePath).text();
117632
118216
  const { frontmatter, body } = this.parseFrontmatter(content);
117633
- const fileName = basename2(filePath, ".md");
118217
+ const fileName = basename3(filePath, ".md");
117634
118218
  const name = frontmatter.name || (prefix ? `${prefix}:${fileName}` : fileName);
117635
118219
  const allowedToolsRaw = frontmatter["allowed-tools"];
117636
118220
  const allowedTools = Array.isArray(allowedToolsRaw) ? allowedToolsRaw.map((t) => String(t).trim()).filter(Boolean) : typeof allowedToolsRaw === "string" ? allowedToolsRaw.split(",").map((t) => t.trim()).filter(Boolean) : undefined;
@@ -117723,6 +118307,8 @@ class CommandLoader {
117723
118307
  }
117724
118308
  }
117725
118309
  // packages/core/src/commands/executor.ts
118310
+ var MAX_SHELL_OUTPUT_LENGTH = 64 * 1024;
118311
+
117726
118312
  class CommandExecutor {
117727
118313
  loader;
117728
118314
  constructor(loader) {
@@ -117806,6 +118392,20 @@ Use /help to see available commands.
117806
118392
  `);
117807
118393
  }
117808
118394
  async executeShell(command, cwd) {
118395
+ const validation = validateBashCommand(command);
118396
+ if (!validation.valid) {
118397
+ getSecurityLogger().log({
118398
+ eventType: "blocked_command",
118399
+ severity: validation.severity || "high",
118400
+ details: {
118401
+ tool: "custom_command",
118402
+ command,
118403
+ reason: validation.reason || "Command blocked by security policy"
118404
+ },
118405
+ sessionId: "custom-command"
118406
+ });
118407
+ return `Error: ${validation.reason || "Command blocked by security policy"}`;
118408
+ }
117809
118409
  try {
117810
118410
  const runtime = getRuntime();
117811
118411
  const timeoutMs = 5000;
@@ -117833,10 +118433,15 @@ Use /help to see available commands.
117833
118433
  return `Error: command timed out after ${Math.round(timeoutMs / 1000)}s.`;
117834
118434
  }
117835
118435
  if (exitCode !== 0 && stderr) {
118436
+ const truncatedStderr = stderr.length > MAX_SHELL_OUTPUT_LENGTH ? `${stderr.slice(0, MAX_SHELL_OUTPUT_LENGTH)}...(truncated)` : stderr;
117836
118437
  return `Error (exit ${exitCode}):
117837
- ${stderr}`;
118438
+ ${truncatedStderr}`;
118439
+ }
118440
+ const trimmedOutput = stdout.trim();
118441
+ if (trimmedOutput.length > MAX_SHELL_OUTPUT_LENGTH) {
118442
+ return `${trimmedOutput.slice(0, MAX_SHELL_OUTPUT_LENGTH)}...(output truncated at ${MAX_SHELL_OUTPUT_LENGTH} bytes)`;
117838
118443
  }
117839
- return stdout.trim() || "(no output)";
118444
+ return trimmedOutput || "(no output)";
117840
118445
  } catch (error) {
117841
118446
  return `Error: ${error instanceof Error ? error.message : String(error)}`;
117842
118447
  }
@@ -118690,7 +119295,7 @@ function formatAge(ms) {
118690
119295
  return `${days}d`;
118691
119296
  }
118692
119297
  // packages/core/src/commands/builtin.ts
118693
- var VERSION = "0.6.54";
119298
+ var VERSION = "0.6.55";
118694
119299
  function resolveAuthTimeout(resolve5) {
118695
119300
  resolve5({ exitCode: 1, stdout: { toString: () => "{}" } });
118696
119301
  }
@@ -123551,6 +124156,8 @@ import { join as join20 } from "path";
123551
124156
  import { readFileSync as readFileSync6, unlink as unlink6 } from "fs";
123552
124157
  class AudioRecorder {
123553
124158
  currentProcess = null;
124159
+ stoppedIntentionally = false;
124160
+ currentOutputPath = null;
123554
124161
  async record(options = {}) {
123555
124162
  if (this.currentProcess) {
123556
124163
  throw new Error("Audio recorder is already running.");
@@ -123559,6 +124166,8 @@ class AudioRecorder {
123559
124166
  const sampleRate = options.sampleRate ?? 16000;
123560
124167
  const channels = options.channels ?? 1;
123561
124168
  const output = join20(tmpdir4(), `assistants-record-${Date.now()}.wav`);
124169
+ this.currentOutputPath = output;
124170
+ this.stoppedIntentionally = false;
123562
124171
  const recorder = this.resolveRecorder(sampleRate, channels, duration, output);
123563
124172
  if (!recorder) {
123564
124173
  throw new Error("No supported audio recorder found. Install sox or ffmpeg.");
@@ -123567,7 +124176,7 @@ class AudioRecorder {
123567
124176
  this.currentProcess = spawn2(recorder.command, recorder.args, { stdio: "ignore" });
123568
124177
  this.currentProcess.on("close", (code) => {
123569
124178
  this.currentProcess = null;
123570
- if (code === 0) {
124179
+ if (code === 0 || this.stoppedIntentionally) {
123571
124180
  resolve5();
123572
124181
  } else {
123573
124182
  reject(new Error("Audio recording failed."));
@@ -123580,12 +124189,13 @@ class AudioRecorder {
123580
124189
  });
123581
124190
  const data = readFileSync6(output);
123582
124191
  unlink6(output, () => {});
124192
+ this.currentOutputPath = null;
123583
124193
  return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
123584
124194
  }
123585
124195
  stop() {
123586
124196
  if (this.currentProcess) {
123587
- this.currentProcess.kill();
123588
- this.currentProcess = null;
124197
+ this.stoppedIntentionally = true;
124198
+ this.currentProcess.kill("SIGINT");
123589
124199
  }
123590
124200
  }
123591
124201
  resolveRecorder(sampleRate, channels, duration, output) {
@@ -123759,6 +124369,15 @@ init_src2();
123759
124369
  import { existsSync as existsSync10 } from "fs";
123760
124370
  import { mkdir as mkdir8, readFile as readFile9, writeFile as writeFile8, rm } from "fs/promises";
123761
124371
  import { join as join21 } from "path";
124372
+ var SAFE_ID_PATTERN4 = /^[a-zA-Z0-9_-]+$/;
124373
+ function isValidId(id) {
124374
+ return typeof id === "string" && id.length > 0 && SAFE_ID_PATTERN4.test(id);
124375
+ }
124376
+ function validateId(id, idType) {
124377
+ if (!isValidId(id)) {
124378
+ throw new Error(`Invalid ${idType}: "${id}" contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.`);
124379
+ }
124380
+ }
123762
124381
  var DEFAULT_PROFILE = {
123763
124382
  displayName: "Assistant",
123764
124383
  timezone: "UTC",
@@ -123784,6 +124403,7 @@ class IdentityManager {
123784
124403
  identities = new Map;
123785
124404
  activeId = null;
123786
124405
  constructor(assistantId, basePath) {
124406
+ validateId(assistantId, "assistantId");
123787
124407
  this.assistantId = assistantId;
123788
124408
  this.basePath = basePath;
123789
124409
  }
@@ -123797,6 +124417,7 @@ class IdentityManager {
123797
124417
  return join21(this.identitiesRoot, "active.json");
123798
124418
  }
123799
124419
  identityPath(id) {
124420
+ validateId(id, "identityId");
123800
124421
  return join21(this.identitiesRoot, `${id}.json`);
123801
124422
  }
123802
124423
  assistantConfigPath() {
@@ -123912,7 +124533,8 @@ class IdentityManager {
123912
124533
  try {
123913
124534
  const raw = await readFile9(this.indexPath, "utf-8");
123914
124535
  const data = JSON.parse(raw);
123915
- return { identities: Array.isArray(data.identities) ? data.identities : [] };
124536
+ const identities = Array.isArray(data.identities) ? data.identities : [];
124537
+ return { identities: identities.filter(isValidId) };
123916
124538
  } catch {
123917
124539
  return { identities: [] };
123918
124540
  }
@@ -123950,7 +124572,11 @@ class IdentityManager {
123950
124572
  try {
123951
124573
  const raw = await readFile9(this.activePath, "utf-8");
123952
124574
  const data = JSON.parse(raw);
123953
- return data.id || null;
124575
+ const id = data.id || null;
124576
+ if (id && !isValidId(id)) {
124577
+ return null;
124578
+ }
124579
+ return id;
123954
124580
  } catch {
123955
124581
  return null;
123956
124582
  }
@@ -123972,6 +124598,15 @@ class IdentityManager {
123972
124598
  }
123973
124599
 
123974
124600
  // packages/core/src/identity/assistant-manager.ts
124601
+ var SAFE_ID_PATTERN5 = /^[a-zA-Z0-9_-]+$/;
124602
+ function isValidId2(id) {
124603
+ return typeof id === "string" && id.length > 0 && SAFE_ID_PATTERN5.test(id);
124604
+ }
124605
+ function validateId2(id, idType) {
124606
+ if (!isValidId2(id)) {
124607
+ throw new Error(`Invalid ${idType}: "${id}" contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.`);
124608
+ }
124609
+ }
123975
124610
  var DEFAULT_SETTINGS = {
123976
124611
  model: "claude-opus-4-5"
123977
124612
  };
@@ -123993,6 +124628,7 @@ class AssistantManager {
123993
124628
  return join22(this.basePath, "active.json");
123994
124629
  }
123995
124630
  assistantConfigPath(id) {
124631
+ validateId2(id, "assistantId");
123996
124632
  return join22(this.assistantsRoot, id, "config.json");
123997
124633
  }
123998
124634
  async initialize() {
@@ -124044,6 +124680,7 @@ class AssistantManager {
124044
124680
  return updated;
124045
124681
  }
124046
124682
  async deleteAssistant(id) {
124683
+ validateId2(id, "assistantId");
124047
124684
  if (!this.assistants.has(id)) {
124048
124685
  throw new Error(`Assistant ${id} not found`);
124049
124686
  }
@@ -124084,7 +124721,8 @@ class AssistantManager {
124084
124721
  try {
124085
124722
  const raw = await readFile10(this.indexPath, "utf-8");
124086
124723
  const data = JSON.parse(raw);
124087
- return { assistants: Array.isArray(data.assistants) ? data.assistants : [] };
124724
+ const assistants = Array.isArray(data.assistants) ? data.assistants : [];
124725
+ return { assistants: assistants.filter(isValidId2) };
124088
124726
  } catch {
124089
124727
  return { assistants: [] };
124090
124728
  }
@@ -124113,6 +124751,7 @@ class AssistantManager {
124113
124751
  }
124114
124752
  }
124115
124753
  async persistAssistant(assistant) {
124754
+ validateId2(assistant.id, "assistantId");
124116
124755
  const dir = join22(this.assistantsRoot, assistant.id);
124117
124756
  await mkdir9(dir, { recursive: true });
124118
124757
  await writeFile9(this.assistantConfigPath(assistant.id), JSON.stringify(assistant, null, 2));
@@ -124123,7 +124762,11 @@ class AssistantManager {
124123
124762
  try {
124124
124763
  const raw = await readFile10(this.activePath, "utf-8");
124125
124764
  const data = JSON.parse(raw);
124126
- return data.id || null;
124765
+ const id = data.id || null;
124766
+ if (id && !isValidId2(id)) {
124767
+ return null;
124768
+ }
124769
+ return id;
124127
124770
  } catch {
124128
124771
  return null;
124129
124772
  }
@@ -132992,8 +133635,34 @@ class S3InboxClient {
132992
133635
  }
132993
133636
 
132994
133637
  // packages/core/src/inbox/storage/local-cache.ts
132995
- import { join as join24 } from "path";
133638
+ import { join as join24, basename as basename4 } from "path";
132996
133639
  import { mkdir as mkdir10, readFile as readFile12, writeFile as writeFile11, rm as rm3, readdir as readdir4, stat as stat3 } from "fs/promises";
133640
+ import { createHash as createHash2 } from "crypto";
133641
+ var STRICT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
133642
+ var SAFE_FILENAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
133643
+ function isValidAgentId(id) {
133644
+ return typeof id === "string" && id.length > 0 && STRICT_ID_PATTERN.test(id);
133645
+ }
133646
+ function validateAgentId(id) {
133647
+ if (!isValidAgentId(id)) {
133648
+ throw new Error(`Invalid agentId: "${id}" contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.`);
133649
+ }
133650
+ }
133651
+ function emailIdToFilename(emailId) {
133652
+ if (SAFE_FILENAME_PATTERN.test(emailId) && emailId.length <= 100) {
133653
+ return emailId;
133654
+ }
133655
+ const hash = createHash2("sha256").update(emailId).digest("base64url").slice(0, 16);
133656
+ const readable = emailId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 20);
133657
+ return readable ? `${readable}_${hash}` : hash;
133658
+ }
133659
+ function isValidMappedFilename(filename) {
133660
+ return typeof filename === "string" && filename.length > 0 && SAFE_FILENAME_PATTERN.test(filename);
133661
+ }
133662
+ function sanitizeFilename(filename) {
133663
+ const base = basename4(filename);
133664
+ return base.replace(/[/\\]/g, "_");
133665
+ }
132997
133666
 
132998
133667
  class LocalInboxCache {
132999
133668
  agentId;
@@ -133001,6 +133670,7 @@ class LocalInboxCache {
133001
133670
  cacheDir;
133002
133671
  index = null;
133003
133672
  constructor(options) {
133673
+ validateAgentId(options.agentId);
133004
133674
  this.agentId = options.agentId;
133005
133675
  this.basePath = options.basePath;
133006
133676
  this.cacheDir = join24(this.basePath, this.agentId);
@@ -133030,13 +133700,18 @@ class LocalInboxCache {
133030
133700
  await writeFile11(indexPath, JSON.stringify(this.index, null, 2));
133031
133701
  }
133032
133702
  async saveEmail(email) {
133703
+ const filename = emailIdToFilename(email.id);
133704
+ if (!isValidMappedFilename(filename)) {
133705
+ throw new Error(`Failed to create safe filename for email ID: "${email.id}"`);
133706
+ }
133033
133707
  await this.ensureDirectories();
133034
- const emailPath = join24(this.cacheDir, "emails", `${email.id}.json`);
133708
+ const emailPath = join24(this.cacheDir, "emails", `${filename}.json`);
133035
133709
  await writeFile11(emailPath, JSON.stringify(email, null, 2));
133036
133710
  const index = await this.loadIndex();
133037
133711
  const existingIdx = index.emails.findIndex((e3) => e3.id === email.id);
133038
133712
  const entry = {
133039
133713
  id: email.id,
133714
+ filename,
133040
133715
  messageId: email.messageId,
133041
133716
  from: email.from.name || email.from.address,
133042
133717
  subject: email.subject,
@@ -133047,6 +133722,7 @@ class LocalInboxCache {
133047
133722
  };
133048
133723
  if (existingIdx >= 0) {
133049
133724
  entry.isRead = index.emails[existingIdx].isRead;
133725
+ entry.filename = index.emails[existingIdx].filename || filename;
133050
133726
  index.emails[existingIdx] = entry;
133051
133727
  } else {
133052
133728
  index.emails.unshift(entry);
@@ -133054,8 +133730,17 @@ class LocalInboxCache {
133054
133730
  await this.saveIndex();
133055
133731
  }
133056
133732
  async loadEmail(id) {
133733
+ const index = await this.loadIndex();
133734
+ const entry = index.emails.find((e3) => e3.id === id);
133735
+ if (!entry) {
133736
+ return null;
133737
+ }
133738
+ const filename = entry.filename || emailIdToFilename(id);
133739
+ if (!isValidMappedFilename(filename)) {
133740
+ return null;
133741
+ }
133057
133742
  try {
133058
- const emailPath = join24(this.cacheDir, "emails", `${id}.json`);
133743
+ const emailPath = join24(this.cacheDir, "emails", `${filename}.json`);
133059
133744
  const content = await readFile12(emailPath, "utf-8");
133060
133745
  return JSON.parse(content);
133061
133746
  } catch {
@@ -133107,15 +133792,31 @@ class LocalInboxCache {
133107
133792
  return new Set(index.emails.map((e3) => e3.id));
133108
133793
  }
133109
133794
  async saveAttachment(emailId, filename, content) {
133110
- const attachmentDir = join24(this.cacheDir, "attachments", emailId);
133795
+ const emailFilename = emailIdToFilename(emailId);
133796
+ if (!isValidMappedFilename(emailFilename)) {
133797
+ throw new Error(`Failed to create safe directory name for email ID: "${emailId}"`);
133798
+ }
133799
+ const safeFilename = sanitizeFilename(filename);
133800
+ if (!safeFilename) {
133801
+ throw new Error("Invalid attachment filename");
133802
+ }
133803
+ const attachmentDir = join24(this.cacheDir, "attachments", emailFilename);
133111
133804
  await mkdir10(attachmentDir, { recursive: true });
133112
- const attachmentPath = join24(attachmentDir, filename);
133805
+ const attachmentPath = join24(attachmentDir, safeFilename);
133113
133806
  await writeFile11(attachmentPath, content);
133114
133807
  return attachmentPath;
133115
133808
  }
133116
133809
  async getAttachmentPath(emailId, filename) {
133810
+ const emailFilename = emailIdToFilename(emailId);
133811
+ if (!isValidMappedFilename(emailFilename)) {
133812
+ return null;
133813
+ }
133814
+ const safeFilename = sanitizeFilename(filename);
133815
+ if (!safeFilename) {
133816
+ return null;
133817
+ }
133117
133818
  try {
133118
- const attachmentPath = join24(this.cacheDir, "attachments", emailId, filename);
133819
+ const attachmentPath = join24(this.cacheDir, "attachments", emailFilename, safeFilename);
133119
133820
  await stat3(attachmentPath);
133120
133821
  return attachmentPath;
133121
133822
  } catch {
@@ -133134,26 +133835,29 @@ class LocalInboxCache {
133134
133835
  async cleanup(maxAgeDays = 30) {
133135
133836
  const index = await this.loadIndex();
133136
133837
  const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
133137
- const removed = [];
133838
+ const toRemove = [];
133138
133839
  for (const entry of index.emails) {
133139
133840
  const cachedAt = new Date(entry.cachedAt).getTime();
133140
133841
  if (cachedAt < cutoff) {
133141
- removed.push(entry.id);
133842
+ const filename = entry.filename || emailIdToFilename(entry.id);
133843
+ if (isValidMappedFilename(filename)) {
133844
+ toRemove.push({ id: entry.id, filename });
133845
+ }
133142
133846
  }
133143
133847
  }
133144
- for (const id of removed) {
133848
+ for (const { id, filename } of toRemove) {
133145
133849
  index.emails = index.emails.filter((e3) => e3.id !== id);
133146
133850
  try {
133147
- await rm3(join24(this.cacheDir, "emails", `${id}.json`));
133851
+ await rm3(join24(this.cacheDir, "emails", `${filename}.json`));
133148
133852
  } catch {}
133149
133853
  try {
133150
- await rm3(join24(this.cacheDir, "attachments", id), { recursive: true });
133854
+ await rm3(join24(this.cacheDir, "attachments", filename), { recursive: true });
133151
133855
  } catch {}
133152
133856
  }
133153
- if (removed.length > 0) {
133857
+ if (toRemove.length > 0) {
133154
133858
  await this.saveIndex();
133155
133859
  }
133156
- return removed.length;
133860
+ return toRemove.length;
133157
133861
  }
133158
133862
  async getCacheSize() {
133159
133863
  let totalSize = 0;
@@ -138527,12 +139231,21 @@ function getMessagesBasePath() {
138527
139231
  const home = envOverride && envOverride.trim() ? envOverride : homedir14();
138528
139232
  return join26(home, ".assistants", "messages");
138529
139233
  }
139234
+ var SAFE_ID_PATTERN6 = /^[a-zA-Z0-9_-]+$/;
138530
139235
 
138531
139236
  class LocalMessagesStorage {
138532
139237
  basePath;
138533
139238
  constructor(options = {}) {
138534
139239
  this.basePath = options.basePath || getMessagesBasePath();
138535
139240
  }
139241
+ validateSafeId(id, idType) {
139242
+ if (!id || typeof id !== "string") {
139243
+ throw new Error(`Invalid ${idType}: must be a non-empty string`);
139244
+ }
139245
+ if (!SAFE_ID_PATTERN6.test(id)) {
139246
+ throw new Error(`Invalid ${idType}: "${id}" contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.`);
139247
+ }
139248
+ }
138536
139249
  async ensureDirectories(agentId) {
138537
139250
  const agentPath = this.getAgentPath(agentId);
138538
139251
  await Promise.all([
@@ -138542,15 +139255,18 @@ class LocalMessagesStorage {
138542
139255
  ]);
138543
139256
  }
138544
139257
  getAgentPath(agentId) {
139258
+ this.validateSafeId(agentId, "agentId");
138545
139259
  return join26(this.basePath, agentId);
138546
139260
  }
138547
139261
  getIndexPath(agentId) {
138548
139262
  return join26(this.getAgentPath(agentId), "index.json");
138549
139263
  }
138550
139264
  getMessagePath(agentId, messageId) {
139265
+ this.validateSafeId(messageId, "messageId");
138551
139266
  return join26(this.getAgentPath(agentId), "messages", `${messageId}.json`);
138552
139267
  }
138553
139268
  getThreadPath(agentId, threadId) {
139269
+ this.validateSafeId(threadId, "threadId");
138554
139270
  return join26(this.getAgentPath(agentId), "threads", `${threadId}.json`);
138555
139271
  }
138556
139272
  getRegistryPath() {
@@ -140479,7 +141195,9 @@ ${effects.message}
140479
141195
  }
140480
141196
  }
140481
141197
  startHeartbeat() {
140482
- if (!this.config?.scheduler?.enabled)
141198
+ if (!this.config)
141199
+ return;
141200
+ if (this.config.scheduler?.enabled === false)
140483
141201
  return;
140484
141202
  const interval = this.config.scheduler?.heartbeatIntervalMs ?? 30000;
140485
141203
  if (this.heartbeatTimer)
@@ -140557,7 +141275,8 @@ ${effects.message}
140557
141275
  leaseTimer.unref();
140558
141276
  }
140559
141277
  try {
140560
- const result = await this.runMessage(current.command, "schedule");
141278
+ const contentToRun = current.actionType === "message" ? current.message || current.command : current.command;
141279
+ const result = await this.runMessage(contentToRun, "schedule");
140561
141280
  const now2 = Date.now();
140562
141281
  await updateSchedule(this.cwd, schedule.id, (live) => {
140563
141282
  const updated = {
@@ -140801,6 +141520,11 @@ init_src2();
140801
141520
  // packages/core/src/logger.ts
140802
141521
  import { existsSync as existsSync12, mkdirSync as mkdirSync8, appendFileSync, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
140803
141522
  import { join as join28 } from "path";
141523
+ var SAFE_ID_PATTERN7 = /^[a-zA-Z0-9_-]+$/;
141524
+ function isValidId3(id) {
141525
+ return typeof id === "string" && id.length > 0 && SAFE_ID_PATTERN7.test(id);
141526
+ }
141527
+
140804
141528
  class Logger {
140805
141529
  logDir;
140806
141530
  logFile;
@@ -140852,9 +141576,13 @@ class SessionStorage {
140852
141576
  sessionFile;
140853
141577
  sessionId;
140854
141578
  constructor(sessionId, basePath, assistantId) {
141579
+ if (!isValidId3(sessionId)) {
141580
+ throw new Error(`Invalid sessionId: "${sessionId}" contains invalid characters. Only alphanumeric characters, hyphens, and underscores are allowed.`);
141581
+ }
140855
141582
  this.sessionId = sessionId;
140856
141583
  const root = basePath || getConfigDir();
140857
- this.sessionsDir = assistantId ? join28(root, "assistants", assistantId, "sessions") : join28(root, "sessions");
141584
+ const safeAssistantId = isValidId3(assistantId) ? assistantId : null;
141585
+ this.sessionsDir = safeAssistantId ? join28(root, "assistants", safeAssistantId, "sessions") : join28(root, "sessions");
140858
141586
  this.ensureDir(this.sessionsDir);
140859
141587
  this.sessionFile = join28(this.sessionsDir, `${sessionId}.json`);
140860
141588
  }
@@ -140887,15 +141615,20 @@ class SessionStorage {
140887
141615
  return null;
140888
141616
  const raw = readFileSync8(activePath, "utf-8");
140889
141617
  const data = JSON.parse(raw);
140890
- return data.id || null;
141618
+ const id = data.id || null;
141619
+ if (id && !isValidId3(id)) {
141620
+ return null;
141621
+ }
141622
+ return id;
140891
141623
  } catch {
140892
141624
  return null;
140893
141625
  }
140894
141626
  }
140895
141627
  static resolveSessionsDir(assistantId) {
140896
141628
  const root = getConfigDir();
140897
- const resolvedId = assistantId ?? SessionStorage.getActiveAssistantId();
140898
- if (resolvedId) {
141629
+ const safeAssistantId = isValidId3(assistantId) ? assistantId : null;
141630
+ const resolvedId = safeAssistantId ?? SessionStorage.getActiveAssistantId();
141631
+ if (resolvedId && isValidId3(resolvedId)) {
140899
141632
  const assistantDir = join28(root, "assistants", resolvedId, "sessions");
140900
141633
  if (existsSync12(assistantDir)) {
140901
141634
  return assistantDir;
@@ -140931,6 +141664,9 @@ class SessionStorage {
140931
141664
  return sessions[0] || null;
140932
141665
  }
140933
141666
  static loadSession(sessionId, assistantId) {
141667
+ if (!isValidId3(sessionId)) {
141668
+ return null;
141669
+ }
140934
141670
  const sessionsDir = SessionStorage.resolveSessionsDir(assistantId);
140935
141671
  const sessionFile = join28(sessionsDir, `${sessionId}.json`);
140936
141672
  try {
@@ -147338,7 +148074,7 @@ var COMMANDS = [
147338
148074
  { name: "/help", description: "show available commands" },
147339
148075
  { name: "/clear", description: "clear the conversation" },
147340
148076
  { name: "/new", description: "start a new conversation" },
147341
- { name: "/session", description: "list/switch sessions (Ctrl+S)" },
148077
+ { name: "/session", description: "list/switch sessions (Ctrl+])" },
147342
148078
  { name: "/status", description: "show session status" },
147343
148079
  { name: "/tokens", description: "show token usage" },
147344
148080
  { name: "/cost", description: "show estimated API cost" },
@@ -147368,10 +148104,23 @@ function Input({
147368
148104
  commands: commands3,
147369
148105
  skills = [],
147370
148106
  isAskingUser = false,
147371
- askPlaceholder
148107
+ askPlaceholder,
148108
+ allowBlankAnswer = false
147372
148109
  }) {
147373
- const [value, setValue] = import_react27.useState("");
147374
- const [cursor, setCursor] = import_react27.useState(0);
148110
+ const [inputState, setInputState] = import_react27.useState({ value: "", cursor: 0 });
148111
+ const { value, cursor } = inputState;
148112
+ const setValue = (newValue) => {
148113
+ setInputState((prev) => ({
148114
+ ...prev,
148115
+ value: typeof newValue === "function" ? newValue(prev.value) : newValue
148116
+ }));
148117
+ };
148118
+ const setCursor = (newCursor) => {
148119
+ setInputState((prev) => ({
148120
+ ...prev,
148121
+ cursor: typeof newCursor === "function" ? newCursor(prev.cursor) : newCursor
148122
+ }));
148123
+ };
147375
148124
  const [preferredColumn, setPreferredColumn] = import_react27.useState(null);
147376
148125
  const [selectedIndex, setSelectedIndex] = import_react27.useState(0);
147377
148126
  const { stdout } = use_stdout_default();
@@ -147399,7 +148148,7 @@ function Input({
147399
148148
  return "command";
147400
148149
  }
147401
148150
  return null;
147402
- }, [value]);
148151
+ }, [value, isAskingUser]);
147403
148152
  const filteredCommands = import_react27.useMemo(() => {
147404
148153
  if (autocompleteMode !== "command")
147405
148154
  return [];
@@ -147421,14 +148170,13 @@ function Input({
147421
148170
  setSelectedIndex((prev) => Math.min(prev, autocompleteItems.length - 1));
147422
148171
  }, [autocompleteItems.length]);
147423
148172
  const setValueAndCursor = (nextValue, nextCursor = nextValue.length) => {
147424
- setValue(nextValue);
147425
148173
  const clamped = Math.max(0, Math.min(nextCursor, nextValue.length));
147426
- setCursor(clamped);
148174
+ setInputState({ value: nextValue, cursor: clamped });
147427
148175
  setPreferredColumn(null);
147428
148176
  setSelectedIndex(0);
147429
148177
  };
147430
148178
  const handleSubmit = (submittedValue) => {
147431
- if (!submittedValue.trim())
148179
+ if (!submittedValue.trim() && !allowBlankAnswer)
147432
148180
  return;
147433
148181
  if (autocompleteMode === "command" && filteredCommands.length > 0 && !submittedValue.includes(" ")) {
147434
148182
  const selected = filteredCommands[selectedIndex] || filteredCommands[0];
@@ -147465,26 +148213,53 @@ function Input({
147465
148213
  const insertText = (text) => {
147466
148214
  if (!text)
147467
148215
  return;
147468
- const next = value.slice(0, cursor) + text + value.slice(cursor);
147469
- setValue(next);
147470
- setCursor(cursor + text.length);
148216
+ setInputState((prev) => ({
148217
+ value: prev.value.slice(0, prev.cursor) + text + prev.value.slice(prev.cursor),
148218
+ cursor: prev.cursor + text.length
148219
+ }));
147471
148220
  setPreferredColumn(null);
147472
148221
  setSelectedIndex(0);
147473
148222
  };
147474
148223
  const deleteBackward = () => {
147475
- if (cursor === 0)
147476
- return;
147477
- const next = value.slice(0, cursor - 1) + value.slice(cursor);
147478
- setValue(next);
147479
- setCursor(cursor - 1);
148224
+ setInputState((prev) => {
148225
+ if (prev.cursor === 0)
148226
+ return prev;
148227
+ return {
148228
+ value: prev.value.slice(0, prev.cursor - 1) + prev.value.slice(prev.cursor),
148229
+ cursor: prev.cursor - 1
148230
+ };
148231
+ });
147480
148232
  setPreferredColumn(null);
147481
148233
  setSelectedIndex(0);
147482
148234
  };
147483
148235
  const deleteForward = () => {
147484
- if (cursor >= value.length)
147485
- return;
147486
- const next = value.slice(0, cursor) + value.slice(cursor + 1);
147487
- setValue(next);
148236
+ setInputState((prev) => {
148237
+ if (prev.cursor >= prev.value.length)
148238
+ return prev;
148239
+ return {
148240
+ value: prev.value.slice(0, prev.cursor) + prev.value.slice(prev.cursor + 1),
148241
+ cursor: prev.cursor
148242
+ };
148243
+ });
148244
+ setPreferredColumn(null);
148245
+ setSelectedIndex(0);
148246
+ };
148247
+ const deleteWordBackward = () => {
148248
+ setInputState((prev) => {
148249
+ if (prev.cursor === 0)
148250
+ return prev;
148251
+ let start = prev.cursor - 1;
148252
+ while (start >= 0 && /\s/.test(prev.value[start])) {
148253
+ start--;
148254
+ }
148255
+ while (start >= 0 && !/\s/.test(prev.value[start])) {
148256
+ start--;
148257
+ }
148258
+ return {
148259
+ value: prev.value.slice(0, start + 1) + prev.value.slice(prev.cursor),
148260
+ cursor: start + 1
148261
+ };
148262
+ });
147488
148263
  setPreferredColumn(null);
147489
148264
  setSelectedIndex(0);
147490
148265
  };
@@ -147495,6 +148270,10 @@ function Input({
147495
148270
  }
147496
148271
  return;
147497
148272
  }
148273
+ if (key.ctrl && input === "w") {
148274
+ deleteWordBackward();
148275
+ return;
148276
+ }
147498
148277
  if (!isAskingUser) {
147499
148278
  if (key.tab) {
147500
148279
  if (autocompleteItems.length > 0) {
@@ -147547,14 +148326,20 @@ function Input({
147547
148326
  moveCursorTo(line.start + line.text.length, false);
147548
148327
  return;
147549
148328
  }
147550
- if (key.backspace || input === "\x7F" || input === "\b") {
148329
+ if (input === "\x7F" || input === "\b" || key.backspace || key.delete) {
147551
148330
  deleteBackward();
147552
148331
  return;
147553
148332
  }
147554
- if (key.delete || input === "\x1B[3~") {
148333
+ if (input === "\x1B[3~") {
147555
148334
  deleteForward();
147556
148335
  return;
147557
148336
  }
148337
+ if (input === "\x1B\r" || input === `\x1B
148338
+ `) {
148339
+ insertText(`
148340
+ `);
148341
+ return;
148342
+ }
147558
148343
  if (key.return) {
147559
148344
  if ((key.shift || key.ctrl) && value.trim()) {
147560
148345
  onSubmit(value, "interrupt");
@@ -148920,7 +149705,7 @@ function groupConsecutiveToolMessages(messages2) {
148920
149705
 
148921
149706
  // packages/terminal/src/components/Messages.tsx
148922
149707
  var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
148923
- import { basename as basename3 } from "path";
149708
+ import { basename as basename5 } from "path";
148924
149709
  function Messages3({
148925
149710
  messages: messages2,
148926
149711
  currentResponse,
@@ -149431,10 +150216,10 @@ function getToolContext(toolCall) {
149431
150216
  return truncate(String(input.command || ""), 20);
149432
150217
  case "read":
149433
150218
  const path2 = String(input.path || input.file_path || "");
149434
- return basename3(path2) || path2;
150219
+ return basename5(path2) || path2;
149435
150220
  case "write":
149436
150221
  const writePath = String(input.filename || input.path || input.file_path || "");
149437
- return basename3(writePath) || writePath;
150222
+ return basename5(writePath) || writePath;
149438
150223
  case "glob":
149439
150224
  return truncate(String(input.pattern || ""), 20);
149440
150225
  case "grep":
@@ -149778,7 +150563,7 @@ function Status({
149778
150563
  dimColor: true,
149779
150564
  children: [
149780
150565
  "/help",
149781
- sessionCount && sessionCount > 1 ? " \xB7 Ctrl+S" : ""
150566
+ sessionCount && sessionCount > 1 ? " \xB7 Ctrl+]" : ""
149782
150567
  ]
149783
150568
  }, undefined, true, undefined, this),
149784
150569
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
@@ -149885,6 +150670,7 @@ function Spinner2({ label }) {
149885
150670
 
149886
150671
  // packages/terminal/src/components/ProcessingIndicator.tsx
149887
150672
  var import_react31 = __toESM(require_react(), 1);
150673
+ init_src2();
149888
150674
  var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
149889
150675
  function ProcessingIndicator({
149890
150676
  isProcessing,
@@ -149893,6 +150679,7 @@ function ProcessingIndicator({
149893
150679
  isThinking = false
149894
150680
  }) {
149895
150681
  const [elapsed, setElapsed] = import_react31.useState(0);
150682
+ const loadingWord = import_react31.useMemo(() => getRandomLoadingWord(), [isProcessing]);
149896
150683
  import_react31.useEffect(() => {
149897
150684
  if (!isProcessing || !startTime) {
149898
150685
  setElapsed(0);
@@ -149930,7 +150717,7 @@ function ProcessingIndicator({
149930
150717
  if (isThinking) {
149931
150718
  parts.push("thinking");
149932
150719
  }
149933
- const label = isThinking ? "Metamorphosing" : "Working";
150720
+ const label = loadingWord;
149934
150721
  return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
149935
150722
  marginY: 1,
149936
150723
  children: [
@@ -150319,7 +151106,7 @@ function AskUserPanel({
150319
151106
  marginTop: 1,
150320
151107
  children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Text3, {
150321
151108
  dimColor: true,
150322
- children: "Multi-line answer allowed. Paste text and press Enter."
151109
+ children: "Multi-line answer allowed. Use Alt+Enter to insert newlines."
150323
151110
  }, undefined, false, undefined, this)
150324
151111
  }, undefined, false, undefined, this),
150325
151112
  /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(Box_default, {
@@ -150376,6 +151163,7 @@ function App2({ cwd: cwd2, version: version3 }) {
150376
151163
  const [askUserState, setAskUserState] = import_react33.useState(null);
150377
151164
  const [processingStartTime, setProcessingStartTime] = import_react33.useState();
150378
151165
  const [currentTurnTokens, setCurrentTurnTokens] = import_react33.useState(0);
151166
+ const [lastWorkedFor, setLastWorkedFor] = import_react33.useState();
150379
151167
  const [skills, setSkills] = import_react33.useState([]);
150380
151168
  const [commands3, setCommands] = import_react33.useState([]);
150381
151169
  const responseRef = import_react33.useRef("");
@@ -150448,6 +151236,7 @@ function App2({ cwd: cwd2, version: version3 }) {
150448
151236
  const turnIdRef = import_react33.useRef(0);
150449
151237
  const initStateRef = import_react33.useRef("idle");
150450
151238
  const isMountedRef = import_react33.useRef(true);
151239
+ const handlersRegisteredRef = import_react33.useRef(false);
150451
151240
  import_react33.useEffect(() => {
150452
151241
  isProcessingRef.current = isProcessing;
150453
151242
  }, [isProcessing]);
@@ -150527,9 +151316,7 @@ function App2({ cwd: cwd2, version: version3 }) {
150527
151316
  }
150528
151317
  if (processingStartTimeRef.current) {
150529
151318
  const workedFor = formatElapsedDuration(Date.now() - processingStartTimeRef.current);
150530
- content = content ? `${content}
150531
-
150532
- \u273B Worked for ${workedFor}` : `\u273B Worked for ${workedFor}`;
151319
+ setLastWorkedFor(workedFor);
150533
151320
  }
150534
151321
  setMessages((prev) => [
150535
151322
  ...prev,
@@ -150569,10 +151356,11 @@ function App2({ cwd: cwd2, version: version3 }) {
150569
151356
  identityInfo,
150570
151357
  processingStartTime,
150571
151358
  currentTurnTokens,
150572
- error: error2
151359
+ error: error2,
151360
+ lastWorkedFor
150573
151361
  });
150574
151362
  }
150575
- }, [activeSessionId, messages2, tokenUsage, energyState, voiceState, identityInfo, processingStartTime, currentTurnTokens, error2]);
151363
+ }, [activeSessionId, messages2, tokenUsage, energyState, voiceState, identityInfo, processingStartTime, currentTurnTokens, error2, lastWorkedFor]);
150576
151364
  const loadSessionState = import_react33.useCallback((sessionId) => {
150577
151365
  const state = sessionUIStates.current.get(sessionId);
150578
151366
  const askState = askUserStateRef.current.get(sessionId) || null;
@@ -150592,6 +151380,7 @@ function App2({ cwd: cwd2, version: version3 }) {
150592
151380
  setProcessingStartTime(state.processingStartTime);
150593
151381
  setCurrentTurnTokens(state.currentTurnTokens);
150594
151382
  setError(state.error);
151383
+ setLastWorkedFor(state.lastWorkedFor);
150595
151384
  setAskUserState(askState);
150596
151385
  } else {
150597
151386
  setMessages([]);
@@ -150609,6 +151398,7 @@ function App2({ cwd: cwd2, version: version3 }) {
150609
151398
  setProcessingStartTime(undefined);
150610
151399
  setCurrentTurnTokens(0);
150611
151400
  setError(null);
151401
+ setLastWorkedFor(undefined);
150612
151402
  setAskUserState(askState);
150613
151403
  }
150614
151404
  }, []);
@@ -150751,21 +151541,24 @@ function App2({ cwd: cwd2, version: version3 }) {
150751
151541
  let cancelled = false;
150752
151542
  const initSession = async () => {
150753
151543
  try {
150754
- registry.onChunk(handleChunk);
150755
- registry.onError((err) => {
150756
- const finalized = finalizeResponse("error");
150757
- if (finalized) {
150758
- skipNextDoneRef.current = true;
150759
- }
150760
- resetTurnState();
150761
- setError(err.message);
150762
- setIsProcessing(false);
150763
- isProcessingRef.current = false;
150764
- const active = registryRef.current.getActiveSession();
150765
- if (active) {
150766
- registryRef.current.setProcessing(active.id, false);
150767
- }
150768
- });
151544
+ if (!handlersRegisteredRef.current) {
151545
+ handlersRegisteredRef.current = true;
151546
+ registry.onChunk(handleChunk);
151547
+ registry.onError((err) => {
151548
+ const finalized = finalizeResponse("error");
151549
+ if (finalized) {
151550
+ skipNextDoneRef.current = true;
151551
+ }
151552
+ resetTurnState();
151553
+ setError(err.message);
151554
+ setIsProcessing(false);
151555
+ isProcessingRef.current = false;
151556
+ const active = registryRef.current.getActiveSession();
151557
+ if (active) {
151558
+ registryRef.current.setProcessing(active.id, false);
151559
+ }
151560
+ });
151561
+ }
150769
151562
  const session = await registry.createSession(cwd2);
150770
151563
  setActiveSessionId(session.id);
150771
151564
  session.client.setAskUserHandler((request2) => beginAskUser(session.id, request2));
@@ -150950,7 +151743,7 @@ function App2({ cwd: cwd2, version: version3 }) {
150950
151743
  }
150951
151744
  }, [cwd2, registry, saveCurrentSessionState, loadSessionState, beginAskUser]);
150952
151745
  use_input_default((input, key) => {
150953
- if (key.ctrl && input === "s") {
151746
+ if (key.ctrl && input === "]") {
150954
151747
  if (sessions.length > 0) {
150955
151748
  setShowSessionSelector(true);
150956
151749
  }
@@ -151113,6 +151906,7 @@ function App2({ cwd: cwd2, version: version3 }) {
151113
151906
  pendingSendsRef.current = pendingSendsRef.current.filter((entry) => entry.sessionId !== activeSession.id);
151114
151907
  setActivityLog([]);
151115
151908
  activityLogRef.current = [];
151909
+ setLastWorkedFor(undefined);
151116
151910
  sessionUIStates.current.set(activeSession.id, {
151117
151911
  messages: [],
151118
151912
  currentResponse: "",
@@ -151125,7 +151919,8 @@ function App2({ cwd: cwd2, version: version3 }) {
151125
151919
  identityInfo,
151126
151920
  processingStartTime: undefined,
151127
151921
  currentTurnTokens: 0,
151128
- error: null
151922
+ error: null,
151923
+ lastWorkedFor: undefined
151129
151924
  });
151130
151925
  } else {
151131
151926
  const userMessage = {
@@ -151220,7 +152015,7 @@ function App2({ cwd: cwd2, version: version3 }) {
151220
152015
  backgroundProcessingCount,
151221
152016
  " session",
151222
152017
  backgroundProcessingCount > 1 ? "s" : "",
151223
- " processing in background (Ctrl+S to switch)"
152018
+ " processing in background (Ctrl+] to switch)"
151224
152019
  ]
151225
152020
  }, undefined, true, undefined, this)
151226
152021
  }, undefined, false, undefined, this),
@@ -151286,6 +152081,17 @@ function App2({ cwd: cwd2, version: version3 }) {
151286
152081
  tokenCount: currentTurnTokens,
151287
152082
  isThinking
151288
152083
  }, undefined, false, undefined, this),
152084
+ !isProcessing && lastWorkedFor && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
152085
+ marginBottom: 0,
152086
+ marginLeft: 2,
152087
+ children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text3, {
152088
+ color: "gray",
152089
+ children: [
152090
+ "\u273B Worked for ",
152091
+ lastWorkedFor
152092
+ ]
152093
+ }, undefined, true, undefined, this)
152094
+ }, undefined, false, undefined, this),
151289
152095
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Input, {
151290
152096
  onSubmit: handleSubmit,
151291
152097
  isProcessing: isBusy,
@@ -151293,7 +152099,8 @@ function App2({ cwd: cwd2, version: version3 }) {
151293
152099
  commands: commands3,
151294
152100
  skills,
151295
152101
  isAskingUser: Boolean(activeAskQuestion),
151296
- askPlaceholder
152102
+ askPlaceholder,
152103
+ allowBlankAnswer: activeAskQuestion?.required === false
151297
152104
  }, undefined, false, undefined, this),
151298
152105
  /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Status, {
151299
152106
  isProcessing: isBusy,
@@ -151492,44 +152299,9 @@ function sanitizeTerminalOutput(chunk) {
151492
152299
  return chunk.replace(/\x1b\[2J\x1b\[3J\x1b\[H/g, "\x1B[H").replace(/\x1b\[3J/g, "");
151493
152300
  }
151494
152301
 
151495
- // packages/terminal/src/index.tsx
151496
- var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
151497
- setRuntime(bunRuntime);
151498
- var VERSION3 = "0.6.54";
151499
- var SYNC_START = "\x1B[?2026h";
151500
- var SYNC_END = "\x1B[?2026l";
151501
- function enableSynchronizedOutput() {
151502
- const originalWrite = process.stdout.write.bind(process.stdout);
151503
- let buffer = "";
151504
- let flushTimeout = null;
151505
- const flush = () => {
151506
- if (buffer) {
151507
- const safe = sanitizeTerminalOutput(buffer);
151508
- originalWrite(SYNC_START + safe + SYNC_END);
151509
- buffer = "";
151510
- }
151511
- flushTimeout = null;
151512
- };
151513
- process.stdout.write = function(chunk, encodingOrCallback, callback) {
151514
- const raw = typeof chunk === "string" ? chunk : chunk.toString();
151515
- buffer += raw;
151516
- if (flushTimeout) {
151517
- clearTimeout(flushTimeout);
151518
- }
151519
- flushTimeout = setTimeout(flush, 0);
151520
- const cb2 = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
151521
- if (cb2) {
151522
- setImmediate(() => cb2());
151523
- }
151524
- return true;
151525
- };
151526
- return () => {
151527
- if (flushTimeout) {
151528
- clearTimeout(flushTimeout);
151529
- }
151530
- flush();
151531
- process.stdout.write = originalWrite;
151532
- };
152302
+ // packages/terminal/src/cli/main.ts
152303
+ function isFlag(arg) {
152304
+ return arg !== undefined && arg.startsWith("-") && arg !== "--";
151533
152305
  }
151534
152306
  function parseArgs(argv) {
151535
152307
  const args = argv.slice(2);
@@ -151544,10 +152316,21 @@ function parseArgs(argv) {
151544
152316
  jsonSchema: null,
151545
152317
  continue: false,
151546
152318
  resume: null,
151547
- cwdProvided: false
152319
+ cwdProvided: false,
152320
+ errors: []
151548
152321
  };
152322
+ let endOfOptions = false;
152323
+ const positionalArgs = [];
151549
152324
  for (let i5 = 0;i5 < args.length; i5++) {
151550
152325
  const arg = args[i5];
152326
+ if (arg === "--") {
152327
+ endOfOptions = true;
152328
+ continue;
152329
+ }
152330
+ if (endOfOptions) {
152331
+ positionalArgs.push(arg);
152332
+ continue;
152333
+ }
151551
152334
  if (arg === "--version" || arg === "-v") {
151552
152335
  options.version = true;
151553
152336
  continue;
@@ -151557,29 +152340,62 @@ function parseArgs(argv) {
151557
152340
  continue;
151558
152341
  }
151559
152342
  if (arg === "--print" || arg === "-p") {
151560
- options.print = args[++i5] || "";
152343
+ const nextArg = args[i5 + 1];
152344
+ if (nextArg === undefined || isFlag(nextArg) || nextArg === "--") {
152345
+ options.print = "";
152346
+ } else {
152347
+ options.print = nextArg;
152348
+ i5++;
152349
+ }
151561
152350
  continue;
151562
152351
  }
151563
152352
  if (arg === "--output-format") {
151564
- const format = args[++i5];
151565
- if (format === "text" || format === "json" || format === "stream-json") {
151566
- options.outputFormat = format;
152353
+ const nextArg = args[i5 + 1];
152354
+ if (nextArg === undefined || isFlag(nextArg)) {
152355
+ options.errors.push("--output-format requires a value (text, json, or stream-json)");
152356
+ } else if (nextArg === "text" || nextArg === "json" || nextArg === "stream-json") {
152357
+ options.outputFormat = nextArg;
152358
+ i5++;
152359
+ } else {
152360
+ options.errors.push(`Invalid output format "${nextArg}". Valid options: text, json, stream-json`);
152361
+ i5++;
151567
152362
  }
151568
152363
  continue;
151569
152364
  }
151570
152365
  if (arg === "--allowed-tools" || arg === "--allowedTools") {
151571
- const tools = args[++i5];
151572
- if (tools) {
151573
- options.allowedTools = tools.split(",").map((t9) => t9.trim());
152366
+ const nextArg = args[i5 + 1];
152367
+ if (nextArg === undefined || isFlag(nextArg)) {
152368
+ options.errors.push(`${arg} requires a comma-separated list of tool names`);
152369
+ } else {
152370
+ const seen = new Set;
152371
+ options.allowedTools = nextArg.split(",").map((t9) => t9.trim()).filter((t9) => {
152372
+ if (!t9 || seen.has(t9))
152373
+ return false;
152374
+ seen.add(t9);
152375
+ return true;
152376
+ });
152377
+ i5++;
151574
152378
  }
151575
152379
  continue;
151576
152380
  }
151577
152381
  if (arg === "--system-prompt") {
151578
- options.systemPrompt = args[++i5] || null;
152382
+ const nextArg = args[i5 + 1];
152383
+ if (nextArg === undefined || isFlag(nextArg)) {
152384
+ options.errors.push("--system-prompt requires a value");
152385
+ } else {
152386
+ options.systemPrompt = nextArg;
152387
+ i5++;
152388
+ }
151579
152389
  continue;
151580
152390
  }
151581
152391
  if (arg === "--json-schema") {
151582
- options.jsonSchema = args[++i5] || null;
152392
+ const nextArg = args[i5 + 1];
152393
+ if (nextArg === undefined || isFlag(nextArg)) {
152394
+ options.errors.push("--json-schema requires a JSON schema string");
152395
+ } else {
152396
+ options.jsonSchema = nextArg;
152397
+ i5++;
152398
+ }
151583
152399
  continue;
151584
152400
  }
151585
152401
  if (arg === "--continue" || arg === "-c") {
@@ -151587,20 +152403,163 @@ function parseArgs(argv) {
151587
152403
  continue;
151588
152404
  }
151589
152405
  if (arg === "--resume" || arg === "-r") {
151590
- options.resume = args[++i5] || null;
152406
+ const nextArg = args[i5 + 1];
152407
+ if (nextArg === undefined || isFlag(nextArg)) {
152408
+ options.errors.push(`${arg} requires a session ID`);
152409
+ } else {
152410
+ options.resume = nextArg;
152411
+ i5++;
152412
+ }
151591
152413
  continue;
151592
152414
  }
151593
152415
  if (arg === "--cwd") {
151594
- options.cwd = args[++i5] || process.cwd();
151595
- options.cwdProvided = true;
152416
+ const nextArg = args[i5 + 1];
152417
+ if (nextArg === undefined || isFlag(nextArg)) {
152418
+ options.errors.push("--cwd requires a path");
152419
+ } else {
152420
+ options.cwd = nextArg;
152421
+ options.cwdProvided = true;
152422
+ i5++;
152423
+ }
151596
152424
  continue;
151597
152425
  }
151598
- if (options.print === "" && !arg.startsWith("-")) {
151599
- options.print = arg;
152426
+ if (!arg.startsWith("-")) {
152427
+ positionalArgs.push(arg);
151600
152428
  }
151601
152429
  }
152430
+ if (options.print === "" && positionalArgs.length > 0) {
152431
+ options.print = positionalArgs.join(" ");
152432
+ }
152433
+ if (options.print === null && positionalArgs.length > 0) {
152434
+ options.print = positionalArgs.join(" ");
152435
+ }
151602
152436
  return options;
151603
152437
  }
152438
+ async function main(argv, deps) {
152439
+ const options = parseArgs(argv);
152440
+ const { runHeadless: runHeadless2, print, exit, VERSION: VERSION3 } = deps;
152441
+ if (options.errors.length > 0) {
152442
+ for (const error2 of options.errors) {
152443
+ print(`Error: ${error2}`);
152444
+ }
152445
+ exit(1);
152446
+ return;
152447
+ }
152448
+ if (options.version) {
152449
+ print(`assistants v${VERSION3}`);
152450
+ exit(0);
152451
+ return;
152452
+ }
152453
+ if (options.help) {
152454
+ print(`
152455
+ assistants - Your personal AI assistant
152456
+
152457
+ Usage:
152458
+ assistants [options] Start interactive mode
152459
+ assistants -p "<prompt>" [options] Run in headless mode
152460
+
152461
+ Options:
152462
+ -h, --help Show this help message
152463
+ -v, --version Show version number
152464
+
152465
+ Headless Mode:
152466
+ -p, --print <prompt> Run non-interactively with the given prompt
152467
+ --output-format <format> Output format: text (default), json, stream-json
152468
+ --allowed-tools <tools> Comma-separated tools to auto-approve (e.g., "Read,Edit,Bash")
152469
+ --system-prompt <prompt> Custom system prompt
152470
+ --json-schema <schema> JSON Schema for structured output (use with --output-format json)
152471
+ -c, --continue Continue the most recent conversation
152472
+ -r, --resume <session_id> Resume a specific session by ID
152473
+ --cwd <path> Set working directory
152474
+
152475
+ Examples:
152476
+ # Ask a question
152477
+ assistants -p "What does the auth module do?"
152478
+
152479
+ # Run with JSON output
152480
+ assistants -p "Summarize this project" --output-format json
152481
+
152482
+ # Stream JSON events
152483
+ assistants -p "Explain this code" --output-format stream-json
152484
+
152485
+ # Auto-approve tools
152486
+ assistants -p "Fix the bug in auth.py" --allowed-tools "Read,Edit,Bash"
152487
+
152488
+ # Get structured output
152489
+ assistants -p "List all functions" --output-format json --json-schema '{"type":"array","items":{"type":"string"}}'
152490
+
152491
+ # Continue conversation
152492
+ assistants -p "What else can you tell me?" --continue
152493
+
152494
+ Interactive Mode:
152495
+ - Type your message and press Enter to send
152496
+ - Use $skill-name to invoke a skill
152497
+ - Use /command for built-in commands
152498
+ - Press Ctrl+] to switch sessions
152499
+ - Press Ctrl+C to exit
152500
+ `);
152501
+ exit(0);
152502
+ return;
152503
+ }
152504
+ if (options.print !== null) {
152505
+ if (!options.print.trim()) {
152506
+ print("Error: Prompt is required with -p/--print flag");
152507
+ exit(1);
152508
+ return;
152509
+ }
152510
+ await runHeadless2({
152511
+ prompt: options.print,
152512
+ cwd: options.cwd,
152513
+ outputFormat: options.outputFormat,
152514
+ allowedTools: options.allowedTools.length > 0 ? options.allowedTools : undefined,
152515
+ systemPrompt: options.systemPrompt || undefined,
152516
+ jsonSchema: options.jsonSchema || undefined,
152517
+ continue: options.continue,
152518
+ resume: options.resume,
152519
+ cwdProvided: options.cwdProvided
152520
+ });
152521
+ }
152522
+ }
152523
+
152524
+ // packages/terminal/src/index.tsx
152525
+ var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
152526
+ setRuntime(bunRuntime);
152527
+ var VERSION3 = "0.6.55";
152528
+ var SYNC_START = "\x1B[?2026h";
152529
+ var SYNC_END = "\x1B[?2026l";
152530
+ function enableSynchronizedOutput() {
152531
+ const originalWrite = process.stdout.write.bind(process.stdout);
152532
+ let buffer = "";
152533
+ let flushTimeout = null;
152534
+ const flush = () => {
152535
+ if (buffer) {
152536
+ const safe = sanitizeTerminalOutput(buffer);
152537
+ originalWrite(SYNC_START + safe + SYNC_END);
152538
+ buffer = "";
152539
+ }
152540
+ flushTimeout = null;
152541
+ };
152542
+ process.stdout.write = function(chunk, encodingOrCallback, callback) {
152543
+ const raw = typeof chunk === "string" ? chunk : chunk.toString();
152544
+ buffer += raw;
152545
+ if (flushTimeout) {
152546
+ clearTimeout(flushTimeout);
152547
+ }
152548
+ flushTimeout = setTimeout(flush, 0);
152549
+ const cb2 = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
152550
+ if (cb2) {
152551
+ setImmediate(() => cb2());
152552
+ }
152553
+ return true;
152554
+ };
152555
+ return () => {
152556
+ if (flushTimeout) {
152557
+ clearTimeout(flushTimeout);
152558
+ }
152559
+ flush();
152560
+ process.stdout.write = originalWrite;
152561
+ };
152562
+ }
151604
152563
  var options = parseArgs(process.argv);
151605
152564
  if (options.version) {
151606
152565
  console.log(`assistants v${VERSION3}`);
@@ -151651,7 +152610,7 @@ Interactive Mode:
151651
152610
  - Type your message and press Enter to send
151652
152611
  - Use $skill-name to invoke a skill
151653
152612
  - Use /command for built-in commands
151654
- - Press Ctrl+S to switch sessions
152613
+ - Press Ctrl+] to switch sessions
151655
152614
  - Press Ctrl+C to exit
151656
152615
  `);
151657
152616
  process.exit(0);
@@ -151690,5 +152649,9 @@ if (options.print !== null) {
151690
152649
  process.exit(0);
151691
152650
  });
151692
152651
  }
152652
+ export {
152653
+ parseArgs,
152654
+ main
152655
+ };
151693
152656
 
151694
- //# debugId=779BE5D95D2D703764756E2164756E21
152657
+ //# debugId=C7451B3CD4B5E92E64756E2164756E21