@agimon-ai/po-git 0.0.0-alpha.9 → 0.0.0-alpha.91

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.
@@ -2,17 +2,20 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_worktreeOperations = require("../worktreeOperations-DGUbUnYU.cjs");
6
- let node_fs = require("node:fs");
7
- node_fs = require_worktreeOperations.__toESM(node_fs, 1);
8
- let node_os = require("node:os");
9
- node_os = require_worktreeOperations.__toESM(node_os, 1);
5
+ const require_worktreeOperations = require("../worktreeOperations-CD7s0YnO.cjs");
6
+ let node_fs_promises = require("node:fs/promises");
7
+ node_fs_promises = require_worktreeOperations.__toESM(node_fs_promises, 1);
10
8
  let node_path = require("node:path");
11
9
  node_path = require_worktreeOperations.__toESM(node_path, 1);
12
- let _agimon_ai_po_core_serverFacet = require("@agimon-ai/po-core/serverFacet");
10
+ let node_os = require("node:os");
11
+ node_os = require_worktreeOperations.__toESM(node_os, 1);
12
+ let _agimon_ai_po_core_serverPlugin = require("@agimon-ai/po-core/serverPlugin");
13
13
  let _agimon_ai_po_core_extensionFile = require("@agimon-ai/po-core/extensionFile");
14
+ let _agimon_ai_po_core_codemode = require("@agimon-ai/po-core/codemode");
14
15
  let _agimon_ai_po_session = require("@agimon-ai/po-session");
15
16
  let typebox = require("typebox");
17
+ let _agimon_ai_po_core_platform = require("@agimon-ai/po-core/platform");
18
+ let _agimon_ai_po_core_packageApi = require("@agimon-ai/po-core/packageApi");
16
19
  //#region src/schemas/runWorktreeTool.ts
