@letta-ai/letta-code 0.28.10 → 0.28.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-presets.js +268 -36
- package/dist/agent-presets.js.map +2 -2
- package/dist/channels-slack.js +2 -11
- package/dist/channels-slack.js.map +3 -3
- package/dist/types/agent/model-catalog.d.ts +0 -38
- package/dist/types/agent/model-catalog.d.ts.map +1 -1
- package/dist/types/channels/slack/progress.d.ts.map +1 -1
- package/dist/types/channels/types.d.ts +1 -0
- package/dist/types/channels/types.d.ts.map +1 -1
- package/letta.js +32445 -31820
- package/package.json +1 -1
- package/scripts/source-file-size-baseline.json +4 -4
- package/skills/creating-mods/references/events.md +4 -4
- package/skills/self-configuration/LICENSE +21 -0
- package/skills/self-configuration/SKILL.md +432 -0
- package/skills/self-configuration/references/api-patch-examples.md +161 -0
- package/skills/self-configuration/references/compaction-prompt-patterns.md +115 -0
- package/skills/self-configuration/references/model-settings.md +55 -0
- package/skills/self-configuration/scripts/add_permission.py +212 -0
- package/skills/self-configuration/scripts/show_config.py +370 -0
- package/skills/self-configuration/scripts/update-agent-settings.ts +384 -0
- package/skills/self-configuration/scripts/update-compaction-prompt.ts +227 -0
- package/skills/modifying-the-harness/SKILL.md +0 -263
- package/skills/modifying-the-harness/references/hooks.md +0 -261
- package/skills/modifying-the-harness/scripts/add_hook.py +0 -223
- package/skills/modifying-the-harness/scripts/add_permission.py +0 -136
- package/skills/modifying-the-harness/scripts/show_config.py +0 -212
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Show relevant Letta Code self-configuration.
|
|
3
|
+
|
|
4
|
+
Displays a secret-safe runtime report plus settings files, permissions, selected
|
|
5
|
+
runtime preferences, environment keys, experiments, and per-agent settings across
|
|
6
|
+
user/project/local scopes with source scope annotated. Secret values are not
|
|
7
|
+
printed.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python3 show_config.py
|
|
11
|
+
python3 show_config.py --cwd /path/to/project
|
|
12
|
+
python3 show_config.py --json
|
|
13
|
+
python3 show_config.py --section runtime --json
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
AGENT_SETTING_KEYS = (
|
|
26
|
+
"agentId",
|
|
27
|
+
"baseUrl",
|
|
28
|
+
"pinned",
|
|
29
|
+
"memfs",
|
|
30
|
+
"toolset",
|
|
31
|
+
"systemPromptPreset",
|
|
32
|
+
"systemPromptHash",
|
|
33
|
+
"systemPromptVersion",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
TOP_LEVEL_KEYS = [
|
|
37
|
+
"tokenStreaming",
|
|
38
|
+
"reasoningTabCycleEnabled",
|
|
39
|
+
"showCompactions",
|
|
40
|
+
"sessionContextEnabled",
|
|
41
|
+
"autoConversationTitles",
|
|
42
|
+
"autoSwapOnQuotaLimit",
|
|
43
|
+
"includeWorktreeTool",
|
|
44
|
+
"preferredBackendMode",
|
|
45
|
+
"channelCredentialsStore",
|
|
46
|
+
"reflectionTrigger",
|
|
47
|
+
"reflectionStepCount",
|
|
48
|
+
"conversationSwitchAlertEnabled",
|
|
49
|
+
"createDefaultAgents",
|
|
50
|
+
"windowTitle",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
VERSION_TIMEOUT_SECONDS = 2
|
|
54
|
+
MAX_VERSION_TEXT = 200
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_settings_paths(working_directory: str) -> list[tuple[str, Path]]:
|
|
58
|
+
"""Return (scope, path) in precedence order (lowest to highest)."""
|
|
59
|
+
return [
|
|
60
|
+
("user", Path.home() / ".letta" / "settings.json"),
|
|
61
|
+
("project", Path(working_directory) / ".letta" / "settings.json"),
|
|
62
|
+
("local", Path(working_directory) / ".letta" / "settings.local.json"),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def load_settings(path: Path) -> dict[str, Any]:
|
|
67
|
+
if not path.exists():
|
|
68
|
+
return {}
|
|
69
|
+
try:
|
|
70
|
+
with open(path) as f:
|
|
71
|
+
parsed = json.load(f)
|
|
72
|
+
return parsed if isinstance(parsed, dict) else {}
|
|
73
|
+
except (json.JSONDecodeError, OSError):
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def effective_setting(all_settings: list[tuple[str, dict[str, Any]]], key: str) -> Any:
|
|
78
|
+
value = None
|
|
79
|
+
for _, settings in all_settings:
|
|
80
|
+
if key in settings:
|
|
81
|
+
value = settings[key]
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def effective_env_value(
|
|
86
|
+
all_settings: list[tuple[str, dict[str, Any]]], key: str
|
|
87
|
+
) -> str | None:
|
|
88
|
+
value = None
|
|
89
|
+
for _, settings in all_settings:
|
|
90
|
+
env = settings.get("env")
|
|
91
|
+
if isinstance(env, dict) and isinstance(env.get(key), str) and env.get(key):
|
|
92
|
+
value = env[key]
|
|
93
|
+
return value
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def env_or_settings(
|
|
97
|
+
all_settings: list[tuple[str, dict[str, Any]]], key: str
|
|
98
|
+
) -> str | None:
|
|
99
|
+
return os.environ.get(key) or effective_env_value(all_settings, key)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def first_line(text: str) -> str:
|
|
103
|
+
line = text.strip().splitlines()[0] if text.strip() else ""
|
|
104
|
+
return line[:MAX_VERSION_TEXT]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def letta_version(letta_binary: str | None) -> str:
|
|
108
|
+
if not letta_binary:
|
|
109
|
+
return "<not found>"
|
|
110
|
+
try:
|
|
111
|
+
result = subprocess.run(
|
|
112
|
+
[letta_binary, "--version"],
|
|
113
|
+
capture_output=True,
|
|
114
|
+
text=True,
|
|
115
|
+
timeout=VERSION_TIMEOUT_SECONDS,
|
|
116
|
+
check=False,
|
|
117
|
+
)
|
|
118
|
+
except subprocess.TimeoutExpired:
|
|
119
|
+
return f"<timeout after {VERSION_TIMEOUT_SECONDS}s>"
|
|
120
|
+
except OSError as exc:
|
|
121
|
+
return f"<failed: {exc.__class__.__name__}>"
|
|
122
|
+
|
|
123
|
+
rendered = first_line(result.stdout) or first_line(result.stderr)
|
|
124
|
+
if result.returncode == 0:
|
|
125
|
+
return rendered or "<no output>"
|
|
126
|
+
if rendered:
|
|
127
|
+
return f"<failed exit {result.returncode}: {rendered}>"
|
|
128
|
+
return f"<failed exit {result.returncode}>"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def normalize_saved_backend(value: Any) -> str | None:
|
|
132
|
+
return value if value in ("api", "local") else None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def format_runtime(
|
|
136
|
+
working_directory: str,
|
|
137
|
+
all_settings: list[tuple[str, dict[str, Any]]],
|
|
138
|
+
as_json: bool,
|
|
139
|
+
) -> dict[str, Any] | None:
|
|
140
|
+
"""Show process/runtime facts without printing secret values."""
|
|
141
|
+
letta_binary = shutil.which("letta")
|
|
142
|
+
runtime = {
|
|
143
|
+
"cwd": str(Path(working_directory).resolve()),
|
|
144
|
+
"letta_binary": letta_binary,
|
|
145
|
+
"letta_version": letta_version(letta_binary),
|
|
146
|
+
"saved_backend": normalize_saved_backend(
|
|
147
|
+
effective_setting(all_settings, "preferredBackendMode")
|
|
148
|
+
),
|
|
149
|
+
"agent_id": os.environ.get("AGENT_ID") or None,
|
|
150
|
+
"conversation_id": os.environ.get("CONVERSATION_ID") or None,
|
|
151
|
+
"base_url": env_or_settings(all_settings, "LETTA_BASE_URL"),
|
|
152
|
+
"settings_base_url": env_or_settings(all_settings, "LETTA_SETTINGS_BASE_URL"),
|
|
153
|
+
"backend_env": env_or_settings(all_settings, "LETTA_BACKEND"),
|
|
154
|
+
"api_key_present": bool(env_or_settings(all_settings, "LETTA_API_KEY")),
|
|
155
|
+
"memory_dir": os.environ.get("MEMORY_DIR") or None,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if as_json:
|
|
159
|
+
return runtime
|
|
160
|
+
|
|
161
|
+
print("=" * 60)
|
|
162
|
+
print("RUNTIME")
|
|
163
|
+
print("=" * 60)
|
|
164
|
+
for key, value in runtime.items():
|
|
165
|
+
print(f" {key}: {render_safe_value(value)}")
|
|
166
|
+
print()
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def format_permissions(
|
|
171
|
+
all_settings: list[tuple[str, dict[str, Any]]], as_json: bool
|
|
172
|
+
) -> dict[str, list[dict[str, str]]] | None:
|
|
173
|
+
"""Collect permissions from all scopes with sources."""
|
|
174
|
+
rule_types = ["allow", "deny", "ask", "alwaysAsk"]
|
|
175
|
+
rules: dict[str, list[tuple[str, str]]] = {rule_type: [] for rule_type in rule_types}
|
|
176
|
+
for scope, settings in all_settings:
|
|
177
|
+
perms = settings.get("permissions", {})
|
|
178
|
+
if not isinstance(perms, dict):
|
|
179
|
+
continue
|
|
180
|
+
for rule_type in rule_types:
|
|
181
|
+
values = perms.get(rule_type, [])
|
|
182
|
+
if not isinstance(values, list):
|
|
183
|
+
continue
|
|
184
|
+
for rule in values:
|
|
185
|
+
if isinstance(rule, str):
|
|
186
|
+
rules[rule_type].append((rule, scope))
|
|
187
|
+
|
|
188
|
+
if as_json:
|
|
189
|
+
return {
|
|
190
|
+
rule_type: [{"rule": rule, "scope": scope} for rule, scope in entries]
|
|
191
|
+
for rule_type, entries in rules.items()
|
|
192
|
+
if entries
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
total = sum(len(v) for v in rules.values())
|
|
196
|
+
print("=" * 60)
|
|
197
|
+
print(f"PERMISSIONS ({total} rules)")
|
|
198
|
+
print("=" * 60)
|
|
199
|
+
if total == 0:
|
|
200
|
+
print(" (none)")
|
|
201
|
+
else:
|
|
202
|
+
for rule_type in rule_types:
|
|
203
|
+
if rules[rule_type]:
|
|
204
|
+
print(f"\n {rule_type.upper()}:")
|
|
205
|
+
for rule, scope in rules[rule_type]:
|
|
206
|
+
print(f" [{scope:7}] {rule}")
|
|
207
|
+
print()
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def render_safe_value(value: Any) -> str:
|
|
212
|
+
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
213
|
+
return json.dumps(value)
|
|
214
|
+
return json.dumps(value, sort_keys=True)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def format_settings(
|
|
218
|
+
all_settings: list[tuple[str, dict[str, Any]]], as_json: bool
|
|
219
|
+
) -> list[dict[str, Any]] | None:
|
|
220
|
+
"""Collect selected non-secret settings from all scopes."""
|
|
221
|
+
rows: list[dict[str, Any]] = []
|
|
222
|
+
for scope, settings in all_settings:
|
|
223
|
+
for key in TOP_LEVEL_KEYS:
|
|
224
|
+
if key in settings:
|
|
225
|
+
rows.append({"scope": scope, "key": key, "value": settings[key]})
|
|
226
|
+
env = settings.get("env")
|
|
227
|
+
if isinstance(env, dict) and env:
|
|
228
|
+
rows.append({"scope": scope, "key": "env_keys", "value": sorted(env.keys())})
|
|
229
|
+
experiments = settings.get("experiments")
|
|
230
|
+
if isinstance(experiments, dict) and experiments:
|
|
231
|
+
rows.append({"scope": scope, "key": "experiments", "value": experiments})
|
|
232
|
+
reflection_by_agent = settings.get("reflectionSettingsByAgent")
|
|
233
|
+
if isinstance(reflection_by_agent, dict) and reflection_by_agent:
|
|
234
|
+
rows.append(
|
|
235
|
+
{
|
|
236
|
+
"scope": scope,
|
|
237
|
+
"key": "reflectionSettingsByAgent",
|
|
238
|
+
"value": reflection_by_agent,
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if as_json:
|
|
243
|
+
return rows
|
|
244
|
+
|
|
245
|
+
print("=" * 60)
|
|
246
|
+
print(f"SELECTED SETTINGS ({len(rows)} entries)")
|
|
247
|
+
print("=" * 60)
|
|
248
|
+
if not rows:
|
|
249
|
+
print(" (none)")
|
|
250
|
+
else:
|
|
251
|
+
for row in rows:
|
|
252
|
+
print(
|
|
253
|
+
f" [{row['scope']:7}] {row['key']}: {render_safe_value(row['value'])}"
|
|
254
|
+
)
|
|
255
|
+
print()
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def format_agents(
|
|
260
|
+
all_settings: list[tuple[str, dict[str, Any]]], as_json: bool
|
|
261
|
+
) -> list[dict[str, Any]] | None:
|
|
262
|
+
"""Collect per-agent settings from all scopes."""
|
|
263
|
+
agents: list[dict[str, Any]] = []
|
|
264
|
+
for scope, settings in all_settings:
|
|
265
|
+
raw_agents = settings.get("agents", [])
|
|
266
|
+
if not isinstance(raw_agents, list):
|
|
267
|
+
continue
|
|
268
|
+
for agent in raw_agents:
|
|
269
|
+
if isinstance(agent, dict):
|
|
270
|
+
safe_agent = {"scope": scope}
|
|
271
|
+
for key in AGENT_SETTING_KEYS:
|
|
272
|
+
if key in agent:
|
|
273
|
+
safe_agent[key] = agent[key]
|
|
274
|
+
agents.append(safe_agent)
|
|
275
|
+
|
|
276
|
+
if as_json:
|
|
277
|
+
return agents
|
|
278
|
+
|
|
279
|
+
print("=" * 60)
|
|
280
|
+
print(f"PER-AGENT SETTINGS ({len(agents)} entries)")
|
|
281
|
+
print("=" * 60)
|
|
282
|
+
if not agents:
|
|
283
|
+
print(" (none)")
|
|
284
|
+
else:
|
|
285
|
+
for agent in agents:
|
|
286
|
+
scope = agent.get("scope", "?")
|
|
287
|
+
agent_id = agent.get("agentId", "?")
|
|
288
|
+
print(f"\n [{scope:7}] {agent_id}")
|
|
289
|
+
for key in AGENT_SETTING_KEYS[1:]:
|
|
290
|
+
if key in agent:
|
|
291
|
+
print(f" {key}: {render_safe_value(agent[key])}")
|
|
292
|
+
print()
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def format_settings_files(
|
|
297
|
+
working_directory: str, as_json: bool
|
|
298
|
+
) -> list[dict[str, Any]] | None:
|
|
299
|
+
rows = [
|
|
300
|
+
{"scope": scope, "path": str(path), "exists": path.exists()}
|
|
301
|
+
for scope, path in get_settings_paths(working_directory)
|
|
302
|
+
]
|
|
303
|
+
if as_json:
|
|
304
|
+
return rows
|
|
305
|
+
|
|
306
|
+
print("=" * 60)
|
|
307
|
+
print("SETTINGS FILES")
|
|
308
|
+
print("=" * 60)
|
|
309
|
+
for row in rows:
|
|
310
|
+
exists = "yes" if row["exists"] else "no"
|
|
311
|
+
print(f" {exists:3} [{row['scope']:7}] {row['path']}")
|
|
312
|
+
print()
|
|
313
|
+
return None
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def main():
|
|
317
|
+
parser = argparse.ArgumentParser(
|
|
318
|
+
description="Show Letta Code self-configuration without dumping secret values"
|
|
319
|
+
)
|
|
320
|
+
parser.add_argument(
|
|
321
|
+
"--cwd",
|
|
322
|
+
default=os.getcwd(),
|
|
323
|
+
help="Working directory for project/local scope (default: cwd)",
|
|
324
|
+
)
|
|
325
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
326
|
+
parser.add_argument(
|
|
327
|
+
"--section",
|
|
328
|
+
choices=["runtime", "files", "permissions", "settings", "agents", "all"],
|
|
329
|
+
default="all",
|
|
330
|
+
help="Which section to show (default: all)",
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
args = parser.parse_args()
|
|
334
|
+
|
|
335
|
+
all_settings = [
|
|
336
|
+
(scope, load_settings(path)) for scope, path in get_settings_paths(args.cwd)
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
if args.json:
|
|
340
|
+
output: dict[str, Any] = {}
|
|
341
|
+
if args.section in ("runtime", "all"):
|
|
342
|
+
output["runtime"] = format_runtime(args.cwd, all_settings, as_json=True)
|
|
343
|
+
if args.section in ("files", "all"):
|
|
344
|
+
output["files"] = format_settings_files(args.cwd, as_json=True)
|
|
345
|
+
if args.section in ("permissions", "all"):
|
|
346
|
+
output["permissions"] = format_permissions(all_settings, as_json=True)
|
|
347
|
+
if args.section in ("settings", "all"):
|
|
348
|
+
output["settings"] = format_settings(all_settings, as_json=True)
|
|
349
|
+
if args.section in ("agents", "all"):
|
|
350
|
+
output["agents"] = format_agents(all_settings, as_json=True)
|
|
351
|
+
print(json.dumps(output, indent=2, sort_keys=True))
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
print("\nLetta Code Self-Configuration")
|
|
355
|
+
print(f"Working directory: {args.cwd}\n")
|
|
356
|
+
if args.section in ("runtime", "all"):
|
|
357
|
+
format_runtime(args.cwd, all_settings, as_json=False)
|
|
358
|
+
if args.section in ("files", "all"):
|
|
359
|
+
format_settings_files(args.cwd, as_json=False)
|
|
360
|
+
if args.section in ("permissions", "all"):
|
|
361
|
+
format_permissions(all_settings, as_json=False)
|
|
362
|
+
if args.section in ("settings", "all"):
|
|
363
|
+
format_settings(all_settings, as_json=False)
|
|
364
|
+
if args.section in ("agents", "all"):
|
|
365
|
+
format_agents(all_settings, as_json=False)
|
|
366
|
+
print("Precedence (highest to lowest): local > project > user\n")
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
if __name__ == "__main__":
|
|
370
|
+
main()
|