@letta-ai/letta-code 0.19.0 → 0.19.1

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 +1478 -1059
  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.1",
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];
@@ -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;
@@ -42214,12 +41475,12 @@ var init_executor = __esm(async () => {
42214
41475
  });
42215
41476
 
42216
41477
  // src/hooks/loader.ts
42217
- import { homedir as homedir11 } from "node:os";
42218
- import { resolve as resolve5 } from "node:path";
41478
+ import { homedir as homedir9 } from "node:os";
41479
+ import { resolve as resolve3 } from "node:path";
42219
41480
  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");
41481
+ const home = process.env.HOME || homedir9();
41482
+ const globalSettingsPath = resolve3(home, ".letta", "settings.json");
41483
+ const projectSettingsPath = resolve3(workingDirectory, ".letta", "settings.json");
42223
41484
  return globalSettingsPath === projectSettingsPath;
42224
41485
  }
42225
41486
  function loadGlobalHooks() {
@@ -42604,6 +41865,946 @@ var init_hooks = __esm(async () => {
42604
41865
  init_types();
42605
41866
  });
42606
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 = "";
42366
+ }
42367
+ };
42368
+ for (let i = 0;i < input.length; i += 1) {
42369
+ const ch = input[i];
42370
+ if (ch === undefined) {
42371
+ continue;
42372
+ }
42373
+ if (escaping) {
42374
+ current += ch;
42375
+ escaping = false;
42376
+ continue;
42377
+ }
42378
+ if (ch === "\\" && quote !== "single") {
42379
+ escaping = true;
42380
+ continue;
42381
+ }
42382
+ if (quote === "single") {
42383
+ if (ch === "'") {
42384
+ quote = null;
42385
+ } else {
42386
+ current += ch;
42387
+ }
42388
+ continue;
42389
+ }
42390
+ if (quote === "double") {
42391
+ if (ch === '"') {
42392
+ quote = null;
42393
+ } else {
42394
+ current += ch;
42395
+ }
42396
+ continue;
42397
+ }
42398
+ if (ch === "'") {
42399
+ quote = "single";
42400
+ continue;
42401
+ }
42402
+ if (ch === '"') {
42403
+ quote = "double";
42404
+ continue;
42405
+ }
42406
+ if (/\s/.test(ch)) {
42407
+ flush();
42408
+ continue;
42409
+ }
42410
+ current += ch;
42411
+ }
42412
+ if (escaping) {
42413
+ current += "\\";
42414
+ }
42415
+ flush();
42416
+ return tokens;
42417
+ }
42418
+ function isDashCFlag(token) {
42419
+ return token === "-c" || /^-[a-zA-Z]*c[a-zA-Z]*$/.test(token);
42420
+ }
42421
+ function extractInnerShellCommand(tokens) {
42422
+ if (tokens.length === 0) {
42423
+ return null;
42424
+ }
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;
42440
+ continue;
42441
+ }
42442
+ break;
42443
+ }
42444
+ }
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;
42456
+ }
42457
+ if (!isDashCFlag(token)) {
42458
+ continue;
42459
+ }
42460
+ const innerCommand = tokens[i + 1];
42461
+ if (!innerCommand) {
42462
+ return null;
42463
+ }
42464
+ return trimMatchingQuotes(innerCommand);
42465
+ }
42466
+ return null;
42467
+ }
42468
+ function unwrapShellLauncherCommand(command) {
42469
+ let current = command.trim();
42470
+ for (let depth = 0;depth < 5; depth += 1) {
42471
+ if (!current) {
42472
+ break;
42473
+ }
42474
+ const tokens = tokenizeShell(current);
42475
+ const inner = extractInnerShellCommand(tokens);
42476
+ if (!inner || inner === current) {
42477
+ break;
42478
+ }
42479
+ current = inner.trim();
42480
+ }
42481
+ return current;
42482
+ }
42483
+ function normalizeBashRulePayload(payload) {
42484
+ const trimmed = payload.trim();
42485
+ if (!trimmed) {
42486
+ return "";
42487
+ }
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;
42495
+ }
42496
+ var SHELL_EXECUTORS;
42497
+ var init_shell_command_normalization = __esm(() => {
42498
+ SHELL_EXECUTORS = new Set(["bash", "sh", "zsh", "dash", "ksh"]);
42499
+ });
42500
+
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";
42512
+ }
42513
+ return global2[MODE_KEY];
42514
+ }
42515
+ function setGlobalMode(value) {
42516
+ const global2 = globalThis;
42517
+ global2[MODE_KEY] = value;
42518
+ }
42519
+ function getGlobalPlanFilePath() {
42520
+ const global2 = globalThis;
42521
+ return global2[PLAN_FILE_KEY] || null;
42522
+ }
42523
+ function setGlobalPlanFilePath(value) {
42524
+ const global2 = globalThis;
42525
+ global2[PLAN_FILE_KEY] = value;
42526
+ }
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));
42541
+ }
42542
+ if (isAbsolute2(trimmedPath)) {
42543
+ return resolve5(trimmedPath);
42544
+ }
42545
+ return resolve5(workingDirectory, trimmedPath);
42546
+ }
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);
42552
+ }
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);
42561
+ }
42562
+ for (const match of input.matchAll(moveDirectivePattern)) {
42563
+ const matchPath = match[1]?.trim();
42564
+ if (matchPath)
42565
+ paths.push(matchPath);
42566
+ }
42567
+ return paths;
42568
+ }
42569
+ function stripMatchingQuotes(value) {
42570
+ const trimmed = value.trim();
42571
+ if (trimmed.length < 2) {
42572
+ return trimmed;
42573
+ }
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;
42580
+ }
42581
+ function extractPlanFileWritePathFromShellCommand(command) {
42582
+ if (!command) {
42583
+ return null;
42584
+ }
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;
42613
+ }
42614
+ }
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);
42624
+ }
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
+ return "allow";
42664
+ case "acceptEdits":
42665
+ if ([
42666
+ "Write",
42667
+ "Edit",
42668
+ "MultiEdit",
42669
+ "NotebookEdit",
42670
+ "apply_patch",
42671
+ "replace",
42672
+ "write_file"
42673
+ ].includes(toolName)) {
42674
+ return "allow";
42675
+ }
42676
+ return null;
42677
+ case "plan": {
42678
+ const allowedInPlan = [
42679
+ "Read",
42680
+ "Glob",
42681
+ "Grep",
42682
+ "NotebookRead",
42683
+ "TodoWrite",
42684
+ "ExitPlanMode",
42685
+ "exit_plan_mode",
42686
+ "AskUserQuestion",
42687
+ "ask_user_question",
42688
+ "read_file",
42689
+ "list_dir",
42690
+ "grep_files",
42691
+ "update_plan",
42692
+ "task_output",
42693
+ "ReadFile",
42694
+ "ListDir",
42695
+ "GrepFiles",
42696
+ "UpdatePlan",
42697
+ "TaskOutput",
42698
+ "read_file_gemini",
42699
+ "glob_gemini",
42700
+ "list_directory",
42701
+ "search_file_content",
42702
+ "write_todos",
42703
+ "read_many_files",
42704
+ "ReadFileGemini",
42705
+ "GlobGemini",
42706
+ "ListDirectory",
42707
+ "SearchFileContent",
42708
+ "WriteTodos",
42709
+ "ReadManyFiles"
42710
+ ];
42711
+ const writeTools = [
42712
+ "Write",
42713
+ "Edit",
42714
+ "MultiEdit",
42715
+ "apply_patch",
42716
+ "ApplyPatch",
42717
+ "write_file_gemini",
42718
+ "WriteFileGemini",
42719
+ "replace",
42720
+ "Replace"
42721
+ ];
42722
+ if (allowedInPlan.includes(toolName)) {
42723
+ return "allow";
42724
+ }
42725
+ if (writeTools.includes(toolName)) {
42726
+ const plansDir = join10(homedir11(), ".letta", "plans");
42727
+ const targetPath = toolArgs?.file_path || toolArgs?.path;
42728
+ let candidatePaths = [];
42729
+ if ((toolName === "ApplyPatch" || toolName === "apply_patch") && toolArgs?.input) {
42730
+ const input = toolArgs.input;
42731
+ candidatePaths = extractApplyPatchPaths(input);
42732
+ } else if (typeof targetPath === "string") {
42733
+ candidatePaths = [targetPath];
42734
+ }
42735
+ if (candidatePaths.length > 0 && candidatePaths.every((path4) => {
42736
+ const resolvedPath = resolvePlanTargetPath(path4, workingDirectory);
42737
+ return resolvedPath ? isPathInPlansDir(resolvedPath, plansDir) : false;
42738
+ })) {
42739
+ return "allow";
42740
+ }
42741
+ }
42742
+ const readOnlySubagentTypes = new Set([
42743
+ "explore",
42744
+ "Explore",
42745
+ "plan",
42746
+ "Plan",
42747
+ "recall",
42748
+ "Recall"
42749
+ ]);
42750
+ if (toolName === "Task" || toolName === "task") {
42751
+ const subagentType = toolArgs?.subagent_type;
42752
+ if (subagentType && readOnlySubagentTypes.has(subagentType)) {
42753
+ return "allow";
42754
+ }
42755
+ }
42756
+ if (toolName === "Skill" || toolName === "skill") {
42757
+ return "allow";
42758
+ }
42759
+ const shellTools = [
42760
+ "Bash",
42761
+ "shell",
42762
+ "Shell",
42763
+ "shell_command",
42764
+ "ShellCommand",
42765
+ "run_shell_command",
42766
+ "RunShellCommand",
42767
+ "run_shell_command_gemini",
42768
+ "RunShellCommandGemini"
42769
+ ];
42770
+ if (shellTools.includes(toolName)) {
42771
+ const command = toolArgs?.command;
42772
+ if (command && isReadOnlyShellCommand(command, { allowExternalPaths: true })) {
42773
+ return "allow";
42774
+ }
42775
+ const planWritePath = extractPlanFileWritePathFromShellCommand(command);
42776
+ if (planWritePath) {
42777
+ const plansDir = join10(homedir11(), ".letta", "plans");
42778
+ const resolvedPath = resolvePlanTargetPath(planWritePath, workingDirectory);
42779
+ if (resolvedPath && isPathInPlansDir(resolvedPath, plansDir)) {
42780
+ return "allow";
42781
+ }
42782
+ }
42783
+ }
42784
+ return "deny";
42785
+ }
42786
+ case "default":
42787
+ return null;
42788
+ default:
42789
+ return null;
42790
+ }
42791
+ }
42792
+ reset() {
42793
+ this.currentMode = "default";
42794
+ setGlobalPlanFilePath(null);
42795
+ setGlobalModeBeforePlan(null);
42796
+ }
42797
+ }
42798
+ var MODE_KEY, PLAN_FILE_KEY, MODE_BEFORE_PLAN_KEY, permissionMode;
42799
+ var init_mode = __esm(() => {
42800
+ init_readOnlyShell();
42801
+ init_shell_command_normalization();
42802
+ MODE_KEY = Symbol.for("@letta/permissionMode");
42803
+ PLAN_FILE_KEY = Symbol.for("@letta/planFilePath");
42804
+ MODE_BEFORE_PLAN_KEY = Symbol.for("@letta/permissionModeBeforePlan");
42805
+ permissionMode = new PermissionModeManager;
42806
+ });
42807
+
42607
42808
  // src/telemetry/index.ts
