@diologue/local-agent 0.7.0 → 0.8.0
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/dist/cli.mjs +201 -72
- package/dist/cli.mjs.map +4 -4
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -83,7 +83,7 @@ var init_engine_release = __esm({
|
|
|
83
83
|
import { access as access2, constants } from "node:fs/promises";
|
|
84
84
|
import { readFileSync } from "node:fs";
|
|
85
85
|
import { createRequire } from "node:module";
|
|
86
|
-
import
|
|
86
|
+
import path4 from "node:path";
|
|
87
87
|
import { fileURLToPath } from "node:url";
|
|
88
88
|
var engineRelease, __filename, __dirname, LOCAL_AGENT_ROOT, REPO_ROOT, BUNDLE_DIR_INSTALLED, BUNDLE_DIR_LOCAL_BUILD, ENGINE_BUNDLE_ENV, exists2, resolveOpencodeAiBinary, bundleFilename, findEngineBundle;
|
|
89
89
|
var init_engine_bundle = __esm({
|
|
@@ -92,15 +92,15 @@ var init_engine_bundle = __esm({
|
|
|
92
92
|
init_engine_release();
|
|
93
93
|
engineRelease = engine_release_default;
|
|
94
94
|
__filename = fileURLToPath(import.meta.url);
|
|
95
|
-
__dirname =
|
|
96
|
-
LOCAL_AGENT_ROOT =
|
|
97
|
-
REPO_ROOT =
|
|
98
|
-
BUNDLE_DIR_INSTALLED =
|
|
95
|
+
__dirname = path4.dirname(__filename);
|
|
96
|
+
LOCAL_AGENT_ROOT = path4.resolve(__dirname, "../..");
|
|
97
|
+
REPO_ROOT = path4.resolve(LOCAL_AGENT_ROOT, "..");
|
|
98
|
+
BUNDLE_DIR_INSTALLED = path4.join(
|
|
99
99
|
LOCAL_AGENT_ROOT,
|
|
100
100
|
"dist/engine",
|
|
101
101
|
engineRelease.releaseTag
|
|
102
102
|
);
|
|
103
|
-
BUNDLE_DIR_LOCAL_BUILD =
|
|
103
|
+
BUNDLE_DIR_LOCAL_BUILD = path4.join(
|
|
104
104
|
REPO_ROOT,
|
|
105
105
|
"build/diologue-engine-bundles"
|
|
106
106
|
);
|
|
@@ -117,11 +117,11 @@ var init_engine_bundle = __esm({
|
|
|
117
117
|
try {
|
|
118
118
|
const require2 = createRequire(import.meta.url);
|
|
119
119
|
const pkgJsonPath = require2.resolve("opencode-ai/package.json");
|
|
120
|
-
const pkgDir =
|
|
120
|
+
const pkgDir = path4.dirname(pkgJsonPath);
|
|
121
121
|
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
122
122
|
const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.opencode;
|
|
123
123
|
if (!binRel) return null;
|
|
124
|
-
return
|
|
124
|
+
return path4.join(pkgDir, binRel);
|
|
125
125
|
} catch {
|
|
126
126
|
return null;
|
|
127
127
|
}
|
|
@@ -145,11 +145,11 @@ var init_engine_bundle = __esm({
|
|
|
145
145
|
return { path: opencodeAi, source: "opencode-ai" };
|
|
146
146
|
}
|
|
147
147
|
const filename = bundleFilename();
|
|
148
|
-
const installed =
|
|
148
|
+
const installed = path4.join(BUNDLE_DIR_INSTALLED, filename);
|
|
149
149
|
if (await exists2(installed)) {
|
|
150
150
|
return { path: installed, source: "installed" };
|
|
151
151
|
}
|
|
152
|
-
const localBuild =
|
|
152
|
+
const localBuild = path4.join(BUNDLE_DIR_LOCAL_BUILD, filename);
|
|
153
153
|
if (await exists2(localBuild)) {
|
|
154
154
|
return { path: localBuild, source: "local-build" };
|
|
155
155
|
}
|
|
@@ -165,7 +165,7 @@ __export(engine_locator_local_exports, {
|
|
|
165
165
|
});
|
|
166
166
|
import { access as access3, constants as constants2 } from "node:fs/promises";
|
|
167
167
|
import { spawn as spawn2 } from "node:child_process";
|
|
168
|
-
import
|
|
168
|
+
import path5 from "node:path";
|
|
169
169
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
170
170
|
var __filename2, __dirname2, REPO_ROOT2, DEFAULT_ENGINE_DIR, ENTRY_REL, DEFAULT_STARTUP_TIMEOUT_MS, LISTENING_REGEX, defaultRuntimeCheck, exists3, isRecord, parseExistingInlineConfig, mergeInlineConfig, buildEngineEnv, defaultClientFactory, LocalEngineLocator, truncate;
|
|
171
171
|
var init_engine_locator_local = __esm({
|
|
@@ -173,9 +173,9 @@ var init_engine_locator_local = __esm({
|
|
|
173
173
|
"use strict";
|
|
174
174
|
init_engine_bundle();
|
|
175
175
|
__filename2 = fileURLToPath2(import.meta.url);
|
|
176
|
-
__dirname2 =
|
|
177
|
-
REPO_ROOT2 =
|
|
178
|
-
DEFAULT_ENGINE_DIR =
|
|
176
|
+
__dirname2 = path5.dirname(__filename2);
|
|
177
|
+
REPO_ROOT2 = path5.resolve(__dirname2, "../../..");
|
|
178
|
+
DEFAULT_ENGINE_DIR = path5.join(REPO_ROOT2, "build/diologue-engine");
|
|
179
179
|
ENTRY_REL = "packages/opencode/src/index.ts";
|
|
180
180
|
DEFAULT_STARTUP_TIMEOUT_MS = 3e4;
|
|
181
181
|
LISTENING_REGEX = /listening on (https?:\/\/[^\s]+)/i;
|
|
@@ -260,7 +260,7 @@ var init_engine_locator_local = __esm({
|
|
|
260
260
|
- Reinstall the package so postinstall fetches a bundle, or
|
|
261
261
|
- Run \`npm run rebrand-engine\` to populate the source tree.`;
|
|
262
262
|
}
|
|
263
|
-
const entry =
|
|
263
|
+
const entry = path5.join(this.enginePath, ENTRY_REL);
|
|
264
264
|
if (!await exists3(entry)) {
|
|
265
265
|
return `[engine-locator/local] Engine entry not found at ${entry}. The rebrand may have produced a partial tree \u2014 re-run \`npm run rebrand-engine\`.`;
|
|
266
266
|
}
|
|
@@ -293,7 +293,7 @@ var init_engine_locator_local = __esm({
|
|
|
293
293
|
command: this.runtime,
|
|
294
294
|
args: [
|
|
295
295
|
"run",
|
|
296
|
-
|
|
296
|
+
path5.join(this.enginePath, ENTRY_REL),
|
|
297
297
|
"serve",
|
|
298
298
|
"--port",
|
|
299
299
|
"0",
|
|
@@ -356,7 +356,7 @@ var init_engine_locator_local = __esm({
|
|
|
356
356
|
return new Promise((resolve, reject) => {
|
|
357
357
|
let settled = false;
|
|
358
358
|
let buffered = "";
|
|
359
|
-
const tag = `${this.runtime}/${
|
|
359
|
+
const tag = `${this.runtime}/${path5.basename(this.enginePath)}`;
|
|
360
360
|
const settle = (fn) => {
|
|
361
361
|
if (settled) return;
|
|
362
362
|
settled = true;
|
|
@@ -560,24 +560,11 @@ var createState = () => {
|
|
|
560
560
|
};
|
|
561
561
|
};
|
|
562
562
|
|
|
563
|
-
// src/
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
helperVersion: config.helperVersion,
|
|
569
|
-
boundHost: config.host,
|
|
570
|
-
port: config.port,
|
|
571
|
-
startedAt: config.startedAt
|
|
572
|
-
};
|
|
573
|
-
res.json(body);
|
|
574
|
-
};
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
// src/routes/repo.ts
|
|
578
|
-
import { Router as createRouter } from "express";
|
|
579
|
-
import path2 from "node:path";
|
|
580
|
-
import { z } from "zod";
|
|
563
|
+
// src/worktree.ts
|
|
564
|
+
import { existsSync } from "node:fs";
|
|
565
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
566
|
+
import { homedir } from "node:os";
|
|
567
|
+
import path from "node:path";
|
|
581
568
|
|
|
582
569
|
// src/lib/git.ts
|
|
583
570
|
import { execFile } from "node:child_process";
|
|
@@ -821,10 +808,105 @@ var branchNameForTitle = (title) => {
|
|
|
821
808
|
const rand = Math.random().toString(36).slice(2, 7);
|
|
822
809
|
return `diologue/${slug || "change"}-${rand}`;
|
|
823
810
|
};
|
|
811
|
+
var branchExists = async (repoPath, branch) => {
|
|
812
|
+
try {
|
|
813
|
+
await runGit(repoPath, [
|
|
814
|
+
"rev-parse",
|
|
815
|
+
"--verify",
|
|
816
|
+
"--quiet",
|
|
817
|
+
`refs/heads/${branch}`
|
|
818
|
+
]);
|
|
819
|
+
return true;
|
|
820
|
+
} catch (err) {
|
|
821
|
+
if (err instanceof GitCommandError) return false;
|
|
822
|
+
throw err;
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
var addWorktree = async (repoPath, worktreePath, branch) => {
|
|
826
|
+
await runGit(repoPath, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
|
|
827
|
+
};
|
|
828
|
+
var addWorktreeForBranch = async (repoPath, worktreePath, branch) => {
|
|
829
|
+
await runGit(repoPath, ["worktree", "add", worktreePath, branch]);
|
|
830
|
+
};
|
|
831
|
+
var removeWorktree = async (repoPath, worktreePath) => {
|
|
832
|
+
await runGit(repoPath, ["worktree", "remove", "--force", worktreePath]);
|
|
833
|
+
};
|
|
834
|
+
var pruneWorktrees = async (repoPath) => {
|
|
835
|
+
await runGit(repoPath, ["worktree", "prune"]);
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
// src/worktree.ts
|
|
839
|
+
var sanitize = (s) => s.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^-+|-+$/g, "").slice(0, 50) || "session";
|
|
840
|
+
var createWorktreeManager = (options = {}) => {
|
|
841
|
+
const baseDir = options.baseDir ?? path.join(homedir(), ".diologue", "worktrees");
|
|
842
|
+
const map = /* @__PURE__ */ new Map();
|
|
843
|
+
const ensure = async (sessionId, repoPath, baseBranch) => {
|
|
844
|
+
const existing = map.get(sessionId);
|
|
845
|
+
if (existing && existsSync(existing.path)) return existing;
|
|
846
|
+
const safe = sanitize(sessionId);
|
|
847
|
+
const branch = `diologue/${safe}`;
|
|
848
|
+
const worktreePath = path.join(baseDir, safe);
|
|
849
|
+
await mkdir(baseDir, { recursive: true });
|
|
850
|
+
await pruneWorktrees(repoPath).catch(() => void 0);
|
|
851
|
+
if (existsSync(worktreePath)) {
|
|
852
|
+
await removeWorktree(repoPath, worktreePath).catch(() => void 0);
|
|
853
|
+
await rm(worktreePath, { recursive: true, force: true }).catch(
|
|
854
|
+
() => void 0
|
|
855
|
+
);
|
|
856
|
+
await pruneWorktrees(repoPath).catch(() => void 0);
|
|
857
|
+
}
|
|
858
|
+
if (await branchExists(repoPath, branch)) {
|
|
859
|
+
await addWorktreeForBranch(repoPath, worktreePath, branch);
|
|
860
|
+
} else {
|
|
861
|
+
await addWorktree(repoPath, worktreePath, branch);
|
|
862
|
+
}
|
|
863
|
+
const info = {
|
|
864
|
+
path: worktreePath,
|
|
865
|
+
branch,
|
|
866
|
+
base: baseBranch,
|
|
867
|
+
repoPath
|
|
868
|
+
};
|
|
869
|
+
map.set(sessionId, info);
|
|
870
|
+
return info;
|
|
871
|
+
};
|
|
872
|
+
const get = (sessionId) => map.get(sessionId) ?? null;
|
|
873
|
+
const remove = async (sessionId) => {
|
|
874
|
+
const info = map.get(sessionId);
|
|
875
|
+
if (!info) return;
|
|
876
|
+
map.delete(sessionId);
|
|
877
|
+
await removeWorktree(info.repoPath, info.path).catch(() => void 0);
|
|
878
|
+
await rm(info.path, { recursive: true, force: true }).catch(
|
|
879
|
+
() => void 0
|
|
880
|
+
);
|
|
881
|
+
};
|
|
882
|
+
const cleanupAll = async () => {
|
|
883
|
+
for (const id of [...map.keys()]) await remove(id);
|
|
884
|
+
};
|
|
885
|
+
return { ensure, get, remove, cleanupAll };
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
// src/routes/health.ts
|
|
889
|
+
var createHealthHandler = (config) => {
|
|
890
|
+
return (_req, res) => {
|
|
891
|
+
const body = {
|
|
892
|
+
ok: true,
|
|
893
|
+
helperVersion: config.helperVersion,
|
|
894
|
+
boundHost: config.host,
|
|
895
|
+
port: config.port,
|
|
896
|
+
startedAt: config.startedAt
|
|
897
|
+
};
|
|
898
|
+
res.json(body);
|
|
899
|
+
};
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
// src/routes/repo.ts
|
|
903
|
+
import { Router as createRouter } from "express";
|
|
904
|
+
import path3 from "node:path";
|
|
905
|
+
import { z } from "zod";
|
|
824
906
|
|
|
825
907
|
// src/lib/paths.ts
|
|
826
908
|
import { access, lstat, realpath, stat } from "node:fs/promises";
|
|
827
|
-
import
|
|
909
|
+
import path2 from "node:path";
|
|
828
910
|
var InvalidRepoPathError = class extends Error {
|
|
829
911
|
code;
|
|
830
912
|
constructor(code, message) {
|
|
@@ -849,7 +931,7 @@ var validateRepoPath = async (raw) => {
|
|
|
849
931
|
if (trimmed.length === 0) {
|
|
850
932
|
throw new InvalidRepoPathError("empty", "path must not be empty");
|
|
851
933
|
}
|
|
852
|
-
if (!
|
|
934
|
+
if (!path2.isAbsolute(trimmed)) {
|
|
853
935
|
throw new InvalidRepoPathError(
|
|
854
936
|
"not_absolute",
|
|
855
937
|
`path must be absolute (got: ${trimmed})`
|
|
@@ -871,7 +953,7 @@ var validateRepoPath = async (raw) => {
|
|
|
871
953
|
`path is not a directory: ${resolved}`
|
|
872
954
|
);
|
|
873
955
|
}
|
|
874
|
-
const gitMarker =
|
|
956
|
+
const gitMarker = path2.join(resolved, ".git");
|
|
875
957
|
if (!await exists(gitMarker)) {
|
|
876
958
|
throw new InvalidRepoPathError(
|
|
877
959
|
"not_a_git_repo",
|
|
@@ -884,7 +966,7 @@ var validateRepoPath = async (raw) => {
|
|
|
884
966
|
|
|
885
967
|
// src/lib/pick-directory.ts
|
|
886
968
|
import { execFile as execFile2 } from "node:child_process";
|
|
887
|
-
import { homedir } from "node:os";
|
|
969
|
+
import { homedir as homedir2 } from "node:os";
|
|
888
970
|
var PROMPT = "Select a git repository";
|
|
889
971
|
var dialogSpec = (platform) => {
|
|
890
972
|
switch (platform) {
|
|
@@ -944,7 +1026,7 @@ var pickDirectory = async () => {
|
|
|
944
1026
|
};
|
|
945
1027
|
const result = await attempt(spec.cmd, spec.args);
|
|
946
1028
|
if (!result.ok && result.reason === "no_gui" && process.platform === "linux") {
|
|
947
|
-
return attempt("kdialog", ["--getexistingdirectory",
|
|
1029
|
+
return attempt("kdialog", ["--getexistingdirectory", homedir2()]);
|
|
948
1030
|
}
|
|
949
1031
|
return result;
|
|
950
1032
|
};
|
|
@@ -1020,14 +1102,18 @@ var selectRepoSchema = z.object({
|
|
|
1020
1102
|
});
|
|
1021
1103
|
var applyPatchSchema = z.object({
|
|
1022
1104
|
unified: z.string().min(1).max(8 * 1024 * 1024),
|
|
1023
|
-
baselineHash: z.string().optional()
|
|
1105
|
+
baselineHash: z.string().optional(),
|
|
1106
|
+
sessionId: z.string().min(1).optional()
|
|
1024
1107
|
});
|
|
1025
1108
|
var revertPatchSchema = z.object({
|
|
1026
|
-
unified: z.string().min(1).max(8 * 1024 * 1024)
|
|
1109
|
+
unified: z.string().min(1).max(8 * 1024 * 1024),
|
|
1110
|
+
sessionId: z.string().min(1).optional()
|
|
1027
1111
|
});
|
|
1028
1112
|
var createPrSchema = z.object({
|
|
1029
1113
|
title: z.string().min(1).max(200),
|
|
1030
|
-
body: z.string().max(2e4).optional()
|
|
1114
|
+
body: z.string().max(2e4).optional(),
|
|
1115
|
+
/** When set, the PR is created from this session's worktree branch. */
|
|
1116
|
+
sessionId: z.string().min(1).optional()
|
|
1031
1117
|
});
|
|
1032
1118
|
var buildRepoStatus = async (resolvedPath) => {
|
|
1033
1119
|
const [branch, head, dirty] = await Promise.all([
|
|
@@ -1037,7 +1123,7 @@ var buildRepoStatus = async (resolvedPath) => {
|
|
|
1037
1123
|
]);
|
|
1038
1124
|
return {
|
|
1039
1125
|
path: resolvedPath,
|
|
1040
|
-
name:
|
|
1126
|
+
name: path3.basename(resolvedPath),
|
|
1041
1127
|
branch,
|
|
1042
1128
|
head,
|
|
1043
1129
|
isDirty: dirty
|
|
@@ -1061,7 +1147,8 @@ var requireSelectedRepo = (state, res) => {
|
|
|
1061
1147
|
}
|
|
1062
1148
|
return repo;
|
|
1063
1149
|
};
|
|
1064
|
-
var
|
|
1150
|
+
var readSessionId = (value) => typeof value === "string" && value ? value : void 0;
|
|
1151
|
+
var createRepoRouter = (state, worktrees) => {
|
|
1065
1152
|
const router = createRouter();
|
|
1066
1153
|
router.post("/select", async (req, res) => {
|
|
1067
1154
|
const parsed = selectRepoSchema.safeParse(req.body);
|
|
@@ -1114,7 +1201,8 @@ var createRepoRouter = (state) => {
|
|
|
1114
1201
|
}
|
|
1115
1202
|
const { title } = parsed.data;
|
|
1116
1203
|
const body = parsed.data.body ?? "Opened from the Diologue coding agent.";
|
|
1117
|
-
const
|
|
1204
|
+
const worktree = parsed.data.sessionId ? worktrees.get(parsed.data.sessionId) : null;
|
|
1205
|
+
const cwd = worktree ? worktree.path : repo.path;
|
|
1118
1206
|
try {
|
|
1119
1207
|
if (!await ghReady()) {
|
|
1120
1208
|
res.status(501).json({
|
|
@@ -1137,12 +1225,17 @@ var createRepoRouter = (state) => {
|
|
|
1137
1225
|
});
|
|
1138
1226
|
return;
|
|
1139
1227
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1228
|
+
let branch;
|
|
1229
|
+
let base;
|
|
1230
|
+
if (worktree) {
|
|
1231
|
+
branch = worktree.branch;
|
|
1232
|
+
base = worktree.base;
|
|
1233
|
+
} else {
|
|
1234
|
+
const current = await getBranch(cwd) ?? "";
|
|
1235
|
+
const onAgentBranch = current.startsWith("diologue/");
|
|
1236
|
+
base = onAgentBranch ? await getDefaultBranch(cwd) : current || "main";
|
|
1237
|
+
branch = onAgentBranch ? current : branchNameForTitle(title);
|
|
1238
|
+
if (!onAgentBranch) await createBranch(cwd, branch);
|
|
1146
1239
|
}
|
|
1147
1240
|
await commitAll(cwd, title);
|
|
1148
1241
|
await pushBranch(cwd, branch);
|
|
@@ -1181,13 +1274,23 @@ var createRepoRouter = (state) => {
|
|
|
1181
1274
|
throw err;
|
|
1182
1275
|
}
|
|
1183
1276
|
});
|
|
1184
|
-
|
|
1277
|
+
const resolveWorkdir = (repo, sessionId) => {
|
|
1278
|
+
if (!sessionId) return repo.path;
|
|
1279
|
+
const wt = worktrees.get(sessionId);
|
|
1280
|
+
return wt ? wt.path : null;
|
|
1281
|
+
};
|
|
1282
|
+
router.get("/diff", async (req, res) => {
|
|
1185
1283
|
const repo = requireSelectedRepo(state, res);
|
|
1186
1284
|
if (!repo) {
|
|
1187
1285
|
return;
|
|
1188
1286
|
}
|
|
1287
|
+
const workdir = resolveWorkdir(repo, readSessionId(req.query.sessionId));
|
|
1288
|
+
if (!workdir) {
|
|
1289
|
+
res.json({ unified: "", sizeBytes: 0 });
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1189
1292
|
try {
|
|
1190
|
-
const unified = await getDiff(
|
|
1293
|
+
const unified = await getDiff(workdir);
|
|
1191
1294
|
const body = {
|
|
1192
1295
|
unified,
|
|
1193
1296
|
sizeBytes: Buffer.byteLength(unified, "utf8")
|
|
@@ -1201,13 +1304,18 @@ var createRepoRouter = (state) => {
|
|
|
1201
1304
|
throw err;
|
|
1202
1305
|
}
|
|
1203
1306
|
});
|
|
1204
|
-
router.get("/changed-files", async (
|
|
1307
|
+
router.get("/changed-files", async (req, res) => {
|
|
1205
1308
|
const repo = requireSelectedRepo(state, res);
|
|
1206
1309
|
if (!repo) {
|
|
1207
1310
|
return;
|
|
1208
1311
|
}
|
|
1312
|
+
const workdir = resolveWorkdir(repo, readSessionId(req.query.sessionId));
|
|
1313
|
+
if (!workdir) {
|
|
1314
|
+
res.json({ files: [] });
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1209
1317
|
try {
|
|
1210
|
-
const files = await getStatusShort(
|
|
1318
|
+
const files = await getStatusShort(workdir);
|
|
1211
1319
|
const body = { files };
|
|
1212
1320
|
res.json(body);
|
|
1213
1321
|
} catch (err) {
|
|
@@ -1226,8 +1334,9 @@ var createRepoRouter = (state) => {
|
|
|
1226
1334
|
res.status(400).json({ error: "invalid_body", issues: parsed.error.issues });
|
|
1227
1335
|
return;
|
|
1228
1336
|
}
|
|
1337
|
+
const applyWorkdir = parsed.data.sessionId && worktrees.get(parsed.data.sessionId)?.path || repo.path;
|
|
1229
1338
|
try {
|
|
1230
|
-
const check = await canApplyDiff(
|
|
1339
|
+
const check = await canApplyDiff(applyWorkdir, parsed.data.unified);
|
|
1231
1340
|
if (!check.ok) {
|
|
1232
1341
|
res.status(409).json({
|
|
1233
1342
|
error: "diff_does_not_apply",
|
|
@@ -1236,7 +1345,7 @@ var createRepoRouter = (state) => {
|
|
|
1236
1345
|
});
|
|
1237
1346
|
return;
|
|
1238
1347
|
}
|
|
1239
|
-
await applyDiff(
|
|
1348
|
+
await applyDiff(applyWorkdir, parsed.data.unified);
|
|
1240
1349
|
const paths = parseDiffPaths(parsed.data.unified);
|
|
1241
1350
|
const body = {
|
|
1242
1351
|
ok: true,
|
|
@@ -1260,8 +1369,9 @@ var createRepoRouter = (state) => {
|
|
|
1260
1369
|
res.status(400).json({ error: "invalid_body", issues: parsed.error.issues });
|
|
1261
1370
|
return;
|
|
1262
1371
|
}
|
|
1372
|
+
const revertWorkdir = parsed.data.sessionId && worktrees.get(parsed.data.sessionId)?.path || repo.path;
|
|
1263
1373
|
try {
|
|
1264
|
-
const check = await canRevertDiff(
|
|
1374
|
+
const check = await canRevertDiff(revertWorkdir, parsed.data.unified);
|
|
1265
1375
|
if (!check.ok) {
|
|
1266
1376
|
res.status(409).json({
|
|
1267
1377
|
error: "diff_does_not_revert",
|
|
@@ -1270,7 +1380,7 @@ var createRepoRouter = (state) => {
|
|
|
1270
1380
|
});
|
|
1271
1381
|
return;
|
|
1272
1382
|
}
|
|
1273
|
-
await revertDiff(
|
|
1383
|
+
await revertDiff(revertWorkdir, parsed.data.unified);
|
|
1274
1384
|
const paths = parseDiffPaths(parsed.data.unified);
|
|
1275
1385
|
const body = {
|
|
1276
1386
|
ok: true,
|
|
@@ -1539,8 +1649,21 @@ var createAgentRouter = (deps) => {
|
|
|
1539
1649
|
res.status(409).json({ error: "no_repo_selected" });
|
|
1540
1650
|
return;
|
|
1541
1651
|
}
|
|
1652
|
+
let workdir = repo.path;
|
|
1653
|
+
try {
|
|
1654
|
+
const worktree = await deps.worktrees.ensure(
|
|
1655
|
+
parsed.data.sessionId,
|
|
1656
|
+
repo.path,
|
|
1657
|
+
repo.branch ?? "main"
|
|
1658
|
+
);
|
|
1659
|
+
workdir = worktree.path;
|
|
1660
|
+
} catch (err) {
|
|
1661
|
+
logAgentRoute(
|
|
1662
|
+
`worktree create failed (falling back to in-place) session=${parsed.data.sessionId.slice(0, 8)} error=${err instanceof Error ? err.message : String(err)}`
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1542
1665
|
logAgentRoute(
|
|
1543
|
-
`message start session=${parsed.data.sessionId.slice(0, 8)} repo=${repo.path} promptChars=${parsed.data.prompt.length} provider=${parsed.data.preferredProvider ?? "(default)"} model=${parsed.data.preferredModel ?? "(default)"}`
|
|
1666
|
+
`message start session=${parsed.data.sessionId.slice(0, 8)} repo=${repo.path} workdir=${workdir} promptChars=${parsed.data.prompt.length} provider=${parsed.data.preferredProvider ?? "(default)"} model=${parsed.data.preferredModel ?? "(default)"}`
|
|
1544
1667
|
);
|
|
1545
1668
|
res.setHeader("Content-Type", "text/event-stream");
|
|
1546
1669
|
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
@@ -1563,7 +1686,7 @@ var createAgentRouter = (deps) => {
|
|
|
1563
1686
|
const history = parsed.data.history ? parsed.data.history.map((m) => ({ role: m.role, content: m.content })) : void 0;
|
|
1564
1687
|
for await (const event of deps.adapter.streamMessage({
|
|
1565
1688
|
sessionId: parsed.data.sessionId,
|
|
1566
|
-
repoPath:
|
|
1689
|
+
repoPath: workdir,
|
|
1567
1690
|
prompt: parsed.data.prompt,
|
|
1568
1691
|
history,
|
|
1569
1692
|
signal: controller.signal,
|
|
@@ -2748,8 +2871,8 @@ var mapEvent = (item, state) => {
|
|
|
2748
2871
|
{
|
|
2749
2872
|
type: "diff_proposed",
|
|
2750
2873
|
unified: "",
|
|
2751
|
-
files: files.map((
|
|
2752
|
-
path:
|
|
2874
|
+
files: files.map((path7) => ({
|
|
2875
|
+
path: path7,
|
|
2753
2876
|
additions: 0,
|
|
2754
2877
|
deletions: 0,
|
|
2755
2878
|
status: "modified"
|
|
@@ -3251,13 +3374,17 @@ var createApp = (options) => {
|
|
|
3251
3374
|
const state = options.state ?? createState();
|
|
3252
3375
|
const adapter = options.adapter ?? buildDefaultAdapter();
|
|
3253
3376
|
const brokerRegistry = createBrokerRegistry();
|
|
3377
|
+
const worktrees = createWorktreeManager();
|
|
3254
3378
|
const app = express();
|
|
3255
3379
|
app.use(express.json({ limit: "1mb" }));
|
|
3256
3380
|
app.use(createCorsMiddleware({ allowedOrigin: options.config.allowedOrigin }));
|
|
3257
3381
|
app.use(createAuthMiddleware({ token: options.config.token }));
|
|
3258
3382
|
app.get("/health", createHealthHandler(options.config));
|
|
3259
|
-
app.use("/repo", createRepoRouter(state));
|
|
3260
|
-
app.use(
|
|
3383
|
+
app.use("/repo", createRepoRouter(state, worktrees));
|
|
3384
|
+
app.use(
|
|
3385
|
+
"/agent",
|
|
3386
|
+
createAgentRouter({ state, adapter, brokerRegistry, worktrees })
|
|
3387
|
+
);
|
|
3261
3388
|
app.use("/agent/llm-response", createLlmResponseRouter({ brokerRegistry }));
|
|
3262
3389
|
app.use("/agent/llm-chunk", createLlmChunkRouter({ brokerRegistry }));
|
|
3263
3390
|
app.use("/agent/permission", createPermissionRouter({ adapter }));
|
|
@@ -3265,7 +3392,7 @@ var createApp = (options) => {
|
|
|
3265
3392
|
app.use((req, res) => {
|
|
3266
3393
|
res.status(404).json({ error: "not_found", method: req.method, path: req.path });
|
|
3267
3394
|
});
|
|
3268
|
-
return { app, state, adapter, brokerRegistry };
|
|
3395
|
+
return { app, state, adapter, brokerRegistry, worktrees };
|
|
3269
3396
|
};
|
|
3270
3397
|
|
|
3271
3398
|
// src/modes/quickstart.ts
|
|
@@ -3360,7 +3487,7 @@ var runQuickstart = async (options) => {
|
|
|
3360
3487
|
|
|
3361
3488
|
// src/lib/keychain.ts
|
|
3362
3489
|
import { promises as fs } from "node:fs";
|
|
3363
|
-
import
|
|
3490
|
+
import path6 from "node:path";
|
|
3364
3491
|
import os from "node:os";
|
|
3365
3492
|
var KEYTAR_SERVICE = "diologue.local-agent";
|
|
3366
3493
|
var KEYTAR_ACCOUNT = "device-credential";
|
|
@@ -3383,7 +3510,7 @@ var loadKeytar = async () => {
|
|
|
3383
3510
|
};
|
|
3384
3511
|
var fileFallbackPath = () => {
|
|
3385
3512
|
const home = os.homedir();
|
|
3386
|
-
return
|
|
3513
|
+
return path6.join(home, ".diologue", "credentials");
|
|
3387
3514
|
};
|
|
3388
3515
|
var createCredentialStore = (options = {}) => {
|
|
3389
3516
|
const filePath = options.filePathOverride ?? fileFallbackPath();
|
|
@@ -3430,7 +3557,7 @@ var createCredentialStore = (options = {}) => {
|
|
|
3430
3557
|
return { backend: "keychain" };
|
|
3431
3558
|
}
|
|
3432
3559
|
}
|
|
3433
|
-
await fs.mkdir(
|
|
3560
|
+
await fs.mkdir(path6.dirname(filePath), { recursive: true });
|
|
3434
3561
|
await fs.writeFile(filePath, JSON.stringify(credential, null, 2), {
|
|
3435
3562
|
mode: 384
|
|
3436
3563
|
});
|
|
@@ -3479,7 +3606,7 @@ var runStart = async (options) => {
|
|
|
3479
3606
|
process.env.LOCAL_AGENT_ALLOWED_ORIGIN = options.allowedOrigin;
|
|
3480
3607
|
if (options.adapter) process.env.LOCAL_AGENT_ADAPTER = options.adapter;
|
|
3481
3608
|
const config = loadConfig();
|
|
3482
|
-
const { app, adapter } = createApp({ config });
|
|
3609
|
+
const { app, adapter, worktrees } = createApp({ config });
|
|
3483
3610
|
const server = app.listen(config.port, config.host, async () => {
|
|
3484
3611
|
const url = `http://${config.host}:${config.port}`;
|
|
3485
3612
|
console.log(`[local-agent] Listening on ${url}`);
|
|
@@ -3495,7 +3622,9 @@ var runStart = async (options) => {
|
|
|
3495
3622
|
const shutdown = (signal) => {
|
|
3496
3623
|
console.log(`
|
|
3497
3624
|
[local-agent] Received ${signal}, shutting down...`);
|
|
3498
|
-
|
|
3625
|
+
void worktrees.cleanupAll().finally(() => {
|
|
3626
|
+
server.close(() => process.exit(0));
|
|
3627
|
+
});
|
|
3499
3628
|
setTimeout(() => process.exit(0), 3e3).unref();
|
|
3500
3629
|
};
|
|
3501
3630
|
process.on("SIGINT", shutdown);
|