17
20
  /**
18
21
  * The tool's action vocabulary.
@@ -254,7 +257,8 @@ async function executeRunWorktreeTool(params, operations, context, options = {})
254
257
  content: text(describe(record, context.sessionId), `Session: ${record.sessionId}`, dirtyFiles.length === 0 ? "Clean." : `${String(dirtyFiles.length)} uncommitted file(s): ${dirtyFiles.slice(0, 20).join(", ")}`),
255
258
  details: {
256
259
  action: params.action,
257
- records: [record]
260
+ records: [record],
261
+ dirtyFiles
258
262
  }
259
263
  };
260
264
  }
@@ -278,7 +282,10 @@ async function executeRunWorktreeTool(params, operations, context, options = {})
278
282
  const waiting = await operations.messages(context, params.id);
279
283
  return {
280
284
  content: waiting.length === 0 ? text("No messages.") : text(`${String(waiting.length)} message(s):`, ...waiting.map((entry) => `[${entry.sentAt}] ${entry.from}: ${entry.text}`)),
281
- details: { action: params.action }
285
+ details: {
286
+ action: params.action,
287
+ messages: waiting
288
+ }
282
289
  };
283
290
  }
284
291
  case WORKTREE_ACTIONS.prune: {
@@ -293,12 +300,172 @@ async function executeRunWorktreeTool(params, operations, context, options = {})
293
300
  `Untracked directories, left alone: ${plan.untracked.length === 0 ? "none" : plan.untracked.join(", ")}`,
294
301
  ...plan.keptBranches.length === 0 ? [] : [`Branches kept, they hold commits: ${plan.keptBranches.join(", ")}`]
295
302
  ]),
296
- details: { action: params.action }
303
+ details: {
304
+ action: params.action,
305
+ plan
306
+ }
297
307
  };
298
308
  }
299
309
  }
300
310
  }
301
311
  //#endregion
312
+ //#region src/services/worktreeNamespace/index.ts
313
+ const DECLARATIONS = `type WorktreeStatus = 'spawning' | 'running' | 'closing' | 'orphaned';
314
+
315
+ interface Worktree {
316
+ id: string;
317
+ branch: string;
318
+ baseRef: string;
319
+ /** Absolute path of the checkout, under ~/.po/git/worktrees. */
320
+ path: string;
321
+ repositoryRoot: string;
322
+ /** The session running inside the worktree. */
323
+ sessionId: string;
324
+ parentSessionId?: string;
325
+ status: WorktreeStatus;
326
+ createdAt: string;
327
+ /** How your session relates to it: 'parent' spawned it, 'child' runs inside it, 'other' is neither. */
328
+ relationship: 'parent' | 'child' | 'other';
329
+ }
330
+
331
+ interface WorktreeMessage {
332
+ worktreeId: string;
333
+ fromSessionId: string;
334
+ from: 'parent' | 'child';
335
+ text: string;
336
+ sentAt: string;
337
+ }
338
+
339
+ interface PrunePlan {
340
+ remove: Worktree[];
341
+ forget: Worktree[];
342
+ /** Kept because they hold uncommitted work. */
343
+ keptDirty: Worktree[];
344
+ untracked: string[];
345
+ /** Branches kept because they hold commits. */
346
+ keptBranches: string[];
347
+ }
348
+
349
+ declare const worktrees: {
350
+ /** Every worktree for this repository. */
351
+ list(): Promise<Worktree[]>;
352
+ /** One worktree and its uncommitted files. */
353
+ status(id: string): Promise<{ worktree: Worktree; dirtyFiles: string[] }>;
354
+ /**
355
+ * Create a worktree on a new branch and start a Po session in it, nested under this one. baseRef defaults to
356
+ * the current branch's remote-tracking ref, then the remote default branch; name labels it in the rail; task is
357
+ * the new session's first message. Setup can be slow, so give exec a larger timeoutMs.
358
+ */
359
+ spawn(request: { branch: string; baseRef?: string; name?: string; task?: string }): Promise<Worktree>;
360
+ /** Stop the worktree's session and delete its checkout. Refused on a dirty tree unless force is true. */
361
+ close(id: string, options?: { force?: boolean }): Promise<Worktree>;
362
+ /** Merge the worktree's branch into this checkout. Refused when this checkout is dirty. */
363
+ merge(id: string, options?: { message?: string }): Promise<Worktree>;
364
+ /** Remove orphaned worktrees. With dryRun, only return the plan. */
365
+ prune(options?: { dryRun?: boolean }): Promise<PrunePlan>;
366
+ /**
367
+ * Message the other side of a worktree. Use the record with relationship 'child' to report to your parent,
368
+ * and 'parent' to reach your own children. 'other' records are not communication edges.
369
+ */
370
+ send(id: string, message: string): Promise<void>;
371
+ /** Messages waiting for you from a worktree. */
372
+ messages(id: string): Promise<WorktreeMessage[]>;
373
+ };`;
374
+ const json = (value) => JSON.parse(JSON.stringify(value));
375
+ /** Git worktrees for code mode: the actions of `run_worktree` as methods, returning records instead of text. */
376
+ function createWorktreeNamespace(options) {
377
+ return {
378
+ name: "worktrees",
379
+ summary: "Git worktrees, each with its own Po session nested under this one: worktrees.list, status, spawn, close, merge, prune, send and messages.",
380
+ declarations: DECLARATIONS,
381
+ bind(run) {
382
+ if (run.sessionId !== options.execution.sessionId) throw new Error("Worktrees namespace belongs to another session.");
383
+ let disposed = false;
384
+ run.onDispose(() => {
385
+ disposed = true;
386
+ });
387
+ const view = (record) => ({
388
+ ...record,
389
+ relationship: record.parentSessionId === options.execution.sessionId ? "parent" : record.sessionId === options.execution.sessionId ? "child" : "other"
390
+ });
391
+ /** One `run_worktree` action through the tool's own code, so the tool and the namespace cannot drift. */
392
+ const act = async (name, action, input) => {
393
+ run.signal.throwIfAborted();
394
+ if (disposed) throw new Error("Worktrees namespace run is disposed.");
395
+ options.assertAvailable();
396
+ const params = validateParams({
397
+ action,
398
+ ...typeof input === "object" && input !== null ? input : {}
399
+ });
400
+ const context = {
401
+ cwd: options.execution.cwd,
402
+ sessionId: options.execution.sessionId
403
+ };
404
+ const started = Date.now();
405
+ try {
406
+ const result = await executeRunWorktreeTool(params, options.operations, context, { signal: run.signal });
407
+ options.assertAvailable();
408
+ run.record?.({
409
+ name: `worktrees.${name}`,
410
+ tool: RUN_WORKTREE_TOOL_NAME,
411
+ args: params,
412
+ ok: true,
413
+ ms: Date.now() - started,
414
+ result
415
+ });
416
+ return result;
417
+ } catch (error) {
418
+ run.record?.({
419
+ name: `worktrees.${name}`,
420
+ tool: RUN_WORKTREE_TOOL_NAME,
421
+ args: params,
422
+ ok: false,
423
+ ms: Date.now() - started,
424
+ error: error instanceof Error ? error.message : String(error)
425
+ });
426
+ throw error;
427
+ }
428
+ };
429
+ const record = (result) => view(result.details.records[0]);
430
+ const members = Object.create(null);
431
+ members.list = async () => json((await act("list", "list", {})).details.records.map(view));
432
+ members.status = async (id) => {
433
+ const { details } = await act("status", "status", { id });
434
+ return json({
435
+ worktree: view(details.records[0]),
436
+ dirtyFiles: details.dirtyFiles ?? []
437
+ });
438
+ };
439
+ members.spawn = async (request) => json(record(await act("spawn", "spawn_worktree", request)));
440
+ members.close = async (id, settings) => json(record(await act("close", "close_worktree", {
441
+ ...settings,
442
+ id
443
+ })));
444
+ members.merge = async (id, settings) => json(record(await act("merge", "merge", {
445
+ ...settings,
446
+ id
447
+ })));
448
+ members.prune = async (settings) => {
449
+ const { plan } = (await act("prune", "prune", settings)).details;
450
+ return json({
451
+ ...plan,
452
+ remove: plan.remove.map(view),
453
+ forget: plan.forget.map(view),
454
+ keptDirty: plan.keptDirty.map(view)
455
+ });
456
+ };
457
+ members.send = async (id, message) => {
458
+ await act("send", "send", {
459
+ id,
460
+ message
461
+ });
462
+ };
463
+ members.messages = async (id) => json((await act("messages", "messages", { id })).details.messages);
464
+ return members;
465
+ }
466
+ };
467
+ }
468
+ //#endregion
302
469
  //#region src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts
303
470
  function progressResult(action, label) {
304
471
  return {
@@ -309,9 +476,9 @@ function progressResult(action, label) {
309
476
  details: { action }
310
477
  };
311
478
  }
312
- function createGitSession({ context, host: serverHost }) {
313
- if (serverHost.context.directEvents === void 0) throw new Error("Git headless facet requires the session direct event bus.");
314
- if (serverHost.context.sessionId === void 0) throw new Error("Git headless facet requires a session identity.");
479
+ function createGitSession({ context, host: serverHost, agent, signal }) {
480
+ if (serverHost.context.directEvents === void 0) throw new Error("Git server plugin requires the session direct event bus.");
481
+ if (serverHost.context.sessionId === void 0) throw new Error("Git server plugin requires a session identity.");
315
482
  const sessionId = serverHost.context.sessionId;
316
483
  const directEvents = serverHost.context.directEvents;
317
484
  const messageInbox = require_worktreeOperations.createWorktreeMessageInbox(directEvents, sessionId);
@@ -322,20 +489,12 @@ function createGitSession({ context, host: serverHost }) {
322
489
  sessionDelivery: () => (0, _agimon_ai_po_session.readPoSessionDelivery)(context)
323
490
  });
324
491
  const resources = [{
325
- when: {
326
- state: { "minor-mode": "help" },
327
- attribution: {
328
- kind: "minor",
329
- mode: "help"
330
- }
331
- },
332
- name: "po-git",
333
- kind: "context",
334
- read: () => (0, _agimon_ai_po_core_serverFacet.readPackageResource)(require("url").pathToFileURL(__filename).href, "llms.txt")
335
- }, {
336
- name: "po-use-git",
492
+ name: "codemode-how-to-git",
493
+ description: "Hand a branch of work to a separate session in its own checkout, keeping the current checkout untouched. Use for a long refactor, a risky migration, or parallel features that touch the same files; not for parallel work on one branch.",
337
494
  kind: "skill",
338
- read: () => (0, _agimon_ai_po_core_serverFacet.readPackageResource)(require("url").pathToFileURL(__filename).href, "src/prompts/po-use-git/SKILL.md")
495
+ pinned: true,
496
+ path: (0, _agimon_ai_po_core_serverPlugin.packageResourcePath)(require("url").pathToFileURL(__filename).href, "src/prompts/codemode-how-to-git/SKILL.md"),
497
+ read: () => (0, _agimon_ai_po_core_serverPlugin.readPackageResource)(require("url").pathToFileURL(__filename).href, "src/prompts/codemode-how-to-git/SKILL.md")
339
498
  }];
340
499
  const tool = {
341
500
  name: RUN_WORKTREE_TOOL_NAME,
@@ -355,17 +514,37 @@ function createGitSession({ context, host: serverHost }) {
355
514
  const changed = (_event, execution) => {
356
515
  directEvents.publish(require_worktreeOperations.GIT_SESSION_CHANGED_EVENT, execution.sessionId, { version: 1 });
357
516
  };
517
+ const hooks = [{
518
+ event: "tool_execution_end",
519
+ handle: changed
520
+ }, {
521
+ event: "agent_settled",
522
+ handle: changed
523
+ }];
524
+ if (agent) {
525
+ const namespace = createWorktreeNamespace({
526
+ operations,
527
+ execution: agent.context,
528
+ assertAvailable: () => {
529
+ if (signal?.aborted) throw new Error("Git session is unavailable.");
530
+ }
531
+ });
532
+ context.inject([_agimon_ai_po_core_codemode.PO_CODEMODE_SERVICE], (scoped) => {
533
+ const codemode = (0, _agimon_ai_po_core_codemode.readPoCodemode)(scoped);
534
+ if (!codemode) return void 0;
535
+ const registration = codemode.register("@agimon-ai/po-git", namespace);
536
+ const restriction = agent.registerToolRestriction({ excludedTools: [RUN_WORKTREE_TOOL_NAME] });
537
+ return () => {
538
+ restriction.dispose();
539
+ registration.dispose();
540
+ };
541
+ });
542
+ }
358
543
  return {
359
544
  value: {
360
545
  resources,
361
546
  tool,
362
- hooks: [{
363
- event: "tool_execution_end",
364
- handle: changed
365
- }, {
366
- event: "agent_settled",
367
- handle: changed
368
- }]
547
+ hooks
369
548
  },
370
549
  onDispose: () => messageInbox.close()
371
550
  };
@@ -476,11 +655,11 @@ const NAME_STATUS = {
476
655
  T: "modified"
477
656
  };
478
657
  /** Line count of a small regular file, or undefined when it is binary, large, or not a regular file. */
479
- function untrackedLines(root, relPath) {
658
+ async function untrackedLines(root, relPath) {
480
659
  const absolute = node_path.default.join(root, relPath);
481
660
  let stat;
482
661
  try {
483
- stat = node_fs.default.lstatSync(absolute);
662
+ stat = await node_fs_promises.default.lstat(absolute);
484
663
  } catch {
485
664
  return;
486
665
  }
@@ -489,7 +668,7 @@ function untrackedLines(root, relPath) {
489
668
  binary: false
490
669
  };
491
670
  if (!stat.isFile() || stat.size > MAX_FILE_BYTES) return void 0;
492
- const content = node_fs.default.readFileSync(absolute);
671
+ const content = await node_fs_promises.default.readFile(absolute);
493
672
  if (content.subarray(0, BINARY_PROBE_BYTES).includes(0)) return {
494
673
  lines: 0,
495
674
  binary: true
@@ -612,8 +791,8 @@ function createBranchDiff() {
612
791
  "--exclude-standard",
613
792
  "-z"
614
793
  ]));
615
- untracked.forEach((relPath, index) => {
616
- const counted = index < MAX_UNTRACKED_COUNTED ? untrackedLines(root, relPath) : void 0;
794
+ for (const [index, relPath] of untracked.entries()) {
795
+ const counted = index < MAX_UNTRACKED_COUNTED ? await untrackedLines(root, relPath) : void 0;
617
796
  entries.push({
618
797
  path: relPath,
619
798
  status: "untracked",
@@ -621,18 +800,20 @@ function createBranchDiff() {
621
800
  removed: 0,
622
801
  ...counted?.binary ? { binary: true } : {}
623
802
  });
624
- });
803
+ }
625
804
  entries.sort((left, right) => left.path.localeCompare(right.path));
626
805
  const conflicts = [...unmerged];
627
- const rebasing = [await readLine$1(root, [
806
+ const rebaseMerge = await readLine$1(root, [
628
807
  "rev-parse",
629
808
  "--git-path",
630
809
  "rebase-merge"
631
- ]), await readLine$1(root, [
810
+ ]);
811
+ const rebaseApply = await readLine$1(root, [
632
812
  "rev-parse",
633
813
  "--git-path",
634
814
  "rebase-apply"
635
- ])].some((gitPath) => gitPath !== void 0 && node_fs.default.existsSync(node_path.default.resolve(root, gitPath)));
815
+ ]);
816
+ const rebasing = (await Promise.all([rebaseMerge, rebaseApply].map(async (gitPath) => gitPath === void 0 ? false : node_fs_promises.default.access(node_path.default.resolve(root, gitPath)).then(() => true, () => false)))).some(Boolean);
636
817
  const upstreamRef = branch === void 0 ? void 0 : await upstreamOf(root, branch);
637
818
  let upstream;
638
819
  if (upstreamRef !== void 0) {
@@ -677,7 +858,7 @@ function createBranchDiff() {
677
858
  if (entry === void 0) throw new Error("That path is not in this review.");
678
859
  if (entry.status === "untracked") {
679
860
  const absolute = node_path.default.join(review.root, entry.path);
680
- const stat = node_fs.default.lstatSync(absolute);
861
+ const stat = await node_fs_promises.default.lstat(absolute);
681
862
  if (stat.isSymbolicLink()) return {
682
863
  path: entry.path,
683
864
  hunks: [{
@@ -685,7 +866,7 @@ function createBranchDiff() {
685
866
  rows: [{
686
867
  marker: "+",
687
868
  line: 1,
688
- content: node_fs.default.readlinkSync(absolute)
869
+ content: await node_fs_promises.default.readlink(absolute)
689
870
  }]
690
871
  }]
691
872
  };
@@ -694,7 +875,7 @@ function createBranchDiff() {
694
875
  hunks: [],
695
876
  tooLarge: true
696
877
  };
697
- const content = node_fs.default.readFileSync(absolute);
878
+ const content = await node_fs_promises.default.readFile(absolute);
698
879
  if (content.subarray(0, BINARY_PROBE_BYTES).includes(0)) return {
699
880
  path: entry.path,
700
881
  hunks: [],
@@ -811,9 +992,9 @@ function isRecord(value) {
811
992
  return typeof value === "object" && value !== null && !Array.isArray(value);
812
993
  }
813
994
  /** The workspace key: the canonical root, so a symlinked path and its target share one entry. */
814
- function workspaceKey(workspaceRoot) {
995
+ async function workspaceKey(workspaceRoot) {
815
996
  try {
816
- return node_fs.default.realpathSync(workspaceRoot);
997
+ return await node_fs_promises.default.realpath(workspaceRoot);
817
998
  } catch {
818
999
  return node_path.default.resolve(workspaceRoot);
819
1000
  }
@@ -862,7 +1043,7 @@ function publicView(config) {
862
1043
  * the saved one, and only for the same host: a token never follows a host it
863
1044
  * was not entered for.
864
1045
  */
865
- function parseGitAuthSave(request, current, homeDir) {
1046
+ async function parseGitAuthSave(request, current, homeDir) {
866
1047
  if (!isRecord(request)) throw require_worktreeOperations.invalidRequest("The git auth request is not an object.", "Send method none, ssh or https.");
867
1048
  if (request.method === "none") return { method: "none" };
868
1049
  if (request.method === "ssh") {
@@ -872,7 +1053,7 @@ function parseGitAuthSave(request, current, homeDir) {
872
1053
  if (!keyPath.startsWith("/") && !keyPath.startsWith("~/")) throw require_worktreeOperations.invalidRequest("The key path is not absolute.", "Give the key as an absolute path or one under ~/.");
873
1054
  let isFile = false;
874
1055
  try {
875
- isFile = node_fs.default.statSync(expandHome(keyPath, homeDir)).isFile();
1056
+ isFile = (await node_fs_promises.default.stat(expandHome(keyPath, homeDir))).isFile();
876
1057
  } catch {
877
1058
  isFile = false;
878
1059
  }
@@ -907,16 +1088,16 @@ function parseGitAuthSave(request, current, homeDir) {
907
1088
  throw require_worktreeOperations.invalidRequest("The git auth method is unknown.", "Send method none, ssh or https.");
908
1089
  }
909
1090
  /** Tightens a file or folder something left readable by group or others. */
910
- function tighten(target, mode) {
1091
+ async function tighten(target, mode) {
911
1092
  try {
912
- if ((node_fs.default.statSync(target).mode & LOOSE_BITS) !== 0) node_fs.default.chmodSync(target, mode);
1093
+ if (((await node_fs_promises.default.stat(target)).mode & LOOSE_BITS) !== 0) await node_fs_promises.default.chmod(target, mode);
913
1094
  } catch {}
914
1095
  }
915
1096
  function createGitAuthStore(homeDir = node_os.default.homedir()) {
916
1097
  const file = require_worktreeOperations.credentialsFile(homeDir);
917
- const readFile = () => {
918
- tighten(file, OWNER_ONLY_FILE);
919
- const raw = require_worktreeOperations.readJson(file);
1098
+ const readFile = async () => {
1099
+ await tighten(file, OWNER_ONLY_FILE);
1100
+ const raw = await require_worktreeOperations.readJson(file);
920
1101
  const workspaces = {};
921
1102
  if (isRecord(raw) && raw.version === 1 && isRecord(raw.workspaces)) for (const [key, value] of Object.entries(raw.workspaces)) {
922
1103
  const config = parseConfig(value);
@@ -927,28 +1108,28 @@ function createGitAuthStore(homeDir = node_os.default.homedir()) {
927
1108
  workspaces
928
1109
  };
929
1110
  };
930
- const read = (workspaceRoot) => readFile().workspaces[workspaceKey(workspaceRoot)] ?? { method: "none" };
1111
+ const read = async (workspaceRoot) => (await readFile()).workspaces[await workspaceKey(workspaceRoot)] ?? { method: "none" };
931
1112
  return {
932
1113
  read,
933
- view: (workspaceRoot) => publicView(read(workspaceRoot)),
934
- save(workspaceRoot, request) {
935
- const key = workspaceKey(workspaceRoot);
936
- const current = readFile();
937
- const next = parseGitAuthSave(request, current.workspaces[key] ?? { method: "none" }, homeDir);
1114
+ view: async (workspaceRoot) => publicView(await read(workspaceRoot)),
1115
+ async save(workspaceRoot, request) {
1116
+ const key = await workspaceKey(workspaceRoot);
1117
+ const current = await readFile();
1118
+ const next = await parseGitAuthSave(request, current.workspaces[key] ?? { method: "none" }, homeDir);
938
1119
  const root = require_worktreeOperations.poGitRoot(homeDir);
939
- node_fs.default.mkdirSync(root, {
1120
+ await node_fs_promises.default.mkdir(root, {
940
1121
  recursive: true,
941
1122
  mode: OWNER_ONLY_DIRECTORY
942
1123
  });
943
- node_fs.default.chmodSync(root, OWNER_ONLY_DIRECTORY);
1124
+ await node_fs_promises.default.chmod(root, OWNER_ONLY_DIRECTORY);
944
1125
  const workspaces = { ...current.workspaces };
945
1126
  if (next.method === "none") delete workspaces[key];
946
1127
  else workspaces[key] = next;
947
- require_worktreeOperations.writeJsonAtomic(file, {
1128
+ await require_worktreeOperations.writeJsonAtomic(file, {
948
1129
  version: 1,
949
1130
  workspaces
950
1131
  });
951
- node_fs.default.chmodSync(file, OWNER_ONLY_FILE);
1132
+ await node_fs_promises.default.chmod(file, OWNER_ONLY_FILE);
952
1133
  return publicView(next);
953
1134
  }
954
1135
  };
@@ -1113,177 +1294,236 @@ function failed(error) {
1113
1294
  }, { status: error.retryable ? 503 : 409 });
1114
1295
  return Response.json({ error: "The worktree operation failed." }, { status: 500 });
1115
1296
  }
1116
- //#endregion
1117
- //#region src/extensions/(backend)/api/git/route.server.ts
1118
- var route_server_default = (0, _agimon_ai_po_core_extensionFile.defineRoute)({
1119
- basePath: "git",
1120
- start(context) {
1121
- if (context.directEvents === void 0) throw new Error("Git hub API requires the direct event bus.");
1122
- const directEvents = context.directEvents;
1123
- const publishLifecycle = (sessionId, repositoryRoot) => {
1124
- directEvents.publish(require_worktreeOperations.GIT_WORKTREE_LIFECYCLE_EVENT, sessionId, {
1125
- version: 1,
1126
- repositoryRoot
1297
+ function createGitApi(openDirectory) {
1298
+ return {
1299
+ basePath: "git",
1300
+ start(context) {
1301
+ if (context.directEvents === void 0) throw new Error("Git hub API requires the direct event bus.");
1302
+ const directEvents = context.directEvents;
1303
+ const publishLifecycle = (sessionId, repositoryRoot) => {
1304
+ directEvents.publish(require_worktreeOperations.GIT_WORKTREE_LIFECYCLE_EVENT, sessionId, {
1305
+ version: 1,
1306
+ repositoryRoot
1307
+ });
1308
+ };
1309
+ const git = require_worktreeOperations.createWorktreeGit();
1310
+ const operations = require_worktreeOperations.createWorktreeOperations({
1311
+ git,
1312
+ sessionService: context.sessionService,
1313
+ ...context.homeDirectory === void 0 ? {} : { homeDir: context.homeDirectory }
1127
1314
  });
1128
- };
1129
- const git = require_worktreeOperations.createWorktreeGit();
1130
- const operations = require_worktreeOperations.createWorktreeOperations({
1131
- git,
1132
- sessionService: context.sessionService,
1133
- ...context.homeDirectory === void 0 ? {} : { homeDir: context.homeDirectory }
1134
- });
1135
- /**
1136
- * The new-session dialog's routes: the workspace's branches, and a top-level
1137
- * worktree session on one. Workspace-mounted only, and always rooted at the
1138
- * mount's own workspace, so a request can never name another directory.
1139
- */
1140
- const newSession = async (request, url) => {
1141
- const isBranches = request.method === "GET" && url.pathname === "/branches";
1142
- const isCreate = request.method === "POST" && url.pathname === "/sessions";
1143
- if (!isBranches && !isCreate) return void 0;
1144
- const workspaceRoot = context.scope === "workspace" ? context.workspaceRoot : void 0;
1145
- if (workspaceRoot === void 0) return Response.json({ error: "This route belongs to a workspace." }, { status: 404 });
1146
- if (isBranches) {
1147
- const repositoryRoot = await git.repositoryRoot(workspaceRoot);
1148
- const branches = repositoryRoot === void 0 ? {
1149
- repository: false,
1150
- local: [],
1151
- remote: []
1152
- } : {
1153
- repository: true,
1154
- ...await git.listBranches(repositoryRoot)
1315
+ /**
1316
+ * The new-session dialog's routes: the workspace's branches, and a top-level
1317
+ * worktree session on one. Workspace-mounted only, and always rooted at the
1318
+ * mount's own workspace, so a request can never name another directory.
1319
+ */
1320
+ const newSession = async (request, url) => {
1321
+ const isBranches = request.method === "GET" && url.pathname === "/branches";
1322
+ const isCreate = request.method === "POST" && url.pathname === "/sessions";
1323
+ const isDelete = request.method === "DELETE" && url.pathname === "/branches";
1324
+ const isOpen = request.method === "POST" && url.pathname === `/branches/open`;
1325
+ if (!isBranches && !isCreate && !isDelete && !isOpen) return void 0;
1326
+ const workspaceRoot = context.scope === "workspace" ? context.workspaceRoot : void 0;
1327
+ if (workspaceRoot === void 0) return Response.json({ error: "This route belongs to a workspace." }, { status: 404 });
1328
+ if (isBranches) {
1329
+ const repositoryRoot = await git.repositoryRoot(workspaceRoot);
1330
+ const branches = repositoryRoot === void 0 ? {
1331
+ repository: false,
1332
+ local: [],
1333
+ remote: []
1334
+ } : {
1335
+ repository: true,
1336
+ ...await git.listBranches(repositoryRoot)
1337
+ };
1338
+ return Response.json(branches);
1339
+ }
1340
+ if (isDelete || isOpen) {
1341
+ if (isOpen && (0, _agimon_ai_po_core_packageApi.poApiCallerFrom)(request.headers)?.locality !== "local") return Response.json({ error: "Opening Finder is only available on the host." }, { status: 403 });
1342
+ const body = await request.json().catch(() => void 0);
1343
+ const branch = typeof body === "object" && body !== null ? body.branch : void 0;
1344
+ if (typeof branch !== "string" || !require_worktreeOperations.validRefName(branch)) return badRequest("A valid branch name is required.");
1345
+ const repositoryRoot = await git.repositoryRoot(workspaceRoot);
1346
+ if (repositoryRoot === void 0) return badRequest("This workspace is not a git checkout.");
1347
+ const branches = await git.listBranches(repositoryRoot);
1348
+ const local = branches.local.find((item) => item.name === branch);
1349
+ if (local === void 0) return Response.json({ error: "That local branch no longer exists." }, { status: 404 });
1350
+ if (isOpen) {
1351
+ if (local.checkedOutAt === void 0) return Response.json({ error: "This branch has no checked-out folder." }, { status: 409 });
1352
+ if (openDirectory === void 0) return Response.json({ error: "Finder is not available on this host." }, { status: 409 });
1353
+ try {
1354
+ await openDirectory(local.checkedOutAt);
1355
+ return Response.json({ opened: true });
1356
+ } catch {
1357
+ return Response.json({ error: "The branch folder could not be opened in Finder." }, { status: 409 });
1358
+ }
1359
+ }
1360
+ if (local.checkedOutAt !== void 0 || branches.current === branch) return Response.json({ error: "Close this branch’s worktree before deleting it." }, { status: 409 });
1361
+ if (!await git.deleteBranch({
1362
+ repositoryRoot,
1363
+ branch
1364
+ })) return Response.json({ error: "Git refused to delete this branch. It may contain unmerged commits or be checked out." }, { status: 409 });
1365
+ return Response.json({ deleted: true });
1366
+ }
1367
+ const parsed = newSessionRequest(await request.json().catch(() => void 0));
1368
+ if ("error" in parsed) return badRequest(parsed.error);
1369
+ const record = await operations.spawn({ cwd: workspaceRoot }, {
1370
+ branch: parsed.branch,
1371
+ checkout: parsed.mode === "existing-branch" ? "existing" : "new",
1372
+ ...parsed.mode === "existing-branch" && parsed.remote !== void 0 ? { remote: parsed.remote } : {},
1373
+ ...parsed.mode === "new-branch" && parsed.baseRef !== void 0 ? { baseRef: parsed.baseRef } : {},
1374
+ ...parsed.name === void 0 ? {} : { name: parsed.name }
1375
+ });
1376
+ publishLifecycle(record.sessionId, record.repositoryRoot);
1377
+ const created = {
1378
+ sessionId: record.sessionId,
1379
+ worktreeId: record.id
1155
1380
  };
1156
- return Response.json(branches);
1157
- }
1158
- const parsed = newSessionRequest(await request.json().catch(() => void 0));
1159
- if ("error" in parsed) return badRequest(parsed.error);
1160
- const record = await operations.spawn({ cwd: workspaceRoot }, {
1161
- branch: parsed.branch,
1162
- checkout: parsed.mode === "existing-branch" ? "existing" : "new",
1163
- ...parsed.mode === "existing-branch" && parsed.remote !== void 0 ? { remote: parsed.remote } : {},
1164
- ...parsed.mode === "new-branch" && parsed.baseRef !== void 0 ? { baseRef: parsed.baseRef } : {},
1165
- ...parsed.name === void 0 ? {} : { name: parsed.name }
1166
- });
1167
- publishLifecycle(record.sessionId, record.repositoryRoot);
1168
- const created = {
1169
- sessionId: record.sessionId,
1170
- worktreeId: record.id
1381
+ return Response.json(created, { status: 201 });
1171
1382
  };
1172
- return Response.json(created, { status: 201 });
1173
- };
1174
- /** The admitted root for a request, or a response explaining why not. */
1175
- const rootOf = (url) => {
1176
- const repositoryId = url.searchParams.get("repositoryId");
1177
- if (repositoryId === null || repositoryId === "") return badRequest("A repositoryId is required.");
1178
- const root = context.resolveRepository?.(repositoryId);
1179
- if (root === void 0) return badRequest("That repository is not admitted to this cockpit.");
1180
- return root;
1181
- };
1182
- const homeDir = context.homeDirectory ?? node_os.default.homedir();
1183
- const gitAuth = createGitAuthStore(homeDir);
1184
- const branchDiff = createBranchDiff();
1185
- const pendingReviews = /* @__PURE__ */ new Map();
1186
- /**
1187
- * A workspace's remote auth. Workspace mounts only, so the step-up gate on
1188
- * `PUT /api/workspaces/<id>/plugins/git/auth` is the one way to write it.
1189
- * The view never carries the token.
1190
- */
1191
- const auth = async (request, url) => {
1192
- if (url.pathname !== "/auth") return void 0;
1193
- if (context.scope !== "workspace") return Response.json({ error: "This route belongs to a workspace." }, { status: 404 });
1194
- if (request.method !== "GET" && request.method !== "PUT") return Response.json({ error: "Use GET or PUT." }, { status: 405 });
1195
- const root = rootOf(url);
1196
- if (root instanceof Response) return root;
1197
- if (request.method === "GET") return Response.json(gitAuth.view(root));
1198
- try {
1199
- return Response.json(gitAuth.save(root, await request.json().catch(() => void 0)));
1200
- } catch (error) {
1201
- if (error instanceof require_worktreeOperations.PoGitExpectedError && error.code === "invalid_request") {
1202
- const text = error.message.replace(/^\[[a-z_]+\] /u, "").replace("\nRecovery: ", " ");
1203
- return Response.json({
1204
- error: text,
1205
- code: error.code
1206
- }, { status: 400 });
1207
- }
1208
- throw error;
1209
- }
1210
- };
1211
- /**
1212
- * The session's change against its base. Session mounts only, rooted at the
1213
- * session's own checkout; a file is served only when it is in the change set
1214
- * computed for the request. Overlapping requests share an in-flight snapshot.
1215
- */
1216
- const review = async (request, url) => {
1217
- if (url.pathname !== "/review" && url.pathname !== "/review/file") return void 0;
1218
- const cwd = context.scope === "session" ? context.cwd : void 0;
1219
- if (cwd === void 0) return Response.json({ error: "This route belongs to a session." }, { status: 404 });
1220
- if (request.method !== "GET") return Response.json({ error: "Use GET." }, { status: 405 });
1221
- let recordedBaseRef;
1222
- try {
1223
- recordedBaseRef = require_worktreeOperations.createWorktreeRegistry(require_worktreeOperations.registryFile(cwd, homeDir)).list().find((record) => record.sessionId === context.sessionId)?.baseRef;
1224
- } catch {
1225
- recordedBaseRef = void 0;
1226
- }
1227
- const key = JSON.stringify([cwd, recordedBaseRef ?? null]);
1228
- let pending = pendingReviews.get(key);
1229
- if (pending === void 0) {
1230
- pending = branchDiff.review(cwd, recordedBaseRef === void 0 ? {} : { recordedBaseRef }).finally(() => pendingReviews.delete(key));
1231
- pendingReviews.set(key, pending);
1232
- }
1233
- const result = await pending;
1234
- if (url.pathname === "/review") return Response.json(result?.summary ?? {
1235
- repository: false,
1236
- files: []
1237
- });
1238
- const filePath = url.searchParams.get("path") ?? "";
1239
- 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 });
1240
- return Response.json(await branchDiff.fileDiff(result, filePath));
1241
- };
1242
- return {
1243
- async fetch(request) {
1244
- const url = new URL(request.url);
1383
+ /** The admitted root for a request, or a response explaining why not. */
1384
+ const rootOf = (url) => {
1385
+ const repositoryId = url.searchParams.get("repositoryId");
1386
+ if (repositoryId === null || repositoryId === "") return badRequest("A repositoryId is required.");
1387
+ const root = context.resolveRepository?.(repositoryId);
1388
+ if (root === void 0) return badRequest("That repository is not admitted to this cockpit.");
1389
+ return root;
1390
+ };
1391
+ const homeDir = context.homeDirectory ?? context.environment?.HOME;
1392
+ const gitAuth = homeDir === void 0 ? void 0 : createGitAuthStore(homeDir);
1393
+ const branchDiff = createBranchDiff();
1394
+ const pendingReviews = /* @__PURE__ */ new Map();
1395
+ /**
1396
+ * A workspace's remote auth. Workspace mounts only, so the step-up gate on
1397
+ * `PUT /api/workspaces/<id>/plugins/git/auth` is the one way to write it.
1398
+ * The view never carries the token.
1399
+ */
1400
+ const auth = async (request, url) => {
1401
+ if (url.pathname !== "/auth") return void 0;
1402
+ if (context.scope !== "workspace") return Response.json({ error: "This route belongs to a workspace." }, { status: 404 });
1403
+ if (request.method !== "GET" && request.method !== "PUT") return Response.json({ error: "Use GET or PUT." }, { status: 405 });
1404
+ if (gitAuth === void 0) throw new Error("Git auth requires the admitted host home directory.");
1405
+ const root = rootOf(url);
1406
+ if (root instanceof Response) return root;
1407
+ if (request.method === "GET") return Response.json(await gitAuth.view(root));
1245
1408
  try {
1246
- const answered = await newSession(request, url) ?? await auth(request, url) ?? await review(request, url);
1247
- if (answered !== void 0) return answered;
1409
+ return Response.json(await gitAuth.save(root, await request.json().catch(() => void 0)));
1248
1410
  } catch (error) {
1249
- context.onNotice(`git request failed: ${error.name}`);
1250
- return failed(error);
1411
+ if (error instanceof require_worktreeOperations.PoGitExpectedError && error.code === "invalid_request") {
1412
+ const text = error.message.replace(/^\[[a-z_]+\] /u, "").replace("\nRecovery: ", " ");
1413
+ return Response.json({
1414
+ error: text,
1415
+ code: error.code
1416
+ }, { status: 400 });
1417
+ }
1418
+ throw error;
1251
1419
  }
1252
- const root = rootOf(url);
1253
- if (root instanceof Response) return root;
1254
- const callerContext = {
1255
- cwd: root,
1256
- sessionId: url.searchParams.get("sessionId") ?? ""
1257
- };
1258
- 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.");
1420
+ };
1421
+ /**
1422
+ * The session's change against its base. Session mounts only, rooted at the
1423
+ * session's own checkout; a file is served only when it is in the change set
1424
+ * computed for the request. Overlapping requests share an in-flight snapshot.
1425
+ */
1426
+ const review = async (request, url) => {
1427
+ if (url.pathname !== "/review" && url.pathname !== "/review/file") return void 0;
1428
+ const cwd = context.scope === "session" ? context.cwd : void 0;
1429
+ if (cwd === void 0) return Response.json({ error: "This route belongs to a session." }, { status: 404 });
1430
+ if (request.method !== "GET") return Response.json({ error: "Use GET." }, { status: 405 });
1431
+ if (homeDir === void 0) throw new Error("Git review requires the admitted host home directory.");
1432
+ let recordedBaseRef;
1259
1433
  try {
1260
- if (request.method === "GET" && url.pathname === "/worktrees") return Response.json({ worktrees: await operations.list(callerContext) });
1261
- if (request.method === "POST" && url.pathname === "/worktrees") {
1262
- const body = await request.json();
1263
- if (typeof body.branch !== "string" || body.branch.trim() === "") return badRequest("A branch is required.");
1264
- const record = await operations.spawn(callerContext, {
1265
- branch: body.branch,
1266
- ...body.baseRef === void 0 ? {} : { baseRef: body.baseRef },
1267
- ...body.name === void 0 ? {} : { name: body.name }
1268
- });
1269
- publishLifecycle(callerContext.sessionId, record.repositoryRoot);
1270
- return Response.json({ worktree: record }, { status: 201 });
1434
+ recordedBaseRef = (await require_worktreeOperations.createWorktreeRegistry(await require_worktreeOperations.registryFile(cwd, homeDir)).list()).find((record) => record.sessionId === context.sessionId)?.baseRef;
1435
+ } catch {
1436
+ recordedBaseRef = void 0;
1437
+ }
1438
+ const key = JSON.stringify([cwd, recordedBaseRef ?? null]);
1439
+ let pending = pendingReviews.get(key);
1440
+ if (pending === void 0) {
1441
+ pending = branchDiff.review(cwd, recordedBaseRef === void 0 ? {} : { recordedBaseRef }).finally(() => pendingReviews.delete(key));
1442
+ pendingReviews.set(key, pending);
1443
+ }
1444
+ const result = await pending;
1445
+ if (url.pathname === "/review") return Response.json(result?.summary ?? {
1446
+ repository: false,
1447
+ files: []
1448
+ });
1449
+ const filePath = url.searchParams.get("path") ?? "";
1450
+ 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 });
1451
+ return Response.json(await branchDiff.fileDiff(result, filePath));
1452
+ };
1453
+ return {
1454
+ async fetch(request) {
1455
+ const url = new URL(request.url);
1456
+ try {
1457
+ const answered = await newSession(request, url) ?? await auth(request, url) ?? await review(request, url);
1458
+ if (answered !== void 0) return answered;
1459
+ } catch (error) {
1460
+ context.onNotice(`git request failed: ${error.name}`);
1461
+ return failed(error);
1271
1462
  }
1272
- const closing = /^\/worktrees\/([^/]+)$/u.exec(url.pathname);
1273
- if (request.method === "DELETE" && closing) {
1274
- const record = await operations.close(callerContext, decodeURIComponent(String(closing[1])), url.searchParams.get("force") === "true");
1275
- publishLifecycle(callerContext.sessionId, record.repositoryRoot);
1276
- return Response.json({ worktree: record });
1463
+ const root = rootOf(url);
1464
+ if (root instanceof Response) return root;
1465
+ const callerContext = {
1466
+ cwd: root,
1467
+ sessionId: url.searchParams.get("sessionId") ?? ""
1468
+ };
1469
+ 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.");
1470
+ try {
1471
+ if (request.method === "GET" && url.pathname === "/worktrees") return Response.json({ worktrees: await operations.list(callerContext) });
1472
+ if (request.method === "POST" && url.pathname === "/worktrees") {
1473
+ const body = await request.json();
1474
+ if (typeof body.branch !== "string" || body.branch.trim() === "") return badRequest("A branch is required.");
1475
+ const record = await operations.spawn(callerContext, {
1476
+ branch: body.branch,
1477
+ ...body.baseRef === void 0 ? {} : { baseRef: body.baseRef },
1478
+ ...body.name === void 0 ? {} : { name: body.name }
1479
+ });
1480
+ publishLifecycle(callerContext.sessionId, record.repositoryRoot);
1481
+ return Response.json({ worktree: record }, { status: 201 });
1482
+ }
1483
+ const closing = /^\/worktrees\/([^/]+)$/u.exec(url.pathname);
1484
+ if (request.method === "DELETE" && closing) {
1485
+ const record = await operations.close(callerContext, decodeURIComponent(String(closing[1])), url.searchParams.get("force") === "true");
1486
+ publishLifecycle(callerContext.sessionId, record.repositoryRoot);
1487
+ return Response.json({ worktree: record });
1488
+ }
1489
+ return Response.json({ error: "No such worktree route." }, { status: 404 });
1490
+ } catch (error) {
1491
+ context.onNotice(`worktree request failed: ${error.name}`);
1492
+ return failed(error);
1277
1493
  }
1278
- return Response.json({ error: "No such worktree route." }, { status: 404 });
1279
- } catch (error) {
1280
- context.onNotice(`worktree request failed: ${error.name}`);
1281
- return failed(error);
1282
- }
1283
- },
1284
- close() {}
1494
+ },
1495
+ close() {}
1496
+ };
1497
+ }
1498
+ };
1499
+ }
1500
+ //#endregion
1501
+ //#region src/extensions/(backend)/api/git/route.server.ts
1502
+ var route_server_default = (0, _agimon_ai_po_core_extensionFile.defineRoute)(({ context, host, signal }) => {
1503
+ let openDirectory;
1504
+ context.inject([_agimon_ai_po_core_platform.PO_SHELL_SERVICE], (child) => {
1505
+ const shell = (0, _agimon_ai_po_core_platform.requirePoShell)(child);
1506
+ const env = Object.fromEntries(Object.entries(host.context.environment ?? {}).filter((entry) => entry[1] !== void 0));
1507
+ openDirectory = async (path) => {
1508
+ const result = await shell.exec([
1509
+ "/usr/bin/open",
1510
+ "-a",
1511
+ "Finder",
1512
+ path
1513
+ ], {
1514
+ env,
1515
+ inheritEnv: false
1516
+ }, (0, _agimon_ai_po_core_platform.withAbortSignal)(AbortSignal.any([signal, AbortSignal.timeout(5e3)]), _agimon_ai_po_core_platform.BACKGROUND_CONTEXT));
1517
+ if (!result.ok || result.value.exitCode !== 0) throw new Error("Finder could not be opened.");
1285
1518
  };
1286
- }
1519
+ return () => {
1520
+ openDirectory = void 0;
1521
+ };
1522
+ });
1523
+ return createGitApi(async (path) => {
1524
+ if (openDirectory === void 0) throw new Error("The host shell is unavailable.");
1525
+ await openDirectory(path);
1526
+ });
1287
1527
  });
1288
1528
  //#endregion
1289
1529
  //#region src/services/gitFailure/index.ts
@@ -1420,7 +1660,7 @@ async function rebasing(root) {
1420
1660
  "--git-path",
1421
1661
  name
1422
1662
  ]);
1423
- if (gitPath !== void 0 && node_fs.default.existsSync(node_path.default.resolve(root, gitPath))) return true;
1663
+ if (gitPath !== void 0 && await node_fs_promises.default.access(node_path.default.resolve(root, gitPath)).then(() => true, () => false)) return true;
1424
1664
  }
1425
1665
  return false;
1426
1666
  }
@@ -1590,9 +1830,9 @@ const GIT_CHANGES_TYPE = "git_changes";
1590
1830
  * DESIGN PATTERNS:
1591
1831
  * - Apart from the worktrees channel. Worktrees are repository management;
1592
1832
  * this is the session's own code change.
1593
- * - Push, not poll. The session facet publishes a "changed" event after every
1594
- * tool call and settled turn; recounts are coalesced to one in flight plus
1595
- * one rerun, so a burst of tool calls costs at most two.
1833
+ * - Push, not poll. The session's server plugin publishes a "changed" event
1834
+ * after every tool call and settled turn; recounts are coalesced to one in
1835
+ * flight plus one rerun, so a burst of tool calls costs at most two.
1596
1836
  * - One sync command per session. A rebase and a push must never overlap.
1597
1837
  * - Auth is read fresh for each command from the session's workspace, and goes
1598
1838
  * only into that command's git child.
@@ -1653,9 +1893,9 @@ function createGitChangesChannel(options = {}) {
1653
1893
  const rerun = /* @__PURE__ */ new Set();
1654
1894
  const busy = /* @__PURE__ */ new Set();
1655
1895
  /** The base the session's own worktree was created from, when it has one. */
1656
- const recordedBaseRef = (scope) => {
1896
+ const recordedBaseRef = async (scope) => {
1657
1897
  try {
1658
- return require_worktreeOperations.createWorktreeRegistry(require_worktreeOperations.registryFile(scope.cwd, homeDir)).list().find((record) => record.sessionId === scope.sessionId)?.baseRef;
1898
+ return (await require_worktreeOperations.createWorktreeRegistry(await require_worktreeOperations.registryFile(scope.cwd, homeDir)).list()).find((record) => record.sessionId === scope.sessionId)?.baseRef;
1659
1899
  } catch {
1660
1900
  return;
1661
1901
  }
@@ -1688,8 +1928,7 @@ function createGitChangesChannel(options = {}) {
1688
1928
  return;
1689
1929
  }
1690
1930
  inFlight.add(sessionId);
1691
- const base = recordedBaseRef(scope);
1692
- diff.review(scope.cwd, base === void 0 ? {} : { recordedBaseRef: base }).then((review) => {
1931
+ recordedBaseRef(scope).then((base) => diff.review(scope.cwd, base === void 0 ? {} : { recordedBaseRef: base })).then((review) => {
1693
1932
  if (scopes.has(sessionId)) update(sessionId, { changes: review?.changes });
1694
1933
  }).catch((error) => {
1695
1934
  host.onNotice(`git diff refresh failed for ${sessionId} (${error instanceof Error ? error.name : "unknown"})`);
@@ -1698,12 +1937,12 @@ function createGitChangesChannel(options = {}) {
1698
1937
  if (rerun.delete(sessionId)) refresh(sessionId);
1699
1938
  });
1700
1939
  };
1701
- const run = (scope, command) => {
1940
+ const run = async (scope, command) => {
1702
1941
  const workspaceRoot = scope.sessionContext?.workspaceRoot ?? scope.cwd;
1703
- const base = recordedBaseRef(scope);
1942
+ const base = await recordedBaseRef(scope);
1704
1943
  const context = {
1705
1944
  cwd: scope.cwd,
1706
- auth: auth.read(workspaceRoot),
1945
+ auth: await auth.read(workspaceRoot),
1707
1946
  ...base === void 0 ? {} : { recordedBaseRef: base }
1708
1947
  };
1709
1948
  if (command.action === "pull") return sync.pull(context);
@@ -1801,9 +2040,9 @@ function payloadOf(state) {
1801
2040
  };
1802
2041
  }
1803
2042
  /** Worktrees visible to this session, using the hub's live session table for ownership. */
1804
- function viewsFor(scope, deps) {
2043
+ async function viewsFor(scope, deps) {
1805
2044
  try {
1806
- return require_worktreeOperations.createWorktreeRegistry(require_worktreeOperations.registryFile(scope.cwd, deps.homeDir)).list().flatMap((record) => {
2045
+ return (await require_worktreeOperations.createWorktreeRegistry(await require_worktreeOperations.registryFile(scope.cwd, deps.homeDir)).list()).flatMap((record) => {
1807
2046
  if (record.parentSessionId === void 0 && record.sessionId !== scope.sessionId) return [];
1808
2047
  const owned = record.parentSessionId === void 0 || record.parentSessionId === scope.sessionId;
1809
2048
  const unowned = !owned && !deps.sessionService.isLive(record.parentSessionId);
@@ -1828,11 +2067,15 @@ function sameList(left, right) {
1828
2067
  return other !== void 0 && entry.id === other.id && entry.branch === other.branch && entry.sessionId === other.sessionId && entry.orphaned === other.orphaned && entry.unowned === other.unowned;
1829
2068
  });
1830
2069
  }
1831
- function errorTargetFor(command) {
1832
- return command.action === "create" ? { action: "create" } : {
2070
+ function errorTargetFor(command, error) {
2071
+ if (command.action === "close") return {
1833
2072
  action: "close",
1834
2073
  id: command.id
1835
2074
  };
2075
+ return error instanceof require_worktreeOperations.PoGitExpectedError && error.code === "rift_missing" ? {
2076
+ action: "create",
2077
+ riftMissing: true
2078
+ } : { action: "create" };
1836
2079
  }
1837
2080
  /** A command the page sent, or undefined when the frame was not one. */
1838
2081
  function parseCommand(payload) {
@@ -1860,11 +2103,15 @@ function parseCommand(payload) {
1860
2103
  /**
1861
2104
  * What the reader is told when a command fails.
1862
2105
  *
1863
- * An expected failure explains itself. Anything else is summarised, never
1864
- * echoed: a git error can carry a remote URL with a token in it.
2106
+ * An expected failure explains itself, minus the `[code]` tag and recovery line
2107
+ * written for the agent's tool result. A dirty close is said in the person's
2108
+ * terms, because its message tells the agent to pass `force`. Anything else is
2109
+ * summarised, never echoed: a git error can carry a remote URL with a token in it.
1865
2110
  */
1866
2111
  function failureText(error) {
1867
- return error instanceof require_worktreeOperations.PoGitExpectedError ? error.message : "The worktree operation failed.";
2112
+ if (!(error instanceof require_worktreeOperations.PoGitExpectedError)) return "The worktree operation failed.";
2113
+ if (error.code === "worktree_dirty") return "The worktree has uncommitted changes.";
2114
+ return error.message.replace(/^\[[a-z_]+\] /u, "").split("\nRecovery:")[0] ?? error.message;
1868
2115
  }
1869
2116
  /** Starts one direct-event subscription per managed session. */
1870
2117
  function createWorktreesChannel(options = {}) {
@@ -1917,10 +2164,13 @@ function createWorktreesChannel(options = {}) {
1917
2164
  return generation;
1918
2165
  };
1919
2166
  /** Republishes a session, skipping a frame that would say nothing new. */
1920
- const publish = (scope, force) => {
2167
+ const publish = async (scope, force) => {
2168
+ const generation = generations.get(scope.sessionId);
2169
+ const worktreeViews = await viewsFor(scope, views);
2170
+ if (generation === void 0 || generations.get(scope.sessionId) !== generation) return;
1921
2171
  const previous = latest.get(scope.sessionId);
1922
2172
  const next = {
1923
- worktrees: viewsFor(scope, views),
2173
+ worktrees: worktreeViews,
1924
2174
  pending: previous?.pending,
1925
2175
  error: previous?.error,
1926
2176
  errorTarget: previous?.errorTarget
@@ -1932,7 +2182,7 @@ function createWorktreesChannel(options = {}) {
1932
2182
  const mark = (scope, pending, error, errorTarget) => {
1933
2183
  const previous = latest.get(scope.sessionId);
1934
2184
  latest.set(scope.sessionId, {
1935
- worktrees: previous?.worktrees ?? viewsFor(scope, views),
2185
+ worktrees: previous?.worktrees ?? [],
1936
2186
  pending,
1937
2187
  error,
1938
2188
  errorTarget
@@ -1972,15 +2222,15 @@ function createWorktreesChannel(options = {}) {
1972
2222
  sessionAdded(scope) {
1973
2223
  newGeneration(scope.sessionId);
1974
2224
  publish(scope, false);
1975
- const unsubscribe = host.directEvents.subscribe(require_worktreeOperations.GIT_WORKTREE_LIFECYCLE_EVENT, scope.sessionId, (payload) => {
2225
+ const unsubscribe = host.directEvents.subscribe(require_worktreeOperations.GIT_WORKTREE_LIFECYCLE_EVENT, scope.sessionId, async (payload) => {
1976
2226
  if (!require_worktreeOperations.isWorktreeLifecycleEvent(payload)) return;
1977
2227
  let sameRepository = false;
1978
2228
  try {
1979
- sameRepository = require_worktreeOperations.registryFile(scope.cwd, options.homeDir) === require_worktreeOperations.registryFile(payload.repositoryRoot, options.homeDir);
2229
+ sameRepository = await require_worktreeOperations.registryFile(scope.cwd, options.homeDir) === await require_worktreeOperations.registryFile(payload.repositoryRoot, options.homeDir);
1980
2230
  } catch {
1981
2231
  sameRepository = false;
1982
2232
  }
1983
- if (sameRepository) publish(scope, false);
2233
+ if (sameRepository) await publish(scope, false);
1984
2234
  });
1985
2235
  subscriptions.set(scope.sessionId, unsubscribe);
1986
2236
  },
@@ -2013,7 +2263,7 @@ function createWorktreesChannel(options = {}) {
2013
2263
  }).catch((error) => {
2014
2264
  if (generations.get(scope.sessionId) !== generation) return;
2015
2265
  host.onNotice(`worktree command failed for ${scope.sessionId} (${failureText(error)})`);
2016
- mark(scope, void 0, failureText(error), errorTargetFor(command));
2266
+ mark(scope, void 0, failureText(error), errorTargetFor(command, error));
2017
2267
  }).finally(() => {
2018
2268
  if (generations.get(scope.sessionId) === generation) busy.delete(scope.sessionId);
2019
2269
  });
@@ -2035,20 +2285,8 @@ var agent_settled_server_default = (0, _agimon_ai_po_core_extensionFile.defineHo
2035
2285
  //#region src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.ts
2036
2286
  var tool_execution_end_server_default = (0, _agimon_ai_po_core_extensionFile.defineHook)((context) => context.root.hooks[0]);
2037
2287
  //#endregion
2038
- //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poGit.server.ts
2039
- var poGit_server_default$1 = (context) => context.root.resources[0];
2040
- //#endregion
2041
- //#region src/extensions/workspaces/sessions/(backend)/resource/poGit.server.ts
2042
- var poGit_server_default = (0, _agimon_ai_po_core_extensionFile.defineResource)(poGit_server_default$1);
2043
- //#endregion
2044
- //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poGitReadme.server.ts
2045
- var poGitReadme_server_default$1 = (context) => context.root.resources[2];
2046
- //#endregion
2047
- //#region src/extensions/workspaces/sessions/(backend)/resource/poGitReadme.server.ts
2048
- var poGitReadme_server_default = (0, _agimon_ai_po_core_extensionFile.defineResource)(poGitReadme_server_default$1);
2049
- //#endregion
2050
2288
  //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poUseGit.server.ts
2051
- var poUseGit_server_default$1 = (context) => context.root.resources[1];
2289
+ var poUseGit_server_default$1 = (context) => context.root.resources[0];
2052
2290
  //#endregion
2053
2291
  //#region src/extensions/workspaces/sessions/(backend)/resource/poUseGit.server.ts
2054
2292
  var poUseGit_server_default = (0, _agimon_ai_po_core_extensionFile.defineResource)(poUseGit_server_default$1);
@@ -2058,19 +2296,22 @@ var runWorktree_server_default = (0, _agimon_ai_po_core_extensionFile.defineServ
2058
2296
  //#endregion
2059
2297
  //#region generated/server.ts
2060
2298
  const at = (value, context) => typeof value === "function" ? value(context) : value;
2299
+ /* v8 ignore next -- generated normalization branch */
2300
+ const defined = (values) => values.filter((value) => value !== void 0);
2061
2301
  const via = (identity, value) => value === void 0 ? void 0 : {
2062
2302
  ...identity,
2063
2303
  ...value
2064
2304
  };
2065
- const facet = (0, _agimon_ai_po_core_serverFacet.defineServerPlugin)({
2305
+ const serverPlugin = (0, _agimon_ai_po_core_serverPlugin.defineServerPlugin)({
2066
2306
  name: "@agimon-ai/po-git",
2067
- providerOverrides: (0, _agimon_ai_po_core_serverFacet.composeServerProviderOverrides)(root_server_default),
2307
+ providerOverrides: (0, _agimon_ai_po_core_serverPlugin.composeServerProviderOverrides)(root_server_default),
2308
+ injects: (0, _agimon_ai_po_core_extensionFile.composeRootInjects)({ session: [root_server_default] }),
2068
2309
  global: (context) => ({
2069
2310
  get api() {
2070
2311
  return [at(route_server_default, context)];
2071
2312
  },
2072
2313
  get channels() {
2073
- return [() => via({ frameType: "git_changes" }, git_changes_server_default()), () => via({ frameType: "git_worktrees" }, git_worktrees_server_default())];
2314
+ return [() => via({ frameType: "git_changes" }, at(git_changes_server_default, context)), () => via({ frameType: "git_worktrees" }, at(git_worktrees_server_default, context))];
2074
2315
  }
2075
2316
  }),
2076
2317
  workspace: (context) => ({
@@ -2078,11 +2319,11 @@ const facet = (0, _agimon_ai_po_core_serverFacet.defineServerPlugin)({
2078
2319
  return [at(route_server_default, context)];
2079
2320
  },
2080
2321
  get channels() {
2081
- return [() => via({ frameType: "git_changes" }, git_changes_server_default()), () => via({ frameType: "git_worktrees" }, git_worktrees_server_default())];
2322
+ return defined([context.hosted("global") ? void 0 : () => via({ frameType: "git_changes" }, at(git_changes_server_default, context)), context.hosted("global") ? void 0 : () => via({ frameType: "git_worktrees" }, at(git_worktrees_server_default, context))]);
2082
2323
  }
2083
2324
  }),
2084
2325
  session: async (context) => {
2085
- const scopeSession = await root_server_default(context);
2326
+ const scopeSession = await context.scopeRoot("session", root_server_default, context);
2086
2327
  const contextSession = {
2087
2328
  ...context,
2088
2329
  root: scopeSession.value
@@ -2093,17 +2334,13 @@ const facet = (0, _agimon_ai_po_core_serverFacet.defineServerPlugin)({
2093
2334
  return [at(route_server_default, context)];
2094
2335
  },
2095
2336
  get channels() {
2096
- return [() => via({ frameType: "git_changes" }, git_changes_server_default()), () => via({ frameType: "git_worktrees" }, git_worktrees_server_default())];
2337
+ return defined([context.hosted("global") ? void 0 : () => via({ frameType: "git_changes" }, at(git_changes_server_default, context)), context.hosted("global") ? void 0 : () => via({ frameType: "git_worktrees" }, at(git_worktrees_server_default, context))]);
2097
2338
  },
2098
2339
  get hooks() {
2099
2340
  return [via({ event: "agent_settled" }, at(agent_settled_server_default, contextSession)), via({ event: "tool_execution_end" }, at(tool_execution_end_server_default, contextSession))];
2100
2341
  },
2101
2342
  get resources() {
2102
- return [
2103
- at(poGit_server_default, contextSession),
2104
- at(poGitReadme_server_default, contextSession),
2105
- at(poUseGit_server_default, contextSession)
2106
- ];
2343
+ return [at(poUseGit_server_default, contextSession)];
2107
2344
  },
2108
2345
  get tools() {
2109
2346
  return [via({ name: "run_worktree" }, at(runWorktree_server_default, contextSession))];
@@ -2116,7 +2353,7 @@ const facet = (0, _agimon_ai_po_core_serverFacet.defineServerPlugin)({
2116
2353
  }
2117
2354
  });
2118
2355
  //#endregion
2119
- exports.default = facet;
2120
- exports.facet = facet;
2356
+ exports.default = serverPlugin;
2357
+ exports.serverPlugin = serverPlugin;
2121
2358
 
2122
2359
  //# sourceMappingURL=server.cjs.map