@ilya-lesikov/pi-pi 0.1.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.
Files changed (271) hide show
  1. package/3p/pi-ask-user/index.ts +1835 -0
  2. package/3p/pi-ask-user/package.json +50 -0
  3. package/3p/pi-ask-user/single-select-layout.ts +203 -0
  4. package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
  5. package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
  6. package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
  7. package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
  8. package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
  9. package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
  10. package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
  11. package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
  12. package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
  13. package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
  14. package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
  15. package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
  16. package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
  17. package/3p/pi-lsp/package.json +54 -0
  18. package/3p/pi-plannotator/AGENTS.md +583 -0
  19. package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
  20. package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
  21. package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
  22. package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
  23. package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
  24. package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
  25. package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
  26. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
  27. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
  28. package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
  29. package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
  30. package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
  31. package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
  32. package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
  33. package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
  34. package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
  35. package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
  36. package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
  37. package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
  38. package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
  39. package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
  40. package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
  41. package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
  42. package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
  43. package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
  44. package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
  45. package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
  46. package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
  47. package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
  48. package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
  49. package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
  50. package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
  51. package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
  52. package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
  53. package/3p/pi-plannotator/package.json +53 -0
  54. package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
  55. package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
  56. package/3p/pi-plannotator/packages/ai/context.ts +250 -0
  57. package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
  58. package/3p/pi-plannotator/packages/ai/index.ts +105 -0
  59. package/3p/pi-plannotator/packages/ai/package.json +21 -0
  60. package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
  61. package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
  62. package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
  63. package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
  64. package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
  65. package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
  66. package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
  67. package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
  68. package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
  69. package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
  70. package/3p/pi-plannotator/packages/ai/types.ts +379 -0
  71. package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
  72. package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
  73. package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
  74. package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
  75. package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
  76. package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
  77. package/3p/pi-plannotator/packages/server/browser.ts +245 -0
  78. package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
  79. package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
  80. package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
  81. package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
  82. package/3p/pi-plannotator/packages/server/config.ts +8 -0
  83. package/3p/pi-plannotator/packages/server/draft.ts +1 -0
  84. package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
  85. package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
  86. package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
  87. package/3p/pi-plannotator/packages/server/git.ts +141 -0
  88. package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
  89. package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
  90. package/3p/pi-plannotator/packages/server/ide.ts +43 -0
  91. package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
  92. package/3p/pi-plannotator/packages/server/image.ts +66 -0
  93. package/3p/pi-plannotator/packages/server/index.ts +648 -0
  94. package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
  95. package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
  96. package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
  97. package/3p/pi-plannotator/packages/server/jj.ts +86 -0
  98. package/3p/pi-plannotator/packages/server/p4.ts +417 -0
  99. package/3p/pi-plannotator/packages/server/package.json +40 -0
  100. package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
  101. package/3p/pi-plannotator/packages/server/pr.ts +146 -0
  102. package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
  103. package/3p/pi-plannotator/packages/server/project.ts +45 -0
  104. package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
  105. package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
  106. package/3p/pi-plannotator/packages/server/remote.ts +74 -0
  107. package/3p/pi-plannotator/packages/server/repo.ts +80 -0
  108. package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
  109. package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
  110. package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
  111. package/3p/pi-plannotator/packages/server/review.ts +1318 -0
  112. package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
  113. package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
  114. package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
  115. package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
  116. package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
  117. package/3p/pi-plannotator/packages/server/storage.ts +17 -0
  118. package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
  119. package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
  120. package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
  121. package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
  122. package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
  123. package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
  124. package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
  125. package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
  126. package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
  127. package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
  128. package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
  129. package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
  130. package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
  131. package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
  132. package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
  133. package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
  134. package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
  135. package/3p/pi-plannotator/packages/shared/config.ts +262 -0
  136. package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
  137. package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
  138. package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
  139. package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
  140. package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
  141. package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
  142. package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
  143. package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
  144. package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
  145. package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
  146. package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
  147. package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
  148. package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
  149. package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
  150. package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
  151. package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
  152. package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
  153. package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
  154. package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
  155. package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
  156. package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
  157. package/3p/pi-plannotator/packages/shared/package.json +55 -0
  158. package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
  159. package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
  160. package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
  161. package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
  162. package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
  163. package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
  164. package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
  165. package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
  166. package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
  167. package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
  168. package/3p/pi-plannotator/packages/shared/project.ts +71 -0
  169. package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
  170. package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
  171. package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
  172. package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
  173. package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
  174. package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
  175. package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
  176. package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
  177. package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
  178. package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
  179. package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
  180. package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
  181. package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
  182. package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
  183. package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
  184. package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
  185. package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
  186. package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
  187. package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
  188. package/3p/pi-plannotator/packages/shared/types.ts +29 -0
  189. package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
  190. package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
  191. package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
  192. package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
  193. package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
  194. package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
  195. package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
  196. package/3p/pi-subagents/package.json +50 -0
  197. package/3p/pi-subagents/src/agent-manager.ts +413 -0
  198. package/3p/pi-subagents/src/agent-runner.ts +502 -0
  199. package/3p/pi-subagents/src/agent-types.ts +248 -0
  200. package/3p/pi-subagents/src/context.ts +58 -0
  201. package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
  202. package/3p/pi-subagents/src/custom-agents.ts +137 -0
  203. package/3p/pi-subagents/src/default-agents.ts +144 -0
  204. package/3p/pi-subagents/src/env.ts +33 -0
  205. package/3p/pi-subagents/src/group-join.ts +141 -0
  206. package/3p/pi-subagents/src/index.ts +1811 -0
  207. package/3p/pi-subagents/src/invocation-config.ts +40 -0
  208. package/3p/pi-subagents/src/memory.ts +165 -0
  209. package/3p/pi-subagents/src/model-resolver.ts +81 -0
  210. package/3p/pi-subagents/src/output-file.ts +77 -0
  211. package/3p/pi-subagents/src/prompts.ts +85 -0
  212. package/3p/pi-subagents/src/skill-loader.ts +79 -0
  213. package/3p/pi-subagents/src/types.ts +111 -0
  214. package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
  215. package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
  216. package/3p/pi-subagents/src/worktree.ts +162 -0
  217. package/3p/pi-tasks/package.json +52 -0
  218. package/3p/pi-tasks/src/auto-clear.ts +91 -0
  219. package/3p/pi-tasks/src/index.ts +1150 -0
  220. package/3p/pi-tasks/src/process-tracker.ts +140 -0
  221. package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
  222. package/3p/pi-tasks/src/task-store.ts +324 -0
  223. package/3p/pi-tasks/src/tasks-config.ts +27 -0
  224. package/3p/pi-tasks/src/types.ts +40 -0
  225. package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
  226. package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
  227. package/AGENTS.md +28 -0
  228. package/LICENSE +201 -0
  229. package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
  230. package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
  231. package/extensions/orchestrator/agents/explore.ts +33 -0
  232. package/extensions/orchestrator/agents/librarian.ts +43 -0
  233. package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
  234. package/extensions/orchestrator/agents/planner.ts +61 -0
  235. package/extensions/orchestrator/agents/registry.ts +156 -0
  236. package/extensions/orchestrator/agents/task.ts +50 -0
  237. package/extensions/orchestrator/agents/tool-routing.ts +84 -0
  238. package/extensions/orchestrator/ast-search.ts +85 -0
  239. package/extensions/orchestrator/cbm.ts +330 -0
  240. package/extensions/orchestrator/command-handlers.test.ts +163 -0
  241. package/extensions/orchestrator/command-handlers.ts +116 -0
  242. package/extensions/orchestrator/commands.test.ts +81 -0
  243. package/extensions/orchestrator/commands.ts +75 -0
  244. package/extensions/orchestrator/config.test.ts +221 -0
  245. package/extensions/orchestrator/config.ts +230 -0
  246. package/extensions/orchestrator/context.test.ts +293 -0
  247. package/extensions/orchestrator/context.ts +206 -0
  248. package/extensions/orchestrator/event-handlers.test.ts +190 -0
  249. package/extensions/orchestrator/event-handlers.ts +1416 -0
  250. package/extensions/orchestrator/exa.ts +104 -0
  251. package/extensions/orchestrator/flant-infra.ts +486 -0
  252. package/extensions/orchestrator/index.ts +80 -0
  253. package/extensions/orchestrator/integration.test.ts +1214 -0
  254. package/extensions/orchestrator/orchestrator.test.ts +252 -0
  255. package/extensions/orchestrator/orchestrator.ts +538 -0
  256. package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
  257. package/extensions/orchestrator/phases/brainstorm.ts +273 -0
  258. package/extensions/orchestrator/phases/implementation.ts +40 -0
  259. package/extensions/orchestrator/phases/machine.test.ts +293 -0
  260. package/extensions/orchestrator/phases/machine.ts +209 -0
  261. package/extensions/orchestrator/phases/planning.ts +255 -0
  262. package/extensions/orchestrator/phases/review.ts +193 -0
  263. package/extensions/orchestrator/plannotator.ts +56 -0
  264. package/extensions/orchestrator/pp-menu.ts +866 -0
  265. package/extensions/orchestrator/state.test.ts +343 -0
  266. package/extensions/orchestrator/state.ts +237 -0
  267. package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
  268. package/extensions/orchestrator/validate-artifacts.ts +272 -0
  269. package/extensions/orchestrator/vendor.d.ts +26 -0
  270. package/package.json +73 -0
  271. package/scripts/postinstall.sh +18 -0
