@esso0428/pi-subagents 0.15.5 → 0.15.7

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 (234) hide show
  1. package/CHANGELOG.md +10 -19
  2. package/README.md +5 -5
  3. package/dist/agent-history-list.d.ts +19 -0
  4. package/dist/agent-history-list.d.ts.map +1 -0
  5. package/dist/agent-history-list.js +69 -0
  6. package/dist/agent-history-list.js.map +1 -0
  7. package/dist/agent-history.d.ts +2 -1
  8. package/dist/agent-history.d.ts.map +1 -0
  9. package/dist/agent-history.js +32 -34
  10. package/dist/agent-history.js.map +1 -0
  11. package/dist/agent-manager.d.ts +24 -8
  12. package/dist/agent-manager.d.ts.map +1 -0
  13. package/dist/agent-manager.js +171 -93
  14. package/dist/agent-manager.js.map +1 -0
  15. package/dist/agent-runner.d.ts +1 -0
  16. package/dist/agent-runner.d.ts.map +1 -0
  17. package/dist/agent-runner.js +75 -86
  18. package/dist/agent-runner.js.map +1 -0
  19. package/dist/agent-types.d.ts +1 -0
  20. package/dist/agent-types.d.ts.map +1 -0
  21. package/dist/agent-types.js +28 -45
  22. package/dist/agent-types.js.map +1 -0
  23. package/dist/context.d.ts +1 -0
  24. package/dist/context.d.ts.map +1 -0
  25. package/dist/context.js +3 -6
  26. package/dist/context.js.map +1 -0
  27. package/dist/cross-extension-rpc.d.ts +1 -0
  28. package/dist/cross-extension-rpc.d.ts.map +1 -0
  29. package/dist/cross-extension-rpc.js +6 -9
  30. package/dist/cross-extension-rpc.js.map +1 -0
  31. package/dist/custom-agents.d.ts +1 -0
  32. package/dist/custom-agents.d.ts.map +1 -0
  33. package/dist/custom-agents.js +16 -18
  34. package/dist/custom-agents.js.map +1 -0
  35. package/dist/default-agents.d.ts +1 -0
  36. package/dist/default-agents.d.ts.map +1 -0
  37. package/dist/default-agents.js +2 -4
  38. package/dist/default-agents.js.map +1 -0
  39. package/dist/enabled-models.d.ts +1 -0
  40. package/dist/enabled-models.d.ts.map +1 -0
  41. package/dist/enabled-models.js +12 -16
  42. package/dist/enabled-models.js.map +1 -0
  43. package/dist/env.d.ts +1 -0
  44. package/dist/env.d.ts.map +1 -0
  45. package/dist/env.js +2 -4
  46. package/dist/env.js.map +1 -0
  47. package/dist/group-join.d.ts +1 -0
  48. package/dist/group-join.d.ts.map +1 -0
  49. package/dist/group-join.js +2 -5
  50. package/dist/group-join.js.map +1 -0
  51. package/dist/index.d.ts +1 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +306 -292
  54. package/dist/index.js.map +1 -0
  55. package/dist/invocation-config.d.ts +1 -0
  56. package/dist/invocation-config.d.ts.map +1 -0
  57. package/dist/invocation-config.js +3 -6
  58. package/dist/invocation-config.js.map +1 -0
  59. package/dist/memory.d.ts +1 -0
  60. package/dist/memory.d.ts.map +1 -0
  61. package/dist/memory.js +25 -34
  62. package/dist/memory.js.map +1 -0
  63. package/dist/model-resolver.d.ts +1 -0
  64. package/dist/model-resolver.d.ts.map +1 -0
  65. package/dist/model-resolver.js +2 -4
  66. package/dist/model-resolver.js.map +1 -0
  67. package/dist/nico-overrides.d.ts +1 -0
  68. package/dist/nico-overrides.d.ts.map +1 -0
  69. package/dist/nico-overrides.js +15 -20
  70. package/dist/nico-overrides.js.map +1 -0
  71. package/dist/output-file.d.ts +1 -0
  72. package/dist/output-file.d.ts.map +1 -0
  73. package/dist/output-file.js +17 -22
  74. package/dist/output-file.js.map +1 -0
  75. package/dist/prompts.d.ts +1 -0
  76. package/dist/prompts.d.ts.map +1 -0
  77. package/dist/prompts.js +2 -4
  78. package/dist/prompts.js.map +1 -0
  79. package/dist/schedule-store.d.ts +1 -0
  80. package/dist/schedule-store.d.ts.map +1 -0
  81. package/dist/schedule-store.js +17 -21
  82. package/dist/schedule-store.js.map +1 -0
  83. package/dist/schedule.d.ts +1 -0
  84. package/dist/schedule.d.ts.map +1 -0
  85. package/dist/schedule.js +9 -12
  86. package/dist/schedule.js.map +1 -0
  87. package/dist/settings.d.ts +1 -0
  88. package/dist/settings.d.ts.map +1 -0
  89. package/dist/settings.js +16 -23
  90. package/dist/settings.js.map +1 -0
  91. package/dist/skill-loader.d.ts +1 -0
  92. package/dist/skill-loader.d.ts.map +1 -0
  93. package/dist/skill-loader.js +21 -23
  94. package/dist/skill-loader.js.map +1 -0
  95. package/dist/status-note.d.ts +1 -0
  96. package/dist/status-note.d.ts.map +1 -0
  97. package/dist/status-note.js +2 -4
  98. package/dist/status-note.js.map +1 -0
  99. package/dist/types.d.ts +5 -1
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +2 -4
  102. package/dist/types.js.map +1 -0
  103. package/dist/ui/agent-widget.d.ts +2 -5
  104. package/dist/ui/agent-widget.d.ts.map +1 -0
  105. package/dist/ui/agent-widget.js +29 -51
  106. package/dist/ui/agent-widget.js.map +1 -0
  107. package/dist/ui/ccstyle/diff/ansi-utils.d.ts +11 -0
  108. package/dist/ui/ccstyle/diff/ansi-utils.d.ts.map +1 -0
  109. package/dist/ui/ccstyle/diff/ansi-utils.js +145 -0
  110. package/dist/ui/ccstyle/diff/ansi-utils.js.map +1 -0
  111. package/dist/ui/ccstyle/diff/diff-presentation.d.ts +12 -0
  112. package/dist/ui/ccstyle/diff/diff-presentation.d.ts.map +1 -0
  113. package/dist/ui/ccstyle/diff/diff-presentation.js +49 -0
  114. package/dist/ui/ccstyle/diff/diff-presentation.js.map +1 -0
  115. package/dist/ui/ccstyle/diff/diff-renderer.d.ts +46 -0
  116. package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +1 -0
  117. package/dist/ui/ccstyle/diff/diff-renderer.js +2044 -0
  118. package/dist/ui/ccstyle/diff/diff-renderer.js.map +1 -0
  119. package/dist/ui/ccstyle/diff/line-width-safety.d.ts +12 -0
  120. package/dist/ui/ccstyle/diff/line-width-safety.d.ts.map +1 -0
  121. package/dist/ui/ccstyle/diff/line-width-safety.js +59 -0
  122. package/dist/ui/ccstyle/diff/line-width-safety.js.map +1 -0
  123. package/dist/ui/ccstyle/diff/render-utils.d.ts +6 -0
  124. package/dist/ui/ccstyle/diff/render-utils.d.ts.map +1 -0
  125. package/dist/ui/ccstyle/diff/render-utils.js +25 -0
  126. package/dist/ui/ccstyle/diff/render-utils.js.map +1 -0
  127. package/dist/ui/ccstyle/diff/types.d.ts +22 -0
  128. package/dist/ui/ccstyle/diff/types.d.ts.map +1 -0
  129. package/dist/ui/ccstyle/diff/types.js +10 -0
  130. package/dist/ui/ccstyle/diff/types.js.map +1 -0
  131. package/dist/ui/ccstyle/diff/write-display-utils.d.ts +2 -0
  132. package/dist/ui/ccstyle/diff/write-display-utils.d.ts.map +1 -0
  133. package/dist/ui/ccstyle/diff/write-display-utils.js +12 -0
  134. package/dist/ui/ccstyle/diff/write-display-utils.js.map +1 -0
  135. package/dist/ui/ccstyle/tool-renderer.d.ts +17 -0
  136. package/dist/ui/ccstyle/tool-renderer.d.ts.map +1 -0
  137. package/dist/ui/ccstyle/tool-renderer.js +58 -0
  138. package/dist/ui/ccstyle/tool-renderer.js.map +1 -0
  139. package/dist/ui/ccstyle/tool-result.d.ts +24 -0
  140. package/dist/ui/ccstyle/tool-result.d.ts.map +1 -0
  141. package/dist/ui/ccstyle/tool-result.js +193 -0
  142. package/dist/ui/ccstyle/tool-result.js.map +1 -0
  143. package/dist/ui/conversation-blocks.d.ts +46 -0
  144. package/dist/ui/conversation-blocks.d.ts.map +1 -0
  145. package/dist/ui/conversation-blocks.js +306 -0
  146. package/dist/ui/conversation-blocks.js.map +1 -0
  147. package/dist/ui/conversation-nvim.d.ts +8 -0
  148. package/dist/ui/conversation-nvim.d.ts.map +1 -0
  149. package/dist/ui/conversation-nvim.js +117 -0
  150. package/dist/ui/conversation-nvim.js.map +1 -0
  151. package/dist/ui/conversation-role.d.ts +13 -0
  152. package/dist/ui/conversation-role.d.ts.map +1 -0
  153. package/dist/ui/conversation-role.js +39 -0
  154. package/dist/ui/conversation-role.js.map +1 -0
  155. package/dist/ui/conversation-search.d.ts +39 -0
  156. package/dist/ui/conversation-search.d.ts.map +1 -0
  157. package/dist/ui/conversation-search.js +124 -0
  158. package/dist/ui/conversation-search.js.map +1 -0
  159. package/dist/ui/conversation-timeline.d.ts +62 -0
  160. package/dist/ui/conversation-timeline.d.ts.map +1 -0
  161. package/dist/ui/conversation-timeline.js +263 -0
  162. package/dist/ui/conversation-timeline.js.map +1 -0
  163. package/dist/ui/conversation-viewer.d.ts +62 -20
  164. package/dist/ui/conversation-viewer.d.ts.map +1 -0
  165. package/dist/ui/conversation-viewer.js +449 -224
  166. package/dist/ui/conversation-viewer.js.map +1 -0
  167. package/dist/ui/fleet-list.d.ts +24 -11
  168. package/dist/ui/fleet-list.d.ts.map +1 -0
  169. package/dist/ui/fleet-list.js +86 -78
  170. package/dist/ui/fleet-list.js.map +1 -0
  171. package/dist/ui/schedule-menu.d.ts +1 -0
  172. package/dist/ui/schedule-menu.d.ts.map +1 -0
  173. package/dist/ui/schedule-menu.js +2 -4
  174. package/dist/ui/schedule-menu.js.map +1 -0
  175. package/dist/ui/viewer-keys.d.ts +1 -0
  176. package/dist/ui/viewer-keys.d.ts.map +1 -0
  177. package/dist/ui/viewer-keys.js +8 -10
  178. package/dist/ui/viewer-keys.js.map +1 -0
  179. package/dist/usage.d.ts +1 -0
  180. package/dist/usage.d.ts.map +1 -0
  181. package/dist/usage.js +5 -10
  182. package/dist/usage.js.map +1 -0
  183. package/dist/worktree.d.ts +1 -0
  184. package/dist/worktree.d.ts.map +1 -0
  185. package/dist/worktree.js +27 -31
  186. package/dist/worktree.js.map +1 -0
  187. package/dist/write-execution.d.ts +42 -0
  188. package/dist/write-execution.d.ts.map +1 -0
  189. package/dist/write-execution.js +138 -0
  190. package/dist/write-execution.js.map +1 -0
  191. package/package.json +1 -1
  192. package/src/agent-history-list.ts +91 -0
  193. package/src/agent-history.ts +12 -17
  194. package/src/agent-manager.ts +169 -81
  195. package/src/agent-runner.ts +12 -13
  196. package/src/index.ts +117 -98
  197. package/src/output-file.ts +1 -1
  198. package/src/types.ts +4 -1
  199. package/src/ui/agent-widget.ts +3 -9
  200. package/src/ui/ccstyle/ATTRIBUTION.md +37 -0
  201. package/src/ui/ccstyle/diff/ansi-utils.ts +168 -0
  202. package/src/ui/ccstyle/diff/diff-presentation.ts +70 -0
  203. package/src/ui/ccstyle/diff/diff-renderer.ts +3148 -0
  204. package/src/ui/ccstyle/diff/line-width-safety.ts +92 -0
  205. package/src/ui/ccstyle/diff/render-utils.ts +32 -0
  206. package/src/ui/ccstyle/diff/types.ts +33 -0
  207. package/src/ui/ccstyle/diff/write-display-utils.ts +12 -0
  208. package/src/ui/ccstyle/tool-renderer.ts +71 -0
  209. package/src/ui/ccstyle/tool-result.ts +238 -0
  210. package/src/ui/conversation-blocks.ts +404 -0
  211. package/src/ui/conversation-nvim.ts +127 -0
  212. package/src/ui/conversation-role.ts +45 -0
  213. package/src/ui/conversation-search.ts +161 -0
  214. package/src/ui/conversation-timeline.ts +315 -0
  215. package/src/ui/conversation-viewer.ts +418 -210
  216. package/src/ui/fleet-list.ts +73 -52
  217. package/src/write-execution.ts +180 -0
  218. package/test/agent-history-list.test.ts +121 -0
  219. package/test/agent-manager-history.test.ts +59 -58
  220. package/test/agent-menu-history-navigation.test.ts +86 -0
  221. package/test/output-file-history.test.ts +50 -0
  222. package/test/ui/conversation-blocks.test.ts +103 -0
  223. package/test/ui/conversation-diff.test.ts +91 -0
  224. package/test/ui/conversation-search.test.ts +55 -0
  225. package/test/ui/conversation-viewer.test.ts +182 -0
  226. package/test/ui/fleet-list.test.ts +131 -0
  227. package/test/write-execution.test.ts +83 -0
  228. package/tsconfig.json +16 -0
  229. package/vitest.temp.config.mts +6 -0
  230. package/dist/ui/markdown-result.d.ts +0 -3
  231. package/dist/ui/markdown-result.js +0 -53
  232. package/src/ui/markdown-result.ts +0 -56
  233. package/test/agent-history.test.ts +0 -63
  234. package/test/ui/markdown-result.test.ts +0 -45
