@letta-ai/letta-code 0.30.30 → 0.30.32

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 (42) hide show
  1. package/dist/gateway-core.js +11 -4
  2. package/dist/gateway-core.js.map +3 -3
  3. package/dist/mcp-client.js +2 -2
  4. package/dist/mcp-client.js.map +1 -1
  5. package/dist/types/backend/api/ephemeral-conversations.d.ts +15 -0
  6. package/dist/types/backend/api/ephemeral-conversations.d.ts.map +1 -0
  7. package/dist/types/backend/dev/pi-model-factory.d.ts +3 -2
  8. package/dist/types/backend/dev/pi-model-factory.d.ts.map +1 -1
  9. package/dist/types/backend/dev/pi-stream-adapter.d.ts.map +1 -1
  10. package/dist/types/backend/local/compaction.d.ts.map +1 -1
  11. package/dist/types/backend/local/local-model-config.d.ts.map +1 -1
  12. package/dist/types/channels/gateway-core.d.ts.map +1 -1
  13. package/dist/types/cli/helpers/accumulator.d.ts.map +1 -1
  14. package/dist/types/mods/mod-change-batcher.d.ts +6 -0
  15. package/dist/types/mods/mod-change-batcher.d.ts.map +1 -0
  16. package/dist/types/mods/mod-engine.d.ts.map +1 -1
  17. package/dist/types/providers/byok-providers.d.ts.map +1 -1
  18. package/dist/types/tools/impl/exec-command.d.ts.map +1 -1
  19. package/dist/types/tools/impl/shell-runner.d.ts +1 -0
  20. package/dist/types/tools/impl/shell-runner.d.ts.map +1 -1
  21. package/dist/types/tools/toolset.d.ts +1 -1
  22. package/dist/types/tools/toolset.d.ts.map +1 -1
  23. package/dist/types/types/external-tool-protocol.d.ts +2 -2
  24. package/dist/types/types/external-tool-protocol.d.ts.map +1 -1
  25. package/dist/types/types/protocol_v2.d.ts +27 -46
  26. package/dist/types/types/protocol_v2.d.ts.map +1 -1
  27. package/dist/types/types/runtime-scope.d.ts +4 -2
  28. package/dist/types/types/runtime-scope.d.ts.map +1 -1
  29. package/dist/types/types/runtime-start-protocol.d.ts +21 -0
  30. package/dist/types/types/runtime-start-protocol.d.ts.map +1 -0
  31. package/dist/types/websocket/listener/protocol-outbound.d.ts +1 -1
  32. package/dist/types/websocket/listener/protocol-outbound.d.ts.map +1 -1
  33. package/dist/types/websocket/listener/scope.d.ts +1 -1
  34. package/dist/types/websocket/listener/scope.d.ts.map +1 -1
  35. package/letta.js +1108 -450
  36. package/package.json +2 -2
  37. package/scripts/codex-watch/release-analysis.test.ts +8 -16
  38. package/scripts/codex-watch/release-analysis.ts +1 -3
  39. package/scripts/codex-watch/tracker.test.ts +7 -28
  40. package/scripts/codex-watch/tracker.ts +26 -17
  41. package/scripts/source-file-size-baseline.json +4 -4
  42. package/skills/self-configuration/SKILL.md +20 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.30.30",
3
+ "version": "0.30.32",
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.10",
@@ -119,7 +119,7 @@
119
119
  "access": "public"
120
120
  },
