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