@songsid/agend 2.1.4-beta.9 → 2.1.5-beta.1

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 (186) hide show
  1. package/README.md +1 -1
  2. package/README.zh-TW.md +1 -1
  3. package/dist/agent-cli-instructions.md +2 -2
  4. package/dist/agent-endpoint.js +1 -1
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/backend/antigravity.d.ts +12 -8
  7. package/dist/backend/antigravity.js +189 -42
  8. package/dist/backend/antigravity.js.map +1 -1
  9. package/dist/backend/claude-code.d.ts +87 -23
  10. package/dist/backend/claude-code.js +361 -46
  11. package/dist/backend/claude-code.js.map +1 -1
  12. package/dist/backend/codex.d.ts +18 -0
  13. package/dist/backend/codex.js +139 -5
  14. package/dist/backend/codex.js.map +1 -1
  15. package/dist/backend/gemini-cli.js +2 -2
  16. package/dist/backend/gemini-cli.js.map +1 -1
  17. package/dist/backend/grok.js +2 -2
  18. package/dist/backend/grok.js.map +1 -1
  19. package/dist/backend/kiro.d.ts +52 -2
  20. package/dist/backend/kiro.js +267 -12
  21. package/dist/backend/kiro.js.map +1 -1
  22. package/dist/backend/opencode.js +17 -4
  23. package/dist/backend/opencode.js.map +1 -1
  24. package/dist/backend/types.d.ts +122 -3
  25. package/dist/backend/types.js +5 -1
  26. package/dist/backend/types.js.map +1 -1
  27. package/dist/backend-outage.d.ts +61 -0
  28. package/dist/backend-outage.js +71 -0
  29. package/dist/backend-outage.js.map +1 -0
  30. package/dist/channel/adapters/discord.d.ts +44 -2
  31. package/dist/channel/adapters/discord.js +523 -90
  32. package/dist/channel/adapters/discord.js.map +1 -1
  33. package/dist/channel/adapters/telegram.d.ts +30 -0
  34. package/dist/channel/adapters/telegram.js +221 -19
  35. package/dist/channel/adapters/telegram.js.map +1 -1
  36. package/dist/channel/agy-mcp-launcher.d.ts +2 -0
  37. package/dist/channel/agy-mcp-launcher.js +28 -0
  38. package/dist/channel/agy-mcp-launcher.js.map +1 -0
  39. package/dist/channel/ipc-bridge.d.ts +2 -1
  40. package/dist/channel/ipc-bridge.js +33 -4
  41. package/dist/channel/ipc-bridge.js.map +1 -1
  42. package/dist/channel/markdown-chunk.d.ts +42 -0
  43. package/dist/channel/markdown-chunk.js +205 -0
  44. package/dist/channel/markdown-chunk.js.map +1 -0
  45. package/dist/channel/mcp-server.js +34 -5
  46. package/dist/channel/mcp-server.js.map +1 -1
  47. package/dist/channel/mcp-tools.js +5 -3
  48. package/dist/channel/mcp-tools.js.map +1 -1
  49. package/dist/channel/message-queue.js +64 -8
  50. package/dist/channel/message-queue.js.map +1 -1
  51. package/dist/channel/types.d.ts +31 -1
  52. package/dist/classic-channel-manager.d.ts +65 -0
  53. package/dist/classic-channel-manager.js +172 -3
  54. package/dist/classic-channel-manager.js.map +1 -1
  55. package/dist/cli.js +124 -83
  56. package/dist/cli.js.map +1 -1
  57. package/dist/config-validator.js +13 -0
  58. package/dist/config-validator.js.map +1 -1
  59. package/dist/config.d.ts +4 -0
  60. package/dist/config.js +12 -1
  61. package/dist/config.js.map +1 -1
  62. package/dist/cross-instance-envelope.d.ts +6 -0
  63. package/dist/cross-instance-envelope.js +30 -0
  64. package/dist/cross-instance-envelope.js.map +1 -0
  65. package/dist/daemon.d.ts +491 -14
  66. package/dist/daemon.js +2121 -276
  67. package/dist/daemon.js.map +1 -1
  68. package/dist/doctor.d.ts +41 -0
  69. package/dist/doctor.js +267 -0
  70. package/dist/doctor.js.map +1 -0
  71. package/dist/fleet-context.d.ts +63 -0
  72. package/dist/fleet-context.js.map +1 -1
  73. package/dist/fleet-manager.d.ts +402 -11
  74. package/dist/fleet-manager.js +2594 -422
  75. package/dist/fleet-manager.js.map +1 -1
  76. package/dist/fleet-yaml-slim.d.ts +10 -0
  77. package/dist/fleet-yaml-slim.js +59 -0
  78. package/dist/fleet-yaml-slim.js.map +1 -0
  79. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +1 -0
  80. package/dist/general-knowledge/skills/model-discovery/SKILL.md +63 -16
  81. package/dist/general-knowledge/skills/session-management/SKILL.md +171 -8
  82. package/dist/instance-lifecycle.d.ts +114 -1
  83. package/dist/instance-lifecycle.js +404 -14
  84. package/dist/instance-lifecycle.js.map +1 -1
  85. package/dist/instructions.js +42 -4
  86. package/dist/instructions.js.map +1 -1
  87. package/dist/locale.js +1024 -320
  88. package/dist/locale.js.map +1 -1
  89. package/dist/login-flows.d.ts +91 -0
  90. package/dist/login-flows.js +170 -0
  91. package/dist/login-flows.js.map +1 -0
  92. package/dist/login-manager.d.ts +63 -0
  93. package/dist/login-manager.js +134 -0
  94. package/dist/login-manager.js.map +1 -0
  95. package/dist/network-family.d.ts +18 -0
  96. package/dist/network-family.js +20 -0
  97. package/dist/network-family.js.map +1 -0
  98. package/dist/outbound-handlers.d.ts +16 -1
  99. package/dist/outbound-handlers.js +174 -40
  100. package/dist/outbound-handlers.js.map +1 -1
  101. package/dist/outbound-schemas.d.ts +10 -3
  102. package/dist/outbound-schemas.js +10 -0
  103. package/dist/outbound-schemas.js.map +1 -1
  104. package/dist/pane-input-residue.d.ts +52 -0
  105. package/dist/pane-input-residue.js +107 -0
  106. package/dist/pane-input-residue.js.map +1 -0
  107. package/dist/process-memory.d.ts +6 -0
  108. package/dist/process-memory.js +45 -0
  109. package/dist/process-memory.js.map +1 -1
  110. package/dist/reply-dedup.d.ts +7 -8
  111. package/dist/reply-dedup.js +0 -0
  112. package/dist/reply-dedup.js.map +1 -1
  113. package/dist/restart-progress.d.ts +2 -0
  114. package/dist/restart-progress.js +3 -0
  115. package/dist/restart-progress.js.map +1 -1
  116. package/dist/scheduler/db.d.ts +12 -0
  117. package/dist/scheduler/db.js +59 -0
  118. package/dist/scheduler/db.js.map +1 -1
  119. package/dist/service-installer.d.ts +11 -0
  120. package/dist/service-installer.js +84 -18
  121. package/dist/service-installer.js.map +1 -1
  122. package/dist/settings-api.js +1 -1
  123. package/dist/settings-api.js.map +1 -1
  124. package/dist/setup-wizard.js +2 -2
  125. package/dist/setup-wizard.js.map +1 -1
  126. package/dist/spawn-gate.d.ts +30 -0
  127. package/dist/spawn-gate.js +79 -0
  128. package/dist/spawn-gate.js.map +1 -0
  129. package/dist/steer-capability.d.ts +1 -0
  130. package/dist/steer-capability.js +6 -0
  131. package/dist/steer-capability.js.map +1 -0
  132. package/dist/storm-window.d.ts +83 -0
  133. package/dist/storm-window.js +251 -0
  134. package/dist/storm-window.js.map +1 -0
  135. package/dist/tips.d.ts +44 -0
  136. package/dist/tips.js +355 -0
  137. package/dist/tips.js.map +1 -0
  138. package/dist/tmux-manager.d.ts +16 -0
  139. package/dist/tmux-manager.js +110 -25
  140. package/dist/tmux-manager.js.map +1 -1
  141. package/dist/topic-commands.d.ts +31 -7
  142. package/dist/topic-commands.js +342 -70
  143. package/dist/topic-commands.js.map +1 -1
  144. package/dist/transcript-sources.d.ts +35 -10
  145. package/dist/transcript-sources.js +190 -38
  146. package/dist/transcript-sources.js.map +1 -1
  147. package/dist/types.d.ts +6 -1
  148. package/dist/ui/dashboard.html +55 -32
  149. package/dist/ui/settings.html +200 -60
  150. package/dist/ui/view.html +147 -30
  151. package/dist/ui/web-terminal/terminal.css +17 -0
  152. package/dist/ui/web-terminal/terminal.html +43 -0
  153. package/dist/ui/web-terminal/terminal.js +148 -0
  154. package/dist/ui/web-terminal/vendor/LICENSE.addon-fit +19 -0
  155. package/dist/ui/web-terminal/vendor/LICENSE.addon-web-links +19 -0
  156. package/dist/ui/web-terminal/vendor/LICENSE.xterm +21 -0
  157. package/dist/ui/web-terminal/vendor/VENDORED.md +22 -0
  158. package/dist/ui/web-terminal/vendor/addon-fit.js +1 -0
  159. package/dist/ui/web-terminal/vendor/addon-web-links.js +1 -0
  160. package/dist/ui/web-terminal/vendor/xterm.css +285 -0
  161. package/dist/ui/web-terminal/vendor/xterm.js +1 -0
  162. package/dist/usage/format-rich.d.ts +1 -1
  163. package/dist/usage/format-rich.js +28 -24
  164. package/dist/usage/format-rich.js.map +1 -1
  165. package/dist/usage/i18n-keys.d.ts +7 -0
  166. package/dist/usage/i18n-keys.js +34 -0
  167. package/dist/usage/i18n-keys.js.map +1 -0
  168. package/dist/usage/i18n.d.ts +5 -0
  169. package/dist/usage/i18n.js +27 -0
  170. package/dist/usage/i18n.js.map +1 -0
  171. package/dist/usage/providers.d.ts +21 -0
  172. package/dist/usage/providers.js +153 -75
  173. package/dist/usage/providers.js.map +1 -1
  174. package/dist/usage/usage-api.d.ts +11 -3
  175. package/dist/usage/usage-api.js +61 -24
  176. package/dist/usage/usage-api.js.map +1 -1
  177. package/dist/web-terminal-http.d.ts +53 -0
  178. package/dist/web-terminal-http.js +396 -0
  179. package/dist/web-terminal-http.js.map +1 -0
  180. package/dist/web-terminal.d.ts +329 -0
  181. package/dist/web-terminal.js +833 -0
  182. package/dist/web-terminal.js.map +1 -0
  183. package/dist/ws-server.d.ts +69 -0
  184. package/dist/ws-server.js +392 -0
  185. package/dist/ws-server.js.map +1 -0
  186. package/package.json +2 -2
