@letta-ai/letta-code 0.34.25 → 0.34.26

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 (76) hide show
  1. package/cursor-worker.js +167 -0
  2. package/dist/agent-presets.js +1 -64
  3. package/dist/agent-presets.js.map +2 -2
  4. package/dist/channels-slack.js +30 -24
  5. package/dist/channels-slack.js.map +3 -3
  6. package/dist/mcp-client.js +233 -75
  7. package/dist/mcp-client.js.map +9 -9
  8. package/dist/mcp-oauth.js +233 -75
  9. package/dist/mcp-oauth.js.map +9 -9
  10. package/dist/types/agent/subagents/index.d.ts +4 -0
  11. package/dist/types/agent/subagents/index.d.ts.map +1 -1
  12. package/dist/types/agent/subagents/manager.d.ts.map +1 -1
  13. package/dist/types/agent/subagents/memory-worker.d.ts +2 -8
  14. package/dist/types/agent/subagents/memory-worker.d.ts.map +1 -1
  15. package/dist/types/backend/api/providers.d.ts +8 -2
  16. package/dist/types/backend/api/providers.d.ts.map +1 -1
  17. package/dist/types/backend/backend.d.ts +5 -2
  18. package/dist/types/backend/backend.d.ts.map +1 -1
  19. package/dist/types/backend/dev/fake-headless-backend.d.ts +2 -1
  20. package/dist/types/backend/dev/fake-headless-backend.d.ts.map +1 -1
  21. package/dist/types/backend/dev/pi-model-factory.d.ts.map +1 -1
  22. package/dist/types/backend/local/local-provider-auth-store.d.ts +2 -0
  23. package/dist/types/backend/local/local-provider-auth-store.d.ts.map +1 -1
  24. package/dist/types/backend/local/local-transcript-history.d.ts +10 -0
  25. package/dist/types/backend/local/local-transcript-history.d.ts.map +1 -0
  26. package/dist/types/backend/local/local-transcript.d.ts +6 -0
  27. package/dist/types/backend/local/local-transcript.d.ts.map +1 -1
  28. package/dist/types/channels/slack/attachment-primitives.d.ts +2 -1
  29. package/dist/types/channels/slack/attachment-primitives.d.ts.map +1 -1
  30. package/dist/types/cli/subcommands/mcp.d.ts +2 -2
  31. package/dist/types/cli/subcommands/mcp.d.ts.map +1 -1
  32. package/dist/types/cron/process-identity.d.ts.map +1 -1
  33. package/dist/types/cron/run-log.d.ts +1 -0
  34. package/dist/types/cron/run-log.d.ts.map +1 -1
  35. package/dist/types/headless-environment-response.d.ts +27 -0
  36. package/dist/types/headless-environment-response.d.ts.map +1 -1
  37. package/dist/types/providers/byok-providers.d.ts +14 -0
  38. package/dist/types/providers/byok-providers.d.ts.map +1 -1
  39. package/dist/types/tools/impl/claude-stream-session.d.ts.map +1 -1
  40. package/dist/types/tools/impl/codex-app-server.d.ts.map +1 -1
  41. package/dist/types/tools/impl/cursor-session.d.ts +26 -0
  42. package/dist/types/tools/impl/cursor-session.d.ts.map +1 -0
  43. package/dist/types/tools/impl/cursor-worker.d.ts +32 -0
  44. package/dist/types/tools/impl/cursor-worker.d.ts.map +1 -0
  45. package/dist/types/tools/impl/external-coding-agent-task.d.ts.map +1 -1
  46. package/dist/types/tools/impl/external-coding-agent.d.ts +1 -1
  47. package/dist/types/tools/impl/external-coding-agent.d.ts.map +1 -1
  48. package/dist/types/tools/impl/memory-task-lifecycle.d.ts +2 -2
  49. package/dist/types/tools/impl/memory-task-lifecycle.d.ts.map +1 -1
  50. package/dist/types/tools/impl/remote-external-coding-agent.d.ts.map +1 -1
  51. package/dist/types/tools/impl/send-agent-message.d.ts.map +1 -1
  52. package/dist/types/tools/impl/task.d.ts.map +1 -1
  53. package/dist/types/tools/workflow/sdk-spawner.d.ts.map +1 -1
  54. package/dist/types/utils/native-cli-env.d.ts +3 -0
  55. package/dist/types/utils/native-cli-env.d.ts.map +1 -0
  56. package/dist/types/utils/process-liveness.d.ts +9 -0
  57. package/dist/types/utils/process-liveness.d.ts.map +1 -1
  58. package/letta.js +3512 -2894
  59. package/npm-shrinkwrap.json +275 -101
  60. package/package.json +5 -3
  61. package/scripts/agent-watch/verify-pr-identity.test.ts +148 -0
  62. package/scripts/agent-watch/verify-pr-identity.ts +72 -14
  63. package/scripts/claude-watch/agent-watch.ts +8 -2
  64. package/scripts/claude-watch/release-source.test.ts +120 -0
  65. package/scripts/claude-watch/release-source.ts +79 -9
  66. package/scripts/codex-watch/agent-watch.ts +2 -2
  67. package/scripts/codex-watch/release-analysis.test.ts +112 -6
  68. package/scripts/codex-watch/release-analysis.ts +63 -6
  69. package/scripts/source-file-size-baseline.json +5 -4
  70. package/skills/initializing-memory/SKILL.md +1 -1
  71. package/skills/managing-agent-webhooks/SKILL.md +23 -3
  72. package/skills/managing-agent-webhooks/scripts/manage-agent-webhooks.mjs +40 -11
  73. package/skills/self-configuration/SKILL.md +1 -1
  74. package/skills/syncing-memory-filesystem/SKILL.md +2 -10
  75. package/dist/types/agent/memory-worktree.d.ts +0 -83
  76. package/dist/types/agent/memory-worktree.d.ts.map +0 -1