42608
42809
  class TelemetryManager {
42609
42810
  events = [];
@@ -46531,13 +46732,23 @@ var init_Edit2 = () => {};
46531
46732
 
46532
46733
  // src/tools/impl/EnterPlanMode.ts
46533
46734
  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);
46735
+ async function enter_plan_mode(args) {
46736
+ const scopedState = args._executionContextId ? getExecutionContextPermissionModeState(args._executionContextId) : undefined;
46737
+ if (scopedState) {
46738
+ if (scopedState.mode !== "plan" || !scopedState.planFilePath) {
46739
+ const planFilePath2 = generatePlanFilePath();
46740
+ scopedState.modeBeforePlan = scopedState.modeBeforePlan ?? scopedState.mode;
46741
+ scopedState.mode = "plan";
46742
+ scopedState.planFilePath = planFilePath2;
46743
+ }
46744
+ } else {
46745
+ if (permissionMode.getMode() !== "plan" || !permissionMode.getPlanFilePath()) {
46746
+ const planFilePath2 = generatePlanFilePath();
46747
+ permissionMode.setMode("plan");
46748
+ permissionMode.setPlanFilePath(planFilePath2);
46749
+ }
46539
46750
  }
46540
- const planFilePath = permissionMode.getPlanFilePath();
46751
+ const planFilePath = scopedState?.planFilePath ?? permissionMode.getPlanFilePath();
46541
46752
  const cwd2 = process.env.USER_CWD || process.cwd();
46542
46753
  const applyPatchRelativePath = planFilePath ? relative4(cwd2, planFilePath).replace(/\\/g, "/") : null;
46543
46754
  return {
@@ -46557,14 +46768,22 @@ Plan file path: ${planFilePath}
46557
46768
  ${applyPatchRelativePath ? `If using apply_patch, use this exact relative patch path: ${applyPatchRelativePath}` : ""}`
46558
46769
  };
46559
46770
  }
46560
- var init_EnterPlanMode2 = __esm(() => {
46771
+ var init_EnterPlanMode2 = __esm(async () => {
46561
46772
  init_planName();
46562
46773
  init_mode();
46774
+ await init_manager3();
46563
46775
  });
46564
46776
 
46565
46777
  // src/tools/impl/ExitPlanMode.ts
46566
- async function exit_plan_mode() {
46567
- if (permissionMode.getMode() === "plan") {
46778
+ async function exit_plan_mode(args = {}) {
46779
+ const scopedState = args._executionContextId ? getExecutionContextPermissionModeState(args._executionContextId) : undefined;
46780
+ if (scopedState) {
46781
+ if (scopedState.mode === "plan") {
46782
+ scopedState.mode = scopedState.modeBeforePlan ?? "default";
46783
+ scopedState.modeBeforePlan = null;
46784
+ scopedState.planFilePath = null;
46785
+ }
46786
+ } else if (permissionMode.getMode() === "plan") {
46568
46787
  const restoredMode = permissionMode.getModeBeforePlan() ?? "default";
46569
46788
  permissionMode.setMode(restoredMode);
46570
46789
  }
@@ -46575,8 +46794,9 @@ async function exit_plan_mode() {
46575
46794
  ` + "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
46795
  };
46577
46796
  }
