@sagentlab/navarch-runtime 0.1.111 → 0.1.112
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.
|
@@ -209,9 +209,9 @@ class ConversationLoop {
|
|
|
209
209
|
...(shutdownSignal ? { signal: shutdownSignal } : {}),
|
|
210
210
|
...this.options.turnOverrides,
|
|
211
211
|
log: this.log,
|
|
212
|
-
prepareWorkspace: async ({ conversationId, repositoryUrl, mode, signal }) => {
|
|
212
|
+
prepareWorkspace: async ({ conversationId, repositoryUrl, workingBranch, mode, signal }) => {
|
|
213
213
|
const workspace = await (0, workspace_cjs_1.prepareConversationWorkspace)({
|
|
214
|
-
workspaceRoot: config.workspaceRoot, conversationId, repositoryUrl, env, signal, mode,
|
|
214
|
+
workspaceRoot: config.workspaceRoot, conversationId, repositoryUrl, workingBranch, env, signal, mode,
|
|
215
215
|
...(this.options.gitRunner ? { runner: this.options.gitRunner } : {}),
|
|
216
216
|
});
|
|
217
217
|
const guard = await (0, worktree_guard_cjs_1.prepareWorktreeGuard)({
|
|
@@ -298,7 +298,9 @@ async function runConversationTurn(deps, conversationId, turnId) {
|
|
|
298
298
|
if (snapshot.mode === "workspace" && !deps.workspaceEnabled)
|
|
299
299
|
throw new Error("Workspace chat is disabled on this machine");
|
|
300
300
|
prompt = (0, prompt_cjs_1.buildConversationPrompt)(snapshot, journal.turnId);
|
|
301
|
-
|
|
301
|
+
// Older servers omit the working branch: the checkout stays on the default branch.
|
|
302
|
+
const workingBranch = (0, workspace_cjs_1.validWorkingBranch)(claim.workingBranch);
|
|
303
|
+
prepared = await deps.prepareWorkspace({ conversationId, repositoryUrl, workingBranch, mode: snapshot.mode ?? "read_only", signal: controller.signal });
|
|
302
304
|
}
|
|
303
305
|
catch (error) {
|
|
304
306
|
if (!run.lost)
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.conversationEnv = conversationEnv;
|
|
7
7
|
exports.validRepositoryUrl = validRepositoryUrl;
|
|
8
|
+
exports.validWorkingBranch = validWorkingBranch;
|
|
8
9
|
exports.conversationRoot = conversationRoot;
|
|
9
10
|
exports.prepareConversationWorkspace = prepareConversationWorkspace;
|
|
10
11
|
exports.removeConversationWorkspace = removeConversationWorkspace;
|
|
@@ -50,6 +51,22 @@ function validRepositoryUrl(value) {
|
|
|
50
51
|
return null;
|
|
51
52
|
return `https://github.com/${fullName}.git`;
|
|
52
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The project's working branch from the claim, checked as strictly as the
|
|
56
|
+
* hosted path's `buildWorkingBranchCheckoutCommand`
|
|
57
|
+
* (worker/hosted-agent/delivery.ts): a plain ref name that cannot be read as a
|
|
58
|
+
* git option. Absent (an older server, or no working branch) is null; anything
|
|
59
|
+
* else that is not a plain name throws, so the turn never runs on a branch it
|
|
60
|
+
* was not given.
|
|
61
|
+
*/
|
|
62
|
+
function validWorkingBranch(value) {
|
|
63
|
+
if (value === undefined || value === null)
|
|
64
|
+
return null;
|
|
65
|
+
if (typeof value !== "string" || value.length > 255 || !/^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(value) || value.includes("..")) {
|
|
66
|
+
throw new Error("working branch is not a valid branch name");
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
53
70
|
function conversationRoot(workspaceRoot, conversationId) {
|
|
54
71
|
return node_path_1.default.join(workspaceRoot, "conversations", (0, journal_cjs_1.assertUuid)(conversationId, "conversation id"));
|
|
55
72
|
}
|
|
@@ -67,6 +84,11 @@ async function prepareConversationWorkspace(options) {
|
|
|
67
84
|
const url = validRepositoryUrl(options.repositoryUrl);
|
|
68
85
|
if (!url)
|
|
69
86
|
throw new Error("repository URL is not a plain https URL");
|
|
87
|
+
const branch = validWorkingBranch(options.workingBranch);
|
|
88
|
+
// The marker records what the checkout was cloned from, so a changed
|
|
89
|
+
// repository or working branch is noticed. Default-branch checkouts keep the
|
|
90
|
+
// bare URL that earlier runtimes wrote.
|
|
91
|
+
const identity = branch ? `${url}\nbranch=${branch}` : url;
|
|
70
92
|
const runner = options.runner ?? sandbox_cjs_1.nodeCommandRunner;
|
|
71
93
|
const root = options.mode === "workspace"
|
|
72
94
|
? node_path_1.default.join(options.workspaceRoot, "chat-workspaces", (0, journal_cjs_1.assertUuid)(options.conversationId, "conversation id"))
|
|
@@ -83,7 +105,7 @@ async function prepareConversationWorkspace(options) {
|
|
|
83
105
|
if (options.mode === "workspace") {
|
|
84
106
|
await node_fs_1.promises.writeFile(node_path_1.default.join(workspace.metaDir, "preserve-workspace"), "Workspace chat: retain until the operator removes it.\n", { mode: 0o600 });
|
|
85
107
|
}
|
|
86
|
-
if (hasCheckout && recorded ===
|
|
108
|
+
if (hasCheckout && recorded === identity)
|
|
87
109
|
return workspace;
|
|
88
110
|
if (options.mode === "workspace") {
|
|
89
111
|
const exists = await node_fs_1.promises.lstat(workspace.checkout).then(() => true, (error) => {
|
|
@@ -94,7 +116,7 @@ async function prepareConversationWorkspace(options) {
|
|
|
94
116
|
if (exists)
|
|
95
117
|
throw new Error("Checkout identity changed; preserving the existing workspace. Start a new conversation.");
|
|
96
118
|
}
|
|
97
|
-
// New conversation, a changed repository URL, or an interrupted clone.
|
|
119
|
+
// New conversation, a changed repository URL or working branch, or an interrupted clone.
|
|
98
120
|
await node_fs_1.promises.rm(workspace.checkout, { recursive: true, force: true });
|
|
99
121
|
const env = {
|
|
100
122
|
...options.env,
|
|
@@ -104,12 +126,12 @@ async function prepareConversationWorkspace(options) {
|
|
|
104
126
|
GIT_CONFIG_KEY_0: "core.hooksPath",
|
|
105
127
|
GIT_CONFIG_VALUE_0: process.platform === "win32" ? "NUL" : "/dev/null",
|
|
106
128
|
};
|
|
107
|
-
const result = await runner.run("git", ["clone", "--quiet", "--filter=blob:none", "--no-tags", "--", url, workspace.checkout], { cwd: root, env, timeoutMs: CLONE_TIMEOUT_MS, signal: options.signal });
|
|
129
|
+
const result = await runner.run("git", ["clone", "--quiet", "--filter=blob:none", "--no-tags", ...(branch ? ["--branch", branch] : []), "--", url, workspace.checkout], { cwd: root, env, timeoutMs: CLONE_TIMEOUT_MS, signal: options.signal });
|
|
108
130
|
if (result.code !== 0) {
|
|
109
131
|
await node_fs_1.promises.rm(workspace.checkout, { recursive: true, force: true }).catch(() => undefined);
|
|
110
132
|
throw new Error(`git clone failed with exit ${result.code}: ${result.stderr.slice(-500)}`);
|
|
111
133
|
}
|
|
112
|
-
await node_fs_1.promises.writeFile(marker,
|
|
134
|
+
await node_fs_1.promises.writeFile(marker, identity, { encoding: "utf8", mode: 0o600 });
|
|
113
135
|
return workspace;
|
|
114
136
|
}
|
|
115
137
|
async function removeConversationWorkspace(workspaceRoot, conversationId) {
|
package/package.json
CHANGED