@@ -0,0 +1,250 @@
1
+ /**
2
+ * conversation-viewer.ts — Live conversation overlay for viewing agent sessions.
3
+ *
4
+ * Displays a scrollable, live-updating view of an agent's conversation.
5
+ * Subscribes to session events for real-time streaming updates.
6
+ */
7
+
8
+ import type { AgentSession } from "@earendil-works/pi-coding-agent";
9
+ import { type Component, matchesKey, type TUI, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
10
+ import { extractText } from "../context.js";
11
+ import type { AgentRecord } from "../types.js";
12
+ import type { Theme } from "./agent-widget.js";
13
+ import { type AgentActivity, describeActivity, formatDuration, formatTokens, getDisplayName, getPromptModeLabel } from "./agent-widget.js";
14
+
15
+ /** Lines consumed by chrome: top border + header + header sep + footer sep + footer + bottom border. */
16
+ const CHROME_LINES = 6;
17
+ const MIN_VIEWPORT = 3;
18
+
19
+ export class ConversationViewer implements Component {
20
+ private scrollOffset = 0;
21
+ private autoScroll = true;
22
+ private unsubscribe: (() => void) | undefined;
23
+ private lastInnerW = 0;
24
+ private closed = false;
25
+
26
+ constructor(
27
+ private tui: TUI,
28
+ private session: AgentSession,
29
+ private record: AgentRecord,
30
+ private activity: AgentActivity | undefined,
31
+ private theme: Theme,
32
+ private done: (result: undefined) => void,
33
+ ) {
34
+ this.unsubscribe = session.subscribe(() => {
35
+ if (this.closed) return;
36
+ this.tui.requestRender();
37
+ });
38
+ }
39
+
40
+ handleInput(data: string): void {
41
+ if (matchesKey(data, "escape") || matchesKey(data, "q")) {
42
+ this.closed = true;
43
+ this.done(undefined);
44
+ return;
45
+ }
46
+
47
+ const totalLines = this.buildContentLines(this.lastInnerW).length;
48
+ const viewportHeight = this.viewportHeight();
49
+ const maxScroll = Math.max(0, totalLines - viewportHeight);
50
+
51
+ if (matchesKey(data, "up") || matchesKey(data, "k")) {
52
+ this.scrollOffset = Math.max(0, this.scrollOffset - 1);
53
+ this.autoScroll = this.scrollOffset >= maxScroll;
54
+ } else if (matchesKey(data, "down") || matchesKey(data, "j")) {
55
+ this.scrollOffset = Math.min(maxScroll, this.scrollOffset + 1);
56
+ this.autoScroll = this.scrollOffset >= maxScroll;
57
+ } else if (matchesKey(data, "pageUp")) {
58
+ this.scrollOffset = Math.max(0, this.scrollOffset - viewportHeight);
59
+ this.autoScroll = false;
60
+ } else if (matchesKey(data, "pageDown")) {
61
+ this.scrollOffset = Math.min(maxScroll, this.scrollOffset + viewportHeight);
62
+ this.autoScroll = this.scrollOffset >= maxScroll;
63
+ } else if (matchesKey(data, "home")) {
64
+ this.scrollOffset = 0;
65
+ this.autoScroll = false;
66
+ } else if (matchesKey(data, "end")) {
67
+ this.scrollOffset = maxScroll;
68
+ this.autoScroll = true;
69
+ }
70
+ }
71
+
72
+ render(width: number): string[] {
73
+ if (width < 6) return []; // too narrow for any meaningful rendering
74
+ const th = this.theme;
75
+ const innerW = width - 4; // border + padding
76
+ this.lastInnerW = innerW;
77
+ const lines: string[] = [];
78
+
79
+ const pad = (s: string, len: number) => {
80
+ const vis = visibleWidth(s);
81
+ return s + " ".repeat(Math.max(0, len - vis));
82
+ };
83
+ const row = (content: string) =>
84
+ th.fg("border", "│") + " " + truncateToWidth(pad(content, innerW), innerW) + " " + th.fg("border", "│");
85
+ const hrTop = th.fg("border", `╭${"─".repeat(width - 2)}╮`);
86
+ const hrBot = th.fg("border", `╰${"─".repeat(width - 2)}╯`);
87
+ const hrMid = row(th.fg("dim", "─".repeat(innerW)));
88
+
89
+ // Header
90
+ lines.push(hrTop);
91
+ const name = getDisplayName(this.record.type);
92
+ const modeLabel = getPromptModeLabel(this.record.type);
93
+ const modeTag = modeLabel ? ` ${th.fg("dim", `(${modeLabel})`)}` : "";
94
+ const statusIcon = this.record.status === "running"
95
+ ? th.fg("accent", "●")
96
+ : this.record.status === "completed"
97
+ ? th.fg("success", "✓")
98
+ : this.record.status === "error"
99
+ ? th.fg("error", "✗")
100
+ : th.fg("dim", "○");
101
+ const duration = formatDuration(this.record.startedAt, this.record.completedAt);
102
+
103
+ const headerParts: string[] = [duration];
104
+ const toolUses = this.activity?.toolUses ?? this.record.toolUses;
105
+ if (toolUses > 0) headerParts.unshift(`${toolUses} tool${toolUses === 1 ? "" : "s"}`);
106
+ if (this.activity?.session) {
107
+ try {
108
+ const tokens = this.activity.session.getSessionStats().tokens.total;
109
+ if (tokens > 0) headerParts.push(formatTokens(tokens));
110
+ } catch { /* */ }
111
+ }
112
+
113
+ lines.push(row(
114
+ `${statusIcon} ${th.bold(name)}${modeTag} ${th.fg("muted", this.record.description)} ${th.fg("dim", "·")} ${th.fg("dim", headerParts.join(" · "))}`,
115
+ ));
116
+ lines.push(hrMid);
117
+
118
+ // Content area — rebuild every render (live data, no cache needed)
119
+ const contentLines = this.buildContentLines(innerW);
120
+ const viewportHeight = this.viewportHeight();
121
+ const maxScroll = Math.max(0, contentLines.length - viewportHeight);
122
+
123
+ if (this.autoScroll) {
124
+ this.scrollOffset = maxScroll;
125
+ }
126
+
127
+ const visibleStart = Math.min(this.scrollOffset, maxScroll);
128
+ const visible = contentLines.slice(visibleStart, visibleStart + viewportHeight);
129
+
130
+ for (let i = 0; i < viewportHeight; i++) {
131
+ lines.push(row(visible[i] ?? ""));
132
+ }
133
+
134
+ // Footer
135
+ lines.push(hrMid);
136
+ const scrollPct = contentLines.length <= viewportHeight
137
+ ? "100%"
138
+ : `${Math.round(((visibleStart + viewportHeight) / contentLines.length) * 100)}%`;
139
+ const footerLeft = th.fg("dim", `${contentLines.length} lines · ${scrollPct}`);
140
+ const footerRight = th.fg("dim", "↑↓ scroll · PgUp/PgDn · Esc close");
141
+ const footerGap = Math.max(1, innerW - visibleWidth(footerLeft) - visibleWidth(footerRight));
142
+ lines.push(row(footerLeft + " ".repeat(footerGap) + footerRight));
143
+ lines.push(hrBot);
144
+
145
+ return lines;
146
+ }
147
+
148
+ invalidate(): void { /* no cached state to clear */ }
149
+
150
+ dispose(): void {
151
+ this.closed = true;
152
+ if (this.unsubscribe) {
153
+ this.unsubscribe();
154
+ this.unsubscribe = undefined;
155
+ }
156
+ }
157
+
158
+ // ---- Private ----
159
+
160
+ private viewportHeight(): number {
161
+ return Math.max(MIN_VIEWPORT, this.tui.terminal.rows - CHROME_LINES);
162
+ }
163
+
164
+ private buildContentLines(width: number): string[] {
165
+ if (width <= 0) return [];
166
+
167
+ const th = this.theme;
168
+ const messages = this.session.messages;
169
+ const lines: string[] = [];
170
+ const detab = (s: string) => s.replace(/\t/g, " ");
171
+
172
+ if (messages.length === 0) {
173
+ lines.push(th.fg("dim", "(waiting for first message...)"));
174
+ return lines;
175
+ }
176
+
177
+ let needsSeparator = false;
178
+ for (const msg of messages) {
179
+ if (msg.role === "user") {
180
+ const text = typeof msg.content === "string"
181
+ ? msg.content
182
+ : extractText(msg.content);
183
+ if (!text.trim()) continue;
184
+ if (needsSeparator) lines.push(th.fg("dim", "───"));
185
+ lines.push(th.fg("accent", "[User]"));
186
+ for (const line of wrapTextWithAnsi(detab(text.trim()), width)) {
187
+ lines.push(line);
188
+ }
189
+ } else if (msg.role === "assistant") {
190
+ const textParts: string[] = [];
191
+ const toolCalls: string[] = [];
192
+ for (const c of msg.content) {
193
+ if (c.type === "text" && c.text) textParts.push(c.text);
194
+ else if (c.type === "toolCall") {
195
+ toolCalls.push((c as any).name ?? (c as any).toolName ?? "unknown");
196
+ }
197
+ }
198
+ if (needsSeparator) lines.push(th.fg("dim", "───"));
199
+ lines.push(th.bold("[Assistant]"));
200
+ if (textParts.length > 0) {
201
+ for (const line of wrapTextWithAnsi(detab(textParts.join("\n").trim()), width)) {
202
+ lines.push(line);
203
+ }
204
+ }
205
+ for (const name of toolCalls) {
206
+ lines.push(truncateToWidth(th.fg("muted", ` [Tool: ${name}]`), width));
207
+ }
208
+ } else if (msg.role === "toolResult") {
209
+ const text = extractText(msg.content);
210
+ const truncated = text.length > 500 ? text.slice(0, 500) + "... (truncated)" : text;
211
+ if (!truncated.trim()) continue;
212
+ if (needsSeparator) lines.push(th.fg("dim", "───"));
213
+ lines.push(th.fg("dim", "[Result]"));
214
+ for (const line of wrapTextWithAnsi(detab(truncated.trim()), width)) {
215
+ lines.push(th.fg("dim", line));
216
+ }
217
+ } else if ((msg as any).role === "bashExecution") {
218
+ const bash = msg as any;
219
+ if (needsSeparator) lines.push(th.fg("dim", "───"));
220
+ lines.push(truncateToWidth(th.fg("muted", ` $ ${detab(bash.command)}`), width));
221
+ if (bash.output?.trim()) {
222
+ const out = bash.output.length > 500
223
+ ? bash.output.slice(0, 500) + "... (truncated)"
224
+ : bash.output;
225
+ for (const line of wrapTextWithAnsi(detab(out.trim()), width)) {
226
+ lines.push(th.fg("dim", line));
227
+ }
228
+ }
229
+ } else {
230
+ continue;
231
+ }
232
+ needsSeparator = true;
233
+ }
234
+
235
+ // Streaming indicator for running agents
236
+ if (this.record.status === "running" && this.activity) {
237
+ const act = describeActivity(this.activity.activeTools, this.activity.responseText);
238
+ lines.push("");
239
+ lines.push(truncateToWidth(th.fg("accent", "▍ ") + th.fg("dim", act), width));
240
+ }
241
+
242
+ return lines.map(l => {
243
+ let truncated = truncateToWidth(l, width);
244
+ while (visibleWidth(truncated) > width && truncated.length > 0) {
245
+ truncated = truncated.slice(0, -1);
246
+ }
247
+ return truncated;
248
+ });
249
+ }
250
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * worktree.ts — Git worktree isolation for agents.
3
+ *
4
+ * Creates a temporary git worktree so the agent works on an isolated copy of the repo.
5
+ * On completion, if no changes were made, the worktree is cleaned up.
6
+ * If changes exist, a branch is created and returned in the result.
7
+ */
8
+
9
+ import { execFileSync } from "node:child_process";
10
+ import { randomUUID } from "node:crypto";
11
+ import { existsSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+
15
+ export interface WorktreeInfo {
16
+ /** Absolute path to the worktree directory. */
17
+ path: string;
18
+ /** Branch name created for this worktree (if changes exist). */
19
+ branch: string;
20
+ }
21
+
22
+ export interface WorktreeCleanupResult {
23
+ /** Whether changes were found in the worktree. */
24
+ hasChanges: boolean;
25
+ /** Branch name if changes were committed. */
26
+ branch?: string;
27
+ /** Worktree path if it was kept. */
28
+ path?: string;
29
+ }
30
+
31
+ /**
32
+ * Create a temporary git worktree for an agent.
33
+ * Returns the worktree path, or undefined if not in a git repo.
34
+ */
35
+ export function createWorktree(cwd: string, agentId: string): WorktreeInfo | undefined {
36
+ // Verify we're in a git repo with at least one commit (HEAD must exist)
37
+ try {
38
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "pipe", timeout: 5000 });
39
+ execFileSync("git", ["rev-parse", "HEAD"], { cwd, stdio: "pipe", timeout: 5000 });
40
+ } catch {
41
+ return undefined;
42
+ }
43
+
44
+ const branch = `pi-agent-${agentId}`;
45
+ const suffix = randomUUID().slice(0, 8);
46
+ const worktreePath = join(tmpdir(), `pi-agent-${agentId}-${suffix}`);
47
+
48
+ try {
49
+ // Create detached worktree at HEAD
50
+ execFileSync("git", ["worktree", "add", "--detach", worktreePath, "HEAD"], {
51
+ cwd,
52
+ stdio: "pipe",
53
+ timeout: 30000,
54
+ });
55
+ return { path: worktreePath, branch };
56
+ } catch {
57
+ // If worktree creation fails, return undefined (agent runs in normal cwd)
58
+ return undefined;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Clean up a worktree after agent completion.
64
+ * - If no changes: remove worktree entirely.
65
+ * - If changes exist: create a branch, commit changes, return branch info.
66
+ */
67
+ export function cleanupWorktree(
68
+ cwd: string,
69
+ worktree: WorktreeInfo,
70
+ agentDescription: string,
71
+ ): WorktreeCleanupResult {
72
+ if (!existsSync(worktree.path)) {
73
+ return { hasChanges: false };
74
+ }
75
+
76
+ try {
77
+ // Check for uncommitted changes in the worktree
78
+ const status = execFileSync("git", ["status", "--porcelain"], {
79
+ cwd: worktree.path,
80
+ stdio: "pipe",
81
+ timeout: 10000,
82
+ }).toString().trim();
83
+
84
+ if (!status) {
85
+ // No changes — remove worktree
86
+ removeWorktree(cwd, worktree.path);
87
+ return { hasChanges: false };
88
+ }
89
+
90
+ // Changes exist — stage, commit, and create a branch
91
+ execFileSync("git", ["add", "-A"], { cwd: worktree.path, stdio: "pipe", timeout: 10000 });
92
+ // Truncate description for commit message (no shell sanitization needed — execFileSync uses argv)
93
+ const safeDesc = agentDescription.slice(0, 200);
94
+ const commitMsg = `pi-agent: ${safeDesc}`;
95
+ execFileSync("git", ["commit", "-m", commitMsg], {
96
+ cwd: worktree.path,
97
+ stdio: "pipe",
98
+ timeout: 10000,
99
+ });
100
+
101
+ // Create a branch pointing to the worktree's HEAD.
102
+ // If the branch already exists, append a suffix to avoid overwriting previous work.
103
+ let branchName = worktree.branch;
104
+ try {
105
+ execFileSync("git", ["branch", branchName], {
106
+ cwd: worktree.path,
107
+ stdio: "pipe",
108
+ timeout: 5000,
109
+ });
110
+ } catch {
111
+ // Branch already exists — use a unique suffix
112
+ branchName = `${worktree.branch}-${Date.now()}`;
113
+ execFileSync("git", ["branch", branchName], {
114
+ cwd: worktree.path,
115
+ stdio: "pipe",
116
+ timeout: 5000,
117
+ });
118
+ }
119
+ // Update branch name in worktree info for the caller
120
+ worktree.branch = branchName;
121
+
122
+ // Remove the worktree (branch persists in main repo)
123
+ removeWorktree(cwd, worktree.path);
124
+
125
+ return {
126
+ hasChanges: true,
127
+ branch: worktree.branch,
128
+ path: worktree.path,
129
+ };
130
+ } catch {
131
+ // Best effort cleanup on error
132
+ try { removeWorktree(cwd, worktree.path); } catch { /* ignore */ }
133
+ return { hasChanges: false };
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Force-remove a worktree.
139
+ */
140
+ function removeWorktree(cwd: string, worktreePath: string): void {
141
+ try {
142
+ execFileSync("git", ["worktree", "remove", "--force", worktreePath], {
143
+ cwd,
144
+ stdio: "pipe",
145
+ timeout: 10000,
146
+ });
147
+ } catch {
148
+ // If git worktree remove fails, try pruning
149
+ try {
150
+ execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
151
+ } catch { /* ignore */ }
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Prune any orphaned worktrees (crash recovery).
157
+ */
158
+ export function pruneWorktrees(cwd: string): void {
159
+ try {
160
+ execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
161
+ } catch { /* ignore */ }
162
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@tintinweb/pi-tasks",
3
+ "version": "0.7.0",
4
+ "description": "A pi extension that brings Claude Code-style task tracking and coordination to pi.",
5
+ "author": "tintinweb",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/tintinweb/pi-tasks.git"
10
+ },
11
+ "homepage": "https://github.com/tintinweb/pi-tasks#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/tintinweb/pi-tasks/issues"
14
+ },
15
+ "keywords": [
16
+ "pi",
17
+ "pi-extension",
18
+ "task",
19
+ "tasks",
20
+ "todo",
21
+ "coordination"
22
+ ],
23
+ "peerDependencies": {
24
+ "@earendil-works/pi-coding-agent": ">=0.74.0",
25
+ "@earendil-works/pi-tui": ">=0.74.0"
26
+ },
27
+ "dependencies": {
28
+ "typebox": "^1.1.34"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc",
32
+ "prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest",
35
+ "typecheck": "tsc --noEmit",
36
+ "lint": "biome check src/ test/",
37
+ "lint:fix": "biome check --fix src/ test/"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^25.0.0",
41
+ "typescript": "^6.0.0",
42
+ "@biomejs/biome": "^2.4.13",
43
+ "vitest": "^4.1.5"
44
+ },
45
+ "pi": {
46
+ "extensions": [
47
+ "./src/index.ts"
48
+ ],
49
+ "video": "https://github.com/tintinweb/pi-tasks/raw/master/media/demo.mp4",
50
+ "image": "https://github.com/tintinweb/pi-tasks/raw/master/media/screenshot.png"
51
+ }
52
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * auto-clear.ts — Turn-based auto-clearing of completed tasks.
3
+ *
4
+ * Two modes:
5
+ * - "on_task_complete": each completed task gets its own REMINDER_INTERVAL countdown, deleted individually
6
+ * - "on_list_complete": countdown starts when ALL tasks are completed, cleared as a batch
7
+ *
8
+ * Both use the same turn delay (REMINDER_INTERVAL) for consistency.
9
+ */
10
+
11
+ import type { TaskStore } from "./task-store.js";
12
+
13
+ export type AutoClearMode = "never" | "on_list_complete" | "on_task_complete";
14
+
15
+ export class AutoClearManager {
16
+ /** Per-task: turn when task was marked completed ("on_task_complete" mode). */
17
+ private completedAtTurn = new Map<string, number>();
18
+ /** Turn when ALL tasks became completed ("on_list_complete" mode). */
19
+ private allCompletedAtTurn: number | null = null;
20
+
21
+ constructor(
22
+ private getStore: () => TaskStore,
23
+ private getMode: () => AutoClearMode,
24
+ /** How many turns completed tasks linger before auto-clearing. */
25
+ private clearDelayTurns = 4,
26
+ ) {}
27
+
28
+ /** Record a task completion. Call AFTER cascade logic. */
29
+ trackCompletion(taskId: string, currentTurn: number): void {
30
+ const mode = this.getMode();
31
+ if (mode === "never") return;
32
+
33
+ if (mode === "on_task_complete") {
34
+ this.completedAtTurn.set(taskId, currentTurn);
35
+ } else if (mode === "on_list_complete") {
36
+ this.checkAllCompleted(currentTurn);
37
+ }
38
+ }
39
+
40
+ /** Check if all tasks are completed and start/reset the batch countdown. */
41
+ private checkAllCompleted(currentTurn: number): void {
42
+ const tasks = this.getStore().list();
43
+ if (tasks.length > 0 && tasks.every(t => t.status === "completed")) {
44
+ if (this.allCompletedAtTurn === null) this.allCompletedAtTurn = currentTurn;
45
+ } else {
46
+ this.allCompletedAtTurn = null;
47
+ }
48
+ }
49
+
50
+ /** Reset batch countdown (e.g., when a new task is created or task goes non-completed). */
51
+ resetBatchCountdown(): void {
52
+ this.allCompletedAtTurn = null;
53
+ }
54
+
55
+ /** Reset all tracking state (e.g., on new session). */
56
+ reset(): void {
57
+ this.completedAtTurn.clear();
58
+ this.allCompletedAtTurn = null;
59
+ }
60
+
61
+ /**
62
+ * Called on each turn start. Deletes tasks whose linger period has expired.
63
+ * Returns true if any tasks were cleared.
64
+ */
65
+ onTurnStart(currentTurn: number): boolean {
66
+ const mode = this.getMode();
67
+ let cleared = false;
68
+
69
+ if (mode === "on_task_complete") {
70
+ for (const [taskId, turn] of this.completedAtTurn) {
71
+ const task = this.getStore().get(taskId);
72
+ if (!task || task.status !== "completed") {
73
+ // Task was deleted or reverted — drop stale tracking entry
74
+ this.completedAtTurn.delete(taskId);
75
+ } else if (currentTurn - turn >= this.clearDelayTurns) {
76
+ this.getStore().delete(taskId);
77
+ this.completedAtTurn.delete(taskId);
78
+ cleared = true;
79
+ }
80
+ }
81
+ } else if (mode === "on_list_complete" && this.allCompletedAtTurn !== null) {
82
+ if (currentTurn - this.allCompletedAtTurn >= this.clearDelayTurns) {
83
+ this.getStore().clearCompleted();
84
+ this.allCompletedAtTurn = null;
85
+ cleared = true;
86
+ }
87
+ }
88
+
89
+ return cleared;
90
+ }
91
+ }