46578
- var init_ExitPlanMode2 = __esm(() => {
46797
+ var init_ExitPlanMode2 = __esm(async () => {
46579
46798
  init_mode();
46799
+ await init_manager3();
46580
46800
  });
46581
46801
 
46582
46802
  // src/tools/impl/Glob.ts
@@ -64052,8 +64272,6 @@ var init_toolDefinitions = __esm(async () => {
64052
64272
  init_AskUserQuestion2();
64053
64273
  init_BashOutput2();
64054
64274
  init_Edit2();
64055
- init_EnterPlanMode2();
64056
- init_ExitPlanMode2();
64057
64275
  init_Glob2();
64058
64276
  init_GlobGemini2();
64059
64277
  init_Grep2();
@@ -64111,6 +64329,8 @@ var init_toolDefinitions = __esm(async () => {
64111
64329
  init_WriteTodosGemini2();
64112
64330
  await __promiseAll([
64113
64331
  init_Bash2(),
64332
+ init_EnterPlanMode2(),
64333
+ init_ExitPlanMode2(),
64114
64334
  init_Memory2(),
64115
64335
  init_Read2(),
64116
64336
  init_ReadFileGemini2(),
@@ -65780,9 +66000,9 @@ function shouldAttachTrace(result) {
65780
66000
  }
65781
66001
  return result.decision === "ask" || result.decision === "deny";
65782
66002
  }
65783
- function checkPermission(toolName, toolArgs, permissions, workingDirectory = process.cwd()) {
66003
+ function checkPermission(toolName, toolArgs, permissions, workingDirectory = process.cwd(), modeState) {
65784
66004
  const engine = isPermissionsV2Enabled() ? "v2" : "v1";
65785
- const primary = checkPermissionForEngine(engine, toolName, toolArgs, permissions, workingDirectory);
66005
+ const primary = checkPermissionForEngine(engine, toolName, toolArgs, permissions, workingDirectory, modeState);
65786
66006
  let result = primary.result;
65787
66007
  const includeTrace = shouldAttachTrace(primary.result);
65788
66008
  if (includeTrace) {
@@ -65801,7 +66021,7 @@ function checkPermission(toolName, toolArgs, permissions, workingDirectory = pro
65801
66021
  }
65802
66022
  if (envFlagEnabled("LETTA_PERMISSIONS_DUAL_EVAL")) {
65803
66023
  const shadowEngine = engine === "v2" ? "v1" : "v2";
65804
- const shadow = checkPermissionForEngine(shadowEngine, toolName, toolArgs, permissions, workingDirectory);
66024
+ const shadow = checkPermissionForEngine(shadowEngine, toolName, toolArgs, permissions, workingDirectory, modeState);
65805
66025
  const mismatch = primary.result.decision !== shadow.result.decision || primary.result.matchedRule !== shadow.result.matchedRule;
65806
66026
  if (mismatch) {
65807
66027
  console.error(`[permissions] dual-eval mismatch ${JSON.stringify({
@@ -65850,7 +66070,7 @@ function traceEvent(trace, stage, message, pattern, matched) {
65850
66070
  event.matched = matched;
65851
66071
  trace.events.push(event);
65852
66072
  }
65853
- function checkPermissionForEngine(engine, toolName, toolArgs, permissions, workingDirectory) {
66073
+ function checkPermissionForEngine(engine, toolName, toolArgs, permissions, workingDirectory, modeState) {
65854
66074
  const canonicalTool = canonicalToolName(toolName);
65855
66075
  const queryTool = engine === "v2" ? canonicalTool : toolName;
65856
66076
  const query = buildPermissionQuery(queryTool, toolArgs, engine);
@@ -65888,20 +66108,20 @@ function checkPermissionForEngine(engine, toolName, toolArgs, permissions, worki
65888
66108
  };
65889
66109
  }
65890
66110
  }
65891
- const modeOverride = permissionMode.checkModeOverride(toolName, toolArgs, workingDirectory);
66111
+ const effectiveMode = modeState?.mode ?? permissionMode.getMode();
66112
+ const effectivePlanFilePath = modeState?.planFilePath ?? permissionMode.getPlanFilePath();
66113
+ const modeOverride = permissionMode.checkModeOverride(toolName, toolArgs, workingDirectory, effectiveMode, effectivePlanFilePath);
65892
66114
  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.`;
66115
+ let reason = `Permission mode: ${effectiveMode}`;
66116
+ if (effectiveMode === "plan" && modeOverride === "deny") {
66117
+ const applyPatchRelativePath = effectivePlanFilePath ? relative6(workingDirectory, effectivePlanFilePath).replace(/\\/g, "/") : null;
66118
+ 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
66119
  }
65900
66120
  traceEvent(trace, "mode-override", reason);
65901
66121
  return {
65902
66122
  result: {
65903
66123
  decision: modeOverride,
65904
- matchedRule: `${currentMode} mode`,
66124
+ matchedRule: `${effectiveMode} mode`,
65905
66125
  reason
65906
66126
  },
65907
66127
  trace
@@ -66171,8 +66391,8 @@ function getDefaultDecision(toolName, toolArgs) {
66171
66391
  }
66172
66392
  return "ask";
66173
66393
  }
66174
- async function checkPermissionWithHooks(toolName, toolArgs, permissions, workingDirectory = process.cwd()) {
66175
- const result = checkPermission(toolName, toolArgs, permissions, workingDirectory);
66394
+ async function checkPermissionWithHooks(toolName, toolArgs, permissions, workingDirectory = process.cwd(), modeState) {
66395
+ const result = checkPermission(toolName, toolArgs, permissions, workingDirectory, modeState);
66176
66396
  if (result.decision === "ask") {
66177
66397
  const hookResult = await runPermissionRequestHooks(toolName, toolArgs, "ask", undefined, workingDirectory);
66178
66398
  if (hookResult.blocked) {
@@ -67017,6 +67237,7 @@ __export(exports_manager2, {
67017
67237
  getInternalToolName: () => getInternalToolName,
67018
67238
  getExternalToolsAsClientTools: () => getExternalToolsAsClientTools,
67019
67239
  getExternalToolDefinition: () => getExternalToolDefinition,
67240
+ getExecutionContextPermissionModeState: () => getExecutionContextPermissionModeState,
67020
67241
  getClientToolsFromRegistry: () => getClientToolsFromRegistry,
67021
67242
  getAllLettaToolNames: () => getAllLettaToolNames,
67022
67243
  executeTool: () => executeTool,
@@ -67084,6 +67305,9 @@ function saveExecutionContext(snapshot) {
67084
67305
  function getExecutionContextById(contextId) {
67085
67306
  return getExecutionContexts().get(contextId);
67086
67307
  }
67308
+ function getExecutionContextPermissionModeState(contextId) {
67309
+ return getExecutionContextById(contextId)?.permissionModeState;
67310
+ }
67087
67311
  function clearCapturedToolExecutionContexts() {
67088
67312
  getExecutionContexts().clear();
67089
67313
  }
@@ -67201,12 +67425,31 @@ function getClientToolsFromRegistry() {
67201
67425
  const externalTools = getExternalToolsAsClientTools();
67202
67426
  return [...builtInTools, ...externalTools];
67203
67427
  }
67204
- function captureToolExecutionContext(workingDirectory = process.env.USER_CWD || process.cwd()) {
67428
+ function captureToolExecutionContext(workingDirectory = process.env.USER_CWD || process.cwd(), permissionModeState) {
67429
+ const effectivePermissionModeState = permissionModeState ?? {
67430
+ get mode() {
67431
+ return permissionMode.getMode();
67432
+ },
67433
+ set mode(value) {
67434
+ permissionMode.setMode(value);
67435
+ },
67436
+ get planFilePath() {
67437
+ return permissionMode.getPlanFilePath();
67438
+ },
67439
+ set planFilePath(value) {
67440
+ permissionMode.setPlanFilePath(value);
67441
+ },
67442
+ get modeBeforePlan() {
67443
+ return permissionMode.getModeBeforePlan();
67444
+ },
67445
+ set modeBeforePlan(_value) {}
67446
+ };
67205
67447
  const snapshot = {
67206
67448
  toolRegistry: new Map(toolRegistry),
67207
67449
  externalTools: new Map(getExternalToolsRegistry()),
67208
67450
  externalExecutor: getExternalToolExecutor(),
67209
- workingDirectory
67451
+ workingDirectory,
67452
+ permissionModeState: effectivePermissionModeState
67210
67453
  };
67211
67454
  const contextId = saveExecutionContext(snapshot);
67212
67455
  const builtInTools = Array.from(snapshot.toolRegistry.entries()).map(([name, tool]) => ({
@@ -67246,11 +67489,11 @@ function getToolPermissions(toolName) {
67246
67489
  function requiresApproval(toolName) {
67247
67490
  return TOOL_PERMISSIONS[toolName]?.requiresApproval ?? false;
67248
67491
  }
67249
- async function checkToolPermission(toolName, toolArgs, workingDirectory = process.cwd()) {
67492
+ async function checkToolPermission(toolName, toolArgs, workingDirectory = process.cwd(), permissionModeStateArg) {
67250
67493
  const { checkPermissionWithHooks: checkPermissionWithHooks2 } = await init_checker().then(() => exports_checker);
67251
67494
  const { loadPermissions: loadPermissions2 } = await Promise.resolve().then(() => (init_loader2(), exports_loader));
67252
67495
  const permissions = await loadPermissions2(workingDirectory);
67253
- return checkPermissionWithHooks2(toolName, toolArgs, permissions, workingDirectory);
67496
+ return checkPermissionWithHooks2(toolName, toolArgs, permissions, workingDirectory, permissionModeStateArg);
67254
67497
  }
67255
67498
  async function savePermissionRule2(rule, ruleType, scope, workingDirectory = process.cwd()) {
67256
67499
  if (scope === "session") {
@@ -67567,6 +67810,18 @@ async function executeTool(name, args, options) {
67567
67810
  if (internalName === "Skill" && options?.toolCallId) {
67568
67811
  enhancedArgs = { ...enhancedArgs, toolCallId: options.toolCallId };
67569
67812
  }
67813
+ const PLAN_MODE_TOOL_NAMES = new Set([
67814
+ "EnterPlanMode",
67815
+ "enter_plan_mode",
67816
+ "ExitPlanMode",
67817
+ "exit_plan_mode"
67818
+ ]);
67819
+ if (PLAN_MODE_TOOL_NAMES.has(internalName) && options?.toolContextId) {
67820
+ enhancedArgs = {
67821
+ ...enhancedArgs,
67822
+ _executionContextId: options.toolContextId
67823
+ };
67824
+ }
67570
67825
  const result = await withExecutionWorkingDirectory(workingDirectory, () => tool.fn(enhancedArgs));
67571
67826
  const duration = Date.now() - startTime;
67572
67827
  if (FILE_MODIFYING_TOOLS.has(internalName)) {
@@ -67696,6 +67951,7 @@ var init_manager3 = __esm(async () => {
67696
67951
  init_subagents();
67697
67952
  init_fileIndex();
67698
67953
  init_constants();
67954
+ init_mode();
67699
67955
  init_debug();
67700
67956
  await __promiseAll([
67701
67957
  init_approval_execution(),
@@ -67904,6 +68160,75 @@ var init_constants2 = __esm(() => {
67904
68160
  SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
67905
68161
  });
67906
68162
 
68163
+ // src/websocket/listener/remote-settings.ts
68164
+ import { existsSync as existsSync16, readFileSync as readFileSync8 } from "node:fs";
68165
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
68166
+ import { homedir as homedir18 } from "node:os";
68167
+ import path20 from "node:path";
68168
+ function getRemoteSettingsPath() {
68169
+ return path20.join(homedir18(), ".letta", "remote-settings.json");
68170
+ }
68171
+ function loadRemoteSettings() {
68172
+ if (_cache !== null) {
68173
+ return _cache;
68174
+ }
68175
+ let loaded = {};
68176
+ try {
68177
+ const settingsPath = getRemoteSettingsPath();
68178
+ if (existsSync16(settingsPath)) {
68179
+ const raw = readFileSync8(settingsPath, "utf-8");
68180
+ const parsed = JSON.parse(raw);
68181
+ loaded = parsed;
68182
+ }
68183
+ } catch {}
68184
+ if (loaded.cwdMap) {
68185
+ const validCwdMap = {};
68186
+ for (const [key, value] of Object.entries(loaded.cwdMap)) {
68187
+ if (typeof value === "string" && existsSync16(value)) {
68188
+ validCwdMap[key] = value;
68189
+ }
68190
+ }
68191
+ loaded.cwdMap = validCwdMap;
68192
+ }
68193
+ if (!loaded.cwdMap) {
68194
+ loaded.cwdMap = loadLegacyCwdCache();
68195
+ }
68196
+ _cache = loaded;
68197
+ return _cache;
68198
+ }
68199
+ function saveRemoteSettings(updates) {
68200
+ if (_cache === null) {
68201
+ loadRemoteSettings();
68202
+ }
68203
+ _cache = {
68204
+ ..._cache,
68205
+ ...updates
68206
+ };
68207
+ const snapshot = _cache;
68208
+ const settingsPath = getRemoteSettingsPath();
68209
+ mkdir3(path20.dirname(settingsPath), { recursive: true }).then(() => writeFile3(settingsPath, JSON.stringify(snapshot, null, 2))).catch(() => {});
68210
+ }
68211
+ function loadLegacyCwdCache() {
68212
+ try {
68213
+ const legacyPath = path20.join(homedir18(), ".letta", "cwd-cache.json");
68214
+ if (!existsSync16(legacyPath))
68215
+ return {};
68216
+ const raw = readFileSync8(legacyPath, "utf-8");
68217
+ const parsed = JSON.parse(raw);
68218
+ const result = {};
68219
+ for (const [key, value] of Object.entries(parsed)) {
68220
+ if (typeof value === "string" && existsSync16(value)) {
68221
+ result[key] = value;
68222
+ }
68223
+ }
68224
+ return result;
68225
+ } catch {
68226
+ return {};
68227
+ }
68228
+ }
68229
+ var _cache = null;
68230
+ var init_remote_settings = () => {};
68231
+
67907
68232
  // src/websocket/listener/scope.ts
67908
68233
  function getOnlyConversationRuntime(runtime) {
67909
68234
  if (!runtime || runtime.conversationRuntimes.size !== 1) {
@@ -67949,10 +68274,6 @@ function resolveRuntimeScope(runtime, params) {
67949
68274
  }
67950
68275
 
67951
68276
  // 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
68277
  function getWorkingDirectoryScopeKey(agentId, conversationId) {
67957
68278
  const normalizedConversationId = normalizeConversationId(conversationId);
67958
68279
  const normalizedAgentId = normalizeCwdAgentId(agentId);
@@ -67965,21 +68286,12 @@ function getConversationWorkingDirectory(runtime, agentId, conversationId) {
67965
68286
  const scopeKey = getWorkingDirectoryScopeKey(agentId, conversationId);
67966
68287
  return runtime.workingDirectoryByConversation.get(scopeKey) ?? runtime.bootWorkingDirectory;
67967
68288
  }
67968
- function getCwdCachePath() {
67969
- return path20.join(homedir18(), ".letta", "cwd-cache.json");
67970
- }
67971
68289
  function loadPersistedCwdMap() {
67972
- if (!shouldPersistCwd)
67973
- return new Map;
67974
68290
  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);
68291
+ const settings = loadRemoteSettings();
67980
68292
  const map = new Map;
67981
- for (const [key, value] of Object.entries(parsed)) {
67982
- if (typeof value === "string" && existsSync16(value)) {
68293
+ if (settings.cwdMap) {
68294
+ for (const [key, value] of Object.entries(settings.cwdMap)) {
67983
68295
  map.set(key, value);
67984
68296
  }
67985
68297
  }
@@ -67989,11 +68301,7 @@ function loadPersistedCwdMap() {
67989
68301
  }
67990
68302
  }
67991
68303
  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(() => {});
68304
+ saveRemoteSettings({ cwdMap: Object.fromEntries(map) });
67997
68305
  }
67998
68306
  function setConversationWorkingDirectory(runtime, agentId, conversationId, workingDirectory) {
67999
68307
  const scopeKey = getWorkingDirectoryScopeKey(agentId, conversationId);
@@ -68004,9 +68312,73 @@ function setConversationWorkingDirectory(runtime, agentId, conversationId, worki
68004
68312
  }
68005
68313
  persistCwdMap(runtime.workingDirectoryByConversation);
68006
68314
  }
68007
- var shouldPersistCwd;
68008
68315
  var init_cwd = __esm(() => {
68009
- shouldPersistCwd = process.env.PERSIST_CWD === "1";
68316
+ init_remote_settings();
68317
+ });
68318
+
68319
+ // src/websocket/listener/permissionMode.ts
68320
+ function getPermissionModeScopeKey(agentId, conversationId) {
68321
+ const normalizedConversationId = normalizeConversationId(conversationId);
68322
+ const normalizedAgentId = normalizeCwdAgentId(agentId);
68323
+ if (normalizedConversationId === "default") {
68324
+ return `agent:${normalizedAgentId ?? "__unknown__"}::conversation:default`;
68325
+ }
68326
+ return `conversation:${normalizedConversationId}`;
68327
+ }
68328
+ function getConversationPermissionModeState(runtime, agentId, conversationId) {
68329
+ const scopeKey = getPermissionModeScopeKey(agentId, conversationId);
68330
+ return runtime.permissionModeByConversation.get(scopeKey) ?? {
68331
+ mode: permissionMode.getMode(),
68332
+ planFilePath: null,
68333
+ modeBeforePlan: null
68334
+ };
68335
+ }
68336
+ function setConversationPermissionModeState(runtime, agentId, conversationId, state) {
68337
+ const scopeKey = getPermissionModeScopeKey(agentId, conversationId);
68338
+ if (state.mode === permissionMode.getMode() && state.planFilePath === null && state.modeBeforePlan === null) {
68339
+ runtime.permissionModeByConversation.delete(scopeKey);
68340
+ } else {
68341
+ runtime.permissionModeByConversation.set(scopeKey, { ...state });
68342
+ }
68343
+ persistPermissionModeMap(runtime.permissionModeByConversation);
68344
+ }
68345
+ function loadPersistedPermissionModeMap() {
68346
+ try {
68347
+ const settings = loadRemoteSettings();
68348
+ const map = new Map;
68349
+ if (!settings.permissionModeMap) {
68350
+ return map;
68351
+ }
68352
+ for (const [key, persisted] of Object.entries(settings.permissionModeMap)) {
68353
+ const restoredMode = persisted.mode === "plan" ? persisted.modeBeforePlan ?? "default" : persisted.mode;
68354
+ map.set(key, {
68355
+ mode: restoredMode,
68356
+ planFilePath: null,
68357
+ modeBeforePlan: null
68358
+ });
68359
+ }
68360
+ return map;
68361
+ } catch {
68362
+ return new Map;
68363
+ }
68364
+ }
68365
+ function persistPermissionModeMap(map) {
68366
+ const permissionModeMap = {};
68367
+ for (const [key, state] of map) {
68368
+ const modeToSave = state.mode === "plan" ? state.modeBeforePlan ?? "default" : state.mode;
68369
+ if (modeToSave === "default" && state.modeBeforePlan === null) {
68370
+ continue;
68371
+ }
68372
+ permissionModeMap[key] = {
68373
+ mode: modeToSave,
68374
+ modeBeforePlan: state.mode === "plan" ? null : state.modeBeforePlan ?? null
68375
+ };
68376
+ }
68377
+ saveRemoteSettings({ permissionModeMap });
68378
+ }
68379
+ var init_permissionMode = __esm(() => {
68380
+ init_mode();
68381
+ init_remote_settings();
68010
68382
  });
68011
68383
 
68012
68384
  // src/websocket/listener/runtime.ts
@@ -68266,12 +68638,13 @@ function buildDeviceStatus(runtime, params) {
68266
68638
  return "auto";
68267
68639
  }
68268
68640
  })();
68641
+ const conversationPermissionModeState = getConversationPermissionModeState(listener, scopedAgentId, scopedConversationId);
68269
68642
  return {
68270
68643
  current_connection_id: listener.connectionId,
68271
68644
  connection_name: listener.connectionName,
68272
68645
  is_online: listener.socket?.readyState === WebSocket2.OPEN,
68273
68646
  is_processing: !!conversationRuntime?.isProcessing,
68274
- current_permission_mode: permissionMode.getMode(),
68647
+ current_permission_mode: conversationPermissionModeState.mode,
68275
68648
  current_working_directory: getConversationWorkingDirectory(listener, scopedAgentId, scopedConversationId),
68276
68649
  letta_code_version: process.env.npm_package_version || null,
68277
68650
  current_toolset: toolsetPreference === "auto" ? null : toolsetPreference,
@@ -68525,6 +68898,7 @@ var init_protocol_outbound = __esm(async () => {
68525
68898
  init_mode();
68526
68899
  init_constants2();
68527
68900
  init_cwd();
68901
+ init_permissionMode();
68528
68902
  init_runtime();
68529
68903
  await __promiseAll([
68530
68904
  init_settings_manager(),
@@ -71812,7 +72186,7 @@ async function sendMessageStream(conversationId, messages, opts = { streamTokens
71812
72186
  const requestStartedAtMs = Date.now();
71813
72187
  const client = await getClient2();
71814
72188
  await waitForToolsetReady();
71815
- const { clientTools, contextId } = captureToolExecutionContext(opts.workingDirectory);
72189
+ const { clientTools, contextId } = captureToolExecutionContext(opts.workingDirectory, opts.permissionModeState);
71816
72190
  const { clientSkills, errors: clientSkillDiscoveryErrors } = await buildClientSkillsPayload({
71817
72191
  agentId: opts.agentId
71818
72192
  });
@@ -74210,7 +74584,7 @@ async function classifyApprovals(approvals, opts = {}) {
74210
74584
  continue;
74211
74585
  }
74212
74586
  const parsedArgs = safeJsonParseOr(approval.toolArgs || "{}", {});
74213
- const permission = await checkToolPermission(toolName, parsedArgs, opts.workingDirectory);
74587
+ const permission = await checkToolPermission(toolName, parsedArgs, opts.workingDirectory, opts.permissionModeState);
74214
74588
  const context3 = opts.getContext ? await opts.getContext(toolName, parsedArgs, opts.workingDirectory) : null;
74215
74589
  let decision = permission.decision;
74216
74590
  if (opts.alwaysRequiresUserInput?.(toolName) && decision === "allow") {
@@ -74316,7 +74690,8 @@ async function resolveStaleApprovals(runtime, socket, abortSignal) {
74316
74690
  alwaysRequiresUserInput: isInteractiveApprovalTool,
74317
74691
  requireArgsForAutoApprove: true,
74318
74692
  missingNameReason: "Tool call incomplete - missing name",
74319
- workingDirectory: recoveryWorkingDirectory
74693
+ workingDirectory: recoveryWorkingDirectory,
74694
+ permissionModeState: getConversationPermissionModeState(runtime.listener, runtime.agentId, runtime.conversationId)
74320
74695
  });
74321
74696
  const decisions = [
74322
74697
  ...autoAllowed.map((ac) => ({
@@ -74697,6 +75072,7 @@ var init_send = __esm(async () => {
74697
75072
  init_interactivePolicy();
74698
75073
  init_constants2();
74699
75074
  init_cwd();
75075
+ init_permissionMode();
74700
75076
  await __promiseAll([
74701
75077
  init_approval_execution(),
74702
75078
  init_approval_recovery(),
@@ -75507,6 +75883,7 @@ async function handleApprovalStop(params) {
75507
75883
  agentId,
75508
75884
  conversationId,
75509
75885
  turnWorkingDirectory,
75886
+ turnPermissionModeState,
75510
75887
  dequeuedBatchId,
75511
75888
  runId,
75512
75889
  msgRunIds,
@@ -75559,7 +75936,8 @@ async function handleApprovalStop(params) {
75559
75936
  treatAskAsDeny: false,
75560
75937
  requireArgsForAutoApprove: true,
75561
75938
  missingNameReason: "Tool call incomplete - missing name",
75562
- workingDirectory: turnWorkingDirectory
75939
+ workingDirectory: turnWorkingDirectory,
75940
+ permissionModeState: turnPermissionModeState
75563
75941
  });
75564
75942
  const lastNeedsUserInputToolCallIds = needsUserInput.map((ac) => ac.approval.toolCallId);
75565
75943
  let lastExecutionResults = null;
@@ -75738,6 +76116,9 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
75738
76116
  const conversationId = requestedConversationId ?? "default";
75739
76117
  const normalizedAgentId = normalizeCwdAgentId(agentId);
75740
76118
  const turnWorkingDirectory = getConversationWorkingDirectory(runtime.listener, normalizedAgentId, conversationId);
76119
+ const turnPermissionModeState = {
76120
+ ...getConversationPermissionModeState(runtime.listener, normalizedAgentId, conversationId)
76121
+ };
75741
76122
  const msgRunIds = [];
75742
76123
  let postStopApprovalRecoveryRetries = 0;
75743
76124
  let llmApiErrorRetries = 0;
@@ -75820,6 +76201,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
75820
76201
  streamTokens: true,
75821
76202
  background: true,
75822
76203
  workingDirectory: turnWorkingDirectory,
76204
+ permissionModeState: turnPermissionModeState,
75823
76205
  ...pendingNormalizationInterruptedToolCallIds.length > 0 ? {
75824
76206
  approvalNormalization: {
75825
76207
  interruptedToolCallIds: pendingNormalizationInterruptedToolCallIds
@@ -76102,6 +76484,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
76102
76484
  agentId,
76103
76485
  conversationId,
76104
76486
  turnWorkingDirectory,
76487
+ turnPermissionModeState,
76105
76488
  dequeuedBatchId,
76106
76489
  runId,
76107
76490
  msgRunIds,
@@ -76189,6 +76572,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
76189
76572
  console.error("[Listen] Error handling message:", error);
76190
76573
  }
76191
76574
  } finally {
76575
+ setConversationPermissionModeState(runtime.listener, normalizedAgentId, conversationId, turnPermissionModeState);
76192
76576
  runtime.activeAbortController = null;
76193
76577
  runtime.cancelRequested = false;
76194
76578
  runtime.isRecoveringApprovals = false;
@@ -76205,6 +76589,7 @@ var init_turn = __esm(async () => {
76205
76589
  init_debug();
76206
76590
  init_constants2();
76207
76591
  init_cwd();
76592
+ init_permissionMode();
76208
76593
  init_runtime();
76209
76594
  await __promiseAll([
76210
76595
  init_approval_recovery(),
@@ -76227,11 +76612,22 @@ import path22 from "node:path";
76227
76612
  import WebSocket4 from "ws";
76228
76613
  function handleModeChange(msg, socket, runtime, scope) {
76229
76614
  try {
76230
- permissionMode.setMode(msg.mode);
76231
- if (msg.mode === "plan" && !permissionMode.getPlanFilePath()) {
76232
- const planFilePath = generatePlanFilePath();
76233
- permissionMode.setPlanFilePath(planFilePath);
76234
- }
76615
+ const agentId = scope?.agent_id ?? null;
76616
+ const conversationId = scope?.conversation_id ?? "default";
76617
+ const current = getConversationPermissionModeState(runtime, agentId, conversationId);
76618
+ const next = { ...current };
76619
+ if (msg.mode === "plan" && current.mode !== "plan") {
76620
+ next.modeBeforePlan = current.mode;
76621
+ }
76622
+ next.mode = msg.mode;
76623
+ if (msg.mode === "plan" && !current.planFilePath) {
76624
+ next.planFilePath = generatePlanFilePath();
76625
+ }
76626
+ if (msg.mode !== "plan") {
76627
+ next.planFilePath = null;
76628
+ next.modeBeforePlan = null;
76629
+ }
76630
+ setConversationPermissionModeState(runtime, agentId, conversationId, next);
76235
76631
  emitDeviceStatusUpdate(socket, runtime, scope);
76236
76632
  if (isDebugEnabled()) {
76237
76633
  console.log(`[Listen] Mode changed to: ${msg.mode}`);
@@ -76366,6 +76762,7 @@ function createRuntime() {
76366
76762
  reminderState: createSharedReminderState(),
76367
76763
  bootWorkingDirectory,
76368
76764
  workingDirectoryByConversation: loadPersistedCwdMap(),
76765
+ permissionModeByConversation: loadPersistedPermissionModeMap(),
76369
76766
  connectionId: null,
76370
76767
  connectionName: null,
76371
76768
  conversationRuntimes: new Map,
@@ -76786,6 +77183,12 @@ function createLegacyTestRuntime() {
76786
77183
  listener.workingDirectoryByConversation = value;
76787
77184
  }
76788
77185
  },
77186
+ permissionModeByConversation: {
77187
+ get: () => listener.permissionModeByConversation,
77188
+ set: (value) => {
77189
+ listener.permissionModeByConversation = value;
77190
+ }
77191
+ },
76789
77192
  bootWorkingDirectory: {
76790
77193
  get: () => listener.bootWorkingDirectory,
76791
77194
  set: (value) => {
@@ -76908,12 +77311,12 @@ var __listenClientTestUtils;
76908
77311
  var init_client4 = __esm(async () => {
76909
77312
  init_planName();
76910
77313
  init_constants();
76911
- init_mode();
76912
77314
  init_queueRuntime();
76913
77315
  init_debug();
76914
77316
  init_terminalHandler();
76915
77317
  init_constants2();
76916
77318
  init_cwd();
77319
+ init_permissionMode();
76917
77320
  init_runtime();
76918
77321
  await __promiseAll([
76919
77322
  init_client2(),
@@ -78370,14 +78773,14 @@ function SetupUI({ onComplete }) {
78370
78773
  children: [
78371
78774
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(AnimatedLogo, {
78372
78775
  color: colors.welcome.accent,
78373
- animate: false
78776
+ animate: AUTH_LOGO_ANIMATE
78374
78777
  }, undefined, false, undefined, this),
78375
78778
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text2, {
78376
78779
  children: " "
78377
78780
  }, undefined, false, undefined, this),
78378
78781
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text2, {
78379
78782
  bold: true,
78380
- children: "Login to Letta Platform"
78783
+ children: AUTH_LOGIN_LABEL
78381
78784
  }, undefined, false, undefined, this),
78382
78785
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text2, {
78383
78786
  children: " "
@@ -78422,7 +78825,8 @@ function SetupUI({ onComplete }) {
78422
78825
  padding: 1,
78423
78826
  children: [
78424
78827
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(AnimatedLogo, {
78425
- color: colors.welcome.accent
78828
+ color: colors.welcome.accent,
78829
+ animate: AUTH_LOGO_ANIMATE
78426
78830
  }, undefined, false, undefined, this),
78427
78831
  /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text2, {
78428
78832
  children: " "
@@ -78445,7 +78849,7 @@ function SetupUI({ onComplete }) {
78445
78849
  color: selectedOption === 0 ? colors.selector.itemHighlighted : undefined,
78446
78850
  children: [
78447
78851
  selectedOption === 0 ? "> " : " ",
78448
- "Login to Letta Platform"
78852
+ AUTH_LOGIN_LABEL
78449
78853
  ]
78450
78854
  }, undefined, true, undefined, this)
78451
78855
  }, undefined, false, undefined, this),
@@ -78468,7 +78872,7 @@ function SetupUI({ onComplete }) {
78468
78872
  ]
78469
78873
  }, undefined, true, undefined, this);
78470
78874
  }
78471
- var import_react31, jsx_dev_runtime10;
78875
+ var import_react31, jsx_dev_runtime10, AUTH_LOGIN_LABEL = "Login to Letta Code", AUTH_LOGO_ANIMATE = false;
78472
78876
  var init_setup_ui = __esm(async () => {
78473
78877
  init_colors();
78474
78878
  init_oauth();
@@ -103344,7 +103748,7 @@ var init_pasteRegistry = __esm(() => {
103344
103748
 
103345
103749
  // src/cli/helpers/clipboard.ts
103346
103750
  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";
103751
+ import { existsSync as existsSync25, readFileSync as readFileSync11, statSync as statSync7, unlinkSync as unlinkSync8 } from "node:fs";
103348
103752
  import { tmpdir as tmpdir3 } from "node:os";
103349
103753
  import { basename as basename4, extname as extname5, isAbsolute as isAbsolute17, join as join33, resolve as resolve26 } from "node:path";
103350
103754
  function countLines2(text) {
@@ -103397,7 +103801,7 @@ function translatePasteForImages(paste) {
103397
103801
  filePath = resolve26(process.cwd(), filePath);
103398
103802
  const ext3 = extname5(filePath || "").toLowerCase();
103399
103803
  if (IMAGE_EXTS.has(ext3) && existsSync25(filePath) && statSync7(filePath).isFile()) {
103400
- const buf = readFileSync10(filePath);
103804
+ const buf = readFileSync11(filePath);
103401
103805
  const b64 = buf.toString("base64");
103402
103806
  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
103807
  const id = allocateImage({
@@ -103457,7 +103861,7 @@ async function tryImportClipboardImageMac() {
103457
103861
  return null;
103458
103862
  const { tempPath, uti } = clipboardResult;
103459
103863
  try {
103460
- const buffer = readFileSync10(tempPath);
103864
+ const buffer = readFileSync11(tempPath);
103461
103865
  try {
103462
103866
  unlinkSync8(tempPath);
103463
103867
  } catch {}
@@ -104053,7 +104457,7 @@ import {
104053
104457
  copyFileSync,
104054
104458
  existsSync as existsSync26,
104055
104459
  mkdirSync as mkdirSync19,
104056
- readFileSync as readFileSync11,
104460
+ readFileSync as readFileSync12,
104057
104461
  writeFileSync as writeFileSync13
104058
104462
  } from "node:fs";
104059
104463
  import { homedir as homedir28, platform as platform4 } from "node:os";
@@ -104123,7 +104527,7 @@ function keybindingExists(keybindingsPath) {
104123
104527
  if (!existsSync26(keybindingsPath))
104124
104528
  return false;
104125
104529
  try {
104126
- const content = readFileSync11(keybindingsPath, { encoding: "utf-8" });
104530
+ const content = readFileSync12(keybindingsPath, { encoding: "utf-8" });
104127
104531
  const keybindings = parseKeybindings(content);
104128
104532
  if (!keybindings)
104129
104533
  return false;
@@ -104156,7 +104560,7 @@ function installKeybinding(keybindingsPath) {
104156
104560
  let backupPath = null;
104157
104561
  if (existsSync26(keybindingsPath)) {
104158
104562
  backupPath = createBackup(keybindingsPath);
104159
- const content = readFileSync11(keybindingsPath, { encoding: "utf-8" });
104563
+ const content = readFileSync12(keybindingsPath, { encoding: "utf-8" });
104160
104564
  const parsed = parseKeybindings(content);
104161
104565
  if (parsed === null) {
104162
104566
  return {
@@ -104187,7 +104591,7 @@ function removeKeybinding(keybindingsPath) {
104187
104591
  if (!existsSync26(keybindingsPath)) {
104188
104592
  return { success: true };
104189
104593
  }
104190
- const content = readFileSync11(keybindingsPath, { encoding: "utf-8" });
104594
+ const content = readFileSync12(keybindingsPath, { encoding: "utf-8" });
104191
104595
  const keybindings = parseKeybindings(content);
104192
104596
  if (!keybindings) {
104193
104597
  return {
@@ -104264,7 +104668,7 @@ function wezTermDeleteFixExists(configPath) {
104264
104668
  if (!existsSync26(configPath))
104265
104669
  return false;
104266
104670
  try {
104267
- const content = readFileSync11(configPath, { encoding: "utf-8" });
104671
+ const content = readFileSync12(configPath, { encoding: "utf-8" });
104268
104672
  return content.includes("Letta Code: Fix Delete key") || content.includes("key = 'Delete'") && content.includes("SendString") && content.includes("\\x1b[3~");
104269
104673
  } catch {
104270
104674
  return false;
@@ -104281,7 +104685,7 @@ function installWezTermDeleteFix() {
104281
104685
  if (existsSync26(configPath)) {
104282
104686
  backupPath = `${configPath}.letta-backup`;
104283
104687
  copyFileSync(configPath, backupPath);
104284
- content = readFileSync11(configPath, { encoding: "utf-8" });
104688
+ content = readFileSync12(configPath, { encoding: "utf-8" });
104285
104689
  }
104286
104690
  if (content.includes("return {") && !content.includes("local config")) {
104287
104691
  content = content.replace(/return\s*\{/, "local config = {");
@@ -110039,7 +110443,7 @@ import {
110039
110443
  existsSync as existsSync28,
110040
110444
  mkdirSync as mkdirSync20,
110041
110445
  mkdtempSync,
110042
- readFileSync as readFileSync12,
110446
+ readFileSync as readFileSync13,
110043
110447
  rmSync as rmSync3,
110044
110448
  writeFileSync as writeFileSync14
110045
110449
  } from "node:fs";
@@ -110297,7 +110701,7 @@ function writeWorkflow(repoDir, workflowPath, content) {
110297
110701
  const next = `${content.trimEnd()}
110298
110702
  `;
110299
110703
  if (existsSync28(absolutePath)) {
110300
- const previous = readFileSync12(absolutePath, "utf8");
110704
+ const previous = readFileSync13(absolutePath, "utf8");
110301
110705
  if (previous === next) {
110302
110706
  return false;
110303
110707
  }
@@ -112561,7 +112965,7 @@ var init_McpSelector = __esm(async () => {
112561
112965
  });
112562
112966
 
112563
112967
  // src/agent/memoryScanner.ts
112564
- import { readdirSync as readdirSync12, readFileSync as readFileSync13, statSync as statSync9 } from "node:fs";
112968
+ import { readdirSync as readdirSync12, readFileSync as readFileSync14, statSync as statSync9 } from "node:fs";
112565
112969
  import { join as join38, relative as relative14 } from "node:path";
112566
112970
  function scanMemoryFilesystem(memoryRoot) {
112567
112971
  const nodes = [];
@@ -112626,7 +113030,7 @@ function getFileNodes(nodes) {
112626
113030
  }
112627
113031
  function readFileContent(fullPath) {
112628
113032
  try {
112629
- return readFileSync13(fullPath, "utf-8");
113033
+ return readFileSync14(fullPath, "utf-8");
112630
113034
  } catch {
112631
113035
  return "(unable to read file)";
112632
113036
  }
@@ -122143,7 +122547,7 @@ var init_contextChart = __esm(() => {
122143
122547
 
122144
122548
  // src/cli/helpers/initCommand.ts
122145
122549
  import { execSync as execSync2 } from "node:child_process";
122146
- import { existsSync as existsSync31, readdirSync as readdirSync13, readFileSync as readFileSync14 } from "node:fs";
122550
+ import { existsSync as existsSync31, readdirSync as readdirSync13, readFileSync as readFileSync15 } from "node:fs";
122147
122551
  import { join as join41 } from "node:path";
122148
122552
  function hasActiveInitSubagent() {
122149
122553
  const snapshot = getSnapshot2();
@@ -122191,7 +122595,7 @@ function gatherExistingMemory(agentId) {
122191
122595
  walk(join41(dir, entry.name), rel);
122192
122596
  } else if (entry.name.endsWith(".md")) {
122193
122597
  try {
122194
- const content = readFileSync14(join41(dir, entry.name), "utf-8");
122598
+ const content = readFileSync15(join41(dir, entry.name), "utf-8");
122195
122599
  paths.push(rel);
122196
122600
  sections.push(`── ${rel}
122197
122601
  ${content.slice(0, 2000)}`);
@@ -123588,7 +123992,7 @@ __export(exports_shellAliases, {
123588
123992
  expandAliases: () => expandAliases,
123589
123993
  clearAliasCache: () => clearAliasCache
123590
123994
  });
123591
- import { existsSync as existsSync32, readFileSync as readFileSync15 } from "node:fs";
123995
+ import { existsSync as existsSync32, readFileSync as readFileSync16 } from "node:fs";
123592
123996
  import { homedir as homedir33 } from "node:os";
123593
123997
  import { join as join43 } from "node:path";
123594
123998
  function parseAliasesFromFile(filePath) {
@@ -123597,7 +124001,7 @@ function parseAliasesFromFile(filePath) {
123597
124001
  return aliases;
123598
124002
  }
123599
124003
  try {
123600
- const content = readFileSync15(filePath, "utf-8");
124004
+ const content = readFileSync16(filePath, "utf-8");
123601
124005
  const lines = content.split(`
123602
124006
  `);
123603
124007
  let inFunction = false;
@@ -124492,7 +124896,7 @@ var exports_App = {};
124492
124896
  __export(exports_App, {
124493
124897
  default: () => App2
124494
124898
  });
124495
- import { existsSync as existsSync33, readFileSync as readFileSync16, renameSync as renameSync2, writeFileSync as writeFileSync16 } from "node:fs";
124899
+ import { existsSync as existsSync33, readFileSync as readFileSync17, renameSync as renameSync2, writeFileSync as writeFileSync16 } from "node:fs";
124496
124900
  import { homedir as homedir34, tmpdir as tmpdir6 } from "node:os";
124497
124901
  import { join as join44, relative as relative16 } from "node:path";
124498
124902
  function deriveReasoningEffort(modelSettings, llmConfig) {
@@ -124677,7 +125081,7 @@ function _readPlanFile(fallbackPlanFilePath) {
124677
125081
  return `Plan file not found at ${planFilePath}`;
124678
125082
  }
124679
125083
  try {
124680
- return readFileSync16(planFilePath, "utf-8");
125084
+ return readFileSync17(planFilePath, "utf-8");
124681
125085
  } catch {
124682
125086
  return `Failed to read plan file at ${planFilePath}`;
124683
125087
  }
@@ -124898,6 +125302,8 @@ function App2({
124898
125302
  const [pendingApprovals, setPendingApprovals] = import_react101.useState([]);
124899
125303
  const [approvalContexts, setApprovalContexts] = import_react101.useState([]);
124900
125304
  const [approvalResults, setApprovalResults] = import_react101.useState([]);
125305
+ const lastAutoApprovedEnterPlanToolCallIdRef = import_react101.useRef(null);
125306
+ const lastAutoHandledExitPlanToolCallIdRef = import_react101.useRef(null);
124901
125307
  const [isExecutingTool, setIsExecutingTool] = import_react101.useState(false);
124902
125308
  const [queuedApprovalResults, setQueuedApprovalResults] = import_react101.useState(null);
124903
125309
  const toolAbortControllerRef = import_react101.useRef(null);
@@ -125966,10 +126372,10 @@ function App2({
125966
126372
  if (!planFilePath)
125967
126373
  return;
125968
126374
  try {
125969
- const { readFileSync: readFileSync17, existsSync: existsSync34 } = __require("node:fs");
126375
+ const { readFileSync: readFileSync18, existsSync: existsSync34 } = __require("node:fs");
125970
126376
  if (!existsSync34(planFilePath))
125971
126377
  return;
125972
- const planContent = readFileSync17(planFilePath, "utf-8");
126378
+ const planContent = readFileSync18(planFilePath, "utf-8");
125973
126379
  const previewItem = {
125974
126380
  kind: "approval_preview",
125975
126381
  id: `approval-preview-${toolCallId}`,
@@ -131900,6 +132306,9 @@ ${guidance}`);
131900
132306
  const currentIndex = approvalResults.length;
131901
132307
  const approval = pendingApprovals[currentIndex];
131902
132308
  if (approval?.toolName === "ExitPlanMode") {
132309
+ if (lastAutoHandledExitPlanToolCallIdRef.current === approval.toolCallId) {
132310
+ return;
132311
+ }
131903
132312
  const mode = permissionMode.getMode();
131904
132313
  const activePlanPath = permissionMode.getPlanFilePath();
131905
132314
  const fallbackPlanPath = lastPlanFilePathRef.current;
@@ -131907,6 +132316,7 @@ ${guidance}`);
131907
132316
  if (mode !== "plan") {
131908
132317
  if (mode === "bypassPermissions") {
131909
132318
  if (hasUsablePlan) {
132319
+ lastAutoHandledExitPlanToolCallIdRef.current = approval.toolCallId;
131910
132320
  handlePlanApprove();
131911
132321
  return;
131912
132322
  }
@@ -131927,6 +132337,7 @@ ${guidance}`);
131927
132337
  lines: ["⚠️ Plan mode session expired (use /plan to re-enter)"]
131928
132338
  });
131929
132339
  buffersRef.current.order.push(statusId);
132340
+ lastAutoHandledExitPlanToolCallIdRef.current = approval.toolCallId;
131930
132341
  const denialResults = [
131931
132342
  {
131932
132343
  type: "approval",
@@ -131946,6 +132357,7 @@ ${guidance}`);
131946
132357
  return;
131947
132358
  }
131948
132359
  if (!hasUsablePlan) {
132360
+ lastAutoHandledExitPlanToolCallIdRef.current = approval.toolCallId;
131949
132361
  const planFilePath = activePlanPath ?? fallbackPlanPath;
131950
132362
  const plansDir = join44(homedir34(), ".letta", "plans");
131951
132363
  handlePlanKeepPlanning(`You must write your plan to a plan file before exiting plan mode.
@@ -132089,6 +132501,10 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
132089
132501
  const approval = pendingApprovals[currentIndex];
132090
132502
  if (approval?.toolName === "EnterPlanMode") {
132091
132503
  if (permissionMode.getMode() === "bypassPermissions") {
132504
+ if (lastAutoApprovedEnterPlanToolCallIdRef.current === approval.toolCallId) {
132505
+ return;
132506
+ }
132507
+ lastAutoApprovedEnterPlanToolCallIdRef.current = approval.toolCallId;
132092
132508
  handleEnterPlanModeApprove(true);
132093
132509
  }
132094
132510
  }
@@ -133317,7 +133733,7 @@ import {
133317
133733
  copyFileSync as copyFileSync2,
133318
133734
  existsSync as existsSync34,
133319
133735
  mkdirSync as mkdirSync22,
133320
- readFileSync as readFileSync17,
133736
+ readFileSync as readFileSync18,
133321
133737
  writeFileSync as writeFileSync17
133322
133738
  } from "node:fs";
133323
133739
  import { homedir as homedir35, platform as platform5 } from "node:os";
@@ -133387,7 +133803,7 @@ function keybindingExists2(keybindingsPath) {
133387
133803
  if (!existsSync34(keybindingsPath))
133388
133804
  return false;
133389
133805
  try {
133390
- const content = readFileSync17(keybindingsPath, { encoding: "utf-8" });
133806
+ const content = readFileSync18(keybindingsPath, { encoding: "utf-8" });
133391
133807
  const keybindings = parseKeybindings2(content);
133392
133808
  if (!keybindings)
133393
133809
  return false;
@@ -133420,7 +133836,7 @@ function installKeybinding2(keybindingsPath) {
133420
133836
  let backupPath = null;
133421
133837
  if (existsSync34(keybindingsPath)) {
133422
133838
  backupPath = createBackup2(keybindingsPath);
133423
- const content = readFileSync17(keybindingsPath, { encoding: "utf-8" });
133839
+ const content = readFileSync18(keybindingsPath, { encoding: "utf-8" });
133424
133840
  const parsed = parseKeybindings2(content);
133425
133841
  if (parsed === null) {
133426
133842
  return {
@@ -133451,7 +133867,7 @@ function removeKeybinding2(keybindingsPath) {
133451
133867
  if (!existsSync34(keybindingsPath)) {
133452
133868
  return { success: true };
133453
133869
  }
133454
- const content = readFileSync17(keybindingsPath, { encoding: "utf-8" });
133870
+ const content = readFileSync18(keybindingsPath, { encoding: "utf-8" });
133455
133871
  const keybindings = parseKeybindings2(content);
133456
133872
  if (!keybindings) {
133457
133873
  return {
@@ -133528,7 +133944,7 @@ function wezTermDeleteFixExists2(configPath) {
133528
133944
  if (!existsSync34(configPath))
133529
133945
  return false;
133530
133946
  try {
133531
- const content = readFileSync17(configPath, { encoding: "utf-8" });
133947
+ const content = readFileSync18(configPath, { encoding: "utf-8" });
133532
133948
  return content.includes("Letta Code: Fix Delete key") || content.includes("key = 'Delete'") && content.includes("SendString") && content.includes("\\x1b[3~");
133533
133949
  } catch {
133534
133950
  return false;
@@ -133545,7 +133961,7 @@ function installWezTermDeleteFix2() {
133545
133961
  if (existsSync34(configPath)) {
133546
133962
  backupPath = `${configPath}.letta-backup`;
133547
133963
  copyFileSync2(configPath, backupPath);
133548
- content = readFileSync17(configPath, { encoding: "utf-8" });
133964
+ content = readFileSync18(configPath, { encoding: "utf-8" });
133549
133965
  }
133550
133966
  if (content.includes("return {") && !content.includes("local config")) {
133551
133967
  content = content.replace(/return\s*\{/, "local config = {");
@@ -138981,8 +139397,10 @@ class PermissionModeManager2 {
138981
139397
  getPlanFilePath() {
138982
139398
  return getGlobalPlanFilePath2();
138983
139399
  }
138984
- checkModeOverride(toolName, toolArgs, workingDirectory = process.cwd()) {
138985
- switch (this.currentMode) {
139400
+ checkModeOverride(toolName, toolArgs, workingDirectory = process.cwd(), modeOverride, planFilePathOverride) {
139401
+ const effectiveMode = modeOverride ?? this.currentMode;
139402
+ const _effectivePlanFilePath = planFilePathOverride !== undefined ? planFilePathOverride : this.getPlanFilePath();
139403
+ switch (effectiveMode) {
138986
139404
  case "bypassPermissions":
138987
139405
  return "allow";
138988
139406
  case "acceptEdits":
@@ -140356,6 +140774,7 @@ var telemetry2 = new TelemetryManager2;
140356
140774
  init_subagents();
140357
140775
  init_fileIndex();
140358
140776
  init_constants();
140777
+ init_mode();
140359
140778
  init_debug();
140360
140779
  await __promiseAll([
140361
140780
  init_approval_execution(),
@@ -140648,7 +141067,7 @@ import {
140648
141067
  existsSync as existsSync19,
140649
141068
  mkdirSync as mkdirSync14,
140650
141069
  readdirSync as readdirSync9,
140651
- readFileSync as readFileSync8,
141070
+ readFileSync as readFileSync9,
140652
141071
  unlinkSync as unlinkSync6
140653
141072
  } from "node:fs";
140654
141073
  import { homedir as homedir23 } from "node:os";
@@ -140705,7 +141124,7 @@ class DebugLogFile2 {
140705
141124
  try {
140706
141125
  if (!existsSync19(this.logPath))
140707
141126
  return;
140708
- const content = readFileSync8(this.logPath, "utf8");
141127
+ const content = readFileSync9(this.logPath, "utf8");
140709
141128
  const lines = content.trimEnd().split(`
140710
141129
  `);
140711
141130
  return lines.slice(-maxLines).join(`
@@ -142186,4 +142605,4 @@ Error during initialization: ${message}`);
142186
142605
  }
142187
142606
  main();
142188
142607
 
142189
- //# debugId=3F2642C6F96B1B5B64756E2164756E21
142608
+ //# debugId=271668920D12255A64756E2164756E21