package/dist/ui/view.html CHANGED
@@ -129,17 +129,17 @@
129
129
  </style>
130
130
  </head>
131
131
  <body>
132
- <button class="help-btn" id="usageBtn" title="AI usage" style="right:140px; display:none">📊</button>
133
- <button class="help-btn" id="densityBtn" title="Font density" style="right:98px">⤢</button>
134
- <button class="help-btn" id="langBtn" title="Language" style="right:56px">🌐</button>
135
- <button class="help-btn" id="helpBtn" title="Help">ℹ️</button>
132
+ <button class="help-btn" id="usageBtn" data-i18n-title="usageButton" title="AI usage" style="right:140px; display:none">📊</button>
133
+ <button class="help-btn" id="densityBtn" data-i18n-title="densityLabel" title="Font density" style="right:98px">⤢</button>
134
+ <button class="help-btn" id="langBtn" data-i18n-title="languageButton" title="Language" style="right:56px">🌐</button>
135
+ <button class="help-btn" id="helpBtn" data-i18n-title="helpButton" title="Help">ℹ️</button>
136
136
  <div class="help-backdrop" id="helpBackdrop"></div>
137
137
  <aside class="help-panel" id="helpPanel">
138
- <button class="close" id="helpClose" title="Close">×</button>
138
+ <button class="close" id="helpClose" data-i18n-title="close" title="Close">×</button>
139
139
  <div id="helpContent"></div>
140
140
  </aside>
141
141
  <aside class="help-panel" id="usagePanel">
142
- <button class="close" id="usageClose" title="Close">×</button>
142
+ <button class="close" id="usageClose" data-i18n-title="close" title="Close">×</button>
143
143
  <div id="usageContent"></div>
144
144
  </aside>
145
145
  <div id="sidebar"><h1 data-i18n="viewTitle">AgEnD · View</h1><div id="list"></div></div>
@@ -152,7 +152,7 @@
152
152
  <div class="meta" id="meta"></div>
153
153
  <div class="desc" id="desc"></div>
154
154
  </div>
155
- <button id="cardToggle" title="Expand profile" aria-expanded="false">▲</button>
155
+ <button id="cardToggle" data-i18n-title="expandProfile" title="Expand profile" aria-expanded="false">▲</button>
156
156
  <button id="editBtn" data-i18n="editProfile">✏️ Edit</button>
157
157
  <div id="edit">
158
158
  <input id="fDisplay" data-i18n-ph="displayName" placeholder="Display name">
@@ -193,7 +193,54 @@
193
193
  tooltipBackend: "Backend: {value}", tooltipStatus: "Status: {value}", tooltipContext: "Context: {value}",
194
194
  tooltipUnavailable: "Unavailable", statusRunning: "Running", statusPaused: "Paused",
195
195
  statusStopped: "Stopped", statusCrashed: "Crashed", statusUnknown: "Unknown",
