@letta-ai/letta-code 0.32.7 → 0.32.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.
Files changed (42) hide show
  1. package/dist/mcp-client.js +2 -2
  2. package/dist/mcp-client.js.map +1 -1
  3. package/dist/types/agent/message-attribution.d.ts +10 -0
  4. package/dist/types/agent/message-attribution.d.ts.map +1 -0
  5. package/dist/types/agent/subagents/manager.d.ts.map +1 -1
  6. package/dist/types/agent/subagents/names.d.ts +7 -0
  7. package/dist/types/agent/subagents/names.d.ts.map +1 -0
  8. package/dist/types/agent/subagents/subagent-launcher.d.ts +2 -0
  9. package/dist/types/agent/subagents/subagent-launcher.d.ts.map +1 -1
  10. package/dist/types/backend/api/agent-message.d.ts +5 -0
  11. package/dist/types/backend/api/agent-message.d.ts.map +1 -1
  12. package/dist/types/backend/api/metadata.d.ts +1 -0
  13. package/dist/types/backend/api/metadata.d.ts.map +1 -1
  14. package/dist/types/backend/backend.d.ts.map +1 -1
  15. package/dist/types/queue/queue-runtime.d.ts.map +1 -1
  16. package/dist/types/runtime-context.d.ts +2 -0
  17. package/dist/types/runtime-context.d.ts.map +1 -1
  18. package/dist/types/telemetry/index.d.ts +1 -0
  19. package/dist/types/telemetry/index.d.ts.map +1 -1
  20. package/dist/types/tools/impl/send-agent-message.d.ts.map +1 -1
  21. package/dist/types/tools/impl/task.d.ts.map +1 -1
  22. package/dist/types/tools/manager.d.ts +1 -3
  23. package/dist/types/tools/manager.d.ts.map +1 -1
  24. package/dist/types/types/protocol_v2.d.ts +3 -5
  25. package/dist/types/types/protocol_v2.d.ts.map +1 -1
  26. package/dist/types/utils/subagent-launch-marker.d.ts +1 -0
  27. package/dist/types/utils/subagent-launch-marker.d.ts.map +1 -1
  28. package/dist/types/websocket/listener/protocol-outbound.d.ts.map +1 -1
  29. package/dist/types/websocket/listener/runtime.d.ts.map +1 -1
  30. package/dist/types/websocket/listener/types.d.ts +12 -9
  31. package/dist/types/websocket/listener/types.d.ts.map +1 -1
  32. package/letta.js +500 -292
  33. package/package.json +1 -1
  34. package/scripts/run-unit-tests.cjs +11 -1
  35. package/scripts/source-file-size-baseline.json +5 -5
  36. package/scripts/test-home-preload.ts +9 -1
  37. package/scripts/test-sharding.cjs +16 -0
  38. package/scripts/test-sharding.test.cjs +37 -0
  39. package/scripts/unit-test-impact.cjs +14 -6
  40. package/scripts/unit-test-impact.test.cjs +41 -9
  41. package/skills/letta-guide/SKILL.md +3 -1
  42. package/skills/working-across-computers/SKILL.md +5 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.32.7",
3
+ "version": "0.32.9",
4
4
  "lettaStartupLogProtocol": 1,
5
5
  "description": "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
6
6
  "type": "module",
@@ -2,6 +2,7 @@
2
2
  const { execFileSync } = require("node:child_process");
3
3
  const { readFileSync, readdirSync } = require("node:fs");
4
4
  const path = require("node:path");
