@nurix/nustack 0.10.0-dev.20 → 0.10.0-dev.22

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.
@@ -43,7 +43,12 @@ export function parseInboundFrame(line) {
43
43
  const permissionMode = typeof frame.permissionMode === "string" ? frame.permissionMode : undefined;
44
44
  const isolated = frame.isolated === true ? true : undefined;
45
45
  const resumeSessionId = typeof frame.resumeSessionId === "string" && frame.resumeSessionId !== "" ? frame.resumeSessionId : undefined;
46
- return { ok: true, frame: { v: FRAME_VERSION, type, sessionId, projectPath, permissionMode, isolated, resumeSessionId } };
46
+ const rawCommit = typeof frame.commit === "string" ? frame.commit : null;
47
+ if (rawCommit !== null && !/^[0-9a-f]{7,64}$/.test(rawCommit)) {
48
+ return { ok: false, error: { code: "BAD_FRAME", name: type, message: "session.open commit must be a hex object name" } };
49
+ }
50
+ const commit = rawCommit ?? undefined;
51
+ return { ok: true, frame: { v: FRAME_VERSION, type, sessionId, projectPath, permissionMode, isolated, commit, resumeSessionId } };
47
52
  }
48
53
  case "session.turn": {
49
54
  const sessionId = requireString("sessionId");
@@ -91,7 +91,7 @@ export class SessionSupervisor {
91
91
  await this.shutdown();
92
92
  return;
93
93
  case "session.open":
94
- await this.openSession(frame.sessionId, frame.projectPath, frame.permissionMode, frame.isolated, frame.resumeSessionId);
94
+ await this.openSession(frame.sessionId, frame.projectPath, frame.permissionMode, frame.isolated, frame.resumeSessionId, frame.commit);
95
95
  return;
96
96
  case "session.turn": {
97
97
  const session = this.sessions.get(frame.sessionId);
@@ -131,11 +131,16 @@ export class SessionSupervisor {
131
131
  return;
132
132
  }
133
133
  }
134
- async openSession(sessionId, projectPath, permissionMode, isolated, resumeSessionId) {
134
+ async openSession(sessionId, projectPath, permissionMode, isolated, resumeSessionId, commit) {
135
135
  if (this.sessions.has(sessionId))
136
136
  return this.error(sessionId, "SESSION_ALREADY_OPEN", `session ${sessionId} is already open`);
137
137
  this.status(sessionId, "starting");
138
- const worktree = await createSessionWorktree(projectPath, sessionId, this.env, isolated === true);
138
+ const worktree = await createSessionWorktree(projectPath, sessionId, this.env, isolated === true, commit);
139
+ if (worktree.commitRefusal !== null) {
140
+ this.error(sessionId, worktree.commitRefusal.code, worktree.commitRefusal.detail);
141
+ this.status(sessionId, "error", `could not open ${String(commit)}`);
142
+ return;
143
+ }
139
144
  const record = {
140
145
  sessionId,
141
146
  projectPath,
@@ -229,6 +234,7 @@ export class SessionSupervisor {
229
234
  worktreePath: worktree.worktreePath,
230
235
  branch: worktree.branch,
231
236
  isolated: worktree.isolated,
237
+ commit: worktree.baseCommit,
232
238
  });
233
239
  this.status(sessionId, "ready");
234
240
  }
@@ -10,6 +10,7 @@ export function sessionsHome(env = process.env) {
10
10
  return path.join(os.homedir(), ".nustack", "sessions");
11
11
  }
12
12
  export const LEGACY_ENGINE_BRANCH_PREFIX = "nustack/lane-";
13
+ const FETCH_TIMEOUT_MS = 10 * 60_000;
13
14
  export function legacyEngineHome(env = process.env) {
14
15
  const override = env.NUSTACK_LEGACY_ENGINE_HOME;
15
16
  if (override && override !== "")
@@ -19,13 +20,13 @@ export function legacyEngineHome(env = process.env) {
19
20
  function git(args, cwd) {
20
21
  return gitOutcome(args, cwd).then((outcome) => (outcome.ok ? outcome.stdout : null));
21
22
  }
22
- export function gitOutcome(args, cwd) {
23
+ export function gitOutcome(args, cwd, timeoutMs = 15_000) {
23
24
  return new Promise((resolve) => {
24
25
  const failed = (error) => {
25
26
  resolve({ ok: false, code: null, stdout: "", stderr: error instanceof Error ? error.message : String(error) });
26
27
  };
27
28
  try {
28
- execFile("git", args, { cwd, timeout: 15_000 }, (error, stdout, stderr) => {
29
+ execFile("git", args, { cwd, timeout: timeoutMs }, (error, stdout, stderr) => {
29
30
  const raw = error?.code;
30
31
  const code = error ? (typeof raw === "number" ? raw : null) : 0;
31
32
  resolve({ ok: !error, code, stdout: stdout.trim(), stderr: stderr.trim() });
@@ -36,21 +37,53 @@ export function gitOutcome(args, cwd) {
36
37
  }
37
38
  });
38
39
  }
39
- export async function createSessionWorktree(projectPath, sessionId, env = process.env, isolate = false) {
40
- const inPlace = { worktreePath: projectPath, branch: null, isolated: false, baseBranch: null, baseCommit: null };
41
- if (!isolate)
40
+ export async function createSessionWorktree(projectPath, sessionId, env = process.env, isolate = false, commit) {
41
+ const inPlace = {
42
+ worktreePath: projectPath,
43
+ branch: null,
44
+ isolated: false,
45
+ baseBranch: null,
46
+ baseCommit: null,
47
+ commitRefusal: null,
48
+ };
49
+ if (!isolate && commit === undefined)
42
50
  return inPlace;
43
51
  const isRepo = await git(["rev-parse", "--is-inside-work-tree"], projectPath);
44
- if (isRepo !== "true")
45
- return inPlace;
52
+ if (isRepo !== "true") {
53
+ return commit === undefined
54
+ ? inPlace
55
+ : { ...inPlace, commitRefusal: { code: "COMMIT_UNREACHABLE", detail: `${projectPath} is not a git checkout, so ${commit} cannot be opened here.` } };
56
+ }
57
+ if (commit !== undefined) {
58
+ const refusal = await fetchCommit(projectPath, commit);
59
+ if (refusal !== null)
60
+ return { ...inPlace, commitRefusal: refusal };
61
+ }
46
62
  const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], projectPath);
47
63
  const baseBranch = head === null || head === "HEAD" ? null : head;
48
- const baseCommit = await git(["rev-parse", "HEAD"], projectPath);
49
64
  const branch = `${ENGINE_SESSION_BRANCH_PREFIX}${sessionId}`;
50
65
  const worktreePath = path.join(sessionsHome(env), "worktrees", sessionId);
51
66
  await mkdir(path.dirname(worktreePath), { recursive: true });
52
- const added = await git(["worktree", "add", worktreePath, "-b", branch], projectPath);
53
- if (added === null)
54
- return inPlace;
55
- return { worktreePath, branch, isolated: true, baseBranch, baseCommit };
67
+ const added = await git(commit === undefined
68
+ ? ["worktree", "add", worktreePath, "-b", branch]
69
+ : ["worktree", "add", worktreePath, "-b", branch, commit], projectPath);
70
+ if (added === null) {
71
+ return commit === undefined
72
+ ? inPlace
73
+ : { ...inPlace, commitRefusal: { code: "COMMIT_UNREACHABLE", detail: `git refused a worktree for ${commit} in ${projectPath}.` } };
74
+ }
75
+ const baseCommit = await git(["rev-parse", "HEAD"], worktreePath);
76
+ return { worktreePath, branch, isolated: true, baseBranch, baseCommit, commitRefusal: null };
77
+ }
78
+ async function fetchCommit(projectPath, commit) {
79
+ const has = () => gitOutcome(["cat-file", "-e", `${commit}^{commit}`], projectPath);
80
+ if ((await has()).ok)
81
+ return null;
82
+ let fetched = await gitOutcome(["fetch", "origin", commit], projectPath, FETCH_TIMEOUT_MS);
83
+ if (!fetched.ok)
84
+ fetched = await gitOutcome(["fetch", "origin"], projectPath, FETCH_TIMEOUT_MS);
85
+ if ((await has()).ok)
86
+ return null;
87
+ const said = `${fetched.stderr}\n${fetched.stdout}`.trim();
88
+ return { code: "COMMIT_UNREACHABLE", detail: said === "" ? `${commit} is not in this checkout and origin did not have it.` : said };
56
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/nustack",
3
- "version": "0.10.0-dev.20",
3
+ "version": "0.10.0-dev.22",
4
4
  "description": "The nustack CLI — bootstrap NuStack services into a repo via the NuStack discovery plane.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -26,16 +26,16 @@
26
26
  "commander": "^14.0.0"
27
27
  },
28
28
  "optionalDependencies": {
29
- "@nurix/codegraph": "0.2.1",
30
- "@nurix/logscope": "0.1.0"
29
+ "@nurix/logscope": "0.1.0",
30
+ "@nurix/codegraph": "0.2.1"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@ngneat/falso": "^8.0.2",
34
34
  "@types/node": "^22.19.20",
35
35
  "typescript": "^5.7.2",
36
36
  "vitest": "^4.1.11",
37
- "@nurix/nustack.ui": "0.0.0",
38
- "@nurix/nustack.studio": "0.0.0"
37
+ "@nurix/nustack.studio": "0.0.0",
38
+ "@nurix/nustack.ui": "0.0.0"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public"