@saadnvd1/agent-os 0.3.465 → 0.3.467
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/README.md +14 -4
- package/app/api/git/commit/route.ts +4 -3
- package/app/api/git/file-content/route.ts +4 -15
- package/app/api/git/history/[hash]/diff/route.ts +1 -1
- package/app/api/git/history/[hash]/route.ts +1 -1
- package/app/api/git/history/route.ts +1 -1
- package/app/api/git/push/route.ts +2 -2
- package/app/api/sessions/[id]/pr/route.ts +2 -2
- package/lib/away/deploy.ts +3 -7
- package/lib/away/run.ts +6 -5
- package/lib/build.ts +2 -6
- package/lib/chat/files.ts +31 -9
- package/lib/db/migrations.ts +31 -0
- package/lib/done/remote.ts +5 -6
- package/lib/done/testing.ts +2 -7
- package/lib/done/worktree.ts +2 -2
- package/lib/git-exec.ts +334 -0
- package/lib/git-history.ts +29 -41
- package/lib/git-status.ts +45 -124
- package/lib/git.ts +44 -159
- package/lib/lumifyhub/publish.ts +2 -2
- package/lib/orchestrator/armed.ts +2 -2
- package/lib/orchestrator/brief.ts +1 -1
- package/lib/orchestrator/diff.ts +15 -13
- package/lib/orchestrator/escalate.ts +7 -7
- package/lib/orchestrator/gates.ts +36 -16
- package/lib/orchestrator/rebase.ts +20 -24
- package/lib/orchestrator/repo-slug.ts +4 -7
- package/lib/orchestrator/repo.ts +24 -35
- package/lib/orchestrator/signoff.ts +1 -1
- package/lib/pr-generation.ts +2 -1
- package/lib/pr.ts +16 -57
- package/lib/sessions/worktree-setup.ts +17 -13
- package/lib/stacks/start.ts +7 -11
- package/lib/tasks/branch-worktree.ts +3 -2
- package/lib/tasks/finish.ts +5 -6
- package/lib/tasks/gh.ts +5 -0
- package/lib/tasks/move-testing.ts +2 -2
- package/lib/tasks/move.ts +3 -2
- package/lib/tasks/project-ref.ts +12 -19
- package/lib/tasks/session.ts +5 -5
- package/lib/worktree-hooks.ts +3 -7
- package/lib/worktree-placed.ts +3 -10
- package/lib/worktrees.ts +4 -42
- package/package.json +1 -1
package/lib/tasks/project-ref.ts
CHANGED
|
@@ -7,12 +7,10 @@
|
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import os from "os";
|
|
9
9
|
import path from "path";
|
|
10
|
-
import { execFile } from "child_process";
|
|
11
|
-
import { promisify } from "util";
|
|
12
10
|
import { createProject, getAllProjects } from "../projects";
|
|
13
11
|
import { isRemoteHost } from "../hosts";
|
|
14
12
|
import type { Project } from "../db";
|
|
15
|
-
import {
|
|
13
|
+
import { git, gitOk } from "../git-exec";
|
|
16
14
|
import { expandHome } from "./session";
|
|
17
15
|
|
|
18
16
|
export interface ProjectRef {
|
|
@@ -23,8 +21,6 @@ export interface ProjectRef {
|
|
|
23
21
|
remote: string | null;
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
const execFileAsync = promisify(execFile);
|
|
27
|
-
|
|
28
24
|
/** A ref as another machine sent it: its shape, before anything reads it. */
|
|
29
25
|
export function isProjectRef(v: unknown): v is ProjectRef {
|
|
30
26
|
const r = v as ProjectRef | null;
|
|
@@ -65,9 +61,7 @@ export function repoIdentity(remote: string | null): string | null {
|
|
|
65
61
|
}
|
|
66
62
|
|
|
67
63
|
export async function originOf(dir: string): Promise<string | null> {
|
|
68
|
-
const url = await
|
|
69
|
-
() => ""
|
|
70
|
-
);
|
|
64
|
+
const url = await git(dir, ["remote", "get-url", "origin"]).catch(() => "");
|
|
71
65
|
return url.trim() || null;
|
|
72
66
|
}
|
|
73
67
|
|
|
@@ -122,7 +116,9 @@ export async function ensureProject(ref: ProjectRef): Promise<Project> {
|
|
|
122
116
|
if (!ref.remote || !REMOTE_URL.test(ref.remote))
|
|
123
117
|
throw new Error(`${ref.name} isn't here and has no remote to clone`);
|
|
124
118
|
fs.mkdirSync(path.dirname(dir), { recursive: true });
|
|
125
|
-
await
|
|
119
|
+
await git(os.homedir(), ["clone", "--", ref.remote, dir], {
|
|
120
|
+
timeout: 600000,
|
|
121
|
+
});
|
|
126
122
|
}
|
|
127
123
|
return createProject({
|
|
128
124
|
name: ref.name || path.basename(dir),
|
|
@@ -148,18 +144,15 @@ export async function whyNotHere(ref: ProjectRef): Promise<string | null> {
|
|
|
148
144
|
const remote = withoutCredentials(ref.remote);
|
|
149
145
|
if (!remote || !REMOTE_URL.test(remote))
|
|
150
146
|
return "Not there, and no remote to clone";
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
147
|
+
const reachable = await gitOk(
|
|
148
|
+
os.homedir(),
|
|
149
|
+
["ls-remote", "--heads", "--", remote],
|
|
150
|
+
{
|
|
154
151
|
timeout: 15000,
|
|
155
152
|
env: {
|
|
156
|
-
...process.env,
|
|
157
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
158
153
|
GIT_SSH_COMMAND: process.env.GIT_SSH_COMMAND ?? "ssh -o BatchMode=yes",
|
|
159
154
|
},
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return "Can't clone it there";
|
|
164
|
-
}
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
return reachable ? null : "Can't clone it there";
|
|
165
158
|
}
|
package/lib/tasks/session.ts
CHANGED
|
@@ -3,7 +3,8 @@ import os from "os";
|
|
|
3
3
|
import { db, queries, type Session } from "../db";
|
|
4
4
|
import { isBranchName } from "../git";
|
|
5
5
|
import { getProject } from "../projects";
|
|
6
|
-
import { findPRStrict
|
|
6
|
+
import { findPRStrict } from "./gh";
|
|
7
|
+
import { currentBranch, git } from "../git-exec";
|
|
7
8
|
import { lookupPR } from "./pr-poll";
|
|
8
9
|
import type { TaskPR } from "./state";
|
|
9
10
|
|
|
@@ -43,15 +44,14 @@ async function worktreeBranch(session: Session): Promise<string | null> {
|
|
|
43
44
|
const dir = localWorktree(session);
|
|
44
45
|
if (!dir) return null;
|
|
45
46
|
try {
|
|
46
|
-
|
|
47
|
-
return out.trim() || null;
|
|
47
|
+
return (await currentBranch(dir, { timeout: 5000 })) || null;
|
|
48
48
|
} catch {
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const hasRef = (dir: string, ref: string) =>
|
|
54
|
-
|
|
54
|
+
git(dir, ["rev-parse", "--verify", "--quiet", ref], { timeout: 5000 }).then(
|
|
55
55
|
() => true,
|
|
56
56
|
// Exit 1 is "no such branch"; a timeout or a lock isn't an answer.
|
|
57
57
|
(e: { code?: unknown }) => e?.code !== 1
|
|
@@ -94,7 +94,7 @@ function moveBranch(session: Session, branch: string): void {
|
|
|
94
94
|
|
|
95
95
|
// git merge-base --is-ancestor: exit 0 yes, 1 no, anything else can't say.
|
|
96
96
|
const isAncestor = (dir: string, sha: string, ref: string) =>
|
|
97
|
-
|
|
97
|
+
git(dir, ["merge-base", "--is-ancestor", sha, ref], { timeout: 5000 }).then(
|
|
98
98
|
() => true as const,
|
|
99
99
|
(e: { code?: unknown }) => (e?.code === 1 ? (false as const) : null)
|
|
100
100
|
);
|
package/lib/worktree-hooks.ts
CHANGED
|
@@ -16,17 +16,13 @@
|
|
|
16
16
|
* own git directory, so it goes when the worktree does.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { execFile } from "child_process";
|
|
20
|
-
import { promisify } from "util";
|
|
21
19
|
import fs from "fs";
|
|
22
20
|
import os from "os";
|
|
23
21
|
import path from "path";
|
|
22
|
+
import { git as runGit } from "./git-exec";
|
|
24
23
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const git = async (cwd: string, args: string[]) =>
|
|
28
|
-
(await execFileAsync("git", ["-C", cwd, ...args], { timeout: 10_000 }))
|
|
29
|
-
.stdout;
|
|
24
|
+
const git = (cwd: string, args: string[]) =>
|
|
25
|
+
runGit(cwd, args, { timeout: 10_000 });
|
|
30
26
|
|
|
31
27
|
// The lines that are attribution, as an extended regex for `grep -iE`. The
|
|
32
28
|
// same rule as lib/tasks/code-review.ts's ATTRIBUTION for PR bodies; a test
|
package/lib/worktree-placed.ts
CHANGED
|
@@ -12,12 +12,10 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { createHash } from "crypto";
|
|
15
|
-
import { execFile } from "child_process";
|
|
16
|
-
import { promisify } from "util";
|
|
17
15
|
import fs from "fs";
|
|
18
16
|
import path from "path";
|
|
17
|
+
import { git } from "./git-exec";
|
|
19
18
|
|
|
20
|
-
const execFileAsync = promisify(execFile);
|
|
21
19
|
const MANIFEST = "agentos-placed.json";
|
|
22
20
|
// A copied folder bigger than this isn't recorded, so it keeps blocking.
|
|
23
21
|
const MAX_FILES = 2000;
|
|
@@ -27,13 +25,8 @@ export interface Placed {
|
|
|
27
25
|
files: Record<string, { size: number; sha: string }>;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
timeout: 30000,
|
|
33
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
34
|
-
});
|
|
35
|
-
return stdout;
|
|
36
|
-
}
|
|
28
|
+
const gitText = (cwd: string, args: string[]) =>
|
|
29
|
+
git(cwd, args, { timeout: 30000 });
|
|
37
30
|
|
|
38
31
|
const manifestPath = async (worktree: string) =>
|
|
39
32
|
path.join(
|
package/lib/worktrees.ts
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
* Git Worktree management for isolated feature development
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { execFile } from "child_process";
|
|
6
|
-
import { promisify } from "util";
|
|
7
5
|
import * as path from "path";
|
|
8
6
|
import * as fs from "fs";
|
|
9
7
|
import * as os from "os";
|
|
@@ -15,11 +13,10 @@ import {
|
|
|
15
13
|
generateBranchName,
|
|
16
14
|
} from "./git";
|
|
17
15
|
import { installWorktreeHooks } from "./worktree-hooks";
|
|
18
|
-
|
|
19
|
-
const execFileAsync = promisify(execFile);
|
|
16
|
+
import { gitResult, isGitError, worktreeList } from "./git-exec";
|
|
20
17
|
|
|
21
18
|
const git = (cwd: string, args: string[], timeout: number) =>
|
|
22
|
-
|
|
19
|
+
gitResult(cwd, args, { timeout });
|
|
23
20
|
|
|
24
21
|
/**
|
|
25
22
|
* The main checkout a worktree belongs to, or "" when it can't be read.
|
|
@@ -200,10 +197,7 @@ export function worktreeAddTimeout(
|
|
|
200
197
|
return Math.min(MAX_ADD_TIMEOUT_MS, Math.round(ADD_TIMEOUT_MS * scale));
|
|
201
198
|
}
|
|
202
199
|
|
|
203
|
-
const isTimeout = (error: unknown) =>
|
|
204
|
-
!!error &&
|
|
205
|
-
typeof error === "object" &&
|
|
206
|
-
(error as { killed?: boolean }).killed === true;
|
|
200
|
+
const isTimeout = (error: unknown) => isGitError(error) && error.timedOut;
|
|
207
201
|
|
|
208
202
|
function describeAddError(error: unknown, timeout: number): Error {
|
|
209
203
|
if (isTimeout(error))
|
|
@@ -406,40 +400,8 @@ export async function listWorktrees(projectPath: string): Promise<
|
|
|
406
400
|
head: string;
|
|
407
401
|
}>
|
|
408
402
|
> {
|
|
409
|
-
const resolvedProjectPath = resolvePath(projectPath);
|
|
410
|
-
|
|
411
403
|
try {
|
|
412
|
-
|
|
413
|
-
resolvedProjectPath,
|
|
414
|
-
["worktree", "list", "--porcelain"],
|
|
415
|
-
10000
|
|
416
|
-
);
|
|
417
|
-
|
|
418
|
-
const worktrees: Array<{ path: string; branch: string; head: string }> = [];
|
|
419
|
-
const entries = stdout.split("\n\n").filter(Boolean);
|
|
420
|
-
|
|
421
|
-
for (const entry of entries) {
|
|
422
|
-
const lines = entry.split("\n");
|
|
423
|
-
let worktreePath = "";
|
|
424
|
-
let branch = "";
|
|
425
|
-
let head = "";
|
|
426
|
-
|
|
427
|
-
for (const line of lines) {
|
|
428
|
-
if (line.startsWith("worktree ")) {
|
|
429
|
-
worktreePath = line.slice(9);
|
|
430
|
-
} else if (line.startsWith("branch ")) {
|
|
431
|
-
branch = line.slice(7).replace("refs/heads/", "");
|
|
432
|
-
} else if (line.startsWith("HEAD ")) {
|
|
433
|
-
head = line.slice(5);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if (worktreePath) {
|
|
438
|
-
worktrees.push({ path: worktreePath, branch, head });
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
return worktrees;
|
|
404
|
+
return await worktreeList(resolvePath(projectPath), { timeout: 10000 });
|
|
443
405
|
} catch {
|
|
444
406
|
return [];
|
|
445
407
|
}
|