@toddzheng024/dscode-desktop 0.0.0-stage → 0.7.34

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 (485) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -2
  3. package/THIRD_PARTY_NOTICES.md +30 -0
  4. package/bin/apply_patch +9 -0
  5. package/cordis.patch.yml +433 -0
  6. package/docs/CHANGELOG.md +649 -0
  7. package/docs/account-login.md +44 -0
  8. package/docs/auto-review.md +128 -0
  9. package/docs/browser-use.md +784 -0
  10. package/docs/computer-use.md +81 -0
  11. package/docs/custom-providers.md +88 -0
  12. package/docs/demo.md +158 -0
  13. package/docs/distribution.md +58 -0
  14. package/docs/dscode-ultra.md +75 -0
  15. package/docs/email.md +296 -0
  16. package/docs/exec.md +40 -0
  17. package/docs/hub-distribution.md +212 -0
  18. package/docs/memory.md +121 -0
  19. package/docs/opencode-go.md +61 -0
  20. package/docs/plugin-hub.md +68 -0
  21. package/docs/releases/0.2.0.md +28 -0
  22. package/docs/releases/0.3.0.md +21 -0
  23. package/docs/releases/0.4.0.md +20 -0
  24. package/docs/releases/0.5.0.md +19 -0
  25. package/docs/releases/0.6.0.md +20 -0
  26. package/docs/releases/0.7.0.md +22 -0
  27. package/docs/releases/0.7.1.md +20 -0
  28. package/docs/releases/0.7.10.md +10 -0
  29. package/docs/releases/0.7.11.md +17 -0
  30. package/docs/releases/0.7.12.md +11 -0
  31. package/docs/releases/0.7.13.md +12 -0
  32. package/docs/releases/0.7.14.md +11 -0
  33. package/docs/releases/0.7.15.md +11 -0
  34. package/docs/releases/0.7.16.md +10 -0
  35. package/docs/releases/0.7.17.md +12 -0
  36. package/docs/releases/0.7.18.md +13 -0
  37. package/docs/releases/0.7.19.md +8 -0
  38. package/docs/releases/0.7.2.md +19 -0
  39. package/docs/releases/0.7.20.md +10 -0
  40. package/docs/releases/0.7.21.md +9 -0
  41. package/docs/releases/0.7.22.md +11 -0
  42. package/docs/releases/0.7.23.md +14 -0
  43. package/docs/releases/0.7.24.md +11 -0
  44. package/docs/releases/0.7.25.md +8 -0
  45. package/docs/releases/0.7.26.md +18 -0
  46. package/docs/releases/0.7.27.md +8 -0
  47. package/docs/releases/0.7.28.md +22 -0
  48. package/docs/releases/0.7.29.md +12 -0
  49. package/docs/releases/0.7.3.md +23 -0
  50. package/docs/releases/0.7.30.md +19 -0
  51. package/docs/releases/0.7.31.md +14 -0
  52. package/docs/releases/0.7.32.md +14 -0
  53. package/docs/releases/0.7.33.md +37 -0
  54. package/docs/releases/0.7.34.md +24 -0
  55. package/docs/releases/0.7.4.md +16 -0
  56. package/docs/releases/0.7.5.md +18 -0
  57. package/docs/releases/0.7.6.md +29 -0
  58. package/docs/releases/0.7.7.md +36 -0
  59. package/docs/releases/0.7.8.md +14 -0
  60. package/docs/releases/0.7.9.md +15 -0
  61. package/docs/session-bridge.md +80 -0
  62. package/docs/session-cards.md +53 -0
  63. package/docs/session-communication.md +186 -0
  64. package/docs/session-metrics.md +41 -0
  65. package/docs/skills.md +81 -0
  66. package/docs/triggers.md +338 -0
  67. package/docs/tui-commands.md +204 -0
  68. package/extensions/browser/manifest.json +22 -0
  69. package/extensions/browser/popup.css +6 -0
  70. package/extensions/browser/popup.html +11 -0
  71. package/extensions/browser/popup.mjs +47 -0
  72. package/extensions/browser/protocol.mjs +168 -0
  73. package/extensions/browser/worker.mjs +94 -0
  74. package/icon.svg +7 -0
  75. package/locale/en.json +6 -0
  76. package/locale/zh-cn.json +6 -0
  77. package/node_modules/@deepseek-ai/node-addon-system/LICENSE +21 -0
  78. package/node_modules/@deepseek-ai/node-addon-system/README.i18n.yaml +6 -0
  79. package/node_modules/@deepseek-ai/node-addon-system/README.md +15 -0
  80. package/node_modules/@deepseek-ai/node-addon-system/README.zh.md +15 -0
  81. package/node_modules/@deepseek-ai/node-addon-system/lib/flock.d.ts +13 -0
  82. package/node_modules/@deepseek-ai/node-addon-system/lib/flock.js +51 -0
  83. package/node_modules/@deepseek-ai/node-addon-system/lib/index.d.ts +73 -0
  84. package/node_modules/@deepseek-ai/node-addon-system/lib/index.js +98 -0
  85. package/node_modules/@deepseek-ai/node-addon-system/package.json +45 -0
  86. package/node_modules/@deepseek-ai/node-addon-system/src/flock.c +164 -0
  87. package/node_modules/@deepseek-ai/node-addon-system/src/main.c +298 -0
  88. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/LICENSE +28 -0
  89. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.i18n.yaml +6 -0
  90. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.md +9 -0
  91. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/README.zh.md +9 -0
  92. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/bin/system.node +0 -0
  93. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/package.json +31 -0
  94. package/node_modules/@deepseek-ai/node-addon-system-darwin-arm64/prebuilds.json +6 -0
  95. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/LICENSE +28 -0
  96. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.i18n.yaml +6 -0
  97. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.md +9 -0
  98. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/README.zh.md +9 -0
  99. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/bin/system.node +0 -0
  100. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/package.json +31 -0
  101. package/node_modules/@deepseek-ai/node-addon-system-darwin-x64/prebuilds.json +6 -0
  102. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/LICENSE +28 -0
  103. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.i18n.yaml +6 -0
  104. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.md +11 -0
  105. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/README.zh.md +11 -0
  106. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/glibc/system.node +0 -0
  107. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/landlock-run +0 -0
  108. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/bin/musl/system.node +0 -0
  109. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/package.json +31 -0
  110. package/node_modules/@deepseek-ai/node-addon-system-linux-arm64/prebuilds.json +24 -0
  111. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/LICENSE +28 -0
  112. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.i18n.yaml +6 -0
  113. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.md +11 -0
  114. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/README.zh.md +11 -0
  115. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/glibc/system.node +0 -0
  116. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/landlock-run +0 -0
  117. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/bin/musl/system.node +0 -0
  118. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/package.json +31 -0
  119. package/node_modules/@deepseek-ai/node-addon-system-linux-x64/prebuilds.json +24 -0
  120. package/package.json +156 -4
  121. package/plugins/account/index.mjs +178 -0
  122. package/plugins/account/state.mjs +123 -0
  123. package/plugins/auto-review/audit.mjs +24 -0
  124. package/plugins/auto-review/index.mjs +267 -0
  125. package/plugins/auto-review/policy.mjs +92 -0
  126. package/plugins/browser/SKILL.md +158 -0
  127. package/plugins/browser/access.mjs +143 -0
  128. package/plugins/browser/config.mjs +60 -0
  129. package/plugins/browser/connection.mjs +385 -0
  130. package/plugins/browser/desktop-client.mjs +282 -0
  131. package/plugins/browser/desktop-host.mjs +74 -0
  132. package/plugins/browser/document-identity.mjs +57 -0
  133. package/plugins/browser/extension-relay.mjs +66 -0
  134. package/plugins/browser/files.mjs +32 -0
  135. package/plugins/browser/index.mjs +235 -0
  136. package/plugins/browser/mcp-entry.mjs +18 -0
  137. package/plugins/browser/mcp-tools.mjs +21 -0
  138. package/plugins/browser/presentation.mjs +45 -0
  139. package/plugins/browser/preview.mjs +77 -0
  140. package/plugins/browser/review.mjs +20 -0
  141. package/plugins/browser/screenshots.mjs +35 -0
  142. package/plugins/clipboard-image/clipboard-image.swift +56 -0
  143. package/plugins/clipboard-image/index.mjs +70 -0
  144. package/plugins/code-review/baseline.mjs +143 -0
  145. package/plugins/code-review/git.mjs +163 -0
  146. package/plugins/code-review/index.mjs +236 -0
  147. package/plugins/compaction/engine.mjs +304 -0
  148. package/plugins/compaction/tetris.mjs +65 -0
  149. package/plugins/compaction/threshold.mjs +99 -0
  150. package/plugins/computer-use/DESKTOP-SKILL.md +64 -0
  151. package/plugins/computer-use/desktop-host.mjs +75 -0
  152. package/plugins/computer-use/desktop-skill.mjs +28 -0
  153. package/plugins/computer-use/desktop-upstream.mjs +3 -0
  154. package/plugins/computer-use/index.mjs +37 -0
  155. package/plugins/cordis-config/plain.mjs +31 -0
  156. package/plugins/credentials/index.mjs +73 -0
  157. package/plugins/custom/adapter.mjs +110 -0
  158. package/plugins/custom/config.mjs +85 -0
  159. package/plugins/custom/desktop-client.mjs +150 -0
  160. package/plugins/custom/desktop-host.mjs +78 -0
  161. package/plugins/custom/images.mjs +32 -0
  162. package/plugins/custom/index.mjs +145 -0
  163. package/plugins/custom/wire.mjs +179 -0
  164. package/plugins/desktop/client.mjs +1041 -0
  165. package/plugins/desktop/index.mjs +10 -0
  166. package/plugins/desktop/shell-environment.mjs +9 -0
  167. package/plugins/desktop-workspace/hooks.mjs +29 -0
  168. package/plugins/desktop-workspace/index.mjs +37 -0
  169. package/plugins/dscode/board.mjs +260 -0
  170. package/plugins/dscode/child-target.mjs +11 -0
  171. package/plugins/dscode/control.mjs +17 -0
  172. package/plugins/dscode/desktop-client.mjs +55 -0
  173. package/plugins/dscode/desktop-host.mjs +37 -0
  174. package/plugins/dscode/index.mjs +270 -0
  175. package/plugins/email/cli.mjs +32 -0
  176. package/plugins/email/contacts.mjs +36 -0
  177. package/plugins/email/desktop-client.mjs +117 -0
  178. package/plugins/email/desktop-host.mjs +54 -0
  179. package/plugins/email/desktop-service.mjs +106 -0
  180. package/plugins/email/gmail-oauth.mjs +69 -0
  181. package/plugins/email/gmail-store.mjs +2 -0
  182. package/plugins/email/gmail.mjs +194 -0
  183. package/plugins/email/imap.mjs +136 -0
  184. package/plugins/email/inbox.d.mts +25 -0
  185. package/plugins/email/inbox.mjs +76 -0
  186. package/plugins/email/smtp.mjs +70 -0
  187. package/plugins/email/store.mjs +32 -0
  188. package/plugins/email-tools/index.mjs +46 -0
  189. package/plugins/exec/cli.mjs +64 -0
  190. package/plugins/exec/index.mjs +112 -0
  191. package/plugins/grok/adapter.mjs +114 -0
  192. package/plugins/grok/auth.mjs +76 -0
  193. package/plugins/grok/billing.mjs +147 -0
  194. package/plugins/grok/index.mjs +123 -0
  195. package/plugins/grok/models.mjs +129 -0
  196. package/plugins/grok/status.mjs +29 -0
  197. package/plugins/grok/wire.mjs +268 -0
  198. package/plugins/hub/catalog.mjs +82 -0
  199. package/plugins/hub/desktop-client.mjs +114 -0
  200. package/plugins/hub/desktop-host.mjs +129 -0
  201. package/plugins/i18n/messages.d.mts +21 -0
  202. package/plugins/i18n/messages.mjs +537 -0
  203. package/plugins/jev/approval.mjs +131 -0
  204. package/plugins/jev/client.mjs +74 -0
  205. package/plugins/jev/index.mjs +100 -0
  206. package/plugins/memory/content.mjs +57 -0
  207. package/plugins/memory/desktop-host.mjs +3 -0
  208. package/plugins/memory/index.mjs +168 -0
  209. package/plugins/memory/pipeline.mjs +79 -0
  210. package/plugins/memory/store.mjs +93 -0
  211. package/plugins/message-source/kind.mjs +20 -0
  212. package/plugins/opencode-go/adapter.mjs +96 -0
  213. package/plugins/opencode-go/command.mjs +128 -0
  214. package/plugins/opencode-go/index.mjs +121 -0
  215. package/plugins/opencode-go/models.mjs +58 -0
  216. package/plugins/opencode-go/oauth.mjs +194 -0
  217. package/plugins/opencode-go/search.mjs +86 -0
  218. package/plugins/opencode-go/usage.mjs +120 -0
  219. package/plugins/opencode-go/version.mjs +10 -0
  220. package/plugins/opencode-go/wire.mjs +73 -0
  221. package/plugins/openrouter/adapter.mjs +232 -0
  222. package/plugins/openrouter/index.mjs +124 -0
  223. package/plugins/openrouter/models.mjs +157 -0
  224. package/plugins/openrouter/search.mjs +113 -0
  225. package/plugins/openrouter/wire.mjs +101 -0
  226. package/plugins/providers/catalog.mjs +160 -0
  227. package/plugins/providers/chat-messages.mjs +120 -0
  228. package/plugins/providers/chat-stream.mjs +174 -0
  229. package/plugins/providers/desktop-client.mjs +84 -0
  230. package/plugins/providers/desktop-host.mjs +63 -0
  231. package/plugins/providers/effort.mjs +35 -0
  232. package/plugins/providers/http-errors.mjs +50 -0
  233. package/plugins/providers/openrouter-account.mjs +171 -0
  234. package/plugins/session-bridge/client.mjs +106 -0
  235. package/plugins/session-bridge/communication.mjs +260 -0
  236. package/plugins/session-bridge/index.mjs +67 -0
  237. package/plugins/session-bridge/mailbox.mjs +267 -0
  238. package/plugins/session-bridge/paths.mjs +21 -0
  239. package/plugins/session-bridge/server.mjs +172 -0
  240. package/plugins/session-bridge/tasks.mjs +229 -0
  241. package/plugins/session-cards/content.mjs +64 -0
  242. package/plugins/session-cards/index.mjs +38 -0
  243. package/plugins/session-cards/manager.mjs +140 -0
  244. package/plugins/session-metrics/attribution.mjs +30 -0
  245. package/plugins/session-metrics/balance.mjs +81 -0
  246. package/plugins/session-metrics/desktop-client.mjs +62 -0
  247. package/plugins/session-metrics/desktop-host.mjs +46 -0
  248. package/plugins/session-metrics/index.mjs +97 -0
  249. package/plugins/session-metrics/pricing.mjs +84 -0
  250. package/plugins/session-metrics/rate.mjs +59 -0
  251. package/plugins/session-metrics/store.mjs +75 -0
  252. package/plugins/session-metrics/turns.mjs +134 -0
  253. package/plugins/session-metrics/view.mjs +272 -0
  254. package/plugins/time-marks/desktop-host.mjs +2 -0
  255. package/plugins/time-marks/index.mjs +116 -0
  256. package/plugins/time-marks/marks.mjs +152 -0
  257. package/plugins/triggers/cli.mjs +558 -0
  258. package/plugins/triggers/commands.mjs +143 -0
  259. package/plugins/triggers/config.mjs +264 -0
  260. package/plugins/triggers/desktop-client.mjs +155 -0
  261. package/plugins/triggers/desktop-host.mjs +88 -0
  262. package/plugins/triggers/desktop-management.mjs +97 -0
  263. package/plugins/triggers/desktop-scheduler.mjs +125 -0
  264. package/plugins/triggers/host.mjs +24 -0
  265. package/plugins/triggers/index.mjs +27 -0
  266. package/plugins/triggers/job-cli.mjs +110 -0
  267. package/plugins/triggers/jobs.mjs +156 -0
  268. package/plugins/triggers/launchd.mjs +101 -0
  269. package/plugins/triggers/lease.mjs +24 -0
  270. package/plugins/triggers/log.mjs +104 -0
  271. package/plugins/triggers/management.mjs +181 -0
  272. package/plugins/triggers/options.mjs +117 -0
  273. package/plugins/triggers/overlay.mjs +7 -0
  274. package/plugins/triggers/poll.mjs +65 -0
  275. package/plugins/triggers/run.mjs +157 -0
  276. package/plugins/triggers/schedule.mjs +40 -0
  277. package/plugins/triggers/scheduler-service.mjs +46 -0
  278. package/plugins/triggers/scheduler.mjs +92 -0
  279. package/plugins/triggers/session-run.mjs +125 -0
  280. package/plugins/triggers/session.mjs +48 -0
  281. package/plugins/triggers/source-emit.mjs +19 -0
  282. package/plugins/triggers/source-host.mjs +78 -0
  283. package/plugins/triggers/source-ingress.mjs +70 -0
  284. package/plugins/triggers/source-sandbox.mjs +35 -0
  285. package/plugins/triggers/sources.mjs +45 -0
  286. package/plugins/triggers/spool.mjs +145 -0
  287. package/plugins/triggers/tools.mjs +67 -0
  288. package/plugins/triggers/unattended.mjs +18 -0
  289. package/plugins/tui-tools/composition.mjs +28 -0
  290. package/plugins/tui-tools/desktop-host.mjs +2 -0
  291. package/plugins/tui-tools/doctor-cli.mjs +22 -0
  292. package/plugins/tui-tools/doctor.mjs +149 -0
  293. package/plugins/tui-tools/hook-sources.mjs +113 -0
  294. package/plugins/tui-tools/hooks.mjs +23 -0
  295. package/plugins/tui-tools/index.mjs +269 -0
  296. package/plugins/tui-tools/sandbox-runner.mjs +123 -0
  297. package/plugins/tui-tools/shell.mjs +27 -0
  298. package/plugins/tui-tools/update.mjs +111 -0
  299. package/plugins/tui-tools/workspace-discovery.mjs +129 -0
  300. package/plugins/ultra/policy.mjs +42 -0
  301. package/plugins/worktree-subagent/worktree.mjs +56 -0
  302. package/runtime-sources.json +94 -0
  303. package/vendor/bash/LICENSE +21 -0
  304. package/vendor/bash/index.js +717 -0
  305. package/vendor/bash/types/background.d.ts +50 -0
  306. package/vendor/bash/types/index.d.ts +38 -0
  307. package/vendor/bash/types/render.d.ts +57 -0
  308. package/vendor/command-goal/LICENSE +21 -0
  309. package/vendor/command-goal/index.js +210 -0
  310. package/vendor/command-goal/types/index.d.ts +10 -0
  311. package/vendor/computer-use/LICENSE +21 -0
  312. package/vendor/computer-use/lib/approval-policy.js +12 -0
  313. package/vendor/computer-use/lib/approval-policy.js.map +1 -0
  314. package/vendor/computer-use/lib/artifacts.js +79 -0
  315. package/vendor/computer-use/lib/artifacts.js.map +1 -0
  316. package/vendor/computer-use/lib/backend.js +3 -0
  317. package/vendor/computer-use/lib/backend.js.map +1 -0
  318. package/vendor/computer-use/lib/client.js +369 -0
  319. package/vendor/computer-use/lib/client.js.map +1 -0
  320. package/vendor/computer-use/lib/config.js +159 -0
  321. package/vendor/computer-use/lib/config.js.map +1 -0
  322. package/vendor/computer-use/lib/confirmations.js +99 -0
  323. package/vendor/computer-use/lib/confirmations.js.map +1 -0
  324. package/vendor/computer-use/lib/diff.js +58 -0
  325. package/vendor/computer-use/lib/diff.js.map +1 -0
  326. package/vendor/computer-use/lib/errors.js +22 -0
  327. package/vendor/computer-use/lib/errors.js.map +1 -0
  328. package/vendor/computer-use/lib/exposure.js +244 -0
  329. package/vendor/computer-use/lib/exposure.js.map +1 -0
  330. package/vendor/computer-use/lib/index.js +66 -0
  331. package/vendor/computer-use/lib/index.js.map +1 -0
  332. package/vendor/computer-use/lib/leases.js +262 -0
  333. package/vendor/computer-use/lib/leases.js.map +1 -0
  334. package/vendor/computer-use/lib/providers/macos.js +243 -0
  335. package/vendor/computer-use/lib/providers/macos.js.map +1 -0
  336. package/vendor/computer-use/lib/providers/native-helper.js +726 -0
  337. package/vendor/computer-use/lib/providers/native-helper.js.map +1 -0
  338. package/vendor/computer-use/lib/providers/unsupported.js +51 -0
  339. package/vendor/computer-use/lib/providers/unsupported.js.map +1 -0
  340. package/vendor/computer-use/lib/service.js +608 -0
  341. package/vendor/computer-use/lib/service.js.map +1 -0
  342. package/vendor/computer-use/lib/skill.js +136 -0
  343. package/vendor/computer-use/lib/skill.js.map +1 -0
  344. package/vendor/computer-use/lib/target-resolver.js +180 -0
  345. package/vendor/computer-use/lib/target-resolver.js.map +1 -0
  346. package/vendor/computer-use/lib/tools.js +511 -0
  347. package/vendor/computer-use/lib/tools.js.map +1 -0
  348. package/vendor/computer-use/lib/types/approval-policy.d.ts +13 -0
  349. package/vendor/computer-use/lib/types/approval-policy.d.ts.map +1 -0
  350. package/vendor/computer-use/lib/types/artifacts.d.ts +10 -0
  351. package/vendor/computer-use/lib/types/artifacts.d.ts.map +1 -0
  352. package/vendor/computer-use/lib/types/backend.d.ts +124 -0
  353. package/vendor/computer-use/lib/types/backend.d.ts.map +1 -0
  354. package/vendor/computer-use/lib/types/client/index.d.ts +83 -0
  355. package/vendor/computer-use/lib/types/client/index.d.ts.map +1 -0
  356. package/vendor/computer-use/lib/types/config.d.ts +94 -0
  357. package/vendor/computer-use/lib/types/config.d.ts.map +1 -0
  358. package/vendor/computer-use/lib/types/confirmations.d.ts +23 -0
  359. package/vendor/computer-use/lib/types/confirmations.d.ts.map +1 -0
  360. package/vendor/computer-use/lib/types/diff.d.ts +5 -0
  361. package/vendor/computer-use/lib/types/diff.d.ts.map +1 -0
  362. package/vendor/computer-use/lib/types/errors.d.ts +15 -0
  363. package/vendor/computer-use/lib/types/errors.d.ts.map +1 -0
  364. package/vendor/computer-use/lib/types/exposure.d.ts +30 -0
  365. package/vendor/computer-use/lib/types/exposure.d.ts.map +1 -0
  366. package/vendor/computer-use/lib/types/index.d.ts +21 -0
  367. package/vendor/computer-use/lib/types/index.d.ts.map +1 -0
  368. package/vendor/computer-use/lib/types/leases.d.ts +81 -0
  369. package/vendor/computer-use/lib/types/leases.d.ts.map +1 -0
  370. package/vendor/computer-use/lib/types/providers/macos.d.ts +39 -0
  371. package/vendor/computer-use/lib/types/providers/macos.d.ts.map +1 -0
  372. package/vendor/computer-use/lib/types/providers/native-helper.d.ts +48 -0
  373. package/vendor/computer-use/lib/types/providers/native-helper.d.ts.map +1 -0
  374. package/vendor/computer-use/lib/types/providers/unsupported.d.ts +23 -0
  375. package/vendor/computer-use/lib/types/providers/unsupported.d.ts.map +1 -0
  376. package/vendor/computer-use/lib/types/service.d.ts +58 -0
  377. package/vendor/computer-use/lib/types/service.d.ts.map +1 -0
  378. package/vendor/computer-use/lib/types/skill.d.ts +14 -0
  379. package/vendor/computer-use/lib/types/skill.d.ts.map +1 -0
  380. package/vendor/computer-use/lib/types/target-resolver.d.ts +38 -0
  381. package/vendor/computer-use/lib/types/target-resolver.d.ts.map +1 -0
  382. package/vendor/computer-use/lib/types/tools.d.ts +6 -0
  383. package/vendor/computer-use/lib/types/tools.d.ts.map +1 -0
  384. package/vendor/computer-use/lib/types/types.d.ts +270 -0
  385. package/vendor/computer-use/lib/types/types.d.ts.map +1 -0
  386. package/vendor/computer-use/lib/types/web.d.ts +33 -0
  387. package/vendor/computer-use/lib/types/web.d.ts.map +1 -0
  388. package/vendor/computer-use/lib/types.js +9 -0
  389. package/vendor/computer-use/lib/types.js.map +1 -0
  390. package/vendor/computer-use/lib/web.js +188 -0
  391. package/vendor/computer-use/lib/web.js.map +1 -0
  392. package/vendor/computer-use/native/macos/Sources/Fixture/main.swift +412 -0
  393. package/vendor/computer-use/native/macos/Sources/Helper/CursorImage.swift +11 -0
  394. package/vendor/computer-use/native/macos/Sources/Helper/CursorMotion.swift +67 -0
  395. package/vendor/computer-use/native/macos/Sources/Helper/CursorOverlay.swift +840 -0
  396. package/vendor/computer-use/native/macos/Sources/Helper/TargetedPointer.swift +327 -0
  397. package/vendor/computer-use/native/macos/Sources/Helper/WindowNumberMatcher.swift +29 -0
  398. package/vendor/computer-use/native/macos/Sources/Helper/main.swift +1244 -0
  399. package/vendor/computer-use/native/macos/Sources/Monitor/main.swift +153 -0
  400. package/vendor/computer-use/native/macos/bin/dsh-computer-use-helper +0 -0
  401. package/vendor/computer-use/native/macos/manifest.json +14 -0
  402. package/vendor/persistent/LICENSE +21 -0
  403. package/vendor/persistent/index.js +411 -0
  404. package/vendor/persistent/types/index.d.ts +24 -0
  405. package/vendor/subagent/LICENSE +21 -0
  406. package/vendor/subagent/index.js +694 -0
  407. package/vendor/subagent/invariant.js +52 -0
  408. package/vendor/subagent/model-selection-settings.js +68 -0
  409. package/vendor/subagent/types/index.d.ts +82 -0
  410. package/vendor/subagent/types/invariant.d.ts +16 -0
  411. package/vendor/subagent/types/list-models.d.ts +10 -0
  412. package/vendor/subagent/types/model-selection-settings.d.ts +45 -0
  413. package/vendor/subagent/types/model-selection-state.d.ts +48 -0
  414. package/vendor/subagent/types/model-selection.d.ts +81 -0
  415. package/vendor/subagent-core/LICENSE +21 -0
  416. package/vendor/subagent-core/index.js +3202 -0
  417. package/vendor/subagent-core/invariant.js +76 -0
  418. package/vendor/subagent-core/typert.host.d.ts +3 -0
  419. package/vendor/subagent-core/typert.host.js +978 -0
  420. package/vendor/subagent-core/typert.remote-client.d.ts +25 -0
  421. package/vendor/subagent-core/typert.remote-client.js +114 -0
  422. package/vendor/subagent-core/types/archive-admission.d.ts +16 -0
  423. package/vendor/subagent-core/types/archive-admission.js +151 -0
  424. package/vendor/subagent-core/types/assistant-output.d.ts +48 -0
  425. package/vendor/subagent-core/types/assistant-output.js +73 -0
  426. package/vendor/subagent-core/types/catalog.d.ts +84 -0
  427. package/vendor/subagent-core/types/catalog.js +109 -0
  428. package/vendor/subagent-core/types/child-agent.d.ts +156 -0
  429. package/vendor/subagent-core/types/child-agent.js +213 -0
  430. package/vendor/subagent-core/types/client.d.ts +8 -0
  431. package/vendor/subagent-core/types/client.js +7 -0
  432. package/vendor/subagent-core/types/continuation-activation.d.ts +267 -0
  433. package/vendor/subagent-core/types/continuation-activation.js +700 -0
  434. package/vendor/subagent-core/types/continuation-messages.d.ts +62 -0
  435. package/vendor/subagent-core/types/continuation-messages.js +106 -0
  436. package/vendor/subagent-core/types/continuation.d.ts +124 -0
  437. package/vendor/subagent-core/types/continuation.js +455 -0
  438. package/vendor/subagent-core/types/control-types.d.ts +135 -0
  439. package/vendor/subagent-core/types/control-types.js +8 -0
  440. package/vendor/subagent-core/types/control.d.ts +45 -0
  441. package/vendor/subagent-core/types/control.js +79 -0
  442. package/vendor/subagent-core/types/depth.d.ts +31 -0
  443. package/vendor/subagent-core/types/depth.js +39 -0
  444. package/vendor/subagent-core/types/descriptor.d.ts +144 -0
  445. package/vendor/subagent-core/types/descriptor.js +193 -0
  446. package/vendor/subagent-core/types/error.d.ts +11 -0
  447. package/vendor/subagent-core/types/error.js +14 -0
  448. package/vendor/subagent-core/types/inbox.d.ts +43 -0
  449. package/vendor/subagent-core/types/inbox.js +61 -0
  450. package/vendor/subagent-core/types/index.d.ts +320 -0
  451. package/vendor/subagent-core/types/index.js +475 -0
  452. package/vendor/subagent-core/types/internal.d.ts +58 -0
  453. package/vendor/subagent-core/types/internal.js +58 -0
  454. package/vendor/subagent-core/types/invariant.d.ts +13 -0
  455. package/vendor/subagent-core/types/invariant.js +91 -0
  456. package/vendor/subagent-core/types/lifecycle.d.ts +113 -0
  457. package/vendor/subagent-core/types/lifecycle.js +180 -0
  458. package/vendor/subagent-core/types/list-children.d.ts +36 -0
  459. package/vendor/subagent-core/types/list-children.js +166 -0
  460. package/vendor/subagent-core/types/out-of-process.d.ts +118 -0
  461. package/vendor/subagent-core/types/out-of-process.js +219 -0
  462. package/vendor/subagent-core/types/projection-types.d.ts +82 -0
  463. package/vendor/subagent-core/types/projection-types.js +7 -0
  464. package/vendor/subagent-core/types/projection.d.ts +100 -0
  465. package/vendor/subagent-core/types/projection.js +157 -0
  466. package/vendor/subagent-core/types/run-settlement.d.ts +17 -0
  467. package/vendor/subagent-core/types/run-settlement.js +70 -0
  468. package/vendor/subagent-core/types/types.d.ts +377 -0
  469. package/vendor/subagent-core/types/types.js +19 -0
  470. package/vendor/subagent-driver/LICENSE +21 -0
  471. package/vendor/subagent-driver/index.js +255 -0
  472. package/vendor/subagent-driver/types/index.d.ts +33 -0
  473. package/vendor/subagent-driver/types/structured.d.ts +42 -0
  474. package/vendor/subagent-fork/LICENSE +21 -0
  475. package/vendor/subagent-fork/index.js +61 -0
  476. package/vendor/subagent-fork/types/index.d.ts +20 -0
  477. package/vendor/subagent-spawn/LICENSE +21 -0
  478. package/vendor/subagent-spawn/index.js +45 -0
  479. package/vendor/subagent-spawn/types/index.d.ts +19 -0
  480. package/vendor/terminal/LICENSE +21 -0
  481. package/vendor/terminal/index.js +1093 -0
  482. package/vendor/terminal/types/config.d.ts +82 -0
  483. package/vendor/terminal/types/index.d.ts +36 -0
  484. package/vendor/terminal/types/sanitize.d.ts +47 -0
  485. package/vendor/terminal/types/session.d.ts +87 -0
