@agimon-ai/po-git 0.0.0-alpha.4 → 0.0.0-alpha.41

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,5 +1,5 @@
1
1
  //#region generated/server.d.ts
2
- export declare const facet: import("@agimon-ai/po-core/serverFacet").PoServerFacet;
2
+ export declare const serverPlugin: import("@agimon-ai/po-core/serverPlugin").PoServerPlugin;
3
3
  //#endregion
4
- export { facet as default };
4
+ export { serverPlugin as default };
5
5
  //# sourceMappingURL=server.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.mts","names":[],"sources":["../../generated/server.ts"],"mappings":";qBAsBa,gDAAK"}
1
+ {"version":3,"file":"server.d.mts","names":[],"sources":["../../generated/server.ts"],"mappings":";qBAuBa,wDAAY"}
@@ -1,9 +1,10 @@
1
- import { _ as NETWORK_TIMEOUT_MS, a as GIT_WORKTREE_LIFECYCLE_EVENT, b as createWorktreeGit, c as isWorktreeLifecycleEvent, d as registryFile, f as readJson, g as invalidRequest, h as PoGitExpectedError, i as GIT_SESSION_CHANGED_EVENT, l as credentialsFile, m as HubUnavailableError, n as createWorktreeRegistry, o as createWorktreeMessageInbox, p as writeJsonAtomic, r as validRefName, s as isSessionChangedEvent, t as createWorktreeOperations, u as poGitRoot, v as READ_TIMEOUT_MS, x as runGit, y as REBASE_TIMEOUT_MS } from "../worktreeOperations-BSeyxUai.mjs";
1
+ import { C as READ_TIMEOUT_MS, E as runGit, S as NETWORK_TIMEOUT_MS, T as createWorktreeGit, a as GIT_WORKTREE_LIFECYCLE_EVENT, c as isWorktreeLifecycleEvent, d as registryFile, f as readJson, g as invalidRequest, h as PoGitExpectedError, i as GIT_SESSION_CHANGED_EVENT, l as credentialsFile, m as HubUnavailableError, n as createWorktreeRegistry, o as createWorktreeMessageInbox, p as writeJsonAtomic, r as validRefName, s as isSessionChangedEvent, t as createWorktreeOperations, u as poGitRoot, w as REBASE_TIMEOUT_MS } from "../worktreeOperations-BqLLypS5.mjs";
2
2
  import fs from "node:fs";
3
- import os from "node:os";
4
3
  import path from "node:path";
5
- import { composeServerProviderOverrides, defineServerPlugin, readPackageResource } from "@agimon-ai/po-core/serverFacet";
6
- import { composeRootHooks, defineChannel, defineHook, defineResource, defineRoot, defineRoute, defineServerTool } from "@agimon-ai/po-core/extensionFile";
4
+ import os from "node:os";
5
+ import { composeServerProviderOverrides, defineServerPlugin, packageResourcePath, readPackageResource } from "@agimon-ai/po-core/serverPlugin";
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,9 +467,9 @@ function progressResult(action, label) {
302
467
  details: { action }
303
468
  };
304
469
  }
305
- function createGitSession({ context, host: serverHost }) {
306
- if (serverHost.context.directEvents === void 0) throw new Error("Git headless facet requires the session direct event bus.");
307
- if (serverHost.context.sessionId === void 0) throw new Error("Git headless facet requires a session identity.");
470
+ function createGitSession({ context, host: serverHost, agent, signal }) {
471
+ if (serverHost.context.directEvents === void 0) throw new Error("Git server plugin requires the session direct event bus.");
472
+ if (serverHost.context.sessionId === void 0) throw new Error("Git server plugin requires a session identity.");
308
473
  const sessionId = serverHost.context.sessionId;
309
474
  const directEvents = serverHost.context.directEvents;
310
475
  const messageInbox = createWorktreeMessageInbox(directEvents, sessionId);
@@ -315,20 +480,12 @@ 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
- name: "po-use-git",
483
+ name: "codemode-how-to-git",
484
+ 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.",
330
485
  kind: "skill",
331
- read: () => readPackageResource(import.meta.url, "src/prompts/po-use-git/SKILL.md")
486
+ pinned: true,
487
+ path: packageResourcePath(import.meta.url, "src/prompts/codemode-how-to-git/SKILL.md"),
488
+ read: () => readPackageResource(import.meta.url, "src/prompts/codemode-how-to-git/SKILL.md")
332
489
  }];