@@ -52,17 +52,121 @@ describe("Codex stable release range", () => {
52
52
  });
53
53
 
54
54
  describe("Codex GitHub release fetch", () => {
55
+ test("resolves latest without fetching a list page when the cursor is current", async () => {
56
+ const urls: string[] = [];
57
+ const releases = await listStableReleases({
58
+ sinceTag: "rust-v0.3.0",
59
+ fetchImpl: (async (input) => {
60
+ urls.push(String(input));
61
+ return Response.json(STABLES[2]);
62
+ }) as typeof fetch,
63
+ });
64
+
65
+ expect(releases).toEqual([STABLES[2]]);
66
+ expect(urls).toEqual([
67
+ "https://api.github.com/repos/openai/codex/releases/latest",
68
+ ]);
69
+ });
70
+
71
+ test("resolves an explicit current tag without fetching a list page", async () => {
72
+ const urls: string[] = [];
73
+ const releases = await listStableReleases({
74
+ currentTag: "rust-v0.2.0",
75
+ sinceTag: "rust-v0.2.0",
76
+ fetchImpl: (async (input) => {
77
+ const url = String(input);
78
+ urls.push(url);
79
+ return Response.json(url.endsWith("/latest") ? STABLES[2] : STABLES[1]);
80
+ }) as typeof fetch,
81
+ });
82
+
83
+ expect(releases).toEqual([STABLES[1], STABLES[2]]);
84
+ expect(urls).toEqual([
85
+ "https://api.github.com/repos/openai/codex/releases/latest",
86
+ "https://api.github.com/repos/openai/codex/releases/tags/rust-v0.2.0",
87
+ ]);
88
+ });
89
+
90
+ test("bounds pagination after finding the adjacent stable release", async () => {
91
+ const urls: string[] = [];
92
+ const prereleases = Array.from({ length: 100 }, (_, index) => ({
93
+ ...release(
94
+ `rust-v1.0.${index}`,
95
+ `2026-09-01T00:${String(index % 60).padStart(2, "0")}:00Z`,
96
+ ),
97
+ prerelease: true,
98
+ }));
99
+ const releases = await listStableReleases({
100
+ fetchImpl: (async (input) => {
101
+ const url = String(input);
102
+ urls.push(url);
103
+ if (url.endsWith("/latest")) return Response.json(STABLES[2]);
104
+ if (url.endsWith("page=1")) return Response.json(prereleases);
105
+ if (url.endsWith("page=2")) return Response.json([STABLES[1]]);
106
+ throw new Error(`Unexpected request ${url}`);
107
+ }) as typeof fetch,
108
+ });
109
+
110
+ expect(releases).toEqual([STABLES[1], STABLES[2]]);
111
+ expect(urls.filter((url) => url.includes("?per_page=100"))).toHaveLength(2);
112
+ });
113
+
114
+ test("stops range pagination as soon as the since tag is found", async () => {
115
+ const urls: string[] = [];
116
+ const fullPage = Array.from({ length: 100 }, (_, index) => ({
117
+ ...release(
118
+ `rust-v1.0.${index}`,
119
+ `2026-09-01T00:${String(index % 60).padStart(2, "0")}:00Z`,
120
+ ),
121
+ prerelease: true,
122
+ }));
123
+ await listStableReleases({
124
+ currentTag: "rust-v0.3.0",
125
+ sinceTag: "rust-v0.1.0",
126
+ fetchImpl: (async (input) => {
127
+ const url = String(input);
128
+ urls.push(url);
129
+ if (url.endsWith("/latest")) return Response.json(STABLES[2]);
130
+ if (url.endsWith("/tags/rust-v0.3.0")) return Response.json(STABLES[2]);
131
+ if (url.endsWith("page=1")) return Response.json(fullPage);
132
+ if (url.endsWith("page=2")) return Response.json(STABLES.slice(0, 2));
133
+ throw new Error(`Unexpected request ${url}`);
134
+ }) as typeof fetch,
135
+ });
136
+
137
+ expect(urls.filter((url) => url.includes("?per_page=100"))).toHaveLength(2);
138
+ });
139
+
140
+ test("reports a missing explicit current tag without listing releases", async () => {
141
+ const urls: string[] = [];
142
+ const result = listStableReleases({
143
+ currentTag: "rust-v9.9.9",
144
+ fetchImpl: (async (input) => {
145
+ const url = String(input);
146
+ urls.push(url);
147
+ if (url.endsWith("/latest")) return Response.json(STABLES[2]);
148
+ return new Response("Not Found", { status: 404 });
149
+ }) as typeof fetch,
150
+ });
151
+
152
+ await expect(result).rejects.toThrow(
153
+ "GitHub releases API failed (404): Not Found",
154
+ );
155
+ expect(urls.some((url) => url.includes("?per_page=100"))).toBe(false);
156
+ });
157
+
55
158
  test("retries a server error before succeeding", async () => {
56
159
  const sleeps: number[] = [];
57
160
  let requests = 0;
58
161
 
59
162
  const releases = await listStableReleases({
163
+ sinceTag: "rust-v0.3.0",
60
164
  fetchImpl: (async () => {
61
165
  requests++;
62
166
  if (requests === 1) {
63
167
  return new Response("Gateway Timeout", { status: 504 });
64
168
  }
65
- return new Response(JSON.stringify(STABLES), { status: 200 });
169
+ return Response.json(STABLES[2]);
66
170
  }) as typeof fetch,
67
171
  sleep: async (delayMs) => {
68
172
  sleeps.push(delayMs);
@@ -71,7 +175,7 @@ describe("Codex GitHub release fetch", () => {
71
175
 
72
176
  expect(requests).toBe(2);
73
177
  expect(sleeps).toEqual([1000]);
74
- expect(releases).toEqual(STABLES);
178
+ expect(releases).toEqual([STABLES[2]]);
75
179
  });
76
180
 
77
181
  test("retries a network error before succeeding", async () => {
@@ -79,10 +183,11 @@ describe("Codex GitHub release fetch", () => {
79
183
  let requests = 0;
80
184
 
81
185
  const releases = await listStableReleases({
186
+ sinceTag: "rust-v0.3.0",
82
187
  fetchImpl: (async () => {
83
188
  requests++;
84
189
  if (requests === 1) throw new TypeError("fetch failed: ECONNRESET");
85
- return new Response(JSON.stringify(STABLES), { status: 200 });
190
+ return Response.json(STABLES[2]);
86
191
  }) as typeof fetch,
87
192
  sleep: async (delayMs) => {
88
193
  sleeps.push(delayMs);
@@ -91,7 +196,7 @@ describe("Codex GitHub release fetch", () => {
91
196
 
92
197
  expect(requests).toBe(2);
93
198
  expect(sleeps).toEqual([1000]);
94
- expect(releases).toEqual(STABLES);
199
+ expect(releases).toEqual([STABLES[2]]);
95
200
  });
96
201
 
97
202
  test("stops after the bounded retry budget", async () => {
@@ -119,6 +224,7 @@ describe("Codex GitHub release fetch", () => {
119
224
  const sleeps: number[] = [];
120
225
  let requests = 0;
121
226
  const releases = await listStableReleases({
227
+ sinceTag: "rust-v0.3.0",
122
228
  fetchImpl: (async () => {
123
229
  requests++;
124
230
  if (requests === 1) {
@@ -131,7 +237,7 @@ describe("Codex GitHub release fetch", () => {
131
237
  { status: 200 },
132
238
  );
133
239
  }
134
- return Response.json(STABLES);
240
+ return Response.json(STABLES[2]);
135
241
  }) as typeof fetch,
136
242
  sleep: async (delayMs) => {
137
243
  sleeps.push(delayMs);
@@ -140,7 +246,7 @@ describe("Codex GitHub release fetch", () => {
140
246
 
141
247
  expect(requests).toBe(2);
142
248
  expect(sleeps).toEqual([1000]);
143
- expect(releases).toEqual(STABLES);
249
+ expect(releases).toEqual([STABLES[2]]);
144
250
  });
145
251
 
146
252
  test("stops retrying disconnected bodies at the existing budget", async () => {
@@ -63,7 +63,12 @@ export interface CodexWatchAnalysis {
63
63
  export async function analyzeCodexRelease(
64
64
  options: AnalyzeCodexReleaseOptions,
65
65
  ): Promise<CodexWatchAnalysis> {
66
- const stables = options.stableReleases ?? (await listStableReleases());
66
+ const stables =
67
+ options.stableReleases ??
68
+ (await listStableReleases({
69
+ currentTag: options.currentTag,
70
+ sinceTag: options.sinceTag,
71
+ }));
67
72
  if (stables.length === 0) throw new Error("No stable Codex releases found");
68
73
 
69
74
  const current = options.currentTag
@@ -173,6 +178,9 @@ export async function listStableReleases(
173
178
  options: {
174
179
  fetchImpl?: typeof fetch;
175
180
  sleep?: (delayMs: number) => Promise<void>;
181
+ currentTag?: string | null;
182
+ sinceTag?: string | null;
183
+ fullHistory?: boolean;
176
184
  } = {},
177
185
  ): Promise<Release[]> {
178
186
  const releases: Release[] = [];
@@ -188,29 +196,78 @@ export async function listStableReleases(
188
196
  ((delayMs: number) =>
189
197
  new Promise<void>((resolve) => setTimeout(resolve, delayMs)));
190
198
 
199
+ if (!options.fullHistory) {
200
+ const latest = await fetchGitHubReleasePayload<Release>(
201
+ `https://api.github.com/repos/${CODEX_REPO}/releases/latest`,
202
+ headers,
203
+ fetchImpl,
204
+ sleep,
205
+ );
206
+ releases.push(latest);
207
+ if (options.currentTag) {
208
+ releases.push(
209
+ await fetchGitHubReleasePayload<Release>(
210
+ `https://api.github.com/repos/${CODEX_REPO}/releases/tags/${encodeURIComponent(options.currentTag)}`,
211
+ headers,
212
+ fetchImpl,
213
+ sleep,
214
+ ),
215
+ );
216
+ }
217
+
218
+ const currentTag = options.currentTag ?? latest.tag_name;
219
+ if (options.sinceTag && options.sinceTag === currentTag) {
220
+ return stableReleases(releases);
221
+ }
222
+ }
223
+
191
224
  for (let page = 1; page <= 10; page++) {
192
225
  const url = `https://api.github.com/repos/${CODEX_REPO}/releases?per_page=100&page=${page}`;
193
- const batch = await fetchGitHubReleasesPage(url, headers, fetchImpl, sleep);
226
+ const batch = await fetchGitHubReleasePayload<Release[]>(
227
+ url,
228
+ headers,
229
+ fetchImpl,
230
+ sleep,
231
+ );
194
232
  releases.push(...batch);
233
+ if (!options.fullHistory) {
234
+ const stables = stableReleases(releases);
235
+ const currentTag = options.currentTag ?? stables.at(-1)?.tag_name;
236
+ const currentIndex = stables.findIndex(
237
+ (release) => release.tag_name === currentTag,
238
+ );
239
+ const foundBoundary = options.sinceTag
240
+ ? stables.some((release) => release.tag_name === options.sinceTag)
241
+ : currentIndex > 0;
242
+ if (foundBoundary) break;
243
+ }
195
244
  if (batch.length < 100) break;
196
245
  }
197
- return releases
246
+ return stableReleases(releases);
247
+ }
248
+
249
+ function stableReleases(releases: Release[]): Release[] {
250
+ const byTag = new Map<string, Release>();
251
+ for (const release of releases) {
252
+ if (!byTag.has(release.tag_name)) byTag.set(release.tag_name, release);
253
+ }
254
+ return [...byTag.values()]
198
255
  .filter(isStableRelease)
199
256
  .sort((a, b) => (a.published_at ?? "").localeCompare(b.published_at ?? ""));
200
257
  }
201
258
 
202
- async function fetchGitHubReleasesPage(
259
+ async function fetchGitHubReleasePayload<T>(
203
260
  url: string,
204
261
  headers: Record<string, string>,
205
262
  fetchImpl: typeof fetch,
206
263
  sleep: (delayMs: number) => Promise<void>,
207
- ): Promise<Release[]> {
264
+ ): Promise<T> {
208
265
  for (let attempt = 1; ; attempt++) {
209
266
  let response: Response;
210
267
  try {
211
268
  response = await fetchImpl(url, { headers });
212
269
  // A successful fetch can still lose its connection while reading the body.
213
- if (response.ok) return (await response.json()) as Release[];
270
+ if (response.ok) return (await response.json()) as T;
214
271
  } catch (error) {
215
272
  if (error instanceof SyntaxError) throw error;
216
273
  if (attempt === GITHUB_RELEASES_FETCH_ATTEMPTS) {
@@ -13,13 +13,14 @@
13
13
  "src/cli/components/AgentSelector.tsx": 1104,
14
14
  "src/cli/components/InputRich.tsx": 2124,
15
15
  "src/cli/components/ModelSelector.tsx": 1259,
16
- "src/cli/components/ProviderSelector.tsx": 1552,
16
+ "src/cli/components/ProviderSelector.tsx": 1544,
17
17
  "src/cli/helpers/accumulator.ts": 1587,
18
18
  "src/cli/helpers/reflection-transcript.ts": 1926,
19
19
  "src/cli/mods/local-mod-loader.test.ts": 1043,
20
20
  "src/cli/reflection-transcript.test.ts": 1084,
21
- "src/cli/subcommands/skills.ts": 1264,
22
- "src/headless.ts": 4851,
21
+ "src/cli/subcommands/mcp.ts": 1045,
22
+ "src/cli/subcommands/skills.ts": 1243,
23
+ "src/headless.ts": 4837,
23
24
  "src/hooks/integration.test.ts": 1147,
24
25
  "src/index.ts": 2575,
25
26
  "src/mods/learning-harness.ts": 2434,
@@ -35,7 +36,7 @@
35
36
  "src/tools/manager.ts": 2609,
36
37
  "src/tools/tool-execution-context.test.ts": 1092,
37
38
  "src/types/protocol_v2.ts": 2409,
38
- "src/websocket/listen-client-concurrency.test.ts": 2554,
39
+ "src/websocket/listen-client-concurrency.test.ts": 2548,
39
40
  "src/websocket/listen-client-protocol.test.ts": 5664,
40
41
  "src/websocket/listener/commands/memory.ts": 1114,
41
42
  "src/websocket/listener/file-commands.ts": 1045,
@@ -7,7 +7,7 @@ description: Comprehensive guide for initializing or reorganizing agent memory.
7
7
 
8
8
  Your memory is projected to a filesystem at `$MEMORY_DIR`, so you can restructure it with ordinary file tools and git. This skill applies the [Context Constitution](https://github.com/letta-ai/context-constitution/blob/main/constitution/CONSTITUTION.md)'s Progressive Disclosure and Efficiency principles to the [MemFS v2 root-first design](https://github.com/letta-ai/letta-code/blob/main/src/agent/prompts/letta_root_memfs.md).
9
9
 
10
- **You own this task through completion**: research, ask, synthesize, write, commit, verify. Delegate research, but integrate the results yourself — launching a background task is not completion. If a memory worker you launched is still editing this checkout, wait for it, then re-read before editing.
10
+ **You own this task through completion**: research, ask, synthesize, write, commit, verify. Delegate research, but integrate the results yourself — launching a background task is not completion.
11
11
 
12
12
  ## Principles
13
13
 
@@ -96,11 +96,13 @@ payload as a user message.
96
96
  unreadable receipt means deletion was accepted but remains unconfirmed; list
97
97
  webhooks before treating the capability URL as removed or retrying.
98
98
  - Normal update output additionally requires the returned webhook ID and
99
- `enabled` state to match the requested operation; normal rotate output
100
- requires the returned webhook ID to match. Canonical but contradictory 2xx
99
+ `enabled` state (or `target_device_id` for `update --computer`) to match the
100
+ requested operation; normal rotate output requires the returned webhook ID
101
+ to match. Canonical but contradictory 2xx
101
102
  records receive the same accepted/reconcile envelope and are never printed as
102
103
  ordinary success.
103
- - Normal create output also requires returned name and enabled state to match.
104
+ - Normal create output also requires returned name, enabled state, and
105
+ `target_device_id` to match.
104
106
  An explicitly supplied preprompt must round-trip exactly; when preprompt is
105
107
  omitted, the server-owned default is accepted only as a nonblank string, while
106
108
  missing, null, empty, whitespace-only, and non-string values are contradictory.
@@ -207,6 +209,24 @@ The caller sends `Authorization: Basic <base64("webhook:<security-key>")>`.
207
209
  Do not print that header or the key. The helper derives it internally for test
208
210
  requests when the same key is piped through stdin.
209
211
 
212
+ ### Run webhook turns on a connected computer
213
+
214
+ By default, webhook-triggered conversations run in the agent's Cloud sandbox.
215
+ To run them on a connected computer instead, get its device ID from
216
+ `letta computers list` and pass it with `--computer` on `create` or `update`:
217
+
218
+ ```bash
219
+ node <SKILL_DIR>/scripts/manage-agent-webhooks.mjs update \
220
+ --webhook-id webhook-agent-... \
221
+ --computer <deviceId>
222
+ ```
223
+
224
+ `list` shows the target as `target_device_id` (`null` means Cloud). If the
225
+ computer is offline when a request arrives, that delivery fails; it does not
226
+ fall back to Cloud. An unknown device ID returns `404 targetDeviceNotFound`.
227
+ Pass `--computer cloud` to `update` to clear the target and go back to the
228
+ Cloud sandbox.
229
+
210
230
  ### Test acceptance
211
231
 
212
232
  Public webhook:
@@ -15,6 +15,7 @@ const COMMANDS = new Set([
15
15
  "requests",
16
16
  "rotate",
17
17
  "test",
18
+ "update",
18
19
  ]);
19
20
  const BOOLEAN_FLAGS = new Set([
20
21
  "confirm",
@@ -28,6 +29,7 @@ const COMMAND_OPTIONS = new Map([
28
29
  [
29
30
  "create",
30
31
  new Set([
32
+ "computer",
31
33
  "credential-output",
32
34
  "disabled",
33
35
  "name",
@@ -53,12 +55,14 @@ const COMMAND_OPTIONS = new Map([
53
55
  "webhook-id",
54
56
  ]),
55
57
  ],
58
+ ["update", new Set(["computer", "webhook-id"])],
56
59
  ]);
57
60
 
58
61
  function usage() {
59
62
  console.error(`Usage:
60
63
  node manage-agent-webhooks.mjs list
61
- node manage-agent-webhooks.mjs create --name <name> (--public | --secure --credential-output <path> | --security-key-stdin) [--preprompt <text>] [--disabled]
64
+ node manage-agent-webhooks.mjs create --name <name> (--public | --secure --credential-output <path> | --security-key-stdin) [--preprompt <text>] [--disabled] [--computer <deviceId>]
65
+ node manage-agent-webhooks.mjs update --webhook-id <id> --computer <deviceId|cloud>
62
66
  node manage-agent-webhooks.mjs test --webhook-id <id> --confirm [--payload-file <path>] [--credential-file <path> | --security-key-stdin]
63
67
  node manage-agent-webhooks.mjs requests --webhook-id <id> [--limit <1-50>] [--include-body]
64
68
  node manage-agent-webhooks.mjs enable --webhook-id <id>
@@ -144,8 +148,10 @@ export function safeAgentWebhook(webhook) {
144
148
  for (const field of ["enabled", "requires_authorization_header"]) {
145
149
  if (typeof webhook[field] === "boolean") safe[field] = webhook[field];
146
150
  }
147
- if (typeof webhook.disabled_at === "string" || webhook.disabled_at === null) {
148
- safe.disabled_at = webhook.disabled_at;
151
+ for (const field of ["disabled_at", "target_device_id"]) {
152
+ if (typeof webhook[field] === "string" || webhook[field] === null) {
153
+ safe[field] = webhook[field];
154
+ }
149
155
  }
150
156
  return safe;
151
157
  }
@@ -753,6 +759,15 @@ function requireWebhookId(args) {
753
759
  return requireString(args["webhook-id"], "Pass --webhook-id");
754
760
  }
755
761
 
762
+ // "cloud" maps to null: run webhook turns in the agent's Cloud sandbox.
763
+ function targetDeviceIdFromArgs(args) {
764
+ const computer = requireString(
765
+ args.computer,
766
+ "--computer must be a device ID or cloud",
767
+ );
768
+ return computer === "cloud" ? null : computer;
769
+ }
770
+
756
771
  function requireConfirmation(args, action) {
757
772
  if (args.confirm !== true) {
758
773
  throw new Error(`${action} requires --confirm`);
@@ -827,6 +842,10 @@ async function runManagementCommand(params) {
827
842
  "--preprompt must contain a non-whitespace character",
828
843
  );
829
844
  }
845
+ if (params.args.computer !== undefined) {
846
+ const targetDeviceId = targetDeviceIdFromArgs(params.args);
847
+ if (targetDeviceId) body.target_device_id = targetDeviceId;
848
+ }
830
849
  if (securityKey) body.security_key = securityKey;
831
850
  const credentialReservation = usesGeneratedCredential
832
851
  ? await reserveCredentialFile(params.args["credential-output"])
@@ -914,6 +933,8 @@ async function runManagementCommand(params) {
914
933
  if (
915
934
  webhook.name !== body.name ||
916
935
  webhook.enabled !== body.enabled ||
936
+ (webhook.target_device_id ?? null) !==
937
+ (body.target_device_id ?? null) ||
917
938
  !prepromptMatches
918
939
  ) {
919
940
  throw new Error(
@@ -1048,14 +1069,22 @@ async function runManagementCommand(params) {
1048
1069
  return;
1049
1070
  }
1050
1071
 
1051
- if (params.command === "enable" || params.command === "disable") {
1072
+ if (
1073
+ params.command === "enable" ||
1074
+ params.command === "disable" ||
1075
+ params.command === "update"
1076
+ ) {
1052
1077
  if (params.command === "disable") {
1053
1078
  requireConfirmation(params.args, "Disabling a webhook");
1054
1079
  }
1080
+ const update =
1081
+ params.command === "update"
1082
+ ? { target_device_id: targetDeviceIdFromArgs(params.args) }
1083
+ : { enabled: params.command === "enable" };
1055
1084
  const mutation = await requestSideEffect(
1056
1085
  itemUrl,
1057
1086
  {
1058
- body: JSON.stringify({ enabled: params.command === "enable" }),
1087
+ body: JSON.stringify(update),
1059
1088
  headers: params.headers,
1060
1089
  method: "PATCH",
1061
1090
  },
@@ -1074,12 +1103,12 @@ async function runManagementCommand(params) {
1074
1103
  params.baseUrl,
1075
1104
  params.agentId,
1076
1105
  );
1077
- const expectedEnabled = params.command === "enable";
1078
- if (
1079
- webhook.id !== webhookId ||
1080
- typeof webhook.enabled !== "boolean" ||
1081
- webhook.enabled !== expectedEnabled
1082
- ) {
1106
+ const stateMatches =
1107
+ params.command === "update"
1108
+ ? webhook.target_device_id === update.target_device_id
1109
+ : typeof webhook.enabled === "boolean" &&
1110
+ webhook.enabled === update.enabled;
1111
+ if (webhook.id !== webhookId || !stateMatches) {
1083
1112
  throw new Error(
1084
1113
  "Update response does not match the requested webhook state",
1085
1114
  );
@@ -110,7 +110,7 @@ Use the active layout shown by the prompt and memory files. Do not create a
110
110
  `system/` directory in a root-layout repository or move existing-layout memory
111
111
  to the root as part of an unrelated self-configuration request.
112
112
 
113
- A requested memory or identity change is memory as the main task: make the edit directly with ordinary file tools, preserve the active layout, stage only the files you changed by explicit path, commit in the same shell call, and verify the result before reporting success. If a memory worker you launched may still be running, read its output file first (it ends with `[Task completed]` or `[Task failed]`) and reread the files before editing what it was asked to change. Delegate to the background `memory` subagent only when the change is incidental to another task the user is waiting on.
113
+ Before editing memory, load the relevant skills: `maintaining-core-memory` for root/core files, `maintaining-deferred-memory` for deferred files, and `creating-skills` for skills. Then make the edit directly with ordinary file tools so the agent with the context applies the memory guidance. Preserve the active layout, stage only changed files by explicit path, commit in the same shell call, and verify the result before reporting success.
114
114
 
115
115
  Do not use API system-prompt replacement for ordinary learning. That can clobber the compiled prompt. Edit the memory files instead.
116
116
 
@@ -5,15 +5,7 @@ description: Diagnose and repair MemFS repository setup, remote sync, authentica
5
5
 
6
6
  # MemFS Repository Repair
7
7
 
8
- Use this skill only when the Git repository behind an agent's memory is not
9
- setting up or syncing correctly. For ordinary memory reads and edits, use the
10
- memory files without loading this skill. Delegate incidental upkeep during another
11
- task to the background `memory` subagent. When memory repair is the user's main
12
- request, diagnose and repair it directly, then verify the result before reporting
13
- success. If a memory worker you launched may still be running, wait for it: re-read
14
- its output file until it ends with `[Task completed]` or `[Task failed]` before
15
- editing memory files or changing Git state. While the harness reports a repair in
16
- progress, leave both alone as well; that worker edits `$MEMORY_DIR` in place.
8
+ Use this skill when the Git repository behind an agent's memory is not setting up or syncing correctly. Before editing memory, load the relevant skills: `maintaining-core-memory` for root/core files, `maintaining-deferred-memory` for deferred files, and `creating-skills` for skills. Then make the edit directly with ordinary file tools so the agent with the context applies the memory guidance. Stage only changed files by explicit path and commit in the same shell call. Verify the repair before reporting success. While the harness reports a repair in progress, leave memory files and Git state to that repair worker, which edits `$MEMORY_DIR` in place.
17
9
 
18
10
  ## Current Model
19
11
 
@@ -122,7 +114,7 @@ cloud-backed agent's pending commits after the turn.
122
114
  The harness first tries a fast-forward pull. When a remote push is rejected
123
115
  because the remote moved, post-turn sync tries `git pull --rebase` and retries
124
116
  the push. If that rebase conflicts, the harness launches a fresh background
125
- `memory` subagent to repair it. The primary conversation is not interrupted.
117
+ internal `memory` subagent to repair it. The primary conversation is not interrupted.
126
118
  The harness retries normal sync after the worker finishes. The instructions
127
119
  below are for explicit troubleshooting; routine repairs run in the background.
128
120
 
@@ -1,83 +0,0 @@
1
- export interface ReflectionMemoryWorktree {
2
- id: string;
3
- parentMemoryDir: string;
4
- worktreeBaseDir: string;
5
- worktreeDir: string;
6
- branchName: string;
7
- baseHead: string;
8
- gitCommonDir: string;
9
- }
10
- export interface CreateReflectionMemoryWorktreeOptions {
11
- parentMemoryDir: string;
12
- now?: Date;
13
- /** Directory and branch prefix; memory workers use "memory-worker". */
14
- label?: string;
15
- }
16
- export declare function createReflectionMemoryWorktree(options: CreateReflectionMemoryWorktreeOptions): Promise<ReflectionMemoryWorktree>;
17
- export interface ReflectionMemoryScope {
18
- primaryRoot: string;
19
- writableRoots: string[];
20
- readonlyRoots: string[];
21
- }
22
- export declare function buildReflectionMemoryScope(worktree: ReflectionMemoryWorktree): ReflectionMemoryScope;
23
- export declare function buildReflectionIntegrationMemoryScope(worktree: ReflectionMemoryWorktree): ReflectionMemoryScope;
24
- export type ReflectionMemoryWorktreeFinalizeStatus = "merged" | "no_changes" | "parent_dirty" | "merge_conflict" | "dirty_uncommitted" | "failed";
25
- export interface ReflectionMemoryWorktreeFinalizeResult {
26
- status: ReflectionMemoryWorktreeFinalizeStatus;
27
- parentMemoryDir: string;
28
- reflectionWorktreeDir: string;
29
- reflectionBranch: string;
30
- commitCount: number;
31
- head?: string;
32
- summary: string;
33
- error?: string;
34
- failurePhase?: "reflection" | "integration";
35
- }
36
- export declare function reflectionIntegrationConsumesTranscript(result: ReflectionMemoryWorktreeFinalizeResult): boolean;
37
- export declare function reflectionIntegrationShouldRecompile(result: ReflectionMemoryWorktreeFinalizeResult): boolean;
38
- export declare function reflectionMemoryParentHasChanges(parentMemoryDir: string): Promise<boolean>;
39
- export interface ReflectionMemoryWorktreeState {
40
- commitCount: number;
41
- dirty: boolean;
42
- head?: string;
43
- }
44
- export declare function inspectReflectionMemoryWorktree(worktree: ReflectionMemoryWorktree): Promise<ReflectionMemoryWorktreeState>;
45
- /** Finalize without taking the checkout lease; the caller must already hold it. */
46
- export declare function finalizeReflectionMemoryWorktreeUnlocked(worktree: ReflectionMemoryWorktree, options: ReflectionMemoryWorktreeFinalizeOptions): Promise<ReflectionMemoryWorktreeFinalizeResult>;
47
- export declare function finalizeReflectionMemoryWorktree(worktree: ReflectionMemoryWorktree, options: ReflectionMemoryWorktreeFinalizeOptions): Promise<ReflectionMemoryWorktreeFinalizeResult>;
48
- export type MemoryWorkerWorktreeOutcome = {
49
- status: "merged";
50
- commitCount: number;
51
- head: string;
52
- } | {
53
- status: "no_changes";
54
- } | {
55
- status: "discarded";
56
- } | {
57
- status: "merge_conflict";
58
- commitCount: number;
59
- branchName: string;
60
- } | {
61
- status: "failed";
62
- error: string;
63
- };
64
- /**
65
- * Bring a memory worker's worktree back into the parent checkout. The caller
66
- * must hold the checkout lease. Commits are merged (fast-forward when
67
- * possible); uncommitted edits in the worktree are dropped, as are all edits
68
- * when `discard` is set (a cancelled worker). A merge that conflicts with the
69
- * parent's own changes is aborted and the branch is kept so nothing is lost.
70
- * Unlike reflection, a dirty parent does not block the merge: Git refuses only
71
- * when the parent's uncommitted files overlap the worker's commits, and that
72
- * surfaces as a conflict here.
73
- */
74
- export declare function integrateMemoryWorkerWorktree(worktree: ReflectionMemoryWorktree, options?: {
75
- discard?: boolean;
76
- }): Promise<MemoryWorkerWorktreeOutcome>;
77
- export interface ReflectionMemoryWorktreeFinalizeOptions {
78
- shouldMerge: boolean;
79
- knownNoChanges?: boolean;
80
- failureSummary?: string;
81
- requireAlreadyMerged?: boolean;
82
- }
83
- //# sourceMappingURL=memory-worktree.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"memory-worktree.d.ts","sourceRoot":"","sources":["../../../src/agent/memory-worktree.ts"],"names":[],"mappings":"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"}