121
121
  "dependencies": {
122
- "@earendil-works/pi-ai": "0.84.2",
122
+ "@earendil-works/pi-ai": "0.84.3",
123
123
  "@janhapke/sharp-electron": "0.35.3-electron.1",
124
124
  "@letta-ai/letta-client": "^1.10.2",
125
125
  "@letta-ai/trajectory": "0.2.0",
@@ -23,25 +23,17 @@ const STABLES = [
23
23
 
24
24
  describe("Codex stable release backlog", () => {
25
25
  test("selects the first release after the durable cursor", () => {
26
- expect(
27
- findNextStableRelease(STABLES, "rust-v0.1.0")?.tag_name,
28
- ).toBe("rust-v0.2.0");
26
+ expect(findNextStableRelease(STABLES, "rust-v0.1.0")?.tag_name).toBe(
27
+ "rust-v0.2.0",
28
+ );
29
29
  });
30
30
 
31
31
  test("identifies only adjacent stable release pairs", () => {
32
32
  expect(
33
- areAdjacentStableReleases(
34
- STABLES,
35
- "rust-v0.1.0",
36
- "rust-v0.2.0",
37
- ),
33
+ areAdjacentStableReleases(STABLES, "rust-v0.1.0", "rust-v0.2.0"),
38
34
  ).toBe(true);
39
35
  expect(
40
- areAdjacentStableReleases(
41
- STABLES,
42
- "rust-v0.1.0",
43
- "rust-v0.3.0",
44
- ),
36
+ areAdjacentStableReleases(STABLES, "rust-v0.1.0", "rust-v0.3.0"),
45
37
  ).toBe(false);
46
38
  });
47
39
 
@@ -50,8 +42,8 @@ describe("Codex stable release backlog", () => {
50
42
  });
51
43
 
52
44
  test("fails instead of skipping an unavailable cursor", () => {
53
- expect(() =>
54
- findNextStableRelease(STABLES, "rust-v0.0.9"),
55
- ).toThrow("Could not find terminal release rust-v0.0.9");
45
+ expect(() => findNextStableRelease(STABLES, "rust-v0.0.9")).toThrow(
46
+ "Could not find terminal release rust-v0.0.9",
47
+ );
56
48
  });
57
49
  });
@@ -212,9 +212,7 @@ export function areAdjacentStableReleases(
212
212
  previousTag: string,
213
213
  currentTag: string,
214
214
  ): boolean {
215
- return (
216
- findPreviousStable(stables, currentTag)?.tag_name === previousTag
217
- );
215
+ return findPreviousStable(stables, currentTag)?.tag_name === previousTag;
218
216
  }
219
217
 
220
218
  export function findNextStableRelease(
@@ -37,7 +37,7 @@ function analysis(
37
37
 
38
38
  function withoutAuditCursor(body: string): string {
39
39
  return body
40
- .replace(/ "audit_cursor_tag": (?:null|"[^"]+"),\n/, "")
40
+ .replace(/ {2}"audit_cursor_tag": (?:null|"[^"]+"),\n/, "")
41
41
  .replace(' "audit_cursor_validated": true,\n', "");
42
42
  }
43
43
 
@@ -198,11 +198,7 @@ describe("tracker state", () => {
198
198
 
199
199
  const body = renderTrackerBody(state);
200
200
  expect(
201
- hasProcessedRange(
202
- parseTrackerState(body),
203
- "rust-v0.1.0",
204
- "rust-v0.2.0",
205
- ),
201
+ hasProcessedRange(parseTrackerState(body), "rust-v0.1.0", "rust-v0.2.0"),
206
202
  ).toBe(true);
207
203
  expect(body).toContain("_No actionable releases recorded yet._");
208
204
  expect(body).toContain("no watched changes");
@@ -215,23 +211,16 @@ describe("tracker state", () => {
215
211
  );
216
212
  state = upsertTrackerEntry(state, entry(2, "error"));
217
213
 
218
- expect(
219
- hasProcessedRange(state, "rust-v0.1.0", "rust-v0.2.0"),
220
- ).toBe(false);
214
+ expect(hasProcessedRange(state, "rust-v0.1.0", "rust-v0.2.0")).toBe(false);
221
215
  expect(getCodexAuditCursorTag(state)).toBe("rust-v0.1.0");
222
216
 
223
217
  state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
224
- expect(
225
- hasProcessedRange(state, "rust-v0.1.0", "rust-v0.2.0"),
226
- ).toBe(true);
218
+ expect(hasProcessedRange(state, "rust-v0.1.0", "rust-v0.2.0")).toBe(true);
227
219
  expect(getCodexAuditCursorTag(state)).toBe("rust-v0.2.0");
228
220
  });
229
221
 
230
222
  test("uses an errored release baseline when no terminal entry exists", () => {
231
- const state = upsertTrackerEntry(
232
- emptyTrackerState(),
233
- entry(2, "error"),
234
- );
223
+ const state = upsertTrackerEntry(emptyTrackerState(), entry(2, "error"));
235
224
 
236
225
  expect(getCodexAuditCursorTag(state)).toBe("rust-v0.1.0");
237
226
  });
@@ -279,12 +268,7 @@ describe("tracker state", () => {
279
268
  expect(getCodexAuditCursorTag(state)).toBe("rust-v0.1.0");
280
269
  expect(
281
270
  getCodexAuditCursorTag(
282
- advanceCodexAuditCursor(
283
- state,
284
- "rust-v0.1.0",
285
- "rust-v0.5.0",
286
- false,
287
- ),
271
+ advanceCodexAuditCursor(state, "rust-v0.1.0", "rust-v0.5.0", false),
288
272
  ),
289
273
  ).toBe("rust-v0.1.0");
290
274
  });
@@ -298,12 +282,7 @@ describe("tracker state", () => {
298
282
  state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
299
283
 
300
284
  expect(getCodexAuditCursorTag(state)).toBe("rust-v0.2.0");
301
- state = advanceCodexAuditCursor(
302
- state,
303
- "rust-v0.2.0",
304
- "rust-v0.3.0",
305
- true,
306
- );
285
+ state = advanceCodexAuditCursor(state, "rust-v0.2.0", "rust-v0.3.0", true);
307
286
  expect(getCodexAuditCursorTag(state)).toBe("rust-v0.3.0");
308
287
  });
309
288
 
@@ -121,7 +121,8 @@ export function advanceCodexAuditCursor(
121
121
  currentTag: string,
122
122
  isAdjacentRelease: boolean,
123
123
  ): TrackerState {
124
- if (!isAdjacentRelease || state.audit_cursor_tag !== previousTag) return state;
124
+ if (!isAdjacentRelease || state.audit_cursor_tag !== previousTag)
125
+ return state;
125
126
  if (!hasProcessedRange(state, previousTag, currentTag)) {
126
127
  throw new Error(
127
128
  `Cannot advance Codex audit cursor without terminal ${previousTag}...${currentTag}`,
@@ -135,17 +136,21 @@ export function recordAnalysis(
135
136
  options: RecordAnalysisOptions,
136
137
  ): TrackerState {
137
138
  const processedAt = options.processedAt ?? new Date().toISOString();
138
- return upsertTrackerEntry(state, {
139
- tag: options.analysis.current_tag,
140
- previous_tag: options.analysis.previous_tag,
141
- verdict: options.analysis.verdict,
142
- outcome: options.outcome,
143
- pr_url: options.prUrl ?? null,
144
- notes: options.notes,
145
- processed_at: processedAt,
146
- compare_url: options.analysis.compare_url,
147
- workflow_run_url: options.analysis.workflow_run_url,
148
- }, options.analysis.is_adjacent_release);
139
+ return upsertTrackerEntry(
140
+ state,
141
+ {
142
+ tag: options.analysis.current_tag,
143
+ previous_tag: options.analysis.previous_tag,
144
+ verdict: options.analysis.verdict,
145
+ outcome: options.outcome,
146
+ pr_url: options.prUrl ?? null,
147
+ notes: options.notes,
148
+ processed_at: processedAt,
149
+ compare_url: options.analysis.compare_url,
150
+ workflow_run_url: options.analysis.workflow_run_url,
151
+ },
152
+ options.analysis.is_adjacent_release,
153
+ );
149
154
  }
150
155
 
151
156
  export function upsertTrackerEntry(
@@ -179,7 +184,10 @@ export function upsertTrackerEntry(
179
184
  ),
180
185
  ];
181
186
  let processed = candidates.slice(0, HIDDEN_STATE_LIMIT);
182
- if (auditCursorTag !== null && !isSupportedAuditCursor(processed, auditCursorTag)) {
187
+ if (
188
+ auditCursorTag !== null &&
189
+ !isSupportedAuditCursor(processed, auditCursorTag)
190
+ ) {
183
191
  const support = candidates.find((candidate) =>
184
192
  isAuditCursorSupportEntry(candidate, auditCursorTag),
185
193
  );
@@ -276,7 +284,10 @@ function escapeTable(value: string): string {
276
284
 
277
285
  function normalizeState(value: unknown): TrackerState {
278
286
  if (!isRecord(value)) throw new TypeError("tracker state must be an object");
279
- if (!Array.isArray(value.processed) || !value.processed.every(isTrackerEntry)) {
287
+ if (
288
+ !Array.isArray(value.processed) ||
289
+ !value.processed.every(isTrackerEntry)
290
+ ) {
280
291
  throw new TypeError("tracker processed entries are invalid");
281
292
  }
282
293
  if (
@@ -346,9 +357,7 @@ function isSupportedAuditCursor(
346
357
  processed: TrackerEntry[],
347
358
  cursorTag: string,
348
359
  ): boolean {
349
- return processed.some((entry) =>
350
- isAuditCursorSupportEntry(entry, cursorTag),
351
- );
360
+ return processed.some((entry) => isAuditCursorSupportEntry(entry, cursorTag));
352
361
  }
353
362
 
354
363
  function isAuditCursorSupportEntry(
@@ -16,7 +16,7 @@
16
16
  "src/cli/components/ModelSelector.tsx": 1259,
17
17
  "src/cli/components/ProviderSelector.tsx": 1692,
18
18
  "src/cli/components/ToolCallMessageRich.tsx": 1109,
19
- "src/cli/helpers/accumulator.ts": 1600,
19
+ "src/cli/helpers/accumulator.ts": 1592,
20
20
  "src/cli/helpers/reflection-transcript.ts": 1956,
21
21
  "src/cli/mods/local-mod-loader.test.ts": 1043,
22
22
  "src/cli/reflection-transcript.test.ts": 1084,
@@ -37,13 +37,13 @@
37
37
  "src/settings-manager.ts": 2102,
38
38
  "src/tools/manager.ts": 2993,
39
39
  "src/tools/tool-execution-context.test.ts": 1138,
40
- "src/types/protocol_v2.ts": 2848,
40
+ "src/types/protocol_v2.ts": 2834,
41
41
  "src/websocket/listen-client-concurrency.test.ts": 2686,
42
42
  "src/websocket/listen-client-protocol.test.ts": 5819,
43
43
  "src/websocket/listener/commands/memory.ts": 1114,
44
44
  "src/websocket/listener/file-commands.ts": 1053,
45
45
  "src/websocket/listener/lifecycle.ts": 1048,
46
- "src/websocket/listener/protocol-inbound.ts": 2242,
46
+ "src/websocket/listener/protocol-inbound.ts": 2237,
47
47
  "src/websocket/listener/protocol-outbound.ts": 1047,
48
- "src/websocket/listener/turn.ts": 1070
48
+ "src/websocket/listener/turn.ts": 1058
49
49
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: self-configuration
3
- description: Inspect or modify Letta Code's own memory, model, context window, system prompt, compaction, permissions, toolsets, mods, skills, channels, schedules, and local runtime settings. Use when the user asks how this agent or conversation is configured, or asks you to change how you behave or how the harness runs you.
3
+ description: Inspect or modify Letta Code's own memory, model, context window, system prompt, compaction, permissions, toolsets, mods, skills, channels, schedules, agent secrets, and local runtime settings. Use when the user asks how this agent or conversation is configured, or asks you to change how you behave or how the harness runs you.
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -22,6 +22,7 @@ The important part is choosing the right layer. Do not smear a preference into d
22
22
  | Skills | Reusable procedural knowledge or bundled scripts | Load `creating-skills` or `acquiring-skills` |
23
23
  | Channels | Slack/Discord/Telegram/WhatsApp/Signal accounts, pairing, routing, listener state | Use `letta channels` or channel commands |
24
24
  | Schedules | Reminders and recurring prompts | Load `scheduling-tasks` and use `letta cron` |
25
+ | Agent secrets | Per-agent `$NAME` credential values for shell commands | Use `letta secret` (or `/secret` in a session) |
25
26
 
26
27
  Decision rule: if the model should remember and reason about it, use memory. If the runtime must enforce it or route it before the model decides anything, use settings, API fields, mods, channels, or schedules.
27
28
 
@@ -370,6 +371,24 @@ Never print provider keys. Shell expansion such as `--api-key "$OPENAI_API_KEY"`
370
371
 
371
372
  After connecting, verify the provider/model from the same backend and process that will run the agent. Do not infer success from a saved credential alone.
372
373
 
374
+ ## Agent secrets
375
+
376
+ Agent-scoped secrets hold credential values that are referenced as `$NAME` in shell commands. Cloud agents store them server-side on the agent; local agents use OS secure storage. The harness substitutes `$NAME` at exec time and scrubs values from tool output, so values never enter agent context.
377
+
378
+ ```bash
379
+ letta secret list # names only, never values
380
+ letta secret set GITHUB_TOKEN --env GITHUB_TOKEN # ingest from the environment
381
+ openssl rand -hex 32 | letta secret set WEBHOOK_TOKEN --stdin # generate without seeing the value
382
+ letta secret unset GITHUB_TOKEN # aliases: delete | remove | rm
383
+ ```
384
+
385
+ Rules:
386
+
387
+ - Pass the source variable *name* to `--env`, not `$NAME`. `--env $GITHUB_TOKEN` triggers harness substitution and places the resolved value in process arguments; `--env GITHUB_TOKEN` reads it from the CLI process environment without exposure.
388
+ - Never echo secret values into tool output. Pipe generated credentials straight into `--stdin`.
389
+ - Inside a session, `AGENT_ID`/`LETTA_AGENT_ID` resolves the target automatically; pass `--agent <agent-id>` otherwise.
390
+ - A running session loads its secret cache at startup; CLI-side changes apply to new sessions. The `/secret` slash command manages the same store interactively and refreshes the live cache.
391
+
373
392
  ## Channels
374
393
 
375
394
  Use channels when the user wants to talk through Slack, Discord, Telegram, WhatsApp, or Signal.