@letta-ai/letta-code 0.27.9 → 0.27.10
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 +2793 -1505
- package/package.json +1 -1
- package/skills/creating-mods/SKILL.md +2 -2
- package/skills/creating-mods/references/events.md +6 -4
- package/skills/creating-mods/references/tools.md +2 -1
- package/skills/creating-mods/references/ui.md +2 -0
- package/skills/customizing-statusline/references/api.md +1 -4
- package/skills/customizing-statusline/references/examples.md +2 -4
- package/skills/customizing-statusline/references/migration.md +1 -2
- package/skills/image-generation/SKILL.md +10 -6
- package/vendor/ink-text-input/build/index.js +5 -3
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ Use this skill to create or update trusted global Letta Code mods in:
|
|
|
11
11
|
~/.letta/mods/
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Mods are trusted local code for Letta Code. They add small composable capabilities through mod APIs, not by importing app internals. Prefer scoped handles (`ctx.conversation`, `ctx.cwd`, `ctx.agent
|
|
14
|
+
Mods are trusted local code for Letta Code. They add small composable capabilities through mod APIs, not by importing app internals. Dynamic agent/conversation/workspace/model state is passed as `ctx` to tool, command, event, permission, status, and statusline callbacks; do not read mutable global context for model-callable behavior. Prefer scoped handles (`ctx.conversation`, `ctx.cwd`, `ctx.agent`) and guard optional UI with `letta.capabilities`.
|
|
15
15
|
|
|
16
16
|
Capabilities vary by surface. TUI/headless may load tools, commands, events, UI, and providers; the desktop listener loads provider-only mods for local provider discovery. Always guard optional capabilities.
|
|
17
17
|
|
|
@@ -132,7 +132,7 @@ Before finishing, verify:
|
|
|
132
132
|
- Timers, intervals, event registrations, and panels are cleaned up in a disposer.
|
|
133
133
|
- Busy commands return `{ type: "handled" }` quickly and avoid main-conversation sends.
|
|
134
134
|
- Conversation work uses `ctx.conversation` or forked handles, not app internals.
|
|
135
|
-
- Local shell/file work is scoped to `ctx.cwd`
|
|
135
|
+
- Local shell/file work is scoped to `ctx.cwd` unless intentionally global.
|
|
136
136
|
- Errors shown to the user are short and actionable.
|
|
137
137
|
|
|
138
138
|
## References
|
|
@@ -30,7 +30,7 @@ export default function activate(letta) {
|
|
|
30
30
|
|
|
31
31
|
return letta.events.on("conversation_open", (event, ctx) => {
|
|
32
32
|
console.log(`conversation ${event.reason}: ${event.agentName ?? event.agentId}`);
|
|
33
|
-
console.log(`cwd: ${ctx.
|
|
33
|
+
console.log(`cwd: ${ctx.cwd}`);
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
```
|
|
@@ -180,13 +180,15 @@ Handlers also receive:
|
|
|
180
180
|
getHistory(options?): Promise<Message[]>;
|
|
181
181
|
sendMessageStream(messages, options?): Promise<AsyncIterable<chunk>>;
|
|
182
182
|
};
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
agent: ModContext["agent"];
|
|
184
|
+
cwd: string;
|
|
185
|
+
model: ModContext["model"];
|
|
186
|
+
permissionMode: string | null;
|
|
185
187
|
signal: AbortSignal;
|
|
186
188
|
}
|
|
187
189
|
```
|
|
188
190
|
|
|
189
|
-
`ctx.conversation`
|
|
191
|
+
`ctx` and `ctx.conversation` are bound when the event is dispatched. Use direct fields such as `ctx.agent`, `ctx.cwd`, `ctx.model`, and `ctx.permissionMode` for scoped state. If an event needs background model work, prefer `ctx.conversation.fork()` and send to the fork. Do not send to the active conversation from `turn_start`; that event is already in the path of sending a turn.
|
|
190
192
|
|
|
191
193
|
Respect `ctx.signal` for long-running async work. It is aborted on `/reload` and app shutdown.
|
|
192
194
|
|
|
@@ -19,7 +19,8 @@ For tools that are part of a larger mod with commands, UI, local state, or event
|
|
|
19
19
|
- `requiresApproval: false` only for read-only, low-risk local introspection.
|
|
20
20
|
- `approvalPolicy: "alwaysAsk"` only for tools that must pause for human approval even in unrestricted/yolo mode.
|
|
21
21
|
- `parallelSafe: true` only for read-only tools with no shared mutation or long-lived exclusive resource.
|
|
22
|
-
- Use `ctx.cwd`
|
|
22
|
+
- Use `ctx.cwd` as the invocation workspace.
|
|
23
|
+
- Use the dynamic context passed to `run(ctx)` (`ctx.agent`, `ctx.model`, `ctx.toolset`, `ctx.permissionMode`) instead of reading global app context.
|
|
23
24
|
- Use `await ctx.conversation.getHistory()` when a tool needs recent conversation context. It returns the most recent messages in chronological order by default.
|
|
24
25
|
- Respect `ctx.signal` for long-running work when practical.
|
|
25
26
|
- Tools should return information for the model to use; they should not start hidden model runs.
|
|
@@ -18,6 +18,8 @@ letta.capabilities.ui.customStatuslineRenderer
|
|
|
18
18
|
|
|
19
19
|
## Panels
|
|
20
20
|
|
|
21
|
+
Panels are app/TUI-global today. Desktop/listener disables panel UI; future scoped panels need an explicit design instead of sharing this global registry across panes/conversations.
|
|
22
|
+
|
|
21
23
|
```ts
|
|
22
24
|
if (letta.capabilities.ui.panels) {
|
|
23
25
|
const panel = letta.ui.openPanel({
|
|
@@ -28,8 +28,6 @@ export default function activate(letta) {
|
|
|
28
28
|
## API
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
|
-
letta.getContext(): StatuslineRenderContext
|
|
32
|
-
|
|
33
31
|
letta.capabilities.ui.statusValues: boolean
|
|
34
32
|
letta.capabilities.ui.customStatuslineRenderer: boolean
|
|
35
33
|
|
|
@@ -72,9 +70,8 @@ export default function activate(letta) {
|
|
|
72
70
|
|
|
73
71
|
const update = async () => {
|
|
74
72
|
try {
|
|
75
|
-
const context = letta.getContext();
|
|
76
73
|
const { stdout } = await execFileAsync("git", ["branch", "--show-current"], {
|
|
77
|
-
cwd:
|
|
74
|
+
cwd: process.cwd(),
|
|
78
75
|
});
|
|
79
76
|
if (letta.capabilities.ui.statusValues) {
|
|
80
77
|
letta.ui.setStatus("branch", stdout.trim());
|
|
@@ -28,9 +28,8 @@ export default function activate(letta) {
|
|
|
28
28
|
|
|
29
29
|
const update = async () => {
|
|
30
30
|
try {
|
|
31
|
-
const context = letta.getContext();
|
|
32
31
|
const { stdout } = await execFileAsync("git", ["branch", "--show-current"], {
|
|
33
|
-
cwd:
|
|
32
|
+
cwd: process.cwd(),
|
|
34
33
|
});
|
|
35
34
|
letta.ui.setStatus("branch", stdout.trim());
|
|
36
35
|
} catch {
|
|
@@ -85,11 +84,10 @@ export default function activate(letta) {
|
|
|
85
84
|
|
|
86
85
|
const update = async () => {
|
|
87
86
|
try {
|
|
88
|
-
const context = letta.getContext();
|
|
89
87
|
const { stdout } = await execFileAsync(
|
|
90
88
|
"gh",
|
|
91
89
|
["pr", "view", "--json", "number,title", "--jq", "\"#\\(.number) \\(.title)\""],
|
|
92
|
-
{ cwd:
|
|
90
|
+
{ cwd: process.cwd() },
|
|
93
91
|
);
|
|
94
92
|
const pr = stdout.trim();
|
|
95
93
|
pr ? letta.ui.setStatus("pr", pr) : letta.ui.clearStatus("pr");
|
|
@@ -60,9 +60,8 @@ import { promisify } from "node:util";
|
|
|
60
60
|
const execFileAsync = promisify(execFile);
|
|
61
61
|
|
|
62
62
|
const update = async () => {
|
|
63
|
-
const context = letta.getContext();
|
|
64
63
|
const { stdout } = await execFileAsync("git", ["branch", "--show-current"], {
|
|
65
|
-
cwd:
|
|
64
|
+
cwd: process.cwd(),
|
|
66
65
|
});
|
|
67
66
|
letta.ui.setStatus("branch", stdout.trim());
|
|
68
67
|
};
|
|
@@ -14,7 +14,9 @@ save either form to a local image file before replying.
|
|
|
14
14
|
Generate the image, save it locally, then show it inline:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
17
|
+
base_url="${LETTA_BASE_URL%/}"
|
|
18
|
+
|
|
19
|
+
curl -sS -X POST "$base_url/v1/images/generations" \
|
|
18
20
|
-H "Authorization: Bearer $LETTA_API_KEY" \
|
|
19
21
|
-H "Content-Type: application/json" \
|
|
20
22
|
-d '{"provider":"gemini","prompt":"a friendly robot mascot waving, flat vector logo, mint green background","n":1}' \
|
|
@@ -39,11 +41,13 @@ print("saved robot-mascot.png; credits:", response["billing"]["credits_charged"]
|
|
|
39
41
|
PY
|
|
40
42
|
```
|
|
41
43
|
|
|
42
|
-
In Bash tools launched by Letta Code, the
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
In Bash tools launched by Letta Code, use the runtime-provided
|
|
45
|
+
`LETTA_BASE_URL` and `LETTA_API_KEY` together for Letta API calls. Build URLs
|
|
46
|
+
relative to `${LETTA_BASE_URL%/}` and send `Authorization: Bearer $LETTA_API_KEY`.
|
|
47
|
+
Do not hardcode `https://api.letta.com`: Desktop and remote runtimes may provide
|
|
48
|
+
a proxy base URL, and the credential may only be valid through that URL. If
|
|
49
|
+
either variable is missing, the user needs to authenticate with Letta Cloud (or
|
|
50
|
+
provide a Letta API key); do **not** ask for an OpenAI/Gemini provider key. This
|
|
47
51
|
endpoint also does not use `/connect` BYOK providers — the only `provider` values
|
|
48
52
|
supported here are `flux`, `gemini`, and `openai`.
|
|
49
53
|
|
|
@@ -13,7 +13,7 @@ const CURSOR_SENTINEL = '\u{10FFFD}';
|
|
|
13
13
|
* Determines if the input should be treated as a control sequence (not inserted as text).
|
|
14
14
|
* This centralizes escape sequence filtering to prevent garbage characters from being inserted.
|
|
15
15
|
*/
|
|
16
|
-
function isControlSequence(input, key) {
|
|
16
|
+
export function isControlSequence(input, key) {
|
|
17
17
|
// Pasted content is handled separately
|
|
18
18
|
if (key?.isPasted) return true;
|
|
19
19
|
|
|
@@ -31,8 +31,10 @@ function isControlSequence(input, key) {
|
|
|
31
31
|
// The handled ones are: ctrl+a, ctrl+e, ctrl+k, ctrl+u, ctrl+y (see useInput below)
|
|
32
32
|
if (key.ctrl && input && /^[a-z]$/i.test(input) && !['a', 'e', 'k', 'u', 'y'].includes(input.toLowerCase())) return true;
|
|
33
33
|
|
|
34
|
-
// Option
|
|
35
|
-
|
|
34
|
+
// Meta/Option shortcuts are handled by parent components and should not
|
|
35
|
+
// also mutate focused text inputs. This covers terminal-specific encodings
|
|
36
|
+
// like Ghostty CSI-u (input='p', meta=true) and WezTerm ESC+p.
|
|
37
|
+
if (key.meta && input) return true;
|
|
36
38
|
|
|
37
39
|
// Filter specific escape sequences that would insert garbage, but allow plain ESC through
|
|
38
40
|
// CSI sequences (ESC[...), Option+Delete (ESC + DEL), and other multi-char escape sequences
|