5
+ const { shardTestFiles } = require("./test-sharding.cjs");
5
6
  const {
6
7
  buildFamilyImpactIndex,
7
8
  getGitChangedFiles,
@@ -77,6 +78,7 @@ const allTestFiles = [
77
78
  ...findTestFiles("scripts/codex-watch"),
78
79
  ...findTestFiles("scripts/claude-watch"),
79
80
  "scripts/unit-test-impact.test.cjs",
81
+ "scripts/test-sharding.test.cjs",
80
82
  ].sort();
81
83
  const discoveredPaths = new Set(allTestFiles);
82
84
 
@@ -192,7 +194,15 @@ function selectTestFiles() {
192
194
  }
193
195
 
194
196
  let exitCode = 0;
195
- const selectedTestFiles = selectTestFiles();
197
+ const shardIndex = process.argv.indexOf("--shard");
198
+ const shard = shardIndex === -1 ? undefined : (process.argv[shardIndex + 1] ?? "");
199
+ // Validate outside selection's full-suite fallback: an invalid shard must fail,
200
+ // not make several jobs duplicate the entire suite or silently run nothing.
201
+ shardTestFiles([], shard);
202
+ const selectedTestFiles = shardTestFiles(selectTestFiles(), shard);
203
+ if (shard) {
204
+ console.log(`unit-test shard ${shard}: ${selectedTestFiles.length} files`);
205
+ }
196
206
  const selectedTestPaths = new Set(selectedTestFiles);
197
207
 
198
208
  // Bun module mocks and process-global state are shared within one test process.
@@ -23,7 +23,7 @@
23
23
  "src/cli/subcommands/skills.ts": 1264,
24
24
  "src/headless.ts": 4864,
25
25
  "src/hooks/integration.test.ts": 1147,
26
- "src/index.ts": 2616,
26
+ "src/index.ts": 2612,
27
27
  "src/mods/learning-harness.ts": 2434,
28
28
  "src/mods/mod-engine.test.ts": 2153,
29
29
  "src/mods/mod-engine.ts": 1838,
@@ -37,11 +37,11 @@
37
37
  "src/settings-manager.ts": 2102,
38
38
  "src/tools/manager.ts": 2897,
39
39
  "src/tools/tool-execution-context.test.ts": 1136,
40
- "src/types/protocol_v2.ts": 2595,
41
- "src/websocket/listen-client-concurrency.test.ts": 2683,
40
+ "src/types/protocol_v2.ts": 2593,
41
+ "src/websocket/listen-client-concurrency.test.ts": 2666,
42
42
  "src/websocket/listen-client-protocol.test.ts": 5762,
43
43
  "src/websocket/listener/commands/memory.ts": 1114,
44
44
  "src/websocket/listener/file-commands.ts": 1053,
45
- "src/websocket/listener/protocol-inbound.ts": 2143,
46
- "src/websocket/listener/protocol-outbound.ts": 1025
45
+ "src/websocket/listener/protocol-inbound.ts": 2137,
46
+ "src/websocket/listener/protocol-outbound.ts": 1022
47
47
  }
@@ -1,4 +1,4 @@
1
- import { afterAll } from "bun:test";
1
+ import { afterAll, afterEach } from "bun:test";
2
2
  import { mkdtempSync, rmSync } from "node:fs";
3
3
  import { createRequire } from "node:module";
4
4
  import { basename, isAbsolute, join, relative, resolve } from "node:path";
@@ -57,6 +57,14 @@ process.env.LETTA_CODE_TELEM ??= "0";
57
57
  // the redirected environment before their runtimes initialize.
58
58
  os.homedir = () => testHome;
59
59
 
60
+ // Load after redirecting home: channel config captures its filesystem root.
61
+ const routes = await import("@/channels/routing");
62
+ afterEach(() => {
63
+ routes.clearAllRoutes();
64
+ routes.__testOverrideLoadRoutes(null);
65
+ routes.__testOverrideSaveRoutes(null);
66
+ });
67
+
60
68
  if (!configuredTestHome) {
61
69
  const cleanup = () => {
62
70
  rmSync(testHome, { recursive: true, force: true });
@@ -0,0 +1,16 @@
1
+ // Assign files before launching Bun, including suites that need their own
2
+ // process. Sharding each Bun invocation separately would duplicate or omit
3
+ // isolated suites. Keep file order within each shard unchanged.
4
+ function shardTestFiles(files, shard) {
5
+ if (shard === undefined) return files;
6
+ if (!/^[1-9]\d*\/[1-9]\d*$/u.test(shard)) {
7
+ throw new Error("Expected --shard INDEX/COUNT, with 1 <= INDEX <= COUNT");
8
+ }
9
+ const [index, count] = shard.split("/").map(Number);
10
+ if (!Number.isSafeInteger(count) || index > count) {
11
+ throw new Error("Expected --shard INDEX/COUNT, with 1 <= INDEX <= COUNT");
12
+ }
13
+ return files.filter((_, position) => position % count === index - 1);
14
+ }
15
+
16
+ module.exports = { shardTestFiles };
@@ -0,0 +1,37 @@
1
+ const { describe, expect, test } = require("bun:test");
2
+ const { shardTestFiles } = require("./test-sharding.cjs");
3
+ const { tests: isolated } = require("./isolated-unit-tests.json");
4
+
5
+ describe("unit test sharding", () => {
6
+ const files = [
7
+ ...isolated.map(({ path }) => path),
8
+ ...Array.from({ length: 103 }, (_, i) => `src/tools/file-${i}.test.ts`),
9
+ ].sort();
10
+
11
+ test.each([1, 4, 8, 200])("%i shards cover every file exactly once", (count) => {
12
+ const shards = Array.from({ length: count }, (_, i) =>
13
+ shardTestFiles(files, `${i + 1}/${count}`),
14
+ );
15
+ expect(shards.flat().sort()).toEqual(files);
16
+ expect(new Set(shards.flat()).size).toBe(files.length);
17
+ for (const shard of shards) {
18
+ expect(shard).toEqual(files.filter((file) => shard.includes(file)));
19
+ }
20
+ });
21
+
22
+ test("selection stays empty rather than expanding into all tests", () => {
23
+ expect(shardTestFiles([], "1/8")).toEqual([]);
24
+ expect(shardTestFiles([files[0]], "8/8")).toEqual([]);
25
+ });
26
+
27
+ test("local runs without a shard preserve the full selection", () => {
28
+ expect(shardTestFiles(files)).toEqual(files);
29
+ });
30
+
31
+ test.each(["", "0/4", "1/0", "5/4", "-1/4", "1.5/4", "1/2/3", "1/Infinity"])(
32
+ "rejects invalid shard %s rather than silently dropping coverage",
33
+ (shard) => {
34
+ expect(() => shardTestFiles(files, shard)).toThrow("Expected --shard");
35
+ },
36
+ );
37
+ });
@@ -527,14 +527,22 @@ function getGitChangedFiles(baseSha, headSha, rootDir = process.cwd()) {
527
527
  });
528
528
  }