package/dist/usage.d.ts CHANGED
@@ -48,3 +48,4 @@ export declare function getSessionTokens(session: SessionLike | undefined): numb
48
48
  * (no model contextWindow, or post-compaction before the next response).
49
49
  */
50
50
  export declare function getSessionContextPercent(session: SessionLike | undefined): number | null;
51
+ //# sourceMappingURL=usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAAA,oFAAoF;AAEpF;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAElF,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,CAE1D;AAED,oEAAoE;AACpE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAIxE;AAED,+DAA+D;AAC/D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,YAAY,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAC3C,CAAC;AACF,MAAM,MAAM,WAAW,GAAG;IAAE,eAAe,IAAI,gBAAgB,CAAA;CAAE,CAAC;AAElE;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,MAAM,CAMzE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAIxF"}
package/dist/usage.js CHANGED
@@ -1,16 +1,10 @@
1
- "use strict";
2
1
  /** usage.ts — Token usage: shapes, accumulator operators, session-stats readers. */
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.getLifetimeTotal = getLifetimeTotal;
5
- exports.addUsage = addUsage;
6
- exports.getSessionTokens = getSessionTokens;
7
- exports.getSessionContextPercent = getSessionContextPercent;
8
2
  /** Sum of lifetime usage components, or 0 if undefined. */