196
- usageResets: "resets in {t}", usageSoon: "resets soon", usageFail: "Could not load usage data." },
196
+ usageResets: "resets in {t}", usageSoon: "resets soon", usageFail: "Could not load usage data.",
197
+ "usage.title": "AI Subscription Usage", "usage.title_plain": "AI subscription usage", "usage.empty_active": "No active backends with usage tracking",
198
+ "usage.not_logged_in": "not logged in", "usage.error_fallback": "error", "usage.no_data": "no data",
199
+ "usage.reset.in": "resets in {0}", "usage.reset.soon": "resets soon",
200
+ "usage.duration.days_hours": "{0}d {1}h", "usage.duration.hours_minutes": "{0}h {1}m", "usage.duration.minutes": "{0}m",
201
+ "usage.metric.session": "Session", "usage.metric.weekly": "Weekly", "usage.metric.monthly": "Monthly",
202
+ "usage.metric.days_window": "{0}-day window", "usage.metric.named": "{0}",
203
+ "usage.metric.named_session": "{0} (session)", "usage.metric.named_weekly": "{0} (weekly)",
204
+ "usage.metric.named_monthly": "{0} (monthly)", "usage.metric.named_days": "{0} ({1}d)",
205
+ "usage.metric.extra_usage": "Extra usage", "usage.metric.rate_limit_resets": "Rate limit resets",
206
+ "usage.metric.credits": "Credits", "usage.metric.weekly_limit": "Weekly limit",
207
+ "usage.metric.pay_as_you_go": "Pay as you go", "usage.metric.bonus_credits": "Bonus credits",
208
+ "usage.metric.overage_charges": "Overage charges", "usage.metric.pay_per_use": "Pay-per-use",
209
+ "usage.metric.usage_monthly": "Usage (monthly)",
210
+ "usage.unit.available": "available", "usage.unit.credits": "credits",
211
+ "usage.value.disabled": "Disabled", "usage.value.cap": "{0} cap",
212
+ "usage.note.binding": "binding", "usage.note.binding_severity": "binding · {0}",
213
+ "usage.note.busiest_model": "busiest of {0} model", "usage.note.busiest_models": "busiest of {0} models",
214
+ "usage.note.used_limit_unit": "{0} / {1} {2}", "usage.note.bonus_codes": "{0} / {1} {2} · {3} codes",
215
+ "usage.metric.agy_claude_others": "Claude & others", "usage.metric.agy_claude_others_session": "Claude & others (session)",
216
+ "usage.metric.agy_claude_others_weekly": "Claude & others (weekly)",
217
+ "usage.hint.statusline_now": "from {0}'s statusline, just now — no API call; plan and per-model limits need a CLI login",
218
+ "usage.hint.statusline_minutes": "from {0}'s statusline, {1}m ago — no API call; plan and per-model limits need a CLI login",
219
+ "usage.hint.claude_api_key": "API-key login has no subscription usage. Use `claude /login` or set CLAUDE_CODE_OAUTH_TOKEN (from `claude setup-token`).",
220
+ "usage.hint.login_claude": "Log in with the Claude Code CLI (`claude`).",
221
+ "usage.hint.login_agy": "Log in with the Antigravity CLI (`agy`).", "usage.hint.no_quota": "No quota information reported for this account.",
222
+ "usage.hint.login_codex": "Log in with the Codex CLI (`codex`).", "usage.hint.codex_no_oauth": "auth.json has no OAuth login (API-key-only auth cannot read usage).",
223
+ "usage.hint.login_grok": "Log in with the Grok CLI (`grok login`).", "usage.hint.grok_no_login": "auth.json has no usable login. Run `grok login`.",
224
+ "usage.hint.login_kiro": "Log in with the Kiro CLI (`kiro-cli`).", "usage.hint.kiro_signed_out": "Signed out — run `kiro-cli` to log in.",
225
+ "usage.hint.token_refreshing": "Token refreshing — try again in a moment.", "usage.hint.subscription_no_credit": "Subscription — no credit usage to report.",
226
+ "usage.hint.no_quota_account": "No quota information available for this account type.",
227
+ "usage.error.unreachable": "Could not reach {0}.", "usage.error.token_rejected": "Token rejected. Run `{0}` once to refresh the login.",
228
+ "usage.error.rate_limited": "Rate limited by {0} — try again later.", "usage.error.http": "Usage request failed (HTTP {0}).",
229
+ "usage.error.billing_http": "Billing request failed (HTTP {0}).", "usage.error.invalid_response": "Invalid response from usage endpoint.",
230
+ "usage.error.invalid_billing_response": "Invalid response from billing endpoint.", "usage.error.invalid_kiro_response": "Invalid response from GetUsageLimits.",
231
+ "usage.error.claude_token_expired": "Access token expired — it refreshes the next time any Claude instance runs.",
232
+ "usage.error.codex_token_expired": "Access token expired. Run `codex` once to refresh it.",
233
+ "usage.error.grok_expired": "Grok session expired. Run `grok login` again.", "usage.error.grok_response_changed": "Grok billing response changed.",
234
+ "usage.error.agy_cap": "Individual cap reached — CLI is rate limited (resets in ~{0}). The per-model pool quotas are separate and may still read 0% used.",
235
+ "usage.error.agy_no_refresh": "Access token expired and no refresh token is stored. Run `agy` once.",
236
+ "usage.error.agy_token_expired": "Access token expired. Run `agy` once to refresh it (or set AGY_OAUTH_CLIENT_ID/SECRET to refresh automatically).",
237
+ "usage.error.agy_login_expired": "Google sign-in expired. Run `agy` once to log in again.", "usage.error.agy_refresh_empty": "Google token refresh returned no token.",
238
+ "usage.error.grok_auth_readonly": "Token expired and auth.json is read-only. Run `grok` once to refresh it.", "usage.error.grok_no_refresh": "Token expired and no refresh token was found. Run `grok login`.",
239
+ "usage.error.grok_refresh_unreachable": "Could not reach auth.x.ai to refresh the token.", "usage.error.grok_invalid_refresh": "Invalid refresh response from auth.x.ai.",
240
+ "usage.error.grok_persist": "Refreshed but could not write auth.json. Run `grok` once instead.",
241
+ usageButton: "AI usage", languageButton: "Language", helpButton: "Help", close: "Close", expandProfile: "Expand profile",
242
+ rosterFailed: "Failed to load roster: {error}\n(Is the fleet running?)", paneFailed: "⚠ Could not load pane for {name} (HTTP {status}).",
243
+ tokenRequired: "web.token required", avatarFailed: "Avatar upload failed: {error}", saved: "Saved", saveFailed: "Save failed: {error}" },
197
244
  "zh-TW": { viewTitle: "AgEnD · 檢視", editProfile: "✏️ 編輯", save: "儲存", cancel: "取消",
198
245
  displayName: "顯示名稱", role: "角色", description: "描述", tokenPh: "web.token(儲存時需要)",
199
246
  saveHint: "檢視為公開(唯讀)。儲存需要 web.token(~/.agend/web.token)。", helpTitle: "📖 使用說明",
@@ -208,14 +255,63 @@
208
255
  tooltipBackend: "後端:{value}", tooltipStatus: "狀態:{value}", tooltipContext: "上下文:{value}",
209
256
  tooltipUnavailable: "無資料", statusRunning: "運行中", statusPaused: "已暫停",
210
257
  statusStopped: "已停止", statusCrashed: "已崩潰", statusUnknown: "未知",
211
- usageResets: "{t} 後重置", usageSoon: "即將重置", usageFail: "無法載入用量資料。" },
258
+ usageResets: "{t}後重置", usageSoon: "即將重置", usageFail: "無法載入用量資料。",
259
+ "usage.title": "AI 訂閱用量", "usage.title_plain": "AI 訂閱用量", "usage.empty_active": "沒有使用中且支援用量追蹤的 Backend",
260
+ "usage.not_logged_in": "未登入", "usage.error_fallback": "錯誤", "usage.no_data": "無資料",
261
+ "usage.reset.in": "{0} 後重置", "usage.reset.soon": "即將重置",
262
+ "usage.duration.days_hours": "{0}d{1}h", "usage.duration.hours_minutes": "{0}h{1}m", "usage.duration.minutes": "{0}m",
263
+ "usage.metric.session": "Session", "usage.metric.weekly": "每週", "usage.metric.monthly": "每月",
264
+ "usage.metric.days_window": "{0} 天視窗", "usage.metric.named": "{0}",
265
+ "usage.metric.named_session": "{0}(Session)", "usage.metric.named_weekly": "{0}(週)",
266
+ "usage.metric.named_monthly": "{0}(每月)", "usage.metric.named_days": "{0}({1} 天)",
267
+ "usage.metric.extra_usage": "額外用量", "usage.metric.rate_limit_resets": "額度重置券",
268
+ "usage.metric.credits": "額度", "usage.metric.weekly_limit": "每週上限",
269
+ "usage.metric.pay_as_you_go": "隨用隨付", "usage.metric.bonus_credits": "贈送額度",
270
+ "usage.metric.overage_charges": "超額費用", "usage.metric.pay_per_use": "隨用隨付",
271
+ "usage.metric.usage_monthly": "用量(每月)",
272
+ "usage.unit.available": "可用", "usage.unit.credits": "額度",
273
+ "usage.value.disabled": "已停用", "usage.value.cap": "上限 {0}",
274
+ "usage.note.binding": "生效", "usage.note.binding_severity": "生效 · {0}",
275
+ "usage.note.busiest_model": "{0} 個模型中用量最高者", "usage.note.busiest_models": "{0} 個模型中用量最高者",
276
+ "usage.note.used_limit_unit": "{0} / {1} {2}", "usage.note.bonus_codes": "{0} / {1} {2} · {3} 組代碼",
277
+ "usage.metric.agy_claude_others": "Claude 與其他模型", "usage.metric.agy_claude_others_session": "Claude 與其他模型(Session)",
278
+ "usage.metric.agy_claude_others_weekly": "Claude 與其他模型(每週)",
279
+ "usage.hint.statusline_now": "來自 {0} 的 statusline(剛剛);未呼叫 API,方案與各模型上限需要 CLI 登入資料",
280
+ "usage.hint.statusline_minutes": "來自 {0} 的 statusline({1} 分鐘前);未呼叫 API,方案與各模型上限需要 CLI 登入資料",
281
+ "usage.hint.claude_api_key": "API key 登入沒有訂閱用量資料。請使用 `claude /login`,或設定 CLAUDE_CODE_OAUTH_TOKEN(由 `claude setup-token` 取得)。",
282
+ "usage.hint.login_claude": "請使用 Claude Code CLI(`claude`)登入。",
283
+ "usage.hint.login_agy": "請使用 Antigravity CLI(`agy`)登入。", "usage.hint.no_quota": "此帳號未回報額度資訊。",
284
+ "usage.hint.login_codex": "請使用 Codex CLI(`codex`)登入。", "usage.hint.codex_no_oauth": "auth.json 沒有 OAuth 登入資料;只有 API key 時無法讀取用量。",
285
+ "usage.hint.login_grok": "請使用 Grok CLI(`grok login`)登入。", "usage.hint.grok_no_login": "auth.json 沒有可用的登入資料。請執行 `grok login`。",
286
+ "usage.hint.login_kiro": "請使用 Kiro CLI(`kiro-cli`)登入。", "usage.hint.kiro_signed_out": "目前已登出;請執行 `kiro-cli` 登入。",
287
+ "usage.hint.token_refreshing": "Token 更新中 — 請稍候再試。", "usage.hint.subscription_no_credit": "訂閱方案沒有可回報的額度用量。",
288
+ "usage.hint.no_quota_account": "此帳號類型沒有可用的額度資訊。",
289
+ "usage.error.unreachable": "無法連線至 {0}。", "usage.error.token_rejected": "Token 已被拒絕。請執行一次 `{0}` 以更新登入。",
290
+ "usage.error.rate_limited": "{0} 暫時限制請求速率,請稍後再試。", "usage.error.http": "用量請求失敗(HTTP {0})。",
291
+ "usage.error.billing_http": "計費請求失敗(HTTP {0})。", "usage.error.invalid_response": "用量端點回傳了無效資料。",
292
+ "usage.error.invalid_billing_response": "計費端點回傳了無效資料。", "usage.error.invalid_kiro_response": "GetUsageLimits 回傳了無效資料。",
293
+ "usage.error.claude_token_expired": "Access token 已過期;下次啟動任一 Claude instance 時會重新整理。",
294
+ "usage.error.codex_token_expired": "Access token 已過期。請執行一次 `codex` 以重新整理。",
295
+ "usage.error.grok_expired": "Grok session 已過期。請再次執行 `grok login`。", "usage.error.grok_response_changed": "Grok 計費回應格式已變更。",
296
+ "usage.error.agy_cap": "個人上限已用完,CLI 正受到速率限制(約 {0} 後重置)。各模型的額度池是另一組計數,因此仍可能顯示 0% 已使用。",
297
+ "usage.error.agy_no_refresh": "Access token 已過期,且未儲存 refresh token。請執行一次 `agy`。",
298
+ "usage.error.agy_token_expired": "Access token 已過期。請執行一次 `agy` 重新整理,或設定 AGY_OAUTH_CLIENT_ID/SECRET 以自動更新。",
299
+ "usage.error.agy_login_expired": "Google 登入已過期。請執行一次 `agy` 重新登入。", "usage.error.agy_refresh_empty": "Google token 更新回應沒有 access token。",
300
+ "usage.error.grok_auth_readonly": "Token 已過期且 auth.json 為唯讀。請執行一次 `grok` 重新整理。", "usage.error.grok_no_refresh": "Token 已過期且找不到 refresh token。請執行 `grok login`。",
301
+ "usage.error.grok_refresh_unreachable": "無法連線至 auth.x.ai 以更新 token。", "usage.error.grok_invalid_refresh": "auth.x.ai 回傳了無效的 token 更新資料。",
302
+ "usage.error.grok_persist": "Token 已更新但無法寫入 auth.json。請改為執行一次 `grok`。",
303
+ usageButton: "AI 訂閱用量", languageButton: "語言", helpButton: "使用說明", close: "關閉", expandProfile: "展開個人資料",
304
+ rosterFailed: "載入 instance 清單失敗:{error}\n(Fleet 是否正在執行?)", paneFailed: "⚠ 無法載入 {name} 的 pane(HTTP {status})。",
305
+ tokenRequired: "需要 web.token", avatarFailed: "上傳頭像失敗:{error}", saved: "已儲存", saveFailed: "儲存失敗:{error}" },
212
306
  };
