@ganglion/xacpx 0.17.0-beta.6 → 0.17.0-beta.7
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/README.md +1 -1
- package/dist/cli.js +518 -65
- package/dist/config/agent-catalog.d.ts +24 -6
- package/dist/control/control-service.d.ts +21 -0
- package/dist/control/workspace-git.d.ts +93 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ If you need to code or work remotely on a temporary basis, `xacpx` gives you a f
|
|
|
29
29
|
|
|
30
30
|
### Prerequisites
|
|
31
31
|
|
|
32
|
-
- Node.js 22+ or Bun
|
|
32
|
+
- Node.js 22.12+ or Bun
|
|
33
33
|
- A working agent CLI you intend to use, such as Codex / Claude Code / Gemini / OpenCode
|
|
34
34
|
- A phone with WeChat, Feishu, or Yuanbao installed
|
|
35
35
|
|
package/dist/cli.js
CHANGED
|
@@ -5310,6 +5310,9 @@ var init_agent_templates = __esm(() => {
|
|
|
5310
5310
|
factorydroid: {
|
|
5311
5311
|
driver: "factorydroid"
|
|
5312
5312
|
},
|
|
5313
|
+
"grok-build": {
|
|
5314
|
+
driver: "grok-build"
|
|
5315
|
+
},
|
|
5313
5316
|
iflow: {
|
|
5314
5317
|
driver: "iflow"
|
|
5315
5318
|
},
|
|
@@ -5322,6 +5325,9 @@ var init_agent_templates = __esm(() => {
|
|
|
5322
5325
|
kiro: {
|
|
5323
5326
|
driver: "kiro"
|
|
5324
5327
|
},
|
|
5328
|
+
mux: {
|
|
5329
|
+
driver: "mux"
|
|
5330
|
+
},
|
|
5325
5331
|
opencode: {
|
|
5326
5332
|
driver: "opencode"
|
|
5327
5333
|
},
|
|
@@ -35123,11 +35129,385 @@ var init_workspace_fs = __esm(() => {
|
|
|
35123
35129
|
};
|
|
35124
35130
|
});
|
|
35125
35131
|
|
|
35132
|
+
// src/control/workspace-git.ts
|
|
35133
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
35134
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
35135
|
+
import { lstat, mkdir as mkdir10, realpath as realpath2 } from "node:fs/promises";
|
|
35136
|
+
import { homedir as homedir13 } from "node:os";
|
|
35137
|
+
import { basename as basename4, isAbsolute as isAbsolute4, join as join22, resolve as resolve4, sep as sep2 } from "node:path";
|
|
35138
|
+
import { promisify as promisify2 } from "node:util";
|
|
35139
|
+
function expandHome3(path14) {
|
|
35140
|
+
if (path14 === "~")
|
|
35141
|
+
return homedir13();
|
|
35142
|
+
if (path14.startsWith(`~${sep2}`) || path14.startsWith("~/"))
|
|
35143
|
+
return resolve4(homedir13(), path14.slice(2));
|
|
35144
|
+
return path14;
|
|
35145
|
+
}
|
|
35146
|
+
|
|
35147
|
+
class WorkspaceGit {
|
|
35148
|
+
listWorkspaces;
|
|
35149
|
+
managedWorktreesRoot;
|
|
35150
|
+
writeTails = new Map;
|
|
35151
|
+
constructor(listWorkspaces, options = {}) {
|
|
35152
|
+
this.listWorkspaces = listWorkspaces;
|
|
35153
|
+
this.managedWorktreesRoot = options.managedWorktreesRoot ?? join22(homedir13(), ".xacpx", "worktrees");
|
|
35154
|
+
}
|
|
35155
|
+
async rootFor(workspace3) {
|
|
35156
|
+
const ref = this.listWorkspaces().find((item) => item.name === workspace3);
|
|
35157
|
+
if (!ref)
|
|
35158
|
+
throw new Error("unknown-workspace");
|
|
35159
|
+
try {
|
|
35160
|
+
return await realpath2(expandHome3(ref.cwd));
|
|
35161
|
+
} catch {
|
|
35162
|
+
throw new Error("workspace-root-missing");
|
|
35163
|
+
}
|
|
35164
|
+
}
|
|
35165
|
+
async run(root, args) {
|
|
35166
|
+
return (await this.runRaw(root, args)).trim();
|
|
35167
|
+
}
|
|
35168
|
+
async runRaw(root, args) {
|
|
35169
|
+
const result = await execFileAsync2("git", ["-C", root, ...args], {
|
|
35170
|
+
timeout: GIT_TIMEOUT_MS2,
|
|
35171
|
+
killSignal: "SIGKILL",
|
|
35172
|
+
maxBuffer: GIT_MAX_BUFFER2
|
|
35173
|
+
});
|
|
35174
|
+
return result.stdout;
|
|
35175
|
+
}
|
|
35176
|
+
validatePaths(paths) {
|
|
35177
|
+
if (!paths.length)
|
|
35178
|
+
throw new Error("git-paths-required");
|
|
35179
|
+
for (const path14 of paths) {
|
|
35180
|
+
if (!path14 || isAbsolute4(path14) || path14.split(/[\\/]/).includes("..")) {
|
|
35181
|
+
throw new Error("invalid-git-path");
|
|
35182
|
+
}
|
|
35183
|
+
}
|
|
35184
|
+
return paths;
|
|
35185
|
+
}
|
|
35186
|
+
async validateBranch(root, raw) {
|
|
35187
|
+
const branch = raw.trim();
|
|
35188
|
+
if (!branch)
|
|
35189
|
+
throw new Error("branch-required");
|
|
35190
|
+
try {
|
|
35191
|
+
await this.run(root, ["check-ref-format", "--branch", branch]);
|
|
35192
|
+
} catch {
|
|
35193
|
+
throw new Error("invalid-branch-name");
|
|
35194
|
+
}
|
|
35195
|
+
return branch;
|
|
35196
|
+
}
|
|
35197
|
+
async validateStartPoint(root, raw) {
|
|
35198
|
+
const startPoint = raw?.trim();
|
|
35199
|
+
if (!startPoint)
|
|
35200
|
+
return;
|
|
35201
|
+
try {
|
|
35202
|
+
await this.run(root, ["rev-parse", "--verify", "--end-of-options", `${startPoint}^{commit}`]);
|
|
35203
|
+
} catch {
|
|
35204
|
+
throw new Error("invalid-start-point");
|
|
35205
|
+
}
|
|
35206
|
+
return startPoint;
|
|
35207
|
+
}
|
|
35208
|
+
async includeRenameSources(root, paths) {
|
|
35209
|
+
const selected = new Set(paths);
|
|
35210
|
+
const expanded = new Set(paths);
|
|
35211
|
+
const fields = (await this.runRaw(root, ["-c", "core.quotePath=false", "status", "--porcelain", "-z"])).split("\x00");
|
|
35212
|
+
for (let index = 0;index < fields.length; index += 1) {
|
|
35213
|
+
const field = fields[index];
|
|
35214
|
+
if (!field)
|
|
35215
|
+
continue;
|
|
35216
|
+
const status = field.slice(0, 2);
|
|
35217
|
+
const path14 = field.slice(3);
|
|
35218
|
+
if (status[0] !== "R" && status[0] !== "C")
|
|
35219
|
+
continue;
|
|
35220
|
+
const originalPath = fields[++index];
|
|
35221
|
+
if (originalPath && selected.has(path14))
|
|
35222
|
+
expanded.add(originalPath);
|
|
35223
|
+
}
|
|
35224
|
+
return [...expanded];
|
|
35225
|
+
}
|
|
35226
|
+
async withWrite(workspace3, action) {
|
|
35227
|
+
const previous = this.writeTails.get(workspace3) ?? Promise.resolve();
|
|
35228
|
+
let release;
|
|
35229
|
+
const current = new Promise((resolve5) => {
|
|
35230
|
+
release = resolve5;
|
|
35231
|
+
});
|
|
35232
|
+
const tail2 = previous.catch(() => {}).then(() => current);
|
|
35233
|
+
this.writeTails.set(workspace3, tail2);
|
|
35234
|
+
await previous.catch(() => {});
|
|
35235
|
+
try {
|
|
35236
|
+
return await action();
|
|
35237
|
+
} finally {
|
|
35238
|
+
release();
|
|
35239
|
+
if (this.writeTails.get(workspace3) === tail2)
|
|
35240
|
+
this.writeTails.delete(workspace3);
|
|
35241
|
+
}
|
|
35242
|
+
}
|
|
35243
|
+
async stage(workspace3, paths) {
|
|
35244
|
+
await this.withWrite(workspace3, async () => {
|
|
35245
|
+
const root = await this.rootFor(workspace3);
|
|
35246
|
+
await this.run(root, ["--literal-pathspecs", "add", "--", ...this.validatePaths(paths)]);
|
|
35247
|
+
});
|
|
35248
|
+
}
|
|
35249
|
+
async unstage(workspace3, paths) {
|
|
35250
|
+
await this.withWrite(workspace3, async () => {
|
|
35251
|
+
const root = await this.rootFor(workspace3);
|
|
35252
|
+
const valid = await this.includeRenameSources(root, this.validatePaths(paths));
|
|
35253
|
+
try {
|
|
35254
|
+
await this.run(root, ["--literal-pathspecs", "restore", "--staged", "--", ...valid]);
|
|
35255
|
+
} catch {
|
|
35256
|
+
await this.run(root, ["--literal-pathspecs", "reset", "--", ...valid]);
|
|
35257
|
+
}
|
|
35258
|
+
});
|
|
35259
|
+
}
|
|
35260
|
+
async commit(workspace3, message) {
|
|
35261
|
+
return this.withWrite(workspace3, async () => {
|
|
35262
|
+
const root = await this.rootFor(workspace3);
|
|
35263
|
+
const normalized = message.trim();
|
|
35264
|
+
if (!normalized)
|
|
35265
|
+
throw new Error("commit-message-required");
|
|
35266
|
+
await this.run(root, ["commit", "-m", normalized]);
|
|
35267
|
+
const hash = await this.run(root, ["rev-parse", "HEAD"]);
|
|
35268
|
+
const shortHash2 = await this.run(root, ["rev-parse", "--short", "HEAD"]);
|
|
35269
|
+
const summary = await this.run(root, ["show", "-s", "--format=%s", "HEAD"]);
|
|
35270
|
+
return { hash, shortHash: shortHash2, summary };
|
|
35271
|
+
});
|
|
35272
|
+
}
|
|
35273
|
+
async checkout(workspace3, options) {
|
|
35274
|
+
await this.withWrite(workspace3, async () => {
|
|
35275
|
+
const root = await this.rootFor(workspace3);
|
|
35276
|
+
const branch = await this.validateBranch(root, options.branch);
|
|
35277
|
+
if (await this.runRaw(root, ["status", "--porcelain", "-z"]) !== "") {
|
|
35278
|
+
throw new Error("dirty-worktree");
|
|
35279
|
+
}
|
|
35280
|
+
if (options.create) {
|
|
35281
|
+
const startPoint = await this.validateStartPoint(root, options.startPoint);
|
|
35282
|
+
await this.run(root, ["switch", "-c", branch, ...startPoint ? [startPoint] : []]);
|
|
35283
|
+
return;
|
|
35284
|
+
}
|
|
35285
|
+
await this.run(root, ["switch", branch]);
|
|
35286
|
+
});
|
|
35287
|
+
}
|
|
35288
|
+
async fetch(workspace3, remote = "origin") {
|
|
35289
|
+
await this.withWrite(workspace3, async () => {
|
|
35290
|
+
const root = await this.rootFor(workspace3);
|
|
35291
|
+
await this.assertRemote(root, remote);
|
|
35292
|
+
await this.run(root, ["fetch", "--prune", "--", remote]);
|
|
35293
|
+
});
|
|
35294
|
+
}
|
|
35295
|
+
async pull(workspace3) {
|
|
35296
|
+
await this.withWrite(workspace3, async () => {
|
|
35297
|
+
const root = await this.rootFor(workspace3);
|
|
35298
|
+
if (await this.runRaw(root, ["status", "--porcelain", "-z"]) !== "") {
|
|
35299
|
+
throw new Error("dirty-worktree");
|
|
35300
|
+
}
|
|
35301
|
+
try {
|
|
35302
|
+
await this.run(root, ["rev-parse", "--verify", "@{upstream}"]);
|
|
35303
|
+
} catch {
|
|
35304
|
+
throw new Error("no-upstream");
|
|
35305
|
+
}
|
|
35306
|
+
await this.run(root, ["pull", "--ff-only"]);
|
|
35307
|
+
});
|
|
35308
|
+
}
|
|
35309
|
+
async push(workspace3, options = {}) {
|
|
35310
|
+
await this.withWrite(workspace3, async () => {
|
|
35311
|
+
const root = await this.rootFor(workspace3);
|
|
35312
|
+
if (options.setUpstream) {
|
|
35313
|
+
const remote = options.remote?.trim() || "origin";
|
|
35314
|
+
await this.assertRemote(root, remote);
|
|
35315
|
+
const branch = await this.run(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
35316
|
+
if (branch === "HEAD")
|
|
35317
|
+
throw new Error("detached-head");
|
|
35318
|
+
await this.run(root, ["push", "--set-upstream", "--", remote, branch]);
|
|
35319
|
+
return;
|
|
35320
|
+
}
|
|
35321
|
+
try {
|
|
35322
|
+
await this.run(root, ["rev-parse", "--verify", "@{upstream}"]);
|
|
35323
|
+
} catch {
|
|
35324
|
+
throw new Error("no-upstream");
|
|
35325
|
+
}
|
|
35326
|
+
await this.run(root, ["push"]);
|
|
35327
|
+
});
|
|
35328
|
+
}
|
|
35329
|
+
async createWorktree(workspace3, options) {
|
|
35330
|
+
return this.withWrite(workspace3, async () => {
|
|
35331
|
+
const root = await this.rootFor(workspace3);
|
|
35332
|
+
const branch = await this.validateBranch(root, options.branch);
|
|
35333
|
+
const startPoint = await this.validateStartPoint(root, options.startPoint);
|
|
35334
|
+
await mkdir10(this.managedWorktreesRoot, { recursive: true });
|
|
35335
|
+
const managedRoot = await realpath2(this.managedWorktreesRoot);
|
|
35336
|
+
const commonDir = await this.run(root, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
35337
|
+
const top = await this.run(root, ["rev-parse", "--show-toplevel"]);
|
|
35338
|
+
const repoKey = `${basename4(top)}-${createHash4("sha256").update(commonDir).digest("hex").slice(0, 8)}`;
|
|
35339
|
+
const branchSlug = branch.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "branch";
|
|
35340
|
+
const branchKey = `${branchSlug}-${createHash4("sha256").update(branch).digest("hex").slice(0, 6)}`;
|
|
35341
|
+
const repoDir = join22(managedRoot, repoKey);
|
|
35342
|
+
try {
|
|
35343
|
+
const existing = await lstat(repoDir);
|
|
35344
|
+
if (!existing.isDirectory() || existing.isSymbolicLink())
|
|
35345
|
+
throw new Error("worktree-path-unsafe");
|
|
35346
|
+
} catch (error2) {
|
|
35347
|
+
if (error2.code !== "ENOENT")
|
|
35348
|
+
throw error2;
|
|
35349
|
+
await mkdir10(repoDir);
|
|
35350
|
+
}
|
|
35351
|
+
const realRepoDir = await realpath2(repoDir);
|
|
35352
|
+
if (!realRepoDir.startsWith(managedRoot + sep2))
|
|
35353
|
+
throw new Error("worktree-path-unsafe");
|
|
35354
|
+
const target = join22(realRepoDir, branchKey);
|
|
35355
|
+
try {
|
|
35356
|
+
await lstat(target);
|
|
35357
|
+
throw new Error("worktree-path-exists");
|
|
35358
|
+
} catch (error2) {
|
|
35359
|
+
if (error2.code !== "ENOENT")
|
|
35360
|
+
throw error2;
|
|
35361
|
+
}
|
|
35362
|
+
if (options.createBranch) {
|
|
35363
|
+
await this.run(root, ["worktree", "add", "-b", branch, target, ...startPoint ? [startPoint] : []]);
|
|
35364
|
+
} else {
|
|
35365
|
+
await this.run(root, ["worktree", "add", target, branch]);
|
|
35366
|
+
}
|
|
35367
|
+
const createdPath = await realpath2(target);
|
|
35368
|
+
if (!createdPath.startsWith(managedRoot + sep2))
|
|
35369
|
+
throw new Error("worktree-path-unsafe");
|
|
35370
|
+
return { path: createdPath, branch, linked: true };
|
|
35371
|
+
});
|
|
35372
|
+
}
|
|
35373
|
+
async removeManagedWorktree(workspace3, path14) {
|
|
35374
|
+
await this.withWrite(workspace3, async () => {
|
|
35375
|
+
const root = await this.rootFor(workspace3);
|
|
35376
|
+
const managedRoot = await realpath2(this.managedWorktreesRoot);
|
|
35377
|
+
const target = await realpath2(path14);
|
|
35378
|
+
if (!target.startsWith(managedRoot + sep2))
|
|
35379
|
+
throw new Error("worktree-outside-managed-root");
|
|
35380
|
+
const worktrees = await this.readWorktrees(root);
|
|
35381
|
+
const worktree = worktrees.find((item) => item.path === target);
|
|
35382
|
+
if (!worktree)
|
|
35383
|
+
throw new Error("worktree-not-found");
|
|
35384
|
+
if (worktree.current)
|
|
35385
|
+
throw new Error("cannot-remove-current-worktree");
|
|
35386
|
+
await this.run(root, ["worktree", "remove", target]);
|
|
35387
|
+
});
|
|
35388
|
+
}
|
|
35389
|
+
async assertRemote(root, remote) {
|
|
35390
|
+
const normalized = remote.trim();
|
|
35391
|
+
if (normalized.startsWith("-"))
|
|
35392
|
+
throw new Error("invalid-remote");
|
|
35393
|
+
const remotes = (await this.run(root, ["remote"])).split(`
|
|
35394
|
+
`).filter(Boolean);
|
|
35395
|
+
if (!normalized || !remotes.includes(normalized))
|
|
35396
|
+
throw new Error("unknown-remote");
|
|
35397
|
+
}
|
|
35398
|
+
async status(workspace3) {
|
|
35399
|
+
const root = await this.rootFor(workspace3);
|
|
35400
|
+
try {
|
|
35401
|
+
await this.run(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
35402
|
+
} catch {
|
|
35403
|
+
throw new Error("not-a-git-repo");
|
|
35404
|
+
}
|
|
35405
|
+
let head;
|
|
35406
|
+
try {
|
|
35407
|
+
head = await this.run(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
35408
|
+
} catch {
|
|
35409
|
+
try {
|
|
35410
|
+
head = await this.run(root, ["symbolic-ref", "--short", "HEAD"]);
|
|
35411
|
+
} catch {
|
|
35412
|
+
head = "HEAD";
|
|
35413
|
+
}
|
|
35414
|
+
}
|
|
35415
|
+
const branch = head === "HEAD" ? undefined : head;
|
|
35416
|
+
let upstream;
|
|
35417
|
+
let ahead = 0;
|
|
35418
|
+
let behind = 0;
|
|
35419
|
+
try {
|
|
35420
|
+
upstream = await this.run(root, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]);
|
|
35421
|
+
const [aheadRaw = "0", behindRaw = "0"] = (await this.run(root, ["rev-list", "--left-right", "--count", "HEAD...@{upstream}"])).split(/\s+/);
|
|
35422
|
+
ahead = Number(aheadRaw) || 0;
|
|
35423
|
+
behind = Number(behindRaw) || 0;
|
|
35424
|
+
} catch {
|
|
35425
|
+
upstream = undefined;
|
|
35426
|
+
}
|
|
35427
|
+
const worktrees = await this.readWorktrees(root);
|
|
35428
|
+
const currentWorktree = worktrees.find((item) => item.current);
|
|
35429
|
+
if (!currentWorktree)
|
|
35430
|
+
throw new Error("git-worktree-context-missing");
|
|
35431
|
+
const branchesRaw = await this.run(root, [
|
|
35432
|
+
"for-each-ref",
|
|
35433
|
+
"--format=%(refname:short)%00%(worktreepath)",
|
|
35434
|
+
"refs/heads"
|
|
35435
|
+
]);
|
|
35436
|
+
const branches = branchesRaw === "" ? [] : branchesRaw.split(`
|
|
35437
|
+
`).map((line) => {
|
|
35438
|
+
const [name = "", worktreePath = ""] = line.split("\x00");
|
|
35439
|
+
return {
|
|
35440
|
+
name,
|
|
35441
|
+
current: name === branch,
|
|
35442
|
+
...worktreePath ? { worktreePath } : {}
|
|
35443
|
+
};
|
|
35444
|
+
});
|
|
35445
|
+
const statusRaw = await this.runRaw(root, ["-c", "core.quotePath=false", "status", "--porcelain", "-z"]);
|
|
35446
|
+
const files = [];
|
|
35447
|
+
const fields = statusRaw.split("\x00");
|
|
35448
|
+
for (let index = 0;index < fields.length; index += 1) {
|
|
35449
|
+
const field = fields[index];
|
|
35450
|
+
if (!field)
|
|
35451
|
+
continue;
|
|
35452
|
+
const status = field.slice(0, 2);
|
|
35453
|
+
const path14 = field.slice(3);
|
|
35454
|
+
if (status[0] === "R" || status[0] === "C")
|
|
35455
|
+
index += 1;
|
|
35456
|
+
files.push({ path: path14, status });
|
|
35457
|
+
}
|
|
35458
|
+
files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
35459
|
+
return {
|
|
35460
|
+
workspace: workspace3,
|
|
35461
|
+
...branch ? { branch } : {},
|
|
35462
|
+
detached: !branch,
|
|
35463
|
+
...upstream ? { upstream } : {},
|
|
35464
|
+
ahead,
|
|
35465
|
+
behind,
|
|
35466
|
+
worktree: { root: currentWorktree.path, linked: currentWorktree.linked },
|
|
35467
|
+
files,
|
|
35468
|
+
branches,
|
|
35469
|
+
worktrees
|
|
35470
|
+
};
|
|
35471
|
+
}
|
|
35472
|
+
async readWorktrees(root) {
|
|
35473
|
+
const output = await this.run(root, ["worktree", "list", "--porcelain"]);
|
|
35474
|
+
const blocks = output.split(/\n\n+/).filter(Boolean);
|
|
35475
|
+
const parsed = blocks.map((block) => {
|
|
35476
|
+
let path14 = "";
|
|
35477
|
+
let branch;
|
|
35478
|
+
let detached = false;
|
|
35479
|
+
for (const line of block.split(`
|
|
35480
|
+
`)) {
|
|
35481
|
+
if (line.startsWith("worktree "))
|
|
35482
|
+
path14 = line.slice("worktree ".length);
|
|
35483
|
+
else if (line.startsWith("branch refs/heads/"))
|
|
35484
|
+
branch = line.slice("branch refs/heads/".length);
|
|
35485
|
+
else if (line === "detached")
|
|
35486
|
+
detached = true;
|
|
35487
|
+
}
|
|
35488
|
+
return { path: path14, branch, detached };
|
|
35489
|
+
});
|
|
35490
|
+
const currentRoot = await realpath2(root);
|
|
35491
|
+
return parsed.map((item, index) => ({
|
|
35492
|
+
path: item.path,
|
|
35493
|
+
...item.branch ? { branch: item.branch } : {},
|
|
35494
|
+
...item.detached ? { detached: true } : {},
|
|
35495
|
+
current: item.path === currentRoot,
|
|
35496
|
+
linked: index !== 0
|
|
35497
|
+
}));
|
|
35498
|
+
}
|
|
35499
|
+
}
|
|
35500
|
+
var execFileAsync2, GIT_TIMEOUT_MS2 = 30000, GIT_MAX_BUFFER2;
|
|
35501
|
+
var init_workspace_git = __esm(() => {
|
|
35502
|
+
execFileAsync2 = promisify2(execFile2);
|
|
35503
|
+
GIT_MAX_BUFFER2 = 8 * 1024 * 1024;
|
|
35504
|
+
});
|
|
35505
|
+
|
|
35126
35506
|
// src/control/turn-support.ts
|
|
35127
35507
|
async function raceWithTimeout(promise2, ms) {
|
|
35128
35508
|
let timer;
|
|
35129
|
-
const timeout = new Promise((
|
|
35130
|
-
timer = setTimeout(
|
|
35509
|
+
const timeout = new Promise((resolve5) => {
|
|
35510
|
+
timer = setTimeout(resolve5, ms);
|
|
35131
35511
|
});
|
|
35132
35512
|
try {
|
|
35133
35513
|
await Promise.race([promise2, timeout]);
|
|
@@ -35408,8 +35788,8 @@ class TurnQueue {
|
|
|
35408
35788
|
params.abortSignal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
35409
35789
|
}
|
|
35410
35790
|
let resolveSettled;
|
|
35411
|
-
const settled = new Promise((
|
|
35412
|
-
resolveSettled =
|
|
35791
|
+
const settled = new Promise((resolve5) => {
|
|
35792
|
+
resolveSettled = resolve5;
|
|
35413
35793
|
});
|
|
35414
35794
|
this.inFlight.set(key, { controller, settled });
|
|
35415
35795
|
if (params.drained) {
|
|
@@ -35530,9 +35910,12 @@ class ControlService {
|
|
|
35530
35910
|
deps;
|
|
35531
35911
|
runner;
|
|
35532
35912
|
turnQueue;
|
|
35913
|
+
workspaceGit;
|
|
35533
35914
|
modelSetTails = new Map;
|
|
35915
|
+
worktreeRegistrationTails = new Map;
|
|
35534
35916
|
constructor(deps) {
|
|
35535
35917
|
this.deps = deps;
|
|
35918
|
+
this.workspaceGit = new WorkspaceGit(() => this.deps.workspaces.list().map((w) => ({ name: w.name, cwd: w.cwd })), { ...deps.gitWorktreesRoot ? { managedWorktreesRoot: deps.gitWorktreesRoot } : {} });
|
|
35536
35919
|
this.runner = new SessionTurnRunner(deps);
|
|
35537
35920
|
this.turnQueue = new TurnQueue({
|
|
35538
35921
|
runTurn: (req, signal, onActivity) => this.runner.run(req, signal, onActivity),
|
|
@@ -35559,6 +35942,75 @@ class ControlService {
|
|
|
35559
35942
|
workspaceGitDiff(workspace3, path15) {
|
|
35560
35943
|
return this.workspaceFs.gitDiff(workspace3, path15);
|
|
35561
35944
|
}
|
|
35945
|
+
workspaceGitStatus(workspace3) {
|
|
35946
|
+
return this.workspaceGit.status(workspace3);
|
|
35947
|
+
}
|
|
35948
|
+
async mutateWorkspaceGit(operation) {
|
|
35949
|
+
if (!this.deps.filesWriteEnabled())
|
|
35950
|
+
throw new Error("files-write-disabled");
|
|
35951
|
+
return operation();
|
|
35952
|
+
}
|
|
35953
|
+
async withWorktreeRegistration(workspaceName, operation) {
|
|
35954
|
+
const previous = this.worktreeRegistrationTails.get(workspaceName) ?? Promise.resolve();
|
|
35955
|
+
let release;
|
|
35956
|
+
const current = new Promise((resolve5) => {
|
|
35957
|
+
release = resolve5;
|
|
35958
|
+
});
|
|
35959
|
+
const tail2 = previous.catch(() => {}).then(() => current);
|
|
35960
|
+
this.worktreeRegistrationTails.set(workspaceName, tail2);
|
|
35961
|
+
await previous.catch(() => {});
|
|
35962
|
+
try {
|
|
35963
|
+
return await operation();
|
|
35964
|
+
} finally {
|
|
35965
|
+
release();
|
|
35966
|
+
if (this.worktreeRegistrationTails.get(workspaceName) === tail2) {
|
|
35967
|
+
this.worktreeRegistrationTails.delete(workspaceName);
|
|
35968
|
+
}
|
|
35969
|
+
}
|
|
35970
|
+
}
|
|
35971
|
+
gitStage(workspace3, paths) {
|
|
35972
|
+
return this.mutateWorkspaceGit(() => this.workspaceGit.stage(workspace3, paths));
|
|
35973
|
+
}
|
|
35974
|
+
gitUnstage(workspace3, paths) {
|
|
35975
|
+
return this.mutateWorkspaceGit(() => this.workspaceGit.unstage(workspace3, paths));
|
|
35976
|
+
}
|
|
35977
|
+
gitCommit(workspace3, message) {
|
|
35978
|
+
return this.mutateWorkspaceGit(() => this.workspaceGit.commit(workspace3, message));
|
|
35979
|
+
}
|
|
35980
|
+
gitFetch(workspace3, remote) {
|
|
35981
|
+
return this.mutateWorkspaceGit(() => this.workspaceGit.fetch(workspace3, remote));
|
|
35982
|
+
}
|
|
35983
|
+
gitPull(workspace3) {
|
|
35984
|
+
return this.mutateWorkspaceGit(() => this.workspaceGit.pull(workspace3));
|
|
35985
|
+
}
|
|
35986
|
+
gitPush(workspace3, options) {
|
|
35987
|
+
return this.mutateWorkspaceGit(() => this.workspaceGit.push(workspace3, options));
|
|
35988
|
+
}
|
|
35989
|
+
gitCheckout(workspace3, options) {
|
|
35990
|
+
return this.mutateWorkspaceGit(() => this.workspaceGit.checkout(workspace3, options));
|
|
35991
|
+
}
|
|
35992
|
+
async gitCreateWorktree(workspace3, input) {
|
|
35993
|
+
const workspaceName = input.workspaceName.trim();
|
|
35994
|
+
if (!workspaceName)
|
|
35995
|
+
throw new Error("workspace-name-required");
|
|
35996
|
+
return this.mutateWorkspaceGit(() => this.withWorktreeRegistration(workspaceName, async () => {
|
|
35997
|
+
if (this.deps.workspaces.list().some((item) => item.name === workspaceName)) {
|
|
35998
|
+
throw new Error("workspace-name-exists");
|
|
35999
|
+
}
|
|
36000
|
+
const worktree = await this.workspaceGit.createWorktree(workspace3, input);
|
|
36001
|
+
try {
|
|
36002
|
+
const registered = await this.deps.workspaces.create(workspaceName, worktree.path, `Git worktree for ${worktree.branch}`);
|
|
36003
|
+
return { worktree, workspace: registered };
|
|
36004
|
+
} catch (registrationError) {
|
|
36005
|
+
try {
|
|
36006
|
+
await this.workspaceGit.removeManagedWorktree(workspace3, worktree.path);
|
|
36007
|
+
} catch (rollbackError) {
|
|
36008
|
+
throw new AggregateError([registrationError, rollbackError], "workspace-registration-rollback-failed");
|
|
36009
|
+
}
|
|
36010
|
+
throw registrationError;
|
|
36011
|
+
}
|
|
36012
|
+
}));
|
|
36013
|
+
}
|
|
35562
36014
|
searchWorkspace(workspace3, opts) {
|
|
35563
36015
|
return this.workspaceFs.search(workspace3, opts);
|
|
35564
36016
|
}
|
|
@@ -35641,8 +36093,8 @@ class ControlService {
|
|
|
35641
36093
|
async runModelSetExclusive(sessionAlias, operation) {
|
|
35642
36094
|
const previous = this.modelSetTails.get(sessionAlias) ?? Promise.resolve();
|
|
35643
36095
|
let release;
|
|
35644
|
-
const gate = new Promise((
|
|
35645
|
-
release =
|
|
36096
|
+
const gate = new Promise((resolve5) => {
|
|
36097
|
+
release = resolve5;
|
|
35646
36098
|
});
|
|
35647
36099
|
const tail2 = previous.catch(() => {}).then(() => gate);
|
|
35648
36100
|
this.modelSetTails.set(sessionAlias, tail2);
|
|
@@ -35858,6 +36310,7 @@ var init_control_service = __esm(() => {
|
|
|
35858
36310
|
init_channel_scope();
|
|
35859
36311
|
init_native_session_history();
|
|
35860
36312
|
init_workspace_fs();
|
|
36313
|
+
init_workspace_git();
|
|
35861
36314
|
init_session_turn_runner();
|
|
35862
36315
|
init_turn_queue();
|
|
35863
36316
|
init_turn_support();
|
|
@@ -36078,10 +36531,10 @@ var init_terminal_service = __esm(() => {
|
|
|
36078
36531
|
|
|
36079
36532
|
// src/control/upload-store.ts
|
|
36080
36533
|
import { mkdtemp as mkdtemp2, readdir as readdir5, rm as rm11, stat as stat4, writeFile as writeFile9 } from "node:fs/promises";
|
|
36081
|
-
import { homedir as
|
|
36534
|
+
import { homedir as homedir14 } from "node:os";
|
|
36082
36535
|
import path15 from "node:path";
|
|
36083
36536
|
function defaultRootDir() {
|
|
36084
|
-
const home = process.env.HOME ??
|
|
36537
|
+
const home = process.env.HOME ?? homedir14();
|
|
36085
36538
|
return path15.join(coreHomeDir(home), "runtime", "uploads");
|
|
36086
36539
|
}
|
|
36087
36540
|
function sanitizeUploadFilename(raw) {
|
|
@@ -36113,8 +36566,8 @@ class UploadStore {
|
|
|
36113
36566
|
if (bytes.byteLength > this.maxBytes)
|
|
36114
36567
|
throw new Error("file-too-large");
|
|
36115
36568
|
const safeName = sanitizeUploadFilename(filename);
|
|
36116
|
-
const { mkdir:
|
|
36117
|
-
await
|
|
36569
|
+
const { mkdir: mkdir11 } = await import("node:fs/promises");
|
|
36570
|
+
await mkdir11(this.rootDir, { recursive: true });
|
|
36118
36571
|
const dir = await mkdtemp2(path15.join(this.rootDir, "u-"));
|
|
36119
36572
|
const filePath = path15.join(dir, safeName);
|
|
36120
36573
|
await writeFile9(filePath, bytes);
|
|
@@ -36156,54 +36609,52 @@ var init_upload_store = __esm(() => {
|
|
|
36156
36609
|
});
|
|
36157
36610
|
|
|
36158
36611
|
// src/config/agent-catalog.ts
|
|
36159
|
-
|
|
36160
|
-
|
|
36161
|
-
function isBinaryOnPath(binary) {
|
|
36162
|
-
const path16 = process.env.PATH ?? "";
|
|
36163
|
-
const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
|
|
36164
|
-
for (const dir of path16.split(delimiter2)) {
|
|
36165
|
-
if (!dir)
|
|
36166
|
-
continue;
|
|
36167
|
-
for (const ext of exts) {
|
|
36168
|
-
try {
|
|
36169
|
-
if (existsSync3(join22(dir, binary + ext)))
|
|
36170
|
-
return true;
|
|
36171
|
-
} catch {}
|
|
36172
|
-
}
|
|
36173
|
-
}
|
|
36174
|
-
return false;
|
|
36612
|
+
function probeTarget(command) {
|
|
36613
|
+
return command.trim().split(/\s+/)[0] ?? "";
|
|
36175
36614
|
}
|
|
36176
|
-
function listAgentCatalog(config4, probe =
|
|
36615
|
+
function listAgentCatalog(config4, { registry: registry2, probe = (binary) => isExecutableOnPath(binary) }) {
|
|
36177
36616
|
const agents = config4.agents ?? {};
|
|
36178
36617
|
const driverConfigured = (driver) => Object.entries(agents).some(([name, a]) => name === driver || a.driver === driver);
|
|
36179
36618
|
return listAgentTemplates().map((driver) => {
|
|
36180
|
-
|
|
36181
|
-
|
|
36182
|
-
installed = "builtin";
|
|
36183
|
-
} else {
|
|
36184
|
-
const binary = DRIVER_BINARIES[driver] ?? driver;
|
|
36185
|
-
installed = probe(binary) ? "yes" : "unknown";
|
|
36186
|
-
}
|
|
36619
|
+
const binary = probeTarget(registry2?.resolve(driver) ?? driver);
|
|
36620
|
+
const installed = binary === "npx" || binary === "uvx" ? "builtin" : probe(binary) ? "yes" : "unknown";
|
|
36187
36621
|
return { driver, configured: driverConfigured(driver), installed };
|
|
36188
36622
|
});
|
|
36189
36623
|
}
|
|
36190
|
-
var BUILTIN_DRIVERS, DRIVER_BINARIES;
|
|
36191
36624
|
var init_agent_catalog = __esm(() => {
|
|
36192
36625
|
init_agent_templates();
|
|
36193
|
-
|
|
36194
|
-
|
|
36195
|
-
|
|
36626
|
+
init_local_agent_bin();
|
|
36627
|
+
});
|
|
36628
|
+
|
|
36629
|
+
// src/transport/agent-registry.ts
|
|
36630
|
+
import { createRequire as createRequire7 } from "node:module";
|
|
36631
|
+
function createAcpxAgentRegistryLoader(deps = {}) {
|
|
36632
|
+
let cached2;
|
|
36633
|
+
return () => {
|
|
36634
|
+
if (cached2 !== undefined)
|
|
36635
|
+
return cached2;
|
|
36636
|
+
try {
|
|
36637
|
+
cached2 = (deps.loadRuntime ?? loadRuntime)().createAgentRegistry();
|
|
36638
|
+
} catch (error2) {
|
|
36639
|
+
cached2 = null;
|
|
36640
|
+
deps.logger?.error("transport.agent_registry.unavailable", "could not read acpx's agent registry; install hints fall back to probing the driver name, so an installed CLI may show as not detected", { error: error2 instanceof Error ? error2.message : String(error2) });
|
|
36641
|
+
}
|
|
36642
|
+
return cached2;
|
|
36196
36643
|
};
|
|
36644
|
+
}
|
|
36645
|
+
var require6, loadRuntime = () => require6("acpx/runtime");
|
|
36646
|
+
var init_agent_registry = __esm(() => {
|
|
36647
|
+
require6 = createRequire7(import.meta.url);
|
|
36197
36648
|
});
|
|
36198
36649
|
|
|
36199
36650
|
// src/config/config-watcher.ts
|
|
36200
36651
|
import { watch } from "node:fs";
|
|
36201
|
-
import { basename as
|
|
36652
|
+
import { basename as basename5, dirname as dirname13 } from "node:path";
|
|
36202
36653
|
function startConfigWatcher(options) {
|
|
36203
36654
|
const { configPath, onChange, debounceMs = 100, logger } = options;
|
|
36204
36655
|
const watchFactory = options.watchFactory ?? watch;
|
|
36205
36656
|
const dir = dirname13(configPath);
|
|
36206
|
-
const target =
|
|
36657
|
+
const target = basename5(configPath);
|
|
36207
36658
|
let timer;
|
|
36208
36659
|
let watcher;
|
|
36209
36660
|
const fire = () => {
|
|
@@ -36218,7 +36669,7 @@ function startConfigWatcher(options) {
|
|
|
36218
36669
|
};
|
|
36219
36670
|
try {
|
|
36220
36671
|
watcher = watchFactory(dir, { persistent: false }, (_event, filename) => {
|
|
36221
|
-
if (filename !== null &&
|
|
36672
|
+
if (filename !== null && basename5(filename.toString()) !== target)
|
|
36222
36673
|
return;
|
|
36223
36674
|
if (timer)
|
|
36224
36675
|
clearTimeout(timer);
|
|
@@ -36257,7 +36708,7 @@ __export(exports_main, {
|
|
|
36257
36708
|
buildApp: () => buildApp
|
|
36258
36709
|
});
|
|
36259
36710
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
36260
|
-
import { homedir as
|
|
36711
|
+
import { homedir as homedir15 } from "node:os";
|
|
36261
36712
|
import { dirname as dirname14, join as join23 } from "node:path";
|
|
36262
36713
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
36263
36714
|
function startProgressHeartbeat(orchestration3, config4, logger, channel) {
|
|
@@ -36761,6 +37212,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
36761
37212
|
shell: () => terminalShell(config4)
|
|
36762
37213
|
});
|
|
36763
37214
|
const uploadStore = new UploadStore;
|
|
37215
|
+
const loadAgentRegistry = createAcpxAgentRegistryLoader({ logger });
|
|
36764
37216
|
uploadStore.cleanup();
|
|
36765
37217
|
const uploadCleanupInterval = setInterval(() => void uploadStore.cleanup().catch(() => {}), 60 * 60 * 1000);
|
|
36766
37218
|
const control = new ControlService({
|
|
@@ -36780,7 +37232,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
36780
37232
|
events: controlEvents,
|
|
36781
37233
|
agents: {
|
|
36782
37234
|
list: () => Object.entries(config4.agents).map(([name, agentConfig]) => ({ name, driver: agentConfig.driver })),
|
|
36783
|
-
catalog: () => listAgentCatalog(config4),
|
|
37235
|
+
catalog: () => listAgentCatalog(config4, { registry: loadAgentRegistry() }),
|
|
36784
37236
|
create: async (name, driver) => {
|
|
36785
37237
|
const updated = await configStore.upsertAgent(name, { driver });
|
|
36786
37238
|
replaceRuntimeConfig(config4, updated);
|
|
@@ -36983,7 +37435,7 @@ async function prepareChannelMedia(configPath, config4) {
|
|
|
36983
37435
|
return { mediaStore, channelDeps: { mediaStore, allowedMediaRoots } };
|
|
36984
37436
|
}
|
|
36985
37437
|
function resolveRuntimePaths() {
|
|
36986
|
-
const home = process.env.HOME ??
|
|
37438
|
+
const home = process.env.HOME ?? homedir15();
|
|
36987
37439
|
if (!home) {
|
|
36988
37440
|
throw new Error("Unable to resolve the current user home directory");
|
|
36989
37441
|
}
|
|
@@ -37059,6 +37511,7 @@ var init_main = __esm(async () => {
|
|
|
37059
37511
|
init_terminal_service();
|
|
37060
37512
|
init_upload_store();
|
|
37061
37513
|
init_agent_catalog();
|
|
37514
|
+
init_agent_registry();
|
|
37062
37515
|
init_config_watcher();
|
|
37063
37516
|
init_perf_tracer();
|
|
37064
37517
|
init_bootstrap();
|
|
@@ -37113,7 +37566,7 @@ function buildDetails(metadata, version2, verbose) {
|
|
|
37113
37566
|
}
|
|
37114
37567
|
async function defaultRunVersion(command) {
|
|
37115
37568
|
const spawnSpec = resolveSpawnCommand(command, ["--version"]);
|
|
37116
|
-
return await new Promise((
|
|
37569
|
+
return await new Promise((resolve5, reject) => {
|
|
37117
37570
|
const child = spawn12(spawnSpec.command, spawnSpec.args, {
|
|
37118
37571
|
stdio: ["ignore", "pipe", "pipe"]
|
|
37119
37572
|
});
|
|
@@ -37130,7 +37583,7 @@ async function defaultRunVersion(command) {
|
|
|
37130
37583
|
if (code === 0) {
|
|
37131
37584
|
const version2 = stdout2.trim() || stderr.trim();
|
|
37132
37585
|
if (version2.length > 0) {
|
|
37133
|
-
|
|
37586
|
+
resolve5(version2);
|
|
37134
37587
|
return;
|
|
37135
37588
|
}
|
|
37136
37589
|
}
|
|
@@ -37254,10 +37707,10 @@ var init_config_check = __esm(async () => {
|
|
|
37254
37707
|
// src/doctor/checks/daemon-check.ts
|
|
37255
37708
|
import { readdir as readdir6, readFile as readFile16, rm as rm12 } from "node:fs/promises";
|
|
37256
37709
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
37257
|
-
import { homedir as
|
|
37710
|
+
import { homedir as homedir16 } from "node:os";
|
|
37258
37711
|
import { join as join24 } from "node:path";
|
|
37259
37712
|
async function checkDaemon(options = {}) {
|
|
37260
|
-
const home = options.home ?? process.env.HOME ??
|
|
37713
|
+
const home = options.home ?? process.env.HOME ?? homedir16();
|
|
37261
37714
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
37262
37715
|
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
37263
37716
|
home,
|
|
@@ -37422,10 +37875,10 @@ var init_daemon_check = __esm(() => {
|
|
|
37422
37875
|
|
|
37423
37876
|
// src/doctor/checks/logs-check.ts
|
|
37424
37877
|
import { stat as stat5, readdir as readdir7 } from "node:fs/promises";
|
|
37425
|
-
import { basename as
|
|
37426
|
-
import { homedir as
|
|
37878
|
+
import { basename as basename6, join as join25 } from "node:path";
|
|
37879
|
+
import { homedir as homedir17 } from "node:os";
|
|
37427
37880
|
async function checkLogs(options = {}) {
|
|
37428
|
-
const home = options.home ?? process.env.HOME ??
|
|
37881
|
+
const home = options.home ?? process.env.HOME ?? homedir17();
|
|
37429
37882
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
37430
37883
|
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
37431
37884
|
home,
|
|
@@ -37452,7 +37905,7 @@ async function checkLogs(options = {}) {
|
|
|
37452
37905
|
`error: ${formatError6(error2)}`
|
|
37453
37906
|
]);
|
|
37454
37907
|
}
|
|
37455
|
-
const baseNames = [
|
|
37908
|
+
const baseNames = [basename6(paths.appLog), basename6(paths.stdoutLog), basename6(paths.stderrLog)];
|
|
37456
37909
|
const tracked = new Set(baseNames);
|
|
37457
37910
|
const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
|
|
37458
37911
|
const files = [];
|
|
@@ -37607,9 +38060,9 @@ var init_orchestration_health = __esm(() => {
|
|
|
37607
38060
|
});
|
|
37608
38061
|
|
|
37609
38062
|
// src/doctor/checks/orchestration-socket-check.ts
|
|
37610
|
-
import { homedir as
|
|
38063
|
+
import { homedir as homedir18 } from "node:os";
|
|
37611
38064
|
async function checkOrchestrationSocket(options = {}) {
|
|
37612
|
-
const home = options.home ?? process.env.HOME ??
|
|
38065
|
+
const home = options.home ?? process.env.HOME ?? homedir18();
|
|
37613
38066
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
37614
38067
|
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
37615
38068
|
home,
|
|
@@ -37784,9 +38237,9 @@ var init_plugin_check = __esm(async () => {
|
|
|
37784
38237
|
import { constants } from "node:fs";
|
|
37785
38238
|
import { access as access4, stat as stat6 } from "node:fs/promises";
|
|
37786
38239
|
import { dirname as dirname15 } from "node:path";
|
|
37787
|
-
import { homedir as
|
|
38240
|
+
import { homedir as homedir19 } from "node:os";
|
|
37788
38241
|
async function checkRuntime(options = {}) {
|
|
37789
|
-
const home = options.home ?? process.env.HOME ??
|
|
38242
|
+
const home = options.home ?? process.env.HOME ?? homedir19();
|
|
37790
38243
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
37791
38244
|
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
37792
38245
|
home,
|
|
@@ -38419,10 +38872,10 @@ var init_render_doctor = __esm(() => {
|
|
|
38419
38872
|
});
|
|
38420
38873
|
|
|
38421
38874
|
// src/doctor/doctor.ts
|
|
38422
|
-
import { homedir as
|
|
38875
|
+
import { homedir as homedir20 } from "node:os";
|
|
38423
38876
|
import { join as join26 } from "node:path";
|
|
38424
38877
|
async function runDoctor(options = {}, deps = {}) {
|
|
38425
|
-
const home = deps.home ?? process.env.HOME ??
|
|
38878
|
+
const home = deps.home ?? process.env.HOME ?? homedir20();
|
|
38426
38879
|
const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
|
|
38427
38880
|
const sharedLoadConfig = createSharedLoadConfig(runtimePaths, deps.loadConfig ?? loadConfig);
|
|
38428
38881
|
const runners = [
|
|
@@ -38759,8 +39212,8 @@ var init_doctor2 = __esm(async () => {
|
|
|
38759
39212
|
// src/cli.ts
|
|
38760
39213
|
init_core_home();
|
|
38761
39214
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
38762
|
-
import { homedir as
|
|
38763
|
-
import { dirname as dirname16, join as join27, sep as
|
|
39215
|
+
import { homedir as homedir21 } from "node:os";
|
|
39216
|
+
import { dirname as dirname16, join as join27, sep as sep3 } from "node:path";
|
|
38764
39217
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
38765
39218
|
|
|
38766
39219
|
// src/runtime/migrate-core-home.ts
|
|
@@ -55073,7 +55526,7 @@ async function defaultRun(options = {}) {
|
|
|
55073
55526
|
const firstRunOnboarding = options.firstRunOnboarding ?? decodeFirstRunOnboarding(coreEnv("FIRST_RUN_ONBOARDING"));
|
|
55074
55527
|
await runConsole2(runtimePaths, {
|
|
55075
55528
|
buildApp: (paths) => buildApp2(paths, {
|
|
55076
|
-
defaultLoggingLevel: resolveCliEntryPath2().includes(`${
|
|
55529
|
+
defaultLoggingLevel: resolveCliEntryPath2().includes(`${sep3}src${sep3}`) ? "debug" : "info",
|
|
55077
55530
|
channel: channelRegistry
|
|
55078
55531
|
}),
|
|
55079
55532
|
beforeReady: firstRunOnboarding ? async (runtime) => {
|
|
@@ -55084,7 +55537,7 @@ async function defaultRun(options = {}) {
|
|
|
55084
55537
|
daemonRuntime,
|
|
55085
55538
|
...firstLockCreator ? {
|
|
55086
55539
|
consumerLockFactory: (runtime) => firstLockCreator.create({
|
|
55087
|
-
lockFilePath: `${daemonPaths.runtimeDir}${
|
|
55540
|
+
lockFilePath: `${daemonPaths.runtimeDir}${sep3}${firstLockCreator.channel.id}-consumer.lock.json`,
|
|
55088
55541
|
onDiagnostic: async (event, context) => {
|
|
55089
55542
|
await runtime.logger.info(`${firstLockCreator.channel.id}.consumer_lock.${event}`, `${firstLockCreator.channel.id} consumer lock diagnostic`, context);
|
|
55090
55543
|
}
|
|
@@ -55279,7 +55732,7 @@ async function defaultPromptSecret(message) {
|
|
|
55279
55732
|
process.stdout.write(message);
|
|
55280
55733
|
process.stdin.setRawMode(true);
|
|
55281
55734
|
process.stdin.resume();
|
|
55282
|
-
return await new Promise((
|
|
55735
|
+
return await new Promise((resolve5, reject) => {
|
|
55283
55736
|
const chunks = [];
|
|
55284
55737
|
let inEscape = false;
|
|
55285
55738
|
const cleanup = () => {
|
|
@@ -55310,7 +55763,7 @@ async function defaultPromptSecret(message) {
|
|
|
55310
55763
|
if (char === "\r" || char === `
|
|
55311
55764
|
`) {
|
|
55312
55765
|
cleanup();
|
|
55313
|
-
|
|
55766
|
+
resolve5(chunks.join(""));
|
|
55314
55767
|
return;
|
|
55315
55768
|
}
|
|
55316
55769
|
if (char === "" || char === "\b") {
|
|
@@ -55365,7 +55818,7 @@ function decodeFirstRunOnboarding(raw) {
|
|
|
55365
55818
|
return null;
|
|
55366
55819
|
}
|
|
55367
55820
|
function requireHome2() {
|
|
55368
|
-
const home = process.env.HOME ??
|
|
55821
|
+
const home = process.env.HOME ?? homedir21();
|
|
55369
55822
|
if (!home) {
|
|
55370
55823
|
throw new Error("Unable to resolve the current user home directory");
|
|
55371
55824
|
}
|
|
@@ -4,11 +4,29 @@ export interface AgentCatalogEntry {
|
|
|
4
4
|
configured: boolean;
|
|
5
5
|
installed: "builtin" | "yes" | "unknown";
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
8
|
-
export
|
|
7
|
+
/** Narrow view of acpx's registry supplied by the transport boundary. */
|
|
8
|
+
export interface AgentCommandRegistry {
|
|
9
|
+
resolve(driver: string): string;
|
|
10
|
+
}
|
|
11
|
+
export interface ListAgentCatalogOptions {
|
|
12
|
+
registry: AgentCommandRegistry | null;
|
|
13
|
+
probe?: (binary: string) => boolean;
|
|
14
|
+
}
|
|
9
15
|
/**
|
|
10
|
-
* Catalog of every acpx driver xacpx knows (from `listAgentTemplates()`), each
|
|
11
|
-
*
|
|
12
|
-
*
|
|
16
|
+
* Catalog of every acpx driver xacpx knows (from `listAgentTemplates()`), each tagged with
|
|
17
|
+
* whether it's already configured and a best-effort install hint.
|
|
18
|
+
*
|
|
19
|
+
* The probe target is derived from the acpx registry supplied by the transport boundary,
|
|
20
|
+
* so it can't drift from acpx the way a hand-copied table did. It resolves aliases too
|
|
21
|
+
* (`factory-droid` and `factorydroid` both reach `droid exec --output-format acp`). What we
|
|
22
|
+
* derive is acpx's DEFAULT command for a
|
|
23
|
+
* driver: its registry accepts an `overrides` map (which acpx populates from ~/.acpx/config.json)
|
|
24
|
+
* that we do not reconstruct, and xacpx itself may substitute a local CLI at spawn time
|
|
25
|
+
* (resolveLocalAgentCommand). Neither changes the answer for any driver today.
|
|
26
|
+
*
|
|
27
|
+
* `resolve()` does not throw on an unknown name — it echoes the name back — so a driver acpx
|
|
28
|
+
* has dropped degrades to probing a binary of the same name.
|
|
29
|
+
*
|
|
30
|
+
* The transport layer supplies `registry`; `probe` remains injectable for tests.
|
|
13
31
|
*/
|
|
14
|
-
export declare function listAgentCatalog(config: AppConfig, probe
|
|
32
|
+
export declare function listAgentCatalog(config: AppConfig, { registry, probe }: ListAgentCatalogOptions): AgentCatalogEntry[];
|
|
@@ -9,6 +9,7 @@ import type { OrchestrationTaskRecord } from "../orchestration/orchestration-typ
|
|
|
9
9
|
import type { ControlEventBus } from "./control-event-bus";
|
|
10
10
|
import type { AgentCatalogEntry } from "../config/agent-catalog";
|
|
11
11
|
import { type DirListing, type FileContent, type SearchOptions, type SearchResult, type WorkspaceDiff } from "./workspace-fs";
|
|
12
|
+
import { type GitCheckoutOptions, type GitCommitResult, type GitPushOptions, type GitStatus, type GitWorktreeCreateOptions, type GitWorktreeCreateResult } from "./workspace-git";
|
|
12
13
|
import type { PromptAttachmentRef } from "@ganglion/xacpx-relay-protocol";
|
|
13
14
|
import type { UploadStore } from "./upload-store.js";
|
|
14
15
|
import { type TurnIdleTimeoutDetail } from "./turn-support";
|
|
@@ -89,6 +90,8 @@ export interface ControlServiceDeps {
|
|
|
89
90
|
terminal: import("./terminal-service").TerminalService;
|
|
90
91
|
terminalEnabled: () => boolean;
|
|
91
92
|
filesWriteEnabled: () => boolean;
|
|
93
|
+
/** Test/embedding override; production defaults to ~/.xacpx/worktrees. */
|
|
94
|
+
gitWorktreesRoot?: string;
|
|
92
95
|
turnIdleTimeoutMs?: () => number;
|
|
93
96
|
onTurnIdleTimeout?: (detail: TurnIdleTimeoutDetail) => void;
|
|
94
97
|
}
|
|
@@ -136,12 +139,30 @@ export declare class ControlService {
|
|
|
136
139
|
private readonly deps;
|
|
137
140
|
private readonly runner;
|
|
138
141
|
private readonly turnQueue;
|
|
142
|
+
private readonly workspaceGit;
|
|
139
143
|
private readonly modelSetTails;
|
|
144
|
+
private readonly worktreeRegistrationTails;
|
|
140
145
|
constructor(deps: ControlServiceDeps);
|
|
141
146
|
private readonly workspaceFs;
|
|
142
147
|
listDirectory(workspace: string, path?: string): Promise<DirListing>;
|
|
143
148
|
readWorkspaceFile(workspace: string, path: string): Promise<FileContent>;
|
|
144
149
|
workspaceGitDiff(workspace: string, path?: string): Promise<WorkspaceDiff>;
|
|
150
|
+
workspaceGitStatus(workspace: string): Promise<GitStatus>;
|
|
151
|
+
private mutateWorkspaceGit;
|
|
152
|
+
private withWorktreeRegistration;
|
|
153
|
+
gitStage(workspace: string, paths: string[]): Promise<void>;
|
|
154
|
+
gitUnstage(workspace: string, paths: string[]): Promise<void>;
|
|
155
|
+
gitCommit(workspace: string, message: string): Promise<GitCommitResult>;
|
|
156
|
+
gitFetch(workspace: string, remote?: string): Promise<void>;
|
|
157
|
+
gitPull(workspace: string): Promise<void>;
|
|
158
|
+
gitPush(workspace: string, options?: GitPushOptions): Promise<void>;
|
|
159
|
+
gitCheckout(workspace: string, options: GitCheckoutOptions): Promise<void>;
|
|
160
|
+
gitCreateWorktree(workspace: string, input: GitWorktreeCreateOptions & {
|
|
161
|
+
workspaceName: string;
|
|
162
|
+
}): Promise<{
|
|
163
|
+
worktree: GitWorktreeCreateResult;
|
|
164
|
+
workspace: ControlWorkspaceInfo;
|
|
165
|
+
}>;
|
|
145
166
|
searchWorkspace(workspace: string, opts: SearchOptions): Promise<SearchResult>;
|
|
146
167
|
fsCreate(workspace: string, path: string, kind: "file" | "dir"): Promise<{
|
|
147
168
|
path: string;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export interface GitWorkspaceRef {
|
|
2
|
+
name: string;
|
|
3
|
+
cwd: string;
|
|
4
|
+
}
|
|
5
|
+
export interface GitBranchInfo {
|
|
6
|
+
name: string;
|
|
7
|
+
current: boolean;
|
|
8
|
+
/** Present when Git reports this branch checked out in a worktree. */
|
|
9
|
+
worktreePath?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface GitWorktreeInfo {
|
|
12
|
+
path: string;
|
|
13
|
+
branch?: string;
|
|
14
|
+
detached?: boolean;
|
|
15
|
+
current: boolean;
|
|
16
|
+
linked: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface GitStatus {
|
|
19
|
+
workspace: string;
|
|
20
|
+
branch?: string;
|
|
21
|
+
detached: boolean;
|
|
22
|
+
upstream?: string;
|
|
23
|
+
ahead: number;
|
|
24
|
+
behind: number;
|
|
25
|
+
worktree: {
|
|
26
|
+
root: string;
|
|
27
|
+
linked: boolean;
|
|
28
|
+
};
|
|
29
|
+
files: Array<{
|
|
30
|
+
path: string;
|
|
31
|
+
status: string;
|
|
32
|
+
}>;
|
|
33
|
+
branches: GitBranchInfo[];
|
|
34
|
+
worktrees: GitWorktreeInfo[];
|
|
35
|
+
}
|
|
36
|
+
export interface GitCommitResult {
|
|
37
|
+
hash: string;
|
|
38
|
+
shortHash: string;
|
|
39
|
+
summary: string;
|
|
40
|
+
}
|
|
41
|
+
export interface GitCheckoutOptions {
|
|
42
|
+
branch: string;
|
|
43
|
+
create?: boolean;
|
|
44
|
+
startPoint?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface GitPushOptions {
|
|
47
|
+
setUpstream?: boolean;
|
|
48
|
+
remote?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface GitWorktreeCreateOptions {
|
|
51
|
+
branch: string;
|
|
52
|
+
createBranch?: boolean;
|
|
53
|
+
startPoint?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface GitWorktreeCreateResult {
|
|
56
|
+
path: string;
|
|
57
|
+
branch: string;
|
|
58
|
+
linked: true;
|
|
59
|
+
}
|
|
60
|
+
export declare class WorkspaceGit {
|
|
61
|
+
private readonly listWorkspaces;
|
|
62
|
+
private readonly managedWorktreesRoot;
|
|
63
|
+
private readonly writeTails;
|
|
64
|
+
constructor(listWorkspaces: () => GitWorkspaceRef[], options?: {
|
|
65
|
+
managedWorktreesRoot?: string;
|
|
66
|
+
});
|
|
67
|
+
private rootFor;
|
|
68
|
+
private run;
|
|
69
|
+
private runRaw;
|
|
70
|
+
private validatePaths;
|
|
71
|
+
private validateBranch;
|
|
72
|
+
private validateStartPoint;
|
|
73
|
+
private includeRenameSources;
|
|
74
|
+
/** Git write commands for one configured workspace share a FIFO. Reads remain
|
|
75
|
+
* concurrent, and a failed write releases the next command instead of poisoning
|
|
76
|
+
* the queue. */
|
|
77
|
+
private withWrite;
|
|
78
|
+
stage(workspace: string, paths: string[]): Promise<void>;
|
|
79
|
+
unstage(workspace: string, paths: string[]): Promise<void>;
|
|
80
|
+
commit(workspace: string, message: string): Promise<GitCommitResult>;
|
|
81
|
+
checkout(workspace: string, options: GitCheckoutOptions): Promise<void>;
|
|
82
|
+
fetch(workspace: string, remote?: string): Promise<void>;
|
|
83
|
+
pull(workspace: string): Promise<void>;
|
|
84
|
+
push(workspace: string, options?: GitPushOptions): Promise<void>;
|
|
85
|
+
createWorktree(workspace: string, options: GitWorktreeCreateOptions): Promise<GitWorktreeCreateResult>;
|
|
86
|
+
/** Internal compensation for a failed workspace-registration step. Not exposed as a
|
|
87
|
+
* user-facing delete operation: it only accepts a non-current worktree beneath the
|
|
88
|
+
* server-managed root. */
|
|
89
|
+
removeManagedWorktree(workspace: string, path: string): Promise<void>;
|
|
90
|
+
private assertRemote;
|
|
91
|
+
status(workspace: string): Promise<GitStatus>;
|
|
92
|
+
private readWorktrees;
|
|
93
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ganglion/xacpx",
|
|
3
|
-
"version": "0.17.0-beta.
|
|
3
|
+
"version": "0.17.0-beta.7",
|
|
4
4
|
"description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"acpx",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"registry": "https://registry.npmjs.org/"
|
|
100
100
|
},
|
|
101
101
|
"engines": {
|
|
102
|
-
"node": ">=22"
|
|
102
|
+
"node": ">=22.12"
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
105
|
"@types/proper-lockfile": "^4.1.4",
|