@letta-ai/letta-code 0.27.17 → 0.27.18
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/README.md +7 -0
- package/dist/types/types/protocol.d.ts +2 -1
- package/dist/types/types/protocol.d.ts.map +1 -1
- package/dist/types/types/protocol_v2.d.ts +16 -2
- package/dist/types/types/protocol_v2.d.ts.map +1 -1
- package/letta.js +128002 -127874
- package/package.json +1 -1
- package/skills/creating-mods/SKILL.md +6 -6
- package/skills/creating-mods/references/architecture.md +15 -4
- package/skills/creating-mods/references/commands.md +2 -1
- package/skills/creating-mods/references/events.md +126 -9
- package/skills/creating-mods/references/plan-mode.md +1 -1
- package/skills/creating-mods/references/ui.md +40 -25
- package/skills/customizing-statusline/SKILL.md +13 -14
- package/skills/customizing-statusline/references/api.md +74 -86
- package/skills/customizing-statusline/references/examples.md +79 -51
- package/skills/customizing-statusline/references/migration.md +38 -19
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: creating-mods
|
|
3
|
-
description: Creates and edits trusted local Letta Code mods, including tools, slash commands, local-only model providers, lifecycle/turn events, scoped conversation helpers, panels,
|
|
3
|
+
description: Creates and edits trusted local Letta Code mods, including tools, slash commands, local-only model providers, lifecycle/turn events, scoped conversation helpers, panels, and capability-gated behavior. Use when asked to make a mod, add an agent-callable tool, add a slash command, add a local provider/model adapter, transform turns, react to app events, or add lightweight mod UI outside the dedicated /statusline flow.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Creating Mods
|
|
7
7
|
|
|
8
|
-
Use this skill to create or update trusted Letta Code mod files. Mods are trusted local code that 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
|
|
8
|
+
Use this skill to create or update trusted Letta Code mod files. Mods are trusted local code that 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, and permission callbacks (panels receive live `agent`/`model` in their render context); 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`.
|
|
9
9
|
|
|
10
10
|
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.
|
|
11
11
|
|
|
@@ -86,11 +86,11 @@ letta.capabilities.commands
|
|
|
86
86
|
letta.capabilities.events.lifecycle
|
|
87
87
|
letta.capabilities.events.tools
|
|
88
88
|
letta.capabilities.events.turns
|
|
89
|
+
letta.capabilities.events.compact
|
|
90
|
+
letta.capabilities.events.llm
|
|
89
91
|
letta.capabilities.permissions
|
|
90
92
|
letta.capabilities.providers
|
|
91
93
|
letta.capabilities.ui.panels
|
|
92
|
-
letta.capabilities.ui.statusValues
|
|
93
|
-
letta.capabilities.ui.customStatuslineRenderer
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
## Scoped API model
|
|
@@ -135,7 +135,7 @@ Before finishing, verify:
|
|
|
135
135
|
- Command/tool IDs are valid; command overrides of built-ins are intentional, and tool IDs do not collide with built-ins.
|
|
136
136
|
- Tool descriptions explain when the model should call them.
|
|
137
137
|
- JSON schemas are object schemas with useful descriptions.
|
|
138
|
-
- Optional UI/event
|
|
138
|
+
- Optional UI/event APIs are capability-guarded.
|
|
139
139
|
- Provider mods are capability-guarded and clearly documented as local-agent only.
|
|
140
140
|
- Timers, intervals, event registrations, and panels are cleaned up in a disposer.
|
|
141
141
|
- Busy commands return `{ type: "handled" }` quickly and avoid main-conversation sends.
|
|
@@ -152,7 +152,7 @@ Before finishing, verify:
|
|
|
152
152
|
| `references/providers.md` | Adding a custom model/API provider for local agents |
|
|
153
153
|
| `references/events.md` | Reacting to lifecycle/tool/turn events or transforming turns/tools |
|
|
154
154
|
| `references/permissions.md` | Enforcing dynamic tool allow/ask/deny policy before approval/execution |
|
|
155
|
-
| `references/ui.md` | Panels
|
|
155
|
+
| `references/ui.md` | Panels (including order-0 statusline) or `ui.panels` capability guards are involved |
|
|
156
156
|
| `references/plan-mode.md` | Recreating plan mode with commands, tools, events, permissions, and local state |
|
|
157
157
|
| `references/analysis-mode.md` | Phrase-triggered diagnostic mode with turn reminders (simpler than plan-mode) |
|
|
158
158
|
| `references/architecture.md` | Multiple capabilities, local state, cleanup, background model work, or non-trivial composition |
|
|
@@ -78,14 +78,22 @@ const stream = await forked.sendMessageStream([
|
|
|
78
78
|
|
|
79
79
|
Do not call `ctx.conversation.sendMessageStream()` on the active conversation from a busy command; direct sends can conflict with the active run.
|
|
80
80
|
|
|
81
|
-
### Event +
|
|
81
|
+
### Event + panel
|
|
82
82
|
|
|
83
|
-
Use lifecycle events to maintain small
|
|
83
|
+
Use lifecycle events to maintain a small panel such as active conversation state. Guard both event and panel capabilities, and re-render with `panel.update()`.
|
|
84
84
|
|
|
85
85
|
```ts
|
|
86
|
-
if (letta.capabilities.events.lifecycle && letta.capabilities.ui.
|
|
86
|
+
if (letta.capabilities.events.lifecycle && letta.capabilities.ui.panels) {
|
|
87
|
+
let conversation = "";
|
|
88
|
+
const panel = letta.ui.openPanel({
|
|
89
|
+
id: "conversation",
|
|
90
|
+
order: 100,
|
|
91
|
+
render: ({ width, row }) => row("conversation", conversation, width),
|
|
92
|
+
});
|
|
93
|
+
disposers.push(() => panel.close());
|
|
87
94
|
disposers.push(letta.events.on("conversation_open", (event) => {
|
|
88
|
-
|
|
95
|
+
conversation = event.reason;
|
|
96
|
+
panel.update();
|
|
89
97
|
}));
|
|
90
98
|
}
|
|
91
99
|
```
|
|
@@ -126,10 +134,13 @@ ctx.conversation.id // string | null
|
|
|
126
134
|
ctx.conversation.getHistory(opts) // recent messages
|
|
127
135
|
ctx.conversation.fork(opts) // returns a scoped handle
|
|
128
136
|
ctx.conversation.sendMessageStream(messages, opts)
|
|
137
|
+
ctx.conversation.updateLlmConfig(opts) // change model / reasoning effort / context window
|
|
129
138
|
```
|
|
130
139
|
|
|
131
140
|
A forked handle keeps the same agent/backend defaults and targets the forked conversation. Use forked handles for background model work. Use `getHistory({ limit, order, includeErrors })` when local logic needs conversation context.
|
|
132
141
|
|
|
142
|
+
`updateLlmConfig({ model?, reasoningEffort?, contextWindow?, scope? })` changes the model, reasoning effort, and/or context window, and works across local and Constellation backends. Only the fields you pass change; the rest are preserved, so `updateLlmConfig({ contextWindow })` adjusts just the context window without touching the model or reasoning effort. `scope` defaults to `"conversation"` (a conversation-scoped override that leaves the agent's default untouched); pass `scope: "agent"` to change the agent default. Changing reasoning effort without a model resolves the current model to rebuild provider-specific settings. The change takes effect on the next turn (the model is resolved per provider request).
|
|
143
|
+
|
|
133
144
|
Tools currently receive `ctx.conversation.getHistory()` but not fork/send helpers. If a tool needs model-side follow-up, return information for the model to act on instead of starting a hidden run from the tool.
|
|
134
145
|
|
|
135
146
|
## Error handling
|
|
@@ -96,9 +96,10 @@ export default function activate(letta) {
|
|
|
96
96
|
return { type: "output", output: `hello ${ctx.args || "there"}` };
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
const greeting = `hello ${ctx.args || "there"}`;
|
|
99
100
|
const panel = letta.ui.openPanel({
|
|
100
101
|
id: "hello-panel",
|
|
101
|
-
|
|
102
|
+
render: () => greeting,
|
|
102
103
|
});
|
|
103
104
|
setTimeout(() => panel.close(), 5_000);
|
|
104
105
|
return { type: "handled" };
|
|
@@ -20,6 +20,8 @@ This is the first slice of the hooks-v2 direction. The long-term goal is for typ
|
|
|
20
20
|
letta.capabilities.events.lifecycle
|
|
21
21
|
letta.capabilities.events.tools
|
|
22
22
|
letta.capabilities.events.turns
|
|
23
|
+
letta.capabilities.events.compact
|
|
24
|
+
letta.capabilities.events.llm
|
|
23
25
|
```
|
|
24
26
|
|
|
25
27
|
Guard events when writing portable mods:
|
|
@@ -55,9 +57,11 @@ letta.events.on("tool_start", (event, ctx) => {
|
|
|
55
57
|
});
|
|
56
58
|
```
|
|
57
59
|
|
|
58
|
-
Lifecycle, turn
|
|
60
|
+
Lifecycle, turn, tool, compaction, and llm events are wired today.
|
|
59
61
|
|
|
60
|
-
Lifecycle handlers are notification-only and should not return values. `turn_start` handlers can transform the outbound input for the next model turn. `tool_start` handlers can transform the tool arguments before execution.
|
|
62
|
+
Lifecycle handlers are notification-only and should not return values. `turn_start` handlers can transform the outbound input for the next model turn. `tool_start` handlers can transform the tool arguments before execution. Compaction and llm handlers are notification-only.
|
|
63
|
+
|
|
64
|
+
`compact_start`/`compact_end` and `llm_start`/`llm_end` only fire on the **local backend**, where compaction and provider requests run client-side. On the constellation backend that work happens server-side and these events do not fire, so guard with `letta.capabilities.events.compact` / `letta.capabilities.events.llm` for portable mods.
|
|
61
65
|
|
|
62
66
|
## Supported events
|
|
63
67
|
|
|
@@ -65,7 +69,12 @@ Lifecycle handlers are notification-only and should not return values. `turn_sta
|
|
|
65
69
|
"conversation_open"
|
|
66
70
|
"conversation_close"
|
|
67
71
|
"tool_start"
|
|
72
|
+
"tool_end"
|
|
68
73
|
"turn_start"
|
|
74
|
+
"compact_start"
|
|
75
|
+
"compact_end"
|
|
76
|
+
"llm_start"
|
|
77
|
+
"llm_end"
|
|
69
78
|
```
|
|
70
79
|
|
|
71
80
|
`conversation_open` event:
|
|
@@ -138,6 +147,43 @@ Handlers run in registration order. Later handlers see the current args after ea
|
|
|
138
147
|
|
|
139
148
|
`tool_start` is intentionally a trusted local mod point: it can rewrite commands, file paths, and other tool inputs before execution. Keep transforms focused and unsurprising.
|
|
140
149
|
|
|
150
|
+
`tool_end` event:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
{
|
|
154
|
+
agentId: string | null;
|
|
155
|
+
conversationId: string | null;
|
|
156
|
+
toolCallId: string | null;
|
|
157
|
+
toolName: string;
|
|
158
|
+
status: "success" | "error";
|
|
159
|
+
output: string;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`tool_end` fires immediately after a tool produces a result, before the agent sees it. Handlers can inspect the result, or return `{ result: { status, output } }` to replace it:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
letta.events.on("tool_end", (event) => {
|
|
167
|
+
if (event.toolName !== "Bash" || event.status !== "success") return;
|
|
168
|
+
return { result: { status: "success", output: redactSecrets(event.output) } };
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The first handler that returns a `result` wins; later handlers are shadowed. Only string results are surfaced — multimodal/image results pass through unchanged. `tool_end` is the trusted-local-mod equivalent of the `PostToolUse` / `PostToolUseFailure` hooks for observing and rewriting tool results.
|
|
173
|
+
|
|
174
|
+
A handler can also react to a specific tool completing by adjusting conversation state. For example, switch model and reasoning effort when entering and exiting plan mode (`tool_end` fires only after the tool succeeds, so a denied approval won't switch):
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
letta.events.on("tool_end", async (event, ctx) => {
|
|
178
|
+
if (event.status !== "success") return;
|
|
179
|
+
if (event.toolName === "enter_plan_mode") {
|
|
180
|
+
await ctx.conversation.updateLlmConfig({ model: "anthropic/claude-opus-4-8", reasoningEffort: "high" });
|
|
181
|
+
} else if (event.toolName === "exit_plan_mode") {
|
|
182
|
+
await ctx.conversation.updateLlmConfig({ model: "openai/gpt-5.5", reasoningEffort: "max" });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
141
187
|
`turn_start` fires before outbound turns that include a user message. In the TUI this includes normal submits and prompt-style command turns. In headless it includes one-shot prompts and bidirectional user turns.
|
|
142
188
|
|
|
143
189
|
Handlers can mutate `event.input` directly or return replacement input:
|
|
@@ -170,6 +216,67 @@ Handlers run in registration order. Later handlers see the current input after e
|
|
|
170
216
|
|
|
171
217
|
`turn_start` is intentionally a trusted local mod point: it can rewrite user messages, approval results, and ordering. Keep transforms focused and unsurprising.
|
|
172
218
|
|
|
219
|
+
`compact_start` event:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
{
|
|
223
|
+
agentId: string | null;
|
|
224
|
+
conversationId: string | null;
|
|
225
|
+
trigger: "manual" | "context_window_overflow" | "context_window_limit";
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`compact_start` fires before the local backend compacts a conversation, while the full message history is still in context. `trigger` distinguishes a manual `/compact` from the two automatic triggers (provider context-window overflow, and exceeding the configured context window). Use it to checkpoint state before eviction.
|
|
230
|
+
|
|
231
|
+
`compact_end` event:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
{
|
|
235
|
+
agentId: string | null;
|
|
236
|
+
conversationId: string | null;
|
|
237
|
+
trigger: "manual" | "context_window_overflow" | "context_window_limit";
|
|
238
|
+
messagesBefore: number;
|
|
239
|
+
messagesAfter: number;
|
|
240
|
+
contextTokensBefore: number;
|
|
241
|
+
contextTokensAfter: number;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`compact_end` fires after compaction completes, carrying the before/after message and context-token counts. Both events are notification-only; return values are ignored. A throwing handler is isolated and never breaks compaction.
|
|
246
|
+
|
|
247
|
+
`llm_start` event:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
{
|
|
251
|
+
agentId: string | null;
|
|
252
|
+
conversationId: string | null;
|
|
253
|
+
model: string;
|
|
254
|
+
messageCount: number;
|
|
255
|
+
contextWindow: number;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`llm_start` fires right before each provider request, with the model handle, the number of messages being sent, and the model's context window. It fires once per provider request, so a retry or an overflow-triggered re-request emits another `llm_start`.
|
|
260
|
+
|
|
261
|
+
`llm_end` event:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
{
|
|
265
|
+
agentId: string | null;
|
|
266
|
+
conversationId: string | null;
|
|
267
|
+
model: string;
|
|
268
|
+
stopReason: string;
|
|
269
|
+
usage: {
|
|
270
|
+
promptTokens: number;
|
|
271
|
+
completionTokens: number;
|
|
272
|
+
totalTokens: number;
|
|
273
|
+
};
|
|
274
|
+
durationMs: number;
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`llm_end` fires once a provider request produces a final message, carrying the stop reason, token usage, and wall-clock duration. A request that fails before producing a final message (e.g. a transport error that triggers a retry) emits no `llm_end`. Both events are notification-only; return values are ignored. A throwing handler is isolated and never breaks the provider request.
|
|
279
|
+
|
|
173
280
|
Handlers also receive:
|
|
174
281
|
|
|
175
282
|
```ts
|
|
@@ -199,12 +306,23 @@ export default function activate(letta) {
|
|
|
199
306
|
if (!letta.capabilities.events.lifecycle) return;
|
|
200
307
|
|
|
201
308
|
const disposers = [];
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
309
|
+
let conversation = "";
|
|
310
|
+
|
|
311
|
+
if (letta.capabilities.ui.panels) {
|
|
312
|
+
const panel = letta.ui.openPanel({
|
|
313
|
+
id: "conversation",
|
|
314
|
+
order: 100,
|
|
315
|
+
render: ({ width, row }) => row("conversation", conversation, width),
|
|
316
|
+
});
|
|
317
|
+
disposers.push(() => panel.close());
|
|
318
|
+
|
|
319
|
+
disposers.push(
|
|
320
|
+
letta.events.on("conversation_open", (event) => {
|
|
321
|
+
conversation = event.reason;
|
|
322
|
+
panel.update();
|
|
323
|
+
}),
|
|
324
|
+
);
|
|
325
|
+
}
|
|
208
326
|
|
|
209
327
|
disposers.push(
|
|
210
328
|
letta.events.on("conversation_close", (event) => {
|
|
@@ -214,7 +332,6 @@ export default function activate(letta) {
|
|
|
214
332
|
|
|
215
333
|
return () => {
|
|
216
334
|
for (const dispose of disposers.reverse()) dispose();
|
|
217
|
-
letta.ui.clearStatus("conversation");
|
|
218
335
|
};
|
|
219
336
|
}
|
|
220
337
|
```
|
|
@@ -42,7 +42,7 @@ Guard each registration with the matching capability:
|
|
|
42
42
|
- `events.turns`: append a focused plan-mode reminder while active
|
|
43
43
|
- `permissions`: block mutating tools except planning coordination tools and plan-file writes
|
|
44
44
|
|
|
45
|
-
Do not use panels for persistent mode state. Panels are transient UI and can be noisy/fragile for mode indicators. Do not
|
|
45
|
+
Do not use panels for persistent mode state. Panels are transient UI and can be noisy/fragile for mode indicators. Do not claim the order-0 statusline panel just to show plan mode; that slot is a single primary line, not an additive indicator. This example intentionally keeps visible mode state out of scope.
|
|
46
46
|
|
|
47
47
|
## State
|
|
48
48
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mod UI recipes
|
|
2
2
|
|
|
3
|
-
UI capabilities are optional. Always guard UI work with `letta.capabilities.ui
|
|
3
|
+
UI capabilities are optional. Always guard UI work with `letta.capabilities.ui.panels` when writing portable mods.
|
|
4
4
|
|
|
5
5
|
For UI that belongs to a larger command/event mod, also read `architecture.md` for cleanup and composition patterns.
|
|
6
6
|
|
|
@@ -8,13 +8,9 @@ For UI that belongs to a larger command/event mod, also read `architecture.md` f
|
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
letta.capabilities.ui.panels
|
|
11
|
-
letta.capabilities.ui.statusValues
|
|
12
|
-
letta.capabilities.ui.customStatuslineRenderer
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
- `panels`:
|
|
16
|
-
- `statusValues`: small named status data that renderers or future surfaces can display.
|
|
17
|
-
- `customStatuslineRenderer`: TUI-only custom bottom-row renderer. Use `customizing-statusline` for statusline work.
|
|
13
|
+
- `panels`: text blocks placed around the input bar (the only mod UI surface). Desktop/listener disables panel UI.
|
|
18
14
|
|
|
19
15
|
## Panels
|
|
20
16
|
|
|
@@ -22,50 +18,69 @@ Panels are app/TUI-global today. Desktop/listener disables panel UI; future scop
|
|
|
22
18
|
|
|
23
19
|
```ts
|
|
24
20
|
if (letta.capabilities.ui.panels) {
|
|
21
|
+
let status = "Working…";
|
|
25
22
|
const panel = letta.ui.openPanel({
|
|
26
23
|
id: "my-mod",
|
|
27
|
-
content: ["Working…"],
|
|
28
24
|
order: 100,
|
|
25
|
+
render: ({ width }) => status,
|
|
29
26
|
});
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
status = "Done";
|
|
29
|
+
panel.update(); // re-invokes render with the current width
|
|
32
30
|
setTimeout(() => panel.close(), 5_000);
|
|
33
31
|
}
|
|
34
32
|
```
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
`render` returns the panel body: a string or string array (one entry per line). The host owns the region — it clips each line to `width` and caps total height — so the mod owns layout: use `width`, `row(left, right, width)`, and `columns(parts, width)` to align. The host re-invokes `render` whenever you call `panel.update()` and on terminal resize. Keep `render` cheap and side-effect-free; for longer text use a command `output` instead.
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
### Placement by `order`
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
`order` is a signed coordinate around the input:
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
- `order > 0` — above the input, higher nearer the top (default `100`).
|
|
41
|
+
- `order === 0` — the primary line just below the input, overriding the built-in `agent · model`. This is the statusline slot; use `customizing-statusline` for that work.
|
|
42
|
+
- `order < 0` — stacks below the primary line, `-1` closest.
|
|
43
|
+
|
|
44
|
+
A panel whose `render` is empty (`""`, `[]`, or only blank lines) is hidden.
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
### Render context
|
|
49
47
|
|
|
50
48
|
```ts
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
};
|
|
49
|
+
render(ctx: {
|
|
50
|
+
width: number;
|
|
51
|
+
agent: { id, name };
|
|
52
|
+
model: { id, displayName, provider, reasoningEffort };
|
|
53
|
+
row(left, right, width): string;
|
|
54
|
+
columns(parts: string[], width): string;
|
|
55
|
+
chalk: ChalkInstance;
|
|
56
|
+
}): string | string[]
|
|
54
57
|
```
|
|
55
58
|
|
|
59
|
+
`row`/`columns` are ANSI-aware, so chalk-colored segments align correctly.
|
|
60
|
+
|
|
61
|
+
Close panels when they are transient, and close/replace long-lived panels from the activation disposer if reload should remove them.
|
|
62
|
+
|
|
56
63
|
## Timers and cleanup
|
|
57
64
|
|
|
58
65
|
```ts
|
|
59
66
|
export default function activate(letta) {
|
|
60
|
-
if (!letta.capabilities.ui.
|
|
67
|
+
if (!letta.capabilities.ui.panels) return;
|
|
68
|
+
|
|
69
|
+
let clock = new Date().toLocaleTimeString();
|
|
70
|
+
const panel = letta.ui.openPanel({
|
|
71
|
+
id: "clock",
|
|
72
|
+
order: 100,
|
|
73
|
+
render: ({ width, row }) => row("clock", clock, width),
|
|
74
|
+
});
|
|
61
75
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
const timer = setInterval(() => {
|
|
77
|
+
clock = new Date().toLocaleTimeString();
|
|
78
|
+
panel.update();
|
|
79
|
+
}, 30_000);
|
|
65
80
|
|
|
66
81
|
return () => {
|
|
67
82
|
clearInterval(timer);
|
|
68
|
-
|
|
83
|
+
panel.close();
|
|
69
84
|
};
|
|
70
85
|
}
|
|
71
86
|
```
|
|
@@ -11,28 +11,28 @@ Use this skill to create or update the global Letta Code statusline mod:
|
|
|
11
11
|
~/.letta/mods/statusline.tsx
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
The statusline is a
|
|
14
|
+
The statusline is a panel registered at `order: 0` — the primary line just below the input. It overrides the built-in `agent · model` line. Host UI can still temporarily preempt it for safety confirmations and transient hints.
|
|
15
15
|
|
|
16
16
|
## Statusline ownership model
|
|
17
17
|
|
|
18
18
|
```text
|
|
19
19
|
safety preemption
|
|
20
20
|
else transient host hint
|
|
21
|
-
else
|
|
21
|
+
else order-0 statusline panel
|
|
22
22
|
else built-in default statusline
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The order-0 panel owns the whole primary row. It renders text (not React) and owns its own layout via the `row`/`columns` helpers.
|
|
26
26
|
|
|
27
27
|
## Workflow
|
|
28
28
|
|
|
29
29
|
1. Check whether `~/.letta/mods/statusline.tsx` exists.
|
|
30
30
|
2. If it exists, read it before editing and preserve unrelated code.
|
|
31
|
-
3. If it does not exist,
|
|
31
|
+
3. If it does not exist, synthesize a focused starter for the user's request.
|
|
32
32
|
4. If the user asks to migrate, import a `.sh` file, or match a shell prompt, read `references/migration.md`.
|
|
33
33
|
5. If API details or concrete patterns are needed, read `references/api.md` and `references/examples.md`.
|
|
34
|
-
6. If the request combines statusline work with commands, tools, events, panels, or stateful mod behavior, also use `creating-mods` and its `references/architecture.md`.
|
|
35
|
-
7. Guard
|
|
34
|
+
6. If the request combines statusline work with commands, tools, events, other panels, or stateful mod behavior, also use `creating-mods` and its `references/architecture.md`.
|
|
35
|
+
7. Guard panel work with `letta.capabilities.ui.panels` when writing new files.
|
|
36
36
|
8. Edit `~/.letta/mods/statusline.tsx`.
|
|
37
37
|
9. Summarize the absolute file path changed and tell the user to run `/reload` unless the command can reload automatically.
|
|
38
38
|
|
|
@@ -42,7 +42,7 @@ If the user ran `/statusline` without a specific request:
|
|
|
42
42
|
|
|
43
43
|
- If a custom statusline file exists, summarize what it appears to do and ask what they want to change.
|
|
44
44
|
- If no custom file exists, explain that Letta is using the built-in default statusline and offer focused next steps:
|
|
45
|
-
1. start from
|
|
45
|
+
1. start from a simple `agent · model` statusline
|
|
46
46
|
2. add project info like git branch, worktree, or PR
|
|
47
47
|
3. migrate an existing legacy statusline `.sh` file
|
|
48
48
|
4. match shell prompt / PS1
|
|
@@ -56,16 +56,15 @@ Keep this conversational. Do not build a menu UI unless the product command expl
|
|
|
56
56
|
- Keep the mod single-file for MVP.
|
|
57
57
|
- Do not assume extra npm packages are available.
|
|
58
58
|
- Do not use relative multi-file imports yet.
|
|
59
|
-
- Keep
|
|
60
|
-
- Do async work in setup code, intervals, subscriptions,
|
|
61
|
-
-
|
|
62
|
-
- Guard
|
|
63
|
-
- Return a disposer that clears timers/subscriptions.
|
|
59
|
+
- Keep `render` synchronous and side-effect-free. Do not shell, fetch, await, or read files inside render.
|
|
60
|
+
- Do async work in setup code, intervals, or subscriptions, store the result in a closure variable, then call `panel.update()` to re-render.
|
|
61
|
+
- Register the statusline at `order: 0`. Compose left/right with `row(left, right, width)`; color with `chalk`.
|
|
62
|
+
- Guard panel work with `letta.capabilities.ui.panels` in new files.
|
|
63
|
+
- Return a disposer that clears timers/subscriptions and calls `panel.close()`.
|
|
64
64
|
- Preserve existing mod code unless the user asks to reset.
|
|
65
|
-
- Do not delete legacy command statusline files or settings unless the user explicitly asks.
|
|
66
65
|
|
|
67
66
|
## Useful references
|
|
68
67
|
|
|
69
|
-
- `references/api.md` -
|
|
68
|
+
- `references/api.md` - panel API, render context, lifecycle rules
|
|
70
69
|
- `references/examples.md` - common statusline patterns
|
|
71
70
|
- `references/migration.md` - legacy command `.sh` and PS1 migration
|