@letta-ai/letta-code 0.27.8 → 0.27.9
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 +2 -2
- package/dist/app-server-client.js +387 -0
- package/dist/app-server-client.js.map +10 -0
- package/dist/types/app-server-client.d.ts +99 -0
- package/dist/types/app-server-client.d.ts.map +1 -0
- package/dist/types/types/app-server-protocol.d.ts +3 -0
- package/dist/types/types/app-server-protocol.d.ts.map +1 -0
- package/dist/types/types/protocol.d.ts.map +1 -0
- package/dist/types/types/protocol_v2.d.ts +2277 -0
- package/dist/types/types/protocol_v2.d.ts.map +1 -0
- package/letta.js +21106 -18642
- package/package.json +11 -2
- package/scripts/check-test-coverage.cjs +1 -1
- package/scripts/run-unit-tests.cjs +1 -1
- package/skills/{creating-extensions → creating-mods}/SKILL.md +29 -29
- package/skills/{creating-extensions → creating-mods}/references/architecture.md +9 -9
- package/skills/{creating-extensions → creating-mods}/references/commands.md +10 -10
- package/skills/{creating-extensions → creating-mods}/references/events.md +10 -10
- package/skills/{creating-extensions → creating-mods}/references/permissions.md +3 -3
- package/skills/{creating-extensions → creating-mods}/references/plan-mode.md +10 -8
- package/skills/{creating-extensions → creating-mods}/references/providers.md +7 -7
- package/skills/{creating-extensions → creating-mods}/references/tools.md +20 -2
- package/skills/{creating-extensions → creating-mods}/references/ui.md +4 -4
- package/skills/customizing-commands/SKILL.md +18 -18
- package/skills/customizing-statusline/SKILL.md +11 -11
- package/skills/customizing-statusline/references/api.md +8 -8
- package/skills/customizing-statusline/references/examples.md +1 -1
- package/skills/customizing-statusline/references/migration.md +1 -1
- package/skills/editing-letta-code-desktop-preferences/SKILL.md +67 -0
- package/skills/image-generation/SKILL.md +22 -12
- package/skills/modifying-the-harness/SKILL.md +21 -2
- package/skills/modifying-the-harness/scripts/add_permission.py +2 -1
- package/skills/modifying-the-harness/scripts/show_config.py +4 -3
- package/dist/types/protocol.d.ts.map +0 -1
- /package/dist/types/{protocol.d.ts → types/protocol.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@letta-ai/letta-code",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.9",
|
|
4
4
|
"description": "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "bun@1.3.0",
|
|
@@ -14,11 +14,20 @@
|
|
|
14
14
|
"scripts",
|
|
15
15
|
"skills",
|
|
16
16
|
"vendor",
|
|
17
|
+
"dist/app-server-client.js",
|
|
18
|
+
"dist/app-server-client.js.map",
|
|
17
19
|
"dist/types",
|
|
18
20
|
"docs"
|
|
19
21
|
],
|
|
20
22
|
"exports": {
|
|
21
23
|
".": "./letta.js",
|
|
24
|
+
"./app-server-protocol": {
|
|
25
|
+
"types": "./dist/types/app-server-protocol.d.ts"
|
|
26
|
+
},
|
|
27
|
+
"./app-server-client": {
|
|
28
|
+
"types": "./dist/types/app-server-client.d.ts",
|
|
29
|
+
"import": "./dist/app-server-client.js"
|
|
30
|
+
},
|
|
22
31
|
"./protocol": {
|
|
23
32
|
"types": "./dist/types/protocol.d.ts"
|
|
24
33
|
}
|
|
@@ -35,8 +44,8 @@
|
|
|
35
44
|
"access": "public"
|
|
36
45
|
},
|
|
37
46
|
"dependencies": {
|
|
47
|
+
"@earendil-works/pi-ai": "^0.79.1",
|
|
38
48
|
"@letta-ai/letta-client": "^1.10.2",
|
|
39
|
-
"@earendil-works/pi-ai": "^0.78.1",
|
|
40
49
|
"@pierre/diffs": "1.2.2",
|
|
41
50
|
"glob": "^13.0.0",
|
|
42
51
|
"ink-link": "^5.0.0",
|
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: creating-
|
|
3
|
-
description: Creates and edits trusted local Letta Code
|
|
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, status values, 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
|
-
# Creating
|
|
6
|
+
# Creating Mods
|
|
7
7
|
|
|
8
|
-
Use this skill to create or update trusted global Letta Code
|
|
8
|
+
Use this skill to create or update trusted global Letta Code mods in:
|
|
9
9
|
|
|
10
10
|
```text
|
|
11
|
-
~/.letta/
|
|
11
|
+
~/.letta/mods/
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
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`, `letta.getContext()`) and guard optional UI with `letta.capabilities`.
|
|
15
15
|
|
|
16
|
-
Capabilities vary by surface. TUI/headless may load tools, commands, events, UI, and providers; the desktop listener loads provider-only
|
|
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
|
|
|
18
18
|
## Choose the right capability
|
|
19
19
|
|
|
20
20
|
| User wants | Build |
|
|
21
21
|
| --- | --- |
|
|
22
|
-
| Agent/model should autonomously call a local capability |
|
|
23
|
-
| User wants `/foo` to send a prompt or run local UI logic |
|
|
24
|
-
| Slash command represents a reusable agent workflow | Skill + thin
|
|
22
|
+
| Agent/model should autonomously call a local capability | Mod tool |
|
|
23
|
+
| User wants `/foo` to send a prompt or run local UI logic | Mod command |
|
|
24
|
+
| Slash command represents a reusable agent workflow | Skill + thin mod command |
|
|
25
25
|
| Command should work while the main agent is busy | Command with `runWhenBusy: true`, `handled`, panel/status, and usually `ctx.conversation.fork()` |
|
|
26
26
|
| Show transient output above input | Panel, usually from a command |
|
|
27
27
|
| Show small persistent state | Status value |
|
|
28
28
|
| React to app/session lifecycle or transform outbound turns | Event |
|
|
29
29
|
| Enforce dynamic allow/ask/deny policy for tool calls | Permission overlay |
|
|
30
|
-
| Add a custom model/API provider for local agents | Provider
|
|
30
|
+
| Add a custom model/API provider for local agents | Provider mod (local agents only) |
|
|
31
31
|
| Change the bottom statusline appearance | Use `customizing-statusline`, not this skill |
|
|
32
32
|
|
|
33
33
|
Default to a **tool** when the model should decide when to use the capability. Default to a **command** when the human explicitly invokes it. Compose capabilities when the UX needs it, e.g. command + panel + scoped conversation fork.
|
|
34
34
|
|
|
35
35
|
## Workflow
|
|
36
36
|
|
|
37
|
-
1. Inspect `~/.letta/
|
|
38
|
-
2. Preserve unrelated
|
|
39
|
-
3. Choose the
|
|
37
|
+
1. Inspect `~/.letta/mods/` for related files.
|
|
38
|
+
2. Preserve unrelated mod code. Prefer a focused new file if merging would be messy.
|
|
39
|
+
3. Choose the mod shape and load only the needed recipe:
|
|
40
40
|
- tools: `references/tools.md`
|
|
41
41
|
- commands: `references/commands.md`
|
|
42
42
|
- local custom providers: `references/providers.md`
|
|
@@ -44,13 +44,13 @@ Default to a **tool** when the model should decide when to use the capability. D
|
|
|
44
44
|
- permissions: `references/permissions.md`
|
|
45
45
|
- panels/status/capabilities: `references/ui.md`
|
|
46
46
|
- complex plan-mode composition: `references/plan-mode.md`
|
|
47
|
-
4. For multi-capability or stateful
|
|
48
|
-
5. Write a single-file
|
|
47
|
+
4. For multi-capability or stateful mods, also read `references/architecture.md`.
|
|
48
|
+
5. Write a single-file mod unless the user asks for something larger.
|
|
49
49
|
6. Return disposers for registered providers/commands/tools/events, timers, subscriptions, and panels that should close on reload.
|
|
50
50
|
7. Do a basic review: valid names, descriptions present, schemas are object schemas, optional capabilities guarded, scoped APIs used, cleanup returned.
|
|
51
|
-
8. Tell the user the absolute file path changed and to run `/reload`. If
|
|
51
|
+
8. Tell the user the absolute file path changed and to run `/reload`. If a mod breaks startup or command handling, recover with `letta --no-mods` or `LETTA_DISABLE_MODS=1 letta`.
|
|
52
52
|
|
|
53
|
-
## Core
|
|
53
|
+
## Core mod shape
|
|
54
54
|
|
|
55
55
|
```ts
|
|
56
56
|
export default function activate(letta) {
|
|
@@ -93,25 +93,25 @@ letta.capabilities.ui.customStatuslineRenderer
|
|
|
93
93
|
- `forked.sendMessageStream([...])` to stream from a fork
|
|
94
94
|
- In tools, use `ctx.conversation.getHistory()` when the tool needs recent context.
|
|
95
95
|
- Use `letta.client` only for server-specific Letta API calls; do not use it as a substitute for scoped conversation helpers.
|
|
96
|
-
- Do not import `@/backend`, `@/cli`, or other Letta Code internals from
|
|
96
|
+
- Do not import `@/backend`, `@/cli`, or other Letta Code internals from mod files.
|
|
97
97
|
|
|
98
98
|
## Diagnostics
|
|
99
99
|
|
|
100
|
-
Use `letta.diagnostics.report({ message, severity })` sparingly as a debug utility for
|
|
100
|
+
Use `letta.diagnostics.report({ message, severity })` sparingly as a debug utility for mod setup/runtime problems an agent should inspect, such as missing required environment variables or failed local configuration. Default severity is `"error"`; use `severity: "warning"` only for optional/degraded behavior. Keep messages short and actionable, and do not dump routine logs or large state.
|
|
101
101
|
|
|
102
|
-
Agents can inspect local
|
|
102
|
+
Agents can inspect local mod diagnostics at:
|
|
103
103
|
|
|
104
104
|
```text
|
|
105
|
-
~/.letta/
|
|
105
|
+
~/.letta/mods/diagnostics/latest.json
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
## Rules
|
|
109
109
|
|
|
110
|
-
- Global trusted code only for now. Do not create project
|
|
111
|
-
- Custom provider
|
|
112
|
-
- Provider
|
|
110
|
+
- Global trusted code only for now. Do not create project mods.
|
|
111
|
+
- Custom provider mods are local-backend/local-agent only. They do not add providers for Constellation/cloud agents.
|
|
112
|
+
- Provider mods may run in a provider-only listener context; keep provider registration independent from commands/tools/UI and guard everything else.
|
|
113
113
|
- Do not assume extra npm packages are available.
|
|
114
|
-
- Do not do surprising side effects on startup;
|
|
114
|
+
- Do not do surprising side effects on startup; mods activate on app start and `/reload`.
|
|
115
115
|
- Keep user-facing output short and intentional.
|
|
116
116
|
- Prefer Node/Bun standard APIs (`node:child_process`, `node:fs`, etc.) for local work.
|
|
117
117
|
- For shell execution, prefer `execFile`/`spawn` over shell strings.
|
|
@@ -119,16 +119,16 @@ Agents can inspect local extension diagnostics at:
|
|
|
119
119
|
- For `runWhenBusy: true`, do not return `prompt`; return `handled` and own the UI/background work.
|
|
120
120
|
- Treat `turn_start` as powerful trusted code: keep transforms narrow and unsurprising.
|
|
121
121
|
|
|
122
|
-
## Pre-flight checklist for complex
|
|
122
|
+
## Pre-flight checklist for complex mods
|
|
123
123
|
|
|
124
124
|
Before finishing, verify:
|
|
125
125
|
|
|
126
|
-
- The
|
|
126
|
+
- The mod has one clear owner/file and does not mix unrelated features.
|
|
127
127
|
- Command/tool IDs are valid; command overrides of built-ins are intentional, and tool IDs do not collide with built-ins.
|
|
128
128
|
- Tool descriptions explain when the model should call them.
|
|
129
129
|
- JSON schemas are object schemas with useful descriptions.
|
|
130
130
|
- Optional UI/event/statusline APIs are capability-guarded.
|
|
131
|
-
- Provider
|
|
131
|
+
- Provider mods are capability-guarded and clearly documented as local-agent only.
|
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Mod architecture patterns
|
|
2
2
|
|
|
3
|
-
Use this reference for non-trivial
|
|
3
|
+
Use this reference for non-trivial mods: multiple capabilities, local state, timers, background model work, or UI.
|
|
4
4
|
|
|
5
5
|
## Contents
|
|
6
6
|
|
|
@@ -14,14 +14,14 @@ Use this reference for non-trivial extensions: multiple capabilities, local stat
|
|
|
14
14
|
|
|
15
15
|
## Mental model
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
A mod is trusted local code that registers capabilities during activation and cleans them up on reload/shutdown. Keep the public surface small:
|
|
18
18
|
|
|
19
19
|
- activation registers commands/tools/events/UI
|
|
20
20
|
- command/tool/event handlers receive scoped context
|
|
21
21
|
- state is local and explicit
|
|
22
22
|
- cleanup is returned from activation
|
|
23
23
|
|
|
24
|
-
Do not import Letta Code internals. If the
|
|
24
|
+
Do not import Letta Code internals. If the mod API does not expose a capability yet, avoid reaching around it.
|
|
25
25
|
|
|
26
26
|
Capabilities vary by host surface. Keep each registration behind the matching `letta.capabilities` guard so one file can run in TUI, headless, and provider-only listener contexts.
|
|
27
27
|
|
|
@@ -92,14 +92,14 @@ if (letta.capabilities.events.lifecycle && letta.capabilities.ui.statusValues) {
|
|
|
92
92
|
|
|
93
93
|
### `turn_start` transform
|
|
94
94
|
|
|
95
|
-
Use `turn_start` only when the
|
|
95
|
+
Use `turn_start` only when the mod needs to inspect or transform the outbound user-message turn. Keep transforms local and predictable. Prefer appending/prepending focused context or replacing explicit shortcuts over broad rewrites.
|
|
96
96
|
|
|
97
97
|
## Local state
|
|
98
98
|
|
|
99
|
-
For small persistent state, use a clearly named file under `~/.letta/
|
|
99
|
+
For small persistent state, use a clearly named file under `~/.letta/mods/`, for example:
|
|
100
100
|
|
|
101
101
|
```text
|
|
102
|
-
~/.letta/
|
|
102
|
+
~/.letta/mods/my-mod.state.json
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
Use atomic-ish writes when practical: write the full JSON file from an in-memory object after each change. Validate parsed state and fall back gracefully if the file is missing or malformed.
|
|
@@ -108,7 +108,7 @@ Keep state separate from source code. Do not store secrets in plain JSON; use ex
|
|
|
108
108
|
|
|
109
109
|
## Timers and subscriptions
|
|
110
110
|
|
|
111
|
-
Timers are okay for active-session behavior, but they only run while the
|
|
111
|
+
Timers are okay for active-session behavior, but they only run while the mod engine is alive. Always clear them:
|
|
112
112
|
|
|
113
113
|
```ts
|
|
114
114
|
const timer = setInterval(update, 30_000);
|
|
@@ -147,4 +147,4 @@ Tools currently receive `ctx.conversation.getHistory()` but not fork/send helper
|
|
|
147
147
|
- `runWhenBusy: true` commands return `handled`, not `prompt`.
|
|
148
148
|
- Background model work uses a forked conversation.
|
|
149
149
|
- Local filesystem and shell work uses scoped paths and `execFile`/`spawn`.
|
|
150
|
-
-
|
|
150
|
+
- Mod output is concise and actionable.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Mod command recipes
|
|
2
2
|
|
|
3
3
|
Use commands when the human explicitly invokes `/foo`.
|
|
4
4
|
|
|
5
|
-
For complex command-driven
|
|
5
|
+
For complex command-driven mods with panels, timers, local state, or background model work, also read `architecture.md`.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
@@ -17,10 +17,10 @@ For complex command-driven extensions with panels, timers, local state, or backg
|
|
|
17
17
|
|
|
18
18
|
| Need | Use |
|
|
19
19
|
| --- | --- |
|
|
20
|
-
| `/foo` expands to a prompt |
|
|
21
|
-
| `/foo` starts a complex reusable workflow | Skill + thin
|
|
22
|
-
| Model should call the capability by itself |
|
|
23
|
-
| Command needs transient UI while doing local work |
|
|
20
|
+
| `/foo` expands to a prompt | Mod command |
|
|
21
|
+
| `/foo` starts a complex reusable workflow | Skill + thin mod command |
|
|
22
|
+
| Model should call the capability by itself | Mod tool |
|
|
23
|
+
| Command needs transient UI while doing local work | Mod command + panel |
|
|
24
24
|
| Command needs model output while the main agent is busy | `runWhenBusy: true` command + forked `ctx.conversation` |
|
|
25
25
|
|
|
26
26
|
If the command represents a durable agent workflow (for example `/goal`), put the workflow instructions in a skill and keep the command as a small launcher/prompt.
|
|
@@ -29,8 +29,8 @@ If the command represents a durable agent workflow (for example `/goal`), put th
|
|
|
29
29
|
|
|
30
30
|
- Do not include the leading slash. Use `id: "review"`, not `id: "/review"`.
|
|
31
31
|
- Use a lowercase slug with letters, numbers, and hyphens only.
|
|
32
|
-
- Built-in commands like `/reload`, `/model`, `/statusline`, etc. can be overridden by trusted local
|
|
33
|
-
- Duplicate
|
|
32
|
+
- Built-in commands like `/reload`, `/model`, `/statusline`, etc. can be overridden by trusted local mods. Do this intentionally and keep recovery in mind: start with `--no-mods` or `LETTA_DISABLE_MODS=1` if an override breaks command handling.
|
|
33
|
+
- Duplicate mod command IDs fail unless `override: true` is intentional.
|
|
34
34
|
|
|
35
35
|
## Prompt command
|
|
36
36
|
|
|
@@ -68,7 +68,7 @@ export default function activate(letta) {
|
|
|
68
68
|
|
|
69
69
|
return letta.commands.register({
|
|
70
70
|
id: "whereami",
|
|
71
|
-
description: "Show the active
|
|
71
|
+
description: "Show the active mod command context",
|
|
72
72
|
run(ctx) {
|
|
73
73
|
return {
|
|
74
74
|
type: "output",
|
|
@@ -125,4 +125,4 @@ const stream = await forked.sendMessageStream([
|
|
|
125
125
|
|
|
126
126
|
Do not send directly to the active conversation from a busy command; fork first unless the user explicitly asked to affect the main conversation later.
|
|
127
127
|
|
|
128
|
-
For a worked multi-capability
|
|
128
|
+
For a worked multi-capability mod that combines commands, tools, events, permissions, and local state, see `plan-mode.md`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Mod event recipes
|
|
2
2
|
|
|
3
|
-
Use events when trusted local code should react to app/session changes or transform outbound turns without the human explicitly invoking a command. For event-driven
|
|
3
|
+
Use events when trusted local code should react to app/session changes or transform outbound turns without the human explicitly invoking a command. For event-driven mods with state, timers, panels, or background model work, also read `architecture.md`.
|
|
4
4
|
|
|
5
5
|
## Contents
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ Use events when trusted local code should react to app/session changes or transf
|
|
|
12
12
|
- Conversation status example
|
|
13
13
|
- Rules
|
|
14
14
|
|
|
15
|
-
This is the first slice of the hooks-v2 direction. The long-term goal is for typed
|
|
15
|
+
This is the first slice of the hooks-v2 direction. The long-term goal is for typed mod events to replace settings-based hooks. Existing hooks still own blocking decisions and model feedback injection until each event has a typed return contract.
|
|
16
16
|
|
|
17
17
|
## Capabilities
|
|
18
18
|
|
|
@@ -22,7 +22,7 @@ letta.capabilities.events.tools
|
|
|
22
22
|
letta.capabilities.events.turns
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Guard events when writing portable
|
|
25
|
+
Guard events when writing portable mods:
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
28
|
export default function activate(letta) {
|
|
@@ -115,7 +115,7 @@ Lifecycle handlers are notification-only and should not return values. `turn_sta
|
|
|
115
115
|
}
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
`tool_start` fires immediately before a client-side tool executes. This includes built-in tools,
|
|
118
|
+
`tool_start` fires immediately before a client-side tool executes. This includes built-in tools, mod tools, and external tools executed through the local tool manager. It runs after permission/approval classification and before `PreToolUse` hooks, so trusted local mods can change the actual executed arguments after the approval UI has already classified the original request. Mod permission overlays are rechecked after `tool_start` on the final args.
|
|
119
119
|
|
|
120
120
|
Handlers can inspect `event.args`, mutate it directly, or return replacement args:
|
|
121
121
|
|
|
@@ -134,9 +134,9 @@ letta.events.on("tool_start", (event) => {
|
|
|
134
134
|
});
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
Handlers run in registration order. Later handlers see the current args after earlier mutations/returns. If a handler throws, its partial `event.args` mutation is rolled back and the error is recorded as
|
|
137
|
+
Handlers run in registration order. Later handlers see the current args after earlier mutations/returns. If a handler throws, its partial `event.args` mutation is rolled back and the error is recorded as a mod diagnostic.
|
|
138
138
|
|
|
139
|
-
`tool_start` is intentionally a trusted local
|
|
139
|
+
`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
140
|
|
|
141
141
|
`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
142
|
|
|
@@ -166,9 +166,9 @@ letta.events.on("turn_start", (event) => {
|
|
|
166
166
|
});
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
Handlers run in registration order. Later handlers see the current input after earlier mutations/returns. If a handler throws, its partial `event.input` mutation is rolled back and the error is recorded as
|
|
169
|
+
Handlers run in registration order. Later handlers see the current input after earlier mutations/returns. If a handler throws, its partial `event.input` mutation is rolled back and the error is recorded as a mod diagnostic.
|
|
170
170
|
|
|
171
|
-
`turn_start` is intentionally a trusted local
|
|
171
|
+
`turn_start` is intentionally a trusted local mod point: it can rewrite user messages, approval results, and ordering. Keep transforms focused and unsurprising.
|
|
172
172
|
|
|
173
173
|
Handlers also receive:
|
|
174
174
|
|
|
@@ -221,5 +221,5 @@ export default function activate(letta) {
|
|
|
221
221
|
|
|
222
222
|
- Do not block user flow unless the event's typed contract explicitly supports blocking.
|
|
223
223
|
- Do not use lifecycle events for safety decisions yet. Existing hooks still own blocking behavior.
|
|
224
|
-
- Catch expected local errors if the user-facing outcome matters. Uncaught errors are isolated and recorded as
|
|
224
|
+
- Catch expected local errors if the user-facing outcome matters. Uncaught errors are isolated and recorded as mod diagnostics.
|
|
225
225
|
- Return disposers from activation for event registrations, timers, subscriptions, and status values.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Mod permission recipes
|
|
2
2
|
|
|
3
3
|
Use permission overlays when trusted local code should participate in tool approval decisions. Prefer permissions over `tool_start` denial for policy: permissions run before approval UI and again before execution on final tool arguments.
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ Use permission overlays when trusted local code should participate in tool appro
|
|
|
8
8
|
letta.capabilities.permissions
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Guard registrations when writing portable
|
|
11
|
+
Guard registrations when writing portable mods:
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
export default function activate(letta) {
|
|
@@ -71,7 +71,7 @@ Composition rules across overlays:
|
|
|
71
71
|
- then `allow`
|
|
72
72
|
- `undefined` means no opinion
|
|
73
73
|
|
|
74
|
-
User/configured hard denials still win before
|
|
74
|
+
User/configured hard denials still win before mod overlays. Mod overlays can override normal auto-allow/default approval behavior, including unrestricted/yolo mode.
|
|
75
75
|
|
|
76
76
|
## Two phases
|
|
77
77
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Plan mode
|
|
1
|
+
# Plan mode mod example
|
|
2
2
|
|
|
3
|
-
Use this as the canonical multi-capability
|
|
3
|
+
Use this as the canonical multi-capability mod example. It composes a slash command, model-callable tools, turn reminders, permission overlays, and local state to recreate the old built-in plan-mode flow with mod APIs.
|
|
4
4
|
|
|
5
|
-
This is a pattern reference, not a full product implementation. Keep local
|
|
5
|
+
This is a pattern reference, not a full product implementation. Keep local mods self-contained and avoid importing Letta Code internals.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
@@ -46,7 +46,7 @@ Do not use panels for persistent mode state. Panels are transient UI and can be
|
|
|
46
46
|
|
|
47
47
|
## State
|
|
48
48
|
|
|
49
|
-
Use small local state under `~/.letta/
|
|
49
|
+
Use small local state under `~/.letta/mods/`, keyed by conversation ID:
|
|
50
50
|
|
|
51
51
|
```ts
|
|
52
52
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -54,7 +54,7 @@ import { homedir } from "node:os";
|
|
|
54
54
|
import { join, relative } from "node:path";
|
|
55
55
|
|
|
56
56
|
const PLANS_DIR = join(homedir(), ".letta", "plans");
|
|
57
|
-
const STATE_PATH = join(homedir(), ".letta", "
|
|
57
|
+
const STATE_PATH = join(homedir(), ".letta", "mods", "plan-mode.state.json");
|
|
58
58
|
const GLOBAL_CONVERSATION_ID = "__global__";
|
|
59
59
|
|
|
60
60
|
type PlanSession = {
|
|
@@ -81,7 +81,7 @@ function readState(): PlanState {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
function writeState(state: PlanState): void {
|
|
84
|
-
mkdirSync(join(homedir(), ".letta", "
|
|
84
|
+
mkdirSync(join(homedir(), ".letta", "mods"), { recursive: true });
|
|
85
85
|
writeFileSync(STATE_PATH, JSON.stringify(state, null, 2));
|
|
86
86
|
}
|
|
87
87
|
```
|
|
@@ -279,7 +279,9 @@ Shell allowlists are easy to get wrong. Start conservative: allow clearly read-o
|
|
|
279
279
|
|
|
280
280
|
## Exit tool
|
|
281
281
|
|
|
282
|
-
In the
|
|
282
|
+
In the mod version, `exit_plan_mode` is not the approval UI. The agent should read the plan file, present the full current plan text with `AskUserQuestion`, then call `exit_plan_mode` only after the user approves.
|
|
283
|
+
|
|
284
|
+
Use `approvalPolicy: "alwaysAsk"` so the final state transition still pauses for human confirmation in unrestricted/yolo mode.
|
|
283
285
|
|
|
284
286
|
```ts
|
|
285
287
|
if (letta.capabilities.tools) {
|
|
@@ -288,7 +290,7 @@ if (letta.capabilities.tools) {
|
|
|
288
290
|
description:
|
|
289
291
|
"Exit plan mode only after the plan file has been written, the full current plan text has been presented with AskUserQuestion, and the user has approved it.",
|
|
290
292
|
parameters: { type: "object", properties: {}, additionalProperties: false },
|
|
291
|
-
|
|
293
|
+
approvalPolicy: "alwaysAsk",
|
|
292
294
|
parallelSafe: false,
|
|
293
295
|
run(ctx) {
|
|
294
296
|
const session = getSession(ctx.conversation.id);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Mod provider recipes
|
|
2
2
|
|
|
3
|
-
Use provider
|
|
3
|
+
Use provider mods when the user wants a **local agent** to use a model provider that is not built into `/connect` and `/model`.
|
|
4
4
|
|
|
5
|
-
Important: provider
|
|
5
|
+
Important: provider mods are local-backend/local-agent only. They register local provider metadata for the TUI, headless local runtime, and desktop listener. They do not add providers for Constellation/cloud agents.
|
|
6
6
|
|
|
7
|
-
For multi-capability
|
|
7
|
+
For multi-capability mods that combine a provider with commands, tools, UI, or state, also read `architecture.md`.
|
|
8
8
|
|
|
9
9
|
## Quick pattern
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
// ~/.letta/
|
|
12
|
+
// ~/.letta/mods/kilo.ts
|
|
13
13
|
export default function activate(letta) {
|
|
14
14
|
if (!letta.capabilities.providers) return;
|
|
15
15
|
|
|
@@ -48,10 +48,10 @@ After `/reload`, the provider appears in local `/connect` and desktop Connect mo
|
|
|
48
48
|
|
|
49
49
|
- Always guard with `letta.capabilities.providers`.
|
|
50
50
|
- Prefer `letta.providers.register(...)` over legacy `letta.registerProvider(...)`.
|
|
51
|
-
- Keep provider registration independent from commands/tools/UI/events and `letta.client`; the desktop listener loads provider-only
|
|
51
|
+
- Keep provider registration independent from commands/tools/UI/events and `letta.client`; the desktop listener loads provider-only mods.
|
|
52
52
|
- Do not hardcode real secrets. `apiKey: "ENV_VAR"` resolves `process.env.ENV_VAR` when present, or lets `/connect` save a local key.
|
|
53
53
|
- Use stable lowercase provider ids. Model ids must be unprefixed and must not contain `/`.
|
|
54
|
-
- Set `api` at provider or model level. Common values include `"openai-completions"`, `"openai-responses"`, `"anthropic-messages"`, and `"bedrock-converse-stream"`; check `src/backend/dev/pi-provider-
|
|
54
|
+
- Set `api` at provider or model level. Common values include `"openai-completions"`, `"openai-responses"`, `"anthropic-messages"`, and `"bedrock-converse-stream"`; check `src/backend/dev/pi-provider-mod-types.ts` and pi-ai model types before using uncommon values.
|
|
55
55
|
|
|
56
56
|
## Model metadata
|
|
57
57
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Mod tool recipes
|
|
2
2
|
|
|
3
3
|
Use tools when the agent/model should call a local capability autonomously.
|
|
4
4
|
|
|
5
|
-
For tools that are part of a larger
|
|
5
|
+
For tools that are part of a larger mod with commands, UI, local state, or events, also read `architecture.md`.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
@@ -17,6 +17,7 @@ For tools that are part of a larger extension with commands, UI, local state, or
|
|
|
17
17
|
- Description: explain when the model should use it.
|
|
18
18
|
- Parameters: JSON Schema object. Use `additionalProperties: false` when possible.
|
|
19
19
|
- `requiresApproval: false` only for read-only, low-risk local introspection.
|
|
20
|
+
- `approvalPolicy: "alwaysAsk"` only for tools that must pause for human approval even in unrestricted/yolo mode.
|
|
20
21
|
- `parallelSafe: true` only for read-only tools with no shared mutation or long-lived exclusive resource.
|
|
21
22
|
- Use `ctx.cwd` / `ctx.workingDirectory` as the workspace.
|
|
22
23
|
- Use `await ctx.conversation.getHistory()` when a tool needs recent conversation context. It returns the most recent messages in chronological order by default.
|
|
@@ -124,3 +125,20 @@ letta.tools.register({
|
|
|
124
125
|
},
|
|
125
126
|
});
|
|
126
127
|
```
|
|
128
|
+
|
|
129
|
+
## Always-ask tool
|
|
130
|
+
|
|
131
|
+
Use `approvalPolicy: "alwaysAsk"` when a tool represents a human gate rather than a risky operation. Deny rules and permission overlays still win, but unrestricted/yolo mode will not auto-approve it.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
letta.tools.register({
|
|
135
|
+
name: "exit_plan_mode",
|
|
136
|
+
description: "Exit plan mode after the user has reviewed and approved the plan.",
|
|
137
|
+
parameters: { type: "object", properties: {}, additionalProperties: false },
|
|
138
|
+
approvalPolicy: "alwaysAsk",
|
|
139
|
+
parallelSafe: false,
|
|
140
|
+
run(ctx) {
|
|
141
|
+
return "Plan approved. You can now start coding.";
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
```
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Mod UI recipes
|
|
2
2
|
|
|
3
|
-
UI capabilities are optional. Always guard UI work with `letta.capabilities.ui.*` when writing portable
|
|
3
|
+
UI capabilities are optional. Always guard UI work with `letta.capabilities.ui.*` when writing portable mods.
|
|
4
4
|
|
|
5
|
-
For UI that belongs to a larger command/event
|
|
5
|
+
For UI that belongs to a larger command/event mod, also read `architecture.md` for cleanup and composition patterns.
|
|
6
6
|
|
|
7
7
|
## Capabilities
|
|
8
8
|
|
|
@@ -21,7 +21,7 @@ letta.capabilities.ui.customStatuslineRenderer
|
|
|
21
21
|
```ts
|
|
22
22
|
if (letta.capabilities.ui.panels) {
|
|
23
23
|
const panel = letta.ui.openPanel({
|
|
24
|
-
id: "my-
|
|
24
|
+
id: "my-mod",
|
|
25
25
|
content: ["Working…"],
|
|
26
26
|
order: 100,
|
|
27
27
|
});
|
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: customizing-commands
|
|
3
|
-
description: Creates, edits, and enables Letta Code
|
|
3
|
+
description: Creates, edits, and enables Letta Code mod-provided slash commands. Use when the user asks to add a custom /command, slash command, command shortcut, scoped conversation-backed command, or command-driven panel behavior.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Customizing Commands
|
|
7
7
|
|
|
8
|
-
Use this as the command-specific entrypoint for local
|
|
8
|
+
Use this as the command-specific entrypoint for local mod slash commands. For broader mod work, recipes live in `../creating-mods/references/commands.md`, `../creating-mods/references/architecture.md`, `../creating-mods/references/ui.md`, and `../creating-mods/references/plan-mode.md`.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Mod files live in:
|
|
11
11
|
|
|
12
12
|
```text
|
|
13
|
-
~/.letta/
|
|
13
|
+
~/.letta/mods/
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
Use a focused file name, e.g. `~/.letta/
|
|
16
|
+
Use a focused file name, e.g. `~/.letta/mods/review.ts` or `~/.letta/mods/commands.ts`.
|
|
17
17
|
|
|
18
18
|
## First decide whether a command is right
|
|
19
19
|
|
|
20
20
|
| User wants | Build |
|
|
21
21
|
| --- | --- |
|
|
22
|
-
| `/foo` sends a prompt or shows local output |
|
|
23
|
-
| `/foo` starts a reusable agent workflow | Skill + thin
|
|
24
|
-
| Agent/model should autonomously call the capability |
|
|
25
|
-
| Command shows transient progress/results |
|
|
22
|
+
| `/foo` sends a prompt or shows local output | Mod command |
|
|
23
|
+
| `/foo` starts a reusable agent workflow | Skill + thin mod command |
|
|
24
|
+
| Agent/model should autonomously call the capability | Mod tool, not a command |
|
|
25
|
+
| Command shows transient progress/results | Mod command + panel |
|
|
26
26
|
| Command needs model output while the main agent is busy | `runWhenBusy: true` command + forked `ctx.conversation` |
|
|
27
27
|
|
|
28
|
-
If the command is a durable workflow like `/goal`, put the workflow instructions in a skill and keep the
|
|
28
|
+
If the command is a durable workflow like `/goal`, put the workflow instructions in a skill and keep the mod command as a small launcher/prompt.
|
|
29
29
|
|
|
30
30
|
## Workflow
|
|
31
31
|
|
|
32
|
-
1. Inspect `~/.letta/
|
|
33
|
-
2. Preserve unrelated
|
|
32
|
+
1. Inspect `~/.letta/mods/` for related command files.
|
|
33
|
+
2. Preserve unrelated mod code; create a focused new file if merging is messy.
|
|
34
34
|
3. Register with `letta.commands.register()` and guard with `letta.capabilities.commands`.
|
|
35
35
|
4. Return the unregister function, or a disposer that calls it plus any timer/panel cleanup.
|
|
36
36
|
5. Tell the user the exact file path changed and to run `/reload`.
|
|
@@ -62,7 +62,7 @@ export default function activate(letta) {
|
|
|
62
62
|
## Command result types
|
|
63
63
|
|
|
64
64
|
```ts
|
|
65
|
-
type
|
|
65
|
+
type ModCommandResult =
|
|
66
66
|
| { type: "prompt"; content: string; systemReminder?: boolean }
|
|
67
67
|
| { type: "output"; output: string; success?: boolean }
|
|
68
68
|
| { type: "handled" };
|
|
@@ -80,11 +80,11 @@ type ExtensionCommandResult =
|
|
|
80
80
|
- `runWhenBusy: true` commands must not return `prompt` while the main agent is busy; use scoped conversation helpers/panels and return `handled`.
|
|
81
81
|
- `showInTranscript: false` commands should usually return `handled`, not `prompt`.
|
|
82
82
|
- Do not import Letta Code app internals.
|
|
83
|
-
- Do not do surprising side effects on startup;
|
|
83
|
+
- Do not do surprising side effects on startup; mods activate on app start and `/reload`.
|
|
84
84
|
|
|
85
85
|
## More recipes
|
|
86
86
|
|
|
87
|
-
- Simple output command, panel command, busy-safe conversation command: `../creating-
|
|
88
|
-
- Complex command architecture, state, cleanup: `../creating-
|
|
89
|
-
- Panel/status UI patterns: `../creating-
|
|
90
|
-
- Worked plan-mode command/tool composition: `../creating-
|
|
87
|
+
- Simple output command, panel command, busy-safe conversation command: `../creating-mods/references/commands.md`
|
|
88
|
+
- Complex command architecture, state, cleanup: `../creating-mods/references/architecture.md`
|
|
89
|
+
- Panel/status UI patterns: `../creating-mods/references/ui.md`
|
|
90
|
+
- Worked plan-mode command/tool composition: `../creating-mods/references/plan-mode.md`
|