@agimon-ai/po-git 0.0.0-alpha.78 → 0.0.0-alpha.79

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.
@@ -7,6 +7,8 @@ import { composeRootHooks, composeRootInjects, defineChannel, defineHook, define
7
7
  import { PO_CODEMODE_SERVICE, readPoCodemode } from "@agimon-ai/po-core/codemode";
8
8
  import { readPoSessionDelivery } from "@agimon-ai/po-session";
9
9
  import { Type } from "typebox";
10
+ import { BACKGROUND_CONTEXT, PO_SHELL_SERVICE, requirePoShell, withAbortSignal } from "@agimon-ai/po-core/platform";
11
+ import { poApiCallerFrom } from "@agimon-ai/po-core/packageApi";
10
12
  //#region src/schemas/runWorktreeTool.ts
11
13
  /**
12
14
  * The tool's action vocabulary.
@@ -1285,177 +1287,236 @@ function failed(error) {
1285
1287
  }, { status: error.retryable ? 503 : 409 });
1286
1288
  return Response.json({ error: "The worktree operation failed." }, { status: 500 });
1287
1289
  }
1288
- //#endregion
1289
- //#region src/extensions/(backend)/api/git/route.server.ts
1290
- var route_server_default = defineRoute({
1291
- basePath: "git",
1292
- start(context) {
1293
- if (context.directEvents === void 0) throw new Error("Git hub API requires the direct event bus.");
1294
- const directEvents = context.directEvents;
1295
- const publishLifecycle = (sessionId, repositoryRoot) => {
1296
- directEvents.publish(GIT_WORKTREE_LIFECYCLE_EVENT, sessionId, {
1297
- version: 1,
1298
- repositoryRoot
1290
+ function createGitApi(openDirectory) {
1291
+ return {
1292
+ basePath: "git",
1293
+ start(context) {
1294
+ if (context.directEvents === void 0) throw new Error("Git hub API requires the direct event bus.");
1295
+ const directEvents = context.directEvents;
1296
+ const publishLifecycle = (sessionId, repositoryRoot) => {
1297
+ directEvents.publish(GIT_WORKTREE_LIFECYCLE_EVENT, sessionId, {
1298
+ version: 1,
1299
+ repositoryRoot
1300
+ });
1301
+ };
1302
+ const git = createWorktreeGit();
1303
+ const operations = createWorktreeOperations({
1304
+ git,
1305
+ sessionService: context.sessionService,
1306
+ ...context.homeDirectory === void 0 ? {} : { homeDir: context.homeDirectory }
1299
1307
  });
1300
- };
1301
- const git = createWorktreeGit();
1302
- const operations = createWorktreeOperations({
1303
- git,
1304
- sessionService: context.sessionService,
1305
- ...context.homeDirectory === void 0 ? {} : { homeDir: context.homeDirectory }
1306
- });
1307
- /**
1308
- * The new-session dialog's routes: the workspace's branches, and a top-level
1309
- * worktree session on one. Workspace-mounted only, and always rooted at the
1310
- * mount's own workspace, so a request can never name another directory.
1311
- */
1312
- const newSession = async (request, url) => {
1313
- const isBranches = request.method === "GET" && url.pathname === "/branches";
1314
- const isCreate = request.method === "POST" && url.pathname === "/sessions";
1315
- if (!isBranches && !isCreate) return void 0;
1316
- const workspaceRoot = context.scope === "workspace" ? context.workspaceRoot : void 0;
1317
- if (workspaceRoot === void 0) return Response.json({ error: "This route belongs to a workspace." }, { status: 404 });
1318
- if (isBranches) {
1319
- const repositoryRoot = await git.repositoryRoot(workspaceRoot);
1320
- const branches = repositoryRoot === void 0 ? {
1321
- repository: false,
1322
- local: [],
1323
- remote: []
1324
- } : {
1325
- repository: true,
1326
- ...await git.listBranches(repositoryRoot)
1308
+ /**
1309
+ * The new-session dialog's routes: the workspace's branches, and a top-level
1310
+ * worktree session on one. Workspace-mounted only, and always rooted at the
1311
+ * mount's own workspace, so a request can never name another directory.
1312
+ */
1313
+ const newSession = async (request, url) => {
1314
+ const isBranches = request.method === "GET" && url.pathname === "/branches";
1315
+ const isCreate = request.method === "POST" && url.pathname === "/sessions";
1316
+ const isDelete = request.method === "DELETE" && url.pathname === "/branches";
1317
+ const isOpen = request.method === "POST" && url.pathname === `/branches/open`;
1318
+ if (!isBranches && !isCreate && !isDelete && !isOpen) return void 0;
1319
+ const workspaceRoot = context.scope === "workspace" ? context.workspaceRoot : void 0;
1320
+ if (workspaceRoot === void 0) return Response.json({ error: "This route belongs to a workspace." }, { status: 404 });
1321
+ if (isBranches) {
1322
+ const repositoryRoot = await git.repositoryRoot(workspaceRoot);
1323
+ const branches = repositoryRoot === void 0 ? {
1324
+ repository: false,
1325
+ local: [],
1326
+ remote: []
1327
+ } : {
1328
+ repository: true,
1329
+ ...await git.listBranches(repositoryRoot)
1330
+ };
1331
+ return Response.json(branches);
1332
+ }
1333
+ if (isDelete || isOpen) {
1334
+ if (isOpen && poApiCallerFrom(request.headers)?.locality !== "local") return Response.json({ error: "Opening Finder is only available on the host." }, { status: 403 });
1335
+ const body = await request.json().catch(() => void 0);
1336
+ const branch = typeof body === "object" && body !== null ? body.branch : void 0;
1337
+ if (typeof branch !== "string" || !validRefName(branch)) return badRequest("A valid branch name is required.");
1338
+ const repositoryRoot = await git.repositoryRoot(workspaceRoot);
1339
+ if (repositoryRoot === void 0) return badRequest("This workspace is not a git checkout.");
1340
+ const branches = await git.listBranches(repositoryRoot);
1341
+ const local = branches.local.find((item) => item.name === branch);
1342
+ if (local === void 0) return Response.json({ error: "That local branch no longer exists." }, { status: 404 });
1343
+ if (isOpen) {
1344
+ if (local.checkedOutAt === void 0) return Response.json({ error: "This branch has no checked-out folder." }, { status: 409 });
1345
+ if (openDirectory === void 0) return Response.json({ error: "Finder is not available on this host." }, { status: 409 });
1346
+ try {
1347
+ await openDirectory(local.checkedOutAt);
1348
+ return Response.json({ opened: true });
1349
+ } catch {
1350
+ return Response.json({ error: "The branch folder could not be opened in Finder." }, { status: 409 });
1351
+ }
1352
+ }
1353
+ if (local.checkedOutAt !== void 0 || branches.current === branch) return Response.json({ error: "Close this branch’s worktree before deleting it." }, { status: 409 });
1354
+ if (!await git.deleteBranch({
1355
+ repositoryRoot,
1356
+ branch
1357
+ })) return Response.json({ error: "Git refused to delete this branch. It may contain unmerged commits or be checked out." }, { status: 409 });
1358
+ return Response.json({ deleted: true });
1359
+ }
1360
+ const parsed = newSessionRequest(await request.json().catch(() => void 0));
1361
+ if ("error" in parsed) return badRequest(parsed.error);
1362
+ const record = await operations.spawn({ cwd: workspaceRoot }, {
1363
+ branch: parsed.branch,
1364
+ checkout: parsed.mode === "existing-branch" ? "existing" : "new",
1365
+ ...parsed.mode === "existing-branch" && parsed.remote !== void 0 ? { remote: parsed.remote } : {},
1366
+ ...parsed.mode === "new-branch" && parsed.baseRef !== void 0 ? { baseRef: parsed.baseRef } : {},
1367
+ ...parsed.name === void 0 ? {} : { name: parsed.name }
1368
+ });
1369
+ publishLifecycle(record.sessionId, record.repositoryRoot);
1370
+ const created = {
1371
+ sessionId: record.sessionId,
1372
+ worktreeId: record.id
1327
1373
  };
1328
- return Response.json(branches);
1329
- }
1330
- const parsed = newSessionRequest(await request.json().catch(() => void 0));
1331
- if ("error" in parsed) return badRequest(parsed.error);
1332
- const record = await operations.spawn({ cwd: workspaceRoot }, {
1333
- branch: parsed.branch,
1334
- checkout: parsed.mode === "existing-branch" ? "existing" : "new",
1335
- ...parsed.mode === "existing-branch" && parsed.remote !== void 0 ? { remote: parsed.remote } : {},
1336
- ...parsed.mode === "new-branch" && parsed.baseRef !== void 0 ? { baseRef: parsed.baseRef } : {},
1337
- ...parsed.name === void 0 ? {} : { name: parsed.name }
1338
- });
1339
- publishLifecycle(record.sessionId, record.repositoryRoot);
1340
- const created = {
1341
- sessionId: record.sessionId,
1342
- worktreeId: record.id
1374
+ return Response.json(created, { status: 201 });
1343
1375
  };
1344
- return Response.json(created, { status: 201 });
1345
- };
1346
- /** The admitted root for a request, or a response explaining why not. */
1347
- const rootOf = (url) => {
1348
- const repositoryId = url.searchParams.get("repositoryId");
1349
- if (repositoryId === null || repositoryId === "") return badRequest("A repositoryId is required.");
1350
- const root = context.resolveRepository?.(repositoryId);
1351
- if (root === void 0) return badRequest("That repository is not admitted to this cockpit.");
1352
- return root;
1353
- };
1354
- const homeDir = context.homeDirectory ?? os.homedir();
1355
- const gitAuth = createGitAuthStore(homeDir);
1356
- const branchDiff = createBranchDiff();
1357
- const pendingReviews = /* @__PURE__ */ new Map();
1358
- /**
1359
- * A workspace's remote auth. Workspace mounts only, so the step-up gate on
1360
- * `PUT /api/workspaces/<id>/plugins/git/auth` is the one way to write it.
1361
- * The view never carries the token.
1362
- */
1363
- const auth = async (request, url) => {
1364
- if (url.pathname !== "/auth") return void 0;
1365
- if (context.scope !== "workspace") return Response.json({ error: "This route belongs to a workspace." }, { status: 404 });
1366
- if (request.method !== "GET" && request.method !== "PUT") return Response.json({ error: "Use GET or PUT." }, { status: 405 });
1367
- const root = rootOf(url);
1368
- if (root instanceof Response) return root;
1369
- if (request.method === "GET") return Response.json(await gitAuth.view(root));
1370
- try {
1371
- return Response.json(await gitAuth.save(root, await request.json().catch(() => void 0)));
1372
- } catch (error) {
1373
- if (error instanceof PoGitExpectedError && error.code === "invalid_request") {
1374
- const text = error.message.replace(/^\[[a-z_]+\] /u, "").replace("\nRecovery: ", " ");
1375
- return Response.json({
1376
- error: text,
1377
- code: error.code
1378
- }, { status: 400 });
1379
- }
1380
- throw error;
1381
- }
1382
- };
1383
- /**
1384
- * The session's change against its base. Session mounts only, rooted at the
1385
- * session's own checkout; a file is served only when it is in the change set
1386
- * computed for the request. Overlapping requests share an in-flight snapshot.
1387
- */
1388
- const review = async (request, url) => {
1389
- if (url.pathname !== "/review" && url.pathname !== "/review/file") return void 0;
1390
- const cwd = context.scope === "session" ? context.cwd : void 0;
1391
- if (cwd === void 0) return Response.json({ error: "This route belongs to a session." }, { status: 404 });
1392
- if (request.method !== "GET") return Response.json({ error: "Use GET." }, { status: 405 });
1393
- let recordedBaseRef;
1394
- try {
1395
- recordedBaseRef = (await createWorktreeRegistry(await registryFile(cwd, homeDir)).list()).find((record) => record.sessionId === context.sessionId)?.baseRef;
1396
- } catch {
1397
- recordedBaseRef = void 0;
1398
- }
1399
- const key = JSON.stringify([cwd, recordedBaseRef ?? null]);
1400
- let pending = pendingReviews.get(key);
1401
- if (pending === void 0) {
1402
- pending = branchDiff.review(cwd, recordedBaseRef === void 0 ? {} : { recordedBaseRef }).finally(() => pendingReviews.delete(key));
1403
- pendingReviews.set(key, pending);
1404
- }
1405
- const result = await pending;
1406
- if (url.pathname === "/review") return Response.json(result?.summary ?? {
1407
- repository: false,
1408
- files: []
1409
- });
1410
- const filePath = url.searchParams.get("path") ?? "";
1411
- if (result === void 0 || !result.summary.files.some((file) => file.path === filePath)) return Response.json({ error: "That file is not part of this review." }, { status: 404 });
1412
- return Response.json(await branchDiff.fileDiff(result, filePath));
1413
- };
1414
- return {
1415
- async fetch(request) {
1416
- const url = new URL(request.url);
1376
+ /** The admitted root for a request, or a response explaining why not. */
1377
+ const rootOf = (url) => {
1378
+ const repositoryId = url.searchParams.get("repositoryId");
1379
+ if (repositoryId === null || repositoryId === "") return badRequest("A repositoryId is required.");
1380
+ const root = context.resolveRepository?.(repositoryId);
1381
+ if (root === void 0) return badRequest("That repository is not admitted to this cockpit.");
1382
+ return root;
1383
+ };
1384
+ const homeDir = context.homeDirectory ?? context.environment?.HOME;
1385
+ const gitAuth = homeDir === void 0 ? void 0 : createGitAuthStore(homeDir);
1386
+ const branchDiff = createBranchDiff();
1387
+ const pendingReviews = /* @__PURE__ */ new Map();
1388
+ /**
1389
+ * A workspace's remote auth. Workspace mounts only, so the step-up gate on
1390
+ * `PUT /api/workspaces/<id>/plugins/git/auth` is the one way to write it.
1391
+ * The view never carries the token.
1392
+ */
1393
+ const auth = async (request, url) => {
1394
+ if (url.pathname !== "/auth") return void 0;
1395
+ if (context.scope !== "workspace") return Response.json({ error: "This route belongs to a workspace." }, { status: 404 });
1396
+ if (request.method !== "GET" && request.method !== "PUT") return Response.json({ error: "Use GET or PUT." }, { status: 405 });
1397
+ if (gitAuth === void 0) throw new Error("Git auth requires the admitted host home directory.");
1398
+ const root = rootOf(url);
1399
+ if (root instanceof Response) return root;
1400
+ if (request.method === "GET") return Response.json(await gitAuth.view(root));
1417
1401
  try {
1418
- const answered = await newSession(request, url) ?? await auth(request, url) ?? await review(request, url);
1419
- if (answered !== void 0) return answered;
1402
+ return Response.json(await gitAuth.save(root, await request.json().catch(() => void 0)));
1420
1403
  } catch (error) {
1421
- context.onNotice(`git request failed: ${error.name}`);
1422
- return failed(error);
1404
+ if (error instanceof PoGitExpectedError && error.code === "invalid_request") {
1405
+ const text = error.message.replace(/^\[[a-z_]+\] /u, "").replace("\nRecovery: ", " ");
1406
+ return Response.json({
1407
+ error: text,
1408
+ code: error.code
1409
+ }, { status: 400 });
1410
+ }
1411
+ throw error;
1423
1412
  }
1424
- const root = rootOf(url);
1425
- if (root instanceof Response) return root;
1426
- const callerContext = {
1427
- cwd: root,
1428
- sessionId: url.searchParams.get("sessionId") ?? ""
1429
- };
1430
- if ((request.method === "POST" && url.pathname === "/worktrees" || request.method === "DELETE" && /^\/worktrees\/[^/]+$/u.test(url.pathname)) && callerContext.sessionId === "") return badRequest("A sessionId is required for worktree mutations.");
1413
+ };
1414
+ /**
1415
+ * The session's change against its base. Session mounts only, rooted at the
1416
+ * session's own checkout; a file is served only when it is in the change set
1417
+ * computed for the request. Overlapping requests share an in-flight snapshot.
1418
+ */
1419
+ const review = async (request, url) => {
1420
+ if (url.pathname !== "/review" && url.pathname !== "/review/file") return void 0;
1421
+ const cwd = context.scope === "session" ? context.cwd : void 0;
1422
+ if (cwd === void 0) return Response.json({ error: "This route belongs to a session." }, { status: 404 });
1423
+ if (request.method !== "GET") return Response.json({ error: "Use GET." }, { status: 405 });
1424
+ if (homeDir === void 0) throw new Error("Git review requires the admitted host home directory.");
1425
+ let recordedBaseRef;
1431
1426
  try {
1432
- if (request.method === "GET" && url.pathname === "/worktrees") return Response.json({ worktrees: await operations.list(callerContext) });
1433
- if (request.method === "POST" && url.pathname === "/worktrees") {
1434
- const body = await request.json();
1435
- if (typeof body.branch !== "string" || body.branch.trim() === "") return badRequest("A branch is required.");
1436
- const record = await operations.spawn(callerContext, {
1437
- branch: body.branch,
1438
- ...body.baseRef === void 0 ? {} : { baseRef: body.baseRef },
1439
- ...body.name === void 0 ? {} : { name: body.name }
1440
- });
1441
- publishLifecycle(callerContext.sessionId, record.repositoryRoot);
1442
- return Response.json({ worktree: record }, { status: 201 });
1427
+ recordedBaseRef = (await createWorktreeRegistry(await registryFile(cwd, homeDir)).list()).find((record) => record.sessionId === context.sessionId)?.baseRef;
1428
+ } catch {
1429
+ recordedBaseRef = void 0;
1430
+ }
1431
+ const key = JSON.stringify([cwd, recordedBaseRef ?? null]);
1432
+ let pending = pendingReviews.get(key);
1433
+ if (pending === void 0) {
1434
+ pending = branchDiff.review(cwd, recordedBaseRef === void 0 ? {} : { recordedBaseRef }).finally(() => pendingReviews.delete(key));
1435
+ pendingReviews.set(key, pending);
1436
+ }
1437
+ const result = await pending;
1438
+ if (url.pathname === "/review") return Response.json(result?.summary ?? {
1439
+ repository: false,
1440
+ files: []
1441
+ });
1442
+ const filePath = url.searchParams.get("path") ?? "";
1443
+ if (result === void 0 || !result.summary.files.some((file) => file.path === filePath)) return Response.json({ error: "That file is not part of this review." }, { status: 404 });
1444
+ return Response.json(await branchDiff.fileDiff(result, filePath));
1445
+ };
1446
+ return {
1447
+ async fetch(request) {
1448
+ const url = new URL(request.url);
1449
+ try {
1450
+ const answered = await newSession(request, url) ?? await auth(request, url) ?? await review(request, url);
1451
+ if (answered !== void 0) return answered;
1452
+ } catch (error) {
1453
+ context.onNotice(`git request failed: ${error.name}`);
1454
+ return failed(error);
1443
1455
  }
1444
- const closing = /^\/worktrees\/([^/]+)$/u.exec(url.pathname);
1445
- if (request.method === "DELETE" && closing) {
1446
- const record = await operations.close(callerContext, decodeURIComponent(String(closing[1])), url.searchParams.get("force") === "true");
1447
- publishLifecycle(callerContext.sessionId, record.repositoryRoot);
1448
- return Response.json({ worktree: record });
1456
+ const root = rootOf(url);
1457
+ if (root instanceof Response) return root;
1458
+ const callerContext = {
1459
+ cwd: root,
1460
+ sessionId: url.searchParams.get("sessionId") ?? ""
1461
+ };
1462
+ if ((request.method === "POST" && url.pathname === "/worktrees" || request.method === "DELETE" && /^\/worktrees\/[^/]+$/u.test(url.pathname)) && callerContext.sessionId === "") return badRequest("A sessionId is required for worktree mutations.");
1463
+ try {
1464
+ if (request.method === "GET" && url.pathname === "/worktrees") return Response.json({ worktrees: await operations.list(callerContext) });
1465
+ if (request.method === "POST" && url.pathname === "/worktrees") {
1466
+ const body = await request.json();
1467
+ if (typeof body.branch !== "string" || body.branch.trim() === "") return badRequest("A branch is required.");
1468
+ const record = await operations.spawn(callerContext, {
1469
+ branch: body.branch,
1470
+ ...body.baseRef === void 0 ? {} : { baseRef: body.baseRef },
1471
+ ...body.name === void 0 ? {} : { name: body.name }
1472
+ });
1473
+ publishLifecycle(callerContext.sessionId, record.repositoryRoot);
1474
+ return Response.json({ worktree: record }, { status: 201 });
1475
+ }
1476
+ const closing = /^\/worktrees\/([^/]+)$/u.exec(url.pathname);
1477
+ if (request.method === "DELETE" && closing) {
1478
+ const record = await operations.close(callerContext, decodeURIComponent(String(closing[1])), url.searchParams.get("force") === "true");
1479
+ publishLifecycle(callerContext.sessionId, record.repositoryRoot);
1480
+ return Response.json({ worktree: record });
1481
+ }
1482
+ return Response.json({ error: "No such worktree route." }, { status: 404 });
1483
+ } catch (error) {
1484
+ context.onNotice(`worktree request failed: ${error.name}`);
1485
+ return failed(error);
1449
1486
  }
1450
- return Response.json({ error: "No such worktree route." }, { status: 404 });
1451
- } catch (error) {
1452
- context.onNotice(`worktree request failed: ${error.name}`);
1453
- return failed(error);
1454
- }
1455
- },
1456
- close() {}
1487
+ },
1488
+ close() {}
1489
+ };
1490
+ }
1491
+ };
1492
+ }
1493
+ //#endregion
1494
+ //#region src/extensions/(backend)/api/git/route.server.ts
1495
+ var route_server_default = defineRoute(({ context, host, signal }) => {
1496
+ let openDirectory;
1497
+ context.inject([PO_SHELL_SERVICE], (child) => {
1498
+ const shell = requirePoShell(child);
1499
+ const env = Object.fromEntries(Object.entries(host.context.environment ?? {}).filter((entry) => entry[1] !== void 0));
1500
+ openDirectory = async (path) => {
1501
+ const result = await shell.exec([
1502
+ "/usr/bin/open",
1503
+ "-a",
1504
+ "Finder",
1505
+ path
1506
+ ], {
1507
+ env,
1508
+ inheritEnv: false
1509
+ }, withAbortSignal(AbortSignal.any([signal, AbortSignal.timeout(5e3)]), BACKGROUND_CONTEXT));
1510
+ if (!result.ok || result.value.exitCode !== 0) throw new Error("Finder could not be opened.");
1457
1511
  };
1458
- }
1512
+ return () => {
1513
+ openDirectory = void 0;
1514
+ };
1515
+ });
1516
+ return createGitApi(async (path) => {
1517
+ if (openDirectory === void 0) throw new Error("The host shell is unavailable.");
1518
+ await openDirectory(path);
1519
+ });
1459
1520
  });
1460
1521
  //#endregion
1461
1522
  //#region src/services/gitFailure/index.ts