@ravshansbox/pi-github 1.1.0 → 1.3.0

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
@@ -7,7 +7,8 @@ Watches open issues in a GitHub repo and answers new issues and comments with [p
7
7
  - Polls the repo of the current directory for new issues and comments every 30 seconds. Pull requests are ignored.
8
8
  - Only handles issues and comments created after the first start. After a restart it picks up where it stopped, including runs a crash cut off. A crash right after posting a reply can lead to that reply being posted twice.
9
9
  - Each issue gets its own git worktree in `.worktrees/issue-<number>` on branch `issue-<number>`, and its own pi session that continues across comments.
10
- - New issues and comments get an 👀 reaction as soon as they are picked up.
10
+ - The terminal shows each issue's progress: every tool call the agent makes, failed tool calls, and when a branch is pushed or a pull request opened.
11
+ - New issues and comments get an 👀 reaction as soon as they are picked up, then 🚀 once answered or 😕 if the run failed.
11
12
  - Issues are handled in parallel, up to 3 at once. Comments on the same issue are handled one after another.
12
13
  - The agent's final message is posted as a comment that starts with `agent:`. Comments that start with `agent:` are ignored, so it does not answer itself.
13
14
  - If the agent made commits, the branch is pushed and a pull request is opened once. Later commits update the same pull request.
package/dist/handler.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { branchFor, ensureWorktree, hasUnpushedCommits, pushBranch } from "./git.js";
2
2
  import { agentPrefix } from "./poller.js";
3
- export async function handleTrigger(trigger, { github, agent, repoRoot }) {
3
+ export async function handleTrigger(trigger, { github, agent, repoRoot, log = () => { }, }) {
4
4
  const base = await github.getDefaultBranch();
5
5
  const worktree = await ensureWorktree(repoRoot, trigger.issueNumber, base);
6
- const answer = await agent.run(trigger, { cwd: worktree });
6
+ const answer = await agent.run(trigger, { cwd: worktree, progress: log });
7
7
  if (await hasUnpushedCommits(worktree, base)) {
8
- await pushBranch(worktree);
9
8
  const branch = branchFor(trigger.issueNumber);
9
+ await pushBranch(worktree);
10
+ log(`pushed ${branch}`);
10
11
  if (!(await github.hasOpenPullRequest(branch))) {
11
12
  const issue = await github.getIssue(trigger.issueNumber);
12
13
  await github.createPullRequest({
@@ -15,6 +16,7 @@ export async function handleTrigger(trigger, { github, agent, repoRoot }) {
15
16
  title: issue.title,
16
17
  body: `${agentPrefix} Closes #${trigger.issueNumber}`,
17
18
  });
19
+ log("opened a pull request");
18
20
  }
19
21
  }
20
22
  await github.postComment(trigger.issueNumber, `${agentPrefix} ${answer}`);
package/dist/pi-agent.js CHANGED
@@ -1,11 +1,18 @@
1
1
  import { join } from "node:path";
2
2
  import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
3
+ import { describeToolCall } from "./progress.js";
3
4
  import { buildPrompt } from "./prompt.js";
4
5
  export function createPiAgent({ github, repo, sessionsDir }) {
5
6
  return {
6
- async run(trigger, { cwd }) {
7
+ async run(trigger, { cwd, progress }) {
7
8
  const sessionManager = SessionManager.continueRecent(cwd, join(sessionsDir, `issue-${trigger.issueNumber}`));
8
9
  const { session } = await createAgentSession({ cwd, sessionManager });
10
+ const unsubscribe = session.subscribe((event) => {
11
+ if (event.type === "tool_execution_start" && !event.parentToolCallId)
12
+ progress(describeToolCall(event.toolName, event.args));
13
+ if (event.type === "tool_execution_end" && event.isError && !event.parentToolCallId)
14
+ progress(`${event.toolName} failed`);
15
+ });
9
16
  try {
10
17
  const issue = await github.getIssue(trigger.issueNumber);
11
18
  const firstInSession = session.messages.length === 0;
@@ -13,6 +20,7 @@ export function createPiAgent({ github, repo, sessionsDir }) {
13
20
  return session.getLastAssistantText() ?? "";
14
21
  }
15
22
  finally {
23
+ unsubscribe();
16
24
  session.dispose();
17
25
  }
18
26
  },
@@ -0,0 +1,8 @@
1
+ const maximumLength = 100;
2
+ export function describeToolCall(toolName, args) {
3
+ const values = args !== null && typeof args === "object" ? args : {};
4
+ const detail = ["command", "path", "pattern", "query"].map((key) => values[key]).find((value) => typeof value === "string") ??
5
+ JSON.stringify(args ?? {});
6
+ const line = detail.split("\n")[0] ?? "";
7
+ return `${toolName}: ${line.length > maximumLength ? `${line.slice(0, maximumLength - 1)}…` : line}`;
8
+ }
package/dist/watch.js CHANGED
@@ -30,12 +30,20 @@ export async function watch({ github, agent, repoRoot, stateFile, intervalMs, co
30
30
  let { pending = [], ...pollState } = await loadState(stateFile);
31
31
  let saving = Promise.resolve();
32
32
  const save = () => (saving = saving.then(() => saveState(stateFile, { ...pollState, pending })));
33
+ const react = (trigger, content) => github
34
+ .addReaction(trigger, content)
35
+ .catch((error) => log(`#${trigger.issueNumber}: reaction failed: ${error instanceof Error ? error.message : error}`));
33
36
  const dispatcher = createDispatcher({
34
37
  concurrency,
35
38
  handle: async (trigger) => {
36
39
  try {
37
- await handleTrigger(trigger, { github, agent, repoRoot });
40
+ await handleTrigger(trigger, { github, agent, repoRoot, log: (message) => log(`#${trigger.issueNumber}: ${message}`) });
38
41
  log(`#${trigger.issueNumber}: answered @${trigger.author}`);
42
+ await react(trigger, "rocket");
43
+ }
44
+ catch (error) {
45
+ await react(trigger, "confused");
46
+ throw error;
39
47
  }
40
48
  finally {
41
49
  pending = pending.filter((candidate) => candidate.key !== trigger.key);
@@ -57,7 +65,7 @@ export async function watch({ github, agent, repoRoot, stateFile, intervalMs, co
57
65
  for (const trigger of result.triggers) {
58
66
  log(`#${trigger.issueNumber}: new ${trigger.kind} from @${trigger.author}`);
59
67
  dispatcher.dispatch(trigger);
60
- await github.addReaction(trigger, "eyes").catch((error) => log(`#${trigger.issueNumber}: reaction failed: ${error instanceof Error ? error.message : error}`));
68
+ await react(trigger, "eyes");
61
69
  }
62
70
  }
63
71
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ravshansbox/pi-github",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Watch GitHub issues and answer them with pi agents",
5
5
  "type": "module",
6
6
  "license": "MIT",