529
529
 
530
- function readPullRequestShas(eventPath) {
530
+ function readPullRequestShas(eventPath, rootDir = process.cwd()) {
531
531
  const event = JSON.parse(readFileSync(eventPath, "utf8"));
532
- const baseSha = event.pull_request?.base?.sha;
533
- const headSha =
534
- event.pull_request?.merge_commit_sha ?? event.pull_request?.head?.sha;
535
- if (typeof baseSha !== "string" || typeof headSha !== "string") {
536
- throw new Error("Pull request event is missing base/head SHAs");
532
+ if (!event.pull_request) {
533
+ throw new Error("Expected a pull request event");
537
534
  }
535
+ // GitHub can enqueue an event whose merge_commit_sha differs from the merge
536
+ // actions/checkout fetches. Compare the tested tree with its own base parent;
537
+ // both are present at fetch-depth: 2. Requiring HEAD^2 also rejects a checkout
538
+ // of a branch head, so the caller safely runs everything in that case.
539
+ const [baseSha, , headSha] = execFileSync(
540
+ "git",
541
+ ["rev-parse", "HEAD^1", "HEAD^2", "HEAD"],
542
+ { cwd: rootDir, encoding: "utf8" },
543
+ )
544
+ .trim()
545
+ .split(/\r?\n/u);
538
546
  return { baseSha, headSha };
539
547
  }
540
548
 
@@ -1,10 +1,13 @@
1
1
  const { describe, expect, test } = require("bun:test");
2
+ const { execFileSync } = require("node:child_process");
2
3
  const { mkdtempSync, rmSync, writeFileSync } = require("node:fs");
3
4
  const { tmpdir } = require("node:os");
4
5
  const { join } = require("node:path");
6
+ const { pathToFileURL } = require("node:url");
5
7
  const {
6
8
  ROOT_FAMILY,
7
9
  buildFamilyImpactIndex,
10
+ getGitChangedFiles,
8
11
  planUnitTests,
9
12
  readPullRequestShas,
10
13
  } = require("./unit-test-impact.cjs");
@@ -233,29 +236,58 @@ describe("unit-test impact planning", () => {
233
236
  expect(result.reason).toBe("no changed files were provided");
234
237
  });
235
238
 
236
- test("pull request selection compares the base with the tested merge tree", () => {
239
+ test("selection uses the shallow checkout when the event names a stale merge", () => {
237
240
  const directory = mkdtempSync(join(tmpdir(), "unit-impact-event-"));
238
241
  const eventPath = join(directory, "event.json");
242
+ const checkout = join(directory, "checkout");
243
+ const git = (...args) =>
244
+ execFileSync("git", args, {
245
+ cwd: directory,
246
+ encoding: "utf8",
247
+ stdio: ["ignore", "pipe", "pipe"],
248
+ env: {
249
+ ...process.env,
250
+ GIT_AUTHOR_NAME: "Test",
251
+ GIT_AUTHOR_EMAIL: "test@example.com",
252
+ GIT_COMMITTER_NAME: "Test",
253
+ GIT_COMMITTER_EMAIL: "test@example.com",
254
+ },
255
+ }).trim();
239
256
  try {
257
+ git("init", "-b", "main");
258
+ writeFileSync(join(directory, "base.txt"), "base");
259
+ git("add", "base.txt");
260
+ git("-c", "core.hooksPath=", "commit", "-m", "base");
261
+ const baseSha = git("rev-parse", "HEAD");
262
+ git("checkout", "-b", "feature");
263
+ writeFileSync(join(directory, "changed.txt"), "feature");
264
+ git("add", "changed.txt");
265
+ git("-c", "core.hooksPath=", "commit", "-m", "feature");
266
+ const branchHead = git("rev-parse", "HEAD");
267
+ git("checkout", "main");
268
+ git("-c", "core.hooksPath=", "merge", "--no-ff", "feature", "-m", "merge");
269
+ const headSha = git("rev-parse", "HEAD");
270
+ git("clone", "--depth=2", pathToFileURL(directory).href, checkout);
240
271
  writeFileSync(
241
272
  eventPath,
242
273
  JSON.stringify({
243
274
  pull_request: {
244
- base: { sha: "base-sha" },
245
- head: { sha: "branch-head-sha" },
246
- merge_commit_sha: "tested-merge-sha",
275
+ base: { sha: baseSha },
276
+ head: { sha: branchHead },
277
+ merge_commit_sha: "700fce0cf421cd048f810e40822d608d7c81a2bc",
247
278
  },
248
279
  }),
249
280
  );
250
281
 
251
- expect(readPullRequestShas(eventPath)).toEqual({
252
- baseSha: "base-sha",
253
- headSha: "tested-merge-sha",
254
- });
282
+ const shas = readPullRequestShas(eventPath, checkout);
283
+ expect(shas).toEqual({ baseSha, headSha });
284
+ expect(getGitChangedFiles(shas.baseSha, shas.headSha, checkout)).toEqual([
285
+ { status: "A", path: "changed.txt", previousPath: undefined },
286
+ ]);
255
287
  } finally {
256
288
  rmSync(directory, { recursive: true, force: true });
257
289
  }
258
- });
290
+ }, 30000);
259
291
  });
