@agimon-ai/po-git 0.0.0-alpha.27 → 0.0.0-alpha.29

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 CHANGED
@@ -15,7 +15,7 @@ Add `@agimon-ai/po-git` to a selected layer in `.po/modes.yaml`, then run `po sy
15
15
 
16
16
  ## Use
17
17
 
18
- Use the browser worktree controls or the native worktree tool.
18
+ Use the browser worktree controls, or an agent session. In code mode the `worktrees` namespace (`list`, `status`, `spawn`, `close`, `merge`, `prune`, `send`, `messages`) returns structured records and the `run_worktree` tool is withdrawn from the model for that session; without code mode `run_worktree` stays. The `po-use-git` skill carries the usage rules.
19
19
 
20
20
  ### New-session dialog
21
21
 
@@ -11,6 +11,7 @@ let node_os = require("node:os");
11
11
  node_os = require_worktreeOperations.__toESM(node_os, 1);
12
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");
16
17
  //#region src/schemas/runWorktreeTool.ts
@@ -254,7 +255,8 @@ async function executeRunWorktreeTool(params, operations, context, options = {})
254
255
  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
256
  details: {
256
257
  action: params.action,
257
- records: [record]
258
+ records: [record],
259
+ dirtyFiles
258
260
  }
259
261
  };
260
262
  }
@@ -278,7 +280,10 @@ async function executeRunWorktreeTool(params, operations, context, options = {})
278
280
  const waiting = await operations.messages(context, params.id);
279
281
  return {
280
282
  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 }
283
+ details: {
284
+ action: params.action,
285
+ messages: waiting
286
+ }
282
287
  };
283
288
  }
284
289
  case WORKTREE_ACTIONS.prune: {
@@ -293,12 +298,172 @@ async function executeRunWorktreeTool(params, operations, context, options = {})
293
298
  `Untracked directories, left alone: ${plan.untracked.length === 0 ? "none" : plan.untracked.join(", ")}`,
294
299
  ...plan.keptBranches.length === 0 ? [] : [`Branches kept, they hold commits: ${plan.keptBranches.join(", ")}`]
295
300
  ]),
296
- details: { action: params.action }
301
+ details: {
302
+ action: params.action,
303
+ plan
304
+ }
297
305
  };
298
306
  }
299
307
  }
300
308
  }
301
309
  //#endregion
310
+ //#region src/services/worktreeNamespace/index.ts
311
+ const DECLARATIONS = `type WorktreeStatus = 'spawning' | 'running' | 'closing' | 'orphaned';
312
+
313
+ interface Worktree {
314
+ id: string;
315
+ branch: string;
316
+ baseRef: string;
317
+ /** Absolute path of the checkout, under ~/.po/git/worktrees. */
318
+ path: string;
319
+ repositoryRoot: string;
320
+ /** The session running inside the worktree. */
321
+ sessionId: string;
322
+ parentSessionId?: string;
323
+ status: WorktreeStatus;
324
+ createdAt: string;
325
+ /** How your session relates to it: 'parent' spawned it, 'child' runs inside it, 'other' is neither. */
326
+ relationship: 'parent' | 'child' | 'other';
327
+ }
328
+
329
+ interface WorktreeMessage {
330
+ worktreeId: string;
331
+ fromSessionId: string;
332
+ from: 'parent' | 'child';
333
+ text: string;
334
+ sentAt: string;
335
+ }
336
+
337
+ interface PrunePlan {
338
+ remove: Worktree[];
339
+ forget: Worktree[];
340
+ /** Kept because they hold uncommitted work. */
341
+ keptDirty: Worktree[];
342
+ untracked: string[];
343
+ /** Branches kept because they hold commits. */
344
+ keptBranches: string[];
345
+ }
346
+
347
+ declare const worktrees: {
348
+ /** Every worktree for this repository. */
349
+ list(): Promise<Worktree[]>;
350
+ /** One worktree and its uncommitted files. */
351
+ status(id: string): Promise<{ worktree: Worktree; dirtyFiles: string[] }>;
352
+ /**
353
+ * Create a worktree on a new branch and start a Po session in it, nested under this one. baseRef defaults to
354
+ * the current branch's remote-tracking ref, then the remote default branch; name labels it in the rail; task is
355
+ * the new session's first message. Setup can be slow, so give exec a larger timeoutMs.
356
+ */
357
+ spawn(request: { branch: string; baseRef?: string; name?: string; task?: string }): Promise<Worktree>;
358
+ /** Stop the worktree's session and delete its checkout. Refused on a dirty tree unless force is true. */
359
+ close(id: string, options?: { force?: boolean }): Promise<Worktree>;
360
+ /** Merge the worktree's branch into this checkout. Refused when this checkout is dirty. */
361
+ merge(id: string, options?: { message?: string }): Promise<Worktree>;
362
+ /** Remove orphaned worktrees. With dryRun, only return the plan. */
363
+ prune(options?: { dryRun?: boolean }): Promise<PrunePlan>;
364
+ /**
365
+ * Message the other side of a worktree. Use the record with relationship 'child' to report to your parent,
366
+ * and 'parent' to reach your own children. 'other' records are not communication edges.
367
+ */
368
+ send(id: string, message: string): Promise<void>;
369
+ /** Messages waiting for you from a worktree. */
370
+ messages(id: string): Promise<WorktreeMessage[]>;
371
+ };`;
372
+ const json = (value) => JSON.parse(JSON.stringify(value));
373
+ /** Git worktrees for code mode: the actions of `run_worktree` as methods, returning records instead of text. */
374
+ function createWorktreeNamespace(options) {
375
+ return {
376
+ name: "worktrees",
377
+ summary: "Git worktrees, each with its own Po session nested under this one: worktrees.list, status, spawn, close, merge, prune, send and messages.",
378
+ declarations: DECLARATIONS,
379
+ bind(run) {
380
+ if (run.sessionId !== options.execution.sessionId) throw new Error("Worktrees namespace belongs to another session.");
381
+ let disposed = false;
382
+ run.onDispose(() => {
383
+ disposed = true;
384
+ });
385
+ const view = (record) => ({
386
+ ...record,
387
+ relationship: record.parentSessionId === options.execution.sessionId ? "parent" : record.sessionId === options.execution.sessionId ? "child" : "other"
388
+ });
389
+ /** One `run_worktree` action through the tool's own code, so the tool and the namespace cannot drift. */
390
+ const act = async (name, action, input) => {
391
+ run.signal.throwIfAborted();
392
+ if (disposed) throw new Error("Worktrees namespace run is disposed.");
393
+ options.assertAvailable();
394
+ const params = validateParams({
395
+ action,
396
+ ...typeof input === "object" && input !== null ? input : {}
397
+ });
398
+ const context = {
399
+ cwd: options.execution.cwd,
400
+ sessionId: options.execution.sessionId
401
+ };
402
+ const started = Date.now();
403
+ try {
404
+ const result = await executeRunWorktreeTool(params, options.operations, context, { signal: run.signal });
405
+ options.assertAvailable();
406
+ run.record?.({
407
+ name: `worktrees.${name}`,
408
+ tool: RUN_WORKTREE_TOOL_NAME,
409
+ args: params,
410
+ ok: true,
411
+ ms: Date.now() - started,
412
+ result
413
+ });
414
+ return result;
415
+ } catch (error) {
416
+ run.record?.({
417
+ name: `worktrees.${name}`,
418
+ tool: RUN_WORKTREE_TOOL_NAME,
419
+ args: params,
420
+ ok: false,
421
+ ms: Date.now() - started,
422
+ error: error instanceof Error ? error.message : String(error)
423
+ });
424
+ throw error;
425
+ }
426
+ };
427
+ const record = (result) => view(result.details.records[0]);
428
+ const members = Object.create(null);
429
+ members.list = async () => json((await act("list", "list", {})).details.records.map(view));
430
+ members.status = async (id) => {
431
+ const { details } = await act("status", "status", { id });
432
+ return json({
433
+ worktree: view(details.records[0]),
434
+ dirtyFiles: details.dirtyFiles ?? []
435
+ });
436
+ };
437
+ members.spawn = async (request) => json(record(await act("spawn", "spawn_worktree", request)));
438
+ members.close = async (id, settings) => json(record(await act("close", "close_worktree", {
439
+ ...settings,
440
+ id
441
+ })));
442
+ members.merge = async (id, settings) => json(record(await act("merge", "merge", {
443
+ ...settings,
444
+ id
445
+ })));
446
+ members.prune = async (settings) => {
447
+ const { plan } = (await act("prune", "prune", settings)).details;
448
+ return json({
449
+ ...plan,
450
+ remove: plan.remove.map(view),
451
+ forget: plan.forget.map(view),
452
+ keptDirty: plan.keptDirty.map(view)
453
+ });
454
+ };
455
+ members.send = async (id, message) => {
456
+ await act("send", "send", {
457
+ id,
458
+ message
459
+ });
460
+ };
461
+ members.messages = async (id) => json((await act("messages", "messages", { id })).details.messages);
462
+ return members;
463
+ }
464
+ };
465
+ }
466
+ //#endregion
302
467
  //#region src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts
303
468
  function progressResult(action, label) {
304
469
  return {
@@ -309,7 +474,7 @@ function progressResult(action, label) {
309
474
  details: { action }
310
475
  };
311
476
  }
312
- function createGitSession({ context, host: serverHost }) {
477
+ function createGitSession({ context, host: serverHost, agent, signal }) {
313
478
  if (serverHost.context.directEvents === void 0) throw new Error("Git server plugin requires the session direct event bus.");
314
479
  if (serverHost.context.sessionId === void 0) throw new Error("Git server plugin requires a session identity.");
315
480
  const sessionId = serverHost.context.sessionId;
@@ -322,17 +487,6 @@ function createGitSession({ context, host: serverHost }) {
322
487
  sessionDelivery: () => (0, _agimon_ai_po_session.readPoSessionDelivery)(context)
323
488
  });
324
489
  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_serverPlugin.readPackageResource)(require("url").pathToFileURL(__filename).href, "llms.txt")
335
- }, {
336
490
  name: "po-use-git",
337
491
  kind: "skill",
338
492
  read: () => (0, _agimon_ai_po_core_serverPlugin.readPackageResource)(require("url").pathToFileURL(__filename).href, "src/prompts/po-use-git/SKILL.md")
@@ -355,17 +509,37 @@ function createGitSession({ context, host: serverHost }) {
355
509
  const changed = (_event, execution) => {
356
510
  directEvents.publish(require_worktreeOperations.GIT_SESSION_CHANGED_EVENT, execution.sessionId, { version: 1 });
357
511
  };
512
+ const hooks = [{
513
+ event: "tool_execution_end",
514
+ handle: changed
515
+ }, {
516
+ event: "agent_settled",
517
+ handle: changed
518
+ }];
519
+ if (agent) {
520
+ const namespace = createWorktreeNamespace({
521
+ operations,
522
+ execution: agent.context,
523
+ assertAvailable: () => {
524
+ if (signal?.aborted) throw new Error("Git session is unavailable.");
525
+ }
526
+ });
527
+ context.inject([_agimon_ai_po_core_codemode.PO_CODEMODE_SERVICE], (scoped) => {
528
+ const codemode = (0, _agimon_ai_po_core_codemode.readPoCodemode)(scoped);
529
+ if (!codemode) return void 0;
530
+ const registration = codemode.register("@agimon-ai/po-git", namespace);
531
+ const restriction = agent.registerToolRestriction({ excludedTools: [RUN_WORKTREE_TOOL_NAME] });
532
+ return () => {
533
+ restriction.dispose();
534
+ registration.dispose();
535
+ };
536
+ });
537
+ }
358
538
  return {
359
539
  value: {
360
540
  resources,
361
541
  tool,
362
- hooks: [{
363
- event: "tool_execution_end",
364
- handle: changed
365
- }, {
366
- event: "agent_settled",
367
- handle: changed
368
- }]
542
+ hooks
369
543
  },
370
544
  onDispose: () => messageInbox.close()
371
545
  };
@@ -2039,20 +2213,8 @@ var agent_settled_server_default = (0, _agimon_ai_po_core_extensionFile.defineHo
2039
2213
  //#region src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.ts
2040
2214
  var tool_execution_end_server_default = (0, _agimon_ai_po_core_extensionFile.defineHook)((context) => context.root.hooks[0]);
2041
2215
  //#endregion
2042
- //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poGit.server.ts
2043
- var poGit_server_default$1 = (context) => context.root.resources[0];
2044
- //#endregion
2045
- //#region src/extensions/workspaces/sessions/(backend)/resource/poGit.server.ts
2046
- var poGit_server_default = (0, _agimon_ai_po_core_extensionFile.defineResource)(poGit_server_default$1);
2047
- //#endregion
2048
- //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poGitReadme.server.ts
2049
- var poGitReadme_server_default$1 = (context) => context.root.resources[2];
2050
- //#endregion
2051
- //#region src/extensions/workspaces/sessions/(backend)/resource/poGitReadme.server.ts
2052
- var poGitReadme_server_default = (0, _agimon_ai_po_core_extensionFile.defineResource)(poGitReadme_server_default$1);
2053
- //#endregion
2054
2216
  //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poUseGit.server.ts
2055
- var poUseGit_server_default$1 = (context) => context.root.resources[1];
2217
+ var poUseGit_server_default$1 = (context) => context.root.resources[0];
2056
2218
  //#endregion
2057
2219
  //#region src/extensions/workspaces/sessions/(backend)/resource/poUseGit.server.ts
2058
2220
  var poUseGit_server_default = (0, _agimon_ai_po_core_extensionFile.defineResource)(poUseGit_server_default$1);
@@ -2077,7 +2239,7 @@ const serverPlugin = (0, _agimon_ai_po_core_serverPlugin.defineServerPlugin)({
2077
2239
  return [at(route_server_default, context)];
2078
2240
  },
2079
2241
  get channels() {
2080
- return [() => via({ frameType: "git_changes" }, git_changes_server_default()), () => via({ frameType: "git_worktrees" }, git_worktrees_server_default())];
2242
+ return [() => via({ frameType: "git_changes" }, at(git_changes_server_default, context)), () => via({ frameType: "git_worktrees" }, at(git_worktrees_server_default, context))];
2081
2243
  }
2082
2244
  }),
2083
2245
  workspace: (context) => ({
@@ -2085,7 +2247,7 @@ const serverPlugin = (0, _agimon_ai_po_core_serverPlugin.defineServerPlugin)({
2085
2247
  return [at(route_server_default, context)];
2086
2248
  },
2087
2249
  get channels() {
2088
- return defined([context.hosted("global") ? void 0 : () => via({ frameType: "git_changes" }, git_changes_server_default()), context.hosted("global") ? void 0 : () => via({ frameType: "git_worktrees" }, git_worktrees_server_default())]);
2250
+ 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))]);
2089
2251
  }
2090
2252
  }),
2091
2253
  session: async (context) => {
@@ -2100,17 +2262,13 @@ const serverPlugin = (0, _agimon_ai_po_core_serverPlugin.defineServerPlugin)({
2100
2262
  return [at(route_server_default, context)];
2101
2263
  },
2102
2264
  get channels() {
2103
- return defined([context.hosted("global") ? void 0 : () => via({ frameType: "git_changes" }, git_changes_server_default()), context.hosted("global") ? void 0 : () => via({ frameType: "git_worktrees" }, git_worktrees_server_default())]);
2265
+ 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))]);
2104
2266
  },
2105
2267
  get hooks() {
2106
2268
  return [via({ event: "agent_settled" }, at(agent_settled_server_default, contextSession)), via({ event: "tool_execution_end" }, at(tool_execution_end_server_default, contextSession))];
2107
2269
  },
2108
2270
  get resources() {
2109
- return [
2110
- at(poGit_server_default, contextSession),
2111
- at(poGitReadme_server_default, contextSession),
2112
- at(poUseGit_server_default, contextSession)
2113
- ];
2271
+ return [at(poUseGit_server_default, contextSession)];
2114
2272
  },
2115
2273
  get tools() {
2116
2274
  return [via({ name: "run_worktree" }, at(runWorktree_server_default, contextSession))];