213
307
  let lang = localStorage.getItem("agend_lang") || (navigator.language && navigator.language.toLowerCase().startsWith("zh") ? "zh-TW" : "en");
214
308
  if (!I18N[lang]) lang = "en";
215
309
  const T = (k) => (I18N[lang] && I18N[lang][k]) || I18N.en[k] || k;
310
+ const TF = (k, values = {}) => Object.entries(values).reduce((s, [name, value]) => s.replaceAll(`{${name}}`, String(value)), T(k));
216
311
  function applyI18n() {
217
312
  document.querySelectorAll("[data-i18n]").forEach(e => { e.textContent = T(e.getAttribute("data-i18n")); });
218
313
  document.querySelectorAll("[data-i18n-ph]").forEach(e => { e.setAttribute("placeholder", T(e.getAttribute("data-i18n-ph"))); });
314
+ document.querySelectorAll("[data-i18n-title]").forEach(e => { e.setAttribute("title", T(e.getAttribute("data-i18n-title"))); });
219
315
  const hc = q("helpContent");
220
316
  if (hc) { hc.innerHTML = ""; const h3 = document.createElement("h3"); h3.textContent = T("helpTitle"); hc.append(h3);
221
317
  const ul = document.createElement("ul"); for (const k of ["help1","help2","help3","help4","help5"]) { const li = document.createElement("li"); li.textContent = T(k); ul.append(li); } hc.append(ul); }
@@ -557,7 +653,7 @@
557
653
  if (!current && roster.length) current = roster[0].instance_name;
558
654
  computeOrder();
559
655
  renderList(); renderCard();
560
- } catch (e) { q("pre").textContent = "Failed to load roster: " + e.message + "\n(Is the fleet running?)"; }
656
+ } catch (e) { q("pre").textContent = TF("rosterFailed", { error: e.message }); }
561
657
  }
