@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.
- package/letta.js +1478 -1059
- 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.
|
|
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 =
|
|
5619
|
+
const match = normalized.match(frontmatterRegex);
|
|
5617
5620
|
if (!match || !match[1] || !match[2]) {
|
|
5618
|
-
return { frontmatter: {}, body:
|
|
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
|
|
41108
|
-
import { join as
|
|
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(
|
|
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((
|
|
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 =
|
|
41331
|
-
const entryPath =
|
|
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 =
|
|
40658
|
+
const homeDir = homedir8();
|
|
41398
40659
|
const sanitizedWorkspace = sanitizeWorkspacePath2(process.cwd());
|
|
41399
|
-
return
|
|
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
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
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
|
|
42218
|
-
import { resolve as
|
|
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 ||
|
|
42221
|
-
const globalSettingsPath =
|
|
42222
|
-
const projectSettingsPath =
|
|
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(
|
|
46535
|
-
|
|
46536
|
-
|
|
46537
|
-
|
|
46538
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
65894
|
-
|
|
65895
|
-
|
|
65896
|
-
|
|
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: `${
|
|
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
|
|
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
|
-
|
|
67982
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
76231
|
-
|
|
76232
|
-
|
|
76233
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
126375
|
+
const { readFileSync: readFileSync18, existsSync: existsSync34 } = __require("node:fs");
|
|
125970
126376
|
if (!existsSync34(planFilePath))
|
|
125971
126377
|
return;
|
|
125972
|
-
const planContent =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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=
|
|
142608
|
+
//# debugId=271668920D12255A64756E2164756E21
|