@saadnvd1/agent-os 0.3.465 → 0.3.466

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.
@@ -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 { run } from "./gh";
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 run("git", ["remote", "get-url", "origin"], dir).catch(
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 run("git", ["clone", "--", ref.remote, dir], os.homedir(), 600000);
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
- try {
152
- await execFileAsync("git", ["ls-remote", "--heads", "--", remote], {
153
- cwd: os.homedir(),
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
- return null;
162
- } catch {
163
- return "Can't clone it there";
164
- }
155
+ }
156
+ );
157
+ return reachable ? null : "Can't clone it there";
165
158
  }
@@ -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, run } from "./gh";
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
- const out = await run("git", ["branch", "--show-current"], dir, 5000);
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
- run("git", ["rev-parse", "--verify", "--quiet", ref], dir, 5000).then(
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
- run("git", ["merge-base", "--is-ancestor", sha, ref], dir, 5000).then(
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
  );
@@ -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 execFileAsync = promisify(execFile);
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
@@ -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
- async function gitText(cwd: string, args: string[]): Promise<string> {
31
- const { stdout } = await execFileAsync("git", ["-C", cwd, ...args], {
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
- execFileAsync("git", ["-C", cwd, ...args], { timeout });
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
- const { stdout } = await git(
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saadnvd1/agent-os",
3
- "version": "0.3.465",
3
+ "version": "0.3.466",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "agent-os": "scripts/agent-os"