562
658
 
563
659
  // ── Terminal ──────────────────────────────────────────
@@ -575,7 +671,7 @@
575
671
  if (!r.ok) {
576
672
  // Surface the failure instead of leaving the previous instance's output
577
673
  // on screen (which reads as "switching did nothing").
578
- q("pre").textContent = `⚠ Could not load pane for ${target} (HTTP ${r.status}).`;
674
+ q("pre").textContent = TF("paneFailed", { name: target, status: r.status });
579
675
  return;
580
676
  }
581
677
  const text = await r.text();
@@ -714,7 +810,7 @@
714
810
  const it = roster.find(r => r.instance_name === current);
715
811
  if (!it) return;
716
812
  const wt = q("fToken").value.trim();
717
- if (!wt) { q("msg").className = "err"; q("msg").textContent = "web.token required"; return; }
813
+ if (!wt) { q("msg").className = "err"; q("msg").textContent = T("tokenRequired"); return; }
718
814
  localStorage.setItem("agend_web_token", wt);
719
815
  const name = encodeURIComponent(current);
720
816
  try {
@@ -727,12 +823,12 @@
727
823
  const file = q("fAvatar").files[0];
728
824
  if (file) {
729
825
  const ar = await fetch(`/api/avatar/${name}`, { method: "POST", headers: { "Content-Type": file.type, "X-Agend-Token": wt }, body: file });
730
- if (!ar.ok) { const e = await ar.json().catch(() => ({})); throw new Error("avatar: " + (e.error || ar.status)); }
826
+ if (!ar.ok) { const e = await ar.json().catch(() => ({})); throw new Error(TF("avatarFailed", { error: e.error || ar.status })); }
731
827
  }
732
- q("msg").className = "ok"; q("msg").textContent = "Saved";
828
+ q("msg").className = "ok"; q("msg").textContent = T("saved");
733
829
  await loadRoster();
734
830
  setTimeout(exitEdit, 500);
735
- } catch (e) { q("msg").className = "err"; q("msg").textContent = e.message; }
831
+ } catch (e) { q("msg").className = "err"; q("msg").textContent = e.message.startsWith(T("avatarFailed").split("{")[0]) ? e.message : TF("saveFailed", { error: e.message }); }
736
832
  }
737
833
 
738
834
  q("editBtn").onclick = enterEdit;
@@ -783,19 +879,32 @@
783
879
  if (!iso) return "";
784
880
  const ms = new Date(iso).getTime() - Date.now();
785
881
  if (isNaN(ms)) return "";
786
- if (ms <= 0) return T("usageSoon");
787
- const h = Math.floor(ms / 3600000), m = Math.floor((ms % 3600000) / 60000);
788
- const t = h >= 48 ? `${Math.floor(h / 24)}d ${h % 24}h` : h > 0 ? `${h}h ${m}m` : `${m}m`;
789
- return T("usageResets").replace("{t}", t);
882
+ if (ms <= 0) return T("usage.reset.soon");
883
+ const totalMinutes = Math.max(1, Math.ceil(ms / 60000));
884
+ const d = Math.floor(totalMinutes / 1440), h = Math.floor((totalMinutes % 1440) / 60), m = totalMinutes % 60;
885
+ const duration = totalMinutes >= 2880
886
+ ? usageLocalized("", { key: "usage.duration.days_hours", args: [d, h] })
887
+ : totalMinutes >= 60
888
+ ? usageLocalized("", { key: "usage.duration.hours_minutes", args: [Math.floor(totalMinutes / 60), m] })
889
+ : usageLocalized("", { key: "usage.duration.minutes", args: [totalMinutes] });
890
+ return usageLocalized("", { key: "usage.reset.in", args: [duration] });
891
+ }
892
+
893
+ function usageLocalized(fallback, ref) {
894
+ if (!ref || typeof ref.key !== "string") return fallback == null ? "" : String(fallback);
895
+ const args = Array.isArray(ref.args) ? ref.args : [];
896
+ return args.reduce((text, value, index) => text.replaceAll(`{${index}}`, String(value)), T(ref.key));
790
897
  }
791
898
 
792
899
  function usageMetricHtml(m) {
900
+ const label = usageLocalized(m.label, m.labelI18n);
901
+ const note = m.note ? usageLocalized(m.note, m.noteI18n) : "";
793
902
  if (m.type === "percent") {
794
903
  const pct = Math.min(100, Math.max(0, m.used ?? 0));
795
904
  const cls = pct >= 90 ? "crit" : pct >= 70 ? "warn" : "";
796
905
  const word = pct >= 90 ? T("usageNear") : pct >= 70 ? T("usageHigh") : "";
797
- const sub = [m.note, usageResetText(m.resetsAt)].filter(Boolean).join(" · ");
798
- return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(m.label)}</span>` +
906
+ const sub = [note, usageResetText(m.resetsAt)].filter(Boolean).join(" · ");
907
+ return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(label)}</span>` +
799
908
  (word ? `<span class="u-status ${cls}">${esc(word)}</span>` : "") +
800
909
  `<span class="u-val">${pct.toFixed(0)}%</span></div>` +
801
910
  `<div class="u-meter"><div class="u-fill ${cls}" style="width:${pct}%"></div></div>` +
@@ -803,19 +912,22 @@
803
912
  }
804
913
  if (m.type === "dollars") {
805
914
  const val = m.limit ? `$${(m.used ?? 0).toFixed(2)} / $${m.limit.toFixed(2)}` : `$${(m.used ?? 0).toFixed(2)}`;
806
- return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(m.label)}</span><span class="u-val">${esc(val)}</span></div></div>`;
915
+ return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(label)}</span><span class="u-val">${esc(val)}</span></div>` +
916
+ (note ? `<div class="u-sub">${esc(note)}</div>` : "") + `</div>`;
807
917
  }
