@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.
- package/cursor-worker.js +167 -0
- package/dist/agent-presets.js +1 -64
- package/dist/agent-presets.js.map +2 -2
- package/dist/channels-slack.js +30 -24
- package/dist/channels-slack.js.map +3 -3
- package/dist/mcp-client.js +233 -75
- package/dist/mcp-client.js.map +9 -9
- package/dist/mcp-oauth.js +233 -75
- package/dist/mcp-oauth.js.map +9 -9
- package/dist/types/agent/subagents/index.d.ts +4 -0
- package/dist/types/agent/subagents/index.d.ts.map +1 -1
- package/dist/types/agent/subagents/manager.d.ts.map +1 -1
- package/dist/types/agent/subagents/memory-worker.d.ts +2 -8
- package/dist/types/agent/subagents/memory-worker.d.ts.map +1 -1
- package/dist/types/backend/api/providers.d.ts +8 -2
- package/dist/types/backend/api/providers.d.ts.map +1 -1
- package/dist/types/backend/backend.d.ts +5 -2
- package/dist/types/backend/backend.d.ts.map +1 -1
- package/dist/types/backend/dev/fake-headless-backend.d.ts +2 -1
- package/dist/types/backend/dev/fake-headless-backend.d.ts.map +1 -1
- package/dist/types/backend/dev/pi-model-factory.d.ts.map +1 -1
- package/dist/types/backend/local/local-provider-auth-store.d.ts +2 -0
- package/dist/types/backend/local/local-provider-auth-store.d.ts.map +1 -1
- package/dist/types/backend/local/local-transcript-history.d.ts +10 -0
- package/dist/types/backend/local/local-transcript-history.d.ts.map +1 -0
- package/dist/types/backend/local/local-transcript.d.ts +6 -0
- package/dist/types/backend/local/local-transcript.d.ts.map +1 -1
- package/dist/types/channels/slack/attachment-primitives.d.ts +2 -1
- package/dist/types/channels/slack/attachment-primitives.d.ts.map +1 -1
- package/dist/types/cli/subcommands/mcp.d.ts +2 -2
- package/dist/types/cli/subcommands/mcp.d.ts.map +1 -1
- package/dist/types/cron/process-identity.d.ts.map +1 -1
- package/dist/types/cron/run-log.d.ts +1 -0
- package/dist/types/cron/run-log.d.ts.map +1 -1
- package/dist/types/headless-environment-response.d.ts +27 -0
- package/dist/types/headless-environment-response.d.ts.map +1 -1
- package/dist/types/providers/byok-providers.d.ts +14 -0
- package/dist/types/providers/byok-providers.d.ts.map +1 -1
- package/dist/types/tools/impl/claude-stream-session.d.ts.map +1 -1
- package/dist/types/tools/impl/codex-app-server.d.ts.map +1 -1
- package/dist/types/tools/impl/cursor-session.d.ts +26 -0
- package/dist/types/tools/impl/cursor-session.d.ts.map +1 -0
- package/dist/types/tools/impl/cursor-worker.d.ts +32 -0
- package/dist/types/tools/impl/cursor-worker.d.ts.map +1 -0
- package/dist/types/tools/impl/external-coding-agent-task.d.ts.map +1 -1
- package/dist/types/tools/impl/external-coding-agent.d.ts +1 -1
- package/dist/types/tools/impl/external-coding-agent.d.ts.map +1 -1
- package/dist/types/tools/impl/memory-task-lifecycle.d.ts +2 -2
- package/dist/types/tools/impl/memory-task-lifecycle.d.ts.map +1 -1
- package/dist/types/tools/impl/remote-external-coding-agent.d.ts.map +1 -1
- package/dist/types/tools/impl/send-agent-message.d.ts.map +1 -1
- package/dist/types/tools/impl/task.d.ts.map +1 -1
- package/dist/types/tools/workflow/sdk-spawner.d.ts.map +1 -1
- package/dist/types/utils/native-cli-env.d.ts +3 -0
- package/dist/types/utils/native-cli-env.d.ts.map +1 -0
- package/dist/types/utils/process-liveness.d.ts +9 -0
- package/dist/types/utils/process-liveness.d.ts.map +1 -1
- package/letta.js +3512 -2894
- package/npm-shrinkwrap.json +275 -101
- package/package.json +5 -3
- package/scripts/agent-watch/verify-pr-identity.test.ts +148 -0
- package/scripts/agent-watch/verify-pr-identity.ts +72 -14
- package/scripts/claude-watch/agent-watch.ts +8 -2
- package/scripts/claude-watch/release-source.test.ts +120 -0
- package/scripts/claude-watch/release-source.ts +79 -9
- package/scripts/codex-watch/agent-watch.ts +2 -2
- package/scripts/codex-watch/release-analysis.test.ts +112 -6
- package/scripts/codex-watch/release-analysis.ts +63 -6
- package/scripts/source-file-size-baseline.json +5 -4
- package/skills/initializing-memory/SKILL.md +1 -1
- package/skills/managing-agent-webhooks/SKILL.md +23 -3
- package/skills/managing-agent-webhooks/scripts/manage-agent-webhooks.mjs +40 -11
- package/skills/self-configuration/SKILL.md +1 -1
- package/skills/syncing-memory-filesystem/SKILL.md +2 -10
- package/dist/types/agent/memory-worktree.d.ts +0 -83
- 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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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<
|
|
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
|
|
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":
|
|
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/
|
|
22
|
-
"src/
|
|
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":
|
|
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.
|
|
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
|
|
100
|
-
requires the returned webhook ID
|
|
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
|
|
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
|
-
|
|
148
|
-
|
|
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 (
|
|
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(
|
|
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
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
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
|
-
|
|
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
|
|
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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"}
|