@letta-ai/letta-code 0.19.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/letta.js +2191 -1532
  2. package/package.json +1 -1
package/letta.js CHANGED
@@ -3240,7 +3240,7 @@ var package_default;
3240
3240
  var init_package = __esm(() => {
3241
3241
  package_default = {
3242
3242
  name: "@letta-ai/letta-code",
3243
- version: "0.19.0",
3243
+ version: "0.19.2",
3244
3244
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
3245
3245
  type: "module",
3246
3246
  bin: {
@@ -5612,10 +5612,13 @@ function getStringField(obj, field) {
5612
5612
  return typeof val === "string" ? val : undefined;
5613
5613
  }
5614
5614
  function parseFrontmatter(content) {
5615
+ const normalized = content.replace(/^\uFEFF/, "").replace(/\r\n/g, `
5616
+ `).replace(/\r/g, `
5617
+ `);
5615
5618
  const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
5616
- const match = content.match(frontmatterRegex);
5619
+ const match = normalized.match(frontmatterRegex);
5617
5620
  if (!match || !match[1] || !match[2]) {
5618
- return { frontmatter: {}, body: content };
5621
+ return { frontmatter: {}, body: normalized };
5619
5622
  }
5620
5623
  const frontmatterText = match[1];
5621
5624
  const body = match[2];
@@ -6362,7 +6365,7 @@ name: recall
6362
6365
  description: Search conversation history to recall past discussions, decisions, and context
6363
6366
  tools: Bash, Read, TaskOutput
6364
6367
  skills: searching-messages
6365
- model: haiku
6368
+ model: auto-fast
6366
6369
  memoryBlocks: none
6367
6370
  mode: stateless
6368
6371
  ---
@@ -6962,6 +6965,7 @@ var init_models2 = __esm(() => {
6962
6965
  models: [
6963
6966
  {
6964
6967
  id: "auto",
6968
+ isDefault: true,
6965
6969
  handle: "letta/auto",
6966
6970
  label: "Auto",
6967
6971
  description: "Automatically select the best model",
@@ -6981,7 +6985,6 @@ var init_models2 = __esm(() => {
6981
6985
  handle: "anthropic/claude-sonnet-4-6",
6982
6986
  label: "Sonnet 4.6",
6983
6987
  description: "Anthropic's new Sonnet model (high reasoning)",
6984
- isDefault: true,
6985
6988
  isFeatured: true,
6986
6989
  updateArgs: {
6987
6990
  context_window: 200000,
@@ -7359,6 +7362,71 @@ var init_models2 = __esm(() => {
7359
7362
  parallel_tool_calls: true
7360
7363
  }
7361
7364
  },
7365
+ {
7366
+ id: "gpt-5.4-mini-plus-pro-none",
7367
+ handle: "chatgpt-plus-pro/gpt-5.4-mini",
7368
+ label: "GPT-5.4 Mini (ChatGPT)",
7369
+ description: "GPT-5.4 Mini (no reasoning) via ChatGPT Plus/Pro",
7370
+ updateArgs: {
7371
+ reasoning_effort: "none",
7372
+ verbosity: "low",
7373
+ context_window: 272000,
7374
+ max_output_tokens: 128000,
7375
+ parallel_tool_calls: true
7376
+ }
7377
+ },
7378
+ {
7379
+ id: "gpt-5.4-mini-plus-pro-low",
7380
+ handle: "chatgpt-plus-pro/gpt-5.4-mini",
7381
+ label: "GPT-5.4 Mini (ChatGPT)",
7382
+ description: "GPT-5.4 Mini (low reasoning) via ChatGPT Plus/Pro",
7383
+ updateArgs: {
7384
+ reasoning_effort: "low",
7385
+ verbosity: "low",
7386
+ context_window: 272000,
7387
+ max_output_tokens: 128000,
7388
+ parallel_tool_calls: true
7389
+ }
7390
+ },
7391
+ {
7392
+ id: "gpt-5.4-mini-plus-pro-medium",
7393
+ handle: "chatgpt-plus-pro/gpt-5.4-mini",
7394
+ label: "GPT-5.4 Mini (ChatGPT)",
7395
+ description: "GPT-5.4 Mini (med reasoning) via ChatGPT Plus/Pro",
7396
+ updateArgs: {
7397
+ reasoning_effort: "medium",
7398
+ verbosity: "low",
7399
+ context_window: 272000,
7400
+ max_output_tokens: 128000,
7401
+ parallel_tool_calls: true
7402
+ }
7403
+ },
7404
+ {
7405
+ id: "gpt-5.4-mini-plus-pro-high",
7406
+ handle: "chatgpt-plus-pro/gpt-5.4-mini",
7407
+ label: "GPT-5.4 Mini (ChatGPT)",
7408
+ description: "GPT-5.4 Mini (high reasoning) via ChatGPT Plus/Pro",
7409
+ updateArgs: {
7410
+ reasoning_effort: "high",
7411
+ verbosity: "low",
7412
+ context_window: 272000,
7413
+ max_output_tokens: 128000,
7414
+ parallel_tool_calls: true
7415
+ }
7416
+ },
7417
+ {
7418
+ id: "gpt-5.4-mini-plus-pro-xhigh",
7419
+ handle: "chatgpt-plus-pro/gpt-5.4-mini",
7420
+ label: "GPT-5.4 Mini (ChatGPT)",
7421
+ description: "GPT-5.4 Mini (max reasoning) via ChatGPT Plus/Pro",
7422
+ updateArgs: {
7423
+ reasoning_effort: "xhigh",
7424
+ verbosity: "low",
7425
+ context_window: 272000,
7426
+ max_output_tokens: 128000,
7427
+ parallel_tool_calls: true
7428
+ }
7429
+ },
7362
7430
  {
7363
7431
  id: "gpt-5.3-codex-plus-pro-none",
7364
7432
  handle: "chatgpt-plus-pro/gpt-5.3-codex",
@@ -7920,6 +7988,137 @@ var init_models2 = __esm(() => {
7920
7988
  parallel_tool_calls: true
7921
7989
  }
7922
7990
  },
7991
+ {
7992
+ id: "gpt-5.4-mini-none",
7993
+ handle: "openai/gpt-5.4-mini",
7994
+ label: "GPT-5.4 Mini",
7995
+ description: "Fast, efficient GPT-5.4 variant (no reasoning)",
7996
+ updateArgs: {
7997
+ reasoning_effort: "none",
7998
+ verbosity: "low",
7999
+ context_window: 272000,
8000
+ max_output_tokens: 128000,
8001
+ parallel_tool_calls: true
8002
+ }
8003
+ },
8004
+ {
8005
+ id: "gpt-5.4-mini-low",
8006
+ handle: "openai/gpt-5.4-mini",
8007
+ label: "GPT-5.4 Mini",
8008
+ description: "Fast, efficient GPT-5.4 variant (low reasoning)",
8009
+ updateArgs: {
8010
+ reasoning_effort: "low",
8011
+ verbosity: "low",
8012
+ context_window: 272000,
8013
+ max_output_tokens: 128000,
8014
+ parallel_tool_calls: true
8015
+ }
8016
+ },
8017
+ {
8018
+ id: "gpt-5.4-mini-medium",
8019
+ handle: "openai/gpt-5.4-mini",
8020
+ label: "GPT-5.4 Mini",
8021
+ description: "Fast, efficient GPT-5.4 variant (med reasoning)",
8022
+ isFeatured: true,
8023
+ updateArgs: {
8024
+ reasoning_effort: "medium",
8025
+ verbosity: "low",
8026
+ context_window: 272000,
8027
+ max_output_tokens: 128000,
8028
+ parallel_tool_calls: true
8029
+ }
8030
+ },
8031
+ {
8032
+ id: "gpt-5.4-mini-high",
8033
+ handle: "openai/gpt-5.4-mini",
8034
+ label: "GPT-5.4 Mini",
8035
+ description: "Fast, efficient GPT-5.4 variant (high reasoning)",
8036
+ updateArgs: {
8037
+ reasoning_effort: "high",
8038
+ verbosity: "low",
8039
+ context_window: 272000,
8040
+ max_output_tokens: 128000,
8041
+ parallel_tool_calls: true
8042
+ }
8043
+ },
8044
+ {
8045
+ id: "gpt-5.4-mini-xhigh",
8046
+ handle: "openai/gpt-5.4-mini",
8047
+ label: "GPT-5.4 Mini",
8048
+ description: "Fast, efficient GPT-5.4 variant (max reasoning)",
8049
+ updateArgs: {
8050
+ reasoning_effort: "xhigh",
8051
+ verbosity: "low",
8052
+ context_window: 272000,
8053
+ max_output_tokens: 128000,
8054
+ parallel_tool_calls: true
8055
+ }
8056
+ },
8057
+ {
8058
+ id: "gpt-5.4-nano-none",
8059
+ handle: "openai/gpt-5.4-nano",
8060
+ label: "GPT-5.4 Nano",
8061
+ description: "Smallest, cheapest GPT-5.4 variant (no reasoning)",
8062
+ updateArgs: {
8063
+ reasoning_effort: "none",
8064
+ verbosity: "low",
8065
+ context_window: 272000,
8066
+ max_output_tokens: 128000,
8067
+ parallel_tool_calls: true
8068
+ }
8069
+ },
8070
+ {
8071
+ id: "gpt-5.4-nano-low",
8072
+ handle: "openai/gpt-5.4-nano",
8073
+ label: "GPT-5.4 Nano",
8074
+ description: "Smallest, cheapest GPT-5.4 variant (low reasoning)",
8075
+ updateArgs: {
8076
+ reasoning_effort: "low",
8077
+ verbosity: "low",
8078
+ context_window: 272000,
8079
+ max_output_tokens: 128000,
8080
+ parallel_tool_calls: true
8081
+ }
8082
+ },
8083
+ {
8084
+ id: "gpt-5.4-nano-medium",
8085
+ handle: "openai/gpt-5.4-nano",
8086
+ label: "GPT-5.4 Nano",
8087
+ description: "Smallest, cheapest GPT-5.4 variant (med reasoning)",
8088
+ updateArgs: {
8089
+ reasoning_effort: "medium",
8090
+ verbosity: "low",
8091
+ context_window: 272000,
8092
+ max_output_tokens: 128000,
8093
+ parallel_tool_calls: true
8094
+ }
8095
+ },
8096
+ {
8097
+ id: "gpt-5.4-nano-high",
8098
+ handle: "openai/gpt-5.4-nano",
8099
+ label: "GPT-5.4 Nano",
8100
+ description: "Smallest, cheapest GPT-5.4 variant (high reasoning)",
8101
+ updateArgs: {
8102
+ reasoning_effort: "high",
8103
+ verbosity: "low",
8104
+ context_window: 272000,
8105
+ max_output_tokens: 128000,
8106
+ parallel_tool_calls: true
8107
+ }
8108
+ },
8109
+ {
8110
+ id: "gpt-5.4-nano-xhigh",
8111
+ handle: "openai/gpt-5.4-nano",
8112
+ label: "GPT-5.4 Nano",
8113
+ description: "Smallest, cheapest GPT-5.4 variant (max reasoning)",
8114
+ updateArgs: {
8115
+ reasoning_effort: "xhigh",
8116
+ verbosity: "low",
8117
+ context_window: 272000,
8118
+ max_output_tokens: 128000,
8119
+ parallel_tool_calls: true
8120
+ }
8121
+ },
7923
8122
  {
7924
8123
  id: "gpt-5.3-codex-none",
7925
8124
  handle: "openai/gpt-5.3-codex",
@@ -39700,944 +39899,6 @@ var init_planName = __esm(() => {
39700
39899
  ];
39701
39900
  });
39702
39901
 
39703
- // src/permissions/readOnlyShell.ts
39704
- import { homedir as homedir8 } from "node:os";
39705
- import { resolve as resolve2 } from "node:path";
39706
- function splitShellSegments(input) {
39707
- const segments = [];
39708
- let current = "";
39709
- let i = 0;
39710
- let quote = null;
39711
- while (i < input.length) {
39712
- const ch = input[i];
39713
- if (!ch) {
39714
- i += 1;
39715
- continue;
39716
- }
39717
- if (quote === "single") {
39718
- current += ch;
39719
- if (ch === "'") {
39720
- quote = null;
39721
- }
39722
- i += 1;
39723
- continue;
39724
- }
39725
- if (quote === "double") {
39726
- if (ch === "\\" && i + 1 < input.length) {
39727
- current += input.slice(i, i + 2);
39728
- i += 2;
39729
- continue;
39730
- }
39731
- if (ch === "`" || input.startsWith("$(", i)) {
39732
- return null;
39733
- }
39734
- current += ch;
39735
- if (ch === '"') {
39736
- quote = null;
39737
- }
39738
- i += 1;
39739
- continue;
39740
- }
39741
- if (ch === "'") {
39742
- quote = "single";
39743
- current += ch;
39744
- i += 1;
39745
- continue;
39746
- }
39747
- if (ch === '"') {
39748
- quote = "double";
39749
- current += ch;
39750
- i += 1;
39751
- continue;
39752
- }
39753
- if (input.startsWith(">>", i) || ch === ">") {
39754
- return null;
39755
- }
39756
- if (ch === "`" || input.startsWith("$(", i)) {
39757
- return null;
39758
- }
39759
- if (input.startsWith("&&", i)) {
39760
- segments.push(current);
39761
- current = "";
39762
- i += 2;
39763
- continue;
39764
- }
39765
- if (input.startsWith("||", i)) {
39766
- segments.push(current);
39767
- current = "";
39768
- i += 2;
39769
- continue;
39770
- }
39771
- if (ch === ";") {
39772
- segments.push(current);
39773
- current = "";
39774
- i += 1;
39775
- continue;
39776
- }
39777
- if (ch === "|") {
39778
- segments.push(current);
39779
- current = "";
39780
- i += 1;
39781
- continue;
39782
- }
39783
- current += ch;
39784
- i += 1;
39785
- }
39786
- segments.push(current);
39787
- return segments.map((segment) => segment.trim()).filter(Boolean);
39788
- }
39789
- function isReadOnlyShellCommand(command, options = {}) {
39790
- if (!command) {
39791
- return false;
39792
- }
39793
- if (Array.isArray(command)) {
39794
- if (command.length === 0) {
39795
- return false;
39796
- }
39797
- const joined = command.join(" ");
39798
- const [executable, ...rest] = command;
39799
- if (executable && isShellExecutor(executable)) {
39800
- const nested = extractDashCArgument(rest);
39801
- if (!nested) {
39802
- return false;
39803
- }
39804
- return isReadOnlyShellCommand(nested, options);
39805
- }
39806
- return isReadOnlyShellCommand(joined, options);
39807
- }
39808
- const trimmed = command.trim();
39809
- if (!trimmed) {
39810
- return false;
39811
- }
39812
- const segments = splitShellSegments(trimmed);
39813
- if (!segments || segments.length === 0) {
39814
- return false;
39815
- }
39816
- for (const segment of segments) {
39817
- if (!isSafeSegment(segment, options)) {
39818
- return false;
39819
- }
39820
- }
39821
- return true;
39822
- }
39823
- function isSafeSegment(segment, options) {
39824
- const tokens = tokenize4(segment);
39825
- if (tokens.length === 0) {
39826
- return false;
39827
- }
39828
- const command = tokens[0];
39829
- if (!command) {
39830
- return false;
39831
- }
39832
- if (isShellExecutor(command)) {
39833
- const nested = extractDashCArgument(tokens.slice(1));
39834
- if (!nested) {
39835
- return false;
39836
- }
39837
- return isReadOnlyShellCommand(stripQuotes(nested), options);
39838
- }
39839
- if (ALWAYS_SAFE_COMMANDS.has(command)) {
39840
- if (command === "cd") {
39841
- if (options.allowExternalPaths) {
39842
- return true;
39843
- }
39844
- return !tokens.slice(1).some((t) => hasAbsoluteOrTraversalPathArg(t));
39845
- }
39846
- const hasExternalPath = !options.allowExternalPaths && tokens.slice(1).some((t) => hasAbsoluteOrTraversalPathArg(t));
39847
- if (hasExternalPath) {
39848
- return false;
39849
- }
39850
- return true;
39851
- }
39852
- if (command === "sed") {
39853
- const usesInPlace = tokens.some((token) => token === "-i" || token.startsWith("-i") || token === "--in-place");
39854
- if (usesInPlace) {
39855
- return false;
39856
- }
39857
- const hasExternalPath = !options.allowExternalPaths && tokens.slice(1).some((t) => hasAbsoluteOrTraversalPathArg(t));
39858
- if (hasExternalPath) {
39859
- return false;
39860
- }
39861
- return true;
39862
- }
39863
- if (command === "git") {
39864
- const subcommand = tokens[1];
39865
- if (!subcommand) {
39866
- return false;
39867
- }
39868
- return SAFE_GIT_SUBCOMMANDS.has(subcommand);
39869
- }
39870
- if (command === "gh") {
39871
- const category = tokens[1];
39872
- if (!category) {
39873
- return false;
39874
- }
39875
- if (!(category in SAFE_GH_COMMANDS)) {
39876
- return false;
39877
- }
39878
- const allowedActions = SAFE_GH_COMMANDS[category];
39879
- if (allowedActions === null) {
39880
- return true;
39881
- }
39882
- if (allowedActions === undefined) {
39883
- return false;
39884
- }
39885
- const action = tokens[2];
39886
- if (!action) {
39887
- return false;
39888
- }
39889
- return allowedActions.has(action);
39890
- }
39891
- if (command === "letta") {
39892
- const group = tokens[1];
39893
- if (!group) {
39894
- return false;
39895
- }
39896
- if (!(group in SAFE_LETTA_COMMANDS)) {
39897
- return false;
39898
- }
39899
- const action = tokens[2];
39900
- if (!action) {
39901
- return false;
39902
- }
39903
- return SAFE_LETTA_COMMANDS[group]?.has(action) ?? false;
39904
- }
39905
- if (command === "find") {
39906
- return !/-delete|\s-exec\b/.test(segment);
39907
- }
39908
- if (command === "sort") {
39909
- return !/\s-o\b/.test(segment);
39910
- }
39911
- return false;
39912
- }
39913
- function isShellExecutor(command) {
39914
- return command === "bash" || command === "sh";
39915
- }
39916
- function tokenize4(segment) {
39917
- const matches = segment.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
39918
- if (!matches) {
39919
- return [];
39920
- }
39921
- return matches.map((token) => stripQuotes(token));
39922
- }
39923
- function stripQuotes(value) {
39924
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
39925
- return value.slice(1, -1);
39926
- }
39927
- return value;
39928
- }
39929
- function extractDashCArgument(tokens) {
39930
- for (let i = 0;i < tokens.length; i += 1) {
39931
- const token = tokens[i];
39932
- if (!token) {
39933
- continue;
39934
- }
39935
- if (token === "-c" || token === "-lc" || /^-[a-zA-Z]*c$/.test(token)) {
39936
- return tokens[i + 1];
39937
- }
39938
- }
39939
- return;
39940
- }
39941
- function isAbsolutePathArg(value) {
39942
- if (!value) {
39943
- return false;
39944
- }
39945
- if (value.startsWith("/")) {
39946
- return true;
39947
- }
39948
- return /^[a-zA-Z]:[\\/]/.test(value) || value.startsWith("\\\\");
39949
- }
39950
- function isHomeAnchoredPathArg(value) {
39951
- if (!value) {
39952
- return false;
39953
- }
39954
- return value.startsWith("~/") || value.startsWith("$HOME/") || value.startsWith("%USERPROFILE%\\") || value.startsWith("%USERPROFILE%/");
39955
- }
39956
- function hasAbsoluteOrTraversalPathArg(value) {
39957
- if (isAbsolutePathArg(value) || isHomeAnchoredPathArg(value)) {
39958
- return true;
39959
- }
39960
- return /(^|[\\/])\.\.([\\/]|$)/.test(value);
39961
- }
39962
- function getAllowedMemoryPrefixes(agentId) {
39963
- const home = homedir8();
39964
- const prefixes = [
39965
- normalizeSeparators(resolve2(home, ".letta", "agents", agentId, "memory")),
39966
- normalizeSeparators(resolve2(home, ".letta", "agents", agentId, "memory-worktrees"))
39967
- ];
39968
- const parentId = process.env.LETTA_PARENT_AGENT_ID;
39969
- if (parentId && parentId !== agentId) {
39970
- prefixes.push(normalizeSeparators(resolve2(home, ".letta", "agents", parentId, "memory")), normalizeSeparators(resolve2(home, ".letta", "agents", parentId, "memory-worktrees")));
39971
- }
39972
- return prefixes;
39973
- }
39974
- function normalizeSeparators(p) {
39975
- return p.replace(/\\/g, "/");
39976
- }
39977
- function expandPath(p) {
39978
- const home = homedir8();
39979
- if (p.startsWith("~/")) {
39980
- return normalizeSeparators(resolve2(home, p.slice(2)));
39981
- }
39982
- if (p.startsWith("$HOME/")) {
39983
- return normalizeSeparators(resolve2(home, p.slice(6)));
39984
- }
39985
- if (p.startsWith('"$HOME/')) {
39986
- return normalizeSeparators(resolve2(home, p.slice(7).replace(/"$/, "")));
39987
- }
39988
- return normalizeSeparators(resolve2(p));
39989
- }
39990
- function isUnderMemoryDir(path3, prefixes) {
39991
- const resolved = expandPath(path3);
39992
- return prefixes.some((prefix) => resolved === prefix || resolved.startsWith(`${prefix}/`));
39993
- }
39994
- function extractCdTarget(segment) {
39995
- const tokens = tokenize4(segment);
39996
- if (tokens[0] === "cd" && tokens[1]) {
39997
- return tokens[1];
39998
- }
39999
- return null;
40000
- }
40001
- function isMemoryDirCommand(command, agentId) {
40002
- if (!command || !agentId) {
40003
- return false;
40004
- }
40005
- const commandStr = typeof command === "string" ? command : command.join(" ");
40006
- const trimmed = commandStr.trim();
40007
- if (!trimmed) {
40008
- return false;
40009
- }
40010
- const prefixes = getAllowedMemoryPrefixes(agentId);
40011
- const segments = trimmed.split(/&&|\|\||;/).map((s) => s.trim()).filter(Boolean);
40012
- if (segments.length === 0) {
40013
- return false;
40014
- }
40015
- let cwd2 = null;
40016
- for (const segment of segments) {
40017
- const pipeParts = segment.split(/\|/).map((s) => s.trim()).filter(Boolean);
40018
- for (const part of pipeParts) {
40019
- const cdTarget = extractCdTarget(part);
40020
- if (cdTarget) {
40021
- const resolved = cwd2 ? expandPath(resolve2(expandPath(cwd2), cdTarget)) : expandPath(cdTarget);
40022
- if (!isUnderMemoryDir(resolved, prefixes)) {
40023
- return false;
40024
- }
40025
- cwd2 = resolved;
40026
- continue;
40027
- }
40028
- if (cwd2 && isUnderMemoryDir(cwd2, prefixes)) {
40029
- const tokens2 = tokenize4(part);
40030
- const currentCwd = cwd2;
40031
- if (!currentCwd) {
40032
- return false;
40033
- }
40034
- const hasExternalPath = tokens2.some((t) => {
40035
- if (isAbsolutePathArg(t) || isHomeAnchoredPathArg(t)) {
40036
- return !isUnderMemoryDir(t, prefixes);
40037
- }
40038
- if (hasAbsoluteOrTraversalPathArg(t)) {
40039
- const resolved = expandPath(resolve2(expandPath(currentCwd), t));
40040
- return !isUnderMemoryDir(resolved, prefixes);
40041
- }
40042
- return false;
40043
- });
40044
- if (hasExternalPath) {
40045
- return false;
40046
- }
40047
- if (!MEMORY_DIR_APPROVE_ALL) {
40048
- const cmd = tokens2[0];
40049
- if (!cmd || !SAFE_MEMORY_DIR_COMMANDS.has(cmd)) {
40050
- return false;
40051
- }
40052
- }
40053
- continue;
40054
- }
40055
- const tokens = tokenize4(part);
40056
- const hasMemoryPath = tokens.some((t) => t.includes(".letta/agents/") && t.includes("/memory") || t.includes(".letta/agents/") && t.includes("/memory-worktrees"));
40057
- if (hasMemoryPath) {
40058
- const agentPaths = tokens.filter((t) => t.includes(".letta/agents/"));
40059
- if (agentPaths.every((p) => isUnderMemoryDir(p, prefixes))) {
40060
- if (!MEMORY_DIR_APPROVE_ALL) {
40061
- const cmd = tokens[0];
40062
- if (!cmd || !SAFE_MEMORY_DIR_COMMANDS.has(cmd)) {
40063
- return false;
40064
- }
40065
- }
40066
- continue;
40067
- }
40068
- }
40069
- return false;
40070
- }
40071
- }
40072
- return true;
40073
- }
40074
- var MEMORY_DIR_APPROVE_ALL = true, SAFE_MEMORY_DIR_COMMANDS, ALWAYS_SAFE_COMMANDS, SAFE_GIT_SUBCOMMANDS, SAFE_LETTA_COMMANDS, SAFE_GH_COMMANDS;
40075
- var init_readOnlyShell = __esm(() => {
40076
- SAFE_MEMORY_DIR_COMMANDS = new Set([
40077
- "git",
40078
- "rm",
40079
- "mv",
40080
- "mkdir",
40081
- "cp",
40082
- "ls",
40083
- "cat",
40084
- "head",
40085
- "tail",
40086
- "tree",
40087
- "find",
40088
- "wc",
40089
- "split",
40090
- "echo",
40091
- "sort",
40092
- "cd"
40093
- ]);
40094
- ALWAYS_SAFE_COMMANDS = new Set([
40095
- "cat",
40096
- "head",
40097
- "tail",
40098
- "less",
40099
- "more",
40100
- "grep",
40101
- "rg",
40102
- "ag",
40103
- "ack",
40104
- "fgrep",
40105
- "egrep",
40106
- "ls",
40107
- "tree",
40108
- "file",
40109
- "stat",
40110
- "du",
40111
- "df",
40112
- "wc",
40113
- "diff",
40114
- "cmp",
40115
- "comm",
40116
- "cut",
40117
- "tr",
40118
- "nl",
40119
- "column",
40120
- "fold",
40121
- "pwd",
40122
- "whoami",
40123
- "hostname",
40124
- "date",
40125
- "uname",
40126
- "uptime",
40127
- "id",
40128
- "echo",
40129
- "printf",
40130
- "env",
40131
- "printenv",
40132
- "which",
40133
- "whereis",
40134
- "type",
40135
- "basename",
40136
- "dirname",
40137
- "realpath",
40138
- "readlink",
40139
- "jq",
40140
- "yq",
40141
- "strings",
40142
- "xxd",
40143
- "hexdump",
40144
- "cd"
40145
- ]);
40146
- SAFE_GIT_SUBCOMMANDS = new Set([
40147
- "status",
40148
- "diff",
40149
- "log",
40150
- "show",
40151
- "branch",
40152
- "tag",
40153
- "remote"
40154
- ]);
40155
- SAFE_LETTA_COMMANDS = {
40156
- memfs: new Set(["status", "help", "backups", "export"]),
40157
- agents: new Set(["list", "help"]),
40158
- messages: new Set(["search", "list", "help"]),
40159
- blocks: new Set(["list", "help"])
40160
- };
40161
- SAFE_GH_COMMANDS = {
40162
- pr: new Set(["list", "status", "checks", "diff", "view"]),
40163
- issue: new Set(["list", "status", "view"]),
40164
- repo: new Set(["list", "view", "gitignore", "license"]),
40165
- run: new Set(["list", "view", "watch", "download"]),
40166
- release: new Set(["list", "view", "download"]),
40167
- search: null,
40168
- api: null,
40169
- status: null
40170
- };
40171
- });
40172
-
40173
- // src/permissions/shell-command-normalization.ts
40174
- function trimMatchingQuotes(value) {
40175
- const trimmed = value.trim();
40176
- if (trimmed.length < 2) {
40177
- return trimmed;
40178
- }
40179
- const first = trimmed[0];
40180
- const last = trimmed[trimmed.length - 1];
40181
- if ((first === '"' || first === "'") && last === first) {
40182
- return trimmed.slice(1, -1);
40183
- }
40184
- return trimmed;
40185
- }
40186
- function normalizeExecutableToken(token) {
40187
- const normalized = trimMatchingQuotes(token).replace(/\\/g, "/");
40188
- const parts = normalized.split("/").filter(Boolean);
40189
- const executable = parts[parts.length - 1] ?? normalized;
40190
- return executable.toLowerCase();
40191
- }
40192
- function tokenizeShell(input) {
40193
- const tokens = [];
40194
- let current = "";
40195
- let quote = null;
40196
- let escaping = false;
40197
- const flush = () => {
40198
- if (current.length > 0) {
40199
- tokens.push(current);
40200
- current = "";
40201
- }
40202
- };
40203
- for (let i = 0;i < input.length; i += 1) {
40204
- const ch = input[i];
40205
- if (ch === undefined) {
40206
- continue;
40207
- }
40208
- if (escaping) {
40209
- current += ch;
40210
- escaping = false;
40211
- continue;
40212
- }
40213
- if (ch === "\\" && quote !== "single") {
40214
- escaping = true;
40215
- continue;
40216
- }
40217
- if (quote === "single") {
40218
- if (ch === "'") {
40219
- quote = null;
40220
- } else {
40221
- current += ch;
40222
- }
40223
- continue;
40224
- }
40225
- if (quote === "double") {
40226
- if (ch === '"') {
40227
- quote = null;
40228
- } else {
40229
- current += ch;
40230
- }
40231
- continue;
40232
- }
40233
- if (ch === "'") {
40234
- quote = "single";
40235
- continue;
40236
- }
40237
- if (ch === '"') {
40238
- quote = "double";
40239
- continue;
40240
- }
40241
- if (/\s/.test(ch)) {
40242
- flush();
40243
- continue;
40244
- }
40245
- current += ch;
40246
- }
40247
- if (escaping) {
40248
- current += "\\";
40249
- }
40250
- flush();
40251
- return tokens;
40252
- }
40253
- function isDashCFlag(token) {
40254
- return token === "-c" || /^-[a-zA-Z]*c[a-zA-Z]*$/.test(token);
40255
- }
40256
- function extractInnerShellCommand(tokens) {
40257
- if (tokens.length === 0) {
40258
- return null;
40259
- }
40260
- let index = 0;
40261
- if (normalizeExecutableToken(tokens[0] ?? "") === "env") {
40262
- index += 1;
40263
- while (index < tokens.length) {
40264
- const token = tokens[index] ?? "";
40265
- if (!token) {
40266
- index += 1;
40267
- continue;
40268
- }
40269
- if (/^-[A-Za-z]+$/.test(token)) {
40270
- index += 1;
40271
- continue;
40272
- }
40273
- if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(token)) {
40274
- index += 1;
40275
- continue;
40276
- }
40277
- break;
40278
- }
40279
- }
40280
- const executableToken = tokens[index];
40281
- if (!executableToken) {
40282
- return null;
40283
- }
40284
- if (!SHELL_EXECUTORS.has(normalizeExecutableToken(executableToken))) {
40285
- return null;
40286
- }
40287
- for (let i = index + 1;i < tokens.length; i += 1) {
40288
- const token = tokens[i];
40289
- if (!token) {
40290
- continue;
40291
- }
40292
- if (!isDashCFlag(token)) {
40293
- continue;
40294
- }
40295
- const innerCommand = tokens[i + 1];
40296
- if (!innerCommand) {
40297
- return null;
40298
- }
40299
- return trimMatchingQuotes(innerCommand);
40300
- }
40301
- return null;
40302
- }
40303
- function unwrapShellLauncherCommand(command) {
40304
- let current = command.trim();
40305
- for (let depth = 0;depth < 5; depth += 1) {
40306
- if (!current) {
40307
- break;
40308
- }
40309
- const tokens = tokenizeShell(current);
40310
- const inner = extractInnerShellCommand(tokens);
40311
- if (!inner || inner === current) {
40312
- break;
40313
- }
40314
- current = inner.trim();
40315
- }
40316
- return current;
40317
- }
40318
- function normalizeBashRulePayload(payload) {
40319
- const trimmed = payload.trim();
40320
- if (!trimmed) {
40321
- return "";
40322
- }
40323
- const hasWildcardSuffix = trimmed.endsWith(":*");
40324
- const withoutWildcard = hasWildcardSuffix ? trimmed.slice(0, -2).trimEnd() : trimmed;
40325
- const unwrapped = unwrapShellLauncherCommand(withoutWildcard);
40326
- if (hasWildcardSuffix) {
40327
- return `${unwrapped}:*`;
40328
- }
40329
- return unwrapped;
40330
- }
40331
- var SHELL_EXECUTORS;
40332
- var init_shell_command_normalization = __esm(() => {
40333
- SHELL_EXECUTORS = new Set(["bash", "sh", "zsh", "dash", "ksh"]);
40334
- });
40335
-
40336
- // src/permissions/mode.ts
40337
- var exports_mode = {};
40338
- __export(exports_mode, {
40339
- permissionMode: () => permissionMode
40340
- });
40341
- import { homedir as homedir9 } from "node:os";
40342
- import { isAbsolute, join as join9, relative as relative2, resolve as resolve3 } from "node:path";
40343
- function getGlobalMode() {
40344
- const global2 = globalThis;
40345
- if (!global2[MODE_KEY]) {
40346
- global2[MODE_KEY] = "default";
40347
- }
40348
- return global2[MODE_KEY];
40349
- }
40350
- function setGlobalMode(value) {
40351
- const global2 = globalThis;
40352
- global2[MODE_KEY] = value;
40353
- }
40354
- function getGlobalPlanFilePath() {
40355
- const global2 = globalThis;
40356
- return global2[PLAN_FILE_KEY] || null;
40357
- }
40358
- function setGlobalPlanFilePath(value) {
40359
- const global2 = globalThis;
40360
- global2[PLAN_FILE_KEY] = value;
40361
- }
40362
- function getGlobalModeBeforePlan() {
40363
- const global2 = globalThis;
40364
- return global2[MODE_BEFORE_PLAN_KEY] ?? null;
40365
- }
40366
- function setGlobalModeBeforePlan(value) {
40367
- const global2 = globalThis;
40368
- global2[MODE_BEFORE_PLAN_KEY] = value;
40369
- }
40370
- function resolvePlanTargetPath(targetPath, workingDirectory) {
40371
- const trimmedPath = targetPath.trim();
40372
- if (!trimmedPath)
40373
- return null;
40374
- if (trimmedPath.startsWith("~/")) {
40375
- return resolve3(homedir9(), trimmedPath.slice(2));
40376
- }
40377
- if (isAbsolute(trimmedPath)) {
40378
- return resolve3(trimmedPath);
40379
- }
40380
- return resolve3(workingDirectory, trimmedPath);
40381
- }
40382
- function isPathInPlansDir(path3, plansDir) {
40383
- if (!path3.endsWith(".md"))
40384
- return false;
40385
- const rel = relative2(plansDir, path3);
40386
- return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
40387
- }
40388
- function extractApplyPatchPaths(input) {
40389
- const paths = [];
40390
- const fileDirectivePattern = /\*\*\* (?:Add|Update|Delete) File:\s*(.+)/g;
40391
- const moveDirectivePattern = /\*\*\* Move to:\s*(.+)/g;
40392
- for (const match of input.matchAll(fileDirectivePattern)) {
40393
- const matchPath = match[1]?.trim();
40394
- if (matchPath)
40395
- paths.push(matchPath);
40396
- }
40397
- for (const match of input.matchAll(moveDirectivePattern)) {
40398
- const matchPath = match[1]?.trim();
40399
- if (matchPath)
40400
- paths.push(matchPath);
40401
- }
40402
- return paths;
40403
- }
40404
- function stripMatchingQuotes(value) {
40405
- const trimmed = value.trim();
40406
- if (trimmed.length < 2) {
40407
- return trimmed;
40408
- }
40409
- const first = trimmed[0];
40410
- const last = trimmed[trimmed.length - 1];
40411
- if ((first === '"' || first === "'") && last === first) {
40412
- return trimmed.slice(1, -1);
40413
- }
40414
- return trimmed;
40415
- }
40416
- function extractPlanFileWritePathFromShellCommand(command) {
40417
- if (!command) {
40418
- return null;
40419
- }
40420
- const commandString = typeof command === "string" ? command : command.join(" ") ?? "";
40421
- const normalizedCommand = unwrapShellLauncherCommand(commandString).trim();
40422
- if (!normalizedCommand) {
40423
- return null;
40424
- }
40425
- const lines = normalizedCommand.split(/\r?\n/);
40426
- const firstLine = lines[0]?.trim() ?? "";
40427
- if (!firstLine) {
40428
- return null;
40429
- }
40430
- const firstLineMatch = firstLine.match(/^cat\s+(?:>\s*(?<path1>"[^"]+"|'[^']+'|\S+)\s+<<-?\s*(?<delim1>"[^"]+"|'[^']+'|\S+)|<<-?\s*(?<delim2>"[^"]+"|'[^']+'|\S+)\s+>\s*(?<path2>"[^"]+"|'[^']+'|\S+))\s*$/);
40431
- if (!firstLineMatch?.groups) {
40432
- return null;
40433
- }
40434
- const rawPath = firstLineMatch.groups.path1 || firstLineMatch.groups.path2;
40435
- const rawDelim = firstLineMatch.groups.delim1 || firstLineMatch.groups.delim2;
40436
- if (!rawPath || !rawDelim) {
40437
- return null;
40438
- }
40439
- const delimiter = stripMatchingQuotes(rawDelim);
40440
- if (!delimiter) {
40441
- return null;
40442
- }
40443
- let terminatorLine = -1;
40444
- for (let i = 1;i < lines.length; i += 1) {
40445
- if ((lines[i] ?? "") === delimiter) {
40446
- terminatorLine = i;
40447
- break;
40448
- }
40449
- }
40450
- if (terminatorLine === -1) {
40451
- return null;
40452
- }
40453
- for (let i = terminatorLine + 1;i < lines.length; i += 1) {
40454
- if ((lines[i] ?? "").trim().length > 0) {
40455
- return null;
40456
- }
40457
- }
40458
- return stripMatchingQuotes(rawPath);
40459
- }
40460
-
40461
- class PermissionModeManager {
40462
- get currentMode() {
40463
- return getGlobalMode();
40464
- }
40465
- set currentMode(value) {
40466
- setGlobalMode(value);
40467
- }
40468
- setMode(mode) {
40469
- const prevMode = this.currentMode;
40470
- if (mode === "plan" && prevMode !== "plan") {
40471
- setGlobalModeBeforePlan(prevMode);
40472
- }
40473
- this.currentMode = mode;
40474
- if (mode !== "plan") {
40475
- setGlobalPlanFilePath(null);
40476
- }
40477
- if (prevMode === "plan" && mode !== "plan") {
40478
- setGlobalModeBeforePlan(null);
40479
- }
40480
- }
40481
- getModeBeforePlan() {
40482
- return getGlobalModeBeforePlan();
40483
- }
40484
- getMode() {
40485
- return this.currentMode;
40486
- }
40487
- setPlanFilePath(path3) {
40488
- setGlobalPlanFilePath(path3);
40489
- }
40490
- getPlanFilePath() {
40491
- return getGlobalPlanFilePath();
40492
- }
40493
- checkModeOverride(toolName, toolArgs, workingDirectory = process.cwd()) {
40494
- switch (this.currentMode) {
40495
- case "bypassPermissions":
40496
- return "allow";
40497
- case "acceptEdits":
40498
- if ([
40499
- "Write",
40500
- "Edit",
40501
- "MultiEdit",
40502
- "NotebookEdit",
40503
- "apply_patch",
40504
- "replace",
40505
- "write_file"
40506
- ].includes(toolName)) {
40507
- return "allow";
40508
- }
40509
- return null;
40510
- case "plan": {
40511
- const allowedInPlan = [
40512
- "Read",
40513
- "Glob",
40514
- "Grep",
40515
- "NotebookRead",
40516
- "TodoWrite",
40517
- "ExitPlanMode",
40518
- "exit_plan_mode",
40519
- "AskUserQuestion",
40520
- "ask_user_question",
40521
- "read_file",
40522
- "list_dir",
40523
- "grep_files",
40524
- "update_plan",
40525
- "task_output",
40526
- "ReadFile",
40527
- "ListDir",
40528
- "GrepFiles",
40529
- "UpdatePlan",
40530
- "TaskOutput",
40531
- "read_file_gemini",
40532
- "glob_gemini",
40533
- "list_directory",
40534
- "search_file_content",
40535
- "write_todos",
40536
- "read_many_files",
40537
- "ReadFileGemini",
40538
- "GlobGemini",
40539
- "ListDirectory",
40540
- "SearchFileContent",
40541
- "WriteTodos",
40542
- "ReadManyFiles"
40543
- ];
40544
- const writeTools = [
40545
- "Write",
40546
- "Edit",
40547
- "MultiEdit",
40548
- "apply_patch",
40549
- "ApplyPatch",
40550
- "write_file_gemini",
40551
- "WriteFileGemini",
40552
- "replace",
40553
- "Replace"
40554
- ];
40555
- if (allowedInPlan.includes(toolName)) {
40556
- return "allow";
40557
- }
40558
- if (writeTools.includes(toolName)) {
40559
- const plansDir = join9(homedir9(), ".letta", "plans");
40560
- const targetPath = toolArgs?.file_path || toolArgs?.path;
40561
- let candidatePaths = [];
40562
- if ((toolName === "ApplyPatch" || toolName === "apply_patch") && toolArgs?.input) {
40563
- const input = toolArgs.input;
40564
- candidatePaths = extractApplyPatchPaths(input);
40565
- } else if (typeof targetPath === "string") {
40566
- candidatePaths = [targetPath];
40567
- }
40568
- if (candidatePaths.length > 0 && candidatePaths.every((path3) => {
40569
- const resolvedPath = resolvePlanTargetPath(path3, workingDirectory);
40570
- return resolvedPath ? isPathInPlansDir(resolvedPath, plansDir) : false;
40571
- })) {
40572
- return "allow";
40573
- }
40574
- }
40575
- const readOnlySubagentTypes = new Set([
40576
- "explore",
40577
- "Explore",
40578
- "plan",
40579
- "Plan",
40580
- "recall",
40581
- "Recall"
40582
- ]);
40583
- if (toolName === "Task" || toolName === "task") {
40584
- const subagentType = toolArgs?.subagent_type;
40585
- if (subagentType && readOnlySubagentTypes.has(subagentType)) {
40586
- return "allow";
40587
- }
40588
- }
40589
- if (toolName === "Skill" || toolName === "skill") {
40590
- return "allow";
40591
- }
40592
- const shellTools = [
40593
- "Bash",
40594
- "shell",
40595
- "Shell",
40596
- "shell_command",
40597
- "ShellCommand",
40598
- "run_shell_command",
40599
- "RunShellCommand",
40600
- "run_shell_command_gemini",
40601
- "RunShellCommandGemini"
40602
- ];
40603
- if (shellTools.includes(toolName)) {
40604
- const command = toolArgs?.command;
40605
- if (command && isReadOnlyShellCommand(command, { allowExternalPaths: true })) {
40606
- return "allow";
40607
- }
40608
- const planWritePath = extractPlanFileWritePathFromShellCommand(command);
40609
- if (planWritePath) {
40610
- const plansDir = join9(homedir9(), ".letta", "plans");
40611
- const resolvedPath = resolvePlanTargetPath(planWritePath, workingDirectory);
40612
- if (resolvedPath && isPathInPlansDir(resolvedPath, plansDir)) {
40613
- return "allow";
40614
- }
40615
- }
40616
- }
40617
- return "deny";
40618
- }
40619
- case "default":
40620
- return null;
40621
- default:
40622
- return null;
40623
- }
40624
- }
40625
- reset() {
40626
- this.currentMode = "default";
40627
- setGlobalPlanFilePath(null);
40628
- setGlobalModeBeforePlan(null);
40629
- }
40630
- }
40631
- var MODE_KEY, PLAN_FILE_KEY, MODE_BEFORE_PLAN_KEY, permissionMode;
40632
- var init_mode = __esm(() => {
40633
- init_readOnlyShell();
40634
- init_shell_command_normalization();
40635
- MODE_KEY = Symbol.for("@letta/permissionMode");
40636
- PLAN_FILE_KEY = Symbol.for("@letta/planFilePath");
40637
- MODE_BEFORE_PLAN_KEY = Symbol.for("@letta/permissionModeBeforePlan");
40638
- permissionMode = new PermissionModeManager;
40639
- });
40640
-
40641
39902
  // src/queue/queueRuntime.ts
40642
39903
  function isCoalescable(kind) {
40643
39904
  return kind === "message" || kind === "task_notification";
@@ -41104,8 +40365,8 @@ import {
41104
40365
  statSync as statSync2,
41105
40366
  writeFileSync as writeFileSync4
41106
40367
  } from "node:fs";
41107
- import { homedir as homedir10 } from "node:os";
41108
- import { join as join10, normalize as normalize3, relative as relative3, sep as sep2 } from "node:path";
40368
+ import { homedir as homedir8 } from "node:os";
40369
+ import { join as join9, normalize as normalize3, relative as relative2, sep as sep2 } from "node:path";
41109
40370
  function normalizeParent2(relativePath) {
41110
40371
  if (relativePath.length === 0) {
41111
40372
  return "";
@@ -41296,7 +40557,7 @@ async function buildDirectory2(dir, relativePath, entries, merkle, statsMap, pre
41296
40557
  continue;
41297
40558
  }
41298
40559
  try {
41299
- const childStat = statSync2(join10(dir, entry));
40560
+ const childStat = statSync2(join9(dir, entry));
41300
40561
  childNames.push(entry);
41301
40562
  childStatsMap.set(entry, childStat);
41302
40563
  } catch {}
@@ -41321,14 +40582,14 @@ async function buildDirectory2(dir, relativePath, entries, merkle, statsMap, pre
41321
40582
  break;
41322
40583
  }
41323
40584
  if (context2.newEntryCount > 0 && context2.newEntryCount % 500 === 0) {
41324
- await new Promise((resolve5) => setImmediate(resolve5));
40585
+ await new Promise((resolve3) => setImmediate(resolve3));
41325
40586
  }
41326
40587
  const entryStat = childStatsMap.get(entry);
41327
40588
  if (!entryStat) {
41328
40589
  continue;
41329
40590
  }
41330
- const fullPath = join10(dir, entry);
41331
- const entryPath = relative3(process.cwd(), fullPath);
40591
+ const fullPath = join9(dir, entry);
40592
+ const entryPath = relative2(process.cwd(), fullPath);
41332
40593
  if (!entryPath) {
41333
40594
  continue;
41334
40595
  }
@@ -41394,9 +40655,9 @@ function sanitizeWorkspacePath2(workspacePath) {
41394
40655
  return sanitized.length === 0 ? "workspace" : sanitized;
41395
40656
  }
41396
40657
  function getProjectStorageDir2() {
41397
- const homeDir = homedir10();
40658
+ const homeDir = homedir8();
41398
40659
  const sanitizedWorkspace = sanitizeWorkspacePath2(process.cwd());
41399
- return join10(homeDir, ".letta", "projects", sanitizedWorkspace);
40660
+ return join9(homeDir, ".letta", "projects", sanitizedWorkspace);
41400
40661
  }
41401
40662
  function ensureProjectStorageDir2() {
41402
40663
  const storageDir = getProjectStorageDir2();
@@ -41406,7 +40667,7 @@ function ensureProjectStorageDir2() {
41406
40667
  return storageDir;
41407
40668
  }
41408
40669
  function getProjectIndexPath2() {
41409
- return join10(getProjectStorageDir2(), PROJECT_INDEX_FILENAME2);
40670
+ return join9(getProjectStorageDir2(), PROJECT_INDEX_FILENAME2);
41410
40671
  }
41411
40672
  function loadCachedIndex2() {
41412
40673
  const indexPath = getProjectIndexPath2();
@@ -41451,7 +40712,7 @@ function loadCachedIndex2() {
41451
40712
  function cacheProjectIndex2(result) {
41452
40713
  try {
41453
40714
  const storageDir = ensureProjectStorageDir2();
41454
- const indexPath = join10(storageDir, PROJECT_INDEX_FILENAME2);
40715
+ const indexPath = join9(storageDir, PROJECT_INDEX_FILENAME2);
41455
40716
  const payload = {
41456
40717
  metadata: {
41457
40718
  rootHash: result.rootHash
@@ -42026,7 +41287,7 @@ async function executeCommandHook(hook, input, workingDirectory = process.cwd())
42026
41287
  };
42027
41288
  }
42028
41289
  function executeWithLauncher(launcher, inputJson, workingDirectory, input, timeout, command, startTime) {
42029
- return new Promise((resolve5, reject) => {
41290
+ return new Promise((resolve3, reject) => {
42030
41291
  let stdout = "";
42031
41292
  let stderr = "";
42032
41293
  let timedOut = false;
@@ -42053,7 +41314,7 @@ function executeWithLauncher(launcher, inputJson, workingDirectory, input, timeo
42053
41314
  `);
42054
41315
  console.log(`\x1B[90m${indented}\x1B[0m`);
42055
41316
  }
42056
- resolve5(result);
41317
+ resolve3(result);
42057
41318
  }
42058
41319
  };
42059
41320
  let child;
@@ -42129,479 +41390,1422 @@ function executeWithLauncher(launcher, inputJson, workingDirectory, input, timeo
42129
41390
  });
42130
41391
  });
42131
41392
  }
42132
- async function executeHooks(hooks, input, workingDirectory = process.cwd()) {
42133
- const results = [];
42134
- const feedback = [];
42135
- let blocked = false;
42136
- let errored = false;
42137
- for (const hook of hooks) {
42138
- const result = await executeHookCommand(hook, input, workingDirectory);
42139
- results.push(result);
42140
- if (result.exitCode === 0 /* ALLOW */) {
42141
- if (result.stdout?.trim() && (input.event_type === "UserPromptSubmit" || input.event_type === "SessionStart")) {
42142
- feedback.push(result.stdout.trim());
42143
- }
42144
- continue;
41393
+ async function executeHooks(hooks, input, workingDirectory = process.cwd()) {
41394
+ const results = [];
41395
+ const feedback = [];
41396
+ let blocked = false;
41397
+ let errored = false;
41398
+ for (const hook of hooks) {
41399
+ const result = await executeHookCommand(hook, input, workingDirectory);
41400
+ results.push(result);
41401
+ if (result.exitCode === 0 /* ALLOW */) {
41402
+ if (result.stdout?.trim() && (input.event_type === "UserPromptSubmit" || input.event_type === "SessionStart")) {
41403
+ feedback.push(result.stdout.trim());
41404
+ }
41405
+ continue;
41406
+ }
41407
+ if (result.exitCode === 2 /* BLOCK */) {
41408
+ blocked = true;
41409
+ if (result.stderr) {
41410
+ feedback.push(`[${getHookIdentifier(hook)}]: ${result.stderr}`);
41411
+ }
41412
+ break;
41413
+ }
41414
+ if (result.exitCode === 1 /* ERROR */) {
41415
+ errored = true;
41416
+ if (result.stderr) {
41417
+ feedback.push(`Hook error: ${result.stderr}`);
41418
+ } else if (result.error) {
41419
+ feedback.push(`Hook error: ${result.error}`);
41420
+ }
41421
+ }
41422
+ }
41423
+ return {
41424
+ blocked,
41425
+ errored,
41426
+ feedback,
41427
+ results
41428
+ };
41429
+ }
41430
+ async function executeHooksParallel(hooks, input, workingDirectory = process.cwd()) {
41431
+ const results = await Promise.all(hooks.map((hook) => executeHookCommand(hook, input, workingDirectory)));
41432
+ const feedback = [];
41433
+ let blocked = false;
41434
+ let errored = false;
41435
+ for (let i = 0;i < results.length; i++) {
41436
+ const result = results[i];
41437
+ const hook = hooks[i];
41438
+ if (!result || !hook)
41439
+ continue;
41440
+ if (result.exitCode === 0 /* ALLOW */ && result.stdout?.trim()) {
41441
+ try {
41442
+ const json = JSON.parse(result.stdout.trim());
41443
+ const additionalContext = json?.hookSpecificOutput?.additionalContext || json?.additionalContext;
41444
+ if (additionalContext) {
41445
+ feedback.push(additionalContext);
41446
+ }
41447
+ } catch {}
41448
+ }
41449
+ if (result.exitCode === 2 /* BLOCK */) {
41450
+ blocked = true;
41451
+ if (result.stderr) {
41452
+ feedback.push(`[${getHookIdentifier(hook)}]: ${result.stderr}`);
41453
+ }
41454
+ }
41455
+ if (result.exitCode === 1 /* ERROR */) {
41456
+ errored = true;
41457
+ if (result.stderr) {
41458
+ feedback.push(`Hook error: ${result.stderr}`);
41459
+ } else if (result.error) {
41460
+ feedback.push(`Hook error: ${result.error}`);
41461
+ }
41462
+ }
41463
+ }
41464
+ return {
41465
+ blocked,
41466
+ errored,
41467
+ feedback,
41468
+ results
41469
+ };
41470
+ }
41471
+ var DEFAULT_TIMEOUT_MS = 60000;
41472
+ var init_executor = __esm(async () => {
41473
+ init_types();
41474
+ await init_prompt_executor();
41475
+ });
41476
+
41477
+ // src/hooks/loader.ts
41478
+ import { homedir as homedir9 } from "node:os";
41479
+ import { resolve as resolve3 } from "node:path";
41480
+ function isProjectSettingsPathCollidingWithGlobal(workingDirectory) {
41481
+ const home = process.env.HOME || homedir9();
41482
+ const globalSettingsPath = resolve3(home, ".letta", "settings.json");
41483
+ const projectSettingsPath = resolve3(workingDirectory, ".letta", "settings.json");
41484
+ return globalSettingsPath === projectSettingsPath;
41485
+ }
41486
+ function loadGlobalHooks() {
41487
+ try {
41488
+ return settingsManager.getSettings().hooks || {};
41489
+ } catch (error) {
41490
+ debugLog("hooks", "loadGlobalHooks: Settings not initialized yet", error);
41491
+ return {};
41492
+ }
41493
+ }
41494
+ async function loadProjectHooks(workingDirectory = process.cwd()) {
41495
+ if (isProjectSettingsPathCollidingWithGlobal(workingDirectory)) {
41496
+ return {};
41497
+ }
41498
+ try {
41499
+ try {
41500
+ settingsManager.getProjectSettings(workingDirectory);
41501
+ } catch {
41502
+ await settingsManager.loadProjectSettings(workingDirectory);
41503
+ }
41504
+ return settingsManager.getProjectSettings(workingDirectory)?.hooks || {};
41505
+ } catch (error) {
41506
+ debugLog("hooks", "loadProjectHooks: Settings not available", error);
41507
+ return {};
41508
+ }
41509
+ }
41510
+ async function loadProjectLocalHooks(workingDirectory = process.cwd()) {
41511
+ try {
41512
+ try {
41513
+ settingsManager.getLocalProjectSettings(workingDirectory);
41514
+ } catch {
41515
+ await settingsManager.loadLocalProjectSettings(workingDirectory);
41516
+ }
41517
+ return settingsManager.getLocalProjectSettings(workingDirectory)?.hooks || {};
41518
+ } catch (error) {
41519
+ debugLog("hooks", "loadProjectLocalHooks: Settings not available", error);
41520
+ return {};
41521
+ }
41522
+ }
41523
+ function mergeHooksConfigs(global2, project, projectLocal = {}) {
41524
+ const merged = {};
41525
+ const allEvents = new Set([
41526
+ ...Object.keys(global2),
41527
+ ...Object.keys(project),
41528
+ ...Object.keys(projectLocal)
41529
+ ]);
41530
+ for (const event of allEvents) {
41531
+ if (isToolEvent(event)) {
41532
+ const toolEvent = event;
41533
+ const globalMatchers = global2[toolEvent] || [];
41534
+ const projectMatchers = project[toolEvent] || [];
41535
+ const projectLocalMatchers = projectLocal[toolEvent] || [];
41536
+ merged[toolEvent] = [
41537
+ ...projectLocalMatchers,
41538
+ ...projectMatchers,
41539
+ ...globalMatchers
41540
+ ];
41541
+ } else {
41542
+ const simpleEvent = event;
41543
+ const globalMatchers = global2[simpleEvent] || [];
41544
+ const projectMatchers = project[simpleEvent] || [];
41545
+ const projectLocalMatchers = projectLocal[simpleEvent] || [];
41546
+ merged[simpleEvent] = [
41547
+ ...projectLocalMatchers,
41548
+ ...projectMatchers,
41549
+ ...globalMatchers
41550
+ ];
41551
+ }
41552
+ }
41553
+ return merged;
41554
+ }
41555
+ async function loadHooks(workingDirectory = process.cwd()) {
41556
+ const [global2, project, projectLocal] = await Promise.all([
41557
+ Promise.resolve(loadGlobalHooks()),
41558
+ loadProjectHooks(workingDirectory),
41559
+ loadProjectLocalHooks(workingDirectory)
41560
+ ]);
41561
+ return mergeHooksConfigs(global2, project, projectLocal);
41562
+ }
41563
+ function matchesTool(pattern, toolName) {
41564
+ if (!pattern || pattern === "*") {
41565
+ return true;
41566
+ }
41567
+ try {
41568
+ const regex2 = new RegExp(`^(?:${pattern})$`);
41569
+ return regex2.test(toolName);
41570
+ } catch (error) {
41571
+ debugLog("hooks", `matchesTool: Invalid regex pattern "${pattern}", falling back to exact match`, error);
41572
+ return pattern === toolName;
41573
+ }
41574
+ }
41575
+ function filterHooksForEvent(hooks, event) {
41576
+ const filtered = [];
41577
+ const promptHooksSupported = supportsPromptHooks(event);
41578
+ for (const hook of hooks) {
41579
+ if (isPromptHook(hook)) {
41580
+ if (!promptHooksSupported) {
41581
+ console.warn(`\x1B[33m[hooks] Warning: Prompt hooks are not supported for the ${event} event. ` + `Ignoring prompt hook.\x1B[0m`);
41582
+ continue;
41583
+ }
41584
+ }
41585
+ filtered.push(hook);
41586
+ }
41587
+ return filtered;
41588
+ }
41589
+ function getMatchingHooks(config, event, toolName) {
41590
+ if (isToolEvent(event)) {
41591
+ const matchers = config[event];
41592
+ if (!matchers || matchers.length === 0) {
41593
+ return [];
41594
+ }
41595
+ const hooks = [];
41596
+ for (const matcher of matchers) {
41597
+ if (!toolName || matchesTool(matcher.matcher, toolName)) {
41598
+ hooks.push(...matcher.hooks);
41599
+ }
41600
+ }
41601
+ return filterHooksForEvent(hooks, event);
41602
+ } else {
41603
+ const matchers = config[event];
41604
+ if (!matchers || matchers.length === 0) {
41605
+ return [];
41606
+ }
41607
+ const hooks = [];
41608
+ for (const matcher of matchers) {
41609
+ hooks.push(...matcher.hooks);
41610
+ }
41611
+ return filterHooksForEvent(hooks, event);
41612
+ }
41613
+ }
41614
+ function areHooksDisabled(workingDirectory = process.cwd()) {
41615
+ try {
41616
+ const userDisabled = settingsManager.getSettings().hooks?.disabled;
41617
+ if (userDisabled === false) {
41618
+ return false;
41619
+ }
41620
+ if (userDisabled === true) {
41621
+ return true;
41622
+ }
41623
+ try {
41624
+ const projectDisabled = settingsManager.getProjectSettings(workingDirectory)?.hooks?.disabled;
41625
+ if (projectDisabled === true) {
41626
+ return true;
41627
+ }
41628
+ } catch {
41629
+ debugLog("hooks", "areHooksDisabled: Project settings not loaded, skipping");
41630
+ }
41631
+ try {
41632
+ const localDisabled = settingsManager.getLocalProjectSettings(workingDirectory)?.hooks?.disabled;
41633
+ if (localDisabled === true) {
41634
+ return true;
41635
+ }
41636
+ } catch {
41637
+ debugLog("hooks", "areHooksDisabled: Local project settings not loaded, skipping");
41638
+ }
41639
+ return false;
41640
+ } catch {
41641
+ debugLog("hooks", "areHooksDisabled: Failed to check hooks disabled status");
41642
+ return false;
41643
+ }
41644
+ }
41645
+ async function getHooksForEvent(event, toolName, workingDirectory = process.cwd()) {
41646
+ if (areHooksDisabled(workingDirectory)) {
41647
+ return [];
41648
+ }
41649
+ const config = await loadHooks(workingDirectory);
41650
+ return getMatchingHooks(config, event, toolName);
41651
+ }
41652
+ var init_loader = __esm(async () => {
41653
+ init_debug();
41654
+ init_types();
41655
+ await init_settings_manager();
41656
+ });
41657
+
41658
+ // src/hooks/index.ts
41659
+ async function runPreToolUseHooks(toolName, toolInput, toolCallId, workingDirectory = process.cwd(), agentId) {
41660
+ const hooks = await getHooksForEvent("PreToolUse", toolName, workingDirectory);
41661
+ if (hooks.length === 0) {
41662
+ return { blocked: false, errored: false, feedback: [], results: [] };
41663
+ }
41664
+ const input = {
41665
+ event_type: "PreToolUse",
41666
+ working_directory: workingDirectory,
41667
+ tool_name: toolName,
41668
+ tool_input: toolInput,
41669
+ tool_call_id: toolCallId,
41670
+ agent_id: agentId
41671
+ };
41672
+ return executeHooks(hooks, input, workingDirectory);
41673
+ }
41674
+ async function runPostToolUseHooks(toolName, toolInput, toolResult, toolCallId, workingDirectory = process.cwd(), agentId, precedingReasoning, precedingAssistantMessage) {
41675
+ const hooks = await getHooksForEvent("PostToolUse", toolName, workingDirectory);
41676
+ if (hooks.length === 0) {
41677
+ return { blocked: false, errored: false, feedback: [], results: [] };
41678
+ }
41679
+ const input = {
41680
+ event_type: "PostToolUse",
41681
+ working_directory: workingDirectory,
41682
+ tool_name: toolName,
41683
+ tool_input: toolInput,
41684
+ tool_call_id: toolCallId,
41685
+ tool_result: toolResult,
41686
+ agent_id: agentId,
41687
+ preceding_reasoning: precedingReasoning,
41688
+ preceding_assistant_message: precedingAssistantMessage
41689
+ };
41690
+ return executeHooksParallel(hooks, input, workingDirectory);
41691
+ }
41692
+ async function runPostToolUseFailureHooks(toolName, toolInput, errorMessage, errorType, toolCallId, workingDirectory = process.cwd(), agentId, precedingReasoning, precedingAssistantMessage) {
41693
+ const hooks = await getHooksForEvent("PostToolUseFailure", toolName, workingDirectory);
41694
+ if (hooks.length === 0) {
41695
+ return { blocked: false, errored: false, feedback: [], results: [] };
41696
+ }
41697
+ const input = {
41698
+ event_type: "PostToolUseFailure",
41699
+ working_directory: workingDirectory,
41700
+ tool_name: toolName,
41701
+ tool_input: toolInput,
41702
+ tool_call_id: toolCallId,
41703
+ error_message: errorMessage,
41704
+ error_type: errorType,
41705
+ agent_id: agentId,
41706
+ preceding_reasoning: precedingReasoning,
41707
+ preceding_assistant_message: precedingAssistantMessage
41708
+ };
41709
+ const result = await executeHooksParallel(hooks, input, workingDirectory);
41710
+ return {
41711
+ blocked: false,
41712
+ errored: result.errored,
41713
+ feedback: result.feedback,
41714
+ results: result.results
41715
+ };
41716
+ }
41717
+ async function runPermissionRequestHooks(toolName, toolInput, permissionType, scope, workingDirectory = process.cwd()) {
41718
+ const hooks = await getHooksForEvent("PermissionRequest", toolName, workingDirectory);
41719
+ if (hooks.length === 0) {
41720
+ return { blocked: false, errored: false, feedback: [], results: [] };
41721
+ }
41722
+ const input = {
41723
+ event_type: "PermissionRequest",
41724
+ working_directory: workingDirectory,
41725
+ tool_name: toolName,
41726
+ tool_input: toolInput,
41727
+ permission: {
41728
+ type: permissionType,
41729
+ scope
41730
+ },
41731
+ session_permissions: sessionPermissions.getRules()
41732
+ };
41733
+ return executeHooks(hooks, input, workingDirectory);
41734
+ }
41735
+ async function runUserPromptSubmitHooks(prompt, isCommand, agentId, conversationId, workingDirectory = process.cwd()) {
41736
+ if (isCommand) {
41737
+ return { blocked: false, errored: false, feedback: [], results: [] };
41738
+ }
41739
+ const hooks = await getHooksForEvent("UserPromptSubmit", undefined, workingDirectory);
41740
+ if (hooks.length === 0) {
41741
+ return { blocked: false, errored: false, feedback: [], results: [] };
41742
+ }
41743
+ const input = {
41744
+ event_type: "UserPromptSubmit",
41745
+ working_directory: workingDirectory,
41746
+ prompt,
41747
+ is_command: isCommand,
41748
+ agent_id: agentId,
41749
+ conversation_id: conversationId
41750
+ };
41751
+ return executeHooks(hooks, input, workingDirectory);
41752
+ }
41753
+ async function runNotificationHooks(message, level = "info", workingDirectory = process.cwd()) {
41754
+ const hooks = await getHooksForEvent("Notification", undefined, workingDirectory);
41755
+ if (hooks.length === 0) {
41756
+ return { blocked: false, errored: false, feedback: [], results: [] };
41757
+ }
41758
+ const input = {
41759
+ event_type: "Notification",
41760
+ working_directory: workingDirectory,
41761
+ message,
41762
+ level
41763
+ };
41764
+ return executeHooksParallel(hooks, input, workingDirectory);
41765
+ }
41766
+ async function runStopHooks(stopReason, messageCount, toolCallCount, workingDirectory = process.cwd(), precedingReasoning, assistantMessage, userMessage) {
41767
+ const hooks = await getHooksForEvent("Stop", undefined, workingDirectory);
41768
+ if (hooks.length === 0) {
41769
+ return { blocked: false, errored: false, feedback: [], results: [] };
41770
+ }
41771
+ const input = {
41772
+ event_type: "Stop",
41773
+ working_directory: workingDirectory,
41774
+ stop_reason: stopReason,
41775
+ message_count: messageCount,
41776
+ tool_call_count: toolCallCount,
41777
+ preceding_reasoning: precedingReasoning,
41778
+ assistant_message: assistantMessage,
41779
+ user_message: userMessage
41780
+ };
41781
+ return executeHooks(hooks, input, workingDirectory);
41782
+ }
41783
+ async function runSubagentStopHooks(subagentType, subagentId, success, error, agentId, conversationId, workingDirectory = process.cwd()) {
41784
+ const hooks = await getHooksForEvent("SubagentStop", undefined, workingDirectory);
41785
+ if (hooks.length === 0) {
41786
+ return { blocked: false, errored: false, feedback: [], results: [] };
41787
+ }
41788
+ const input = {
41789
+ event_type: "SubagentStop",
41790
+ working_directory: workingDirectory,
41791
+ subagent_type: subagentType,
41792
+ subagent_id: subagentId,
41793
+ success,
41794
+ error,
41795
+ agent_id: agentId,
41796
+ conversation_id: conversationId
41797
+ };
41798
+ return executeHooks(hooks, input, workingDirectory);
41799
+ }
41800
+ async function runPreCompactHooks(contextLength, maxContextLength, agentId, conversationId, workingDirectory = process.cwd()) {
41801
+ const hooks = await getHooksForEvent("PreCompact", undefined, workingDirectory);
41802
+ if (hooks.length === 0) {
41803
+ return { blocked: false, errored: false, feedback: [], results: [] };
41804
+ }
41805
+ const input = {
41806
+ event_type: "PreCompact",
41807
+ working_directory: workingDirectory,
41808
+ context_length: contextLength,
41809
+ max_context_length: maxContextLength,
41810
+ agent_id: agentId,
41811
+ conversation_id: conversationId
41812
+ };
41813
+ return executeHooksParallel(hooks, input, workingDirectory);
41814
+ }
41815
+ async function runSessionStartHooks(isNewSession, agentId, agentName, conversationId, workingDirectory = process.cwd()) {
41816
+ const hooks = await getHooksForEvent("SessionStart", undefined, workingDirectory);
41817
+ if (hooks.length === 0) {
41818
+ return { blocked: false, errored: false, feedback: [], results: [] };
41819
+ }
41820
+ const input = {
41821
+ event_type: "SessionStart",
41822
+ working_directory: workingDirectory,
41823
+ is_new_session: isNewSession,
41824
+ agent_id: agentId,
41825
+ agent_name: agentName,
41826
+ conversation_id: conversationId
41827
+ };
41828
+ const result = await executeHooks(hooks, input, workingDirectory);
41829
+ const feedback = [];
41830
+ for (const hookResult of result.results) {
41831
+ if (hookResult.stdout?.trim()) {
41832
+ feedback.push(hookResult.stdout.trim());
41833
+ }
41834
+ }
41835
+ return {
41836
+ blocked: false,
41837
+ errored: result.errored,
41838
+ feedback,
41839
+ results: result.results
41840
+ };
41841
+ }
41842
+ async function runSessionEndHooks(durationMs, messageCount, toolCallCount, agentId, conversationId, workingDirectory = process.cwd()) {
41843
+ const hooks = await getHooksForEvent("SessionEnd", undefined, workingDirectory);
41844
+ if (hooks.length === 0) {
41845
+ return { blocked: false, errored: false, feedback: [], results: [] };
41846
+ }
41847
+ const input = {
41848
+ event_type: "SessionEnd",
41849
+ working_directory: workingDirectory,
41850
+ duration_ms: durationMs,
41851
+ message_count: messageCount,
41852
+ tool_call_count: toolCallCount,
41853
+ agent_id: agentId,
41854
+ conversation_id: conversationId
41855
+ };
41856
+ return executeHooksParallel(hooks, input, workingDirectory);
41857
+ }
41858
+ var init_hooks = __esm(async () => {
41859
+ init_session();
41860
+ await __promiseAll([
41861
+ init_executor(),
41862
+ init_loader(),
41863
+ init_loader()
41864
+ ]);
41865
+ init_types();
41866
+ });
41867
+
41868
+ // src/permissions/readOnlyShell.ts
41869
+ import { homedir as homedir10 } from "node:os";
41870
+ import { resolve as resolve4 } from "node:path";
41871
+ function splitShellSegments(input) {
41872
+ const segments = [];
41873
+ let current = "";
41874
+ let i = 0;
41875
+ let quote = null;
41876
+ while (i < input.length) {
41877
+ const ch = input[i];
41878
+ if (!ch) {
41879
+ i += 1;
41880
+ continue;
41881
+ }
41882
+ if (quote === "single") {
41883
+ current += ch;
41884
+ if (ch === "'") {
41885
+ quote = null;
41886
+ }
41887
+ i += 1;
41888
+ continue;
41889
+ }
41890
+ if (quote === "double") {
41891
+ if (ch === "\\" && i + 1 < input.length) {
41892
+ current += input.slice(i, i + 2);
41893
+ i += 2;
41894
+ continue;
41895
+ }
41896
+ if (ch === "`" || input.startsWith("$(", i)) {
41897
+ return null;
41898
+ }
41899
+ current += ch;
41900
+ if (ch === '"') {
41901
+ quote = null;
41902
+ }
41903
+ i += 1;
41904
+ continue;
41905
+ }
41906
+ if (ch === "'") {
41907
+ quote = "single";
41908
+ current += ch;
41909
+ i += 1;
41910
+ continue;
41911
+ }
41912
+ if (ch === '"') {
41913
+ quote = "double";
41914
+ current += ch;
41915
+ i += 1;
41916
+ continue;
41917
+ }
41918
+ if (input.startsWith(">>", i) || ch === ">") {
41919
+ return null;
41920
+ }
41921
+ if (ch === "`" || input.startsWith("$(", i)) {
41922
+ return null;
41923
+ }
41924
+ if (input.startsWith("&&", i)) {
41925
+ segments.push(current);
41926
+ current = "";
41927
+ i += 2;
41928
+ continue;
41929
+ }
41930
+ if (input.startsWith("||", i)) {
41931
+ segments.push(current);
41932
+ current = "";
41933
+ i += 2;
41934
+ continue;
41935
+ }
41936
+ if (ch === ";") {
41937
+ segments.push(current);
41938
+ current = "";
41939
+ i += 1;
41940
+ continue;
41941
+ }
41942
+ if (ch === "|") {
41943
+ segments.push(current);
41944
+ current = "";
41945
+ i += 1;
41946
+ continue;
41947
+ }
41948
+ current += ch;
41949
+ i += 1;
41950
+ }
41951
+ segments.push(current);
41952
+ return segments.map((segment) => segment.trim()).filter(Boolean);
41953
+ }
41954
+ function isReadOnlyShellCommand(command, options = {}) {
41955
+ if (!command) {
41956
+ return false;
41957
+ }
41958
+ if (Array.isArray(command)) {
41959
+ if (command.length === 0) {
41960
+ return false;
41961
+ }
41962
+ const joined = command.join(" ");
41963
+ const [executable, ...rest] = command;
41964
+ if (executable && isShellExecutor(executable)) {
41965
+ const nested = extractDashCArgument(rest);
41966
+ if (!nested) {
41967
+ return false;
41968
+ }
41969
+ return isReadOnlyShellCommand(nested, options);
41970
+ }
41971
+ return isReadOnlyShellCommand(joined, options);
41972
+ }
41973
+ const trimmed = command.trim();
41974
+ if (!trimmed) {
41975
+ return false;
41976
+ }
41977
+ const segments = splitShellSegments(trimmed);
41978
+ if (!segments || segments.length === 0) {
41979
+ return false;
41980
+ }
41981
+ for (const segment of segments) {
41982
+ if (!isSafeSegment(segment, options)) {
41983
+ return false;
41984
+ }
41985
+ }
41986
+ return true;
41987
+ }
41988
+ function isSafeSegment(segment, options) {
41989
+ const tokens = tokenize4(segment);
41990
+ if (tokens.length === 0) {
41991
+ return false;
41992
+ }
41993
+ const command = tokens[0];
41994
+ if (!command) {
41995
+ return false;
41996
+ }
41997
+ if (isShellExecutor(command)) {
41998
+ const nested = extractDashCArgument(tokens.slice(1));
41999
+ if (!nested) {
42000
+ return false;
42001
+ }
42002
+ return isReadOnlyShellCommand(stripQuotes(nested), options);
42003
+ }
42004
+ if (ALWAYS_SAFE_COMMANDS.has(command)) {
42005
+ if (command === "cd") {
42006
+ if (options.allowExternalPaths) {
42007
+ return true;
42008
+ }
42009
+ return !tokens.slice(1).some((t) => hasAbsoluteOrTraversalPathArg(t));
42010
+ }
42011
+ const hasExternalPath = !options.allowExternalPaths && tokens.slice(1).some((t) => hasAbsoluteOrTraversalPathArg(t));
42012
+ if (hasExternalPath) {
42013
+ return false;
42014
+ }
42015
+ return true;
42016
+ }
42017
+ if (command === "sed") {
42018
+ const usesInPlace = tokens.some((token) => token === "-i" || token.startsWith("-i") || token === "--in-place");
42019
+ if (usesInPlace) {
42020
+ return false;
42021
+ }
42022
+ const hasExternalPath = !options.allowExternalPaths && tokens.slice(1).some((t) => hasAbsoluteOrTraversalPathArg(t));
42023
+ if (hasExternalPath) {
42024
+ return false;
42025
+ }
42026
+ return true;
42027
+ }
42028
+ if (command === "git") {
42029
+ const subcommand = tokens[1];
42030
+ if (!subcommand) {
42031
+ return false;
42032
+ }
42033
+ return SAFE_GIT_SUBCOMMANDS.has(subcommand);
42034
+ }
42035
+ if (command === "gh") {
42036
+ const category = tokens[1];
42037
+ if (!category) {
42038
+ return false;
42039
+ }
42040
+ if (!(category in SAFE_GH_COMMANDS)) {
42041
+ return false;
42042
+ }
42043
+ const allowedActions = SAFE_GH_COMMANDS[category];
42044
+ if (allowedActions === null) {
42045
+ return true;
42046
+ }
42047
+ if (allowedActions === undefined) {
42048
+ return false;
42049
+ }
42050
+ const action = tokens[2];
42051
+ if (!action) {
42052
+ return false;
42053
+ }
42054
+ return allowedActions.has(action);
42055
+ }
42056
+ if (command === "letta") {
42057
+ const group = tokens[1];
42058
+ if (!group) {
42059
+ return false;
42060
+ }
42061
+ if (!(group in SAFE_LETTA_COMMANDS)) {
42062
+ return false;
42063
+ }
42064
+ const action = tokens[2];
42065
+ if (!action) {
42066
+ return false;
42067
+ }
42068
+ return SAFE_LETTA_COMMANDS[group]?.has(action) ?? false;
42069
+ }
42070
+ if (command === "find") {
42071
+ return !/-delete|\s-exec\b/.test(segment);
42072
+ }
42073
+ if (command === "sort") {
42074
+ return !/\s-o\b/.test(segment);
42075
+ }
42076
+ return false;
42077
+ }
42078
+ function isShellExecutor(command) {
42079
+ return command === "bash" || command === "sh";
42080
+ }
42081
+ function tokenize4(segment) {
42082
+ const matches = segment.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
42083
+ if (!matches) {
42084
+ return [];
42085
+ }
42086
+ return matches.map((token) => stripQuotes(token));
42087
+ }
42088
+ function stripQuotes(value) {
42089
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
42090
+ return value.slice(1, -1);
42091
+ }
42092
+ return value;
42093
+ }
42094
+ function extractDashCArgument(tokens) {
42095
+ for (let i = 0;i < tokens.length; i += 1) {
42096
+ const token = tokens[i];
42097
+ if (!token) {
42098
+ continue;
42099
+ }
42100
+ if (token === "-c" || token === "-lc" || /^-[a-zA-Z]*c$/.test(token)) {
42101
+ return tokens[i + 1];
42102
+ }
42103
+ }
42104
+ return;
42105
+ }
42106
+ function isAbsolutePathArg(value) {
42107
+ if (!value) {
42108
+ return false;
42109
+ }
42110
+ if (value.startsWith("/")) {
42111
+ return true;
42112
+ }
42113
+ return /^[a-zA-Z]:[\\/]/.test(value) || value.startsWith("\\\\");
42114
+ }
42115
+ function isHomeAnchoredPathArg(value) {
42116
+ if (!value) {
42117
+ return false;
42118
+ }
42119
+ return value.startsWith("~/") || value.startsWith("$HOME/") || value.startsWith("%USERPROFILE%\\") || value.startsWith("%USERPROFILE%/");
42120
+ }
42121
+ function hasAbsoluteOrTraversalPathArg(value) {
42122
+ if (isAbsolutePathArg(value) || isHomeAnchoredPathArg(value)) {
42123
+ return true;
42124
+ }
42125
+ return /(^|[\\/])\.\.([\\/]|$)/.test(value);
42126
+ }
42127
+ function getAllowedMemoryPrefixes(agentId) {
42128
+ const home = homedir10();
42129
+ const prefixes = [
42130
+ normalizeSeparators(resolve4(home, ".letta", "agents", agentId, "memory")),
42131
+ normalizeSeparators(resolve4(home, ".letta", "agents", agentId, "memory-worktrees"))
42132
+ ];
42133
+ const parentId = process.env.LETTA_PARENT_AGENT_ID;
42134
+ if (parentId && parentId !== agentId) {
42135
+ prefixes.push(normalizeSeparators(resolve4(home, ".letta", "agents", parentId, "memory")), normalizeSeparators(resolve4(home, ".letta", "agents", parentId, "memory-worktrees")));
42136
+ }
42137
+ return prefixes;
42138
+ }
42139
+ function normalizeSeparators(p) {
42140
+ return p.replace(/\\/g, "/");
42141
+ }
42142
+ function expandPath(p) {
42143
+ const home = homedir10();
42144
+ if (p.startsWith("~/")) {
42145
+ return normalizeSeparators(resolve4(home, p.slice(2)));
42146
+ }
42147
+ if (p.startsWith("$HOME/")) {
42148
+ return normalizeSeparators(resolve4(home, p.slice(6)));
42149
+ }
42150
+ if (p.startsWith('"$HOME/')) {
42151
+ return normalizeSeparators(resolve4(home, p.slice(7).replace(/"$/, "")));
42152
+ }
42153
+ return normalizeSeparators(resolve4(p));
42154
+ }
42155
+ function isUnderMemoryDir(path4, prefixes) {
42156
+ const resolved = expandPath(path4);
42157
+ return prefixes.some((prefix) => resolved === prefix || resolved.startsWith(`${prefix}/`));
42158
+ }
42159
+ function extractCdTarget(segment) {
42160
+ const tokens = tokenize4(segment);
42161
+ if (tokens[0] === "cd" && tokens[1]) {
42162
+ return tokens[1];
42163
+ }
42164
+ return null;
42165
+ }
42166
+ function isMemoryDirCommand(command, agentId) {
42167
+ if (!command || !agentId) {
42168
+ return false;
42169
+ }
42170
+ const commandStr = typeof command === "string" ? command : command.join(" ");
42171
+ const trimmed = commandStr.trim();
42172
+ if (!trimmed) {
42173
+ return false;
42174
+ }
42175
+ const prefixes = getAllowedMemoryPrefixes(agentId);
42176
+ const segments = trimmed.split(/&&|\|\||;/).map((s) => s.trim()).filter(Boolean);
42177
+ if (segments.length === 0) {
42178
+ return false;
42179
+ }
42180
+ let cwd2 = null;
42181
+ for (const segment of segments) {
42182
+ const pipeParts = segment.split(/\|/).map((s) => s.trim()).filter(Boolean);
42183
+ for (const part of pipeParts) {
42184
+ const cdTarget = extractCdTarget(part);
42185
+ if (cdTarget) {
42186
+ const resolved = cwd2 ? expandPath(resolve4(expandPath(cwd2), cdTarget)) : expandPath(cdTarget);
42187
+ if (!isUnderMemoryDir(resolved, prefixes)) {
42188
+ return false;
42189
+ }
42190
+ cwd2 = resolved;
42191
+ continue;
42192
+ }
42193
+ if (cwd2 && isUnderMemoryDir(cwd2, prefixes)) {
42194
+ const tokens2 = tokenize4(part);
42195
+ const currentCwd = cwd2;
42196
+ if (!currentCwd) {
42197
+ return false;
42198
+ }
42199
+ const hasExternalPath = tokens2.some((t) => {
42200
+ if (isAbsolutePathArg(t) || isHomeAnchoredPathArg(t)) {
42201
+ return !isUnderMemoryDir(t, prefixes);
42202
+ }
42203
+ if (hasAbsoluteOrTraversalPathArg(t)) {
42204
+ const resolved = expandPath(resolve4(expandPath(currentCwd), t));
42205
+ return !isUnderMemoryDir(resolved, prefixes);
42206
+ }
42207
+ return false;
42208
+ });
42209
+ if (hasExternalPath) {
42210
+ return false;
42211
+ }
42212
+ if (!MEMORY_DIR_APPROVE_ALL) {
42213
+ const cmd = tokens2[0];
42214
+ if (!cmd || !SAFE_MEMORY_DIR_COMMANDS.has(cmd)) {
42215
+ return false;
42216
+ }
42217
+ }
42218
+ continue;
42219
+ }
42220
+ const tokens = tokenize4(part);
42221
+ const hasMemoryPath = tokens.some((t) => t.includes(".letta/agents/") && t.includes("/memory") || t.includes(".letta/agents/") && t.includes("/memory-worktrees"));
42222
+ if (hasMemoryPath) {
42223
+ const agentPaths = tokens.filter((t) => t.includes(".letta/agents/"));
42224
+ if (agentPaths.every((p) => isUnderMemoryDir(p, prefixes))) {
42225
+ if (!MEMORY_DIR_APPROVE_ALL) {
42226
+ const cmd = tokens[0];
42227
+ if (!cmd || !SAFE_MEMORY_DIR_COMMANDS.has(cmd)) {
42228
+ return false;
42229
+ }
42230
+ }
42231
+ continue;
42232
+ }
42233
+ }
42234
+ return false;
42235
+ }
42236
+ }
42237
+ return true;
42238
+ }
42239
+ var MEMORY_DIR_APPROVE_ALL = true, SAFE_MEMORY_DIR_COMMANDS, ALWAYS_SAFE_COMMANDS, SAFE_GIT_SUBCOMMANDS, SAFE_LETTA_COMMANDS, SAFE_GH_COMMANDS;
42240
+ var init_readOnlyShell = __esm(() => {
42241
+ SAFE_MEMORY_DIR_COMMANDS = new Set([
42242
+ "git",
42243
+ "rm",
42244
+ "mv",
42245
+ "mkdir",
42246
+ "cp",
42247
+ "ls",
42248
+ "cat",
42249
+ "head",
42250
+ "tail",
42251
+ "tree",
42252
+ "find",
42253
+ "wc",
42254
+ "split",
42255
+ "echo",
42256
+ "sort",
42257
+ "cd"
42258
+ ]);
42259
+ ALWAYS_SAFE_COMMANDS = new Set([
42260
+ "cat",
42261
+ "head",
42262
+ "tail",
42263
+ "less",
42264
+ "more",
42265
+ "grep",
42266
+ "rg",
42267
+ "ag",
42268
+ "ack",
42269
+ "fgrep",
42270
+ "egrep",
42271
+ "ls",
42272
+ "tree",
42273
+ "file",
42274
+ "stat",
42275
+ "du",
42276
+ "df",
42277
+ "wc",
42278
+ "diff",
42279
+ "cmp",
42280
+ "comm",
42281
+ "cut",
42282
+ "tr",
42283
+ "nl",
42284
+ "column",
42285
+ "fold",
42286
+ "pwd",
42287
+ "whoami",
42288
+ "hostname",
42289
+ "date",
42290
+ "uname",
42291
+ "uptime",
42292
+ "id",
42293
+ "echo",
42294
+ "printf",
42295
+ "env",
42296
+ "printenv",
42297
+ "which",
42298
+ "whereis",
42299
+ "type",
42300
+ "basename",
42301
+ "dirname",
42302
+ "realpath",
42303
+ "readlink",
42304
+ "jq",
42305
+ "yq",
42306
+ "strings",
42307
+ "xxd",
42308
+ "hexdump",
42309
+ "cd"
42310
+ ]);
42311
+ SAFE_GIT_SUBCOMMANDS = new Set([
42312
+ "status",
42313
+ "diff",
42314
+ "log",
42315
+ "show",
42316
+ "branch",
42317
+ "tag",
42318
+ "remote"
42319
+ ]);
42320
+ SAFE_LETTA_COMMANDS = {
42321
+ memfs: new Set(["status", "help", "backups", "export"]),
42322
+ agents: new Set(["list", "help"]),
42323
+ messages: new Set(["search", "list", "help"]),
42324
+ blocks: new Set(["list", "help"])
42325
+ };
42326
+ SAFE_GH_COMMANDS = {
42327
+ pr: new Set(["list", "status", "checks", "diff", "view"]),
42328
+ issue: new Set(["list", "status", "view"]),
42329
+ repo: new Set(["list", "view", "gitignore", "license"]),
42330
+ run: new Set(["list", "view", "watch", "download"]),
42331
+ release: new Set(["list", "view", "download"]),
42332
+ search: null,
42333
+ api: null,
42334
+ status: null
42335
+ };
42336
+ });
42337
+
42338
+ // src/permissions/shell-command-normalization.ts
42339
+ function trimMatchingQuotes(value) {
42340
+ const trimmed = value.trim();
42341
+ if (trimmed.length < 2) {
42342
+ return trimmed;
42343
+ }
42344
+ const first = trimmed[0];
42345
+ const last = trimmed[trimmed.length - 1];
42346
+ if ((first === '"' || first === "'") && last === first) {
42347
+ return trimmed.slice(1, -1);
42348
+ }
42349
+ return trimmed;
42350
+ }
42351
+ function normalizeExecutableToken(token) {
42352
+ const normalized = trimMatchingQuotes(token).replace(/\\/g, "/");
42353
+ const parts = normalized.split("/").filter(Boolean);
42354
+ const executable = parts[parts.length - 1] ?? normalized;
42355
+ return executable.toLowerCase();
42356
+ }
42357
+ function tokenizeShell(input) {
42358
+ const tokens = [];
42359
+ let current = "";
42360
+ let quote = null;
42361
+ let escaping = false;
42362
+ const flush = () => {
42363
+ if (current.length > 0) {
42364
+ tokens.push(current);
42365
+ current = "";
42145
42366
  }
42146
- if (result.exitCode === 2 /* BLOCK */) {
42147
- blocked = true;
42148
- if (result.stderr) {
42149
- feedback.push(`[${getHookIdentifier(hook)}]: ${result.stderr}`);
42150
- }
42151
- break;
42367
+ };
42368
+ for (let i = 0;i < input.length; i += 1) {
42369
+ const ch = input[i];
42370
+ if (ch === undefined) {
42371
+ continue;
42152
42372
  }
42153
- if (result.exitCode === 1 /* ERROR */) {
42154
- errored = true;
42155
- if (result.stderr) {
42156
- feedback.push(`Hook error: ${result.stderr}`);
42157
- } else if (result.error) {
42158
- feedback.push(`Hook error: ${result.error}`);
42159
- }
42373
+ if (escaping) {
42374
+ current += ch;
42375
+ escaping = false;
42376
+ continue;
42160
42377
  }
42161
- }
42162
- return {
42163
- blocked,
42164
- errored,
42165
- feedback,
42166
- results
42167
- };
42168
- }
42169
- async function executeHooksParallel(hooks, input, workingDirectory = process.cwd()) {
42170
- const results = await Promise.all(hooks.map((hook) => executeHookCommand(hook, input, workingDirectory)));
42171
- const feedback = [];
42172
- let blocked = false;
42173
- let errored = false;
42174
- for (let i = 0;i < results.length; i++) {
42175
- const result = results[i];
42176
- const hook = hooks[i];
42177
- if (!result || !hook)
42378
+ if (ch === "\\" && quote !== "single") {
42379
+ escaping = true;
42178
42380
  continue;
42179
- if (result.exitCode === 0 /* ALLOW */ && result.stdout?.trim()) {
42180
- try {
42181
- const json = JSON.parse(result.stdout.trim());
42182
- const additionalContext = json?.hookSpecificOutput?.additionalContext || json?.additionalContext;
42183
- if (additionalContext) {
42184
- feedback.push(additionalContext);
42185
- }
42186
- } catch {}
42187
42381
  }
42188
- if (result.exitCode === 2 /* BLOCK */) {
42189
- blocked = true;
42190
- if (result.stderr) {
42191
- feedback.push(`[${getHookIdentifier(hook)}]: ${result.stderr}`);
42382
+ if (quote === "single") {
42383
+ if (ch === "'") {
42384
+ quote = null;
42385
+ } else {
42386
+ current += ch;
42192
42387
  }
42388
+ continue;
42193
42389
  }
42194
- if (result.exitCode === 1 /* ERROR */) {
42195
- errored = true;
42196
- if (result.stderr) {
42197
- feedback.push(`Hook error: ${result.stderr}`);
42198
- } else if (result.error) {
42199
- feedback.push(`Hook error: ${result.error}`);
42390
+ if (quote === "double") {
42391
+ if (ch === '"') {
42392
+ quote = null;
42393
+ } else {
42394
+ current += ch;
42200
42395
  }
42396
+ continue;
42201
42397
  }
42202
- }
42203
- return {
42204
- blocked,
42205
- errored,
42206
- feedback,
42207
- results
42208
- };
42209
- }
42210
- var DEFAULT_TIMEOUT_MS = 60000;
42211
- var init_executor = __esm(async () => {
42212
- init_types();
42213
- await init_prompt_executor();
42214
- });
42215
-
42216
- // src/hooks/loader.ts
42217
- import { homedir as homedir11 } from "node:os";
42218
- import { resolve as resolve5 } from "node:path";
42219
- function isProjectSettingsPathCollidingWithGlobal(workingDirectory) {
42220
- const home = process.env.HOME || homedir11();
42221
- const globalSettingsPath = resolve5(home, ".letta", "settings.json");
42222
- const projectSettingsPath = resolve5(workingDirectory, ".letta", "settings.json");
42223
- return globalSettingsPath === projectSettingsPath;
42224
- }
42225
- function loadGlobalHooks() {
42226
- try {
42227
- return settingsManager.getSettings().hooks || {};
42228
- } catch (error) {
42229
- debugLog("hooks", "loadGlobalHooks: Settings not initialized yet", error);
42230
- return {};
42231
- }
42232
- }
42233
- async function loadProjectHooks(workingDirectory = process.cwd()) {
42234
- if (isProjectSettingsPathCollidingWithGlobal(workingDirectory)) {
42235
- return {};
42236
- }
42237
- try {
42238
- try {
42239
- settingsManager.getProjectSettings(workingDirectory);
42240
- } catch {
42241
- await settingsManager.loadProjectSettings(workingDirectory);
42398
+ if (ch === "'") {
42399
+ quote = "single";
42400
+ continue;
42242
42401
  }
42243
- return settingsManager.getProjectSettings(workingDirectory)?.hooks || {};
42244
- } catch (error) {
42245
- debugLog("hooks", "loadProjectHooks: Settings not available", error);
42246
- return {};
42247
- }
42248
- }
42249
- async function loadProjectLocalHooks(workingDirectory = process.cwd()) {
42250
- try {
42251
- try {
42252
- settingsManager.getLocalProjectSettings(workingDirectory);
42253
- } catch {
42254
- await settingsManager.loadLocalProjectSettings(workingDirectory);
42402
+ if (ch === '"') {
42403
+ quote = "double";
42404
+ continue;
42255
42405
  }
42256
- return settingsManager.getLocalProjectSettings(workingDirectory)?.hooks || {};
42257
- } catch (error) {
42258
- debugLog("hooks", "loadProjectLocalHooks: Settings not available", error);
42259
- return {};
42260
- }
42261
- }
42262
- function mergeHooksConfigs(global2, project, projectLocal = {}) {
42263
- const merged = {};
42264
- const allEvents = new Set([
42265
- ...Object.keys(global2),
42266
- ...Object.keys(project),
42267
- ...Object.keys(projectLocal)
42268
- ]);
42269
- for (const event of allEvents) {
42270
- if (isToolEvent(event)) {
42271
- const toolEvent = event;
42272
- const globalMatchers = global2[toolEvent] || [];
42273
- const projectMatchers = project[toolEvent] || [];
42274
- const projectLocalMatchers = projectLocal[toolEvent] || [];
42275
- merged[toolEvent] = [
42276
- ...projectLocalMatchers,
42277
- ...projectMatchers,
42278
- ...globalMatchers
42279
- ];
42280
- } else {
42281
- const simpleEvent = event;
42282
- const globalMatchers = global2[simpleEvent] || [];
42283
- const projectMatchers = project[simpleEvent] || [];
42284
- const projectLocalMatchers = projectLocal[simpleEvent] || [];
42285
- merged[simpleEvent] = [
42286
- ...projectLocalMatchers,
42287
- ...projectMatchers,
42288
- ...globalMatchers
42289
- ];
42406
+ if (/\s/.test(ch)) {
42407
+ flush();
42408
+ continue;
42290
42409
  }
42410
+ current += ch;
42291
42411
  }
42292
- return merged;
42412
+ if (escaping) {
42413
+ current += "\\";
42414
+ }
42415
+ flush();
42416
+ return tokens;
42293
42417
  }
42294
- async function loadHooks(workingDirectory = process.cwd()) {
42295
- const [global2, project, projectLocal] = await Promise.all([
42296
- Promise.resolve(loadGlobalHooks()),
42297
- loadProjectHooks(workingDirectory),
42298
- loadProjectLocalHooks(workingDirectory)
42299
- ]);
42300
- return mergeHooksConfigs(global2, project, projectLocal);
42418
+ function isDashCFlag(token) {
42419
+ return token === "-c" || /^-[a-zA-Z]*c[a-zA-Z]*$/.test(token);
42301
42420
  }
42302
- function matchesTool(pattern, toolName) {
42303
- if (!pattern || pattern === "*") {
42304
- return true;
42305
- }
42306
- try {
42307
- const regex2 = new RegExp(`^(?:${pattern})$`);
42308
- return regex2.test(toolName);
42309
- } catch (error) {
42310
- debugLog("hooks", `matchesTool: Invalid regex pattern "${pattern}", falling back to exact match`, error);
42311
- return pattern === toolName;
42421
+ function extractInnerShellCommand(tokens) {
42422
+ if (tokens.length === 0) {
42423
+ return null;
42312
42424
  }
42313
- }
42314
- function filterHooksForEvent(hooks, event) {
42315
- const filtered = [];
42316
- const promptHooksSupported = supportsPromptHooks(event);
42317
- for (const hook of hooks) {
42318
- if (isPromptHook(hook)) {
42319
- if (!promptHooksSupported) {
42320
- console.warn(`\x1B[33m[hooks] Warning: Prompt hooks are not supported for the ${event} event. ` + `Ignoring prompt hook.\x1B[0m`);
42425
+ let index = 0;
42426
+ if (normalizeExecutableToken(tokens[0] ?? "") === "env") {
42427
+ index += 1;
42428
+ while (index < tokens.length) {
42429
+ const token = tokens[index] ?? "";
42430
+ if (!token) {
42431
+ index += 1;
42432
+ continue;
42433
+ }
42434
+ if (/^-[A-Za-z]+$/.test(token)) {
42435
+ index += 1;
42436
+ continue;
42437
+ }
42438
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(token)) {
42439
+ index += 1;
42321
42440
  continue;
42322
42441
  }
42442
+ break;
42323
42443
  }
42324
- filtered.push(hook);
42325
42444
  }
42326
- return filtered;
42327
- }
42328
- function getMatchingHooks(config, event, toolName) {
42329
- if (isToolEvent(event)) {
42330
- const matchers = config[event];
42331
- if (!matchers || matchers.length === 0) {
42332
- return [];
42333
- }
42334
- const hooks = [];
42335
- for (const matcher of matchers) {
42336
- if (!toolName || matchesTool(matcher.matcher, toolName)) {
42337
- hooks.push(...matcher.hooks);
42338
- }
42445
+ const executableToken = tokens[index];
42446
+ if (!executableToken) {
42447
+ return null;
42448
+ }
42449
+ if (!SHELL_EXECUTORS.has(normalizeExecutableToken(executableToken))) {
42450
+ return null;
42451
+ }
42452
+ for (let i = index + 1;i < tokens.length; i += 1) {
42453
+ const token = tokens[i];
42454
+ if (!token) {
42455
+ continue;
42339
42456
  }
42340
- return filterHooksForEvent(hooks, event);
42341
- } else {
42342
- const matchers = config[event];
42343
- if (!matchers || matchers.length === 0) {
42344
- return [];
42457
+ if (!isDashCFlag(token)) {
42458
+ continue;
42345
42459
  }
42346
- const hooks = [];
42347
- for (const matcher of matchers) {
42348
- hooks.push(...matcher.hooks);
42460
+ const innerCommand = tokens[i + 1];
42461
+ if (!innerCommand) {
42462
+ return null;
42349
42463
  }
42350
- return filterHooksForEvent(hooks, event);
42464
+ return trimMatchingQuotes(innerCommand);
42351
42465
  }
42466
+ return null;
42352
42467
  }
42353
- function areHooksDisabled(workingDirectory = process.cwd()) {
42354
- try {
42355
- const userDisabled = settingsManager.getSettings().hooks?.disabled;
42356
- if (userDisabled === false) {
42357
- return false;
42358
- }
42359
- if (userDisabled === true) {
42360
- return true;
42361
- }
42362
- try {
42363
- const projectDisabled = settingsManager.getProjectSettings(workingDirectory)?.hooks?.disabled;
42364
- if (projectDisabled === true) {
42365
- return true;
42366
- }
42367
- } catch {
42368
- debugLog("hooks", "areHooksDisabled: Project settings not loaded, skipping");
42468
+ function unwrapShellLauncherCommand(command) {
42469
+ let current = command.trim();
42470
+ for (let depth = 0;depth < 5; depth += 1) {
42471
+ if (!current) {
42472
+ break;
42369
42473
  }
42370
- try {
42371
- const localDisabled = settingsManager.getLocalProjectSettings(workingDirectory)?.hooks?.disabled;
42372
- if (localDisabled === true) {
42373
- return true;
42374
- }
42375
- } catch {
42376
- debugLog("hooks", "areHooksDisabled: Local project settings not loaded, skipping");
42474
+ const tokens = tokenizeShell(current);
42475
+ const inner = extractInnerShellCommand(tokens);
42476
+ if (!inner || inner === current) {
42477
+ break;
42377
42478
  }
42378
- return false;
42379
- } catch {
42380
- debugLog("hooks", "areHooksDisabled: Failed to check hooks disabled status");
42381
- return false;
42479
+ current = inner.trim();
42382
42480
  }
42481
+ return current;
42383
42482
  }
42384
- async function getHooksForEvent(event, toolName, workingDirectory = process.cwd()) {
42385
- if (areHooksDisabled(workingDirectory)) {
42386
- return [];
42483
+ function normalizeBashRulePayload(payload) {
42484
+ const trimmed = payload.trim();
42485
+ if (!trimmed) {
42486
+ return "";
42387
42487
  }
42388
- const config = await loadHooks(workingDirectory);
42389
- return getMatchingHooks(config, event, toolName);
42488
+ const hasWildcardSuffix = trimmed.endsWith(":*");
42489
+ const withoutWildcard = hasWildcardSuffix ? trimmed.slice(0, -2).trimEnd() : trimmed;
42490
+ const unwrapped = unwrapShellLauncherCommand(withoutWildcard);
42491
+ if (hasWildcardSuffix) {
42492
+ return `${unwrapped}:*`;
42493
+ }
42494
+ return unwrapped;
42390
42495
  }
42391
- var init_loader = __esm(async () => {
42392
- init_debug();
42393
- init_types();
42394
- await init_settings_manager();
42496
+ var SHELL_EXECUTORS;
42497
+ var init_shell_command_normalization = __esm(() => {
42498
+ SHELL_EXECUTORS = new Set(["bash", "sh", "zsh", "dash", "ksh"]);
42395
42499
  });
42396
42500
 
42397
- // src/hooks/index.ts
42398
- async function runPreToolUseHooks(toolName, toolInput, toolCallId, workingDirectory = process.cwd(), agentId) {
42399
- const hooks = await getHooksForEvent("PreToolUse", toolName, workingDirectory);
42400
- if (hooks.length === 0) {
42401
- return { blocked: false, errored: false, feedback: [], results: [] };
42501
+ // src/permissions/mode.ts
42502
+ var exports_mode = {};
42503
+ __export(exports_mode, {
42504
+ permissionMode: () => permissionMode
42505
+ });
42506
+ import { homedir as homedir11 } from "node:os";
42507
+ import { isAbsolute as isAbsolute2, join as join10, relative as relative3, resolve as resolve5 } from "node:path";
42508
+ function getGlobalMode() {
42509
+ const global2 = globalThis;
42510
+ if (!global2[MODE_KEY]) {
42511
+ global2[MODE_KEY] = "default";
42402
42512
  }
42403
- const input = {
42404
- event_type: "PreToolUse",
42405
- working_directory: workingDirectory,
42406
- tool_name: toolName,
42407
- tool_input: toolInput,
42408
- tool_call_id: toolCallId,
42409
- agent_id: agentId
42410
- };
42411
- return executeHooks(hooks, input, workingDirectory);
42513
+ return global2[MODE_KEY];
42412
42514
  }
42413
- async function runPostToolUseHooks(toolName, toolInput, toolResult, toolCallId, workingDirectory = process.cwd(), agentId, precedingReasoning, precedingAssistantMessage) {
42414
- const hooks = await getHooksForEvent("PostToolUse", toolName, workingDirectory);
42415
- if (hooks.length === 0) {
42416
- return { blocked: false, errored: false, feedback: [], results: [] };
42417
- }
42418
- const input = {
42419
- event_type: "PostToolUse",
42420
- working_directory: workingDirectory,
42421
- tool_name: toolName,
42422
- tool_input: toolInput,
42423
- tool_call_id: toolCallId,
42424
- tool_result: toolResult,
42425
- agent_id: agentId,
42426
- preceding_reasoning: precedingReasoning,
42427
- preceding_assistant_message: precedingAssistantMessage
42428
- };
42429
- return executeHooksParallel(hooks, input, workingDirectory);
42515
+ function setGlobalMode(value) {
42516
+ const global2 = globalThis;
42517
+ global2[MODE_KEY] = value;
42430
42518
  }
42431
- async function runPostToolUseFailureHooks(toolName, toolInput, errorMessage, errorType, toolCallId, workingDirectory = process.cwd(), agentId, precedingReasoning, precedingAssistantMessage) {
42432
- const hooks = await getHooksForEvent("PostToolUseFailure", toolName, workingDirectory);
42433
- if (hooks.length === 0) {
42434
- return { blocked: false, errored: false, feedback: [], results: [] };
42435
- }
42436
- const input = {
42437
- event_type: "PostToolUseFailure",
42438
- working_directory: workingDirectory,
42439
- tool_name: toolName,
42440
- tool_input: toolInput,
42441
- tool_call_id: toolCallId,
42442
- error_message: errorMessage,
42443
- error_type: errorType,
42444
- agent_id: agentId,
42445
- preceding_reasoning: precedingReasoning,
42446
- preceding_assistant_message: precedingAssistantMessage
42447
- };
42448
- const result = await executeHooksParallel(hooks, input, workingDirectory);
42449
- return {
42450
- blocked: false,
42451
- errored: result.errored,
42452
- feedback: result.feedback,
42453
- results: result.results
42454
- };
42519
+ function getGlobalPlanFilePath() {
42520
+ const global2 = globalThis;
42521
+ return global2[PLAN_FILE_KEY] || null;
42455
42522
  }
42456
- async function runPermissionRequestHooks(toolName, toolInput, permissionType, scope, workingDirectory = process.cwd()) {
42457
- const hooks = await getHooksForEvent("PermissionRequest", toolName, workingDirectory);
42458
- if (hooks.length === 0) {
42459
- return { blocked: false, errored: false, feedback: [], results: [] };
42460
- }
42461
- const input = {
42462
- event_type: "PermissionRequest",
42463
- working_directory: workingDirectory,
42464
- tool_name: toolName,
42465
- tool_input: toolInput,
42466
- permission: {
42467
- type: permissionType,
42468
- scope
42469
- },
42470
- session_permissions: sessionPermissions.getRules()
42471
- };
42472
- return executeHooks(hooks, input, workingDirectory);
42523
+ function setGlobalPlanFilePath(value) {
42524
+ const global2 = globalThis;
42525
+ global2[PLAN_FILE_KEY] = value;
42473
42526
  }
42474
- async function runUserPromptSubmitHooks(prompt, isCommand, agentId, conversationId, workingDirectory = process.cwd()) {
42475
- if (isCommand) {
42476
- return { blocked: false, errored: false, feedback: [], results: [] };
42527
+ function getGlobalModeBeforePlan() {
42528
+ const global2 = globalThis;
42529
+ return global2[MODE_BEFORE_PLAN_KEY] ?? null;
42530
+ }
42531
+ function setGlobalModeBeforePlan(value) {
42532
+ const global2 = globalThis;
42533
+ global2[MODE_BEFORE_PLAN_KEY] = value;
42534
+ }
42535
+ function resolvePlanTargetPath(targetPath, workingDirectory) {
42536
+ const trimmedPath = targetPath.trim();
42537
+ if (!trimmedPath)
42538
+ return null;
42539
+ if (trimmedPath.startsWith("~/")) {
42540
+ return resolve5(homedir11(), trimmedPath.slice(2));
42477
42541
  }
42478
- const hooks = await getHooksForEvent("UserPromptSubmit", undefined, workingDirectory);
42479
- if (hooks.length === 0) {
42480
- return { blocked: false, errored: false, feedback: [], results: [] };
42542
+ if (isAbsolute2(trimmedPath)) {
42543
+ return resolve5(trimmedPath);
42481
42544
  }
42482
- const input = {
42483
- event_type: "UserPromptSubmit",
42484
- working_directory: workingDirectory,
42485
- prompt,
42486
- is_command: isCommand,
42487
- agent_id: agentId,
42488
- conversation_id: conversationId
42489
- };
42490
- return executeHooks(hooks, input, workingDirectory);
42545
+ return resolve5(workingDirectory, trimmedPath);
42491
42546
  }
42492
- async function runNotificationHooks(message, level = "info", workingDirectory = process.cwd()) {
42493
- const hooks = await getHooksForEvent("Notification", undefined, workingDirectory);
42494
- if (hooks.length === 0) {
42495
- return { blocked: false, errored: false, feedback: [], results: [] };
42496
- }
42497
- const input = {
42498
- event_type: "Notification",
42499
- working_directory: workingDirectory,
42500
- message,
42501
- level
42502
- };
42503
- return executeHooksParallel(hooks, input, workingDirectory);
42547
+ function isPathInPlansDir(path4, plansDir) {
42548
+ if (!path4.endsWith(".md"))
42549
+ return false;
42550
+ const rel = relative3(plansDir, path4);
42551
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
42504
42552
  }
42505
- async function runStopHooks(stopReason, messageCount, toolCallCount, workingDirectory = process.cwd(), precedingReasoning, assistantMessage, userMessage) {
42506
- const hooks = await getHooksForEvent("Stop", undefined, workingDirectory);
42507
- if (hooks.length === 0) {
42508
- return { blocked: false, errored: false, feedback: [], results: [] };
42553
+ function extractApplyPatchPaths(input) {
42554
+ const paths = [];
42555
+ const fileDirectivePattern = /\*\*\* (?:Add|Update|Delete) File:\s*(.+)/g;
42556
+ const moveDirectivePattern = /\*\*\* Move to:\s*(.+)/g;
42557
+ for (const match of input.matchAll(fileDirectivePattern)) {
42558
+ const matchPath = match[1]?.trim();
42559
+ if (matchPath)
42560
+ paths.push(matchPath);
42509
42561
  }
42510
- const input = {
42511
- event_type: "Stop",
42512
- working_directory: workingDirectory,
42513
- stop_reason: stopReason,
42514
- message_count: messageCount,
42515
- tool_call_count: toolCallCount,
42516
- preceding_reasoning: precedingReasoning,
42517
- assistant_message: assistantMessage,
42518
- user_message: userMessage
42519
- };
42520
- return executeHooks(hooks, input, workingDirectory);
42521
- }
42522
- async function runSubagentStopHooks(subagentType, subagentId, success, error, agentId, conversationId, workingDirectory = process.cwd()) {
42523
- const hooks = await getHooksForEvent("SubagentStop", undefined, workingDirectory);
42524
- if (hooks.length === 0) {
42525
- return { blocked: false, errored: false, feedback: [], results: [] };
42562
+ for (const match of input.matchAll(moveDirectivePattern)) {
42563
+ const matchPath = match[1]?.trim();
42564
+ if (matchPath)
42565
+ paths.push(matchPath);
42526
42566
  }
42527
- const input = {
42528
- event_type: "SubagentStop",
42529
- working_directory: workingDirectory,
42530
- subagent_type: subagentType,
42531
- subagent_id: subagentId,
42532
- success,
42533
- error,
42534
- agent_id: agentId,
42535
- conversation_id: conversationId
42536
- };
42537
- return executeHooks(hooks, input, workingDirectory);
42567
+ return paths;
42538
42568
  }
42539
- async function runPreCompactHooks(contextLength, maxContextLength, agentId, conversationId, workingDirectory = process.cwd()) {
42540
- const hooks = await getHooksForEvent("PreCompact", undefined, workingDirectory);
42541
- if (hooks.length === 0) {
42542
- return { blocked: false, errored: false, feedback: [], results: [] };
42569
+ function stripMatchingQuotes(value) {
42570
+ const trimmed = value.trim();
42571
+ if (trimmed.length < 2) {
42572
+ return trimmed;
42543
42573
  }
42544
- const input = {
42545
- event_type: "PreCompact",
42546
- working_directory: workingDirectory,
42547
- context_length: contextLength,
42548
- max_context_length: maxContextLength,
42549
- agent_id: agentId,
42550
- conversation_id: conversationId
42551
- };
42552
- return executeHooksParallel(hooks, input, workingDirectory);
42574
+ const first = trimmed[0];
42575
+ const last = trimmed[trimmed.length - 1];
42576
+ if ((first === '"' || first === "'") && last === first) {
42577
+ return trimmed.slice(1, -1);
42578
+ }
42579
+ return trimmed;
42553
42580
  }
42554
- async function runSessionStartHooks(isNewSession, agentId, agentName, conversationId, workingDirectory = process.cwd()) {
42555
- const hooks = await getHooksForEvent("SessionStart", undefined, workingDirectory);
42556
- if (hooks.length === 0) {
42557
- return { blocked: false, errored: false, feedback: [], results: [] };
42581
+ function extractPlanFileWritePathFromShellCommand(command) {
42582
+ if (!command) {
42583
+ return null;
42558
42584
  }
42559
- const input = {
42560
- event_type: "SessionStart",
42561
- working_directory: workingDirectory,
42562
- is_new_session: isNewSession,
42563
- agent_id: agentId,
42564
- agent_name: agentName,
42565
- conversation_id: conversationId
42566
- };
42567
- const result = await executeHooks(hooks, input, workingDirectory);
42568
- const feedback = [];
42569
- for (const hookResult of result.results) {
42570
- if (hookResult.stdout?.trim()) {
42571
- feedback.push(hookResult.stdout.trim());
42585
+ const commandString = typeof command === "string" ? command : command.join(" ") ?? "";
42586
+ const normalizedCommand = unwrapShellLauncherCommand(commandString).trim();
42587
+ if (!normalizedCommand) {
42588
+ return null;
42589
+ }
42590
+ const lines = normalizedCommand.split(/\r?\n/);
42591
+ const firstLine = lines[0]?.trim() ?? "";
42592
+ if (!firstLine) {
42593
+ return null;
42594
+ }
42595
+ const firstLineMatch = firstLine.match(/^cat\s+(?:>\s*(?<path1>"[^"]+"|'[^']+'|\S+)\s+<<-?\s*(?<delim1>"[^"]+"|'[^']+'|\S+)|<<-?\s*(?<delim2>"[^"]+"|'[^']+'|\S+)\s+>\s*(?<path2>"[^"]+"|'[^']+'|\S+))\s*$/);
42596
+ if (!firstLineMatch?.groups) {
42597
+ return null;
42598
+ }
42599
+ const rawPath = firstLineMatch.groups.path1 || firstLineMatch.groups.path2;
42600
+ const rawDelim = firstLineMatch.groups.delim1 || firstLineMatch.groups.delim2;
42601
+ if (!rawPath || !rawDelim) {
42602
+ return null;
42603
+ }
42604
+ const delimiter = stripMatchingQuotes(rawDelim);
42605
+ if (!delimiter) {
42606
+ return null;
42607
+ }
42608
+ let terminatorLine = -1;
42609
+ for (let i = 1;i < lines.length; i += 1) {
42610
+ if ((lines[i] ?? "") === delimiter) {
42611
+ terminatorLine = i;
42612
+ break;
42572
42613
  }
42573
42614
  }
42574
- return {
42575
- blocked: false,
42576
- errored: result.errored,
42577
- feedback,
42578
- results: result.results
42579
- };
42615
+ if (terminatorLine === -1) {
42616
+ return null;
42617
+ }
42618
+ for (let i = terminatorLine + 1;i < lines.length; i += 1) {
42619
+ if ((lines[i] ?? "").trim().length > 0) {
42620
+ return null;
42621
+ }
42622
+ }
42623
+ return stripMatchingQuotes(rawPath);
42580
42624
  }
42581
- async function runSessionEndHooks(durationMs, messageCount, toolCallCount, agentId, conversationId, workingDirectory = process.cwd()) {
42582
- const hooks = await getHooksForEvent("SessionEnd", undefined, workingDirectory);
42583
- if (hooks.length === 0) {
42584
- return { blocked: false, errored: false, feedback: [], results: [] };
42625
+
42626
+ class PermissionModeManager {
42627
+ get currentMode() {
42628
+ return getGlobalMode();
42629
+ }
42630
+ set currentMode(value) {
42631
+ setGlobalMode(value);
42632
+ }
42633
+ setMode(mode) {
42634
+ const prevMode = this.currentMode;
42635
+ if (mode === "plan" && prevMode !== "plan") {
42636
+ setGlobalModeBeforePlan(prevMode);
42637
+ }
42638
+ this.currentMode = mode;
42639
+ if (mode !== "plan") {
42640
+ setGlobalPlanFilePath(null);
42641
+ }
42642
+ if (prevMode === "plan" && mode !== "plan") {
42643
+ setGlobalModeBeforePlan(null);
42644
+ }
42645
+ }
42646
+ getModeBeforePlan() {
42647
+ return getGlobalModeBeforePlan();
42648
+ }
42649
+ getMode() {
42650
+ return this.currentMode;
42651
+ }
42652
+ setPlanFilePath(path4) {
42653
+ setGlobalPlanFilePath(path4);
42654
+ }
42655
+ getPlanFilePath() {
42656
+ return getGlobalPlanFilePath();
42657
+ }
42658
+ checkModeOverride(toolName, toolArgs, workingDirectory = process.cwd(), modeOverride, planFilePathOverride) {
42659
+ const effectiveMode = modeOverride ?? this.currentMode;
42660
+ const _effectivePlanFilePath = planFilePathOverride !== undefined ? planFilePathOverride : this.getPlanFilePath();
42661
+ switch (effectiveMode) {
42662
+ case "bypassPermissions":
42663
+ if (toolName === "ExitPlanMode" || toolName === "exit_plan_mode") {
42664
+ return null;
42665
+ }
42666
+ return "allow";
42667
+ case "acceptEdits":
42668
+ if ([
42669
+ "Write",
42670
+ "Edit",
42671
+ "MultiEdit",
42672
+ "NotebookEdit",
42673
+ "apply_patch",
42674
+ "replace",
42675
+ "write_file"
42676
+ ].includes(toolName)) {
42677
+ return "allow";
42678
+ }
42679
+ return null;
42680
+ case "plan": {
42681
+ const allowedInPlan = [
42682
+ "Read",
42683
+ "Glob",
42684
+ "Grep",
42685
+ "NotebookRead",
42686
+ "TodoWrite",
42687
+ "ExitPlanMode",
42688
+ "exit_plan_mode",
42689
+ "AskUserQuestion",
42690
+ "ask_user_question",
42691
+ "read_file",
42692
+ "list_dir",
42693
+ "grep_files",
42694
+ "update_plan",
42695
+ "task_output",
42696
+ "ReadFile",
42697
+ "ListDir",
42698
+ "GrepFiles",
42699
+ "UpdatePlan",
42700
+ "TaskOutput",
42701
+ "read_file_gemini",
42702
+ "glob_gemini",
42703
+ "list_directory",
42704
+ "search_file_content",
42705
+ "write_todos",
42706
+ "read_many_files",
42707
+ "ReadFileGemini",
42708
+ "GlobGemini",
42709
+ "ListDirectory",
42710
+ "SearchFileContent",
42711
+ "WriteTodos",
42712
+ "ReadManyFiles"
42713
+ ];
42714
+ const writeTools = [
42715
+ "Write",
42716
+ "Edit",
42717
+ "MultiEdit",
42718
+ "apply_patch",
42719
+ "ApplyPatch",
42720
+ "write_file_gemini",
42721
+ "WriteFileGemini",
42722
+ "replace",
42723
+ "Replace"
42724
+ ];
42725
+ if (allowedInPlan.includes(toolName)) {
42726
+ return "allow";
42727
+ }
42728
+ if (writeTools.includes(toolName)) {
42729
+ const plansDir = join10(homedir11(), ".letta", "plans");
42730
+ const targetPath = toolArgs?.file_path || toolArgs?.path;
42731
+ let candidatePaths = [];
42732
+ if ((toolName === "ApplyPatch" || toolName === "apply_patch") && toolArgs?.input) {
42733
+ const input = toolArgs.input;
42734
+ candidatePaths = extractApplyPatchPaths(input);
42735
+ } else if (typeof targetPath === "string") {
42736
+ candidatePaths = [targetPath];
42737
+ }
42738
+ if (candidatePaths.length > 0 && candidatePaths.every((path4) => {
42739
+ const resolvedPath = resolvePlanTargetPath(path4, workingDirectory);
42740
+ return resolvedPath ? isPathInPlansDir(resolvedPath, plansDir) : false;
42741
+ })) {
42742
+ return "allow";
42743
+ }
42744
+ }
42745
+ const readOnlySubagentTypes = new Set([
42746
+ "explore",
42747
+ "Explore",
42748
+ "plan",
42749
+ "Plan",
42750
+ "recall",
42751
+ "Recall"
42752
+ ]);
42753
+ if (toolName === "Task" || toolName === "task") {
42754
+ const subagentType = toolArgs?.subagent_type;
42755
+ if (subagentType && readOnlySubagentTypes.has(subagentType)) {
42756
+ return "allow";
42757
+ }
42758
+ }
42759
+ if (toolName === "Skill" || toolName === "skill") {
42760
+ return "allow";
42761
+ }
42762
+ const shellTools = [
42763
+ "Bash",
42764
+ "shell",
42765
+ "Shell",
42766
+ "shell_command",
42767
+ "ShellCommand",
42768
+ "run_shell_command",
42769
+ "RunShellCommand",
42770
+ "run_shell_command_gemini",
42771
+ "RunShellCommandGemini"
42772
+ ];
42773
+ if (shellTools.includes(toolName)) {
42774
+ const command = toolArgs?.command;
42775
+ if (command && isReadOnlyShellCommand(command, { allowExternalPaths: true })) {
42776
+ return "allow";
42777
+ }
42778
+ const planWritePath = extractPlanFileWritePathFromShellCommand(command);
42779
+ if (planWritePath) {
42780
+ const plansDir = join10(homedir11(), ".letta", "plans");
42781
+ const resolvedPath = resolvePlanTargetPath(planWritePath, workingDirectory);
42782
+ if (resolvedPath && isPathInPlansDir(resolvedPath, plansDir)) {
42783
+ return "allow";
42784
+ }
42785
+ }
42786
+ }
42787
+ return "deny";
42788
+ }
42789
+ case "default":
42790
+ return null;
42791
+ default:
42792
+ return null;
42793
+ }
42794
+ }
42795
+ reset() {
42796
+ this.currentMode = "default";
42797
+ setGlobalPlanFilePath(null);
42798
+ setGlobalModeBeforePlan(null);
42585
42799
  }
42586
- const input = {
42587
- event_type: "SessionEnd",
42588
- working_directory: workingDirectory,
42589
- duration_ms: durationMs,
42590
- message_count: messageCount,
42591
- tool_call_count: toolCallCount,
42592
- agent_id: agentId,
42593
- conversation_id: conversationId
42594
- };
42595
- return executeHooksParallel(hooks, input, workingDirectory);
42596
42800
  }
42597
- var init_hooks = __esm(async () => {
42598
- init_session();
42599
- await __promiseAll([
42600
- init_executor(),
42601
- init_loader(),
42602
- init_loader()
42603
- ]);
42604
- init_types();
42801
+ var MODE_KEY, PLAN_FILE_KEY, MODE_BEFORE_PLAN_KEY, permissionMode;
42802
+ var init_mode = __esm(() => {
42803
+ init_readOnlyShell();
42804
+ init_shell_command_normalization();
42805
+ MODE_KEY = Symbol.for("@letta/permissionMode");
42806
+ PLAN_FILE_KEY = Symbol.for("@letta/planFilePath");
42807
+ MODE_BEFORE_PLAN_KEY = Symbol.for("@letta/permissionModeBeforePlan");
42808
+ permissionMode = new PermissionModeManager;
42605
42809
  });
42606
42810
 
42607
42811
  // src/telemetry/index.ts
@@ -46531,13 +46735,23 @@ var init_Edit2 = () => {};
46531
46735
 
46532
46736
  // src/tools/impl/EnterPlanMode.ts
46533
46737
  import { relative as relative4 } from "node:path";
46534
- async function enter_plan_mode(_args) {
46535
- if (permissionMode.getMode() !== "plan" || !permissionMode.getPlanFilePath()) {
46536
- const planFilePath2 = generatePlanFilePath();
46537
- permissionMode.setMode("plan");
46538
- permissionMode.setPlanFilePath(planFilePath2);
46738
+ async function enter_plan_mode(args) {
46739
+ const scopedState = args._executionContextId ? getExecutionContextPermissionModeState(args._executionContextId) : undefined;
46740
+ if (scopedState) {
46741
+ if (scopedState.mode !== "plan" || !scopedState.planFilePath) {
46742
+ const planFilePath2 = generatePlanFilePath();
46743
+ scopedState.modeBeforePlan = scopedState.modeBeforePlan ?? scopedState.mode;
46744
+ scopedState.mode = "plan";
46745
+ scopedState.planFilePath = planFilePath2;
46746
+ }
46747
+ } else {
46748
+ if (permissionMode.getMode() !== "plan" || !permissionMode.getPlanFilePath()) {
46749
+ const planFilePath2 = generatePlanFilePath();
46750
+ permissionMode.setMode("plan");
46751
+ permissionMode.setPlanFilePath(planFilePath2);
46752
+ }
46539
46753
  }
46540
- const planFilePath = permissionMode.getPlanFilePath();
46754
+ const planFilePath = scopedState?.planFilePath ?? permissionMode.getPlanFilePath();
46541
46755
  const cwd2 = process.env.USER_CWD || process.cwd();
46542
46756
  const applyPatchRelativePath = planFilePath ? relative4(cwd2, planFilePath).replace(/\\/g, "/") : null;
46543
46757
  return {
@@ -46557,14 +46771,22 @@ Plan file path: ${planFilePath}
46557
46771
  ${applyPatchRelativePath ? `If using apply_patch, use this exact relative patch path: ${applyPatchRelativePath}` : ""}`
46558
46772
  };
46559
46773
  }
46560
- var init_EnterPlanMode2 = __esm(() => {
46774
+ var init_EnterPlanMode2 = __esm(async () => {
46561
46775
  init_planName();
46562
46776
  init_mode();
46777
+ await init_manager3();
46563
46778
  });
46564
46779
 
46565
46780
  // src/tools/impl/ExitPlanMode.ts
46566
- async function exit_plan_mode() {
46567
- if (permissionMode.getMode() === "plan") {
46781
+ async function exit_plan_mode(args = {}) {
46782
+ const scopedState = args._executionContextId ? getExecutionContextPermissionModeState(args._executionContextId) : undefined;
46783
+ if (scopedState) {
46784
+ if (scopedState.mode === "plan") {
46785
+ scopedState.mode = scopedState.modeBeforePlan ?? "default";
46786
+ scopedState.modeBeforePlan = null;
46787
+ scopedState.planFilePath = null;
46788
+ }
46789
+ } else if (permissionMode.getMode() === "plan") {
46568
46790
  const restoredMode = permissionMode.getModeBeforePlan() ?? "default";
46569
46791
  permissionMode.setMode(restoredMode);
46570
46792
  }
@@ -46575,8 +46797,9 @@ async function exit_plan_mode() {
46575
46797
  ` + "Tip: If this plan will be referenced in the future by your future-self, " + "other agents, or humans, consider renaming the plan file to something easily " + "identifiable with a timestamp (e.g., `2026-01-auth-refactor.md`) rather than the random name."
46576
46798
  };
46577
46799
  }
46578
- var init_ExitPlanMode2 = __esm(() => {
46800
+ var init_ExitPlanMode2 = __esm(async () => {
46579
46801
  init_mode();
46802
+ await init_manager3();
46580
46803
  });
46581
46804
 
46582
46805
  // src/tools/impl/Glob.ts
@@ -64052,8 +64275,6 @@ var init_toolDefinitions = __esm(async () => {
64052
64275
  init_AskUserQuestion2();
64053
64276
  init_BashOutput2();
64054
64277
  init_Edit2();
64055
- init_EnterPlanMode2();
64056
- init_ExitPlanMode2();
64057
64278
  init_Glob2();
64058
64279
  init_GlobGemini2();
64059
64280
  init_Grep2();
@@ -64111,6 +64332,8 @@ var init_toolDefinitions = __esm(async () => {
64111
64332
  init_WriteTodosGemini2();
64112
64333
  await __promiseAll([
64113
64334
  init_Bash2(),
64335
+ init_EnterPlanMode2(),
64336
+ init_ExitPlanMode2(),
64114
64337
  init_Memory2(),
64115
64338
  init_Read2(),
64116
64339
  init_ReadFileGemini2(),
@@ -65780,9 +66003,9 @@ function shouldAttachTrace(result) {
65780
66003
  }
65781
66004
  return result.decision === "ask" || result.decision === "deny";
65782
66005
  }
65783
- function checkPermission(toolName, toolArgs, permissions, workingDirectory = process.cwd()) {
66006
+ function checkPermission(toolName, toolArgs, permissions, workingDirectory = process.cwd(), modeState) {
65784
66007
  const engine = isPermissionsV2Enabled() ? "v2" : "v1";
65785
- const primary = checkPermissionForEngine(engine, toolName, toolArgs, permissions, workingDirectory);
66008
+ const primary = checkPermissionForEngine(engine, toolName, toolArgs, permissions, workingDirectory, modeState);
65786
66009
  let result = primary.result;
65787
66010
  const includeTrace = shouldAttachTrace(primary.result);
65788
66011
  if (includeTrace) {
@@ -65801,7 +66024,7 @@ function checkPermission(toolName, toolArgs, permissions, workingDirectory = pro
65801
66024
  }
65802
66025
  if (envFlagEnabled("LETTA_PERMISSIONS_DUAL_EVAL")) {
65803
66026
  const shadowEngine = engine === "v2" ? "v1" : "v2";
65804
- const shadow = checkPermissionForEngine(shadowEngine, toolName, toolArgs, permissions, workingDirectory);
66027
+ const shadow = checkPermissionForEngine(shadowEngine, toolName, toolArgs, permissions, workingDirectory, modeState);
65805
66028
  const mismatch = primary.result.decision !== shadow.result.decision || primary.result.matchedRule !== shadow.result.matchedRule;
65806
66029
  if (mismatch) {
65807
66030
  console.error(`[permissions] dual-eval mismatch ${JSON.stringify({
@@ -65850,7 +66073,7 @@ function traceEvent(trace, stage, message, pattern, matched) {
65850
66073
  event.matched = matched;
65851
66074
  trace.events.push(event);
65852
66075
  }
65853
- function checkPermissionForEngine(engine, toolName, toolArgs, permissions, workingDirectory) {
66076
+ function checkPermissionForEngine(engine, toolName, toolArgs, permissions, workingDirectory, modeState) {
65854
66077
  const canonicalTool = canonicalToolName(toolName);
65855
66078
  const queryTool = engine === "v2" ? canonicalTool : toolName;
65856
66079
  const query = buildPermissionQuery(queryTool, toolArgs, engine);
@@ -65888,20 +66111,20 @@ function checkPermissionForEngine(engine, toolName, toolArgs, permissions, worki
65888
66111
  };
65889
66112
  }
65890
66113
  }
65891
- const modeOverride = permissionMode.checkModeOverride(toolName, toolArgs, workingDirectory);
66114
+ const effectiveMode = modeState?.mode ?? permissionMode.getMode();
66115
+ const effectivePlanFilePath = modeState?.planFilePath ?? permissionMode.getPlanFilePath();
66116
+ const modeOverride = permissionMode.checkModeOverride(toolName, toolArgs, workingDirectory, effectiveMode, effectivePlanFilePath);
65892
66117
  if (modeOverride) {
65893
- const currentMode = permissionMode.getMode();
65894
- let reason = `Permission mode: ${currentMode}`;
65895
- if (currentMode === "plan" && modeOverride === "deny") {
65896
- const planFilePath = permissionMode.getPlanFilePath();
65897
- const applyPatchRelativePath = planFilePath ? relative6(workingDirectory, planFilePath).replace(/\\/g, "/") : null;
65898
- reason = `Plan mode is active. You can only use read-only tools (Read, Grep, Glob, etc.) and write to the plan file. ` + `Write your plan to: ${planFilePath || "(error: plan file path not configured)"}. ` + (applyPatchRelativePath ? `If using apply_patch, use this exact relative path in patch headers: ${applyPatchRelativePath}. ` : "") + `Use ExitPlanMode when your plan is ready for user approval.`;
66118
+ let reason = `Permission mode: ${effectiveMode}`;
66119
+ if (effectiveMode === "plan" && modeOverride === "deny") {
66120
+ const applyPatchRelativePath = effectivePlanFilePath ? relative6(workingDirectory, effectivePlanFilePath).replace(/\\/g, "/") : null;
66121
+ reason = `Plan mode is active. You can only use read-only tools (Read, Grep, Glob, etc.) and write to the plan file. ` + `Write your plan to: ${effectivePlanFilePath || "(error: plan file path not configured)"}. ` + (applyPatchRelativePath ? `If using apply_patch, use this exact relative path in patch headers: ${applyPatchRelativePath}. ` : "") + `Use ExitPlanMode when your plan is ready for user approval.`;
65899
66122
  }
65900
66123
  traceEvent(trace, "mode-override", reason);
65901
66124
  return {
65902
66125
  result: {
65903
66126
  decision: modeOverride,
65904
- matchedRule: `${currentMode} mode`,
66127
+ matchedRule: `${effectiveMode} mode`,
65905
66128
  reason
65906
66129
  },
65907
66130
  trace
@@ -66171,8 +66394,8 @@ function getDefaultDecision(toolName, toolArgs) {
66171
66394
  }
66172
66395
  return "ask";
66173
66396
  }
66174
- async function checkPermissionWithHooks(toolName, toolArgs, permissions, workingDirectory = process.cwd()) {
66175
- const result = checkPermission(toolName, toolArgs, permissions, workingDirectory);
66397
+ async function checkPermissionWithHooks(toolName, toolArgs, permissions, workingDirectory = process.cwd(), modeState) {
66398
+ const result = checkPermission(toolName, toolArgs, permissions, workingDirectory, modeState);
66176
66399
  if (result.decision === "ask") {
66177
66400
  const hookResult = await runPermissionRequestHooks(toolName, toolArgs, "ask", undefined, workingDirectory);
66178
66401
  if (hookResult.blocked) {
@@ -67017,6 +67240,7 @@ __export(exports_manager2, {
67017
67240
  getInternalToolName: () => getInternalToolName,
67018
67241
  getExternalToolsAsClientTools: () => getExternalToolsAsClientTools,
67019
67242
  getExternalToolDefinition: () => getExternalToolDefinition,
67243
+ getExecutionContextPermissionModeState: () => getExecutionContextPermissionModeState,
67020
67244
  getClientToolsFromRegistry: () => getClientToolsFromRegistry,
67021
67245
  getAllLettaToolNames: () => getAllLettaToolNames,
67022
67246
  executeTool: () => executeTool,
@@ -67084,6 +67308,9 @@ function saveExecutionContext(snapshot) {
67084
67308
  function getExecutionContextById(contextId) {
67085
67309
  return getExecutionContexts().get(contextId);
67086
67310
  }
67311
+ function getExecutionContextPermissionModeState(contextId) {
67312
+ return getExecutionContextById(contextId)?.permissionModeState;
67313
+ }
67087
67314
  function clearCapturedToolExecutionContexts() {
67088
67315
  getExecutionContexts().clear();
67089
67316
  }
@@ -67201,12 +67428,31 @@ function getClientToolsFromRegistry() {
67201
67428
  const externalTools = getExternalToolsAsClientTools();
67202
67429
  return [...builtInTools, ...externalTools];
67203
67430
  }
67204
- function captureToolExecutionContext(workingDirectory = process.env.USER_CWD || process.cwd()) {
67431
+ function captureToolExecutionContext(workingDirectory = process.env.USER_CWD || process.cwd(), permissionModeState) {
67432
+ const effectivePermissionModeState = permissionModeState ?? {
67433
+ get mode() {
67434
+ return permissionMode.getMode();
67435
+ },
67436
+ set mode(value) {
67437
+ permissionMode.setMode(value);
67438
+ },
67439
+ get planFilePath() {
67440
+ return permissionMode.getPlanFilePath();
67441
+ },
67442
+ set planFilePath(value) {
67443
+ permissionMode.setPlanFilePath(value);
67444
+ },
67445
+ get modeBeforePlan() {
67446
+ return permissionMode.getModeBeforePlan();
67447
+ },
67448
+ set modeBeforePlan(_value) {}
67449
+ };
67205
67450
  const snapshot = {
67206
67451
  toolRegistry: new Map(toolRegistry),
67207
67452
  externalTools: new Map(getExternalToolsRegistry()),
67208
67453
  externalExecutor: getExternalToolExecutor(),
67209
- workingDirectory
67454
+ workingDirectory,
67455
+ permissionModeState: effectivePermissionModeState
67210
67456
  };
67211
67457
  const contextId = saveExecutionContext(snapshot);
67212
67458
  const builtInTools = Array.from(snapshot.toolRegistry.entries()).map(([name, tool]) => ({
@@ -67246,11 +67492,11 @@ function getToolPermissions(toolName) {
67246
67492
  function requiresApproval(toolName) {
67247
67493
  return TOOL_PERMISSIONS[toolName]?.requiresApproval ?? false;
67248
67494
  }
67249
- async function checkToolPermission(toolName, toolArgs, workingDirectory = process.cwd()) {
67495
+ async function checkToolPermission(toolName, toolArgs, workingDirectory = process.cwd(), permissionModeStateArg) {
67250
67496
  const { checkPermissionWithHooks: checkPermissionWithHooks2 } = await init_checker().then(() => exports_checker);
67251
67497
  const { loadPermissions: loadPermissions2 } = await Promise.resolve().then(() => (init_loader2(), exports_loader));
67252
67498
  const permissions = await loadPermissions2(workingDirectory);
67253
- return checkPermissionWithHooks2(toolName, toolArgs, permissions, workingDirectory);
67499
+ return checkPermissionWithHooks2(toolName, toolArgs, permissions, workingDirectory, permissionModeStateArg);
67254
67500
  }
67255
67501
  async function savePermissionRule2(rule, ruleType, scope, workingDirectory = process.cwd()) {
67256
67502
  if (scope === "session") {
@@ -67567,6 +67813,18 @@ async function executeTool(name, args, options) {
67567
67813
  if (internalName === "Skill" && options?.toolCallId) {
67568
67814
  enhancedArgs = { ...enhancedArgs, toolCallId: options.toolCallId };
67569
67815
  }
67816
+ const PLAN_MODE_TOOL_NAMES = new Set([
67817
+ "EnterPlanMode",
67818
+ "enter_plan_mode",
67819
+ "ExitPlanMode",
67820
+ "exit_plan_mode"
67821
+ ]);
67822
+ if (PLAN_MODE_TOOL_NAMES.has(internalName) && options?.toolContextId) {
67823
+ enhancedArgs = {
67824
+ ...enhancedArgs,
67825
+ _executionContextId: options.toolContextId
67826
+ };
67827
+ }
67570
67828
  const result = await withExecutionWorkingDirectory(workingDirectory, () => tool.fn(enhancedArgs));
67571
67829
  const duration = Date.now() - startTime;
67572
67830
  if (FILE_MODIFYING_TOOLS.has(internalName)) {
@@ -67696,6 +67954,7 @@ var init_manager3 = __esm(async () => {
67696
67954
  init_subagents();
67697
67955
  init_fileIndex();
67698
67956
  init_constants();
67957
+ init_mode();
67699
67958
  init_debug();
67700
67959
  await __promiseAll([
67701
67960
  init_approval_execution(),
@@ -67875,7 +68134,125 @@ var init_manager3 = __esm(async () => {
67875
68134
  });
67876
68135
 
67877
68136
  // src/websocket/terminalHandler.ts
68137
+ import * as os4 from "node:os";
67878
68138
  import WebSocket from "ws";
68139
+ function getDefaultShell() {
68140
+ if (os4.platform() === "win32") {
68141
+ return process.env.COMSPEC || "cmd.exe";
68142
+ }
68143
+ return process.env.SHELL || "/bin/zsh";
68144
+ }
68145
+ function sendTerminalMessage(socket, message) {
68146
+ if (socket.readyState === WebSocket.OPEN) {
68147
+ socket.send(JSON.stringify(message));
68148
+ }
68149
+ }
68150
+ function handleTerminalSpawn(msg, socket, cwd2) {
68151
+ const { terminal_id, cols, rows } = msg;
68152
+ killTerminal(terminal_id);
68153
+ const shell2 = getDefaultShell();
68154
+ console.log(`[Terminal] Spawning PTY: shell=${shell2}, cwd=${cwd2}, cols=${cols}, rows=${rows}`);
68155
+ try {
68156
+ const proc2 = Bun.spawn([shell2], {
68157
+ cwd: cwd2,
68158
+ env: {
68159
+ ...process.env,
68160
+ TERM: "xterm-256color",
68161
+ COLORTERM: "truecolor"
68162
+ },
68163
+ terminal: {
68164
+ cols: cols || 80,
68165
+ rows: rows || 24,
68166
+ data: (() => {
68167
+ let buffer = "";
68168
+ let flushTimer = null;
68169
+ return (_terminal, data) => {
68170
+ buffer += new TextDecoder().decode(data);
68171
+ if (!flushTimer) {
68172
+ flushTimer = setTimeout(() => {
68173
+ if (buffer.length > 0) {
68174
+ sendTerminalMessage(socket, {
68175
+ type: "terminal_output",
68176
+ terminal_id,
68177
+ data: buffer
68178
+ });
68179
+ buffer = "";
68180
+ }
68181
+ flushTimer = null;
68182
+ }, 16);
68183
+ }
68184
+ };
68185
+ })()
68186
+ }
68187
+ });
68188
+ const terminal = proc2.terminal;
68189
+ console.log(`[Terminal] proc.pid=${proc2.pid}, terminal=${typeof terminal}, keys=${Object.keys(proc2).join(",")}`);
68190
+ if (!terminal) {
68191
+ console.error("[Terminal] terminal object is undefined on proc — Bun.Terminal API may not be available");
68192
+ sendTerminalMessage(socket, {
68193
+ type: "terminal_exited",
68194
+ terminal_id,
68195
+ exitCode: 1
68196
+ });
68197
+ return;
68198
+ }
68199
+ const session = {
68200
+ process: proc2,
68201
+ terminal,
68202
+ terminalId: terminal_id,
68203
+ spawnedAt: Date.now()
68204
+ };
68205
+ terminals.set(terminal_id, session);
68206
+ console.log(`[Terminal] Session stored for terminal_id=${terminal_id}, map size=${terminals.size}`);
68207
+ const myPid = proc2.pid;
68208
+ proc2.exited.then((exitCode) => {
68209
+ const current = terminals.get(terminal_id);
68210
+ if (current && current.process.pid === myPid) {
68211
+ console.log(`[Terminal] PTY process exited: terminal_id=${terminal_id}, pid=${myPid}, exitCode=${exitCode}`);
68212
+ terminals.delete(terminal_id);
68213
+ sendTerminalMessage(socket, {
68214
+ type: "terminal_exited",
68215
+ terminal_id,
68216
+ exitCode: exitCode ?? 0
68217
+ });
68218
+ } else {
68219
+ console.log(`[Terminal] Stale PTY exit ignored: terminal_id=${terminal_id}, pid=${myPid} (current pid=${current?.process.pid})`);
68220
+ }
68221
+ });
68222
+ sendTerminalMessage(socket, {
68223
+ type: "terminal_spawned",
68224
+ terminal_id,
68225
+ pid: proc2.pid
68226
+ });
68227
+ } catch (error) {
68228
+ console.error("[Terminal] Failed to spawn PTY:", error);
68229
+ sendTerminalMessage(socket, {
68230
+ type: "terminal_exited",
68231
+ terminal_id,
68232
+ exitCode: 1
68233
+ });
68234
+ }
68235
+ }
68236
+ function handleTerminalInput(msg) {
68237
+ const session = terminals.get(msg.terminal_id);
68238
+ if (session) {
68239
+ session.terminal.write(msg.data);
68240
+ }
68241
+ }
68242
+ function handleTerminalResize(msg) {
68243
+ const session = terminals.get(msg.terminal_id);
68244
+ if (session) {
68245
+ session.terminal.resize(msg.cols, msg.rows);
68246
+ }
68247
+ }
68248
+ function handleTerminalKill(msg) {
68249
+ const session = terminals.get(msg.terminal_id);
68250
+ if (session && Date.now() - session.spawnedAt < 2000) {
68251
+ console.log(`[Terminal] Ignoring kill for recently spawned session (age=${Date.now() - session.spawnedAt}ms)`);
68252
+ return;
68253
+ }
68254
+ killTerminal(msg.terminal_id);
68255
+ }
67879
68256
  function killTerminal(terminalId) {
67880
68257
  const session = terminals.get(terminalId);
67881
68258
  if (session) {
@@ -67904,6 +68281,75 @@ var init_constants2 = __esm(() => {
67904
68281
  SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
67905
68282
  });
67906
68283
 
68284
+ // src/websocket/listener/remote-settings.ts
68285
+ import { existsSync as existsSync16, readFileSync as readFileSync8 } from "node:fs";
68286
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
68287
+ import { homedir as homedir18 } from "node:os";
68288
+ import path20 from "node:path";
68289
+ function getRemoteSettingsPath() {
68290
+ return path20.join(homedir18(), ".letta", "remote-settings.json");
68291
+ }
68292
+ function loadRemoteSettings() {
68293
+ if (_cache !== null) {
68294
+ return _cache;
68295
+ }
68296
+ let loaded = {};
68297
+ try {
68298
+ const settingsPath = getRemoteSettingsPath();
68299
+ if (existsSync16(settingsPath)) {
68300
+ const raw = readFileSync8(settingsPath, "utf-8");
68301
+ const parsed = JSON.parse(raw);
68302
+ loaded = parsed;
68303
+ }
68304
+ } catch {}
68305
+ if (loaded.cwdMap) {
68306
+ const validCwdMap = {};
68307
+ for (const [key, value] of Object.entries(loaded.cwdMap)) {
68308
+ if (typeof value === "string" && existsSync16(value)) {
68309
+ validCwdMap[key] = value;
68310
+ }
68311
+ }
68312
+ loaded.cwdMap = validCwdMap;
68313
+ }
68314
+ if (!loaded.cwdMap) {
68315
+ loaded.cwdMap = loadLegacyCwdCache();
68316
+ }
68317
+ _cache = loaded;
68318
+ return _cache;
68319
+ }
68320
+ function saveRemoteSettings(updates) {
68321
+ if (_cache === null) {
68322
+ loadRemoteSettings();
68323
+ }
68324
+ _cache = {
68325
+ ..._cache,
68326
+ ...updates
68327
+ };
68328
+ const snapshot = _cache;
68329
+ const settingsPath = getRemoteSettingsPath();
68330
+ mkdir3(path20.dirname(settingsPath), { recursive: true }).then(() => writeFile3(settingsPath, JSON.stringify(snapshot, null, 2))).catch(() => {});
68331
+ }
68332
+ function loadLegacyCwdCache() {
68333
+ try {
68334
+ const legacyPath = path20.join(homedir18(), ".letta", "cwd-cache.json");
68335
+ if (!existsSync16(legacyPath))
68336
+ return {};
68337
+ const raw = readFileSync8(legacyPath, "utf-8");
68338
+ const parsed = JSON.parse(raw);
68339
+ const result = {};
68340
+ for (const [key, value] of Object.entries(parsed)) {
68341
+ if (typeof value === "string" && existsSync16(value)) {
68342
+ result[key] = value;
68343
+ }
68344
+ }
68345
+ return result;
68346
+ } catch {
68347
+ return {};
68348
+ }
68349
+ }
68350
+ var _cache = null;
68351
+ var init_remote_settings = () => {};
68352
+
67907
68353
  // src/websocket/listener/scope.ts
67908
68354
  function getOnlyConversationRuntime(runtime) {
67909
68355
  if (!runtime || runtime.conversationRuntimes.size !== 1) {
@@ -67949,10 +68395,6 @@ function resolveRuntimeScope(runtime, params) {
67949
68395
  }
67950
68396
 
67951
68397
  // src/websocket/listener/cwd.ts
67952
- import { existsSync as existsSync16 } from "node:fs";
67953
- import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
67954
- import { homedir as homedir18 } from "node:os";
67955
- import path20 from "node:path";
67956
68398
  function getWorkingDirectoryScopeKey(agentId, conversationId) {
67957
68399
  const normalizedConversationId = normalizeConversationId(conversationId);
67958
68400
  const normalizedAgentId = normalizeCwdAgentId(agentId);
@@ -67965,21 +68407,12 @@ function getConversationWorkingDirectory(runtime, agentId, conversationId) {
67965
68407
  const scopeKey = getWorkingDirectoryScopeKey(agentId, conversationId);
67966
68408
  return runtime.workingDirectoryByConversation.get(scopeKey) ?? runtime.bootWorkingDirectory;
67967
68409
  }
67968
- function getCwdCachePath() {
67969
- return path20.join(homedir18(), ".letta", "cwd-cache.json");
67970
- }
67971
68410
  function loadPersistedCwdMap() {
67972
- if (!shouldPersistCwd)
67973
- return new Map;
67974
68411
  try {
67975
- const cachePath = getCwdCachePath();
67976
- if (!existsSync16(cachePath))
67977
- return new Map;
67978
- const raw = __require("node:fs").readFileSync(cachePath, "utf-8");
67979
- const parsed = JSON.parse(raw);
68412
+ const settings = loadRemoteSettings();
67980
68413
  const map = new Map;
67981
- for (const [key, value] of Object.entries(parsed)) {
67982
- if (typeof value === "string" && existsSync16(value)) {
68414
+ if (settings.cwdMap) {
68415
+ for (const [key, value] of Object.entries(settings.cwdMap)) {
67983
68416
  map.set(key, value);
67984
68417
  }
67985
68418
  }
@@ -67989,11 +68422,7 @@ function loadPersistedCwdMap() {
67989
68422
  }
67990
68423
  }
67991
68424
  function persistCwdMap(map) {
67992
- if (!shouldPersistCwd)
67993
- return;
67994
- const cachePath = getCwdCachePath();
67995
- const obj = Object.fromEntries(map);
67996
- mkdir3(path20.dirname(cachePath), { recursive: true }).then(() => writeFile3(cachePath, JSON.stringify(obj, null, 2))).catch(() => {});
68425
+ saveRemoteSettings({ cwdMap: Object.fromEntries(map) });
67997
68426
  }
67998
68427
  function setConversationWorkingDirectory(runtime, agentId, conversationId, workingDirectory) {
67999
68428
  const scopeKey = getWorkingDirectoryScopeKey(agentId, conversationId);
@@ -68004,9 +68433,73 @@ function setConversationWorkingDirectory(runtime, agentId, conversationId, worki
68004
68433
  }
68005
68434
  persistCwdMap(runtime.workingDirectoryByConversation);
68006
68435
  }
68007
- var shouldPersistCwd;
68008
68436
  var init_cwd = __esm(() => {
68009
- shouldPersistCwd = process.env.PERSIST_CWD === "1";
68437
+ init_remote_settings();
68438
+ });
68439
+
68440
+ // src/websocket/listener/permissionMode.ts
68441
+ function getPermissionModeScopeKey(agentId, conversationId) {
68442
+ const normalizedConversationId = normalizeConversationId(conversationId);
68443
+ const normalizedAgentId = normalizeCwdAgentId(agentId);
68444
+ if (normalizedConversationId === "default") {
68445
+ return `agent:${normalizedAgentId ?? "__unknown__"}::conversation:default`;
68446
+ }
68447
+ return `conversation:${normalizedConversationId}`;
68448
+ }
68449
+ function getConversationPermissionModeState(runtime, agentId, conversationId) {
68450
+ const scopeKey = getPermissionModeScopeKey(agentId, conversationId);
68451
+ return runtime.permissionModeByConversation.get(scopeKey) ?? {
68452
+ mode: permissionMode.getMode(),
68453
+ planFilePath: null,
68454
+ modeBeforePlan: null
68455
+ };
68456
+ }
68457
+ function setConversationPermissionModeState(runtime, agentId, conversationId, state) {
68458
+ const scopeKey = getPermissionModeScopeKey(agentId, conversationId);
68459
+ if (state.mode === permissionMode.getMode() && state.planFilePath === null && state.modeBeforePlan === null) {
68460
+ runtime.permissionModeByConversation.delete(scopeKey);
68461
+ } else {
68462
+ runtime.permissionModeByConversation.set(scopeKey, { ...state });
68463
+ }
68464
+ persistPermissionModeMap(runtime.permissionModeByConversation);
68465
+ }
68466
+ function loadPersistedPermissionModeMap() {
68467
+ try {
68468
+ const settings = loadRemoteSettings();
68469
+ const map = new Map;
68470
+ if (!settings.permissionModeMap) {
68471
+ return map;
68472
+ }
68473
+ for (const [key, persisted] of Object.entries(settings.permissionModeMap)) {
68474
+ const restoredMode = persisted.mode === "plan" ? persisted.modeBeforePlan ?? "default" : persisted.mode;
68475
+ map.set(key, {
68476
+ mode: restoredMode,
68477
+ planFilePath: null,
68478
+ modeBeforePlan: null
68479
+ });
68480
+ }
68481
+ return map;
68482
+ } catch {
68483
+ return new Map;
68484
+ }
68485
+ }
68486
+ function persistPermissionModeMap(map) {
68487
+ const permissionModeMap = {};
68488
+ for (const [key, state] of map) {
68489
+ const modeToSave = state.mode === "plan" ? state.modeBeforePlan ?? "default" : state.mode;
68490
+ if (modeToSave === "default" && state.modeBeforePlan === null) {
68491
+ continue;
68492
+ }
68493
+ permissionModeMap[key] = {
68494
+ mode: modeToSave,
68495
+ modeBeforePlan: state.mode === "plan" ? null : state.modeBeforePlan ?? null
68496
+ };
68497
+ }
68498
+ saveRemoteSettings({ permissionModeMap });
68499
+ }
68500
+ var init_permissionMode = __esm(() => {
68501
+ init_mode();
68502
+ init_remote_settings();
68010
68503
  });
68011
68504
 
68012
68505
  // src/websocket/listener/runtime.ts
@@ -68266,12 +68759,13 @@ function buildDeviceStatus(runtime, params) {
68266
68759
  return "auto";
68267
68760
  }
68268
68761
  })();
68762
+ const conversationPermissionModeState = getConversationPermissionModeState(listener, scopedAgentId, scopedConversationId);
68269
68763
  return {
68270
68764
  current_connection_id: listener.connectionId,
68271
68765
  connection_name: listener.connectionName,
68272
68766
  is_online: listener.socket?.readyState === WebSocket2.OPEN,
68273
68767
  is_processing: !!conversationRuntime?.isProcessing,
68274
- current_permission_mode: permissionMode.getMode(),
68768
+ current_permission_mode: conversationPermissionModeState.mode,
68275
68769
  current_working_directory: getConversationWorkingDirectory(listener, scopedAgentId, scopedConversationId),
68276
68770
  letta_code_version: process.env.npm_package_version || null,
68277
68771
  current_toolset: toolsetPreference === "auto" ? null : toolsetPreference,
@@ -68525,6 +69019,7 @@ var init_protocol_outbound = __esm(async () => {
68525
69019
  init_mode();
68526
69020
  init_constants2();
68527
69021
  init_cwd();
69022
+ init_permissionMode();
68528
69023
  init_runtime();
68529
69024
  await __promiseAll([
68530
69025
  init_settings_manager(),
@@ -69194,11 +69689,35 @@ function isSyncCommand(value) {
69194
69689
  const candidate = value;
69195
69690
  return candidate.type === "sync" && isRuntimeScope(candidate.runtime);
69196
69691
  }
69692
+ function isTerminalSpawnCommand(value) {
69693
+ if (!value || typeof value !== "object")
69694
+ return false;
69695
+ const c = value;
69696
+ return c.type === "terminal_spawn" && typeof c.terminal_id === "string" && typeof c.cols === "number" && typeof c.rows === "number";
69697
+ }
69698
+ function isTerminalInputCommand(value) {
69699
+ if (!value || typeof value !== "object")
69700
+ return false;
69701
+ const c = value;
69702
+ return c.type === "terminal_input" && typeof c.terminal_id === "string" && typeof c.data === "string";
69703
+ }
69704
+ function isTerminalResizeCommand(value) {
69705
+ if (!value || typeof value !== "object")
69706
+ return false;
69707
+ const c = value;
69708
+ return c.type === "terminal_resize" && typeof c.terminal_id === "string" && typeof c.cols === "number" && typeof c.rows === "number";
69709
+ }
69710
+ function isTerminalKillCommand(value) {
69711
+ if (!value || typeof value !== "object")
69712
+ return false;
69713
+ const c = value;
69714
+ return c.type === "terminal_kill" && typeof c.terminal_id === "string";
69715
+ }
69197
69716
  function parseServerMessage(data) {
69198
69717
  try {
69199
69718
  const raw = typeof data === "string" ? data : data.toString();
69200
69719
  const parsed = JSON.parse(raw);
69201
- if (isInputCommand(parsed) || isChangeDeviceStateCommand(parsed) || isAbortMessageCommand(parsed) || isSyncCommand(parsed)) {
69720
+ if (isInputCommand(parsed) || isChangeDeviceStateCommand(parsed) || isAbortMessageCommand(parsed) || isSyncCommand(parsed) || isTerminalSpawnCommand(parsed) || isTerminalInputCommand(parsed) || isTerminalResizeCommand(parsed) || isTerminalKillCommand(parsed)) {
69202
69721
  return parsed;
69203
69722
  }
69204
69723
  const invalidInput = getInvalidInputReason(parsed);
@@ -71812,7 +72331,7 @@ async function sendMessageStream(conversationId, messages, opts = { streamTokens
71812
72331
  const requestStartedAtMs = Date.now();
71813
72332
  const client = await getClient2();
71814
72333
  await waitForToolsetReady();
71815
- const { clientTools, contextId } = captureToolExecutionContext(opts.workingDirectory);
72334
+ const { clientTools, contextId } = captureToolExecutionContext(opts.workingDirectory, opts.permissionModeState);
71816
72335
  const { clientSkills, errors: clientSkillDiscoveryErrors } = await buildClientSkillsPayload({
71817
72336
  agentId: opts.agentId
71818
72337
  });
@@ -74210,7 +74729,7 @@ async function classifyApprovals(approvals, opts = {}) {
74210
74729
  continue;
74211
74730
  }
74212
74731
  const parsedArgs = safeJsonParseOr(approval.toolArgs || "{}", {});
74213
- const permission = await checkToolPermission(toolName, parsedArgs, opts.workingDirectory);
74732
+ const permission = await checkToolPermission(toolName, parsedArgs, opts.workingDirectory, opts.permissionModeState);
74214
74733
  const context3 = opts.getContext ? await opts.getContext(toolName, parsedArgs, opts.workingDirectory) : null;
74215
74734
  let decision = permission.decision;
74216
74735
  if (opts.alwaysRequiresUserInput?.(toolName) && decision === "allow") {
@@ -74316,7 +74835,8 @@ async function resolveStaleApprovals(runtime, socket, abortSignal) {
74316
74835
  alwaysRequiresUserInput: isInteractiveApprovalTool,
74317
74836
  requireArgsForAutoApprove: true,
74318
74837
  missingNameReason: "Tool call incomplete - missing name",
74319
- workingDirectory: recoveryWorkingDirectory
74838
+ workingDirectory: recoveryWorkingDirectory,
74839
+ permissionModeState: getConversationPermissionModeState(runtime.listener, runtime.agentId, runtime.conversationId)
74320
74840
  });
74321
74841
  const decisions = [
74322
74842
  ...autoAllowed.map((ac) => ({
@@ -74697,6 +75217,7 @@ var init_send = __esm(async () => {
74697
75217
  init_interactivePolicy();
74698
75218
  init_constants2();
74699
75219
  init_cwd();
75220
+ init_permissionMode();
74700
75221
  await __promiseAll([
74701
75222
  init_approval_execution(),
74702
75223
  init_approval_recovery(),
@@ -74986,7 +75507,7 @@ function gatherGitContextSnapshot(options = {}) {
74986
75507
  var init_gitContext = () => {};
74987
75508
 
74988
75509
  // src/cli/helpers/sessionContext.ts
74989
- import { platform as platform3 } from "node:os";
75510
+ import { platform as platform4 } from "node:os";
74990
75511
  function getLocalTime() {
74991
75512
  const now = new Date;
74992
75513
  return now.toLocaleString(undefined, {
@@ -75000,7 +75521,7 @@ function getLocalTime() {
75000
75521
  });
75001
75522
  }
75002
75523
  function getDeviceType() {
75003
- const p = platform3();
75524
+ const p = platform4();
75004
75525
  switch (p) {
75005
75526
  case "darwin":
75006
75527
  return "macOS";
@@ -75073,7 +75594,7 @@ ${gitInfo.status}
75073
75594
  context3 += `- **Git repository**: No
75074
75595
  `;
75075
75596
  }
75076
- if (platform3() === "win32") {
75597
+ if (platform4() === "win32") {
75077
75598
  context3 += `
75078
75599
  ## Windows Shell Notes
75079
75600
  - The Bash tool uses PowerShell or cmd.exe on Windows
@@ -75507,6 +76028,7 @@ async function handleApprovalStop(params) {
75507
76028
  agentId,
75508
76029
  conversationId,
75509
76030
  turnWorkingDirectory,
76031
+ turnPermissionModeState,
75510
76032
  dequeuedBatchId,
75511
76033
  runId,
75512
76034
  msgRunIds,
@@ -75559,7 +76081,8 @@ async function handleApprovalStop(params) {
75559
76081
  treatAskAsDeny: false,
75560
76082
  requireArgsForAutoApprove: true,
75561
76083
  missingNameReason: "Tool call incomplete - missing name",
75562
- workingDirectory: turnWorkingDirectory
76084
+ workingDirectory: turnWorkingDirectory,
76085
+ permissionModeState: turnPermissionModeState
75563
76086
  });
75564
76087
  const lastNeedsUserInputToolCallIds = needsUserInput.map((ac) => ac.approval.toolCallId);
75565
76088
  let lastExecutionResults = null;
@@ -75738,6 +76261,9 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
75738
76261
  const conversationId = requestedConversationId ?? "default";
75739
76262
  const normalizedAgentId = normalizeCwdAgentId(agentId);
75740
76263
  const turnWorkingDirectory = getConversationWorkingDirectory(runtime.listener, normalizedAgentId, conversationId);
76264
+ const turnPermissionModeState = {
76265
+ ...getConversationPermissionModeState(runtime.listener, normalizedAgentId, conversationId)
76266
+ };
75741
76267
  const msgRunIds = [];
75742
76268
  let postStopApprovalRecoveryRetries = 0;
75743
76269
  let llmApiErrorRetries = 0;
@@ -75820,6 +76346,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
75820
76346
  streamTokens: true,
75821
76347
  background: true,
75822
76348
  workingDirectory: turnWorkingDirectory,
76349
+ permissionModeState: turnPermissionModeState,
75823
76350
  ...pendingNormalizationInterruptedToolCallIds.length > 0 ? {
75824
76351
  approvalNormalization: {
75825
76352
  interruptedToolCallIds: pendingNormalizationInterruptedToolCallIds
@@ -76102,6 +76629,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
76102
76629
  agentId,
76103
76630
  conversationId,
76104
76631
  turnWorkingDirectory,
76632
+ turnPermissionModeState,
76105
76633
  dequeuedBatchId,
76106
76634
  runId,
76107
76635
  msgRunIds,
@@ -76189,6 +76717,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
76189
76717
  console.error("[Listen] Error handling message:", error);
76190
76718
  }
76191
76719
  } finally {
76720
+ setConversationPermissionModeState(runtime.listener, normalizedAgentId, conversationId, turnPermissionModeState);
76192
76721
  runtime.activeAbortController = null;
76193
76722
  runtime.cancelRequested = false;
76194
76723
  runtime.isRecoveringApprovals = false;
@@ -76205,6 +76734,7 @@ var init_turn = __esm(async () => {
76205
76734
  init_debug();
76206
76735
  init_constants2();
76207
76736
  init_cwd();
76737
+ init_permissionMode();
76208
76738
  init_runtime();
76209
76739
  await __promiseAll([
76210
76740
  init_approval_recovery(),
@@ -76227,11 +76757,22 @@ import path22 from "node:path";
76227
76757
  import WebSocket4 from "ws";
76228
76758
  function handleModeChange(msg, socket, runtime, scope) {
76229
76759
  try {
76230
- permissionMode.setMode(msg.mode);
76231
- if (msg.mode === "plan" && !permissionMode.getPlanFilePath()) {
76232
- const planFilePath = generatePlanFilePath();
76233
- permissionMode.setPlanFilePath(planFilePath);
76234
- }
76760
+ const agentId = scope?.agent_id ?? null;
76761
+ const conversationId = scope?.conversation_id ?? "default";
76762
+ const current = getConversationPermissionModeState(runtime, agentId, conversationId);
76763
+ const next = { ...current };
76764
+ if (msg.mode === "plan" && current.mode !== "plan") {
76765
+ next.modeBeforePlan = current.mode;
76766
+ }
76767
+ next.mode = msg.mode;
76768
+ if (msg.mode === "plan" && !current.planFilePath) {
76769
+ next.planFilePath = generatePlanFilePath();
76770
+ }
76771
+ if (msg.mode !== "plan") {
76772
+ next.planFilePath = null;
76773
+ next.modeBeforePlan = null;
76774
+ }
76775
+ setConversationPermissionModeState(runtime, agentId, conversationId, next);
76235
76776
  emitDeviceStatusUpdate(socket, runtime, scope);
76236
76777
  if (isDebugEnabled()) {
76237
76778
  console.log(`[Listen] Mode changed to: ${msg.mode}`);
@@ -76366,6 +76907,7 @@ function createRuntime() {
76366
76907
  reminderState: createSharedReminderState(),
76367
76908
  bootWorkingDirectory,
76368
76909
  workingDirectoryByConversation: loadPersistedCwdMap(),
76910
+ permissionModeByConversation: loadPersistedPermissionModeMap(),
76369
76911
  connectionId: null,
76370
76912
  connectionName: null,
76371
76913
  conversationRuntimes: new Map,
@@ -76695,6 +77237,22 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
76695
77237
  scheduleQueuePump(scopedRuntime, socket, opts, processQueuedTurn);
76696
77238
  return;
76697
77239
  }
77240
+ if (parsed.type === "terminal_spawn") {
77241
+ handleTerminalSpawn(parsed, socket, runtime.bootWorkingDirectory);
77242
+ return;
77243
+ }
77244
+ if (parsed.type === "terminal_input") {
77245
+ handleTerminalInput(parsed);
77246
+ return;
77247
+ }
77248
+ if (parsed.type === "terminal_resize") {
77249
+ handleTerminalResize(parsed);
77250
+ return;
77251
+ }
77252
+ if (parsed.type === "terminal_kill") {
77253
+ handleTerminalKill(parsed);
77254
+ return;
77255
+ }
76698
77256
  });
76699
77257
  socket.on("close", (code, reason) => {
76700
77258
  if (runtime !== getActiveRuntime()) {
@@ -76786,6 +77344,12 @@ function createLegacyTestRuntime() {
76786
77344
  listener.workingDirectoryByConversation = value;
76787
77345
  }
76788
77346
  },
77347
+ permissionModeByConversation: {
77348
+ get: () => listener.permissionModeByConversation,
77349
+ set: (value) => {
77350
+ listener.permissionModeByConversation = value;
77351
+ }
77352
+ },
76789
77353
  bootWorkingDirectory: {
76790
77354
  get: () => listener.bootWorkingDirectory,
76791
77355
  set: (value) => {
@@ -76908,12 +77472,12 @@ var __listenClientTestUtils;
76908
77472
  var init_client4 = __esm(async () => {
76909
77473
  init_planName();
76910
77474
  init_constants();
76911
- init_mode();
76912
77475
  init_queueRuntime();
76913
77476
  init_debug();
76914
77477
  init_terminalHandler();
76915
77478
  init_constants2();
76916
77479
  init_cwd();
77480
+ init_permissionMode();
76917
77481
  init_runtime();
76918
77482
  await __promiseAll([
76919
77483
  init_client2(),
@@ -77950,10 +78514,10 @@ __export(exports_overflow, {
77950
78514
  });
77951
78515
  import { randomUUID as randomUUID4 } from "node:crypto";
77952
78516
  import * as fs15 from "node:fs";
77953
- import * as os4 from "node:os";
78517
+ import * as os5 from "node:os";
77954
78518
  import * as path24 from "node:path";
77955
78519
  function getOverflowDirectory2(workingDirectory) {
77956
- const homeDir = os4.homedir();
78520
+ const homeDir = os5.homedir();
77957
78521
  const lettaDir = path24.join(homeDir, ".letta");
77958
78522
  const normalizedPath = path24.normalize(workingDirectory);
77959
78523
  const sanitizedPath = normalizedPath.replace(/^[/\\]/, "").replace(/[/\\:]/g, "_").replace(/\s+/g, "_");
@@ -78370,14 +78934,14 @@ function SetupUI({ onComplete }) {
78370
78934
  children: [
78371
78935
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(AnimatedLogo, {
78372
78936
  color: colors.welcome.accent,
78373
- animate: false
78937
+ animate: AUTH_LOGO_ANIMATE
78374
78938
  }, undefined, false, undefined, this),
78375
78939
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text2, {
78376
78940
  children: " "
78377
78941
  }, undefined, false, undefined, this),
78378
78942
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text2, {
78379
78943
  bold: true,
78380
- children: "Login to Letta Platform"
78944
+ children: AUTH_LOGIN_LABEL
78381
78945
  }, undefined, false, undefined, this),
78382
78946
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text2, {
78383
78947
  children: " "
@@ -78422,7 +78986,8 @@ function SetupUI({ onComplete }) {
78422
78986
  padding: 1,
78423
78987
  children: [
78424
78988
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(AnimatedLogo, {
78425
- color: colors.welcome.accent
78989
+ color: colors.welcome.accent,
78990
+ animate: AUTH_LOGO_ANIMATE
78426
78991
  }, undefined, false, undefined, this),
78427
78992
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text2, {
78428
78993
  children: " "
@@ -78445,7 +79010,7 @@ function SetupUI({ onComplete }) {
78445
79010
  color: selectedOption === 0 ? colors.selector.itemHighlighted : undefined,
78446
79011
  children: [
78447
79012
  selectedOption === 0 ? "> " : " ",
78448
- "Login to Letta Platform"
79013
+ AUTH_LOGIN_LABEL
78449
79014
  ]
78450
79015
  }, undefined, true, undefined, this)
78451
79016
  }, undefined, false, undefined, this),
@@ -78468,7 +79033,7 @@ function SetupUI({ onComplete }) {
78468
79033
  ]
78469
79034
  }, undefined, true, undefined, this);
78470
79035
  }
78471
- var import_react31, jsx_dev_runtime10;
79036
+ var import_react31, jsx_dev_runtime10, AUTH_LOGIN_LABEL = "Login to Letta Code", AUTH_LOGO_ANIMATE = false;
78472
79037
  var init_setup_ui = __esm(async () => {
78473
79038
  init_colors();
78474
79039
  init_oauth();
@@ -82205,10 +82770,10 @@ var init_reconcileExistingAgentState = __esm(() => {
82205
82770
 
82206
82771
  // src/agent/sessionHistory.ts
82207
82772
  import * as fs17 from "node:fs";
82208
- import * as os5 from "node:os";
82773
+ import * as os6 from "node:os";
82209
82774
  import * as path25 from "node:path";
82210
82775
  function getHistoryDir() {
82211
- const homeDir = os5.homedir();
82776
+ const homeDir = os6.homedir();
82212
82777
  return path25.join(homeDir, ".letta-code");
82213
82778
  }
82214
82779
  function getHistoryFilePath() {
@@ -97848,6 +98413,26 @@ var init_lowlight = __esm(() => {
97848
98413
  });
97849
98414
 
97850
98415
  // src/cli/components/SyntaxHighlightedCommand.tsx
98416
+ function clipStyledSpans(spans, maxColumns) {
98417
+ if (maxColumns <= 0) {
98418
+ return { spans: [], clipped: spans.length > 0 };
98419
+ }
98420
+ let remaining = maxColumns;
98421
+ const clipped = [];
98422
+ for (const span of spans) {
98423
+ if (remaining <= 0) {
98424
+ return { spans: clipped, clipped: true };
98425
+ }
98426
+ if (span.text.length <= remaining) {
98427
+ clipped.push(span);
98428
+ remaining -= span.text.length;
98429
+ continue;
98430
+ }
98431
+ clipped.push({ text: span.text.slice(0, remaining), color: span.color });
98432
+ return { spans: clipped, clipped: true };
98433
+ }
98434
+ return { spans: clipped, clipped: false };
98435
+ }
97851
98436
  function languageFromPath(filePath) {
97852
98437
  const basename4 = filePath.split("/").pop() ?? filePath;
97853
98438
  const lower = basename4.toLowerCase();
@@ -98075,34 +98660,70 @@ var init_SyntaxHighlightedCommand = __esm(async () => {
98075
98660
  };
98076
98661
  HEREDOC_RE = /<<-?\s*['"]?(\w+)['"]?\s*$/;
98077
98662
  REDIRECT_FILE_RE = />>?\s+(\S+)/;
98078
- SyntaxHighlightedCommand = import_react34.memo(({ command, showPrompt = true, prefix, suffix }) => {
98663
+ SyntaxHighlightedCommand = import_react34.memo(({
98664
+ command,
98665
+ showPrompt = true,
98666
+ prefix,
98667
+ suffix,
98668
+ maxLines,
98669
+ maxColumns,
98670
+ showTruncationHint = false
98671
+ }) => {
98079
98672
  const palette = colors.shellSyntax;
98080
- const lines = highlightCommand(command, palette);
98673
+ const highlightedLines = highlightCommand(command, palette);
98674
+ const hasLineCap = typeof maxLines === "number" && maxLines >= 0;
98675
+ const visibleLines = hasLineCap ? highlightedLines.slice(0, maxLines) : highlightedLines;
98676
+ const hiddenLineCount = Math.max(0, highlightedLines.length - visibleLines.length);
98677
+ const renderedLines = [];
98678
+ let anyColumnClipping = false;
98679
+ for (let i = 0;i < visibleLines.length; i++) {
98680
+ const spans = visibleLines[i] ?? [];
98681
+ if (typeof maxColumns === "number") {
98682
+ const prefixLen = i === 0 && prefix ? prefix.length : 0;
98683
+ const suffixLen = i === visibleLines.length - 1 && suffix ? suffix.length : 0;
98684
+ const textBudget = Math.max(0, maxColumns - prefixLen - suffixLen);
98685
+ const clipped = clipStyledSpans(spans, textBudget);
98686
+ renderedLines.push(clipped.spans);
98687
+ anyColumnClipping = anyColumnClipping || clipped.clipped;
98688
+ } else {
98689
+ renderedLines.push(spans);
98690
+ }
98691
+ }
98081
98692
  return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
98082
98693
  flexDirection: "column",
98083
- children: lines.map((spans, lineIdx) => {
98084
- const lineKey = spans.map((s) => s.text).join("");
98085
- return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
98086
- children: [
98087
- showPrompt ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text2, {
98088
- color: palette.prompt,
98089
- children: lineIdx === 0 ? FIRST_LINE_PREFIX : " "
98090
- }, undefined, false, undefined, this) : null,
98091
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text2, {
98092
- color: palette.text,
98093
- children: [
98094
- lineIdx === 0 && prefix ? prefix : null,
98095
- spans.map((span, si) => /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text2, {
98096
- color: span.color,
98097
- children: span.text
98098
- }, `${si}:${span.color}`, false, undefined, this)),
98099
- lineIdx === lines.length - 1 && suffix ? suffix : null
98100
- ]
98101
- }, undefined, true, undefined, this)
98102
- ]
98103
- }, `${lineIdx}:${lineKey}`, true, undefined, this);
98104
- })
98105
- }, undefined, false, undefined, this);
98694
+ children: [
98695
+ renderedLines.map((spans, lineIdx) => {
98696
+ const lineKey = spans.map((s) => s.text).join("");
98697
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Box_default, {
98698
+ children: [
98699
+ showPrompt ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text2, {
98700
+ color: palette.prompt,
98701
+ children: lineIdx === 0 ? FIRST_LINE_PREFIX : " "
98702
+ }, undefined, false, undefined, this) : null,
98703
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text2, {
98704
+ color: palette.text,
98705
+ children: [
98706
+ lineIdx === 0 && prefix ? prefix : null,
98707
+ spans.map((span, si) => /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text2, {
98708
+ color: span.color,
98709
+ children: span.text
98710
+ }, `${si}:${span.color}`, false, undefined, this)),
98711
+ lineIdx === renderedLines.length - 1 && suffix ? suffix : null
98712
+ ]
98713
+ }, undefined, true, undefined, this)
98714
+ ]
98715
+ }, `${lineIdx}:${lineKey}`, true, undefined, this);
98716
+ }),
98717
+ showTruncationHint && hiddenLineCount > 0 && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text2, {
98718
+ dimColor: true,
98719
+ children: `… +${hiddenLineCount} more lines`
98720
+ }, undefined, false, undefined, this),
98721
+ showTruncationHint && hiddenLineCount === 0 && anyColumnClipping && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(Text2, {
98722
+ dimColor: true,
98723
+ children: "… output clipped"
98724
+ }, undefined, false, undefined, this)
98725
+ ]
98726
+ }, undefined, true, undefined, this);
98106
98727
  });
98107
98728
  SyntaxHighlightedCommand.displayName = "SyntaxHighlightedCommand";
98108
98729
  });
@@ -98859,7 +99480,7 @@ var init_AdvancedDiffRenderer = __esm(async () => {
98859
99480
  });
98860
99481
 
98861
99482
  // src/cli/components/previews/BashPreview.tsx
98862
- var import_react36, jsx_dev_runtime15, SOLID_LINE3 = "─", BashPreview;
99483
+ var import_react36, jsx_dev_runtime15, SOLID_LINE3 = "─", BASH_PREVIEW_MAX_LINES = 3, BashPreview;
98863
99484
  var init_BashPreview = __esm(async () => {
98864
99485
  init_useTerminalWidth();
98865
99486
  init_colors();
@@ -98892,7 +99513,10 @@ var init_BashPreview = __esm(async () => {
98892
99513
  flexDirection: "column",
98893
99514
  children: [
98894
99515
  /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(SyntaxHighlightedCommand, {
98895
- command
99516
+ command,
99517
+ maxLines: BASH_PREVIEW_MAX_LINES,
99518
+ maxColumns: Math.max(10, columns - 2),
99519
+ showTruncationHint: true
98896
99520
  }, undefined, false, undefined, this),
98897
99521
  description && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(Text2, {
98898
99522
  dimColor: true,
@@ -99294,7 +99918,7 @@ var init_useTextInputCursor = __esm(() => {
99294
99918
  });
99295
99919
 
99296
99920
  // src/cli/components/InlineBashApproval.tsx
99297
- var import_react41, jsx_dev_runtime18, SOLID_LINE6 = "─", InlineBashApproval;
99921
+ var import_react41, jsx_dev_runtime18, SOLID_LINE6 = "─", BASH_PREVIEW_MAX_LINES2 = 3, InlineBashApproval;
99298
99922
  var init_InlineBashApproval = __esm(async () => {
99299
99923
  init_useProgressIndicator();
99300
99924
  init_useTerminalWidth();
@@ -99406,7 +100030,10 @@ var init_InlineBashApproval = __esm(async () => {
99406
100030
  flexDirection: "column",
99407
100031
  children: [
99408
100032
  /* @__PURE__ */ jsx_dev_runtime18.jsxDEV(SyntaxHighlightedCommand, {
99409
- command: bashInfo.command
100033
+ command: bashInfo.command,
100034
+ maxLines: BASH_PREVIEW_MAX_LINES2,
100035
+ maxColumns: Math.max(10, columns - 2),
100036
+ showTruncationHint: true
99410
100037
  }, undefined, false, undefined, this),
99411
100038
  bashInfo.description && /* @__PURE__ */ jsx_dev_runtime18.jsxDEV(Box_default, {
99412
100039
  marginTop: 1,
@@ -99418,7 +100045,7 @@ var init_InlineBashApproval = __esm(async () => {
99418
100045
  ]
99419
100046
  }, undefined, true, undefined, this)
99420
100047
  ]
99421
- }, undefined, true, undefined, this), [bashInfo.command, bashInfo.description, solidLine]);
100048
+ }, undefined, true, undefined, this), [bashInfo.command, bashInfo.description, solidLine, columns]);
99422
100049
  const hintText = isOnCustomOption ? customReason ? "Enter to submit · Esc to clear" : "Type reason · Esc to cancel" : "Enter to select · Esc to cancel";
99423
100050
  const optionsMarginTop = showPreview ? 1 : 0;
99424
100051
  return /* @__PURE__ */ jsx_dev_runtime18.jsxDEV(Box_default, {
@@ -102031,6 +102658,7 @@ var init_CollapsedOutputDisplay = __esm(async () => {
102031
102658
  // src/cli/components/StreamingOutputDisplay.tsx
102032
102659
  var import_react53, jsx_dev_runtime30, StreamingOutputDisplay;
102033
102660
  var init_StreamingOutputDisplay = __esm(async () => {
102661
+ init_useTerminalWidth();
102034
102662
  await __promiseAll([
102035
102663
  init_build2(),
102036
102664
  init_Text2()
@@ -102038,6 +102666,7 @@ var init_StreamingOutputDisplay = __esm(async () => {
102038
102666
  import_react53 = __toESM(require_react(), 1);
102039
102667
  jsx_dev_runtime30 = __toESM(require_jsx_dev_runtime(), 1);
102040
102668
  StreamingOutputDisplay = import_react53.memo(({ streaming, showInterruptHint }) => {
102669
+ const columns = useTerminalWidth();
102041
102670
  const [, forceUpdate] = import_react53.useState(0);
102042
102671
  import_react53.useEffect(() => {
102043
102672
  const interval = setInterval(() => forceUpdate((n) => n + 1), 1000);
@@ -102046,6 +102675,16 @@ var init_StreamingOutputDisplay = __esm(async () => {
102046
102675
  const elapsed = Math.floor((Date.now() - streaming.startTime) / 1000);
102047
102676
  const { tailLines, totalLineCount } = streaming;
102048
102677
  const hiddenCount = Math.max(0, totalLineCount - tailLines.length);
102678
+ const contentWidth = Math.max(10, columns - 5);
102679
+ const clipToWidth = (text) => {
102680
+ if (text.length <= contentWidth) {
102681
+ return text;
102682
+ }
102683
+ if (contentWidth <= 1) {
102684
+ return "…";
102685
+ }
102686
+ return `${text.slice(0, contentWidth - 1)}…`;
102687
+ };
102049
102688
  const firstLine = tailLines[0];
102050
102689
  const interruptHint = showInterruptHint ? " (esc to interrupt)" : "";
102051
102690
  if (!firstLine) {
@@ -102068,7 +102707,7 @@ var init_StreamingOutputDisplay = __esm(async () => {
102068
102707
  /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text2, {
102069
102708
  dimColor: !firstLine.isStderr,
102070
102709
  color: firstLine.isStderr ? "red" : undefined,
102071
- children: firstLine.text
102710
+ children: clipToWidth(firstLine.text)
102072
102711
  }, undefined, false, undefined, this)
102073
102712
  ]
102074
102713
  }, undefined, true, undefined, this),
@@ -102077,7 +102716,7 @@ var init_StreamingOutputDisplay = __esm(async () => {
102077
102716
  color: line.isStderr ? "red" : undefined,
102078
102717
  children: [
102079
102718
  " ",
102080
- line.text
102719
+ clipToWidth(line.text)
102081
102720
  ]
102082
102721
  }, i, true, undefined, this)),
102083
102722
  hiddenCount > 0 && /* @__PURE__ */ jsx_dev_runtime30.jsxDEV(Text2, {
@@ -103344,7 +103983,7 @@ var init_pasteRegistry = __esm(() => {
103344
103983
 
103345
103984
  // src/cli/helpers/clipboard.ts
103346
103985
  import { execFileSync as execFileSync3 } from "node:child_process";
103347
- import { existsSync as existsSync25, readFileSync as readFileSync10, statSync as statSync7, unlinkSync as unlinkSync8 } from "node:fs";
103986
+ import { existsSync as existsSync25, readFileSync as readFileSync11, statSync as statSync7, unlinkSync as unlinkSync8 } from "node:fs";
103348
103987
  import { tmpdir as tmpdir3 } from "node:os";
103349
103988
  import { basename as basename4, extname as extname5, isAbsolute as isAbsolute17, join as join33, resolve as resolve26 } from "node:path";
103350
103989
  function countLines2(text) {
@@ -103397,7 +104036,7 @@ function translatePasteForImages(paste) {
103397
104036
  filePath = resolve26(process.cwd(), filePath);
103398
104037
  const ext3 = extname5(filePath || "").toLowerCase();
103399
104038
  if (IMAGE_EXTS.has(ext3) && existsSync25(filePath) && statSync7(filePath).isFile()) {
103400
- const buf = readFileSync10(filePath);
104039
+ const buf = readFileSync11(filePath);
103401
104040
  const b64 = buf.toString("base64");
103402
104041
  const mt = ext3 === ".png" ? "image/png" : ext3 === ".jpg" || ext3 === ".jpeg" ? "image/jpeg" : ext3 === ".gif" ? "image/gif" : ext3 === ".webp" ? "image/webp" : ext3 === ".bmp" ? "image/bmp" : ext3 === ".svg" ? "image/svg+xml" : ext3 === ".tif" || ext3 === ".tiff" ? "image/tiff" : ext3 === ".heic" ? "image/heic" : ext3 === ".heif" ? "image/heif" : ext3 === ".avif" ? "image/avif" : "application/octet-stream";
103403
104042
  const id = allocateImage({
@@ -103457,7 +104096,7 @@ async function tryImportClipboardImageMac() {
103457
104096
  return null;
103458
104097
  const { tempPath, uti } = clipboardResult;
103459
104098
  try {
103460
- const buffer = readFileSync10(tempPath);
104099
+ const buffer = readFileSync11(tempPath);
103461
104100
  try {
103462
104101
  unlinkSync8(tempPath);
103463
104102
  } catch {}
@@ -104053,10 +104692,10 @@ import {
104053
104692
  copyFileSync,
104054
104693
  existsSync as existsSync26,
104055
104694
  mkdirSync as mkdirSync19,
104056
- readFileSync as readFileSync11,
104695
+ readFileSync as readFileSync12,
104057
104696
  writeFileSync as writeFileSync13
104058
104697
  } from "node:fs";
104059
- import { homedir as homedir28, platform as platform4 } from "node:os";
104698
+ import { homedir as homedir28, platform as platform5 } from "node:os";
104060
104699
  import { dirname as dirname13, join as join34 } from "node:path";
104061
104700
  function detectTerminalType() {
104062
104701
  if (process.env.CURSOR_TRACE_ID || process.env.CURSOR_CHANNEL) {
@@ -104087,17 +104726,17 @@ function getKeybindingsPath(terminal) {
104087
104726
  cursor: "Cursor",
104088
104727
  windsurf: "Windsurf"
104089
104728
  }[terminal];
104090
- const os6 = platform4();
104091
- if (os6 === "darwin") {
104729
+ const os7 = platform5();
104730
+ if (os7 === "darwin") {
104092
104731
  return join34(homedir28(), "Library", "Application Support", appName, "User", "keybindings.json");
104093
104732
  }
104094
- if (os6 === "win32") {
104733
+ if (os7 === "win32") {
104095
104734
  const appData = process.env.APPDATA;
104096
104735
  if (!appData)
104097
104736
  return null;
104098
104737
  return join34(appData, appName, "User", "keybindings.json");
104099
104738
  }
104100
- if (os6 === "linux") {
104739
+ if (os7 === "linux") {
104101
104740
  return join34(homedir28(), ".config", appName, "User", "keybindings.json");
104102
104741
  }
104103
104742
  return null;
@@ -104123,7 +104762,7 @@ function keybindingExists(keybindingsPath) {
104123
104762
  if (!existsSync26(keybindingsPath))
104124
104763
  return false;
104125
104764
  try {
104126
- const content = readFileSync11(keybindingsPath, { encoding: "utf-8" });
104765
+ const content = readFileSync12(keybindingsPath, { encoding: "utf-8" });
104127
104766
  const keybindings = parseKeybindings(content);
104128
104767
  if (!keybindings)
104129
104768
  return false;
@@ -104156,7 +104795,7 @@ function installKeybinding(keybindingsPath) {
104156
104795
  let backupPath = null;
104157
104796
  if (existsSync26(keybindingsPath)) {
104158
104797
  backupPath = createBackup(keybindingsPath);
104159
- const content = readFileSync11(keybindingsPath, { encoding: "utf-8" });
104798
+ const content = readFileSync12(keybindingsPath, { encoding: "utf-8" });
104160
104799
  const parsed = parseKeybindings(content);
104161
104800
  if (parsed === null) {
104162
104801
  return {
@@ -104187,7 +104826,7 @@ function removeKeybinding(keybindingsPath) {
104187
104826
  if (!existsSync26(keybindingsPath)) {
104188
104827
  return { success: true };
104189
104828
  }
104190
- const content = readFileSync11(keybindingsPath, { encoding: "utf-8" });
104829
+ const content = readFileSync12(keybindingsPath, { encoding: "utf-8" });
104191
104830
  const keybindings = parseKeybindings(content);
104192
104831
  if (!keybindings) {
104193
104832
  return {
@@ -104264,7 +104903,7 @@ function wezTermDeleteFixExists(configPath) {
104264
104903
  if (!existsSync26(configPath))
104265
104904
  return false;
104266
104905
  try {
104267
- const content = readFileSync11(configPath, { encoding: "utf-8" });
104906
+ const content = readFileSync12(configPath, { encoding: "utf-8" });
104268
104907
  return content.includes("Letta Code: Fix Delete key") || content.includes("key = 'Delete'") && content.includes("SendString") && content.includes("\\x1b[3~");
104269
104908
  } catch {
104270
104909
  return false;
@@ -104281,7 +104920,7 @@ function installWezTermDeleteFix() {
104281
104920
  if (existsSync26(configPath)) {
104282
104921
  backupPath = `${configPath}.letta-backup`;
104283
104922
  copyFileSync(configPath, backupPath);
104284
- content = readFileSync11(configPath, { encoding: "utf-8" });
104923
+ content = readFileSync12(configPath, { encoding: "utf-8" });
104285
104924
  }
104286
104925
  if (content.includes("return {") && !content.includes("local config")) {
104287
104926
  content = content.replace(/return\s*\{/, "local config = {");
@@ -107165,7 +107804,7 @@ var require_jsx_runtime = __commonJS((exports, module) => {
107165
107804
 
107166
107805
  // node_modules/supports-color/index.js
107167
107806
  import process19 from "node:process";
107168
- import os6 from "node:os";
107807
+ import os7 from "node:os";
107169
107808
  import tty2 from "node:tty";
107170
107809
  function hasFlag2(flag, argv = globalThis.Deno ? globalThis.Deno.args : process19.argv) {
107171
107810
  const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
@@ -107231,7 +107870,7 @@ function _supportsColor2(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
107231
107870
  return min;
107232
107871
  }
107233
107872
  if (process19.platform === "win32") {
107234
- const osRelease = os6.release().split(".");
107873
+ const osRelease = os7.release().split(".");
107235
107874
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
107236
107875
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
107237
107876
  }
@@ -110039,7 +110678,7 @@ import {
110039
110678
  existsSync as existsSync28,
110040
110679
  mkdirSync as mkdirSync20,
110041
110680
  mkdtempSync,
110042
- readFileSync as readFileSync12,
110681
+ readFileSync as readFileSync13,
110043
110682
  rmSync as rmSync3,
110044
110683
  writeFileSync as writeFileSync14
110045
110684
  } from "node:fs";
@@ -110297,7 +110936,7 @@ function writeWorkflow(repoDir, workflowPath, content) {
110297
110936
  const next = `${content.trimEnd()}
110298
110937
  `;
110299
110938
  if (existsSync28(absolutePath)) {
110300
- const previous = readFileSync12(absolutePath, "utf8");
110939
+ const previous = readFileSync13(absolutePath, "utf8");
110301
110940
  if (previous === next) {
110302
110941
  return false;
110303
110942
  }
@@ -112561,7 +113200,7 @@ var init_McpSelector = __esm(async () => {
112561
113200
  });
112562
113201
 
112563
113202
  // src/agent/memoryScanner.ts
112564
- import { readdirSync as readdirSync12, readFileSync as readFileSync13, statSync as statSync9 } from "node:fs";
113203
+ import { readdirSync as readdirSync12, readFileSync as readFileSync14, statSync as statSync9 } from "node:fs";
112565
113204
  import { join as join38, relative as relative14 } from "node:path";
112566
113205
  function scanMemoryFilesystem(memoryRoot) {
112567
113206
  const nodes = [];
@@ -112626,7 +113265,7 @@ function getFileNodes(nodes) {
112626
113265
  }
112627
113266
  function readFileContent(fullPath) {
112628
113267
  try {
112629
- return readFileSync13(fullPath, "utf-8");
113268
+ return readFileSync14(fullPath, "utf-8");
112630
113269
  } catch {
112631
113270
  return "(unable to read file)";
112632
113271
  }
@@ -120651,7 +121290,7 @@ function colorizeArgs(argsStr) {
120651
121290
  children: parts
120652
121291
  }, undefined, false, undefined, this);
120653
121292
  }
120654
- var import_react94, jsx_dev_runtime74, ToolCallMessage;
121293
+ var import_react94, jsx_dev_runtime74, LIVE_SHELL_ARGS_MAX_LINES = 2, ToolCallMessage;
120655
121294
  var init_ToolCallMessageRich = __esm(async () => {
120656
121295
  init_constants();
120657
121296
  init_subagentState();
@@ -121420,7 +122059,9 @@ var init_ToolCallMessageRich = __esm(async () => {
121420
122059
  command: shellCommand,
121421
122060
  showPrompt: false,
121422
122061
  prefix: "(",
121423
- suffix: ")"
122062
+ suffix: ")",
122063
+ maxLines: LIVE_SHELL_ARGS_MAX_LINES,
122064
+ maxColumns: Math.max(10, rightWidth - displayName.length)
121424
122065
  }, undefined, false, undefined, this)
121425
122066
  }, undefined, false, undefined, this) : args ? /* @__PURE__ */ jsx_dev_runtime74.jsxDEV(Box_default, {
121426
122067
  flexGrow: 1,
@@ -122143,7 +122784,7 @@ var init_contextChart = __esm(() => {
122143
122784
 
122144
122785
  // src/cli/helpers/initCommand.ts
122145
122786
  import { execSync as execSync2 } from "node:child_process";
122146
- import { existsSync as existsSync31, readdirSync as readdirSync13, readFileSync as readFileSync14 } from "node:fs";
122787
+ import { existsSync as existsSync31, readdirSync as readdirSync13, readFileSync as readFileSync15 } from "node:fs";
122147
122788
  import { join as join41 } from "node:path";
122148
122789
  function hasActiveInitSubagent() {
122149
122790
  const snapshot = getSnapshot2();
@@ -122191,7 +122832,7 @@ function gatherExistingMemory(agentId) {
122191
122832
  walk(join41(dir, entry.name), rel);
122192
122833
  } else if (entry.name.endsWith(".md")) {
122193
122834
  try {
122194
- const content = readFileSync14(join41(dir, entry.name), "utf-8");
122835
+ const content = readFileSync15(join41(dir, entry.name), "utf-8");
122195
122836
  paths.push(rel);
122196
122837
  sections.push(`── ${rel}
122197
122838
  ${content.slice(0, 2000)}`);
@@ -123588,7 +124229,7 @@ __export(exports_shellAliases, {
123588
124229
  expandAliases: () => expandAliases,
123589
124230
  clearAliasCache: () => clearAliasCache
123590
124231
  });
123591
- import { existsSync as existsSync32, readFileSync as readFileSync15 } from "node:fs";
124232
+ import { existsSync as existsSync32, readFileSync as readFileSync16 } from "node:fs";
123592
124233
  import { homedir as homedir33 } from "node:os";
123593
124234
  import { join as join43 } from "node:path";
123594
124235
  function parseAliasesFromFile(filePath) {
@@ -123597,7 +124238,7 @@ function parseAliasesFromFile(filePath) {
123597
124238
  return aliases;
123598
124239
  }
123599
124240
  try {
123600
- const content = readFileSync15(filePath, "utf-8");
124241
+ const content = readFileSync16(filePath, "utf-8");
123601
124242
  const lines = content.split(`
123602
124243
  `);
123603
124244
  let inFunction = false;
@@ -124492,7 +125133,7 @@ var exports_App = {};
124492
125133
  __export(exports_App, {
124493
125134
  default: () => App2
124494
125135
  });
124495
- import { existsSync as existsSync33, readFileSync as readFileSync16, renameSync as renameSync2, writeFileSync as writeFileSync16 } from "node:fs";
125136
+ import { existsSync as existsSync33, readFileSync as readFileSync17, renameSync as renameSync2, writeFileSync as writeFileSync16 } from "node:fs";
124496
125137
  import { homedir as homedir34, tmpdir as tmpdir6 } from "node:os";
124497
125138
  import { join as join44, relative as relative16 } from "node:path";
124498
125139
  function deriveReasoningEffort(modelSettings, llmConfig) {
@@ -124677,7 +125318,7 @@ function _readPlanFile(fallbackPlanFilePath) {
124677
125318
  return `Plan file not found at ${planFilePath}`;
124678
125319
  }
124679
125320
  try {
124680
- return readFileSync16(planFilePath, "utf-8");
125321
+ return readFileSync17(planFilePath, "utf-8");
124681
125322
  } catch {
124682
125323
  return `Failed to read plan file at ${planFilePath}`;
124683
125324
  }
@@ -124898,6 +125539,8 @@ function App2({
124898
125539
  const [pendingApprovals, setPendingApprovals] = import_react101.useState([]);
124899
125540
  const [approvalContexts, setApprovalContexts] = import_react101.useState([]);
124900
125541
  const [approvalResults, setApprovalResults] = import_react101.useState([]);
125542
+ const lastAutoApprovedEnterPlanToolCallIdRef = import_react101.useRef(null);
125543
+ const lastAutoHandledExitPlanToolCallIdRef = import_react101.useRef(null);
124901
125544
  const [isExecutingTool, setIsExecutingTool] = import_react101.useState(false);
124902
125545
  const [queuedApprovalResults, setQueuedApprovalResults] = import_react101.useState(null);
124903
125546
  const toolAbortControllerRef = import_react101.useRef(null);
@@ -125724,7 +126367,7 @@ function App2({
125724
126367
  description = typeof args.description === "string" ? args.description : typeof args.justification === "string" ? args.justification : "";
125725
126368
  }
125726
126369
  let lines2 = 3;
125727
- lines2 += countWrappedLines(command, wrapWidth);
126370
+ lines2 += Math.min(countWrappedLines(command, wrapWidth), SHELL_PREVIEW_MAX_LINES);
125728
126371
  if (description) {
125729
126372
  lines2 += countWrappedLines(description, wrapWidth);
125730
126373
  }
@@ -125966,10 +126609,10 @@ function App2({
125966
126609
  if (!planFilePath)
125967
126610
  return;
125968
126611
  try {
125969
- const { readFileSync: readFileSync17, existsSync: existsSync34 } = __require("node:fs");
126612
+ const { readFileSync: readFileSync18, existsSync: existsSync34 } = __require("node:fs");
125970
126613
  if (!existsSync34(planFilePath))
125971
126614
  return;
125972
- const planContent = readFileSync17(planFilePath, "utf-8");
126615
+ const planContent = readFileSync18(planFilePath, "utf-8");
125973
126616
  const previewItem = {
125974
126617
  kind: "approval_preview",
125975
126618
  id: `approval-preview-${toolCallId}`,
@@ -131900,6 +132543,9 @@ ${guidance}`);
131900
132543
  const currentIndex = approvalResults.length;
131901
132544
  const approval = pendingApprovals[currentIndex];
131902
132545
  if (approval?.toolName === "ExitPlanMode") {
132546
+ if (lastAutoHandledExitPlanToolCallIdRef.current === approval.toolCallId) {
132547
+ return;
132548
+ }
131903
132549
  const mode = permissionMode.getMode();
131904
132550
  const activePlanPath = permissionMode.getPlanFilePath();
131905
132551
  const fallbackPlanPath = lastPlanFilePathRef.current;
@@ -131907,6 +132553,7 @@ ${guidance}`);
131907
132553
  if (mode !== "plan") {
131908
132554
  if (mode === "bypassPermissions") {
131909
132555
  if (hasUsablePlan) {
132556
+ lastAutoHandledExitPlanToolCallIdRef.current = approval.toolCallId;
131910
132557
  handlePlanApprove();
131911
132558
  return;
131912
132559
  }
@@ -131927,6 +132574,7 @@ ${guidance}`);
131927
132574
  lines: ["⚠️ Plan mode session expired (use /plan to re-enter)"]
131928
132575
  });
131929
132576
  buffersRef.current.order.push(statusId);
132577
+ lastAutoHandledExitPlanToolCallIdRef.current = approval.toolCallId;
131930
132578
  const denialResults = [
131931
132579
  {
131932
132580
  type: "approval",
@@ -131946,6 +132594,7 @@ ${guidance}`);
131946
132594
  return;
131947
132595
  }
131948
132596
  if (!hasUsablePlan) {
132597
+ lastAutoHandledExitPlanToolCallIdRef.current = approval.toolCallId;
131949
132598
  const planFilePath = activePlanPath ?? fallbackPlanPath;
131950
132599
  const plansDir = join44(homedir34(), ".letta", "plans");
131951
132600
  handlePlanKeepPlanning(`You must write your plan to a plan file before exiting plan mode.
@@ -132089,6 +132738,10 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
132089
132738
  const approval = pendingApprovals[currentIndex];
132090
132739
  if (approval?.toolName === "EnterPlanMode") {
132091
132740
  if (permissionMode.getMode() === "bypassPermissions") {
132741
+ if (lastAutoApprovedEnterPlanToolCallIdRef.current === approval.toolCallId) {
132742
+ return;
132743
+ }
132744
+ lastAutoApprovedEnterPlanToolCallIdRef.current = approval.toolCallId;
132092
132745
  handleEnterPlanModeApprove(true);
132093
132746
  }
132094
132747
  }
@@ -133145,7 +133798,7 @@ Open /mcp to attach or detach tools for this server.`, true);
133145
133798
  ]
133146
133799
  }, resumeKey, true, undefined, this);
133147
133800
  }
133148
- var import_react101, jsx_dev_runtime78, CLEAR_SCREEN_AND_HOME = "\x1B[2J\x1B[H", MIN_RESIZE_DELTA = 2, RESIZE_SETTLE_MS = 250, MIN_CLEAR_INTERVAL_MS = 750, STABLE_WIDTH_SETTLE_MS = 180, TOOL_CALL_COMMIT_DEFER_MS = 50, ANIMATION_RESUME_HYSTERESIS_ROWS = 2, EAGER_CANCEL = true, LLM_API_ERROR_MAX_RETRIES3 = 3, EMPTY_RESPONSE_MAX_RETRIES3 = 2, CONVERSATION_BUSY_MAX_RETRIES2 = 3, INTERRUPT_MESSAGE = "Interrupted – tell the agent what to do differently. Something went wrong? Use /feedback to report issues.", ERROR_FEEDBACK_HINT = "Something went wrong? Use /feedback to report issues.", OPUS_BEDROCK_FALLBACK_HINT = "Downstream provider issues? Use /model to switch to Bedrock Opus 4.5", PROVIDER_FALLBACK_HINT = "Downstream provider issues? Use /model to switch to another provider", INTERACTIVE_SLASH_COMMANDS, NON_STATE_COMMANDS, APPROVAL_OPTIONS_HEIGHT = 8, APPROVAL_PREVIEW_BUFFER = 4, MIN_WRAP_WIDTH = 10, TEXT_WRAP_GUTTER = 6, DIFF_WRAP_GUTTER = 12, AUTO_REFLECTION_DESCRIPTION = "Reflect on recent conversations";
133801
+ var import_react101, jsx_dev_runtime78, CLEAR_SCREEN_AND_HOME = "\x1B[2J\x1B[H", MIN_RESIZE_DELTA = 2, RESIZE_SETTLE_MS = 250, MIN_CLEAR_INTERVAL_MS = 750, STABLE_WIDTH_SETTLE_MS = 180, TOOL_CALL_COMMIT_DEFER_MS = 50, ANIMATION_RESUME_HYSTERESIS_ROWS = 2, EAGER_CANCEL = true, LLM_API_ERROR_MAX_RETRIES3 = 3, EMPTY_RESPONSE_MAX_RETRIES3 = 2, CONVERSATION_BUSY_MAX_RETRIES2 = 3, INTERRUPT_MESSAGE = "Interrupted – tell the agent what to do differently. Something went wrong? Use /feedback to report issues.", ERROR_FEEDBACK_HINT = "Something went wrong? Use /feedback to report issues.", OPUS_BEDROCK_FALLBACK_HINT = "Downstream provider issues? Use /model to switch to Bedrock Opus 4.5", PROVIDER_FALLBACK_HINT = "Downstream provider issues? Use /model to switch to another provider", INTERACTIVE_SLASH_COMMANDS, NON_STATE_COMMANDS, APPROVAL_OPTIONS_HEIGHT = 8, APPROVAL_PREVIEW_BUFFER = 4, MIN_WRAP_WIDTH = 10, TEXT_WRAP_GUTTER = 6, DIFF_WRAP_GUTTER = 12, SHELL_PREVIEW_MAX_LINES = 3, AUTO_REFLECTION_DESCRIPTION = "Reflect on recent conversations";
133149
133802
  var init_App2 = __esm(async () => {
133150
133803
  init_error();
133151
133804
  init_check_approval();
@@ -133317,10 +133970,10 @@ import {
133317
133970
  copyFileSync as copyFileSync2,
133318
133971
  existsSync as existsSync34,
133319
133972
  mkdirSync as mkdirSync22,
133320
- readFileSync as readFileSync17,
133973
+ readFileSync as readFileSync18,
133321
133974
  writeFileSync as writeFileSync17
133322
133975
  } from "node:fs";
133323
- import { homedir as homedir35, platform as platform5 } from "node:os";
133976
+ import { homedir as homedir35, platform as platform6 } from "node:os";
133324
133977
  import { dirname as dirname16, join as join45 } from "node:path";
133325
133978
  function detectTerminalType2() {
133326
133979
  if (process.env.CURSOR_TRACE_ID || process.env.CURSOR_CHANNEL) {
@@ -133351,17 +134004,17 @@ function getKeybindingsPath2(terminal) {
133351
134004
  cursor: "Cursor",
133352
134005
  windsurf: "Windsurf"
133353
134006
  }[terminal];
133354
- const os7 = platform5();
133355
- if (os7 === "darwin") {
134007
+ const os8 = platform6();
134008
+ if (os8 === "darwin") {
133356
134009
  return join45(homedir35(), "Library", "Application Support", appName, "User", "keybindings.json");
133357
134010
  }
133358
- if (os7 === "win32") {
134011
+ if (os8 === "win32") {
133359
134012
  const appData = process.env.APPDATA;
133360
134013
  if (!appData)
133361
134014
  return null;
133362
134015
  return join45(appData, appName, "User", "keybindings.json");
133363
134016
  }
133364
- if (os7 === "linux") {
134017
+ if (os8 === "linux") {
133365
134018
  return join45(homedir35(), ".config", appName, "User", "keybindings.json");
133366
134019
  }
133367
134020
  return null;
@@ -133387,7 +134040,7 @@ function keybindingExists2(keybindingsPath) {
133387
134040
  if (!existsSync34(keybindingsPath))
133388
134041
  return false;
133389
134042
  try {
133390
- const content = readFileSync17(keybindingsPath, { encoding: "utf-8" });
134043
+ const content = readFileSync18(keybindingsPath, { encoding: "utf-8" });
133391
134044
  const keybindings = parseKeybindings2(content);
133392
134045
  if (!keybindings)
133393
134046
  return false;
@@ -133420,7 +134073,7 @@ function installKeybinding2(keybindingsPath) {
133420
134073
  let backupPath = null;
133421
134074
  if (existsSync34(keybindingsPath)) {
133422
134075
  backupPath = createBackup2(keybindingsPath);
133423
- const content = readFileSync17(keybindingsPath, { encoding: "utf-8" });
134076
+ const content = readFileSync18(keybindingsPath, { encoding: "utf-8" });
133424
134077
  const parsed = parseKeybindings2(content);
133425
134078
  if (parsed === null) {
133426
134079
  return {
@@ -133451,7 +134104,7 @@ function removeKeybinding2(keybindingsPath) {
133451
134104
  if (!existsSync34(keybindingsPath)) {
133452
134105
  return { success: true };
133453
134106
  }
133454
- const content = readFileSync17(keybindingsPath, { encoding: "utf-8" });
134107
+ const content = readFileSync18(keybindingsPath, { encoding: "utf-8" });
133455
134108
  const keybindings = parseKeybindings2(content);
133456
134109
  if (!keybindings) {
133457
134110
  return {
@@ -133528,7 +134181,7 @@ function wezTermDeleteFixExists2(configPath) {
133528
134181
  if (!existsSync34(configPath))
133529
134182
  return false;
133530
134183
  try {
133531
- const content = readFileSync17(configPath, { encoding: "utf-8" });
134184
+ const content = readFileSync18(configPath, { encoding: "utf-8" });
133532
134185
  return content.includes("Letta Code: Fix Delete key") || content.includes("key = 'Delete'") && content.includes("SendString") && content.includes("\\x1b[3~");
133533
134186
  } catch {
133534
134187
  return false;
@@ -133545,7 +134198,7 @@ function installWezTermDeleteFix2() {
133545
134198
  if (existsSync34(configPath)) {
133546
134199
  backupPath = `${configPath}.letta-backup`;
133547
134200
  copyFileSync2(configPath, backupPath);
133548
- content = readFileSync17(configPath, { encoding: "utf-8" });
134201
+ content = readFileSync18(configPath, { encoding: "utf-8" });
133549
134202
  }
133550
134203
  if (content.includes("return {") && !content.includes("local config")) {
133551
134204
  content = content.replace(/return\s*\{/, "local config = {");
@@ -138981,9 +139634,14 @@ class PermissionModeManager2 {
138981
139634
  getPlanFilePath() {
138982
139635
  return getGlobalPlanFilePath2();
138983
139636
  }
138984
- checkModeOverride(toolName, toolArgs, workingDirectory = process.cwd()) {
138985
- switch (this.currentMode) {
139637
+ checkModeOverride(toolName, toolArgs, workingDirectory = process.cwd(), modeOverride, planFilePathOverride) {
139638
+ const effectiveMode = modeOverride ?? this.currentMode;
139639
+ const _effectivePlanFilePath = planFilePathOverride !== undefined ? planFilePathOverride : this.getPlanFilePath();
139640
+ switch (effectiveMode) {
138986
139641
  case "bypassPermissions":
139642
+ if (toolName === "ExitPlanMode" || toolName === "exit_plan_mode") {
139643
+ return null;
139644
+ }
138987
139645
  return "allow";
138988
139646
  case "acceptEdits":
138989
139647
  if ([
@@ -140356,6 +141014,7 @@ var telemetry2 = new TelemetryManager2;
140356
141014
  init_subagents();
140357
141015
  init_fileIndex();
140358
141016
  init_constants();
141017
+ init_mode();
140359
141018
  init_debug();
140360
141019
  await __promiseAll([
140361
141020
  init_approval_execution(),
@@ -140648,7 +141307,7 @@ import {
140648
141307
  existsSync as existsSync19,
140649
141308
  mkdirSync as mkdirSync14,
140650
141309
  readdirSync as readdirSync9,
140651
- readFileSync as readFileSync8,
141310
+ readFileSync as readFileSync9,
140652
141311
  unlinkSync as unlinkSync6
140653
141312
  } from "node:fs";
140654
141313
  import { homedir as homedir23 } from "node:os";
@@ -140705,7 +141364,7 @@ class DebugLogFile2 {
140705
141364
  try {
140706
141365
  if (!existsSync19(this.logPath))
140707
141366
  return;
140708
- const content = readFileSync8(this.logPath, "utf8");
141367
+ const content = readFileSync9(this.logPath, "utf8");
140709
141368
  const lines = content.trimEnd().split(`
140710
141369
  `);
140711
141370
  return lines.slice(-maxLines).join(`
@@ -142186,4 +142845,4 @@ Error during initialization: ${message}`);
142186
142845
  }
142187
142846
  main();
142188
142847
 
142189
- //# debugId=3F2642C6F96B1B5B64756E2164756E21
142848
+ //# debugId=795EEAC4C0962C6964756E2164756E21