@agimon-ai/po-git 0.0.0-alpha.28 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.mts","names":[],"sources":["../../generated/server.ts"],"mappings":";qBAyBa,wDAAY"}
1
+ {"version":3,"file":"server.d.mts","names":[],"sources":["../../generated/server.ts"],"mappings":";qBAuBa,wDAAY"}
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import os from "node:os";
5
5
  import { composeServerProviderOverrides, defineServerPlugin, readPackageResource } from "@agimon-ai/po-core/serverPlugin";
6
6
  import { composeRootHooks, composeRootInjects, defineChannel, defineHook, defineResource, defineRoot, defineRoute, defineServerTool } from "@agimon-ai/po-core/extensionFile";
7
+ import { PO_CODEMODE_SERVICE, readPoCodemode } from "@agimon-ai/po-core/codemode";
7
8
  import { readPoSessionDelivery } from "@agimon-ai/po-session";
8
9
  import { Type } from "typebox";
9
10
  //#region src/schemas/runWorktreeTool.ts
@@ -247,7 +248,8 @@ async function executeRunWorktreeTool(params, operations, context, options = {})
247
248
  content: text(describe(record, context.sessionId), `Session: ${record.sessionId}`, dirtyFiles.length === 0 ? "Clean." : `${String(dirtyFiles.length)} uncommitted file(s): ${dirtyFiles.slice(0, 20).join(", ")}`),
248
249
  details: {
249
250
  action: params.action,
250
- records: [record]
251
+ records: [record],
252
+ dirtyFiles
251
253
  }
252
254
  };
253
255
  }
@@ -271,7 +273,10 @@ async function executeRunWorktreeTool(params, operations, context, options = {})
271
273
  const waiting = await operations.messages(context, params.id);
272
274
  return {
273
275
  content: waiting.length === 0 ? text("No messages.") : text(`${String(waiting.length)} message(s):`, ...waiting.map((entry) => `[${entry.sentAt}] ${entry.from}: ${entry.text}`)),
274
- details: { action: params.action }
276
+ details: {
277
+ action: params.action,
278
+ messages: waiting
279
+ }
275
280
  };
276
281
  }
277
282
  case WORKTREE_ACTIONS.prune: {
@@ -286,12 +291,172 @@ async function executeRunWorktreeTool(params, operations, context, options = {})
286
291
  `Untracked directories, left alone: ${plan.untracked.length === 0 ? "none" : plan.untracked.join(", ")}`,
287
292
  ...plan.keptBranches.length === 0 ? [] : [`Branches kept, they hold commits: ${plan.keptBranches.join(", ")}`]
288
293
  ]),
289
- details: { action: params.action }
294
+ details: {
295
+ action: params.action,
296
+ plan
297
+ }
290
298
  };
291
299
  }
292
300
  }
293
301
  }
294
302
  //#endregion
