tailii-host 0.1.1 → 0.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.
Files changed (348) hide show
  1. package/README.md +27 -10
  2. package/dist/{broker.d.ts → approval/broker.d.ts} +9 -5
  3. package/dist/{broker.js → approval/broker.js} +42 -6
  4. package/dist/approval/broker.js.map +1 -0
  5. package/dist/{hook.d.ts → approval/hook.d.ts} +48 -7
  6. package/dist/{hook.js → approval/hook.js} +359 -50
  7. package/dist/approval/hook.js.map +1 -0
  8. package/dist/backend/herdr.d.ts +226 -0
  9. package/dist/backend/herdr.js +749 -0
  10. package/dist/backend/herdr.js.map +1 -0
  11. package/dist/backend/sessionBackend.d.ts +81 -0
  12. package/dist/backend/sessionBackend.js +114 -0
  13. package/dist/backend/sessionBackend.js.map +1 -0
  14. package/dist/backend/tmux.d.ts +125 -0
  15. package/dist/backend/tmux.js +392 -0
  16. package/dist/backend/tmux.js.map +1 -0
  17. package/dist/{chatTailController.d.ts → chat/chatTailController.d.ts} +29 -6
  18. package/dist/{chatTailController.js → chat/chatTailController.js} +94 -18
  19. package/dist/chat/chatTailController.js.map +1 -0
  20. package/dist/{imageService.d.ts → chat/imageService.d.ts} +12 -1
  21. package/dist/{imageService.js → chat/imageService.js} +39 -11
  22. package/dist/chat/imageService.js.map +1 -0
  23. package/dist/chat/remoteQuestionMonitor.d.ts +27 -0
  24. package/dist/chat/remoteQuestionMonitor.js +128 -0
  25. package/dist/chat/remoteQuestionMonitor.js.map +1 -0
  26. package/dist/chat/subagentTailer.d.ts +30 -0
  27. package/dist/chat/subagentTailer.js +704 -0
  28. package/dist/chat/subagentTailer.js.map +1 -0
  29. package/dist/chat/subagentTranscript.d.ts +20 -0
  30. package/dist/chat/subagentTranscript.js +273 -0
  31. package/dist/chat/subagentTranscript.js.map +1 -0
  32. package/dist/{transcriptTailer.d.ts → chat/transcriptTailer.d.ts} +12 -1
  33. package/dist/{transcriptTailer.js → chat/transcriptTailer.js} +183 -10
  34. package/dist/chat/transcriptTailer.js.map +1 -0
  35. package/dist/cli.js +15 -10
  36. package/dist/cli.js.map +1 -1
  37. package/dist/codex/codexAccountUsage.d.ts +22 -0
  38. package/dist/codex/codexAccountUsage.js +128 -0
  39. package/dist/codex/codexAccountUsage.js.map +1 -0
  40. package/dist/codex/codexAppServer.d.ts +166 -0
  41. package/dist/codex/codexAppServer.js +1297 -0
  42. package/dist/codex/codexAppServer.js.map +1 -0
  43. package/dist/codex/codexNativeTurnController.d.ts +153 -0
  44. package/dist/codex/codexNativeTurnController.js +673 -0
  45. package/dist/codex/codexNativeTurnController.js.map +1 -0
  46. package/dist/codex/codexRolloutTailer.d.ts +100 -0
  47. package/dist/codex/codexRolloutTailer.js +568 -0
  48. package/dist/codex/codexRolloutTailer.js.map +1 -0
  49. package/dist/{codexSessionStore.d.ts → codex/codexSessionStore.d.ts} +10 -2
  50. package/dist/{codexSessionStore.js → codex/codexSessionStore.js} +184 -15
  51. package/dist/codex/codexSessionStore.js.map +1 -0
  52. package/dist/codex/codexSubagentTracker.d.ts +21 -0
  53. package/dist/codex/codexSubagentTracker.js +281 -0
  54. package/dist/codex/codexSubagentTracker.js.map +1 -0
  55. package/dist/codex/codexToolActivity.d.ts +69 -0
  56. package/dist/codex/codexToolActivity.js +425 -0
  57. package/dist/codex/codexToolActivity.js.map +1 -0
  58. package/dist/{codexUsage.d.ts → codex/codexUsage.d.ts} +1 -1
  59. package/dist/{codexUsage.js → codex/codexUsage.js} +19 -26
  60. package/dist/codex/codexUsage.js.map +1 -0
  61. package/dist/commands/doctor.d.ts +65 -0
  62. package/dist/commands/doctor.js +425 -0
  63. package/dist/commands/doctor.js.map +1 -0
  64. package/dist/{hookSettings.d.ts → commands/hookSettings.d.ts} +14 -1
  65. package/dist/{hookSettings.js → commands/hookSettings.js} +96 -12
  66. package/dist/commands/hookSettings.js.map +1 -0
  67. package/dist/{kick.js → commands/kick.js} +16 -1
  68. package/dist/commands/kick.js.map +1 -0
  69. package/dist/{launch.d.ts → commands/launch.d.ts} +67 -5
  70. package/dist/commands/launch.js +716 -0
  71. package/dist/commands/launch.js.map +1 -0
  72. package/dist/commands/legacyHomeMigration.js.map +1 -0
  73. package/dist/{pairingCode.d.ts → commands/pairingCode.d.ts} +29 -2
  74. package/dist/{pairingCode.js → commands/pairingCode.js} +120 -11
  75. package/dist/commands/pairingCode.js.map +1 -0
  76. package/dist/{pushTokenCommand.d.ts → commands/pushTokenCommand.d.ts} +1 -1
  77. package/dist/{pushTokenCommand.js → commands/pushTokenCommand.js} +2 -2
  78. package/dist/commands/pushTokenCommand.js.map +1 -0
  79. package/dist/{setup.d.ts → commands/setup.d.ts} +17 -6
  80. package/dist/{setup.js → commands/setup.js} +133 -75
  81. package/dist/commands/setup.js.map +1 -0
  82. package/dist/engine/context.d.ts +145 -0
  83. package/dist/engine/context.js +184 -0
  84. package/dist/engine/context.js.map +1 -0
  85. package/dist/engine/engine.d.ts +121 -0
  86. package/dist/engine/engine.js +799 -0
  87. package/dist/engine/engine.js.map +1 -0
  88. package/dist/engine/handlers/accountUsage.d.ts +53 -0
  89. package/dist/engine/handlers/accountUsage.js +203 -0
  90. package/dist/engine/handlers/accountUsage.js.map +1 -0
  91. package/dist/engine/handlers/claudeModels.d.ts +2 -0
  92. package/dist/engine/handlers/claudeModels.js +23 -0
  93. package/dist/engine/handlers/claudeModels.js.map +1 -0
  94. package/dist/engine/handlers/codex.d.ts +2 -0
  95. package/dist/engine/handlers/codex.js +97 -0
  96. package/dist/engine/handlers/codex.js.map +1 -0
  97. package/dist/engine/handlers/conversation.d.ts +2 -0
  98. package/dist/engine/handlers/conversation.js +259 -0
  99. package/dist/engine/handlers/conversation.js.map +1 -0
  100. package/dist/engine/handlers/core.d.ts +2 -0
  101. package/dist/engine/handlers/core.js +66 -0
  102. package/dist/engine/handlers/core.js.map +1 -0
  103. package/dist/engine/handlers/git.d.ts +2 -0
  104. package/dist/engine/handlers/git.js +127 -0
  105. package/dist/engine/handlers/git.js.map +1 -0
  106. package/dist/engine/handlers/index.d.ts +2 -0
  107. package/dist/engine/handlers/index.js +32 -0
  108. package/dist/engine/handlers/index.js.map +1 -0
  109. package/dist/engine/handlers/mode.d.ts +2 -0
  110. package/dist/engine/handlers/mode.js +194 -0
  111. package/dist/engine/handlers/mode.js.map +1 -0
  112. package/dist/engine/handlers/officialApps.d.ts +2 -0
  113. package/dist/engine/handlers/officialApps.js +79 -0
  114. package/dist/engine/handlers/officialApps.js.map +1 -0
  115. package/dist/engine/handlers/preview.d.ts +2 -0
  116. package/dist/engine/handlers/preview.js +59 -0
  117. package/dist/engine/handlers/preview.js.map +1 -0
  118. package/dist/engine/handlers/session.d.ts +2 -0
  119. package/dist/engine/handlers/session.js +385 -0
  120. package/dist/engine/handlers/session.js.map +1 -0
  121. package/dist/engine/handlers/usage.d.ts +2 -0
  122. package/dist/engine/handlers/usage.js +96 -0
  123. package/dist/engine/handlers/usage.js.map +1 -0
  124. package/dist/engine/handlers/workspace.d.ts +2 -0
  125. package/dist/engine/handlers/workspace.js +110 -0
  126. package/dist/engine/handlers/workspace.js.map +1 -0
  127. package/dist/engine/slashCommands.d.ts +3 -0
  128. package/dist/engine/slashCommands.js +211 -0
  129. package/dist/engine/slashCommands.js.map +1 -0
  130. package/dist/hub/engineRelaySocket.d.ts +58 -0
  131. package/dist/hub/engineRelaySocket.js +161 -0
  132. package/dist/hub/engineRelaySocket.js.map +1 -0
  133. package/dist/hub/hubClient.d.ts +28 -0
  134. package/dist/hub/hubClient.js +187 -0
  135. package/dist/hub/hubClient.js.map +1 -0
  136. package/dist/hub/hubDaemon.d.ts +69 -0
  137. package/dist/hub/hubDaemon.js +466 -0
  138. package/dist/hub/hubDaemon.js.map +1 -0
  139. package/dist/hub/hubProtocol.d.ts +161 -0
  140. package/dist/hub/hubProtocol.js +379 -0
  141. package/dist/hub/hubProtocol.js.map +1 -0
  142. package/dist/hub/panePreviewPump.d.ts +65 -0
  143. package/dist/hub/panePreviewPump.js +187 -0
  144. package/dist/hub/panePreviewPump.js.map +1 -0
  145. package/dist/hub/questionInjection.d.ts +19 -0
  146. package/dist/hub/questionInjection.js +126 -0
  147. package/dist/hub/questionInjection.js.map +1 -0
  148. package/dist/hub/reaper.d.ts +74 -0
  149. package/dist/hub/reaper.js +333 -0
  150. package/dist/hub/reaper.js.map +1 -0
  151. package/dist/hub/sessionHub.d.ts +203 -0
  152. package/dist/hub/sessionHub.js +1862 -0
  153. package/dist/hub/sessionHub.js.map +1 -0
  154. package/dist/protocol/common.d.ts +18 -0
  155. package/dist/protocol/common.js +78 -0
  156. package/dist/protocol/common.js.map +1 -0
  157. package/dist/protocol/decode.d.ts +3 -0
  158. package/dist/protocol/decode.js +1036 -0
  159. package/dist/protocol/decode.js.map +1 -0
  160. package/dist/protocol/encode.d.ts +3 -0
  161. package/dist/protocol/encode.js +84 -0
  162. package/dist/protocol/encode.js.map +1 -0
  163. package/dist/protocol/messages.d.ts +1002 -0
  164. package/dist/protocol/messages.js +8 -0
  165. package/dist/protocol/messages.js.map +1 -0
  166. package/dist/protocol.d.ts +4 -329
  167. package/dist/protocol.js +13 -531
  168. package/dist/protocol.js.map +1 -1
  169. package/dist/push/apnsConfigStore.js.map +1 -0
  170. package/dist/push/apnsJwtProvider.js.map +1 -0
  171. package/dist/{apnsSender.js.map → push/apnsSender.js.map} +1 -1
  172. package/dist/{approvalPushNotifier.js → push/approvalPushNotifier.js} +1 -1
  173. package/dist/push/approvalPushNotifier.js.map +1 -0
  174. package/dist/push/deviceTokenStore.js.map +1 -0
  175. package/dist/{pushTypes.js → push/pushTypes.js} +1 -1
  176. package/dist/push/pushTypes.js.map +1 -0
  177. package/dist/push/sendLog.js.map +1 -0
  178. package/dist/services/accountIdentity.d.ts +44 -0
  179. package/dist/services/accountIdentity.js +184 -0
  180. package/dist/services/accountIdentity.js.map +1 -0
  181. package/dist/services/claudeCredentialRefresh.d.ts +30 -0
  182. package/dist/services/claudeCredentialRefresh.js +345 -0
  183. package/dist/services/claudeCredentialRefresh.js.map +1 -0
  184. package/dist/services/claudeModelCatalog.d.ts +21 -0
  185. package/dist/services/claudeModelCatalog.js +181 -0
  186. package/dist/services/claudeModelCatalog.js.map +1 -0
  187. package/dist/{dirLister.d.ts → services/dirLister.d.ts} +9 -4
  188. package/dist/services/dirLister.js +169 -0
  189. package/dist/services/dirLister.js.map +1 -0
  190. package/dist/services/fileService.d.ts +10 -0
  191. package/dist/services/fileService.js +123 -0
  192. package/dist/services/fileService.js.map +1 -0
  193. package/dist/services/gitService.d.ts +79 -0
  194. package/dist/services/gitService.js +648 -0
  195. package/dist/services/gitService.js.map +1 -0
  196. package/dist/services/hostVersions.d.ts +38 -0
  197. package/dist/services/hostVersions.js +120 -0
  198. package/dist/services/hostVersions.js.map +1 -0
  199. package/dist/services/officialApps.d.ts +148 -0
  200. package/dist/services/officialApps.js +1079 -0
  201. package/dist/services/officialApps.js.map +1 -0
  202. package/dist/services/planUsageFetcher.d.ts +119 -0
  203. package/dist/services/planUsageFetcher.js +357 -0
  204. package/dist/services/planUsageFetcher.js.map +1 -0
  205. package/dist/services/previewServer.d.ts +31 -0
  206. package/dist/services/previewServer.js +233 -0
  207. package/dist/services/previewServer.js.map +1 -0
  208. package/dist/services/quicGateway.d.ts +143 -0
  209. package/dist/services/quicGateway.js +292 -0
  210. package/dist/services/quicGateway.js.map +1 -0
  211. package/dist/services/serveService.d.ts +50 -0
  212. package/dist/services/serveService.js +302 -0
  213. package/dist/services/serveService.js.map +1 -0
  214. package/dist/services/usageAggregator.js.map +1 -0
  215. package/dist/sessions/claudeSessionStore.d.ts +80 -0
  216. package/dist/sessions/claudeSessionStore.js +477 -0
  217. package/dist/sessions/claudeSessionStore.js.map +1 -0
  218. package/dist/sessions/heartbeat.d.ts +24 -0
  219. package/dist/sessions/heartbeat.js +86 -0
  220. package/dist/sessions/heartbeat.js.map +1 -0
  221. package/dist/sessions/liveSessionJoin.d.ts +20 -0
  222. package/dist/sessions/liveSessionJoin.js +80 -0
  223. package/dist/sessions/liveSessionJoin.js.map +1 -0
  224. package/dist/sessions/sessionActivityProvider.d.ts +14 -0
  225. package/dist/sessions/sessionActivityProvider.js +57 -0
  226. package/dist/sessions/sessionActivityProvider.js.map +1 -0
  227. package/dist/{sessionListService.d.ts → sessions/sessionListService.d.ts} +3 -3
  228. package/dist/{sessionListService.js → sessions/sessionListService.js} +23 -6
  229. package/dist/sessions/sessionListService.js.map +1 -0
  230. package/dist/{sessionMetadataStore.d.ts → sessions/sessionMetadataStore.d.ts} +18 -0
  231. package/dist/{sessionMetadataStore.js → sessions/sessionMetadataStore.js} +36 -1
  232. package/dist/sessions/sessionMetadataStore.js.map +1 -0
  233. package/dist/{sessionSearch.d.ts → sessions/sessionSearch.d.ts} +1 -1
  234. package/dist/sessions/sessionSearch.js.map +1 -0
  235. package/dist/shared/hostDisplayName.d.ts +11 -0
  236. package/dist/shared/hostDisplayName.js +50 -0
  237. package/dist/shared/hostDisplayName.js.map +1 -0
  238. package/dist/{lineWriter.d.ts → shared/lineWriter.d.ts} +1 -1
  239. package/dist/{lineWriter.js → shared/lineWriter.js} +1 -1
  240. package/dist/shared/lineWriter.js.map +1 -0
  241. package/dist/{observationLog.js.map → shared/observationLog.js.map} +1 -1
  242. package/dist/{paths.d.ts → shared/paths.d.ts} +7 -0
  243. package/dist/{paths.js → shared/paths.js} +9 -0
  244. package/dist/shared/paths.js.map +1 -0
  245. package/dist/shared/permissionMode.d.ts +10 -0
  246. package/dist/shared/permissionMode.js +51 -0
  247. package/dist/shared/permissionMode.js.map +1 -0
  248. package/dist/shared/skillInjection.d.ts +9 -0
  249. package/dist/shared/skillInjection.js +16 -0
  250. package/dist/shared/skillInjection.js.map +1 -0
  251. package/dist/shared/sleep.js.map +1 -0
  252. package/dist/{socketPath.d.ts → shared/socketPath.d.ts} +4 -0
  253. package/dist/{socketPath.js → shared/socketPath.js} +22 -0
  254. package/dist/shared/socketPath.js.map +1 -0
  255. package/dist/shared/version.js.map +1 -0
  256. package/package.json +13 -7
  257. package/dist/apnsConfigStore.js.map +0 -1
  258. package/dist/apnsJwtProvider.js.map +0 -1
  259. package/dist/approvalPushNotifier.js.map +0 -1
  260. package/dist/broker.js.map +0 -1
  261. package/dist/chatTailController.js.map +0 -1
  262. package/dist/claudeSessionStore.d.ts +0 -15
  263. package/dist/claudeSessionStore.js +0 -201
  264. package/dist/claudeSessionStore.js.map +0 -1
  265. package/dist/codexRolloutTailer.d.ts +0 -56
  266. package/dist/codexRolloutTailer.js +0 -303
  267. package/dist/codexRolloutTailer.js.map +0 -1
  268. package/dist/codexSessionStore.js.map +0 -1
  269. package/dist/codexUsage.js.map +0 -1
  270. package/dist/deviceTokenStore.js.map +0 -1
  271. package/dist/dirLister.js +0 -98
  272. package/dist/dirLister.js.map +0 -1
  273. package/dist/doctor.d.ts +0 -36
  274. package/dist/doctor.js +0 -162
  275. package/dist/doctor.js.map +0 -1
  276. package/dist/engine.d.ts +0 -85
  277. package/dist/engine.js +0 -962
  278. package/dist/engine.js.map +0 -1
  279. package/dist/hook.js.map +0 -1
  280. package/dist/hookSettings.js.map +0 -1
  281. package/dist/idleReaper.d.ts +0 -13
  282. package/dist/idleReaper.js +0 -30
  283. package/dist/idleReaper.js.map +0 -1
  284. package/dist/imageService.js.map +0 -1
  285. package/dist/kick.js.map +0 -1
  286. package/dist/launch.js +0 -396
  287. package/dist/launch.js.map +0 -1
  288. package/dist/legacyHomeMigration.js.map +0 -1
  289. package/dist/lineWriter.js.map +0 -1
  290. package/dist/mdnsAdvertiser.d.ts +0 -46
  291. package/dist/mdnsAdvertiser.js +0 -268
  292. package/dist/mdnsAdvertiser.js.map +0 -1
  293. package/dist/pairingCode.js.map +0 -1
  294. package/dist/paths.js.map +0 -1
  295. package/dist/permissionMode.d.ts +0 -8
  296. package/dist/permissionMode.js +0 -32
  297. package/dist/permissionMode.js.map +0 -1
  298. package/dist/planUsageFetcher.d.ts +0 -35
  299. package/dist/planUsageFetcher.js +0 -172
  300. package/dist/planUsageFetcher.js.map +0 -1
  301. package/dist/pushTokenCommand.js.map +0 -1
  302. package/dist/pushTypes.js.map +0 -1
  303. package/dist/sendLog.js.map +0 -1
  304. package/dist/sessionActivityProvider.d.ts +0 -7
  305. package/dist/sessionActivityProvider.js +0 -44
  306. package/dist/sessionActivityProvider.js.map +0 -1
  307. package/dist/sessionIdleTracker.d.ts +0 -19
  308. package/dist/sessionIdleTracker.js +0 -42
  309. package/dist/sessionIdleTracker.js.map +0 -1
  310. package/dist/sessionListService.js.map +0 -1
  311. package/dist/sessionMetadataStore.js.map +0 -1
  312. package/dist/sessionSearch.js.map +0 -1
  313. package/dist/setup.js.map +0 -1
  314. package/dist/sleep.js.map +0 -1
  315. package/dist/socketPath.js.map +0 -1
  316. package/dist/subagentTailer.d.ts +0 -15
  317. package/dist/subagentTailer.js +0 -399
  318. package/dist/subagentTailer.js.map +0 -1
  319. package/dist/tmux.d.ts +0 -62
  320. package/dist/tmux.js +0 -163
  321. package/dist/tmux.js.map +0 -1
  322. package/dist/transcriptTailer.js.map +0 -1
  323. package/dist/usageAggregator.js.map +0 -1
  324. package/dist/version.js.map +0 -1
  325. /package/dist/{kick.d.ts → commands/kick.d.ts} +0 -0
  326. /package/dist/{legacyHomeMigration.d.ts → commands/legacyHomeMigration.d.ts} +0 -0
  327. /package/dist/{legacyHomeMigration.js → commands/legacyHomeMigration.js} +0 -0
  328. /package/dist/{apnsConfigStore.d.ts → push/apnsConfigStore.d.ts} +0 -0
  329. /package/dist/{apnsConfigStore.js → push/apnsConfigStore.js} +0 -0
  330. /package/dist/{apnsJwtProvider.d.ts → push/apnsJwtProvider.d.ts} +0 -0
  331. /package/dist/{apnsJwtProvider.js → push/apnsJwtProvider.js} +0 -0
  332. /package/dist/{apnsSender.d.ts → push/apnsSender.d.ts} +0 -0
  333. /package/dist/{apnsSender.js → push/apnsSender.js} +0 -0
  334. /package/dist/{approvalPushNotifier.d.ts → push/approvalPushNotifier.d.ts} +0 -0
  335. /package/dist/{deviceTokenStore.d.ts → push/deviceTokenStore.d.ts} +0 -0
  336. /package/dist/{deviceTokenStore.js → push/deviceTokenStore.js} +0 -0
  337. /package/dist/{pushTypes.d.ts → push/pushTypes.d.ts} +0 -0
  338. /package/dist/{sendLog.d.ts → push/sendLog.d.ts} +0 -0
  339. /package/dist/{sendLog.js → push/sendLog.js} +0 -0
  340. /package/dist/{usageAggregator.d.ts → services/usageAggregator.d.ts} +0 -0
  341. /package/dist/{usageAggregator.js → services/usageAggregator.js} +0 -0
  342. /package/dist/{sessionSearch.js → sessions/sessionSearch.js} +0 -0
  343. /package/dist/{observationLog.d.ts → shared/observationLog.d.ts} +0 -0
  344. /package/dist/{observationLog.js → shared/observationLog.js} +0 -0
  345. /package/dist/{sleep.d.ts → shared/sleep.d.ts} +0 -0
  346. /package/dist/{sleep.js → shared/sleep.js} +0 -0
  347. /package/dist/{version.d.ts → shared/version.d.ts} +0 -0
  348. /package/dist/{version.js → shared/version.js} +0 -0
