@letta-ai/letta-code 0.31.5 → 0.31.7
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/dist/agent-presets.js +5 -1
- package/dist/agent-presets.js.map +2 -2
- package/dist/mcp-client.js +2 -2
- package/dist/mcp-client.js.map +1 -1
- package/dist/types/agent/attached-repositories.d.ts +7 -0
- package/dist/types/agent/attached-repositories.d.ts.map +1 -0
- package/dist/types/agent/client-skills.d.ts +1 -1
- package/dist/types/agent/client-skills.d.ts.map +1 -1
- package/dist/types/agent/memory-constraints.d.ts +22 -0
- package/dist/types/agent/memory-constraints.d.ts.map +1 -0
- package/dist/types/agent/memory-git-hooks.d.ts +5 -5
- package/dist/types/agent/memory-git-hooks.d.ts.map +1 -1
- package/dist/types/agent/memory-git.d.ts +36 -5
- package/dist/types/agent/memory-git.d.ts.map +1 -1
- package/dist/types/agent/shared-memory-skills.d.ts +1 -1
- package/dist/types/agent/shared-memory-skills.d.ts.map +1 -1
- package/dist/types/backend/backend.d.ts +7 -0
- package/dist/types/backend/backend.d.ts.map +1 -1
- package/dist/types/backend/dev/pi-model-factory.d.ts +0 -1
- package/dist/types/backend/dev/pi-model-factory.d.ts.map +1 -1
- package/dist/types/backend/local/local-model-config.d.ts.map +1 -1
- package/dist/types/tools/impl/skill.d.ts +1 -1
- package/dist/types/tools/impl/skill.d.ts.map +1 -1
- package/dist/types/utils/secrets-store.d.ts +4 -0
- package/dist/types/utils/secrets-store.d.ts.map +1 -1
- package/letta.js +20198 -20036
- package/package.json +2 -2
- package/scripts/agent-watch/verify-pr-identity.test.ts +97 -0
- package/scripts/agent-watch/verify-pr-identity.ts +119 -0
- package/scripts/builtin-skills-watch/aggregate-results.ts +13 -8
- package/scripts/builtin-skills-watch/evidence.test.ts +61 -17
- package/scripts/builtin-skills-watch/evidence.ts +66 -38
- package/scripts/builtin-skills-watch/finalize-result.test.ts +125 -0
- package/scripts/builtin-skills-watch/finalize-result.ts +44 -0
- package/scripts/builtin-skills-watch/reconcile-results.test.ts +58 -0
- package/scripts/builtin-skills-watch/reconcile-results.ts +311 -0
- package/scripts/builtin-skills-watch/result-artifacts.test.ts +83 -0
- package/scripts/builtin-skills-watch/result-artifacts.ts +160 -0
- package/scripts/builtin-skills-watch/update-tracker.test.ts +17 -1
- package/scripts/builtin-skills-watch/update-tracker.ts +89 -15
- package/scripts/claude-watch/update-tracker.test.ts +30 -0
- package/scripts/claude-watch/update-tracker.ts +19 -5
- package/scripts/codex-watch/update-tracker.test.ts +28 -0
- package/scripts/codex-watch/update-tracker.ts +59 -7
- package/scripts/source-file-size-baseline.json +1 -1
- package/skills/letta-guide/SKILL.md +6 -2
- package/skills/managing-shared-memory/SKILL.md +4 -5
- package/skills/submitting-feedback/SKILL.md +21 -0
|
@@ -37,6 +37,7 @@ export interface PullRequestView {
|
|
|
37
37
|
files: Array<{ path: string }>;
|
|
38
38
|
headRefOid: string;
|
|
39
39
|
isDraft: boolean;
|
|
40
|
+
mergedAt?: string | null;
|
|
40
41
|
state: string;
|
|
41
42
|
url: string;
|
|
42
43
|
}
|
|
@@ -337,19 +338,35 @@ export function parseReviewResult(
|
|
|
337
338
|
"notes",
|
|
338
339
|
"pr_url",
|
|
339
340
|
"evidence",
|
|
340
|
-
])
|
|
341
|
-
|
|
341
|
+
])
|
|
342
|
+
) {
|
|
343
|
+
throw new Error("Review result has unknown or missing fields");
|
|
344
|
+
}
|
|
345
|
+
if (value.schema_version !== 1) {
|
|
346
|
+
throw new Error("Review result must use schema version 1");
|
|
347
|
+
}
|
|
348
|
+
if (
|
|
342
349
|
value.candidate_id !== analysis.candidate_id ||
|
|
343
|
-
value.skill !== analysis.skill
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
350
|
+
value.skill !== analysis.skill
|
|
351
|
+
) {
|
|
352
|
+
throw new Error("Review result does not match the pending candidate");
|
|
353
|
+
}
|
|
354
|
+
if (
|
|
355
|
+
value.outcome !== "no_drift" &&
|
|
356
|
+
value.outcome !== "pr_created" &&
|
|
357
|
+
value.outcome !== "needs_human_review"
|
|
358
|
+
) {
|
|
359
|
+
throw new Error("Review result outcome is invalid");
|
|
360
|
+
}
|
|
361
|
+
if (
|
|
347
362
|
typeof value.notes !== "string" ||
|
|
348
363
|
value.notes.length === 0 ||
|
|
349
|
-
value.notes.length > 120
|
|
350
|
-
(value.pr_url !== null && typeof value.pr_url !== "string")
|
|
364
|
+
value.notes.length > 120
|
|
351
365
|
) {
|
|
352
|
-
throw new Error("Review result
|
|
366
|
+
throw new Error("Review result notes must contain 1 to 120 characters");
|
|
367
|
+
}
|
|
368
|
+
if (value.pr_url !== null && typeof value.pr_url !== "string") {
|
|
369
|
+
throw new Error("Review result pr_url must be a string or null");
|
|
353
370
|
}
|
|
354
371
|
if (
|
|
355
372
|
(value.outcome === "pr_created") !==
|
|
@@ -381,28 +398,61 @@ export function verifyPullRequest(
|
|
|
381
398
|
analysis: BuiltinSkillWatchAnalysis,
|
|
382
399
|
expectedGithubLogin: string,
|
|
383
400
|
): void {
|
|
401
|
+
verifyPullRequestIdentity(expectedGithubLogin);
|
|
402
|
+
const pullRequest = getPullRequest(repo, prUrl);
|
|
403
|
+
validatePullRequestView(pullRequest, prUrl, analysis, expectedGithubLogin);
|
|
404
|
+
verifyPullRequestAncestry(repo, pullRequest, analysis);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function verifyReconciledPullRequest(
|
|
408
|
+
repo: string,
|
|
409
|
+
prUrl: string,
|
|
410
|
+
analysis: BuiltinSkillWatchAnalysis,
|
|
411
|
+
expectedGithubLogin: string,
|
|
412
|
+
): void {
|
|
413
|
+
verifyPullRequestIdentity(expectedGithubLogin);
|
|
414
|
+
const pullRequest = getPullRequest(repo, prUrl);
|
|
415
|
+
validateReconciledPullRequestView(
|
|
416
|
+
pullRequest,
|
|
417
|
+
prUrl,
|
|
418
|
+
analysis,
|
|
419
|
+
expectedGithubLogin,
|
|
420
|
+
);
|
|
421
|
+
verifyPullRequestAncestry(repo, pullRequest, analysis);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function verifyPullRequestIdentity(expectedGithubLogin: string): void {
|
|
384
425
|
const authenticatedLogin = ghJson<{ login: string }>(["api", "user"]).login;
|
|
385
426
|
if (authenticatedLogin !== expectedGithubLogin) {
|
|
386
427
|
throw new Error(
|
|
387
428
|
`Authenticated GitHub login ${authenticatedLogin} does not match ${expectedGithubLogin}`,
|
|
388
429
|
);
|
|
389
430
|
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function getPullRequest(repo: string, prUrl: string): PullRequestView {
|
|
390
434
|
const match = prUrl.match(
|
|
391
435
|
/^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)\/?$/,
|
|
392
436
|
);
|
|
393
437
|
if (!match || match[1] !== repo) {
|
|
394
438
|
throw new Error(`PR URL must belong to https://github.com/${repo}`);
|
|
395
439
|
}
|
|
396
|
-
|
|
440
|
+
return ghJson<PullRequestView>([
|
|
397
441
|
"pr",
|
|
398
442
|
"view",
|
|
399
443
|
match[2] as string,
|
|
400
444
|
"--repo",
|
|
401
445
|
repo,
|
|
402
446
|
"--json",
|
|
403
|
-
"author,baseRefName,body,files,headRefOid,isDraft,state,url",
|
|
447
|
+
"author,baseRefName,body,files,headRefOid,isDraft,mergedAt,state,url",
|
|
404
448
|
]);
|
|
405
|
-
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function verifyPullRequestAncestry(
|
|
452
|
+
repo: string,
|
|
453
|
+
pullRequest: PullRequestView,
|
|
454
|
+
analysis: BuiltinSkillWatchAnalysis,
|
|
455
|
+
): void {
|
|
406
456
|
const comparison = ghJson<{
|
|
407
457
|
status: string;
|
|
408
458
|
merge_base_commit: { sha: string };
|
|
@@ -423,6 +473,33 @@ export function validatePullRequestView(
|
|
|
423
473
|
prUrl: string,
|
|
424
474
|
analysis: BuiltinSkillWatchAnalysis,
|
|
425
475
|
expectedGithubLogin: string,
|
|
476
|
+
): void {
|
|
477
|
+
validatePullRequestScope(pullRequest, prUrl, analysis, expectedGithubLogin);
|
|
478
|
+
if (pullRequest.state !== "OPEN" || !pullRequest.isDraft) {
|
|
479
|
+
throw new Error("Watcher PR must be open and draft");
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function validateReconciledPullRequestView(
|
|
484
|
+
pullRequest: PullRequestView,
|
|
485
|
+
prUrl: string,
|
|
486
|
+
analysis: BuiltinSkillWatchAnalysis,
|
|
487
|
+
expectedGithubLogin: string,
|
|
488
|
+
): void {
|
|
489
|
+
validatePullRequestScope(pullRequest, prUrl, analysis, expectedGithubLogin);
|
|
490
|
+
const isOpenDraft = pullRequest.state === "OPEN" && pullRequest.isDraft;
|
|
491
|
+
const isMerged =
|
|
492
|
+
pullRequest.state === "MERGED" && typeof pullRequest.mergedAt === "string";
|
|
493
|
+
if (!isOpenDraft && !isMerged) {
|
|
494
|
+
throw new Error("Reconciled watcher PR must be an open draft or merged");
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function validatePullRequestScope(
|
|
499
|
+
pullRequest: PullRequestView,
|
|
500
|
+
prUrl: string,
|
|
501
|
+
analysis: BuiltinSkillWatchAnalysis,
|
|
502
|
+
expectedGithubLogin: string,
|
|
426
503
|
): void {
|
|
427
504
|
if (pullRequest.url !== prUrl.replace(/\/$/, "")) {
|
|
428
505
|
throw new Error(`PR URL mismatch: ${pullRequest.url}`);
|
|
@@ -432,9 +509,6 @@ export function validatePullRequestView(
|
|
|
432
509
|
`PR author ${pullRequest.author.login} does not match ${expectedGithubLogin}`,
|
|
433
510
|
);
|
|
434
511
|
}
|
|
435
|
-
if (pullRequest.state !== "OPEN" || !pullRequest.isDraft) {
|
|
436
|
-
throw new Error("Watcher PR must be open and draft");
|
|
437
|
-
}
|
|
438
512
|
if (pullRequest.baseRefName !== "main") {
|
|
439
513
|
throw new Error("Watcher PR must target main");
|
|
440
514
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { parseArgs } from "./update-tracker.ts";
|
|
3
|
+
|
|
4
|
+
describe("parseArgs", () => {
|
|
5
|
+
const required = [
|
|
6
|
+
"--tracker-issue",
|
|
7
|
+
"123",
|
|
8
|
+
"--analysis-file",
|
|
9
|
+
"/tmp/analysis.json",
|
|
10
|
+
"--state-commit-sha",
|
|
11
|
+
"abc123",
|
|
12
|
+
"--outcome",
|
|
13
|
+
"pr_created",
|
|
14
|
+
"--pr-url",
|
|
15
|
+
"https://github.com/letta-ai/letta-code/pull/456",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
test("requires the expected GitHub login for a PR", () => {
|
|
19
|
+
expect(() => parseArgs(required)).toThrow(
|
|
20
|
+
"--expected-github-login is required for pr_created",
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("accepts the expected GitHub login for a PR", () => {
|
|
25
|
+
expect(
|
|
26
|
+
parseArgs([...required, "--expected-github-login", "amelia-letta"])
|
|
27
|
+
.expectedGithubLogin,
|
|
28
|
+
).toBe("amelia-letta");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -27,6 +27,7 @@ interface Args {
|
|
|
27
27
|
outcome: ClaudeWatchOutcome | null;
|
|
28
28
|
notes: string;
|
|
29
29
|
prUrl: string | null;
|
|
30
|
+
expectedGithubLogin: string | null;
|
|
30
31
|
assertTerminal: boolean;
|
|
31
32
|
dryRun: boolean;
|
|
32
33
|
}
|
|
@@ -41,6 +42,7 @@ export function parseArgs(argv: string[]): Args {
|
|
|
41
42
|
outcome: null,
|
|
42
43
|
notes: "",
|
|
43
44
|
prUrl: null,
|
|
45
|
+
expectedGithubLogin: null,
|
|
44
46
|
assertTerminal: false,
|
|
45
47
|
dryRun: false,
|
|
46
48
|
};
|
|
@@ -59,6 +61,8 @@ export function parseArgs(argv: string[]): Args {
|
|
|
59
61
|
args.outcome = parseOutcome(argv[++index]);
|
|
60
62
|
else if (argument === "--notes") args.notes = argv[++index] ?? "";
|
|
61
63
|
else if (argument === "--pr-url") args.prUrl = argv[++index] ?? null;
|
|
64
|
+
else if (argument === "--expected-github-login")
|
|
65
|
+
args.expectedGithubLogin = argv[++index] ?? null;
|
|
62
66
|
else if (argument === "--assert-terminal") args.assertTerminal = true;
|
|
63
67
|
else if (argument === "--dry-run") args.dryRun = true;
|
|
64
68
|
else throw new Error(`Unknown argument: ${argument}`);
|
|
@@ -75,8 +79,12 @@ export function parseArgs(argv: string[]): Args {
|
|
|
75
79
|
if (!args.outcome) throw new Error("--outcome is required");
|
|
76
80
|
if (isTerminalOutcome(args.outcome) && !args.stateCommitSha)
|
|
77
81
|
throw new Error("terminal outcomes require --state-commit-sha");
|
|
78
|
-
if (args.outcome === "pr_created"
|
|
79
|
-
throw new Error("--pr-url is required for pr_created");
|
|
82
|
+
if (args.outcome === "pr_created") {
|
|
83
|
+
if (!args.prUrl) throw new Error("--pr-url is required for pr_created");
|
|
84
|
+
if (!args.expectedGithubLogin) {
|
|
85
|
+
throw new Error("--expected-github-login is required for pr_created");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
80
88
|
}
|
|
81
89
|
return args;
|
|
82
90
|
}
|
|
@@ -117,15 +125,16 @@ function verifyParityPr(
|
|
|
117
125
|
repo: string,
|
|
118
126
|
prUrl: string,
|
|
119
127
|
candidateId: string,
|
|
128
|
+
expectedGithubLogin: string,
|
|
120
129
|
): void {
|
|
121
130
|
const pr = ghJson<{
|
|
122
131
|
isDraft: boolean;
|
|
123
132
|
author: { login: string };
|
|
124
133
|
body: string | null;
|
|
125
134
|
}>(["pr", "view", prUrl, "--repo", repo, "--json", "isDraft,author,body"]);
|
|
126
|
-
if (!pr.isDraft || pr.author.login !==
|
|
135
|
+
if (!pr.isDraft || pr.author.login !== expectedGithubLogin) {
|
|
127
136
|
throw new Error(
|
|
128
|
-
`Parity PR must be a draft authored by
|
|
137
|
+
`Parity PR must be a draft authored by ${expectedGithubLogin} (got draft=${pr.isDraft}, author=${pr.author.login})`,
|
|
129
138
|
);
|
|
130
139
|
}
|
|
131
140
|
if (!pr.body?.includes(`Claude-watch: ${candidateId}`)) {
|
|
@@ -164,7 +173,12 @@ export function main(argv = process.argv.slice(2)): void {
|
|
|
164
173
|
verifyStateCandidate(analysis.candidate_id, args.stateCommitSha as string);
|
|
165
174
|
}
|
|
166
175
|
if (args.outcome === "pr_created") {
|
|
167
|
-
verifyParityPr(
|
|
176
|
+
verifyParityPr(
|
|
177
|
+
args.repo,
|
|
178
|
+
args.prUrl as string,
|
|
179
|
+
analysis.candidate_id,
|
|
180
|
+
args.expectedGithubLogin as string,
|
|
181
|
+
);
|
|
168
182
|
}
|
|
169
183
|
const next = recordAnalysis(state, {
|
|
170
184
|
analysis,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { parseArgs } from "./update-tracker.ts";
|
|
3
|
+
|
|
4
|
+
describe("parseArgs", () => {
|
|
5
|
+
const required = [
|
|
6
|
+
"--tracker-issue",
|
|
7
|
+
"123",
|
|
8
|
+
"--analysis-file",
|
|
9
|
+
"/tmp/analysis.json",
|
|
10
|
+
"--outcome",
|
|
11
|
+
"pr_created",
|
|
12
|
+
"--pr-url",
|
|
13
|
+
"https://github.com/letta-ai/letta-code/pull/456",
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
test("requires the expected GitHub login for a PR", () => {
|
|
17
|
+
expect(() => parseArgs(required)).toThrow(
|
|
18
|
+
"--expected-github-login is required for pr_created",
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("accepts the expected GitHub login for a PR", () => {
|
|
23
|
+
expect(
|
|
24
|
+
parseArgs([...required, "--expected-github-login", "amelia-letta"])
|
|
25
|
+
.expectedGithubLogin,
|
|
26
|
+
).toBe("amelia-letta");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { readFileSync } from "node:fs";
|
|
10
|
-
import { editIssueBody, getIssueBody } from "./github.ts";
|
|
10
|
+
import { editIssueBody, getIssueBody, ghJson } from "./github.ts";
|
|
11
11
|
import {
|
|
12
12
|
type CodexWatchAnalysis,
|
|
13
13
|
DEFAULT_TARGET_REPO,
|
|
@@ -26,10 +26,11 @@ interface Args {
|
|
|
26
26
|
outcome: TrackerOutcome | null;
|
|
27
27
|
notes: string;
|
|
28
28
|
prUrl: string | null;
|
|
29
|
+
expectedGithubLogin: string | null;
|
|
29
30
|
dryRun: boolean;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function parseArgs(argv: string[]): Args {
|
|
33
|
+
export function parseArgs(argv: string[]): Args {
|
|
33
34
|
const args: Args = {
|
|
34
35
|
repo: DEFAULT_TARGET_REPO,
|
|
35
36
|
trackerIssue: null,
|
|
@@ -37,6 +38,7 @@ function parseArgs(argv: string[]): Args {
|
|
|
37
38
|
outcome: null,
|
|
38
39
|
notes: "",
|
|
39
40
|
prUrl: null,
|
|
41
|
+
expectedGithubLogin: null,
|
|
40
42
|
dryRun: false,
|
|
41
43
|
};
|
|
42
44
|
|
|
@@ -49,10 +51,12 @@ function parseArgs(argv: string[]): Args {
|
|
|
49
51
|
else if (a === "--outcome") args.outcome = parseOutcome(argv[++i]);
|
|
50
52
|
else if (a === "--notes") args.notes = argv[++i] ?? "";
|
|
51
53
|
else if (a === "--pr-url") args.prUrl = argv[++i] ?? null;
|
|
52
|
-
else if (a === "--
|
|
54
|
+
else if (a === "--expected-github-login") {
|
|
55
|
+
args.expectedGithubLogin = argv[++i] ?? null;
|
|
56
|
+
} else if (a === "--dry-run") args.dryRun = true;
|
|
53
57
|
else if (a === "--help" || a === "-h") {
|
|
54
58
|
console.log(
|
|
55
|
-
"Usage: bun scripts/codex-watch/update-tracker.ts --tracker-issue ISSUE --analysis-file FILE --outcome OUTCOME [--notes TEXT] [--pr-url URL] [--repo OWNER/REPO] [--dry-run]",
|
|
59
|
+
"Usage: bun scripts/codex-watch/update-tracker.ts --tracker-issue ISSUE --analysis-file FILE --outcome OUTCOME [--notes TEXT] [--pr-url URL --expected-github-login LOGIN] [--repo OWNER/REPO] [--dry-run]",
|
|
56
60
|
);
|
|
57
61
|
process.exit(0);
|
|
58
62
|
} else {
|
|
@@ -65,8 +69,13 @@ function parseArgs(argv: string[]): Args {
|
|
|
65
69
|
}
|
|
66
70
|
if (!args.analysisFile) throw new Error("--analysis-file is required");
|
|
67
71
|
if (!args.outcome) throw new Error("--outcome is required");
|
|
68
|
-
if (args.outcome === "pr_created"
|
|
69
|
-
|
|
72
|
+
if (args.outcome === "pr_created") {
|
|
73
|
+
if (!args.prUrl) {
|
|
74
|
+
throw new Error("--pr-url is required when --outcome pr_created");
|
|
75
|
+
}
|
|
76
|
+
if (!args.expectedGithubLogin) {
|
|
77
|
+
throw new Error("--expected-github-login is required for pr_created");
|
|
78
|
+
}
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
return args;
|
|
@@ -94,6 +103,14 @@ function main() {
|
|
|
94
103
|
const analysis = readAnalysis(args.analysisFile as string);
|
|
95
104
|
const body = getIssueBody(args.repo, args.trackerIssue as number);
|
|
96
105
|
const state = parseTrackerState(body);
|
|
106
|
+
if (args.outcome === "pr_created") {
|
|
107
|
+
verifyParityPr(
|
|
108
|
+
args.repo,
|
|
109
|
+
args.prUrl as string,
|
|
110
|
+
args.expectedGithubLogin as string,
|
|
111
|
+
analysis.current_tag,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
97
114
|
const next = recordAnalysis(state, {
|
|
98
115
|
analysis,
|
|
99
116
|
outcome: args.outcome as TrackerOutcome,
|
|
@@ -113,6 +130,41 @@ function main() {
|
|
|
113
130
|
);
|
|
114
131
|
}
|
|
115
132
|
|
|
133
|
+
function verifyParityPr(
|
|
134
|
+
repo: string,
|
|
135
|
+
prUrl: string,
|
|
136
|
+
expectedGithubLogin: string,
|
|
137
|
+
currentTag: string,
|
|
138
|
+
): void {
|
|
139
|
+
const pullRequest = ghJson<{
|
|
140
|
+
author: { login: string };
|
|
141
|
+
body: string | null;
|
|
142
|
+
isDraft: boolean;
|
|
143
|
+
state: string;
|
|
144
|
+
}>([
|
|
145
|
+
"pr",
|
|
146
|
+
"view",
|
|
147
|
+
prUrl,
|
|
148
|
+
"--repo",
|
|
149
|
+
repo,
|
|
150
|
+
"--json",
|
|
151
|
+
"author,body,isDraft,state",
|
|
152
|
+
]);
|
|
153
|
+
if (
|
|
154
|
+
pullRequest.author.login !== expectedGithubLogin ||
|
|
155
|
+
!pullRequest.isDraft ||
|
|
156
|
+
pullRequest.state !== "OPEN"
|
|
157
|
+
) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Codex PR must be an open draft authored by ${expectedGithubLogin} (got author=${pullRequest.author.login}, draft=${pullRequest.isDraft}, state=${pullRequest.state})`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const marker = `Codex-watch: openai/codex ${currentTag}`;
|
|
163
|
+
if (!pullRequest.body?.includes(marker)) {
|
|
164
|
+
throw new Error(`Codex PR body is missing marker: ${marker}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
116
168
|
function defaultNotes(outcome: TrackerOutcome): string {
|
|
117
169
|
switch (outcome) {
|
|
118
170
|
case "recorded_noop":
|
|
@@ -128,4 +180,4 @@ function defaultNotes(outcome: TrackerOutcome): string {
|
|
|
128
180
|
}
|
|
129
181
|
}
|
|
130
182
|
|
|
131
|
-
main();
|
|
183
|
+
if (import.meta.main) main();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: letta-guide
|
|
3
|
-
description:
|
|
3
|
+
description: Read the official Letta documentation (docs.letta.com) through its cached, ETag-checked fetch route. Load before ANY docs.letta.com retrieval — answering how Letta works, what Letta (or you) can do, setting up providers, models, channels, skills, memory, schedules, permissions, self-hosting, pricing, or billing, AND looking up Letta API, Agent SDK, or Letta Code reference while writing code. Do not use fetch_webpage or web_search on docs.letta.com; this skill's helper is the docs route. Never answer Letta product questions from memory alone.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Letta Guide
|
|
@@ -8,7 +8,11 @@ description: Answer questions about Letta itself from the official documentation
|
|
|
8
8
|
You are running inside Letta, but your training data about Letta's commands,
|
|
9
9
|
flags, settings, UI, pricing, and providers is out of date. Users lose trust
|
|
10
10
|
fastest when an agent confidently invents product details. This skill defines
|
|
11
|
-
how to
|
|
11
|
+
how to read the Letta docs correctly — both when answering questions about
|
|
12
|
+
Letta and when looking up API, Agent SDK, or Letta Code reference during
|
|
13
|
+
development. Guessing a docs URL and fetching it with `fetch_webpage` misses
|
|
14
|
+
pages that exist under a different path and can serve stale content; the
|
|
15
|
+
helper below fetches the live index first, so you pick a URL that exists.
|
|
12
16
|
|
|
13
17
|
## Source route (in order)
|
|
14
18
|
|
|
@@ -7,7 +7,7 @@ description: Create and manage shared memory — git-tracked repositories hosted
|
|
|
7
7
|
|
|
8
8
|
Shared memory is memory created independently of any single agent, designed to be dynamically attached to or detached from multiple agents. Each unit of shared memory is a **shared memory repository**: a git repository hosted on Letta Cloud, owned by your organization rather than by one agent, reachable from any environment (sandboxes, remote machines, sessions).
|
|
9
9
|
|
|
10
|
-
Shared memory works
|
|
10
|
+
Shared memory works like your MemFS: attached repositories are real git checkouts on disk, and you read, edit, and commit with ordinary git. The harness pushes clean committed changes after each turn. Each repository has its own projection root (next to your memory directory, not inside it) and its own remote origin, and other agents may be writing to it too.
|
|
11
11
|
|
|
12
12
|
Create a shared memory repository when:
|
|
13
13
|
- You have context an agent should be able to access that doesn't belong in its own MemFS (input files, datasets, docs, working artifacts)
|
|
@@ -23,16 +23,15 @@ ls "$MEMORY_DIR/../" # attached repositories appear here by
|
|
|
23
23
|
cat "$MEMORY_DIR/../<repo-name>/<path>" # read like any file
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Edit files with your normal file tools, then commit
|
|
26
|
+
Edit files with your normal file tools, then commit with git. The mount's origin and credentials are already configured:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
cd "$MEMORY_DIR/../<repo-name>"
|
|
30
30
|
git add <files>
|
|
31
31
|
git commit -m "describe the change"
|
|
32
|
-
git push
|
|
33
32
|
```
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
The harness pushes clean commits from read/write attached repositories after the turn. If a push collides with another agent's work, it pulls with rebase and retries once. Dirty files and conflicts are not changed automatically; the harness adds a reminder to the next turn instead.
|
|
36
35
|
|
|
37
36
|
To pick up other agents' changes:
|
|
38
37
|
|
|
@@ -84,7 +83,7 @@ letta shared-memory history shared-notes --path docs/plan.md
|
|
|
84
83
|
- **`sync` reports "mount path already exists and is not a git repository"** — a plain directory (usually created by hand before the mount existed) is occupying the mount path. Inspect it, salvage anything worth keeping, move or delete it, then re-run `letta shared-memory sync`.
|
|
85
84
|
- **Never hand-clone the repository to another location (e.g. /tmp) to work around a broken mount** — fix the mount with `letta shared-memory sync` so every session and other agents see the same checkout.
|
|
86
85
|
- **Permission denied under another agent's directory** — shared repositories mount per-agent. Only your own mount (under your agent directory) is accessible; another agent's mount of the same repository is walled off by the cross-agent guard. Run `letta shared-memory sync` to get your own mount.
|
|
87
|
-
- **
|
|
86
|
+
- **Shared-memory conflict reminder** — resolve the conflict in the named repository, finish the rebase or commit, and leave the repository clean. The harness retries the push after a future turn.
|
|
88
87
|
|
|
89
88
|
## Notes and Limits
|
|
90
89
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: submitting-feedback
|
|
3
|
+
description: Submits user-approved feedback about Letta Code or the current agent to the Letta team. Load when the user is upset, frustrated, dissatisfied, reports poor agent behavior, or asks to send feedback. Works with cloud-hosted and local agents. Ask before submitting unless the user already explicitly requested submission.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Submitting Feedback
|
|
7
|
+
|
|
8
|
+
When the user appears upset with the agent, acknowledge the problem and ask whether they want you to submit feedback to the Letta team. Do not submit merely because the user expressed frustration.
|
|
9
|
+
|
|
10
|
+
If the user says yes, or directly asks you to submit feedback:
|
|
11
|
+
|
|
12
|
+
1. Write a short factual message in the user's voice. Include what happened, what the user expected, and any useful error or behavior detail already present in the conversation. Do not add claims the user did not make.
|
|
13
|
+
2. Submit it with:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
letta feedback --message '<feedback>'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
3. Tell the user whether submission succeeded. If it failed, report the safe CLI error and do not claim the team received it.
|
|
20
|
+
|
|
21
|
+
Do not include secrets, credentials, unrelated conversation content, or private file contents. The command adds the current agent and conversation identifiers so the team can find the relevant run.
|