303
+ //#region src/services/worktreeNamespace/index.ts
304
+ const DECLARATIONS = `type WorktreeStatus = 'spawning' | 'running' | 'closing' | 'orphaned';
305
+
306
+ interface Worktree {
307
+ id: string;
308
+ branch: string;
309
+ baseRef: string;
310
+ /** Absolute path of the checkout, under ~/.po/git/worktrees. */
311
+ path: string;
312
+ repositoryRoot: string;
313
+ /** The session running inside the worktree. */
314
+ sessionId: string;
315
+ parentSessionId?: string;
316
+ status: WorktreeStatus;
317
+ createdAt: string;
318
+ /** How your session relates to it: 'parent' spawned it, 'child' runs inside it, 'other' is neither. */
319
+ relationship: 'parent' | 'child' | 'other';
320
+ }
321
+
322
+ interface WorktreeMessage {
323
+ worktreeId: string;
324
+ fromSessionId: string;
325
+ from: 'parent' | 'child';
326
+ text: string;
327
+ sentAt: string;
328
+ }
329
+
330
+ interface PrunePlan {
331
+ remove: Worktree[];
332
+ forget: Worktree[];
333
+ /** Kept because they hold uncommitted work. */
334
+ keptDirty: Worktree[];
335
+ untracked: string[];
336
+ /** Branches kept because they hold commits. */
337
+ keptBranches: string[];
338
+ }
339
+
340
+ declare const worktrees: {
341
+ /** Every worktree for this repository. */
342
+ list(): Promise<Worktree[]>;
343
+ /** One worktree and its uncommitted files. */
344
+ status(id: string): Promise<{ worktree: Worktree; dirtyFiles: string[] }>;
345
+ /**
346
+ * Create a worktree on a new branch and start a Po session in it, nested under this one. baseRef defaults to
347
+ * the current branch's remote-tracking ref, then the remote default branch; name labels it in the rail; task is
348
+ * the new session's first message. Setup can be slow, so give exec a larger timeoutMs.
349
+ */
350
+ spawn(request: { branch: string; baseRef?: string; name?: string; task?: string }): Promise<Worktree>;
351
+ /** Stop the worktree's session and delete its checkout. Refused on a dirty tree unless force is true. */
352
+ close(id: string, options?: { force?: boolean }): Promise<Worktree>;
353
+ /** Merge the worktree's branch into this checkout. Refused when this checkout is dirty. */
354
+ merge(id: string, options?: { message?: string }): Promise<Worktree>;
355
+ /** Remove orphaned worktrees. With dryRun, only return the plan. */
356
+ prune(options?: { dryRun?: boolean }): Promise<PrunePlan>;
357
+ /**
358
+ * Message the other side of a worktree. Use the record with relationship 'child' to report to your parent,
359
+ * and 'parent' to reach your own children. 'other' records are not communication edges.
360
+ */
361
+ send(id: string, message: string): Promise<void>;
362
+ /** Messages waiting for you from a worktree. */
363
+ messages(id: string): Promise<WorktreeMessage[]>;
364
+ };`;
365
+ const json = (value) => JSON.parse(JSON.stringify(value));
366
+ /** Git worktrees for code mode: the actions of `run_worktree` as methods, returning records instead of text. */
367
+ function createWorktreeNamespace(options) {
368
+ return {
369
+ name: "worktrees",
370
+ summary: "Git worktrees, each with its own Po session nested under this one: worktrees.list, status, spawn, close, merge, prune, send and messages.",
371
+ declarations: DECLARATIONS,
372
+ bind(run) {
373
+ if (run.sessionId !== options.execution.sessionId) throw new Error("Worktrees namespace belongs to another session.");
374
+ let disposed = false;
375
+ run.onDispose(() => {
376
+ disposed = true;
377
+ });
378
+ const view = (record) => ({
379
+ ...record,
380
+ relationship: record.parentSessionId === options.execution.sessionId ? "parent" : record.sessionId === options.execution.sessionId ? "child" : "other"
381
+ });
382
+ /** One `run_worktree` action through the tool's own code, so the tool and the namespace cannot drift. */
383
+ const act = async (name, action, input) => {
384
+ run.signal.throwIfAborted();
385
+ if (disposed) throw new Error("Worktrees namespace run is disposed.");
386
+ options.assertAvailable();
387
+ const params = validateParams({
388
+ action,
389
+ ...typeof input === "object" && input !== null ? input : {}
390
+ });
391
+ const context = {
392
+ cwd: options.execution.cwd,
393
+ sessionId: options.execution.sessionId
394
+ };
395
+ const started = Date.now();
396
+ try {
397
+ const result = await executeRunWorktreeTool(params, options.operations, context, { signal: run.signal });
398
+ options.assertAvailable();
399
+ run.record?.({
400
+ name: `worktrees.${name}`,
401
+ tool: RUN_WORKTREE_TOOL_NAME,
402
+ args: params,
403
+ ok: true,
404
+ ms: Date.now() - started,
405
+ result
406
+ });
407
+ return result;
408
+ } catch (error) {
409
+ run.record?.({
410
+ name: `worktrees.${name}`,
411
+ tool: RUN_WORKTREE_TOOL_NAME,
412
+ args: params,
413
+ ok: false,
414
+ ms: Date.now() - started,
415
+ error: error instanceof Error ? error.message : String(error)
416
+ });
417
+ throw error;
418
+ }
419
+ };
420
+ const record = (result) => view(result.details.records[0]);
421
+ const members = Object.create(null);
422
+ members.list = async () => json((await act("list", "list", {})).details.records.map(view));
423
+ members.status = async (id) => {
424
+ const { details } = await act("status", "status", { id });
425
+ return json({
426
+ worktree: view(details.records[0]),
427
+ dirtyFiles: details.dirtyFiles ?? []
428
+ });
429
+ };
430
+ members.spawn = async (request) => json(record(await act("spawn", "spawn_worktree", request)));
431
+ members.close = async (id, settings) => json(record(await act("close", "close_worktree", {
432
+ ...settings,
433
+ id
434
+ })));
435
+ members.merge = async (id, settings) => json(record(await act("merge", "merge", {
436
+ ...settings,
437
+ id
438
+ })));
439
+ members.prune = async (settings) => {
440
+ const { plan } = (await act("prune", "prune", settings)).details;
441
+ return json({
442
+ ...plan,
443
+ remove: plan.remove.map(view),
444
+ forget: plan.forget.map(view),
445
+ keptDirty: plan.keptDirty.map(view)
446
+ });
447
+ };
448
+ members.send = async (id, message) => {
449
+ await act("send", "send", {
450
+ id,
451
+ message
452
+ });
453
+ };
454
+ members.messages = async (id) => json((await act("messages", "messages", { id })).details.messages);
455
+ return members;
456
+ }
457
+ };
458
+ }
459
+ //#endregion
295
460
  //#region src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts
296
461
  function progressResult(action, label) {
297
462
  return {
@@ -302,7 +467,7 @@ function progressResult(action, label) {
302
467
  details: { action }
303
468
  };
304
469
  }
305
- function createGitSession({ context, host: serverHost }) {
470
+ function createGitSession({ context, host: serverHost, agent, signal }) {
306
471
  if (serverHost.context.directEvents === void 0) throw new Error("Git server plugin requires the session direct event bus.");
307
472
  if (serverHost.context.sessionId === void 0) throw new Error("Git server plugin requires a session identity.");
308
473
  const sessionId = serverHost.context.sessionId;
@@ -315,17 +480,6 @@ function createGitSession({ context, host: serverHost }) {
315
480
  sessionDelivery: () => readPoSessionDelivery(context)
316
481
  });
317
482
  const resources = [{
318
- when: {
319
- state: { "minor-mode": "help" },
320
- attribution: {
321
- kind: "minor",
322
- mode: "help"
323
- }
324
- },
325
- name: "po-git",
326
- kind: "context",
327
- read: () => readPackageResource(import.meta.url, "llms.txt")
328
- }, {
329
483
  name: "po-use-git",
330
484
  kind: "skill",
331
485
  read: () => readPackageResource(import.meta.url, "src/prompts/po-use-git/SKILL.md")
@@ -348,17 +502,37 @@ function createGitSession({ context, host: serverHost }) {
348
502
  const changed = (_event, execution) => {
349
503
  directEvents.publish(GIT_SESSION_CHANGED_EVENT, execution.sessionId, { version: 1 });
350
504
  };
505
+ const hooks = [{
506
+ event: "tool_execution_end",
507
+ handle: changed
508
+ }, {
509
+ event: "agent_settled",
510
+ handle: changed
511
+ }];
512
+ if (agent) {
513
+ const namespace = createWorktreeNamespace({
514
+ operations,
515
+ execution: agent.context,
516
+ assertAvailable: () => {
517
+ if (signal?.aborted) throw new Error("Git session is unavailable.");
518
+ }
519
+ });
520
+ context.inject([PO_CODEMODE_SERVICE], (scoped) => {
521
+ const codemode = readPoCodemode(scoped);
522
+ if (!codemode) return void 0;
523
+ const registration = codemode.register("@agimon-ai/po-git", namespace);
524
+ const restriction = agent.registerToolRestriction({ excludedTools: [RUN_WORKTREE_TOOL_NAME] });
525
+ return () => {
526
+ restriction.dispose();
527
+ registration.dispose();
528
+ };
529
+ });
530
+ }
351
531
  return {
352
532
  value: {
353
533
  resources,
354
534
  tool,
355
- hooks: [{
356
- event: "tool_execution_end",
357
- handle: changed
358
- }, {
359
- event: "agent_settled",
360
- handle: changed
361
- }]
535
+ hooks
362
536
  },
363
537
  onDispose: () => messageInbox.close()
364
538
  };
@@ -2032,20 +2206,8 @@ var agent_settled_server_default = defineHook((context) => context.root.hooks[1]
2032
2206
  //#region src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.ts
2033
2207
  var tool_execution_end_server_default = defineHook((context) => context.root.hooks[0]);
2034
2208
  //#endregion
2035
- //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poGit.server.ts
2036
- var poGit_server_default$1 = (context) => context.root.resources[0];
2037
- //#endregion
2038
- //#region src/extensions/workspaces/sessions/(backend)/resource/poGit.server.ts
2039
- var poGit_server_default = defineResource(poGit_server_default$1);
2040
- //#endregion
2041
- //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poGitReadme.server.ts
2042
- var poGitReadme_server_default$1 = (context) => context.root.resources[2];
2043
- //#endregion
2044
- //#region src/extensions/workspaces/sessions/(backend)/resource/poGitReadme.server.ts
2045
- var poGitReadme_server_default = defineResource(poGitReadme_server_default$1);
2046
- //#endregion
2047
2209
  //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poUseGit.server.ts
2048
- var poUseGit_server_default$1 = (context) => context.root.resources[1];
2210
+ var poUseGit_server_default$1 = (context) => context.root.resources[0];
2049
2211
  //#endregion
2050
2212
  //#region src/extensions/workspaces/sessions/(backend)/resource/poUseGit.server.ts
2051
2213
  var poUseGit_server_default = defineResource(poUseGit_server_default$1);
@@ -2070,7 +2232,7 @@ const serverPlugin = defineServerPlugin({
2070
2232
  return [at(route_server_default, context)];
2071
2233
  },
2072
2234
  get channels() {
2073
- return [() => via({ frameType: "git_changes" }, git_changes_server_default()), () => via({ frameType: "git_worktrees" }, git_worktrees_server_default())];
2235
+ return [() => via({ frameType: "git_changes" }, at(git_changes_server_default, context)), () => via({ frameType: "git_worktrees" }, at(git_worktrees_server_default, context))];
2074
2236
  }
2075
2237
  }),
2076
2238
  workspace: (context) => ({
@@ -2078,7 +2240,7 @@ const serverPlugin = defineServerPlugin({
2078
2240
  return [at(route_server_default, context)];
2079
2241
  },
2080
2242
  get channels() {
2081
- 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())]);
2243
+ 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
2244
  }
2083
2245
  }),
2084
2246
  session: async (context) => {
@@ -2093,17 +2255,13 @@ const serverPlugin = defineServerPlugin({
2093
2255
  return [at(route_server_default, context)];
2094
2256
  },
2095
2257
  get channels() {
2096
- 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())]);
2258
+ 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
2259
  },
2098
2260
  get hooks() {
2099
2261
  return [via({ event: "agent_settled" }, at(agent_settled_server_default, contextSession)), via({ event: "tool_execution_end" }, at(tool_execution_end_server_default, contextSession))];
2100
2262
  },
2101
2263
  get resources() {
2102
- return [
2103
- at(poGit_server_default, contextSession),
2104
- at(poGitReadme_server_default, contextSession),
2105
- at(poUseGit_server_default, contextSession)
2106
- ];
2264
+ return [at(poUseGit_server_default, contextSession)];
2107
2265
  },
2108
2266
  get tools() {
2109
2267
  return [via({ name: "run_worktree" }, at(runWorktree_server_default, contextSession))];