@@ -0,0 +1,704 @@
1
+ // subagentTailer.ts
2
+ // tailii (TS host) — サブエージェント進捗ツリー tail
3
+ // Swift 版 SubagentTailer.swift の移植。
4
+ // claude の `<sessionId>/subagents/agent-*.meta.json` と main/subagent transcript を監視し、
5
+ // subagent_node(spawn/running → completed/error)を engine チャネルへ流す。
6
+ import { execFile } from "node:child_process";
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+ import { PROTOCOL_V2 } from "../protocol.js";
10
+ import { abortableSleep } from "../shared/sleep.js";
11
+ import { extractToolActivities, TranscriptTailer } from "./transcriptTailer.js";
12
+ /** サブエージェント meta/jsonl と親 transcript の tool_result を監視する。 */
13
+ export class SubagentTailer {
14
+ pollIntervalMs;
15
+ tailIndefinitely;
16
+ orphanGraceMs;
17
+ orphanProbeIntervalMs;
18
+ probeOutputOpen;
19
+ jsonlPaths = new Map();
20
+ outputPaths = new Map();
21
+ constructor(options = {}) {
22
+ this.pollIntervalMs = options.pollIntervalMs ?? 50;
23
+ this.tailIndefinitely = options.tailIndefinitely ?? true;
24
+ this.orphanGraceMs = options.orphanGraceMs ?? 60_000;
25
+ this.orphanProbeIntervalMs = options.orphanProbeIntervalMs ?? 30_000;
26
+ this.probeOutputOpen = options.probeOutputOpen ?? defaultProbeOutputOpen;
27
+ }
28
+ /** 現在 tail 中の nodeId から transcript 実体を引く。 */
29
+ jsonlPath(nodeId) {
30
+ return this.jsonlPaths.get(nodeId) ?? null;
31
+ }
32
+ /** バックグラウンドコマンドの nodeId(taskId) から出力ファイルを引く。 */
33
+ outputPath(nodeId) {
34
+ return this.outputPaths.get(nodeId) ?? null;
35
+ }
36
+ async *streamProjectDir(projectDir, preferredSessionId, newerThanMs = null, signal) {
37
+ this.jsonlPaths.clear();
38
+ this.outputPaths.clear();
39
+ const start = Date.now();
40
+ let mainTranscript = null;
41
+ while (!signal?.aborted) {
42
+ mainTranscript = TranscriptTailer.resolveJsonl(projectDir, preferredSessionId, newerThanMs);
43
+ if (mainTranscript !== null)
44
+ break;
45
+ if (!this.tailIndefinitely)
46
+ return;
47
+ await abortableSleep(this.pollIntervalMs, signal);
48
+ if (!this.tailIndefinitely && Date.now() - start > this.pollIntervalMs)
49
+ return;
50
+ }
51
+ if (mainTranscript === null || signal?.aborted)
52
+ return;
53
+ yield* this.streamSession(mainTranscript, signal);
54
+ }
55
+ async *streamSession(mainTranscript, signal) {
56
+ const sessionId = path.basename(mainTranscript, ".jsonl");
57
+ const subagentsDir = path.join(path.dirname(mainTranscript), sessionId, "subagents");
58
+ const tracked = new Map();
59
+ const fileStates = new Map();
60
+ const ownerByToolUseId = new Map();
61
+ const resultByToolUseId = new Map();
62
+ const notificationByTaskId = new Map();
63
+ const bgCommandByTaskId = new Map();
64
+ const bgLastKeyByTaskId = new Map();
65
+ // 通知が握り潰された背景コマンドの孤児判定(taskId → 終了扱いにした ts)。
66
+ const bgOrphanTsByTaskId = new Map();
67
+ const bgFirstSeenMsByTaskId = new Map();
68
+ let lastOrphanProbeMs = 0;
69
+ let aggregateDirty = true;
70
+ while (!signal?.aborted) {
71
+ if (discoverMetaFiles(subagentsDir, tracked))
72
+ aggregateDirty = true;
73
+ for (const node of tracked.values()) {
74
+ if (node.jsonlPath !== null)
75
+ this.jsonlPaths.set(node.nodeId, node.jsonlPath);
76
+ }
77
+ const transcriptOwners = transcriptFiles(mainTranscript, tracked);
78
+ for (const transcript of transcriptOwners) {
79
+ const read = readNewLines(transcript.path, fileStates);
80
+ const node = transcript.nodeId === null ? null : (tracked.get(transcript.nodeId) ?? null);
81
+ if (read.reset) {
82
+ aggregateDirty = true;
83
+ if (node !== null) {
84
+ node.currentActivity = null;
85
+ node.lastJsonlTimestampMs = null;
86
+ }
87
+ }
88
+ if (node !== null)
89
+ node.firstJsonlTimestampMs = read.state.firstTimestampMs;
90
+ if (read.lines.length > 0)
91
+ aggregateDirty = true;
92
+ for (const line of read.lines) {
93
+ const lineTs = timestampMs(line);
94
+ if (read.state.firstTimestampMs === null && lineTs !== null) {
95
+ read.state.firstTimestampMs = lineTs;
96
+ }
97
+ if (node !== null) {
98
+ node.firstJsonlTimestampMs = read.state.firstTimestampMs;
99
+ if (lineTs !== null)
100
+ node.lastJsonlTimestampMs = lineTs;
101
+ }
102
+ for (const id of extractSpawnToolUseIds(line))
103
+ read.state.ownerByToolUseId.set(id, transcript.owner);
104
+ for (const spawn of extractBackgroundSpawns(line)) {
105
+ read.state.bgSpawnByToolUseId.set(spawn.id, { label: spawn.label, ts: lineTs });
106
+ }
107
+ for (const hit of extractToolResults(line)) {
108
+ read.state.resultByToolUseId.set(hit.id, {
109
+ isError: hit.isError,
110
+ ts: hit.ts,
111
+ asyncLaunch: hit.asyncLaunch,
112
+ });
113
+ const launch = hit.backgroundLaunch;
114
+ const spawn = read.state.bgSpawnByToolUseId.get(hit.id);
115
+ if (launch !== null && spawn !== undefined) {
116
+ read.state.bgCommandByTaskId.set(launch.taskId, {
117
+ taskId: launch.taskId,
118
+ toolUseId: hit.id,
119
+ owner: transcript.owner,
120
+ label: spawn.label,
121
+ startTs: spawn.ts ?? hit.ts,
122
+ outputPath: launch.outputPath,
123
+ });
124
+ }
125
+ // TaskStop で停止されたタスクは task-notification を残さない。
126
+ // 停止 ack を「静かな完了」として通知と同列に扱う。
127
+ if (hit.stoppedTaskId !== null) {
128
+ read.state.notificationByTaskId.set(hit.stoppedTaskId, {
129
+ status: "completed",
130
+ exitCode: null,
131
+ ts: hit.ts ?? lineTs,
132
+ });
133
+ }
134
+ }
135
+ const notification = extractTaskNotification(line);
136
+ if (notification !== null) {
137
+ read.state.notificationByTaskId.set(notification.taskId, {
138
+ status: notification.status,
139
+ exitCode: notification.exitCode,
140
+ ts: notification.ts ?? lineTs,
141
+ });
142
+ }
143
+ if (node !== null) {
144
+ const activity = latestActivitySummary(line);
145
+ if (activity !== null)
146
+ node.currentActivity = activity;
147
+ }
148
+ }
149
+ }
150
+ if (aggregateDirty) {
151
+ ownerByToolUseId.clear();
152
+ resultByToolUseId.clear();
153
+ notificationByTaskId.clear();
154
+ bgCommandByTaskId.clear();
155
+ for (const transcript of transcriptOwners) {
156
+ const state = fileStates.get(transcript.path);
157
+ if (state === undefined)
158
+ continue;
159
+ for (const [id, owner] of state.ownerByToolUseId)
160
+ ownerByToolUseId.set(id, owner);
161
+ for (const [id, result] of state.resultByToolUseId)
162
+ resultByToolUseId.set(id, result);
163
+ for (const [id, notification] of state.notificationByTaskId) {
164
+ const existing = notificationByTaskId.get(id);
165
+ if (existing === undefined || (notification.ts ?? 0) >= (existing.ts ?? 0)) {
166
+ notificationByTaskId.set(id, notification);
167
+ }
168
+ }
169
+ for (const [id, command] of state.bgCommandByTaskId)
170
+ bgCommandByTaskId.set(id, command);
171
+ }
172
+ aggregateDirty = false;
173
+ }
174
+ for (const node of tracked.values()) {
175
+ const result = resultByToolUseId.get(node.meta.toolUseId) ?? null;
176
+ let status;
177
+ let ts;
178
+ if (result !== null && !result.asyncLaunch) {
179
+ // 同期実行: 親 transcript の tool_result が終了信号。
180
+ status = result.isError ? "error" : "completed";
181
+ ts = result.ts ?? node.firstJsonlTimestampMs ?? mtimeMs(node.metaPath);
182
+ }
183
+ else {
184
+ // バックグラウンド実行(または結果未着): task-notification が終了信号。
185
+ // 通知後に自分の transcript が伸びたら resume とみなし running へ戻す。
186
+ const notification = notificationByTaskId.get(node.nodeId) ?? null;
187
+ const resumedAfter = notification !== null && notification.ts !== null
188
+ && (node.lastJsonlTimestampMs ?? 0) > notification.ts;
189
+ if (notification !== null && !resumedAfter) {
190
+ status = notification.status === "completed" ? "completed" : "error";
191
+ ts = notification.ts ?? node.lastJsonlTimestampMs ?? mtimeMs(node.metaPath);
192
+ }
193
+ else {
194
+ status = "running";
195
+ ts = node.firstJsonlTimestampMs ?? mtimeMs(node.metaPath);
196
+ }
197
+ }
198
+ const parentNodeId = ownerByToolUseId.get(node.meta.toolUseId) ?? fallbackParent(node.meta.spawnDepth);
199
+ const messageNode = {
200
+ nodeId: node.nodeId,
201
+ toolUseId: node.meta.toolUseId,
202
+ parentNodeId,
203
+ agentType: node.meta.agentType,
204
+ label: node.meta.description,
205
+ depth: node.meta.spawnDepth,
206
+ status,
207
+ currentActivity: status === "running" ? node.currentActivity : null,
208
+ ts,
209
+ };
210
+ const key = stableNodeKey(messageNode);
211
+ if (key === node.lastKey)
212
+ continue;
213
+ node.lastKey = key;
214
+ yield { type: "subagent_node", v: PROTOCOL_V2, node: messageNode };
215
+ }
216
+ // 孤児判定: task-notification は起動元エージェントが先に終了すると届かないまま
217
+ // 消えることがある(完了信号の消失)。output ファイルを開いているプロセスが
218
+ // いなければ「終了済みなのに通知が来ない孤児」として完了へ落とす。
219
+ // 実行中のコマンドはシェルが output の fd を保持し続けるため誤爆しない。
220
+ const nowMs = Date.now();
221
+ if (nowMs - lastOrphanProbeMs >= this.orphanProbeIntervalMs) {
222
+ lastOrphanProbeMs = nowMs;
223
+ for (const command of bgCommandByTaskId.values()) {
224
+ if (notificationByTaskId.has(command.taskId))
225
+ continue;
226
+ if (bgOrphanTsByTaskId.has(command.taskId))
227
+ continue;
228
+ if (command.outputPath === null)
229
+ continue;
230
+ let bornMs = bgFirstSeenMsByTaskId.get(command.taskId);
231
+ if (bornMs === undefined) {
232
+ bornMs = command.startTs ?? nowMs;
233
+ bgFirstSeenMsByTaskId.set(command.taskId, bornMs);
234
+ }
235
+ if (nowMs - bornMs < this.orphanGraceMs)
236
+ continue;
237
+ const open = await this.probeOutputOpen(command.outputPath);
238
+ if (open === false) {
239
+ const endTs = mtimeMs(command.outputPath);
240
+ bgOrphanTsByTaskId.set(command.taskId, endTs > 0 ? endTs : nowMs);
241
+ }
242
+ }
243
+ }
244
+ for (const command of bgCommandByTaskId.values()) {
245
+ if (command.outputPath !== null)
246
+ this.outputPaths.set(command.taskId, command.outputPath);
247
+ const notification = notificationByTaskId.get(command.taskId) ?? null;
248
+ // 遅配された本物の通知が届いたら exit code 込みでそちらを優先する。
249
+ const orphanTs = notification === null ? (bgOrphanTsByTaskId.get(command.taskId) ?? null) : null;
250
+ const failed = notification !== null
251
+ && (notification.status !== "completed" || (notification.exitCode ?? 0) !== 0);
252
+ const status = notification !== null
253
+ ? (failed ? "error" : "completed")
254
+ : (orphanTs !== null ? "completed" : "running");
255
+ const ownerNode = tracked.get(command.owner) ?? null;
256
+ const messageNode = {
257
+ nodeId: command.taskId,
258
+ toolUseId: command.toolUseId,
259
+ parentNodeId: command.owner,
260
+ agentType: "Bash",
261
+ label: command.label,
262
+ depth: ownerNode === null ? 1 : ownerNode.meta.spawnDepth + 1,
263
+ status,
264
+ currentActivity: null,
265
+ ts: notification?.ts ?? orphanTs ?? command.startTs ?? 0,
266
+ kind: "command",
267
+ };
268
+ const key = stableNodeKey(messageNode);
269
+ if (key === bgLastKeyByTaskId.get(command.taskId))
270
+ continue;
271
+ bgLastKeyByTaskId.set(command.taskId, key);
272
+ yield { type: "subagent_node", v: PROTOCOL_V2, node: messageNode };
273
+ }
274
+ if (!this.tailIndefinitely)
275
+ return;
276
+ await abortableSleep(this.pollIntervalMs, signal);
277
+ }
278
+ }
279
+ }
280
+ function discoverMetaFiles(dir, tracked) {
281
+ let files;
282
+ try {
283
+ files = fs.readdirSync(dir);
284
+ }
285
+ catch {
286
+ return false;
287
+ }
288
+ let changed = false;
289
+ for (const file of files.sort()) {
290
+ const match = /^agent-(.+)\.meta\.json$/.exec(file);
291
+ if (match === null)
292
+ continue;
293
+ const nodeId = match[1];
294
+ const existing = tracked.get(nodeId);
295
+ if (existing !== undefined) {
296
+ const jsonlPath = siblingJsonl(existing.metaPath);
297
+ if (existing.jsonlPath !== jsonlPath) {
298
+ existing.jsonlPath = jsonlPath;
299
+ changed = true;
300
+ }
301
+ continue;
302
+ }
303
+ const metaPath = path.join(dir, file);
304
+ const meta = readMeta(metaPath);
305
+ if (meta === null)
306
+ continue;
307
+ tracked.set(nodeId, {
308
+ nodeId,
309
+ meta,
310
+ metaPath,
311
+ jsonlPath: siblingJsonl(metaPath),
312
+ firstJsonlTimestampMs: null,
313
+ lastJsonlTimestampMs: null,
314
+ currentActivity: null,
315
+ lastKey: null,
316
+ });
317
+ changed = true;
318
+ }
319
+ return changed;
320
+ }
321
+ function readMeta(metaPath) {
322
+ try {
323
+ const parsed = JSON.parse(fs.readFileSync(metaPath, "utf8"));
324
+ if (typeof parsed["agentType"] !== "string" ||
325
+ typeof parsed["description"] !== "string" ||
326
+ typeof parsed["toolUseId"] !== "string" ||
327
+ typeof parsed["spawnDepth"] !== "number") {
328
+ return null;
329
+ }
330
+ return {
331
+ agentType: parsed["agentType"],
332
+ description: parsed["description"],
333
+ toolUseId: parsed["toolUseId"],
334
+ spawnDepth: parsed["spawnDepth"],
335
+ };
336
+ }
337
+ catch {
338
+ return null;
339
+ }
340
+ }
341
+ function siblingJsonl(metaPath) {
342
+ const jsonl = metaPath.replace(/\.meta\.json$/, ".jsonl");
343
+ return fs.existsSync(jsonl) ? jsonl : null;
344
+ }
345
+ function transcriptFiles(mainTranscript, tracked) {
346
+ const out = [
347
+ { path: mainTranscript, owner: "root", nodeId: null },
348
+ ];
349
+ for (const node of tracked.values()) {
350
+ if (node.jsonlPath !== null)
351
+ out.push({ path: node.jsonlPath, owner: node.nodeId, nodeId: node.nodeId });
352
+ }
353
+ return out;
354
+ }
355
+ function readNewLines(file, states) {
356
+ let stat;
357
+ try {
358
+ stat = fs.statSync(file);
359
+ }
360
+ catch {
361
+ const state = ensureFileState(file, states);
362
+ return { lines: [], reset: false, state };
363
+ }
364
+ const state = ensureFileState(file, states);
365
+ let reset = false;
366
+ if (stat.size < state.position) {
367
+ state.position = 0;
368
+ state.lineBuf = Buffer.alloc(0);
369
+ state.ownerByToolUseId.clear();
370
+ state.resultByToolUseId.clear();
371
+ state.notificationByTaskId.clear();
372
+ state.bgSpawnByToolUseId.clear();
373
+ state.bgCommandByTaskId.clear();
374
+ state.firstTimestampMs = null;
375
+ reset = true;
376
+ }
377
+ let fd;
378
+ try {
379
+ fd = fs.openSync(file, "r");
380
+ }
381
+ catch {
382
+ return { lines: [], reset, state };
383
+ }
384
+ const out = [];
385
+ const chunk = Buffer.alloc(4096);
386
+ try {
387
+ for (;;) {
388
+ const bytesRead = fs.readSync(fd, chunk, 0, chunk.length, state.position);
389
+ if (bytesRead === 0)
390
+ break;
391
+ state.position += bytesRead;
392
+ state.lineBuf = Buffer.concat([state.lineBuf, chunk.subarray(0, bytesRead)]);
393
+ let nl = state.lineBuf.indexOf(0x0a);
394
+ while (nl >= 0) {
395
+ const line = state.lineBuf.subarray(0, nl).toString("utf8").replaceAll("\r", "");
396
+ state.lineBuf = state.lineBuf.subarray(nl + 1);
397
+ if (line.length > 0)
398
+ out.push(line);
399
+ nl = state.lineBuf.indexOf(0x0a);
400
+ }
401
+ }
402
+ }
403
+ catch {
404
+ return { lines: out, reset, state };
405
+ }
406
+ finally {
407
+ // Subagent tailing watches a changing set of files, so each tick opens only for
408
+ // the incremental read and closes immediately. Offsets and partial lines stay cached.
409
+ try {
410
+ fs.closeSync(fd);
411
+ }
412
+ catch {
413
+ // 二重 close 等は無視。
414
+ }
415
+ }
416
+ return { lines: out, reset, state };
417
+ }
418
+ function ensureFileState(file, states) {
419
+ let state = states.get(file);
420
+ if (state === undefined) {
421
+ state = {
422
+ position: 0,
423
+ lineBuf: Buffer.alloc(0),
424
+ ownerByToolUseId: new Map(),
425
+ resultByToolUseId: new Map(),
426
+ notificationByTaskId: new Map(),
427
+ bgSpawnByToolUseId: new Map(),
428
+ bgCommandByTaskId: new Map(),
429
+ firstTimestampMs: null,
430
+ };
431
+ states.set(file, state);
432
+ }
433
+ return state;
434
+ }
435
+ function extractSpawnToolUseIds(line) {
436
+ const content = messageContent(line);
437
+ if (!Array.isArray(content))
438
+ return [];
439
+ const ids = [];
440
+ for (const block of content) {
441
+ if (typeof block !== "object" || block === null)
442
+ continue;
443
+ const rec = block;
444
+ if (rec["type"] !== "tool_use")
445
+ continue;
446
+ // 実データでは Agent、brief のサンプル/旧名では Task。どちらも同じ spawn path として扱う。
447
+ if (rec["name"] !== "Task" && rec["name"] !== "Agent")
448
+ continue;
449
+ if (typeof rec["id"] === "string")
450
+ ids.push(rec["id"]);
451
+ }
452
+ return ids;
453
+ }
454
+ function extractToolResults(line) {
455
+ const content = messageContent(line);
456
+ const ts = timestampMs(line);
457
+ if (!Array.isArray(content))
458
+ return [];
459
+ const out = [];
460
+ for (const block of content) {
461
+ if (typeof block !== "object" || block === null)
462
+ continue;
463
+ const rec = block;
464
+ if (rec["type"] !== "tool_result")
465
+ continue;
466
+ if (typeof rec["tool_use_id"] !== "string")
467
+ continue;
468
+ const text = toolResultPlainText(rec["content"]);
469
+ const bgMatch = /Command running in background with ID: (\S+?)\.?(?:\s|$)/.exec(text);
470
+ const outputMatch = /Output is being written to: (\S+?)\.?(?:\s|$)/.exec(text);
471
+ const stopMatch = /Successfully stopped task: ([A-Za-z0-9_-]+)/.exec(text);
472
+ out.push({
473
+ id: rec["tool_use_id"],
474
+ isError: rec["is_error"] === true,
475
+ ts,
476
+ asyncLaunch: text.startsWith("Async agent launched successfully"),
477
+ backgroundLaunch: bgMatch === null
478
+ ? null
479
+ : { taskId: bgMatch[1], outputPath: outputMatch?.[1] ?? null },
480
+ stoppedTaskId: stopMatch?.[1] ?? null,
481
+ });
482
+ }
483
+ return out;
484
+ }
485
+ function toolResultPlainText(content) {
486
+ if (typeof content === "string")
487
+ return content;
488
+ if (!Array.isArray(content))
489
+ return "";
490
+ const parts = [];
491
+ for (const block of content) {
492
+ if (typeof block !== "object" || block === null)
493
+ continue;
494
+ const rec = block;
495
+ if (rec["type"] === "text" && typeof rec["text"] === "string")
496
+ parts.push(rec["text"]);
497
+ }
498
+ return parts.join("\n");
499
+ }
500
+ /** Bash の run_in_background 起動(バックグラウンドコマンド)の tool_use を抽出する。 */
501
+ function extractBackgroundSpawns(line) {
502
+ const content = messageContent(line);
503
+ if (!Array.isArray(content))
504
+ return [];
505
+ const out = [];
506
+ for (const block of content) {
507
+ if (typeof block !== "object" || block === null)
508
+ continue;
509
+ const rec = block;
510
+ if (rec["type"] !== "tool_use" || rec["name"] !== "Bash")
511
+ continue;
512
+ if (typeof rec["id"] !== "string")
513
+ continue;
514
+ const input = typeof rec["input"] === "object" && rec["input"] !== null
515
+ ? rec["input"]
516
+ : null;
517
+ if (input?.["run_in_background"] !== true)
518
+ continue;
519
+ const description = typeof input["description"] === "string" ? input["description"] : "";
520
+ const command = typeof input["command"] === "string" ? input["command"] : "";
521
+ const label = description.length > 0 ? description : truncateActivityLabel(command);
522
+ out.push({ id: rec["id"], label: label.length > 0 ? label : "background command" });
523
+ }
524
+ return out;
525
+ }
526
+ /**
527
+ * 親 transcript の `<task-notification>` 行(user メッセージの文字列 content)を解析する。
528
+ * バックグラウンドのエージェント/コマンド共通の完了信号で、task-id はエージェントの
529
+ * nodeId またはコマンドの taskId に一致する。
530
+ */
531
+ function extractTaskNotification(line) {
532
+ const content = messageContent(line);
533
+ if (typeof content !== "string" || !content.includes("<task-notification>"))
534
+ return null;
535
+ const taskId = /<task-id>([^<]+)<\/task-id>/.exec(content)?.[1];
536
+ if (taskId === undefined)
537
+ return null;
538
+ const status = /<status>([^<]+)<\/status>/.exec(content)?.[1] ?? "completed";
539
+ const summary = /<summary>([^<]*)<\/summary>/.exec(content)?.[1] ?? "";
540
+ const exitCode = /exit code (\d+)/.exec(summary)?.[1];
541
+ return {
542
+ taskId,
543
+ status,
544
+ exitCode: exitCode === undefined ? null : Number(exitCode),
545
+ ts: timestampMs(line),
546
+ };
547
+ }
548
+ function latestActivitySummary(line) {
549
+ const activities = extractToolActivities(messageContent(line));
550
+ const latest = activities.at(-1);
551
+ return latest === undefined ? null : truncateActivityLabel(formatActivityLabel(latest));
552
+ }
553
+ function formatActivityLabel(activity) {
554
+ switch (activity.name) {
555
+ case "Bash":
556
+ return `Bash: ${activity.command ?? activity.description ?? stripActivityPrefix(activity.label, activity.name)}`;
557
+ case "Edit":
558
+ case "MultiEdit":
559
+ return `${activity.name} ${displayActivityTarget(activity)}`;
560
+ case "Write":
561
+ case "NotebookEdit":
562
+ return `${activity.name} ${displayActivityTarget(activity)}`;
563
+ case "Read":
564
+ return `Read ${displayActivityTarget(activity)}`;
565
+ case "TodoWrite":
566
+ return "Todo更新";
567
+ default: {
568
+ const detail = displayActivityTarget(activity);
569
+ return detail === activity.name ? activity.name : `${activity.name}: ${detail}`;
570
+ }
571
+ }
572
+ }
573
+ function displayActivityTarget(activity) {
574
+ if (activity.file !== undefined && activity.file.length > 0) {
575
+ const base = path.basename(activity.file);
576
+ return base.length > 0 ? base : activity.file;
577
+ }
578
+ return stripActivityPrefix(activity.label, activity.name);
579
+ }
580
+ function stripActivityPrefix(label, fallback) {
581
+ const stripped = label
582
+ .replace(/^(実行済み|編集済み|作成済み|既読|検索済み)\s*/, "")
583
+ .replace(/^Todoを更新しました$/, "Todo更新")
584
+ .trim();
585
+ return stripped.length > 0 ? stripped : fallback;
586
+ }
587
+ function truncateActivityLabel(label) {
588
+ const limit = 60;
589
+ return label.length <= limit ? label : `${label.slice(0, limit - 1)}…`;
590
+ }
591
+ function messageContent(line) {
592
+ try {
593
+ const obj = JSON.parse(line);
594
+ const message = obj["message"];
595
+ if (typeof message === "object" && message !== null) {
596
+ return message["content"];
597
+ }
598
+ return obj["content"];
599
+ }
600
+ catch {
601
+ return null;
602
+ }
603
+ }
604
+ function timestampMs(line) {
605
+ try {
606
+ const obj = JSON.parse(line);
607
+ if (typeof obj["timestamp"] !== "string")
608
+ return null;
609
+ const ms = Date.parse(obj["timestamp"]);
610
+ return Number.isFinite(ms) ? ms : null;
611
+ }
612
+ catch {
613
+ return null;
614
+ }
615
+ }
616
+ function mtimeMs(file) {
617
+ try {
618
+ return Math.floor(fs.statSync(file).mtimeMs);
619
+ }
620
+ catch {
621
+ return 0;
622
+ }
623
+ }
624
+ function fallbackParent(depth) {
625
+ return depth <= 1 ? "root" : null;
626
+ }
627
+ /**
628
+ * 背景コマンドの output ファイルを開いているプロセスの有無を調べる。
629
+ * ハーネスは背景シェルの stdout/stderr を output へ向けるため、コマンド(と子孫)が
630
+ * 生きている限り fd 保持者が存在する。true=生存 / false=誰も開いていない / null=判定不能。
631
+ */
632
+ async function defaultProbeOutputOpen(filePath) {
633
+ const viaLsof = await probeViaLsof(filePath);
634
+ if (viaLsof !== null)
635
+ return viaLsof;
636
+ return probeViaProcFd(filePath);
637
+ }
638
+ function probeViaLsof(filePath) {
639
+ return new Promise((resolve) => {
640
+ execFile("lsof", ["-t", "--", filePath], { timeout: 10_000 }, (error, stdout) => {
641
+ if (error === null) {
642
+ resolve(stdout.trim().length > 0);
643
+ return;
644
+ }
645
+ // exit 1 = 開いているプロセスなし(ファイル消失も同じ扱いで孤児側に倒す)。
646
+ // lsof 不在・タイムアウト等は判定不能として running 維持(安全側)。
647
+ const code = error.code;
648
+ resolve(code === 1 ? false : null);
649
+ });
650
+ });
651
+ }
652
+ /** lsof が無い Linux 向けフォールバック。/proc/<pid>/fd を走査する。 */
653
+ function probeViaProcFd(filePath) {
654
+ let target;
655
+ try {
656
+ target = fs.realpathSync(filePath);
657
+ }
658
+ catch {
659
+ return false;
660
+ }
661
+ let pids;
662
+ try {
663
+ pids = fs.readdirSync("/proc");
664
+ }
665
+ catch {
666
+ return null;
667
+ }
668
+ for (const pid of pids) {
669
+ if (!/^\d+$/.test(pid))
670
+ continue;
671
+ let fds;
672
+ try {
673
+ fds = fs.readdirSync(`/proc/${pid}/fd`);
674
+ }
675
+ catch {
676
+ continue;
677
+ }
678
+ for (const fd of fds) {
679
+ try {
680
+ if (fs.readlinkSync(`/proc/${pid}/fd/${fd}`) === target)
681
+ return true;
682
+ }
683
+ catch {
684
+ // 走査中に消えた fd は無視。
685
+ }
686
+ }
687
+ }
688
+ return false;
689
+ }
690
+ function stableNodeKey(node) {
691
+ return JSON.stringify([
692
+ node.nodeId,
693
+ node.toolUseId,
694
+ node.parentNodeId ?? null,
695
+ node.agentType,
696
+ node.label,
697
+ node.depth,
698
+ node.status,
699
+ node.currentActivity ?? null,
700
+ node.ts,
701
+ node.kind ?? null,
702
+ ]);
703
+ }
704
+ //# sourceMappingURL=subagentTailer.js.map