333
490
  const tool = {
334
491
  name: RUN_WORKTREE_TOOL_NAME,
@@ -348,17 +505,37 @@ function createGitSession({ context, host: serverHost }) {
348
505
  const changed = (_event, execution) => {
349
506
  directEvents.publish(GIT_SESSION_CHANGED_EVENT, execution.sessionId, { version: 1 });
350
507
  };
508
+ const hooks = [{
509
+ event: "tool_execution_end",
510
+ handle: changed
511
+ }, {
512
+ event: "agent_settled",
513
+ handle: changed
514
+ }];
515
+ if (agent) {
516
+ const namespace = createWorktreeNamespace({
517
+ operations,
518
+ execution: agent.context,
519
+ assertAvailable: () => {
520
+ if (signal?.aborted) throw new Error("Git session is unavailable.");
521
+ }
522
+ });
523
+ context.inject([PO_CODEMODE_SERVICE], (scoped) => {
524
+ const codemode = readPoCodemode(scoped);
525
+ if (!codemode) return void 0;
526
+ const registration = codemode.register("@agimon-ai/po-git", namespace);
527
+ const restriction = agent.registerToolRestriction({ excludedTools: [RUN_WORKTREE_TOOL_NAME] });
528
+ return () => {
529
+ restriction.dispose();
530
+ registration.dispose();
531
+ };
532
+ });
533
+ }
351
534
  return {
352
535
  value: {
353
536
  resources,
354
537
  tool,
355
- hooks: [{
356
- event: "tool_execution_end",
357
- handle: changed
358
- }, {
359
- event: "agent_settled",
360
- handle: changed
361
- }]
538
+ hooks
362
539
  },
363
540
  onDispose: () => messageInbox.close()
364
541
  };
@@ -1583,9 +1760,9 @@ const GIT_CHANGES_TYPE = "git_changes";
1583
1760
  * DESIGN PATTERNS:
1584
1761
  * - Apart from the worktrees channel. Worktrees are repository management;
1585
1762
  * this is the session's own code change.
1586
- * - Push, not poll. The session facet publishes a "changed" event after every
1587
- * tool call and settled turn; recounts are coalesced to one in flight plus
1588
- * one rerun, so a burst of tool calls costs at most two.
1763
+ * - Push, not poll. The session's server plugin publishes a "changed" event
1764
+ * after every tool call and settled turn; recounts are coalesced to one in
1765
+ * flight plus one rerun, so a burst of tool calls costs at most two.
1589
1766
  * - One sync command per session. A rebase and a push must never overlap.
1590
1767
  * - Auth is read fresh for each command from the session's workspace, and goes
1591
1768
  * only into that command's git child.
@@ -1853,11 +2030,15 @@ function parseCommand(payload) {
1853
2030
  /**
1854
2031
  * What the reader is told when a command fails.
1855
2032
  *
1856
- * An expected failure explains itself. Anything else is summarised, never
1857
- * echoed: a git error can carry a remote URL with a token in it.
2033
+ * An expected failure explains itself, minus the `[code]` tag and recovery line
2034
+ * written for the agent's tool result. A dirty close is said in the person's
2035
+ * terms, because its message tells the agent to pass `force`. Anything else is
2036
+ * summarised, never echoed: a git error can carry a remote URL with a token in it.
1858
2037
  */
1859
2038
  function failureText(error) {
1860
- return error instanceof PoGitExpectedError ? error.message : "The worktree operation failed.";
2039
+ if (!(error instanceof PoGitExpectedError)) return "The worktree operation failed.";
2040
+ if (error.code === "worktree_dirty") return "The worktree has uncommitted changes.";
2041
+ return error.message.replace(/^\[[a-z_]+\] /u, "").split("\nRecovery:")[0] ?? error.message;
1861
2042
  }
1862
2043
  /** Starts one direct-event subscription per managed session. */
1863
2044
  function createWorktreesChannel(options = {}) {
@@ -2028,20 +2209,8 @@ var agent_settled_server_default = defineHook((context) => context.root.hooks[1]
2028
2209
  //#region src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.ts
2029
2210
  var tool_execution_end_server_default = defineHook((context) => context.root.hooks[0]);
2030
2211
  //#endregion
2031
- //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poGit.server.ts
2032
- var poGit_server_default$1 = (context) => context.root.resources[0];
2033
- //#endregion
2034
- //#region src/extensions/workspaces/sessions/(backend)/resource/poGit.server.ts
2035
- var poGit_server_default = defineResource(poGit_server_default$1);
2036
- //#endregion
2037
- //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poGitReadme.server.ts
2038
- var poGitReadme_server_default$1 = (context) => context.root.resources[2];
2039
- //#endregion
2040
- //#region src/extensions/workspaces/sessions/(backend)/resource/poGitReadme.server.ts
2041
- var poGitReadme_server_default = defineResource(poGitReadme_server_default$1);
2042
- //#endregion
2043
2212
  //#region src/extensions/workspaces/sessions/(backend)/resource/_lib/poUseGit.server.ts
2044
- var poUseGit_server_default$1 = (context) => context.root.resources[1];
2213
+ var poUseGit_server_default$1 = (context) => context.root.resources[0];
2045
2214
  //#endregion
2046
2215
  //#region src/extensions/workspaces/sessions/(backend)/resource/poUseGit.server.ts
2047
2216
  var poUseGit_server_default = defineResource(poUseGit_server_default$1);
@@ -2051,19 +2220,22 @@ var runWorktree_server_default = defineServerTool((context) => context.root.tool
2051
2220
  //#endregion
2052
2221
  //#region generated/server.ts
2053
2222
  const at = (value, context) => typeof value === "function" ? value(context) : value;
2223
+ /* v8 ignore next -- generated normalization branch */
2224
+ const defined = (values) => values.filter((value) => value !== void 0);
2054
2225
  const via = (identity, value) => value === void 0 ? void 0 : {
2055
2226
  ...identity,
2056
2227
  ...value
2057
2228
  };
2058
- const facet = defineServerPlugin({
2229
+ const serverPlugin = defineServerPlugin({
2059
2230
  name: "@agimon-ai/po-git",
2060
2231
  providerOverrides: composeServerProviderOverrides(root_server_default),
2232
+ injects: composeRootInjects({ session: [root_server_default] }),
2061
2233
  global: (context) => ({
2062
2234
  get api() {
2063
2235
  return [at(route_server_default, context)];
2064
2236
  },
2065
2237
  get channels() {
2066
- return [() => via({ frameType: "git_changes" }, git_changes_server_default()), () => via({ frameType: "git_worktrees" }, git_worktrees_server_default())];
2238
+ return [() => via({ frameType: "git_changes" }, at(git_changes_server_default, context)), () => via({ frameType: "git_worktrees" }, at(git_worktrees_server_default, context))];
2067
2239
  }
2068
2240
  }),
2069
2241
  workspace: (context) => ({
@@ -2071,11 +2243,11 @@ const facet = defineServerPlugin({
2071
2243
  return [at(route_server_default, context)];
2072
2244
  },
2073
2245
  get channels() {
2074
- return [() => via({ frameType: "git_changes" }, git_changes_server_default()), () => via({ frameType: "git_worktrees" }, git_worktrees_server_default())];
2246
+ 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))]);
2075
2247
  }
2076
2248
  }),
2077
2249
  session: async (context) => {
2078
- const scopeSession = await root_server_default(context);
2250
+ const scopeSession = await context.scopeRoot("session", root_server_default, context);
2079
2251
  const contextSession = {
2080
2252
  ...context,
2081
2253
  root: scopeSession.value
@@ -2086,17 +2258,13 @@ const facet = defineServerPlugin({
2086
2258
  return [at(route_server_default, context)];
2087
2259
  },
2088
2260
  get channels() {
2089
- return [() => via({ frameType: "git_changes" }, git_changes_server_default()), () => via({ frameType: "git_worktrees" }, git_worktrees_server_default())];
2261
+ 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))]);
2090
2262
  },
2091
2263
  get hooks() {
2092
2264
  return [via({ event: "agent_settled" }, at(agent_settled_server_default, contextSession)), via({ event: "tool_execution_end" }, at(tool_execution_end_server_default, contextSession))];
2093
2265
  },
2094
2266
  get resources() {
2095
- return [
2096
- at(poGit_server_default, contextSession),
2097
- at(poGitReadme_server_default, contextSession),
2098
- at(poUseGit_server_default, contextSession)
2099
- ];
2267
+ return [at(poUseGit_server_default, contextSession)];
2100
2268
  },
2101
2269
  get tools() {
2102
2270
  return [via({ name: "run_worktree" }, at(runWorktree_server_default, contextSession))];
@@ -2109,6 +2277,6 @@ const facet = defineServerPlugin({
2109
2277
  }
2110
2278
  });
2111
2279
  //#endregion
2112
- export { facet as default, facet };
2280
+ export { serverPlugin as default, serverPlugin };
2113
2281
 
2114
2282
  //# sourceMappingURL=server.mjs.map