@@ -0,0 +1,134 @@
1
+ // Per-turn cost attribution and the session budget gate. The footer reports the
2
+ // whole session's spend and /usage reports tokens per turn; these functions
3
+ // answer what neither does with that ledger: which turn spent the money, so the
4
+ // footer can show the last completed turn and /usage can price each one.
5
+
6
+ /**
7
+ * Turn windows from the session's own events. `turn/start` opens a window and
8
+ * `turn/end` closes it; an unterminated turn stays open to `Infinity` so the
9
+ * turn in flight still attributes its settled calls. Windows come back in
10
+ * ascending start order, which the attribution lookup below depends on.
11
+ */
12
+ export function turnWindows(events = []) {
13
+ const open = new Map();
14
+ const windows = [];
15
+ for (const event of events) {
16
+ const turn = event.data?.turn;
17
+ // A boundary without a time cannot place a window or a call inside one. It
18
+ // is skipped rather than stored: a NaN edge would make the ascending-search
19
+ // contract false and silently orphan every call of that turn.
20
+ if (!Number.isFinite(turn) || !Number.isFinite(event.time)) continue;
21
+ if (event.type === 'turn/start') {
22
+ const existing = open.get(turn);
23
+ // A resumed turn can re-emit its start; keep the earliest boundary.
24
+ open.set(turn, existing === undefined ? event.time : Math.min(existing, event.time));
25
+ } else if (event.type === 'turn/end') {
26
+ const start = open.get(turn);
27
+ if (start === undefined) continue;
28
+ open.delete(turn);
29
+ windows.push({ turn, start, end: event.time });
30
+ }
31
+ }
32
+ for (const [turn, start] of open) windows.push({ turn, start, end: Infinity });
33
+ return windows.sort((left, right) => left.start - right.start || left.turn - right.turn);
34
+ }
35
+
36
+ /**
37
+ * The clauses a call is priced against: each turn's window, with an open turn
38
+ * (no `turn/end`) bounded by the next turn's start. An interrupted turn must not
39
+ * swallow every later turn, while the turn genuinely in flight keeps running to
40
+ * `Infinity`. Satisfies {@link attributeCostByTurn}'s ascending-start contract.
41
+ */
42
+ export function turnBounds(events = []) {
43
+ return turnWindows(events).map((window, index, windows) => ({
44
+ turn: window.turn,
45
+ start: window.start,
46
+ // Math.max keeps the window non-inverted even for a log whose timestamps
47
+ // are out of order; an inverted bound would break the ascending search.
48
+ end: window.end === Infinity && index + 1 < windows.length ? Math.max(window.start, windows[index + 1].start) : window.end,
49
+ }));
50
+ }
51
+
52
+ /**
53
+ * The window containing `time`, by binary search over the ascending bounds. The
54
+ * index is the FIRST window starting after `time`, so the one before it is the
55
+ * only candidate that can contain it; `-1` means no turn was running.
56
+ * @param bounds - ascending {@link turnBounds} rows.
57
+ * @param time - the call's start time (`time` prices the call, so it is also when the turn ran).
58
+ * @returns the bounds index, or -1 when `time` falls in no window.
59
+ */
60
+ export function turnAt(bounds, time) {
61
+ let low = 0, high = bounds.length;
62
+ while (low < high) {
63
+ const mid = (low + high) >> 1;
64
+ if (bounds[mid].start <= time) low = mid + 1;
65
+ else high = mid;
66
+ }
67
+ const candidate = bounds[low - 1];
68
+ return candidate !== undefined && time <= candidate.end ? low - 1 : -1;
69
+ }
70
+
71
+ /**
72
+ * Cost and call count per turn. Roughly O(calls × log turns), so the footer can
73
+ * ask for the last turn once a second. A call is attributed by its start time,
74
+ * and a call outside every window (before the first `turn/start`, in a gap
75
+ * between two turns, or a backfilled row without one) is reported under
76
+ * `unattributed` rather than dropped.
77
+ * @param rows - ledger rows (`{ kind, time, cost, purpose }`).
78
+ * @param events - the session's events, for the turn windows.
79
+ * @returns One bucket per turn, plus `lastTurn` (the newest turn that is no
80
+ * longer running, undefined while none has stopped), the `unattributed` total
81
+ * and whether any of it was unpriceable. A turn that never emitted `turn/end`
82
+ * still counts as stopped once the next turn opens its window.
83
+ */
84
+ export function attributeCostByTurn(rows = [], events = []) {
85
+ const bounds = turnBounds(events);
86
+ const turns = bounds.map(bound => ({ turn: bound.turn, cost: 0, calls: 0, unknown: false }));
87
+ let unattributed = 0;
88
+ let unattributedUnknown = false;
89
+ for (const row of rows ?? []) {
90
+ if (row?.kind !== 'end') continue;
91
+ const at = turnAt(bounds, row.time);
92
+ if (at === -1) {
93
+ // Outside every window the spend still exists: an unpriceable row must be
94
+ // visible as such rather than counted as nothing.
95
+ if (Number.isFinite(row.cost)) unattributed += row.cost;
96
+ else unattributedUnknown = true;
97
+ continue;
98
+ }
99
+ turns[at].calls += 1;
100
+ if (Number.isFinite(row.cost)) turns[at].cost += row.cost;
101
+ else turns[at].unknown = true;
102
+ }
103
+ let lastTurn;
104
+ for (let index = bounds.length - 1; index >= 0; index -= 1) {
105
+ if (bounds[index].end !== Infinity) { lastTurn = turns[index]; break; }
106
+ }
107
+ return { turns, lastTurn, unattributed, unattributedUnknown };
108
+ }
109
+
110
+ /**
111
+ * The budget gate's verdict. A missing or non-positive limit means no budget is
112
+ * set, so the gate never fires and the footer shows no budget slot. `warn` is
113
+ * the alerting share; `over` means the next prompt would spend past the limit.
114
+ */
115
+ export function evaluateBudget(spent, limit, options = {}) {
116
+ const warnPercent = Number.isFinite(options.warnPercent) ? options.warnPercent : 80
117
+ if (!Number.isFinite(limit) || limit <= 0) return { state: 'unset', limit: null, spent, ratio: null, percent: null, warnPercent }
118
+ const safeSpent = Number.isFinite(spent) ? spent : 0
119
+ const ratio = safeSpent / limit
120
+ return {
121
+ state: ratio >= 1 ? 'over' : ratio * 100 >= warnPercent ? 'warn' : 'ok',
122
+ limit, spent: safeSpent, ratio,
123
+ percent: Math.round(ratio * 100),
124
+ warnPercent,
125
+ }
126
+ }
127
+
128
+ /** Parse the `DSCODE_SESSION_BUDGET_USD`-style value; anything else is no budget. */
129
+ export function parseBudget(value) {
130
+ if (typeof value === 'number') return Number.isFinite(value) && value > 0 ? value : null
131
+ if (typeof value !== 'string') return null
132
+ const parsed = Number.parseFloat(value.trim())
133
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null
134
+ }
@@ -0,0 +1,272 @@
1
+ import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
2
+ import { readMetrics } from './store.mjs';
3
+ import { t } from '../i18n/messages.mjs';
4
+ import { estimateCost, peakEmoji } from './pricing.mjs';
5
+ import { balanceNow, trustedNow } from './balance.mjs';
6
+ import { grokSubscriptionNow } from '../grok/billing.mjs';
7
+ import { goUsageNow } from '../opencode-go/usage.mjs';
8
+ import { canonicalProvider } from '../providers/catalog.mjs';
9
+ import { sessionAverageTps } from './rate.mjs';
10
+ import { attributeCostByTurn, evaluateBudget, parseBudget } from './turns.mjs';
11
+ let source;
12
+ export function setMetricSource(next) { source = next; return () => { if (source === next) source = undefined; }; }
13
+ export function summarize(rows, events = [], corrupt = false) {
14
+ const requests = new Map();
15
+ let first = Infinity;
16
+ for (const row of rows) {
17
+ if (row.kind === 'start') first = Math.min(first, row.time);
18
+ requests.set(row.id, { ...requests.get(row.id), ...row });
19
+ }
20
+ // Backfill old main/compaction calls from the durable log, only before the new ledger.
21
+ let route = {};
22
+ const history = [];
23
+ for (const event of events) {
24
+ if (event.type === 'request/header') route = event.data.header.config;
25
+ // Ledger rows cover everything from the first row's time on, so only older events are
26
+ // backfilled. `continue`, not `break`: the loop must not depend on event ordering.
27
+ if (event.time >= first) continue;
28
+ if (event.type === 'assistant/message' || event.type === 'compaction/summary' && event.data.llmStreamCall) {
29
+ const r = event.type === 'compaction/summary' ? event.data : route;
30
+ history.push({ usage: event.data.usage, cost: estimateCost(r.provider, r.model, event.data.usage, event.time), kind: 'end' });
31
+ } else if (event.type === 'assistant/attempt') history.push({ kind: 'end', cost: null });
32
+ }
33
+ let cost = 0, unknown = corrupt || history.length > 0, input = 0, hit = 0, cacheUnknown = false, calls = 0, pending = 0;
34
+ // Historical overhead (review/title) was not recorded by this collector: mark partial.
35
+ for (const row of [...history, ...requests.values()]) {
36
+ calls++;
37
+ if (row.kind !== 'end') { pending++; continue; }
38
+ if (!Number.isFinite(row.cost)) unknown = true;
39
+ else cost += row.cost;
40
+ const u = row.usage;
41
+ // An auxiliary call (memory, review, an aborted stream) can end without a usage chunk: that
42
+ // call is unaccounted, not a cache miss, so it must not blank the whole session's ratio.
43
+ if (!u) continue;
44
+ if (!Number.isFinite(u.inputTokens) || !Number.isFinite(u.outputTokens)) { cacheUnknown = true; continue; }
45
+ const total = u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
46
+ // OpenRouter reports cache reads only when there are some: a missing count is zero, not unknown.
47
+ input += total; hit += u.cacheReadTokens ?? 0;
48
+ }
49
+ // Per-turn attribution needs the ledger's own rows (the backfilled history has
50
+ // no turn boundary), so it reads them directly instead of the merged map.
51
+ const attribution = attributeCostByTurn(rows, events);
52
+ return {
53
+ cost, unknown, calls, pending,
54
+ cache: input > 0 && !cacheUnknown ? Math.min(100, hit / input * 100) : null,
55
+ turns: attribution.turns,
56
+ lastTurn: attribution.lastTurn,
57
+ unattributed: attribution.unattributed,
58
+ unattributedUnknown: attribution.unattributedUnknown,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Per-turn cost for one live session, straight from its ledger, keyed by the
64
+ * durable turn number. The /usage panel prices its turns with this: the token
65
+ * meter says what each turn billed, this says what it cost.
66
+ * @param id - the session id.
67
+ * @param events - the session's durable events, for the turn windows.
68
+ * @returns `{ turn, cost, calls, unknown }` per completed or running turn.
69
+ */
70
+ export function turnCostsFor(id, events = []) {
71
+ const entries = id ? readMetrics(resolveDshHome(), id).rows : [];
72
+ return attributeCostByTurn(entries, events).turns;
73
+ }
74
+ /**
75
+ * Terminal columns of a string: East Asian wide characters (such as the cache label's
76
+ * CJK glyphs) take two, and zero-width marks take none. The variation selectors matter
77
+ * here: `❄️` is U+2744 plus U+FE0F, and counting that selector as a column made the
78
+ * footer's provider budget swing with the DeepSeek pricing window.
79
+ */
80
+ const WIDE = /[\u1100-\u115f\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe4f\uff00-\uff60\uffe0-\uffe6\u2600-\u27bf\u{1f300}-\u{1faff}]/u;
81
+ // eslint-disable-next-line no-misleading-character-class -- these are zero width by design
82
+ const ZERO_WIDTH = /[\u0300-\u036f\u200d\ufe00-\ufe0f]/;
83
+ export function displayWidth(text) {
84
+ let width = 0;
85
+ for (const char of text) width += ZERO_WIDTH.test(char) ? 0 : WIDE.test(char) ? 2 : 1;
86
+ return width;
87
+ }
88
+ /**
89
+ * The money slot for the Grok subscription rail: a plan has credits and a reset time, not a
90
+ * bill. The percentage is optional (the server omits it for a period without usage), and an
91
+ * unread window shows the tier alone instead of a wrong number.
92
+ */
93
+ export function grokFooterFact(subscription, locale = 'en', now = Date.now()) {
94
+ const parts = [subscription?.tier ?? 'Grok'];
95
+ const used = subscription?.usedPercent;
96
+ if (Number.isFinite(used)) parts.push((Number.isInteger(used) ? String(used) : used.toFixed(1)) + '% ' + t(locale, 'footer.grokUsed'));
97
+ const reset = resetStamp(subscription?.periodEnd, now);
98
+ if (reset !== undefined) parts.push(t(locale, 'footer.grokResets') + ' ' + reset);
99
+ return parts.join(' · ');
100
+ }
101
+
102
+ const GO_WINDOWS = Object.freeze([['rolling', '5h'], ['weekly', '7d'], ['monthly', '30d']]);
103
+
104
+ /**
105
+ * The money slot for the OpenCode Go subscription: the share used of each cap Go enforces —
106
+ * rolling five hours, week, month — and, when one is exhausted, when it lifts. An unread
107
+ * snapshot shows the plan name alone instead of a wrong number.
108
+ */
109
+ export function goFooterFact(usage, locale = 'en', now = Date.now()) {
110
+ const parts = ['Go'];
111
+ for (const [key, label] of GO_WINDOWS) {
112
+ const percent = usage?.[key]?.percent;
113
+ if (Number.isFinite(percent)) parts.push(label + ' ' + (Number.isInteger(percent) ? String(percent) : percent.toFixed(1)) + '%' + (usage[key].limited ? '!' : ''));
114
+ }
115
+ // Blocked until the last exhausted window lifts: that is when requests work again.
116
+ const lifts = GO_WINDOWS.map(([key]) => usage?.[key]).filter(window => window?.limited).map(window => window.resetsAt)
117
+ .filter(iso => Number.isFinite(Date.parse(iso ?? ''))).sort((a, b) => Date.parse(b) - Date.parse(a))[0];
118
+ const stamp = resetStamp(lifts, now);
119
+ if (stamp !== undefined) parts.push(t(locale, 'footer.goLimited') + ' ' + stamp);
120
+ return parts.join(' · ');
121
+ }
122
+
123
+ /** Local `MM-DD HH:MM` for a reset time, or undefined when the window is unknown or past. */
124
+ function resetStamp(iso, now) {
125
+ const at = Date.parse(typeof iso === 'string' ? iso : '');
126
+ if (!Number.isFinite(at) || at <= now) return undefined;
127
+ const pad = value => String(value).padStart(2, '0');
128
+ const date = new Date(at);
129
+ return pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + ' ' + pad(date.getHours()) + ':' + pad(date.getMinutes());
130
+ }
131
+
132
+ /**
133
+ * Fixed figure columns for the live footer. The status line re-reads these
134
+ * figures every second, so a growing digit count must never move the segments
135
+ * after it: each figure is right-aligned inside its own columns, and only the
136
+ * terminal width decides which segments the drop ladder keeps.
137
+ */
138
+ const FIGURE = {
139
+ /** Request and session-average rates share the same fixed figure width. */
140
+ rate: 6,
141
+ /** `100%` context occupancy. */
142
+ percent: 4,
143
+ /** `100.0%` cache share. */
144
+ share: 6,
145
+ };
146
+
147
+ /**
148
+ * Columns {@link formatFooter} needs on top of the budget it is handed. The
149
+ * fixed figures above only stop moving once their columns are really reserved,
150
+ * so a caller that budgets the footer's width adds these columns first —
151
+ * otherwise the padding evicts a figure the width would have seated. Six
152
+ * measures the padding a live reading adds across the rate, context and cache
153
+ * figures at the widths the drop ladder actually decides on.
154
+ */
155
+ export const FOOTER_FIGURE_RESERVE = 6;
156
+
157
+ /** Right-align one figure inside its columns; a wider reading keeps its own width. */
158
+ function figure(text, width) {
159
+ const padding = width - displayWidth(text);
160
+ return padding > 0 ? ' '.repeat(padding) + text : text;
161
+ }
162
+
163
+ function footerFigures(metrics, context, rates, locale = 'en', provider = 'deepseek-official') {
164
+ provider = canonicalProvider(provider);
165
+ const label = key => t(locale, key);
166
+ const ctx = figure(Number.isFinite(context) ? `${Math.round(context)}%` : '--', FIGURE.percent);
167
+ const cache = figure(metrics.cache === null ? '--' : `${metrics.cache.toFixed(1)}%`, FIGURE.share);
168
+ // The balance belongs to the provider serving the route; only DeepSeek's official one bills by a peak window.
169
+ const balance = balanceNow(provider);
170
+ const spend = metrics.unknown && metrics.cost === 0 ? '--' : `$${metrics.cost.toFixed(2)}${metrics.unknown ? '+' : ''}${metrics.pending ? '…' : ''}`;
171
+ // The money is the one live figure whose digits are not reserved: they cross a
172
+ // column a handful of times per session, and reserving them would evict a
173
+ // per-second figure at the widths the footer actually runs at. A balance the
174
+ // provider cannot report is left out instead of parked as `$--`.
175
+ const dollars = provider === 'grok' ? grokFooterFact(grokSubscriptionNow(), locale)
176
+ : provider === 'opencode-go' ? goFooterFact(goUsageNow(), locale)
177
+ : spend + (balance === null ? '' : ' / $' + balance.toFixed(2)) + (provider === 'deepseek-official' ? ' ' + peakEmoji(trustedNow()) : '');
178
+ // The budget slot rides the money figure: it answers "how much of the session's
179
+ // limit is spent", which is a fact about the cost, not a second cost. A session
180
+ // with no limit renders exactly as before, so the footer only grows by choice.
181
+ const budget = metrics.budget;
182
+ const budgeted = budget === undefined || budget.limit === null ? ''
183
+ : `/${budget.limit.toFixed(2)}${budget.state === 'over' ? '⚠!' : budget.state === 'warn' ? '⚠' : ''}`;
184
+ const money = dollars + budgeted;
185
+ // The last completed turn is the one figure the whole-session total cannot
186
+ // give: `$1.23 / $9.86 · #12 $0.04` reads "the session so far, of which the
187
+ // last turn cost this". `+` marks a turn whose settled calls were not all
188
+ // priceable, the same mark the total uses.
189
+ const turn = metrics.lastTurn === undefined || !Number.isFinite(metrics.lastTurn.cost) ? ''
190
+ : `#${metrics.lastTurn.turn} $${metrics.lastTurn.cost.toFixed(2)}${metrics.lastTurn.unknown ? '+' : ''}`;
191
+ return {
192
+ current: rates ? `${rates.active ? '◌' : ' '} ${figure(Number.isFinite(rates.current) ? rates.current.toFixed(1) : '--', FIGURE.rate)} tps` : '',
193
+ average: rates ? `${figure(Number.isFinite(rates.average) ? rates.average.toFixed(1) : '--', FIGURE.rate)} tps ${label('footer.average')}` : '',
194
+ context: `${ctx} ${label('footer.context')}`,
195
+ money,
196
+ cache: `${cache} ${label('footer.cache')}`,
197
+ turn,
198
+ };
199
+ }
200
+
201
+ /** Flat footer for text consumers; the TUI uses the same figures as separate groups. */
202
+ export function formatFooter(metrics, context, columns = 80, rates, locale = 'en', provider = 'deepseek-official') {
203
+ const figures = footerFigures(metrics, context, rates, locale, provider);
204
+ const { current, average, context: ctx, money, cache, turn } = figures;
205
+ const base = rates ? [current, average, ctx, money, cache] : [ctx, money, cache];
206
+ // The slot is reserved even when no turn has cost anything yet: the drop
207
+ // ladder's indices are fixed against this array, and an unfilled slot renders
208
+ // as '' (skipped by `render`) exactly like the absent figure would.
209
+ base.splice(rates ? 3 : 1, 0, turn);
210
+ // Narrow terminals shed the quietest figures first: the last turn, then
211
+ // average, current and context, and only then the cache. The running cost is
212
+ // the last thing standing, and its slot is index 4 (or 2 without rates) now
213
+ // that the turn slot holds index 3 (or 1) open.
214
+ const drops = rates ? [3, 1, 0, 2, 5, 4] : [1, 0, 3, 2];
215
+ const render = omit => base.filter((part, index) => part !== '' && !omit.has(index)).join(' · ');
216
+ for (let dropped = 0; dropped <= drops.length; dropped++) {
217
+ const value = render(new Set(drops.slice(0, dropped)));
218
+ if (displayWidth(value) <= columns) return value;
219
+ }
220
+ const floor = render(new Set(drops));
221
+ let clipped = '';
222
+ for (const char of floor) { if (displayWidth(clipped + char) > columns) break; clipped += char; }
223
+ return clipped;
224
+ }
225
+ /**
226
+ * The session's recorded spend and whether any of it is unpriceable, for a host
227
+ * that must decide something (the budget gate) rather than only render it. Same
228
+ * ledger and same rules as {@link footerFor}, so the figure the gate compares
229
+ * and the figure the footer shows can never disagree.
230
+ */
231
+ export function sessionSpend(id) {
232
+ try {
233
+ const ledger = id ? readMetrics(resolveDshHome(), id) : { rows: [], corrupt: false };
234
+ const data = id ? source?.(id) : undefined;
235
+ const summary = summarize(ledger.rows, data?.events ?? [], ledger.corrupt);
236
+ return { cost: summary.cost, unknown: summary.unknown, pending: summary.pending };
237
+ } catch { return { cost: 0, unknown: true, pending: 0 }; }
238
+ }
239
+
240
+ /** Per-events memo: the status line renders up to once a second, and summarize/average are O(events). */
241
+ const footerCache = new WeakMap();
242
+ export function footerFor(id, stats, columns, provider = 'deepseek-official', locale = 'en', format = formatFooter) {
243
+ const limit = parseBudget(process.env.DSCODE_SESSION_BUDGET_USD);
244
+ try {
245
+ // Live estimation can be unavailable while a Host/agent is reconnecting.
246
+ // Keep the durable prompt reading visible instead of losing all telemetry.
247
+ let data;
248
+ try { data = id ? source?.(id) : undefined; } catch { /* use the TUI's durable projection */ }
249
+ const ledger = id ? readMetrics(resolveDshHome(), id) : { rows: [], corrupt: false };
250
+ const events = data?.events ?? [];
251
+ // Identity alone is not enough: a session event list may be appended to in place.
252
+ const hit = events.length > 0 ? footerCache.get(events) : undefined;
253
+ const tail = events.at(-1)?.time;
254
+ const fresh = hit !== undefined && hit.key === ledger.rows && hit.length === events.length && hit.tail === tail;
255
+ const summary = fresh ? hit.summary : summarize(ledger.rows, events, ledger.corrupt);
256
+ const used = Number.isFinite(data?.used) && data.used >= 0 ? data.used
257
+ : Number.isFinite(stats.lastPromptTokens) && stats.lastPromptTokens > 0 ? stats.lastPromptTokens : undefined;
258
+ const capacity = Number.isFinite(data?.capacity) && data.capacity > 0 ? data.capacity : stats.contextWindow;
259
+ const average = fresh ? hit.average : sessionAverageTps(events);
260
+ if (events.length > 0 && !fresh) footerCache.set(events, { key: ledger.rows, length: events.length, tail, summary, average });
261
+ // The budget comes from the environment for this process only: it is a
262
+ // per-machine spending guard, not a session property worth persisting.
263
+ const budget = limit === null ? undefined : evaluateBudget(summary.cost, limit);
264
+ return format({ ...summary, ...(budget === undefined ? {} : { budget }) }, Number.isFinite(used) && capacity > 0 ? used / capacity * 100 : undefined, columns, { current: data?.currentTps, average, active: data?.requestActive }, locale, provider);
265
+ } catch { return format({ cost: 0, unknown: true, cache: null }, undefined, columns, { current: null, average: null }, locale, provider); }
266
+ }
267
+
268
+ /** Structured figures preserve money/cache as one right-aligned group in the TUI. */
269
+ export function footerFiguresFor(id, stats, provider = 'deepseek-official', locale = 'en') {
270
+ return footerFor(id, stats, 0, provider, locale,
271
+ (metrics, context, _columns, rates, language, route) => footerFigures(metrics, context, rates, language, route));
272
+ }
@@ -0,0 +1,2 @@
1
+ export const inject = ['agentPresets'];
2
+ export { Config, applyDesktop as apply } from './index.mjs';
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Clock marks in the model's own context: every message a step admits, and
3
+ * every turn that closed before it, carries one reading of the host clock. A
4
+ * mark is its own plugin-sourced message rather than an edit of the prompt, so
5
+ * message bodies, session cards and title selection read exactly what they
6
+ * read before. Like the time-context reading it is durable: it spends model
7
+ * context from then on, replays on resume, and the terminal hides it by
8
+ * producer.
9
+ *
10
+ * @module dscode-time-marks
11
+ */
12
+ import z from '@deepseek-ai/schemastery';
13
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
14
+ import { arrivalMark, resolveTimeZone, turnEndMark } from './marks.mjs';
15
+ import { producerKind } from '../message-source/kind.mjs';
16
+ export const name = 'dscode-time-marks';
17
+ export const Config = z.object({
18
+ /** Fallback display zone; empty uses the host zone. */
19
+ timeZone: z.string().default(''),
20
+ });
21
+ /** Marks carried by one request at most, so a delivery burst cannot flood a step. */
22
+ const MARK_LIMIT = 8;
23
+
24
+ /** Recover bounded turn endings that no durable clock message has reported. */
25
+ export function pendingTimeMarks(events) {
26
+ const pending = [];
27
+ let covered = -1, crossedEnd = false;
28
+ for (let index = events.length - 1; index >= 0; index--) {
29
+ const event = events[index];
30
+ if (event.type === 'user/message' && producerKind(event.data.source) === name) {
31
+ // New clocks carry explicit IDs. Older clocks are trusted plugin output;
32
+ // their rendered turn lines are the available durable evidence.
33
+ const turns = event.data.source.closedTurns ?? (event.data.content ?? []).flatMap(block =>
34
+ block.type === 'text' ? [...block.text.matchAll(/^Time mark: .+ — turn (\d+) ended(?:,|\.)/gm)].map(match => +match[1]) : []);
35
+ if (Array.isArray(turns)) for (const turn of turns) if (Number.isSafeInteger(turn)) covered = Math.max(covered, turn);
36
+ }
37
+ if (event.type === 'turn/end') {
38
+ if (event.data.turn <= covered) crossedEnd = true;
39
+ else if (pending.length < MARK_LIMIT) pending.push({ turn: event.data.turn, endedAt: event.time });
40
+ }
41
+ if (event.type === 'turn/start') {
42
+ const ended = pending.find(item => item.turn === event.data.turn);
43
+ if (ended) ended.startedAt = event.time;
44
+ }
45
+ if ((crossedEnd || pending.length === MARK_LIMIT) && pending.every(item => item.startedAt !== undefined)) break;
46
+ }
47
+ return pending.reverse();
48
+ }
49
+ /**
50
+ * Per-agent mark state: closed turns still waiting for a step to carry them,
51
+ * and the opening time of the turn each one closed.
52
+ */
53
+ export function apply(ctx, config = {}) {
54
+ return install(ctx, config, () => true);
55
+ }
56
+
57
+ export function applyDesktop(ctx, config = {}) {
58
+ return install(ctx, config, agent => ctx.agentPresets.composedPreset(agent.ctx) === 'dscode');
59
+ }
60
+
61
+ function install(ctx, config, accepts) {
62
+ const marks = new WeakMap();
63
+ const stateFor = agent => {
64
+ let state = marks.get(agent);
65
+ if (state === undefined) marks.set(agent, state = { turns: pendingTimeMarks(agent.session?.snapshotEvents?.() ?? []), starts: new Map() });
66
+ return state;
67
+ };
68
+ const timeZone = resolveTimeZone(config.timeZone);
69
+ let closed = false;
70
+ ctx.effect(() => () => { closed = true; });
71
+ ctx.on('agent/pre-step', async (payload, next) => {
72
+ if (closed || !accepts(payload.agent)) return next();
73
+ const state = stateFor(payload.agent);
74
+ // Keep inbox provenance before downstream handlers add runtime context or
75
+ // filter the batch. Some native additions (e.g. skill-catalog) have no form.
76
+ const arrivals = new Set(payload.messages.map(message => message.id));
77
+ // The first step of a turn is the only moment its opening is observable.
78
+ if (payload.step === 1) state.starts.set(payload.turn, Date.now());
79
+ const decision = await next();
80
+ if (closed || decision.kind !== 'enter') return decision;
81
+ const at = Date.now();
82
+ const lines = [], closedTurns = [];
83
+ for (const message of decision.messages) {
84
+ if (!arrivals.has(message.id)) continue;
85
+ // Marks are injected context, never an inbox arrival; skipping our own
86
+ // producer keeps a future delivery path from marking itself.
87
+ if (producerKind(message.source) === name) continue;
88
+ const line = arrivalMark(message, at, timeZone);
89
+ if (line !== null && lines.length < MARK_LIMIT) lines.push(line);
90
+ }
91
+ // An arrival burst that fills the limit leaves the remaining turn marks
92
+ // queued: the next step carries them instead of losing them here.
93
+ while (lines.length < MARK_LIMIT && state.turns.length > 0) {
94
+ const ended = state.turns.shift();
95
+ lines.push(turnEndMark(ended.turn, ended.endedAt, ended.startedAt, timeZone));
96
+ closedTurns.push(ended.turn);
97
+ }
98
+ if (lines.length === 0) return decision;
99
+ return {
100
+ ...decision,
101
+ // Ahead of the admitted batch: clocks must not take the prompt's place.
102
+ // The Host may independently append its runtime context after the input.
103
+ messages: [createUserMessage({
104
+ content: [{ type: 'text', text: lines.join('\n') }],
105
+ source: { kind: name, form: 'snapshot', closedTurns },
106
+ }), ...decision.messages],
107
+ };
108
+ }, { prepend: true });
109
+ ctx.on('agent/turn-stopping', payload => {
110
+ if (closed || !accepts(payload.agent)) return;
111
+ const state = stateFor(payload.agent);
112
+ state.turns.push({ turn: payload.turn, endedAt: Date.now(), startedAt: state.starts.get(payload.turn) });
113
+ state.starts.delete(payload.turn);
114
+ if (state.turns.length > MARK_LIMIT) state.turns.length = MARK_LIMIT;
115
+ });
116
+ }