260
292
 
261
293
  describe("current repository impact graph", () => {
@@ -1,10 +1,12 @@
1
1
  ---
2
2
  name: letta-guide
3
- description: Read the official Letta documentation (docs.letta.com) through its cached, ETag-checked fetch route. Load before ANY docs.letta.com retrieval — answering how Letta works, what Letta (or you) can do, setting up providers, models, channels, skills, memory, schedules, permissions, self-hosting, pricing, or billing, AND looking up Letta API, Agent SDK, or Letta Code reference while writing code. Do not use fetch_webpage or web_search on docs.letta.com; this skill's helper is the docs route. Never answer Letta product questions from memory alone.
3
+ description: Read the official Letta documentation (docs.letta.com) through its cached, ETag-checked fetch route. Load before ANY docs.letta.com retrieval — answering how Letta works, what Letta (or you) can do, and looking up Letta API, Agent SDK, or Letta Code reference.
4
4
  ---
5
5
 
6
6
  # Letta Guide
7
7
 
8
+ Do not use fetch_webpage or web_search on docs.letta.com; this skill's helper is the docs route.
9
+
8
10
  You are running inside Letta, but your training data about Letta's commands,
9
11
  flags, settings, UI, pricing, and providers is out of date. Users lose trust
10
12
  fastest when an agent confidently invents product details. This skill defines
@@ -1,10 +1,14 @@
1
1
  ---
2
2
  name: working-across-computers
3
- description: Guides work across Cloud, local, and other available computers, including teleporting the current conversation, orchestrating remote subagents, and uploading or downloading files to Cloud sandboxes. Use when moving between computers (e.g. teleporting between cloud/local) or coordinating work and files across them.
3
+ description: Guides work across Cloud, local, and other available computers, including teleporting conversations, running remote subagents, and transferring files. Load when a request needs the user's laptop or another machine's files, apps, or tools; when connecting a computer; or when moving or delegating work between local and Cloud in either direction, including running Cloud subagents from local.
4
4
  ---
5
5
 
6
6
  # Working Across Computers
7
7
 
8
+ ## Discover access before declaring a limitation
9
+
10
+ The current filesystem is not the full set of computers you can use. When a request needs another computer, run `letta teleport list`. If a suitable computer is available, delegate there; otherwise follow the [connection instructions](references/connect-a-computer-for-remote-access.md). Do not infer that another computer is inaccessible merely because you are running in Cloud.
11
+
8
12
  ## Choose where to work
9
13
 
10
14
  - When running in Cloud, prefer staying there and delegating local work to subagents on the relevant computer.