@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
|
-
|
|
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:
|
|
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 = {
|
|
41
|
-
|
|
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
|
|
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(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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.
|
|
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/
|
|
30
|
-
"@nurix/
|
|
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.
|
|
38
|
-
"@nurix/nustack.
|
|
37
|
+
"@nurix/nustack.studio": "0.0.0",
|
|
38
|
+
"@nurix/nustack.ui": "0.0.0"
|
|
39
39
|
},
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public"
|