808
- const val = `${m.value ?? ""} ${m.unit ?? ""}`.trim();
809
- return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(m.label)}</span><span class="u-val">${esc(val)}</span></div>` +
810
- (m.note ? `<div class="u-sub">${esc(m.note)}</div>` : "") + `</div>`;
918
+ const value = usageLocalized(m.value ?? "", m.valueI18n);
919
+ const unit = usageLocalized(m.unit ?? "", m.unitI18n);
920
+ const val = `${value} ${unit}`.trim();
921
+ return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(label)}</span><span class="u-val">${esc(val)}</span></div>` +
922
+ (note ? `<div class="u-sub">${esc(note)}</div>` : "") + `</div>`;
811
923
  }
812
924
 
813
925
  function renderUsage() {
814
926
  const box = q("usageContent");
815
- if (!usageData || !Array.isArray(usageData.providers)) { box.innerHTML = `<h3>${T("usageTitle")}</h3><div class="u-err">${T("usageFail")}</div>`; return; }
816
- let html = `<h3>${T("usageTitle")}</h3>`;
927
+ if (!usageData || !Array.isArray(usageData.providers)) { box.innerHTML = `<h3>📊 ${T("usage.title")}</h3><div class="u-err">${T("usageFail")}</div>`; return; }
928
+ let html = `<h3>📊 ${T("usage.title")}</h3>`;
817
929
  const providers = orderedUsageProviders(usageData.providers);
818
- if (!providers.length) html += `<div class="u-err">${T("usageEmpty")}</div>`;
930
+ if (!providers.length) html += `<div class="u-err">${T("usage.empty_active")}</div>`;
819
931
  for (let index = 0; index < providers.length; index++) {
820
932
  const p = providers[index];
821
933
  const key = providerKey(p);
@@ -823,13 +935,18 @@
823
935
  (p.plan ? `<span class="u-plan">${esc(p.plan)}</span>` : "") +
824
936
  `<span class="u-order"><button class="u-move" data-provider="${esc(key)}" data-delta="-1" title="${T("usageMoveUp")}" aria-label="${T("usageMoveUp")}"${index === 0 ? " disabled" : ""}>↑</button>` +
825
937
  `<button class="u-move" data-provider="${esc(key)}" data-delta="1" title="${T("usageMoveDown")}" aria-label="${T("usageMoveDown")}"${index === providers.length - 1 ? " disabled" : ""}>↓</button></span></div>`;
826
- if (p.status === "error") html += `<div class="u-err">${esc(p.error || "error")}</div>`;
938
+ if (p.status === "error") html += `<div class="u-err">${esc(usageLocalized(p.error || T("usage.error_fallback"), p.errorI18n))}</div>`;
939
+ else if (p.status === "no-credentials") {
940
+ html += `<div class="u-note">${esc(T("usage.not_logged_in"))}</div>`;
941
+ if (p.hint) html += `<div class="u-note">${esc(usageLocalized(p.hint, p.hintI18n))}</div>`;
942
+ }
827
943
  else {
828
944
  html += (p.metrics || []).map(usageMetricHtml).join("");
829
945
  // A provider with nothing to meter (subscription plan) or one awaiting
830
946
  // sign-in carries a hint instead of metrics. Without this the panel
831
947
  // rendered an empty box, so those states looked broken.
832
- if (p.hint) html += `<div class="u-note">${esc(p.hint)}</div>`;
948
+ if (p.hint) html += `<div class="u-note">${esc(usageLocalized(p.hint, p.hintI18n))}</div>`;
949
+ else if (!(p.metrics || []).length) html += `<div class="u-note">${esc(T("usage.no_data"))}</div>`;
833
950
  }
834
951
  html += `</div>`;
835
952
  }
@@ -0,0 +1,17 @@
1
+ html, body { margin: 0; height: 100%; background: #111; color: #ddd; font: 14px system-ui, sans-serif; }
2
+ body { display: flex; flex-direction: column; }
3
+ #bar { display: flex; align-items: center; gap: 12px; padding: 6px 10px; background: #1c1c1c; border-bottom: 1px solid #333; }
4
+ #bar #title { font-weight: 600; }
5
+ #bar #ttl { margin-left: auto; font-variant-numeric: tabular-nums; color: #9ad; }
6
+ #bar button { background: #402; color: #fbb; border: 1px solid #733; border-radius: 4px; padding: 3px 10px; cursor: pointer; }
7
+ #gate { max-width: 560px; margin: 40px auto; padding: 0 16px; line-height: 1.5; }
8
+ #gate form { display: flex; gap: 8px; margin-top: 12px; }
9
+ #gate input { flex: 1; font: 16px ui-monospace, monospace; letter-spacing: 1px; padding: 8px; background: #000; color: #fff; border: 1px solid #555; border-radius: 4px; }
10
+ #gate button { padding: 8px 16px; background: #246; color: #fff; border: 0; border-radius: 4px; cursor: pointer; }
11
+ #gate-msg { color: #f88; min-height: 1.5em; }
12
+ #term { flex: 1; min-height: 0; padding: 4px; }
13
+ #keys { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px; background: #1c1c1c; border-top: 1px solid #333; }
14
+ #keys button { background: #222; color: #ddd; border: 1px solid #444; border-radius: 4px; padding: 6px 10px; font: 14px ui-monospace, monospace; cursor: pointer; }
15
+ #done { padding: 10px; background: #1c1c1c; border-top: 1px solid #333; }
16
+ #done.ok { color: #8d8; } #done.bad { color: #f88; }
17
+ @media (min-width: 900px) { #keys { display: none; } }
@@ -0,0 +1,43 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
6
+ <meta name="referrer" content="no-referrer">
7
+ <title>AgEnD terminal</title>
8
+ <link rel="stylesheet" href="assets/xterm.css">
9
+ <link rel="stylesheet" href="assets/terminal.css">
10
+ </head>
11
+ <body>
12
+ <header id="bar">
13
+ <span id="title">AgEnD terminal</span>
14
+ <span id="ttl" hidden></span>
15
+ <button id="btn-close" type="button" hidden title="End this session now (the command is stopped and the terminal closes)">Stop</button>
16
+ </header>
17
+ <section id="gate">
18
+ <p>This terminal is connected to <strong>one command only</strong> and closes when it exits or when the time limit lapses. There is no shell behind it.</p>
19
+ <p>Paste the <strong>access token</strong> you received in chat. It is separate from this link; the link alone grants nothing.</p>
20
+ <form id="gate-form" autocomplete="off">
21
+ <input id="token" type="text" inputmode="text" autocapitalize="characters" spellcheck="false" placeholder="access token" maxlength="40" required>
22
+ <button type="submit">Open</button>
23
+ </form>
24
+ <p id="gate-msg" role="alert"></p>
25
+ </section>
26
+ <main id="term" hidden></main>
27
+ <footer id="keys" hidden>
28
+ <button data-seq="\u001b" type="button">Esc</button>
29
+ <button data-seq="\t" type="button">Tab</button>
30
+ <button data-seq="\u001b[A" type="button">&uarr;</button>
31
+ <button data-seq="\u001b[B" type="button">&darr;</button>
32
+ <button data-seq="\u001b[D" type="button">&larr;</button>
33
+ <button data-seq="\u001b[C" type="button">&rarr;</button>
34
+ <button data-seq="\r" type="button">Enter</button>
35
+ <button data-seq="\u0003" type="button">Ctrl-C</button>
36
+ </footer>
37
+ <div id="done" hidden></div>
38
+ <script src="assets/xterm.js"></script>
39
+ <script src="assets/addon-fit.js"></script>
40
+ <script src="assets/addon-web-links.js"></script>
41
+ <script src="assets/terminal.js"></script>
42
+ </body>
43
+ </html>
@@ -0,0 +1,148 @@
1
+ /* Browser side of the AgEnD web terminal. No external origins (CSP). */
2
+ (function () {
3
+ "use strict";
4
+ var base = location.pathname.replace(/\/$/, "");
5
+ var gate = document.getElementById("gate");
6
+ var gateForm = document.getElementById("gate-form");
7
+ var gateMsg = document.getElementById("gate-msg");
8
+ var tokenInput = document.getElementById("token");
9
+ var termEl = document.getElementById("term");
10
+ var keys = document.getElementById("keys");
11
+ var ttlEl = document.getElementById("ttl");
12
+ var doneEl = document.getElementById("done");
13
+ var closeBtn = document.getElementById("btn-close");
14
+ var ws = null, term = null, fit = null, ttlTimer = null, expiresAt = 0;
15
+ var finished = false, reconnectAttempts = 0;
16
+
17
+ // A reload or a second tab within the TTL still holds the HttpOnly cookie:
18
+ // try the WebSocket first and only fall back to the token gate on refusal.
19
+ // This GET-free probe keeps the page itself side-effect free.
20
+ connect(true);
21
+
22
+ gateForm.addEventListener("submit", function (ev) {
23
+ ev.preventDefault();
24
+ var token = tokenInput.value.trim();
25
+ if (!token) return;
26
+ gateMsg.textContent = "";
27
+ fetch(base + "/open", {
28
+ method: "POST",
29
+ credentials: "same-origin",
30
+ headers: { "Content-Type": "application/json" },
31
+ body: JSON.stringify({ token: token })
32
+ }).then(function (res) {
33
+ if (res.status === 204) {
34
+ tokenInput.value = "";
35
+ // The one-time token is now consumed: the first connection MUST get the
36
+ // full retry budget even though the server's TTL is not yet known.
37
+ if (!expiresAt) expiresAt = Date.now() + 60000;
38
+ reconnectAttempts = 0;
39
+ connect(false);
40
+ return;
41
+ }
42
+ return res.json().catch(function () { return {}; }).then(function (body) {
43
+ if (res.status === 403 && typeof body.remaining === "number") {
44
+ gateMsg.textContent = "Invalid token. " + body.remaining + " attempt" + (body.remaining === 1 ? "" : "s") + " left before this session is destroyed.";
45
+ } else if (res.status === 409) {
46
+ gateMsg.textContent = "This token was already used. If that was not you, tell the fleet admin: the link may have leaked.";
47
+ } else if (res.status === 410) {
48
+ gateMsg.textContent = "This session has ended.";
49
+ } else {
50
+ gateMsg.textContent = "Could not open: " + (body.error || res.status);
51
+ }
52
+ });
53
+ }).catch(function () { gateMsg.textContent = "Network error."; });
54
+ });
55
+
56
+ function connect(probe) {
57
+ var proto = location.protocol === "https:" ? "wss://" : "ws://";
58
+ var opened = false;
59
+ // One live connection at a time: a new connect() supersedes the previous
60
+ // one, and every callback below is fenced on `conn` so a stale socket's
61
+ // events can never touch the current state (a pending cookie probe while
62
+ // the user submits a token, or an old socket closing after a reconnect).
63
+ if (ws && ws.readyState <= 1) { try { ws.close(); } catch (e) { /* ignore */ } }
64
+ var conn = new WebSocket(proto + location.host + base + "/ws");
65
+ ws = conn;
66
+ conn.binaryType = "arraybuffer";
67
+ conn.onopen = function () {
68
+ if (ws !== conn) return;
69
+ opened = true; reconnectAttempts = 0;
70
+ gate.hidden = true; termEl.hidden = false; keys.hidden = false; closeBtn.hidden = false;
71
+ if (!term) {
72
+ term = new Terminal({ cursorBlink: true, fontSize: 14, scrollback: 2000, theme: { background: "#111" } });
73
+ fit = new FitAddon.FitAddon();
74
+ term.loadAddon(fit);
75
+ term.loadAddon(new WebLinksAddon.WebLinksAddon());
76
+ term.open(termEl);
77
+ term.onData(function (s) { send(new TextEncoder().encode(s)); });
78
+ term.onBinary(function (s) { var b = new Uint8Array(s.length); for (var i = 0; i < s.length; i++) b[i] = s.charCodeAt(i) & 255; send(b); });
79
+ term.onResize(function (size) { if (ws && ws.readyState === 1) ws.send(JSON.stringify({ t: "resize", cols: size.cols, rows: size.rows })); });
80
+ window.addEventListener("resize", function () { if (fit) fit.fit(); });
81
+ } else {
82
+ term.reset(); // the server replays its buffer on (re)connect
83
+ }
84
+ fit.fit();
85
+ term.focus();
86
+ };
87
+ conn.onmessage = function (ev) {
88
+ if (ws !== conn) return;
89
+ if (typeof ev.data === "string") {
90
+ var msg; try { msg = JSON.parse(ev.data); } catch (e) { return; }
91
+ if (msg.t === "hello") {
92
+ document.getElementById("title").textContent = "AgEnD · " + msg.kind + " · " + msg.backend;
93
+ expiresAt = Date.now() + msg.ttlRemainingMs;
94
+ if (ttlTimer) clearInterval(ttlTimer); // exactly one countdown, however many reconnects
95
+ ttlEl.hidden = false; tick(); ttlTimer = setInterval(tick, 1000);
96
+ if (msg.truncated && term) term.writeln("\x1b[33m[earlier output truncated]\x1b[0m");
97
+ } else if (msg.t === "exit") {
98
+ finish(msg.ok, (msg.ok ? "Finished" : "Ended") + ": " + msg.detail + (typeof msg.exitCode === "number" ? " (exit " + msg.exitCode + ")" : ""));
99
+ }
100
+ return;
101
+ }
102
+ if (term) term.write(new Uint8Array(ev.data));
103
+ };
104
+ conn.onclose = function (ev) {
105
+ if (ws !== conn || finished) return;
106
+ if (!opened && probe) { gate.hidden = false; return; } // initial cookie probe refused → token gate
107
+ if (opened && ev.code === 4000) { finish(false, "Replaced by a newer connection."); return; }
108
+ if (opened && (ev.code === 1000 || ev.code === 1001 || ev.code === 1008)) { finish(false, "Connection closed (" + (ev.reason || ev.code) + ")."); return; }
109
+ // Transient drop, or a reconnect attempt that failed before opening: the
110
+ // process is still running; keep trying within the TTL and the budget.
111
+ scheduleReconnect();
112
+ };
113
+ }
114
+
115
+ function scheduleReconnect() {
116
+ if (reconnectAttempts < 5 && (expiresAt === 0 || Date.now() < expiresAt)) {
117
+ reconnectAttempts++;
118
+ setTimeout(function () { if (!finished) connect(false); }, 500 * reconnectAttempts);
119
+ return;
120
+ }
121
+ finish(false, "Connection lost.");
122
+ }
123
+
124
+ function send(bytes) { if (ws && ws.readyState === 1 && bytes.length) ws.send(bytes); }
125
+
126
+ function tick() {
127
+ var left = Math.max(0, expiresAt - Date.now());
128
+ var m = Math.floor(left / 60000), s = Math.floor((left % 60000) / 1000);
129
+ ttlEl.textContent = "closes in " + m + ":" + (s < 10 ? "0" : "") + s;
130
+ }
131
+
132
+ function finish(ok, text) {
133
+ finished = true;
134
+ if (ttlTimer) clearInterval(ttlTimer);
135
+ doneEl.hidden = false; doneEl.className = ok ? "ok" : "bad"; doneEl.textContent = text;
136
+ keys.hidden = true; closeBtn.hidden = true;
137
+ if (term) term.options.disableStdin = true;
138
+ }
139
+
140
+ keys.addEventListener("click", function (ev) {
141
+ var b = ev.target.closest("button[data-seq]");
142
+ if (!b) return;
143
+ send(new TextEncoder().encode(JSON.parse('"' + b.getAttribute("data-seq") + '"')));
144
+ if (term) term.focus();
145
+ });
146
+ // Stop = end the session for real (server-side cancel), not just a Ctrl-C the CLI may ignore.
147
+ closeBtn.addEventListener("click", function () { if (ws && ws.readyState === 1) ws.send(JSON.stringify({ t: "cancel" })); });
148
+ })();
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
2
+ Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com)
3
+ Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,22 @@
1
+ # Vendored browser assets for the web terminal
2
+
3
+ These files are copied verbatim from the official npm tarballs (`npm pack`) and
4
+ served by AgEnD itself (`/t/<sid>/assets/*`). They are **not** install-time
5
+ dependencies: the CSP of the terminal page forbids every external origin, so
6
+ the fleet must ship them. All three packages are MIT licensed (see LICENSE.*).
7
+
8
+ | file | package | version | tarball sha512 (npm `dist.integrity`) |
9
+ |---|---|---|---|
10
+ | xterm.js, xterm.css | @xterm/xterm | 6.0.0 | sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg== |
11
+ | addon-fit.js | @xterm/addon-fit | 0.11.0 | sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g== |
12
+ | addon-web-links.js | @xterm/addon-web-links | 0.12.0 | sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw== |
13
+
14
+ The only local edit: the trailing `//# sourceMappingURL=…` line was removed
15
+ from all three .js files (no source maps are shipped). To refresh:
16
+
17
+ ```
18
+ npm pack @xterm/xterm@<v> @xterm/addon-fit@<v> @xterm/addon-web-links@<v>
19
+ tar xzf …; cp package/lib/*.js package/css/xterm.css package/LICENSE here
20
+ ```
21
+
22
+ then update this table with the new versions and `npm view <pkg> dist.integrity`.
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(globalThis,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core._renderService.dimensions;if(0===e.css.cell.width||0===e.css.cell.height)return;const t=0===this._terminal.options.scrollback?0:this._terminal.options.overviewRuler?.width||14,r=window.getComputedStyle(this._terminal.element.parentElement),i=parseInt(r.getPropertyValue("height")),o=Math.max(0,parseInt(r.getPropertyValue("width"))),s=window.getComputedStyle(this._terminal.element),n=i-(parseInt(s.getPropertyValue("padding-top"))+parseInt(s.getPropertyValue("padding-bottom"))),l=o-(parseInt(s.getPropertyValue("padding-right"))+parseInt(s.getPropertyValue("padding-left")))-t;return{cols:Math.max(2,Math.floor(l/e.css.cell.width)),rows:Math.max(1,Math.floor(n/e.css.cell.height))}}}})(),e})()));
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.WebLinksAddon=t():e.WebLinksAddon=t()}(globalThis,(()=>(()=>{"use strict";var e={490:(e,t)=>{function n(e){try{const t=new URL(e),n=t.password&&t.username?`${t.protocol}//${t.username}:${t.password}@${t.host}`:t.username?`${t.protocol}//${t.username}@${t.host}`:`${t.protocol}//${t.host}`;return e.toLocaleLowerCase().startsWith(n.toLocaleLowerCase())}catch(e){return!1}}Object.defineProperty(t,"__esModule",{value:!0}),t.LinkComputer=t.WebLinkProvider=void 0,t.WebLinkProvider=class{constructor(e,t,n,o={}){this._terminal=e,this._regex=t,this._handler=n,this._options=o}provideLinks(e,t){const n=o.computeLink(e,this._regex,this._terminal,this._handler);t(this._addCallbacks(n))}_addCallbacks(e){return e.map((e=>(e.leave=this._options.leave,e.hover=(t,n)=>{if(this._options.hover){const{range:o}=e;this._options.hover(t,n,o)}},e)))}};class o{static computeLink(e,t,r,i){const s=new RegExp(t.source,(t.flags||"")+"g"),[a,c]=o._getWindowedLineStrings(e-1,r),l=a.join("");let d;const p=[];for(;d=s.exec(l);){const e=d[0];if(!n(e))continue;const[t,s]=o._mapStrIdx(r,c,0,d.index),[a,l]=o._mapStrIdx(r,t,s,e.length);if(-1===t||-1===s||-1===a||-1===l)continue;const h={start:{x:s+1,y:t+1},end:{x:l,y:a+1}};p.push({range:h,text:e,activate:i})}return p}static _getWindowedLineStrings(e,t){let n,o=e,r=e,i=0,s="";const a=[];if(n=t.buffer.active.getLine(e)){const e=n.translateToString(!0);if(n.isWrapped&&" "!==e[0]){for(i=0;(n=t.buffer.active.getLine(--o))&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),n.isWrapped&&-1===s.indexOf(" ")););a.reverse()}for(a.push(e),i=0;(n=t.buffer.active.getLine(++r))&&n.isWrapped&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),-1===s.indexOf(" ")););}return[a,o]}static _mapStrIdx(e,t,n,o){const r=e.buffer.active,i=r.getNullCell();let s=n;for(;o;){const e=r.getLine(t);if(!e)return[-1,-1];for(let n=s;n<e.length;++n){e.getCell(n,i);const s=i.getChars();if(i.getWidth()&&(o-=s.length||1,n===e.length-1&&""===s)){const e=r.getLine(t+1);e&&e.isWrapped&&(e.getCell(0,i),2===i.getWidth()&&(o+=1))}if(o<0)return[t,n]}t++,s=0}return[t,s]}}t.LinkComputer=o}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var i=t[o]={exports:{}};return e[o](i,i.exports,n),i.exports}var o={};return(()=>{var e=o;Object.defineProperty(e,"__esModule",{value:!0}),e.WebLinksAddon=void 0;const t=n(490),r=/(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;function i(e,t){const n=window.open();if(n){try{n.opener=null}catch{}n.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}e.WebLinksAddon=class{constructor(e=i,t={}){this._handler=e,this._options=t}activate(e){this._terminal=e;const n=this._options,o=n.urlRegex||r;this._linkProvider=this._terminal.registerLinkProvider(new t.WebLinkProvider(this._terminal,o,this._handler,n))}dispose(){this._linkProvider?.dispose()}}})(),o})()));