9
- function getLifetimeTotal(u) {
3
+ export function getLifetimeTotal(u) {
10
4
  return u ? u.input + u.output + u.cacheWrite : 0;
11
5
  }
12
6
  /** Add a usage delta into a target accumulator (mutates target). */
13
- function addUsage(into, delta) {
7
+ export function addUsage(into, delta) {
14
8
  into.input += delta.input;
15
9
  into.output += delta.output;
16
10
  into.cacheWrite += delta.cacheWrite;
@@ -28,7 +22,7 @@ function addUsage(into, delta) {
28
22
  * and so counts the cumulative cached prefix N times across N turns
29
23
  * (issue #38).
30
24
  */
31
- function getSessionTokens(session) {
25
+ export function getSessionTokens(session) {
32
26
  if (!session)
33
27
  return 0;
34
28
  try {
@@ -43,7 +37,7 @@ function getSessionTokens(session) {
43
37
  * Context-window utilization (0–100), or null when unavailable
44
38
  * (no model contextWindow, or post-compaction before the next response).
45
39
  */
46
- function getSessionContextPercent(session) {
40
+ export function getSessionContextPercent(session) {
47
41
  if (!session)
48
42
  return null;
49
43
  try {
@@ -53,3 +47,4 @@ function getSessionContextPercent(session) {
53
47
  return null;
54
48
  }
55
49
  }
50
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAAA,oFAAoF;AAWpF,2DAA2D;AAC3D,MAAM,UAAU,gBAAgB,CAAC,CAAiB;IAChD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,QAAQ,CAAC,IAAmB,EAAE,KAAoB;IAChE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC;IAC1B,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC;IAC5B,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,UAAU,CAAC;AACtC,CAAC;AASD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgC;IAC/D,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,MAAM,CAAC;QAC3C,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,UAAU,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,CAAC,CAAC;IAAC,CAAC;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAgC;IACvE,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,CAAC;QAAC,OAAO,OAAO,CAAC,eAAe,EAAE,CAAC,YAAY,EAAE,OAAO,IAAI,IAAI,CAAC;IAAC,CAAC;IACvE,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AACxB,CAAC"}
@@ -43,3 +43,4 @@ export declare function cleanupWorktree(cwd: string, worktree: WorktreeInfo, age
43
43
  * Prune any orphaned worktrees (crash recovery).
44
44
  */
45
45
  export declare function pruneWorktrees(cwd: string): void;
46
+ //# sourceMappingURL=worktree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../src/worktree.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,kDAAkD;IAClD,UAAU,EAAE,OAAO,CAAC;IACpB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAqCrF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,YAAY,EACtB,gBAAgB,EAAE,MAAM,GACvB,qBAAqB,CAwEvB;AAoBD;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAIhD"}
package/dist/worktree.js CHANGED
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * worktree.ts — Git worktree isolation for agents.
4
3
  *
@@ -6,51 +5,47 @@
6
5
  * On completion, if no changes were made, the worktree is cleaned up.
7
6
  * If changes exist, a branch is created and returned in the result.
8
7
  */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.createWorktree = createWorktree;
11
- exports.cleanupWorktree = cleanupWorktree;
12
- exports.pruneWorktrees = pruneWorktrees;
13
- const node_child_process_1 = require("node:child_process");
14
- const node_crypto_1 = require("node:crypto");
15
- const node_fs_1 = require("node:fs");
16
- const node_os_1 = require("node:os");
17
- const node_path_1 = require("node:path");
8
+ import { execFileSync } from "node:child_process";
9
+ import { randomUUID } from "node:crypto";
10
+ import { existsSync, realpathSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join, relative } from "node:path";
18
13
  /**
19
14
  * Create a temporary git worktree for an agent.
20
15
  * Returns the worktree path, or undefined if not in a git repo.
21
16
  */
22
- function createWorktree(cwd, agentId) {
17
+ export function createWorktree(cwd, agentId) {
23
18
  // Verify we're in a git repo with at least one commit (HEAD must exist)
24
19
  let baseSha;
25
20
  let subdir;
26
21
  try {
27
- (0, node_child_process_1.execFileSync)("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "pipe", timeout: 5000 });
28
- baseSha = (0, node_child_process_1.execFileSync)("git", ["rev-parse", "HEAD"], { cwd, stdio: "pipe", timeout: 5000 })
22
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "pipe", timeout: 5000 });
23
+ baseSha = execFileSync("git", ["rev-parse", "HEAD"], { cwd, stdio: "pipe", timeout: 5000 })
29
24
  .toString()
30
25
  .trim();
31
26
  // Where cwd sits inside the repo ("" at the root): the agent must work at
32
27
  // the same subdirectory inside the copy, or a monorepo-package cwd would
33
28
  // silently widen to the whole repo. realpath both sides — git emits
34
29
  // resolved paths while cwd may arrive through a symlink (macOS /tmp).
35
- const topLevel = (0, node_child_process_1.execFileSync)("git", ["rev-parse", "--show-toplevel"], { cwd, stdio: "pipe", timeout: 5000 })
30
+ const topLevel = execFileSync("git", ["rev-parse", "--show-toplevel"], { cwd, stdio: "pipe", timeout: 5000 })
36
31
  .toString()
37
32
  .trim();
38
- subdir = (0, node_path_1.relative)((0, node_fs_1.realpathSync)(topLevel), (0, node_fs_1.realpathSync)(cwd));
33
+ subdir = relative(realpathSync(topLevel), realpathSync(cwd));
39
34
  }
40
35
  catch {
41
36
  return undefined;
42
37
  }
43
38
  const branch = `pi-agent-${agentId}`;
44
- const suffix = (0, node_crypto_1.randomUUID)().slice(0, 8);
45
- const worktreePath = (0, node_path_1.join)((0, node_os_1.tmpdir)(), `pi-agent-${agentId}-${suffix}`);
39
+ const suffix = randomUUID().slice(0, 8);
40
+ const worktreePath = join(tmpdir(), `pi-agent-${agentId}-${suffix}`);
46
41
  try {
47
42
  // Create detached worktree at HEAD
48
- (0, node_child_process_1.execFileSync)("git", ["worktree", "add", "--detach", worktreePath, "HEAD"], {
43
+ execFileSync("git", ["worktree", "add", "--detach", worktreePath, "HEAD"], {
49
44
  cwd,
50
45
  stdio: "pipe",
51
46
  timeout: 30000,
52
47
  });
53
- return { path: worktreePath, branch, baseSha, workPath: subdir ? (0, node_path_1.join)(worktreePath, subdir) : worktreePath };
48
+ return { path: worktreePath, branch, baseSha, workPath: subdir ? join(worktreePath, subdir) : worktreePath };
54
49
  }
55
50
  catch {
56
51
  // If worktree creation fails, return undefined (agent runs in normal cwd)
@@ -62,31 +57,31 @@ function createWorktree(cwd, agentId) {
62
57
  * - If no changes: remove worktree entirely.
63
58
  * - If changes exist: create a branch, commit changes, return branch info.
64
59
  */
65
- function cleanupWorktree(cwd, worktree, agentDescription) {
66
- if (!(0, node_fs_1.existsSync)(worktree.path)) {
60
+ export function cleanupWorktree(cwd, worktree, agentDescription) {
61
+ if (!existsSync(worktree.path)) {
67
62
  return { hasChanges: false };
68
63
  }
69
64
  try {
70
65
  // Check for uncommitted changes in the worktree
71
- const status = (0, node_child_process_1.execFileSync)("git", ["status", "--porcelain"], {
66
+ const status = execFileSync("git", ["status", "--porcelain"], {
72
67
  cwd: worktree.path,
73
68
  stdio: "pipe",
74
69
  timeout: 10000,
75
70
  }).toString().trim();
76
71
  if (status) {
77
72
  // Changes exist — stage, commit, and create a branch
78
- (0, node_child_process_1.execFileSync)("git", ["add", "-A"], { cwd: worktree.path, stdio: "pipe", timeout: 10000 });
73
+ execFileSync("git", ["add", "-A"], { cwd: worktree.path, stdio: "pipe", timeout: 10000 });
79
74
  // Truncate description for commit message (no shell sanitization needed — execFileSync uses argv)
80
75
  const safeDesc = agentDescription.slice(0, 200);
81
76
  const commitMsg = `pi-agent: ${safeDesc}`;
82
- (0, node_child_process_1.execFileSync)("git", ["commit", "--no-verify", "-m", commitMsg], {
77
+ execFileSync("git", ["commit", "--no-verify", "-m", commitMsg], {
83
78
  cwd: worktree.path,
84
79
  stdio: "pipe",
85
80
  timeout: 10000,
86
81
  });
87
82
  }
88
83
  else {
89
- const currentSha = (0, node_child_process_1.execFileSync)("git", ["rev-parse", "HEAD"], {
84
+ const currentSha = execFileSync("git", ["rev-parse", "HEAD"], {
90
85
  cwd: worktree.path,
91
86
  stdio: "pipe",
92
87
  timeout: 5000,
@@ -101,7 +96,7 @@ function cleanupWorktree(cwd, worktree, agentDescription) {
101
96
  // If the branch already exists, append a suffix to avoid overwriting previous work.
102
97
  let branchName = worktree.branch;
103
98
  try {
104
- (0, node_child_process_1.execFileSync)("git", ["branch", branchName], {
99
+ execFileSync("git", ["branch", branchName], {
105
100
  cwd: worktree.path,
106
101
  stdio: "pipe",
107
102
  timeout: 5000,
@@ -110,7 +105,7 @@ function cleanupWorktree(cwd, worktree, agentDescription) {
110
105
  catch {
111
106
  // Branch already exists — use a unique suffix
112
107
  branchName = `${worktree.branch}-${Date.now()}`;
113
- (0, node_child_process_1.execFileSync)("git", ["branch", branchName], {
108
+ execFileSync("git", ["branch", branchName], {
114
109
  cwd: worktree.path,
115
110
  stdio: "pipe",
116
111
  timeout: 5000,
@@ -140,7 +135,7 @@ function cleanupWorktree(cwd, worktree, agentDescription) {
140
135
  */
141
136
  function removeWorktree(cwd, worktreePath) {
142
137
  try {
143
- (0, node_child_process_1.execFileSync)("git", ["worktree", "remove", "--force", worktreePath], {
138
+ execFileSync("git", ["worktree", "remove", "--force", worktreePath], {
144
139
  cwd,
145
140
  stdio: "pipe",
146
141
  timeout: 10000,
@@ -149,7 +144,7 @@ function removeWorktree(cwd, worktreePath) {
149
144
  catch {
150
145
  // If git worktree remove fails, try pruning
151
146
  try {
152
- (0, node_child_process_1.execFileSync)("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
147
+ execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
153
148
  }
154
149
  catch { /* ignore */ }
155
150
  }
@@ -157,9 +152,10 @@ function removeWorktree(cwd, worktreePath) {
157
152
  /**
158
153
  * Prune any orphaned worktrees (crash recovery).
159
154
  */
160
- function pruneWorktrees(cwd) {
155
+ export function pruneWorktrees(cwd) {
161
156
  try {
162
- (0, node_child_process_1.execFileSync)("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
157
+ execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
163
158
  }
164
159
  catch { /* ignore */ }
165
160
  }
161
+ //# sourceMappingURL=worktree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree.js","sourceRoot":"","sources":["../src/worktree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { type ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+ /** Maximum UTF-8 size of the durable display diff plus unified patch. */
3
+ export declare const MAX_DURABLE_DIFF_BYTES: number;
4
+ /** Avoid retaining arbitrarily large previous files in the live-only store. */
5
+ export declare const MAX_COMPARABLE_WRITE_BYTES = 512000;
6
+ export declare const MAX_WRITE_METADATA_ENTRIES = 100;
7
+ export interface DurableWriteDetails {
8
+ diff?: string;
9
+ patch?: string;
10
+ firstChangedLine?: number;
11
+ diffUnavailableReason?: string;
12
+ }
13
+ /** Ephemeral execution metadata used by the live renderer. Never persisted as a before/after pair. */
14
+ export interface WriteExecutionMeta extends DurableWriteDetails {
15
+ fileExistedBeforeWrite: boolean;
16
+ previousContent?: string;
17
+ }
18
+ export declare class WriteExecutionMetadataStore {
19
+ readonly entries: Map<string, WriteExecutionMeta>;
20
+ set(toolCallId: string, metadata: WriteExecutionMeta): void;
21
+ get(toolCallId: unknown): WriteExecutionMeta | undefined;
22
+ delete(toolCallId: string): void;
23
+ clear(): void;
24
+ }
25
+ type WriteParams = {
26
+ path: string;
27
+ content: string;
28
+ };
29
+ export declare function executeWriteWithMetadata(store: WriteExecutionMetadataStore, toolCallId: string, params: WriteParams, signal: AbortSignal | undefined, cwd: string): Promise<{
30
+ content: Array<{
31
+ type: "text";
32
+ text: string;
33
+ }>;
34
+ details?: DurableWriteDetails;
35
+ }>;
36
+ /**
37
+ * A session-local write definition. It retains Pi's public schema/description
38
+ * and replaces only execution so the parent registry is never mutated.
39
+ */
40
+ export declare function createTrackedWriteTool(cwd: string, store?: WriteExecutionMetadataStore): ToolDefinition<any, DurableWriteDetails>;
41
+ export {};
42
+ //# sourceMappingURL=write-execution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-execution.d.ts","sourceRoot":"","sources":["../src/write-execution.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8F,KAAK,cAAc,EAAyB,MAAM,iCAAiC,CAAC;AAIzL,yEAAyE;AACzE,eAAO,MAAM,sBAAsB,QAAkB,CAAC;AACtD,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,SAAU,CAAC;AAClD,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,sGAAsG;AACtG,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,sBAAsB,EAAE,OAAO,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,2BAA2B;IACtC,QAAQ,CAAC,OAAO,kCAAyC;IAEzD,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,IAAI;IAU3D,GAAG,CAAC,UAAU,EAAE,OAAO,GAAG,kBAAkB,GAAG,SAAS;IAIxD,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIhC,KAAK,IAAI,IAAI;CAGd;AAED,KAAK,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AA2ErD,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,2BAA2B,EAClC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,mBAAmB,CAAA;CAAE,CAAC,CA0B5F;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,EACX,KAAK,8BAAoC,GACxC,cAAc,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAc1C"}
@@ -0,0 +1,138 @@
1
+ import { createWriteToolDefinition, generateDiffString, generateUnifiedPatch, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
2
+ import { lstat, mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname, isAbsolute, resolve } from "node:path";
4
+ /** Maximum UTF-8 size of the durable display diff plus unified patch. */
5
+ export const MAX_DURABLE_DIFF_BYTES = 4 * 1024 * 1024;
6
+ /** Avoid retaining arbitrarily large previous files in the live-only store. */
7
+ export const MAX_COMPARABLE_WRITE_BYTES = 512_000;
8
+ export const MAX_WRITE_METADATA_ENTRIES = 100;
9
+ export class WriteExecutionMetadataStore {
10
+ entries = new Map();
11
+ set(toolCallId, metadata) {
12
+ this.entries.delete(toolCallId);
13
+ this.entries.set(toolCallId, metadata);
14
+ while (this.entries.size > MAX_WRITE_METADATA_ENTRIES) {
15
+ const oldest = this.entries.keys().next().value;
16
+ if (oldest === undefined)
17
+ break;
18
+ this.entries.delete(oldest);
19
+ }
20
+ }
21
+ get(toolCallId) {
22
+ return typeof toolCallId === "string" ? this.entries.get(toolCallId) : undefined;
23
+ }
24
+ delete(toolCallId) {
25
+ this.entries.delete(toolCallId);
26
+ }
27
+ clear() {
28
+ this.entries.clear();
29
+ }
30
+ }
31
+ function resolveWritePath(path, cwd) {
32
+ if (path === "~")
33
+ return process.env.HOME ?? cwd;
34
+ if (path.startsWith("~/") || path.startsWith("~\\")) {
35
+ return resolve(process.env.HOME ?? cwd, path.slice(2));
36
+ }
37
+ return isAbsolute(path) ? path : resolve(cwd, path);
38
+ }
39
+ async function capturePreviousContent(absolutePath) {
40
+ let info;
41
+ try {
42
+ info = await lstat(absolutePath);
43
+ }
44
+ catch (error) {
45
+ if (error?.code === "ENOENT")
46
+ return { fileExistedBeforeWrite: false, previousContent: "" };
47
+ return {
48
+ fileExistedBeforeWrite: true,
49
+ diffUnavailableReason: "unable to inspect the previous file",
50
+ };
51
+ }
52
+ if (!info.isFile()) {
53
+ return {
54
+ fileExistedBeforeWrite: true,
55
+ diffUnavailableReason: "previous path is not a regular file",
56
+ };
57
+ }
58
+ if (info.size > MAX_COMPARABLE_WRITE_BYTES) {
59
+ return {
60
+ fileExistedBeforeWrite: true,
61
+ diffUnavailableReason: `previous file exceeds ${MAX_COMPARABLE_WRITE_BYTES} bytes`,
62
+ };
63
+ }
64
+ try {
65
+ const bytes = await readFile(absolutePath);
66
+ if (bytes.byteLength > MAX_COMPARABLE_WRITE_BYTES) {
67
+ return {
68
+ fileExistedBeforeWrite: true,
69
+ diffUnavailableReason: `previous file exceeds ${MAX_COMPARABLE_WRITE_BYTES} bytes`,
70
+ };
71
+ }
72
+ const previousContent = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
73
+ return { fileExistedBeforeWrite: true, previousContent };
74
+ }
75
+ catch {
76
+ return {
77
+ fileExistedBeforeWrite: true,
78
+ diffUnavailableReason: "previous file is not comparable UTF-8 text",
79
+ };
80
+ }
81
+ }
82
+ function durableDetails(metadata, path, content) {
83
+ if (metadata.diffUnavailableReason) {
84
+ return { diffUnavailableReason: metadata.diffUnavailableReason };
85
+ }
86
+ const previousContent = metadata.previousContent ?? "";
87
+ const diffResult = generateDiffString(previousContent, content);
88
+ const patch = generateUnifiedPatch(path, previousContent, content);
89
+ const totalBytes = Buffer.byteLength(diffResult.diff, "utf8") + Buffer.byteLength(patch, "utf8");
90
+ if (totalBytes > MAX_DURABLE_DIFF_BYTES) {
91
+ return { diffUnavailableReason: `durable diff exceeds ${MAX_DURABLE_DIFF_BYTES} bytes` };
92
+ }
93
+ return {
94
+ diff: diffResult.diff,
95
+ patch,
96
+ firstChangedLine: diffResult.firstChangedLine,
97
+ };
98
+ }
99
+ export async function executeWriteWithMetadata(store, toolCallId, params, signal, cwd) {
100
+ const absolutePath = resolveWritePath(params.path, cwd);
101
+ store.delete(toolCallId);
102
+ return withFileMutationQueue(absolutePath, async () => {
103
+ const throwIfAborted = () => {
104
+ if (signal?.aborted)
105
+ throw new Error("Operation aborted");
106
+ };
107
+ throwIfAborted();
108
+ const metadata = await capturePreviousContent(absolutePath);
109
+ throwIfAborted();
110
+ await mkdir(dirname(absolutePath), { recursive: true });
111
+ throwIfAborted();
112
+ await writeFile(absolutePath, params.content, "utf8");
113
+ throwIfAborted();
114
+ const details = durableDetails(metadata, params.path, params.content);
115
+ store.set(toolCallId, { ...metadata, ...details });
116
+ return {
117
+ content: [{ type: "text", text: `Successfully wrote to ${params.path}` }],
118
+ details,
119
+ };
120
+ }).catch((error) => {
121
+ store.delete(toolCallId);
122
+ throw error;
123
+ });
124
+ }
125
+ /**
126
+ * A session-local write definition. It retains Pi's public schema/description
127
+ * and replaces only execution so the parent registry is never mutated.
128
+ */
129
+ export function createTrackedWriteTool(cwd, store = new WriteExecutionMetadataStore()) {
130
+ const nativeWrite = createWriteToolDefinition(cwd);
131
+ return {
132
+ ...nativeWrite,
133
+ async execute(toolCallId, params, signal, _onUpdate, ctx) {
134
+ return executeWriteWithMetadata(store, toolCallId, params, signal, ctx.cwd || cwd);
135
+ },
136
+ };
137
+ }
138
+ //# sourceMappingURL=write-execution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-execution.js","sourceRoot":"","sources":["../src/write-execution.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esso0428/pi-subagents",
3
- "version": "0.15.5",
3
+ "version": "0.15.7",
4
4
  "description": "A pi extension that brings smart Claude Code-style autonomous sub-agents to pi, with npm:pi-subagents-style JSON agent overrides.",
5
5
  "author": "ESSO0428",
6
6
  "repository": {
@@ -0,0 +1,91 @@
1
+ /** Helpers for separating openable active agents from terminal history. */
2
+
3
+ import { hasAgentHistory } from "./agent-history.js";
4
+ import type { AgentRecord } from "./types.js";
5
+ import { getAgentConfig } from "./agent-types.js";
6
+ import { formatDuration, getDisplayName } from "./ui/agent-widget.js";
7
+
8
+ export type AgentHistoryStatus =
9
+ | "completed"
10
+ | "steered"
11
+ | "stopped"
12
+ | "aborted"
13
+ | "error";
14
+
15
+ const TERMINAL_STATUSES: ReadonlySet<string> = new Set<AgentHistoryStatus>([
16
+ "completed",
17
+ "steered",
18
+ "stopped",
19
+ "aborted",
20
+ "error",
21
+ ]);
22
+
23
+ /** Return whether a record status represents a terminal agent run. */
24
+ export function isTerminalAgentStatus(status: string): status is AgentHistoryStatus {
25
+ return TERMINAL_STATUSES.has(status);
26
+ }
27
+
28
+ /** Return whether an active record has a live session that can be opened. */
29
+ export function canOpenActiveAgent(record: AgentRecord): boolean {
30
+ return (record.status === "running" || record.status === "queued")
31
+ && record.session !== undefined;
32
+ }
33
+
34
+ /** Return whether a terminal record has an in-memory or durable conversation. */
35
+ export function canOpenAgentHistory(record: AgentRecord, cwd: string | undefined): boolean {
36
+ if (!isTerminalAgentStatus(record.status)) return false;
37
+ return record.session !== undefined
38
+ || (cwd !== undefined && hasAgentHistory(cwd, record.transcriptPath));
39
+ }
40
+
41
+ /** Split records into openable active and terminal-history buckets. */
42
+ export function splitAgentRecords(
43
+ records: readonly AgentRecord[],
44
+ cwd: string | undefined,
45
+ ): { active: AgentRecord[]; history: AgentRecord[] } {
46
+ const active: AgentRecord[] = [];
47
+ const history: AgentRecord[] = [];
48
+
49
+ for (const record of records) {
50
+ if (canOpenActiveAgent(record)) {
51
+ active.push(record);
52
+ } else if (canOpenAgentHistory(record, cwd)) {
53
+ history.push(record);
54
+ }
55
+ }
56
+
57
+ return { active, history };
58
+ }
59
+
60
+ const DESCRIPTION_LIMIT = 72;
61
+ const LABEL_LIMIT = 140;
62
+
63
+ function boundedDescription(description: string): string {
64
+ const line = description.split("\n").find((part) => part.trim())?.trim() ?? "";
65
+ if (line.length <= DESCRIPTION_LIMIT) return line;
66
+ return `${line.slice(0, DESCRIPTION_LIMIT - 1)}…`;
67
+ }
68
+
69
+ /** Format a bounded, single-line history menu option. */
70
+ export function formatAgentHistoryOption(record: AgentRecord, now: number): string {
71
+ const duration = formatDuration(record.startedAt, record.completedAt ?? now);
72
+ // Unit consumers may format persisted records before the registry has been
73
+ // initialized; preserve the persisted type instead of falling back to
74
+ // general-purpose's generic display name in that case.
75
+ const displayName = getAgentConfig(record.type)?.displayName
76
+ ?? (record.type === "general-purpose" ? getDisplayName(record.type) : record.type);
77
+ const option = `[${record.status}] ${displayName} · ${boundedDescription(record.description)} · ${duration}`;
78
+ return option.length <= LABEL_LIMIT ? option : `${option.slice(0, LABEL_LIMIT - 1)}…`;
79
+ }
80
+
81
+ /** Build the visible status-section labels for the agents menu. */
82
+ export function buildAgentStatusMenuEntries(
83
+ records: readonly AgentRecord[],
84
+ cwd: string | undefined,
85
+ ): string[] {
86
+ const { active, history } = splitAgentRecords(records, cwd);
87
+ return [
88
+ ...(active.length > 0 ? [`Running agents (${active.length})`] : []),
89
+ ...(history.length > 0 ? [`Agent history (${history.length})`] : []),
90
+ ];
91
+ }
@@ -19,14 +19,12 @@ export function ensureSubagentsGitignore(cwd: string): string {
19
19
  const directory = join(cwd, SUBAGENTS_DIR);
20
20
  mkdirSync(directory, { recursive: true });
21
21
  const path = join(directory, ".gitignore");
22
-
23
22
  let content = "";
24
23
  try {
25
24
  content = readFileSync(path, "utf8");
26
25
  } catch {
27
26
  // The append below also creates a missing file.
28
27
  }
29
-
30
28
  if (!content.split(/\r?\n/).some((line) => line.trim() === "*")) {
31
29
  const separator = content.length > 0 && !/[\r\n]$/.test(content) ? "\n" : "";
32
30
  appendFileSync(path, `${separator}*\n`, "utf8");
@@ -35,17 +33,12 @@ export function ensureSubagentsGitignore(cwd: string): string {
35
33
  }
36
34
 
37
35
  /** Return the durable transcript path for an agent. */
38
- export function createAgentHistoryPath(
39
- cwd: string,
40
- agentId: string,
41
- agentType = "agent",
42
- ): string {
36
+ export function createAgentHistoryPath(cwd: string, agentId: string): string {
43
37
  ensureSubagentsGitignore(cwd);
44
38
  const directory = join(cwd, SUBAGENTS_DIR, TRANSCRIPTS_DIR);
45
39
  mkdirSync(directory, { recursive: true });
46
40
  const safeId = agentId.replace(/[^A-Za-z0-9._-]+/g, "-") || "agent";
47
- const safeType = agentType.replace(/[^A-Za-z0-9._-]+/g, "-") || "agent";
48
- return join(directory, `${safeId}_${safeType}_transcript.jsonl`);
41
+ return join(directory, `${safeId}.jsonl`);
49
42
  }
50
43
 
51
44
  /** Return the project-relative path stored in the parent session record. */
@@ -55,7 +48,12 @@ export function agentHistoryLocator(cwd: string, historyPath: string): string {
55
48
 
56
49
  /** Resolve only paths in this package's project-local transcript namespace. */
57
50
  export function resolveAgentHistoryPath(cwd: string, locator: string): string | undefined {
58
- if (!locator || isAbsolute(locator)) return undefined;
51
+ // Locators are persisted data, not arbitrary paths. Reject traversal syntax
52
+ // before resolving so a path which normalizes back inside the namespace is
53
+ // still not accepted as an unsafe locator.
54
+ if (!locator || isAbsolute(locator) || locator.includes("\0") || locator.includes("\\")) return undefined;
55
+ const segments = locator.split("/");
56
+ if (segments.some((segment) => segment === ".." || segment === "")) return undefined;
59
57
  const root = resolve(cwd, SUBAGENTS_DIR, TRANSCRIPTS_DIR);
60
58
  const candidate = resolve(cwd, locator);
61
59
  if (candidate !== root && !candidate.startsWith(`${root}${sep}`)) return undefined;
@@ -74,12 +72,9 @@ export function hasAgentHistory(cwd: string, locator: string | undefined): boole
74
72
  * conversation viewer. Malformed lines and unknown records are skipped so one
75
73
  * damaged entry cannot hide the rest of a history.
76
74
  */
77
- export function readAgentHistory(
78
- cwd: string,
79
- locator: string,
80
- ): AgentSession["messages"] | undefined {
75
+ export function readAgentHistory(cwd: string, locator: string): AgentSession["messages"] | undefined {
81
76
  const path = resolveAgentHistoryPath(cwd, locator);
82
- if (!path) return undefined;
77
+ if (!path || !existsSync(path)) return undefined;
83
78
 
84
79
  let raw: string;
85
80
  try {
@@ -93,8 +88,8 @@ export function readAgentHistory(
93
88
  for (const line of raw.split(/\r?\n/)) {
94
89
  if (!line.trim()) continue;
95
90
  try {
96
- const entry = JSON.parse(line) as { message?: unknown };
97
- const message = entry.message;
91
+ const entry: unknown = JSON.parse(line);
92
+ const message = (entry as { message?: unknown } | null)?.message;
98
93
  if (!message || typeof message !== "object") continue;
99
94
  const role = (message as { role?: unknown }).role;
100
95
  if (typeof role !== "string") continue;