@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.
Files changed (27) hide show
  1. package/dist/agent-presets.js +268 -36
  2. package/dist/agent-presets.js.map +2 -2
  3. package/dist/channels-slack.js +2 -11
  4. package/dist/channels-slack.js.map +3 -3
  5. package/dist/types/agent/model-catalog.d.ts +0 -38
  6. package/dist/types/agent/model-catalog.d.ts.map +1 -1
  7. package/dist/types/channels/slack/progress.d.ts.map +1 -1
  8. package/dist/types/channels/types.d.ts +1 -0
  9. package/dist/types/channels/types.d.ts.map +1 -1
  10. package/letta.js +32445 -31820
  11. package/package.json +1 -1
  12. package/scripts/source-file-size-baseline.json +4 -4
  13. package/skills/creating-mods/references/events.md +4 -4
  14. package/skills/self-configuration/LICENSE +21 -0
  15. package/skills/self-configuration/SKILL.md +432 -0
  16. package/skills/self-configuration/references/api-patch-examples.md +161 -0
  17. package/skills/self-configuration/references/compaction-prompt-patterns.md +115 -0
  18. package/skills/self-configuration/references/model-settings.md +55 -0
  19. package/skills/self-configuration/scripts/add_permission.py +212 -0
  20. package/skills/self-configuration/scripts/show_config.py +370 -0
  21. package/skills/self-configuration/scripts/update-agent-settings.ts +384 -0
  22. package/skills/self-configuration/scripts/update-compaction-prompt.ts +227 -0
  23. package/skills/modifying-the-harness/SKILL.md +0 -263
  24. package/skills/modifying-the-harness/references/hooks.md +0 -261
  25. package/skills/modifying-the-harness/scripts/add_hook.py +0 -223
  26. package/skills/modifying-the-harness/scripts/add_permission.py +0 -136
  27. 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()