@letta-ai/letta-code 0.33.1 → 0.33.3

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 (202) hide show
  1. package/README.md +2 -2
  2. package/dist/agent-presets.js +22 -37
  3. package/dist/agent-presets.js.map +3 -3
  4. package/dist/channels-public.js +6 -35
  5. package/dist/channels-public.js.map +3 -3
  6. package/dist/channels-slack.js +2 -2
  7. package/dist/channels-slack.js.map +4 -4
  8. package/dist/gateway-core.js +8 -36
  9. package/dist/gateway-core.js.map +4 -4
  10. package/dist/mcp-client.js +2 -2
  11. package/dist/mcp-client.js.map +1 -1
  12. package/dist/memory-confinement.js.map +1 -1
  13. package/dist/types/agent/approval-execution.d.ts.map +1 -1
  14. package/dist/types/agent/cloud-managed-system-prompt.d.ts +10 -0
  15. package/dist/types/agent/cloud-managed-system-prompt.d.ts.map +1 -0
  16. package/dist/types/agent/create-agent-request.d.ts +4 -0
  17. package/dist/types/agent/create-agent-request.d.ts.map +1 -1
  18. package/dist/types/agent/legacy-default-system-prompt-hashes.d.ts +14 -0
  19. package/dist/types/agent/legacy-default-system-prompt-hashes.d.ts.map +1 -0
  20. package/dist/types/agent/memory-conflict-repair.d.ts +5 -4
  21. package/dist/types/agent/memory-conflict-repair.d.ts.map +1 -1
  22. package/dist/types/agent/memory-constraints-audit.d.ts +19 -0
  23. package/dist/types/agent/memory-constraints-audit.d.ts.map +1 -0
  24. package/dist/types/agent/memory-constraints.d.ts.map +1 -1
  25. package/dist/types/agent/memory-filesystem.d.ts.map +1 -1
  26. package/dist/types/agent/memory-format.d.ts +1 -3
  27. package/dist/types/agent/memory-format.d.ts.map +1 -1
  28. package/dist/types/agent/memory-git-hooks.d.ts +7 -0
  29. package/dist/types/agent/memory-git-hooks.d.ts.map +1 -1
  30. package/dist/types/agent/memory-git.d.ts +3 -12
  31. package/dist/types/agent/memory-git.d.ts.map +1 -1
  32. package/dist/types/agent/modify.d.ts +1 -1
  33. package/dist/types/agent/modify.d.ts.map +1 -1
  34. package/dist/types/agent/subagents/index.d.ts.map +1 -1
  35. package/dist/types/agent/subagents/manager.d.ts.map +1 -1
  36. package/dist/types/agent/subagents/sandbox.d.ts +1 -1
  37. package/dist/types/agent/subagents/subagent-launcher.d.ts +1 -1
  38. package/dist/types/agent/system-prompt-versioning.d.ts +8 -0
  39. package/dist/types/agent/system-prompt-versioning.d.ts.map +1 -1
  40. package/dist/types/backend/api/conversation-enqueue.d.ts +3 -1
  41. package/dist/types/backend/api/conversation-enqueue.d.ts.map +1 -1
  42. package/dist/types/backend/api/request.d.ts +2 -1
  43. package/dist/types/backend/api/request.d.ts.map +1 -1
  44. package/dist/types/backend/local/initial-memory.d.ts +7 -0
  45. package/dist/types/backend/local/initial-memory.d.ts.map +1 -0
  46. package/dist/types/backend/local/local-backend.d.ts.map +1 -1
  47. package/dist/types/backend/local/local-store.d.ts +66 -26
  48. package/dist/types/backend/local/local-store.d.ts.map +1 -1
  49. package/dist/types/backend/local/local-transcript.d.ts +171 -0
  50. package/dist/types/backend/local/local-transcript.d.ts.map +1 -0
  51. package/dist/types/backend/local/local-types.d.ts +8 -0
  52. package/dist/types/backend/local/local-types.d.ts.map +1 -1
  53. package/dist/types/backend/local/system-prompt-compilation.d.ts.map +1 -1
  54. package/dist/types/channels/message-channel-tool-definition.d.ts.map +1 -1
  55. package/dist/types/channels/progress-formatting.d.ts.map +1 -1
  56. package/dist/types/channels/slack/progress.d.ts.map +1 -1
  57. package/dist/types/cli/helpers/tool-name-mapping.d.ts.map +1 -1
  58. package/dist/types/cron/runner.d.ts +16 -68
  59. package/dist/types/cron/runner.d.ts.map +1 -1
  60. package/dist/types/cron/scheduler.d.ts.map +1 -1
  61. package/dist/types/managed-cloud-runtime.d.ts +9 -0
  62. package/dist/types/managed-cloud-runtime.d.ts.map +1 -0
  63. package/dist/types/mods/disabled-mod-adapter.d.ts.map +1 -1
  64. package/dist/types/mods/event-emitter.d.ts +2 -0
  65. package/dist/types/mods/event-emitter.d.ts.map +1 -1
  66. package/dist/types/mods/mod-adapter.d.ts.map +1 -1
  67. package/dist/types/permissions/canonical.d.ts.map +1 -1
  68. package/dist/types/permissions/checker.d.ts.map +1 -1
  69. package/dist/types/permissions/cross-agent-guard.d.ts +2 -2
  70. package/dist/types/permissions/cross-agent-guard.d.ts.map +1 -1
  71. package/dist/types/permissions/mode.d.ts.map +1 -1
  72. package/dist/types/permissions/workspace-sandbox.d.ts.map +1 -1
  73. package/dist/types/queue/queue-runtime.d.ts +2 -0
  74. package/dist/types/queue/queue-runtime.d.ts.map +1 -1
  75. package/dist/types/queue/turn-queue-runtime.d.ts +23 -0
  76. package/dist/types/queue/turn-queue-runtime.d.ts.map +1 -0
  77. package/dist/types/reminders/catalog.d.ts +1 -1
  78. package/dist/types/reminders/catalog.d.ts.map +1 -1
  79. package/dist/types/reminders/disk-space.d.ts +47 -0
  80. package/dist/types/reminders/disk-space.d.ts.map +1 -0
  81. package/dist/types/reminders/engine.d.ts.map +1 -1
  82. package/dist/types/reminders/state.d.ts +2 -0
  83. package/dist/types/reminders/state.d.ts.map +1 -1
  84. package/dist/types/sandbox/availability.d.ts +1 -1
  85. package/dist/types/telemetry/event-queue.d.ts +32 -0
  86. package/dist/types/telemetry/event-queue.d.ts.map +1 -0
  87. package/dist/types/telemetry/index.d.ts +1 -0
  88. package/dist/types/telemetry/index.d.ts.map +1 -1
  89. package/dist/types/tools/external-tool-background-eligibility.d.ts +24 -0
  90. package/dist/types/tools/external-tool-background-eligibility.d.ts.map +1 -0
  91. package/dist/types/tools/external-tool-background.d.ts +28 -0
  92. package/dist/types/tools/external-tool-background.d.ts.map +1 -0
  93. package/dist/types/tools/external-tool-types.d.ts +44 -0
  94. package/dist/types/tools/external-tool-types.d.ts.map +1 -0
  95. package/dist/types/tools/impl/bash.d.ts.map +1 -1
  96. package/dist/types/tools/impl/exec-command.d.ts.map +1 -1
  97. package/dist/types/tools/impl/external-coding-agent.d.ts +2 -0
  98. package/dist/types/tools/impl/external-coding-agent.d.ts.map +1 -1
  99. package/dist/types/tools/impl/github-pull-request-diff.d.ts +7 -0
  100. package/dist/types/tools/impl/github-pull-request-diff.d.ts.map +1 -0
  101. package/dist/types/tools/impl/github-pull-request-query.d.ts +4 -0
  102. package/dist/types/tools/impl/github-pull-request-query.d.ts.map +1 -0
  103. package/dist/types/tools/impl/github-pull-request-state.d.ts +11 -0
  104. package/dist/types/tools/impl/github-pull-request-state.d.ts.map +1 -0
  105. package/dist/types/tools/impl/github-pull-request-types.d.ts +53 -0
  106. package/dist/types/tools/impl/github-pull-request-types.d.ts.map +1 -0
  107. package/dist/types/tools/impl/github-pull-request-watch.d.ts +16 -0
  108. package/dist/types/tools/impl/github-pull-request-watch.d.ts.map +1 -0
  109. package/dist/types/tools/impl/kill-bash.d.ts +0 -8
  110. package/dist/types/tools/impl/kill-bash.d.ts.map +1 -1
  111. package/dist/types/tools/impl/memory-task-lifecycle.d.ts +9 -8
  112. package/dist/types/tools/impl/memory-task-lifecycle.d.ts.map +1 -1
  113. package/dist/types/tools/impl/monitor.d.ts +20 -0
  114. package/dist/types/tools/impl/monitor.d.ts.map +1 -1
  115. package/dist/types/tools/impl/process_manager.d.ts +2 -2
  116. package/dist/types/tools/impl/process_manager.d.ts.map +1 -1
  117. package/dist/types/tools/impl/send-agent-message.d.ts.map +1 -1
  118. package/dist/types/tools/impl/shell-env.d.ts +1 -1
  119. package/dist/types/tools/impl/shell-env.d.ts.map +1 -1
  120. package/dist/types/tools/impl/skill.d.ts +0 -6
  121. package/dist/types/tools/impl/skill.d.ts.map +1 -1
  122. package/dist/types/tools/impl/task-stop.d.ts.map +1 -1
  123. package/dist/types/tools/impl/tool-return-clamp.d.ts +2 -3
  124. package/dist/types/tools/impl/tool-return-clamp.d.ts.map +1 -1
  125. package/dist/types/tools/impl/truncation.d.ts +0 -16
  126. package/dist/types/tools/impl/truncation.d.ts.map +1 -1
  127. package/dist/types/tools/impl/wake.d.ts.map +1 -1
  128. package/dist/types/tools/impl/watch-pr.d.ts +32 -0
  129. package/dist/types/tools/impl/watch-pr.d.ts.map +1 -0
  130. package/dist/types/tools/impl/windows-command-safety.d.ts +9 -0
  131. package/dist/types/tools/impl/windows-command-safety.d.ts.map +1 -0
  132. package/dist/types/tools/impl/workflow.d.ts +3 -2
  133. package/dist/types/tools/impl/workflow.d.ts.map +1 -1
  134. package/dist/types/tools/manager.d.ts +4 -56
  135. package/dist/types/tools/manager.d.ts.map +1 -1
  136. package/dist/types/tools/removed-tools.d.ts +4 -0
  137. package/dist/types/tools/removed-tools.d.ts.map +1 -0
  138. package/dist/types/tools/task-tool-assets.d.ts +9 -0
  139. package/dist/types/tools/task-tool-assets.d.ts.map +1 -1
  140. package/dist/types/tools/tool-definitions.d.ts +2 -93
  141. package/dist/types/tools/tool-definitions.d.ts.map +1 -1
  142. package/dist/types/tools/toolset-catalog.d.ts +1 -1
  143. package/dist/types/tools/toolset-catalog.d.ts.map +1 -1
  144. package/dist/types/tools/toolset.d.ts.map +1 -1
  145. package/dist/types/tools/workflow/sdk-spawner.d.ts.map +1 -1
  146. package/dist/types/tools/workflow/types.d.ts +4 -0
  147. package/dist/types/tools/workflow/types.d.ts.map +1 -1
  148. package/dist/types/types/background-process-protocol.d.ts +1 -1
  149. package/dist/types/types/background-process-protocol.d.ts.map +1 -1
  150. package/dist/types/types/external-tool-protocol.d.ts +4 -0
  151. package/dist/types/types/external-tool-protocol.d.ts.map +1 -1
  152. package/dist/types/utils/debug.d.ts +11 -1
  153. package/dist/types/utils/debug.d.ts.map +1 -1
  154. package/dist/types/utils/message-queue-bridge.d.ts +3 -0
  155. package/dist/types/utils/message-queue-bridge.d.ts.map +1 -1
  156. package/dist/types/utils/task-notifications.d.ts +1 -1
  157. package/dist/types/utils/task-notifications.d.ts.map +1 -1
  158. package/dist/types/websocket/listener/queue.d.ts.map +1 -1
  159. package/dist/types/websocket/listener/runtime.d.ts +2 -0
  160. package/dist/types/websocket/listener/runtime.d.ts.map +1 -1
  161. package/dist/types/websocket/listener/types.d.ts +4 -0
  162. package/dist/types/websocket/listener/types.d.ts.map +1 -1
  163. package/letta.js +25953 -27978
  164. package/package.json +3 -2
  165. package/scripts/agent-watch/verify-pr-identity.test.ts +52 -0
  166. package/scripts/agent-watch/verify-pr-identity.ts +13 -4
  167. package/scripts/isolated-unit-tests.json +5 -10
  168. package/scripts/postinstall-patches.js +53 -3
  169. package/scripts/source-file-size-baseline.json +8 -10
  170. package/scripts/stage-pypi-deps.mjs +10 -1
  171. package/skills/creating-mods/references/plan-mode.md +6 -8
  172. package/skills/initializing-memory/SKILL.md +122 -640
  173. package/skills/initializing-memory/scripts/history-coverage.mjs +129 -0
  174. package/skills/initializing-memory/scripts/prepare-history.mjs +219 -0
  175. package/skills/scheduling-tasks/SKILL.md +15 -24
  176. package/skills/submitting-feedback/SKILL.md +2 -0
  177. package/skills/workflow-authoring/SKILL.md +6 -5
  178. package/dist/types/agent/memory-markdown.d.ts +0 -25
  179. package/dist/types/agent/memory-markdown.d.ts.map +0 -1
  180. package/dist/types/tools/impl/grep-files.d.ts +0 -19
  181. package/dist/types/tools/impl/grep-files.d.ts.map +0 -1
  182. package/dist/types/tools/impl/list-dir-codex.d.ts +0 -21
  183. package/dist/types/tools/impl/list-dir-codex.d.ts.map +0 -1
  184. package/dist/types/tools/impl/memory-apply-patch.d.ts +0 -10
  185. package/dist/types/tools/impl/memory-apply-patch.d.ts.map +0 -1
  186. package/dist/types/tools/impl/memory.d.ts +0 -20
  187. package/dist/types/tools/impl/memory.d.ts.map +0 -1
  188. package/dist/types/tools/impl/multi-edit.d.ts +0 -21
  189. package/dist/types/tools/impl/multi-edit.d.ts.map +0 -1
  190. package/dist/types/tools/impl/read-file-codex.d.ts +0 -24
  191. package/dist/types/tools/impl/read-file-codex.d.ts.map +0 -1
  192. package/dist/types/tools/impl/shell-command.d.ts +0 -25
  193. package/dist/types/tools/impl/shell-command.d.ts.map +0 -1
  194. package/dist/types/tools/impl/shell.d.ts +0 -25
  195. package/dist/types/tools/impl/shell.d.ts.map +0 -1
  196. package/dist/types/tools/impl/todo-write.d.ts +0 -14
  197. package/dist/types/tools/impl/todo-write.d.ts.map +0 -1
  198. package/dist/types/tools/memory-tool-assets.d.ts +0 -6
  199. package/dist/types/tools/memory-tool-assets.d.ts.map +0 -1
  200. package/dist/types/tools/tool-permissions.d.ts +0 -7
  201. package/dist/types/tools/tool-permissions.d.ts.map +0 -1
  202. package/skills/initializing-memory/ROOT_MEMORY.md +0 -723
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.33.1",
3
+ "version": "0.33.3",
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",
@@ -132,12 +132,13 @@
132
132
  "dependencies": {
133
133
  "@earendil-works/pi-ai": "^0.87.1",
134
134
  "@janhapke/sharp-electron": "0.35.3-electron.1",
135
- "@letta-ai/letta-agent-sdk": "^0.8.11",
135
+ "@letta-ai/letta-agent-sdk": "^0.8.17",
136
136
  "@letta-ai/letta-client": "^1.10.2",
137
137
  "@letta-ai/trajectory": "0.2.0",
138
138
  "@modelcontextprotocol/sdk": "1.30.0",
139
139
  "@pierre/diffs": "1.2.2",
140
140
  "@scarf/scarf": "^1.4.0",
141
+ "@shikijs/langs": "4.0.2",
141
142
  "cron-parser": "^5.6.1",
142
143
  "cross-spawn": "^7.0.6",
143
144
  "glob": "^13.0.0",
@@ -23,11 +23,13 @@ function pullRequest(
23
23
  function enforce(
24
24
  pullRequests: WatcherPullRequest[],
25
25
  closed: string[] = [],
26
+ pullRequestState: "draft" | "ready" = "draft",
26
27
  ): WatcherPullRequest | null {
27
28
  return enforceWatcherPrIdentity({
28
29
  repo: "letta-ai/letta-code",
29
30
  marker,
30
31
  expectedLogin: "amelia-letta",
32
+ pullRequestState,
31
33
  pullRequests,
32
34
  closePullRequest: (candidate) => closed.push(candidate.url),
33
35
  });
@@ -58,6 +60,20 @@ describe("enforceWatcherPrIdentity", () => {
58
60
  expect(closed).toHaveLength(1);
59
61
  });
60
62
 
63
+ test("returns the matching Amelia ready PR when ready is required", () => {
64
+ expect(
65
+ enforce([pullRequest({ isDraft: false })], [], "ready")?.number,
66
+ ).toBe(123);
67
+ });
68
+
69
+ test("closes and rejects a draft PR when ready is required", () => {
70
+ const closed: string[] = [];
71
+ expect(() => enforce([pullRequest()], closed, "ready")).toThrow(
72
+ "state=ready",
73
+ );
74
+ expect(closed).toHaveLength(1);
75
+ });
76
+
61
77
  test("rejects duplicate valid PRs", () => {
62
78
  expect(() =>
63
79
  enforce([
@@ -78,6 +94,19 @@ describe("parseArgs", () => {
78
94
  );
79
95
  });
80
96
 
97
+ test("requires the expected pull request state", () => {
98
+ expect(() =>
99
+ parseArgs([
100
+ "--repo",
101
+ "letta-ai/letta-code",
102
+ "--marker",
103
+ marker,
104
+ "--expected-login",
105
+ "amelia-letta",
106
+ ]),
107
+ ).toThrow("--pull-request-state is required");
108
+ });
109
+
81
110
  test("parses a complete invocation", () => {
82
111
  expect(
83
112
  parseArgs([
@@ -87,11 +116,34 @@ describe("parseArgs", () => {
87
116
  marker,
88
117
  "--expected-login",
89
118
  "amelia-letta",
119
+ "--pull-request-state",
120
+ "draft",
90
121
  ]),
91
122
  ).toEqual({
92
123
  repo: "letta-ai/letta-code",
93
124
  marker,
94
125
  expectedLogin: "amelia-letta",
126
+ pullRequestState: "draft",
127
+ });
128
+ });
129
+
130
+ test("parses a ready pull request requirement", () => {
131
+ expect(
132
+ parseArgs([
133
+ "--repo",
134
+ "letta-ai/letta-cloud",
135
+ "--marker",
136
+ marker,
137
+ "--expected-login",
138
+ "amelia-letta",
139
+ "--pull-request-state",
140
+ "ready",
141
+ ]),
142
+ ).toEqual({
143
+ repo: "letta-ai/letta-cloud",
144
+ marker,
145
+ expectedLogin: "amelia-letta",
146
+ pullRequestState: "ready",
95
147
  });
96
148
  });
97
149
  });
@@ -13,6 +13,7 @@ export interface WatcherPullRequest {
13
13
  interface Args {
14
14
  expectedLogin: string;
15
15
  marker: string;
16
+ pullRequestState: "draft" | "ready";
16
17
  repo: string;
17
18
  }
18
19
 
@@ -30,7 +31,7 @@ export function enforceWatcherPrIdentity(
30
31
  const invalid = matches.filter(
31
32
  (pullRequest) =>
32
33
  pullRequest.author?.login !== options.expectedLogin ||
33
- !pullRequest.isDraft,
34
+ pullRequest.isDraft !== (options.pullRequestState === "draft"),
34
35
  );
35
36
 
36
37
  for (const pullRequest of invalid) options.closePullRequest(pullRequest);
@@ -43,7 +44,7 @@ export function enforceWatcherPrIdentity(
43
44
  )
44
45
  .join(", ");
45
46
  throw new Error(
46
- `Closed watcher PRs that did not match author=${options.expectedLogin} and draft=true: ${details}`,
47
+ `Closed watcher PRs that did not match author=${options.expectedLogin} and state=${options.pullRequestState}: ${details}`,
47
48
  );
48
49
  }
49
50
  if (matches.length > 1) {
@@ -58,12 +59,19 @@ export function parseArgs(argv: string[]): Args {
58
59
  let repo = "";
59
60
  let marker = "";
60
61
  let expectedLogin = "";
62
+ let pullRequestState: "draft" | "ready" | undefined;
61
63
  for (let index = 0; index < argv.length; index += 1) {
62
64
  const argument = argv[index];
63
65
  if (argument === "--repo") repo = argv[++index] ?? "";
64
66
  else if (argument === "--marker") marker = argv[++index] ?? "";
65
67
  else if (argument === "--expected-login") {
66
68
  expectedLogin = argv[++index] ?? "";
69
+ } else if (argument === "--pull-request-state") {
70
+ const state = argv[++index];
71
+ if (state !== "draft" && state !== "ready") {
72
+ throw new Error("--pull-request-state must be draft or ready");
73
+ }
74
+ pullRequestState = state;
67
75
  } else {
68
76
  throw new Error(`Unknown argument: ${argument}`);
69
77
  }
@@ -71,7 +79,8 @@ export function parseArgs(argv: string[]): Args {
71
79
  if (!/^[^/]+\/[^/]+$/.test(repo)) throw new Error("--repo is required");
72
80
  if (!marker) throw new Error("--marker is required");
73
81
  if (!expectedLogin) throw new Error("--expected-login is required");
74
- return { repo, marker, expectedLogin };
82
+ if (!pullRequestState) throw new Error("--pull-request-state is required");
83
+ return { repo, marker, expectedLogin, pullRequestState };
75
84
  }
76
85
 
77
86
  function runGh(args: string[]): string {
@@ -111,7 +120,7 @@ function main(): void {
111
120
  });
112
121
  console.log(
113
122
  verified
114
- ? `Verified Amelia draft PR: ${verified.url}`
123
+ ? `Verified Amelia ${args.pullRequestState} PR: ${verified.url}`
115
124
  : `No open PR contains marker ${JSON.stringify(args.marker)}`,
116
125
  );
117
126
  }
@@ -175,16 +175,6 @@
175
175
  "timeoutMs": 30000,
176
176
  "reason": "Spawns real Git processes and mutates HOME, USER_CWD, cwd, and runtime singletons."
177
177
  },
178
- {
179
- "path": "src/tools/memory-apply-patch.test.ts",
180
- "timeoutMs": 15000,
181
- "reason": "Uses a top-level Bun module mock for the backend client."
182
- },
183
- {
184
- "path": "src/tools/memory-tool.test.ts",
185
- "timeoutMs": 15000,
186
- "reason": "Uses a top-level Bun module mock for the backend client."
187
- },
188
178
  {
189
179
  "path": "src/agent/available-models.test.ts",
190
180
  "timeoutMs": 15000,
@@ -200,6 +190,11 @@
200
190
  "timeoutMs": 15000,
201
191
  "reason": "Uses a top-level Bun module mock for the backend client."
202
192
  },
193
+ {
194
+ "path": "src/websocket/app-server-openai.test.ts",
195
+ "timeoutMs": 30000,
196
+ "reason": "Starts a real App Server and swaps the process-global backend while exercising persistent LocalBackend conversation state."
197
+ },
203
198
  {
204
199
  "path": "src/websocket/listen-client-concurrency.test.ts",
205
200
  "timeoutMs": 30000,
@@ -171,11 +171,17 @@ await patchInkRuntime([
171
171
  },
172
172
  {
173
173
  before: [
174
+ // Current patched state from the 8 MiB follow-up on this branch.
175
+ " fullStaticOutput;\n staticOutputRepaintPending = false;\n isCi = () => isInCi && !this.options.stdout.isTTY;\n // Cap the retained static transcript tail (in UTF-16 code units). Ink keeps\n // fullStaticOutput so overflow frames and static repaints can rewrite the\n // newest tail after a viewport clear. Overflow uses 2J+H (no 3J) so emulator\n // scrollback keeps older history; 8 MiB is a UX-sized rewrite window (256 KB\n // is one large Read/git diff) and bounds per-frame write volume. Uncapped,\n // the string grows with the whole session and is re-serialized on overflow.\n staticOutputRetainLimit = 8 * 1024 * 1024;\n retainStaticOutput = (value) => {\n if (value.length <= this.staticOutputRetainLimit) {\n return value;\n }\n const cut = value.indexOf('\\n', value.length - this.staticOutputRetainLimit);\n return '\\u001B[0m' + (cut === -1 ? value.slice(value.length - this.staticOutputRetainLimit) : value.slice(cut + 1));\n };\n exitPromise;",
176
+ // Current patched state from this PR's first commit (256 KB cap).
177
+ " fullStaticOutput;\n staticOutputRepaintPending = false;\n isCi = () => isInCi && !this.options.stdout.isTTY;\n // Cap the retained static transcript tail (in UTF-16 code units). Ink keeps\n // fullStaticOutput so overflow frames and static repaints can rewrite the\n // transcript after clearing the terminal; uncapped it grows linearly with\n // the whole session transcript and is re-serialized on every overflow frame.\n staticOutputRetainLimit = 256 * 1024;\n retainStaticOutput = (value) => {\n if (value.length <= this.staticOutputRetainLimit) {\n return value;\n }\n const cut = value.indexOf('\\n', value.length - this.staticOutputRetainLimit);\n return '\\u001B[0m' + (cut === -1 ? value.slice(value.length - this.staticOutputRetainLimit) : value.slice(cut + 1));\n };\n exitPromise;",
178
+ // Current patched state from a previous install (transition path).
179
+ " fullStaticOutput;\n staticOutputRepaintPending = false;\n isCi = () => isInCi && !this.options.stdout.isTTY;\n exitPromise;",
174
180
  " fullStaticOutput;\n staticOutputRepaintPending = false;\n exitPromise;",
175
181
  " fullStaticOutput;\n exitPromise;",
176
182
  ],
177
183
  after:
178
- " fullStaticOutput;\n staticOutputRepaintPending = false;\n isCi = () => isInCi && !this.options.stdout.isTTY;\n exitPromise;",
184
+ " fullStaticOutput;\n staticOutputRepaintPending = false;\n isCi = () => isInCi && !this.options.stdout.isTTY;\n // Cap the retained static transcript tail (in UTF-16 code units). Ink keeps\n // fullStaticOutput so overflow frames and static repaints can rewrite the\n // newest tail after a viewport clear. Overflow uses 2J+H (no 3J) so emulator\n // scrollback keeps older history; 2 MiB is a UX-sized rewrite window (256 KB\n // is one large Read/git diff) and bounds per-frame write volume. Uncapped,\n // the string grows with the whole session and is re-serialized on overflow.\n staticOutputRetainLimit = 2 * 1024 * 1024;\n retainStaticOutput = (value) => {\n if (value.length <= this.staticOutputRetainLimit) {\n return value;\n }\n const cut = value.indexOf('\\n', value.length - this.staticOutputRetainLimit);\n return '\\u001B[0m' + (cut === -1 ? value.slice(value.length - this.staticOutputRetainLimit) : value.slice(cut + 1));\n };\n exitPromise;",
179
185
  },
180
186
  {
181
187
  before: " if (isInCi) {",
@@ -191,9 +197,53 @@ await patchInkRuntime([
191
197
  after: " if (!this.isCi() && !this.options.debug) {",
192
198
  },
193
199
  {
194
- before: " if (this.options.debug) {",
195
- after:
200
+ before: [
201
+ // Current patched state from a previous install (transition path).
196
202
  " if (this.staticOutputRepaintPending && (this.options.debug || this.isCi())) {\n if (this.options.stdout.isTTY) {\n this.fullStaticOutput = hasStaticOutput ? staticOutput : '';\n this.repaintStaticOutput(output);\n return;\n }\n this.staticOutputRepaintPending = false;\n }\n if (this.options.debug) {",
203
+ " if (this.options.debug) {",
204
+ ],
205
+ after:
206
+ " if (this.staticOutputRepaintPending && (this.options.debug || this.isCi())) {\n if (this.options.stdout.isTTY) {\n this.fullStaticOutput = this.retainStaticOutput(hasStaticOutput ? staticOutput : '');\n this.repaintStaticOutput(output);\n return;\n }\n this.staticOutputRepaintPending = false;\n }\n if (this.options.debug) {",
207
+ },
208
+ {
209
+ // Bound steady-state growth: route every append through the tail cap.
210
+ before: "this.fullStaticOutput += staticOutput;",
211
+ after:
212
+ "this.fullStaticOutput = this.retainStaticOutput(this.fullStaticOutput + staticOutput);",
213
+ all: true,
214
+ },
215
+ {
216
+ // Overflow frames: do not replay fullStaticOutput and do not clear the
217
+ // screen. 2J erases on-screen transcript rows without saving them
218
+ // (xterm.js) or copies every live frame into history (tmux
219
+ // scroll-on-clear). Clip the live output to its last rows - 1 lines
220
+ // (approval/input sit at the bottom of AppView) so log-update's cursor-up
221
+ // region always fits on screen, then take the normal log-update path:
222
+ // new static rows scroll into history above the live region, the live
223
+ // region is rewritten in place, and unchanged frames are skipped.
224
+ // lastOutput keeps the clipped frame, so console redraws and later diffs
225
+ // match the screen. fullStaticOutput stays for the one-shot #4032 repaint.
226
+ before: [
227
+ // Current branch: 2J+H replace of the live tail clipped to rows lines.
228
+ " if (hasStaticOutput) {\n this.options.stdout.write(staticOutput + '\\n'.repeat(this.options.stdout.rows || 1));\n }\n if (hasStaticOutput || output !== this.lastOutput) {\n const rows = this.options.stdout.rows || 1;\n const liveLines = output.split('\\n');\n this.options.stdout.write('\\u001B[2J\\u001B[H' + (liveLines.length > rows ? liveLines.slice(-rows).join('\\n') : output));\n }\n this.lastOutput = output;\n return;",
229
+ // Earlier: clip kept the top of the live tree (hides input/approval).
230
+ " if (hasStaticOutput) {\n this.options.stdout.write(staticOutput + '\\n'.repeat(this.options.stdout.rows || 1));\n }\n if (hasStaticOutput || output !== this.lastOutput) {\n const rows = this.options.stdout.rows || 1;\n const liveLines = output.split('\\n');\n this.options.stdout.write('\\u001B[2J\\u001B[H' + (liveLines.length > rows ? liveLines.slice(0, rows).join('\\n') : output));\n }\n this.lastOutput = output;\n return;",
231
+ // Earlier: scroll increment then 2J+H the unclipped live region
232
+ // (still scrolls when outputHeight > rows).
233
+ " if (hasStaticOutput) {\n this.options.stdout.write(staticOutput + '\\n'.repeat(this.options.stdout.rows || 1));\n }\n if (hasStaticOutput || output !== this.lastOutput) {\n this.options.stdout.write('\\u001B[2J\\u001B[H' + output);\n }\n this.lastOutput = output;\n return;",
234
+ // Earlier: append increment and live with no viewport replace.
235
+ " if (hasStaticOutput) {\n this.options.stdout.write(staticOutput);\n }\n if (output !== this.lastOutput) {\n this.options.stdout.write(output);\n }\n this.lastOutput = output;\n return;",
236
+ // Earlier: increment immediately followed by 2J (erases the item).
237
+ " if (hasStaticOutput || output !== this.lastOutput) {\n this.options.stdout.write((hasStaticOutput ? staticOutput : '') + '\\u001B[2J\\u001B[H' + output);\n }\n this.lastOutput = output;\n return;",
238
+ // Earlier: 2J+H but still replayed the retained tail.
239
+ " if (hasStaticOutput || output !== this.lastOutput) {\n this.options.stdout.write('\\u001B[2J\\u001B[H' + this.fullStaticOutput + output);\n }\n this.lastOutput = output;\n return;",
240
+ // Earlier: skip-no-change guard still used clearTerminal (3J).
241
+ " if (hasStaticOutput || output !== this.lastOutput) {\n this.options.stdout.write(ansiEscapes.clearTerminal + this.fullStaticOutput + output);\n }\n this.lastOutput = output;\n return;",
242
+ // Pristine Ink / earlier patched state: always-rewrite overflow.
243
+ " this.options.stdout.write(ansiEscapes.clearTerminal + this.fullStaticOutput + output);\n this.lastOutput = output;\n return;",
244
+ ],
245
+ after:
246
+ " const liveOutput = output.split('\\n').slice(-Math.max(1, (this.options.stdout.rows || 1) - 1)).join('\\n');\n if (hasStaticOutput) {\n this.log.clear();\n this.options.stdout.write(staticOutput);\n this.log(liveOutput);\n }\n else if (liveOutput !== this.lastOutput) {\n this.throttledLog(liveOutput);\n }\n this.lastOutput = liveOutput;\n return;",
197
247
  },
198
248
  {
199
249
  before: " if (outputHeight >= this.options.stdout.rows) {",
@@ -1,21 +1,19 @@
1
1
  {
2
2
  "src/agent/client-skills.test.ts": 1134,
3
- "src/agent/memory-git.ts": 2092,
4
- "src/backend/local-backend.test.ts": 2473,
5
- "src/backend/local/local-backend.ts": 1005,
6
- "src/backend/local/local-store.ts": 3457,
3
+ "src/agent/memory-git.ts": 2013,
4
+ "src/backend/local-backend.test.ts": 2472,
5
+ "src/backend/local/local-store.ts": 3276,
7
6
  "src/backend/pi-stream-adapter.test.ts": 1255,
8
- "src/cli/app/AppCoordinator.tsx": 5136,
7
+ "src/cli/app/AppCoordinator.tsx": 5109,
9
8
  "src/cli/app/AppView.tsx": 1735,
10
9
  "src/cli/app/use-approval-flow.ts": 1163,
11
10
  "src/cli/app/use-configuration-handlers.ts": 1417,
12
- "src/cli/app/use-conversation-loop.ts": 2903,
11
+ "src/cli/app/use-conversation-loop.ts": 2887,
13
12
  "src/cli/app/use-submit-handler.ts": 3804,
14
13
  "src/cli/components/AgentSelector.tsx": 1104,
15
14
  "src/cli/components/InputRich.tsx": 2116,
16
15
  "src/cli/components/ModelSelector.tsx": 1259,
17
16
  "src/cli/components/ProviderSelector.tsx": 1564,
18
- "src/cli/components/ToolCallMessageRich.tsx": 1013,
19
17
  "src/cli/helpers/accumulator.ts": 1587,
20
18
  "src/cli/helpers/reflection-transcript.ts": 1926,
21
19
  "src/cli/mods/local-mod-loader.test.ts": 1043,
@@ -23,7 +21,7 @@
23
21
  "src/cli/subcommands/skills.ts": 1264,
24
22
  "src/headless.ts": 4788,
25
23
  "src/hooks/integration.test.ts": 1147,
26
- "src/index.ts": 2597,
24
+ "src/index.ts": 2578,
27
25
  "src/mods/learning-harness.ts": 2434,
28
26
  "src/mods/mod-engine.test.ts": 2153,
29
27
  "src/mods/mod-engine.ts": 1838,
@@ -35,8 +33,8 @@
35
33
  "src/providers/chatgpt-usage-service.ts": 1112,
36
34
  "src/settings-manager.test.ts": 1635,
37
35
  "src/settings-manager.ts": 2101,
38
- "src/tools/manager.ts": 2713,
39
- "src/tools/tool-execution-context.test.ts": 1114,
36
+ "src/tools/manager.ts": 2649,
37
+ "src/tools/tool-execution-context.test.ts": 1092,
40
38
  "src/types/protocol_v2.ts": 2574,
41
39
  "src/websocket/listen-client-concurrency.test.ts": 2666,
42
40
  "src/websocket/listen-client-protocol.test.ts": 5759,
@@ -13,7 +13,16 @@ import { dirname, join, resolve } from "node:path";
13
13
  const root = process.cwd();
14
14
  const app = resolve(process.argv[2]);
15
15
  // sharp-electron is only reachable under Electron, never our bundled Node.
16
- const roots = ["ws", "@vscode/ripgrep", "node-pty", "grammy", "@pierre/diffs"];
16
+ // @shikijs/langs is external in the bundle so lazily-loaded grammars
17
+ // (LET-13149) resolve from here at runtime.
18
+ const roots = [
19
+ "ws",
20
+ "@vscode/ripgrep",
21
+ "node-pty",
22
+ "grammy",
23
+ "@pierre/diffs",
24
+ "@shikijs/langs",
25
+ ];
17
26
 
18
27
  function locate(name, from) {
19
28
  for (let dir = from; ; dir = dirname(dir)) {
@@ -180,24 +180,19 @@ if (letta.capabilities.events.turns) {
180
180
 
181
181
  ## Permission overlay
182
182
 
183
- Use a permission overlay, not `tool_start`, for policy. Normalize tool names by family; UI display names and provider-specific tool names drift (`Read`, `read`, `read_file`, `ReadFile`, etc.). Keep pure read-only tools separate from planning coordination tools like `AskUserQuestion` and todo/plan updates so the policy stays honest.
183
+ Use a permission overlay, not `tool_start`, for policy. Normalize tool names by family; UI display names and provider-specific tool names drift (`Read`, `read`, etc.). Keep pure read-only tools separate from planning coordination tools like `AskUserQuestion` and todo/plan updates so the policy stays honest.
184
184
 
185
185
  ```ts
186
186
  const readOnlyToolNames = new Set([
187
187
  "glob",
188
188
  "grep",
189
- "grepfiles",
190
189
  "list",
191
- "listdir",
192
- "ls",
193
190
  "notebookread",
194
191
  "read",
195
- "readfile",
196
192
  "readlsp",
197
193
  "search",
198
194
  "searchfiles",
199
195
  "skill",
200
- "taskoutput",
201
196
  "viewimage",
202
197
  ]);
203
198
 
@@ -205,7 +200,10 @@ const planningToolNames = new Set([
205
200
  "askuserquestion",
206
201
  "enterplanmode",
207
202
  "exitplanmode",
208
- "todowrite",
203
+ "taskcreate",
204
+ "taskget",
205
+ "tasklist",
206
+ "taskupdate",
209
207
  "updateplan",
210
208
  ]);
211
209
 
@@ -259,7 +257,7 @@ if (letta.capabilities.permissions) {
259
257
  return {
260
258
  decision: "deny",
261
259
  reason:
262
- `Plan mode is active. Use direct read-only tools (Read, Grep, Glob, List, Search, Skill, safe read-only Bash), planning tools (AskUserQuestion, TodoWrite/UpdatePlan), or recall-style subagents only. ` +
260
+ `Plan mode is active. Use direct read-only tools (Read, Grep, Glob, ViewImage, Skill, safe read-only Bash), planning tools (AskUserQuestion, TaskCreate/TaskUpdate, UpdatePlan), or recall-style subagents only. ` +
263
261
  `Do not use coding, general-purpose, or fork subagents in plan mode. ` +
264
262
  `Write your plan to: ${session.planFilePath}. ` +
265
263
  `When ready, read the plan file and include the full current plan text in AskUserQuestion for approval, then call exit_plan_mode after approval.`,