@letta-ai/letta-code 0.27.23 → 0.27.25
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 +87 -17
- package/dist/agent-presets.js.map +2 -2
- package/dist/types/agent/model-catalog.d.ts +6 -25
- package/dist/types/agent/model-catalog.d.ts.map +1 -1
- package/dist/types/types/protocol_v2.d.ts +16 -0
- package/dist/types/types/protocol_v2.d.ts.map +1 -1
- package/letta.js +773 -364
- package/package.json +2 -1
- package/scripts/check-skill-frontmatter.js +116 -0
- package/scripts/check.js +1 -0
- package/skills/context-doctor/SKILL.md +2 -1
- package/skills/creating-mods/SKILL.md +1 -1
- package/skills/creating-mods/references/ui.md +40 -1
- package/skills/initializing-memory/SKILL.md +2 -1
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.25",
|
|
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",
|
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
"check:filename-casing": "node scripts/check-filename-casing.js",
|
|
98
98
|
"check:test-mock-isolation": "bun run scripts/check-test-mock-isolation.js",
|
|
99
99
|
"check:test-coverage": "node scripts/check-test-coverage.cjs",
|
|
100
|
+
"check:skill-frontmatter": "node scripts/check-skill-frontmatter.js",
|
|
100
101
|
"check:bundled-skill-scripts": "node scripts/check-bundled-skill-scripts.js",
|
|
101
102
|
"check": "bun run scripts/check.js",
|
|
102
103
|
"dev": "node scripts/dev.cjs",
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
const isStagedOnly = process.argv.includes("--staged");
|
|
7
|
+
|
|
8
|
+
function git(args) {
|
|
9
|
+
const result = spawnSync("git", args, {
|
|
10
|
+
cwd: process.cwd(),
|
|
11
|
+
encoding: "utf8",
|
|
12
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (result.status !== 0) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
(result.stderr || result.stdout || "git command failed").trim(),
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return result.stdout;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function splitNul(output) {
|
|
25
|
+
return output.split("\0").filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isSkillMarkdownFile(file) {
|
|
29
|
+
return file.split(/[\\/]/).pop() === "SKILL.md";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getCandidateFiles() {
|
|
33
|
+
if (isStagedOnly) {
|
|
34
|
+
return splitNul(
|
|
35
|
+
git(["diff", "--cached", "--name-only", "--diff-filter=ACMR", "-z"]),
|
|
36
|
+
).filter(isSkillMarkdownFile);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return splitNul(git(["ls-files", "-z"])).filter(isSkillMarkdownFile);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readStagedFile(file) {
|
|
43
|
+
return git(["show", `:${file}`]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readCurrentFile(file) {
|
|
47
|
+
if (!existsSync(file)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return readFileSync(file, "utf8");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function checkSkillFrontmatterName(content) {
|
|
54
|
+
const normalized = content.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n");
|
|
55
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---(?:\n|$)/);
|
|
56
|
+
if (!match) {
|
|
57
|
+
return "missing YAML frontmatter";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const frontmatter = match[1] ?? "";
|
|
61
|
+
const nameLine = frontmatter
|
|
62
|
+
.split("\n")
|
|
63
|
+
.find((line) => /^\s*name\s*:/.test(line));
|
|
64
|
+
|
|
65
|
+
if (!nameLine) {
|
|
66
|
+
return "missing name frontmatter";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const nameValue = nameLine.replace(/^\s*name\s*:\s*/, "").trim();
|
|
70
|
+
if (!nameValue) {
|
|
71
|
+
return "empty name frontmatter";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let violations = [];
|
|
78
|
+
let files = [];
|
|
79
|
+
try {
|
|
80
|
+
files = getCandidateFiles();
|
|
81
|
+
violations = files.flatMap((file) => {
|
|
82
|
+
const content = isStagedOnly ? readStagedFile(file) : readCurrentFile(file);
|
|
83
|
+
if (content === null) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const reason = checkSkillFrontmatterName(content);
|
|
88
|
+
return reason ? [{ file, reason }] : [];
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(
|
|
92
|
+
`Failed to check skill frontmatter: ${error instanceof Error ? error.message : String(error)}`,
|
|
93
|
+
);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (violations.length > 0) {
|
|
98
|
+
console.error("\n❌ Skill frontmatter violations found:\n");
|
|
99
|
+
for (const violation of violations) {
|
|
100
|
+
console.error(`${violation.file}`);
|
|
101
|
+
console.error(` ${violation.reason}`);
|
|
102
|
+
console.error(
|
|
103
|
+
" ↳ Add a non-empty `name:` field to SKILL.md frontmatter.\n",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
console.error(
|
|
107
|
+
`Found ${violations.length} skill frontmatter violation${violations.length === 1 ? "" : "s"}.`,
|
|
108
|
+
);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!isStagedOnly) {
|
|
113
|
+
console.log(
|
|
114
|
+
`✅ Skill frontmatter name headers present (${files.length} SKILL.md file${files.length === 1 ? "" : "s"}).`,
|
|
115
|
+
);
|
|
116
|
+
}
|
package/scripts/check.js
CHANGED
|
@@ -19,6 +19,7 @@ const checks = [
|
|
|
19
19
|
{ name: "filename casing", script: ["check:filename-casing"] },
|
|
20
20
|
{ name: "test mock isolation", script: ["check:test-mock-isolation"] },
|
|
21
21
|
{ name: "test coverage", script: ["check:test-coverage"] },
|
|
22
|
+
{ name: "skill frontmatter", script: ["check:skill-frontmatter"] },
|
|
22
23
|
{ name: "bundled skill scripts", script: ["check:bundled-skill-scripts"] },
|
|
23
24
|
{ name: "biome", script: ["lint"] },
|
|
24
25
|
{ name: "typescript", script: ["typecheck"] },
|
|
@@ -116,7 +116,8 @@ Review changes, then commit with a descriptive message:
|
|
|
116
116
|
cd $MEMORY_DIR
|
|
117
117
|
git status # Review what changed before staging
|
|
118
118
|
git add <specific files> # Stage targeted paths — avoid blind `git add -A`
|
|
119
|
-
|
|
119
|
+
author_name="${AGENT_NAME:-$AGENT_ID}"
|
|
120
|
+
git commit --author="$author_name <$AGENT_ID@letta.com>" -m "fix(doctor): <summary> 🏥
|
|
120
121
|
|
|
121
122
|
<identified issues and implemented solutions>"
|
|
122
123
|
|
|
@@ -155,7 +155,7 @@ Before finishing, verify:
|
|
|
155
155
|
| `references/providers.md` | Adding a custom model/API provider for local agents |
|
|
156
156
|
| `references/events.md` | Reacting to lifecycle/tool/turn events or transforming turns/tools |
|
|
157
157
|
| `references/permissions.md` | Enforcing dynamic tool allow/ask/deny policy before approval/execution |
|
|
158
|
-
| `references/ui.md` | Panels (including order-0 statusline) or `ui.panels` capability guards are involved |
|
|
158
|
+
| `references/ui.md` | Panels (including order-0 statusline and order-1 dreaming indicator) or `ui.panels` capability guards are involved |
|
|
159
159
|
| `references/plan-mode.md` | Recreating plan mode with commands, tools, events, permissions, and local state |
|
|
160
160
|
| `references/analysis-mode.md` | Phrase-triggered diagnostic mode with turn reminders (simpler than plan-mode) |
|
|
161
161
|
| `references/architecture.md` | Multiple capabilities, local state, cleanup, background model work, or non-trivial composition |
|
|
@@ -38,7 +38,7 @@ if (letta.capabilities.ui.panels) {
|
|
|
38
38
|
`order` is a signed coordinate around the input:
|
|
39
39
|
|
|
40
40
|
- `order > 1` — additive panels above the input, higher nearer the top (default `100`).
|
|
41
|
-
- `order === 1` — replaces the default
|
|
41
|
+
- `order === 1` — replaces the default dreaming/reflection indicator above the input. Use this only when intentionally overriding that row; otherwise use `order > 1`.
|
|
42
42
|
- `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.
|
|
43
43
|
- `order < 0` — stacks below the primary line, `-1` closest.
|
|
44
44
|
|
|
@@ -51,6 +51,12 @@ render(ctx: {
|
|
|
51
51
|
width: number;
|
|
52
52
|
agent: { id, name };
|
|
53
53
|
model: { id, displayName, provider, reasoningEffort };
|
|
54
|
+
backgroundAgents: Array<{
|
|
55
|
+
type: string;
|
|
56
|
+
status: string;
|
|
57
|
+
durationMs: number;
|
|
58
|
+
agentId: string | null;
|
|
59
|
+
}>;
|
|
54
60
|
subagents: { list(): SubagentLifecycleItem[] };
|
|
55
61
|
row(left, right, width): string;
|
|
56
62
|
columns(parts: string[], width): string;
|
|
@@ -63,6 +69,39 @@ render(ctx: {
|
|
|
63
69
|
|
|
64
70
|
Close panels when they are transient, and close/replace long-lived panels from the activation disposer if reload should remove them.
|
|
65
71
|
|
|
72
|
+
### Panel use case: dreaming indicator overrides
|
|
73
|
+
|
|
74
|
+
When a user asks to change the "dreaming" UI/indicator, the reflection status above the input, or to add the full background-agent URL, use an `order: 1` panel replacement. The user should not need to know any internal row/component name.
|
|
75
|
+
|
|
76
|
+
Checklist:
|
|
77
|
+
|
|
78
|
+
- Open a panel with `order: 1`, not an additive panel.
|
|
79
|
+
- Read active hidden background agents from `ctx.backgroundAgents`; filter by `status` (`pending`/`running`).
|
|
80
|
+
- Use `agent.agentId` to build `https://app.letta.com/chat/${agent.agentId}`.
|
|
81
|
+
- If the user asks for the full URL, render visible text; do not use `ctx.link()` because it hides the URL behind OSC-8.
|
|
82
|
+
- If preserving animation, own the timer and call `panel.update()`; clean up timer and panel in the disposer.
|
|
83
|
+
- Keep `render()` pure: do not call diagnostics or mutate external state from render.
|
|
84
|
+
|
|
85
|
+
Critical shape:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
const panel = letta.ui.openPanel({
|
|
89
|
+
id: "dreaming-url",
|
|
90
|
+
order: 1,
|
|
91
|
+
render(ctx) {
|
|
92
|
+
const agent = ctx.backgroundAgents.find(
|
|
93
|
+
(a) => a.status === "pending" || a.status === "running",
|
|
94
|
+
);
|
|
95
|
+
if (!agent) return "";
|
|
96
|
+
|
|
97
|
+
const url = agent.agentId
|
|
98
|
+
? `https://app.letta.com/chat/${agent.agentId}`
|
|
99
|
+
: null;
|
|
100
|
+
// Render spinner/label/elapsed, plus visible URL if requested.
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
66
105
|
### Commands that open panels
|
|
67
106
|
|
|
68
107
|
If a command's `run()` opens a panel, guard the command **registration** on `letta.capabilities.ui.panels` — not just the `openPanel` call:
|
|
@@ -706,7 +706,8 @@ Check if they're satisfied or want further refinement. Then commit and push memo
|
|
|
706
706
|
cd $MEMORY_DIR
|
|
707
707
|
git status # Review what changed before staging
|
|
708
708
|
git add <specific files> # Stage targeted paths — avoid blind `git add -A`
|
|
709
|
-
|
|
709
|
+
author_name="${AGENT_NAME:-$AGENT_ID}"
|
|
710
|
+
git commit --author="$author_name <$AGENT_ID@letta.com>" -m "feat(init): <summary> ✨
|
|
710
711
|
|
|
711
712
|
<what was initialized and key decisions made>"
|
|
712
713
|
|