@cocorograph/hub-agent 0.6.8 → 0.6.10

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの tmux/pty を中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/src/main.mjs CHANGED
@@ -37,7 +37,7 @@ import {
37
37
  listWorktreeStubs,
38
38
  removeWorktree as removeWorktreeDir,
39
39
  } from "./tmux.mjs"
40
- import { getSessionUsages, getUsage } from "./usage.mjs"
40
+ import { getSessionUsages, getUsage, recordChatRateLimit } from "./usage.mjs"
41
41
 
42
42
  const logger = pino({ name: "hub-agent" })
43
43
 
@@ -179,6 +179,15 @@ export async function startDaemon({ version, ptyModule, claudeSdk } = {}) {
179
179
  // claude.error を返す経路に分岐 (dispatch 側で判定)。
180
180
  if (claudeBridge) {
181
181
  claudeBridge.on("event", ({ stream_id, session_id, event }) => {
182
+ // SDK の rate_limit_event を捕捉して usage の 5h/7d 実値に反映する
183
+ // (チャットモードでは statusLine が更新されないため、これが live 値の唯一の源)。
184
+ if (event?.type === "rate_limit_event" && event.rate_limit_info) {
185
+ try {
186
+ recordChatRateLimit(event.rate_limit_info)
187
+ } catch {
188
+ /* ignore */
189
+ }
190
+ }
182
191
  client.send({ type: "claude.event", stream_id, session_id, event })
183
192
  })
184
193
  claudeBridge.on("permission", ({ stream_id, request_id, tool_name, input }) => {
package/src/skills.mjs CHANGED
@@ -52,6 +52,10 @@ function extractFrontmatter(text) {
52
52
  if (nameMatch) out.name = nameMatch[1].trim()
53
53
  const descMatch = fm.match(/^description:\s*["']?(.*)["']?\s*$/m)
54
54
  if (descMatch) out.description = descMatch[1].replace(/^["']|["']$/g, "").trim()
55
+ // label_ja: cockpit chat の SkillBar / サジェストに出す日本語ボタンラベル。
56
+ // 無ければ表示側で `/name` にフォールバック (送信は常に `/name`)。
57
+ const labelMatch = fm.match(/^label_ja:\s*["']?(.*?)["']?\s*$/m)
58
+ if (labelMatch) out.label_ja = labelMatch[1].replace(/^["']|["']$/g, "").trim()
55
59
  return out
56
60
  }
57
61
 
@@ -80,6 +84,7 @@ async function loadSkillsDir(skillsDir, source) {
80
84
  out.push({
81
85
  name: fm.name || e.name,
82
86
  description: fm.description || "",
87
+ ...(fm.label_ja ? { label: fm.label_ja } : {}),
83
88
  source,
84
89
  })
85
90
  }
@@ -103,6 +108,7 @@ async function loadCommandsDir(commandsDir, source) {
103
108
  out.push({
104
109
  name: fm.name || f.replace(/\.md$/, ""),
105
110
  description: fm.description || "",
111
+ ...(fm.label_ja ? { label: fm.label_ja } : {}),
106
112
  source,
107
113
  })
108
114
  }
package/src/usage.mjs CHANGED
@@ -107,6 +107,42 @@ async function readOrNull(p) {
107
107
  }
108
108
  }
109
109
 
110
+ // ---------------------------------------------------------------------------
111
+ // チャット(SDK) の rate_limit_event から取得した最新 rate-limit (プロセス内共有)。
112
+ // statusLine cache はターミナルでしか更新されないため、チャットモードでは
113
+ // SDK stream の rate_limit_event を捕捉してここに溜め、getUsage で 5h/7d に反映する。
114
+ // ---------------------------------------------------------------------------
115
+ const chatRateLimits = {
116
+ /** @type {{percent:number, resetAtMs:number|null}|null} */ five_hour: null,
117
+ /** @type {{percent:number, resetAtMs:number|null}|null} */ seven_day: null,
118
+ updatedAtMs: 0,
119
+ }
120
+
121
+ /** utilization (0-1 割合 or 0-100 %) を % に正規化。 */
122
+ function utilizationToPercent(u) {
123
+ if (typeof u !== "number" || !Number.isFinite(u)) return null
124
+ return u <= 1 ? Math.round(u * 1000) / 10 : Math.round(u * 10) / 10
125
+ }
126
+
127
+ /**
128
+ * SDK の SDKRateLimitInfo を取り込む (main.mjs の rate_limit_event ハンドラから呼ぶ)。
129
+ * rateLimitType: 'five_hour' / 'seven_day*' を 5h / 7d スロットに振り分ける。
130
+ */
131
+ export function recordChatRateLimit(info) {
132
+ if (!info || typeof info !== "object") return
133
+ const type = info.rateLimitType
134
+ const slot = type === "five_hour" ? "five_hour" : typeof type === "string" && type.startsWith("seven_day") ? "seven_day" : null
135
+ if (!slot) return // overage 等は対象外
136
+ const percent = utilizationToPercent(info.utilization)
137
+ if (percent === null) return
138
+ let resetAtMs = null
139
+ if (typeof info.resetsAt === "number") {
140
+ resetAtMs = info.resetsAt < 1e12 ? info.resetsAt * 1000 : info.resetsAt
141
+ }
142
+ chatRateLimits[slot] = { percent, resetAtMs }
143
+ chatRateLimits.updatedAtMs = Date.now()
144
+ }
145
+
110
146
  async function readOfficial(now) {
111
147
  const text = await readOrNull(statuslineCache())
112
148
  if (!text) return null
@@ -339,10 +375,10 @@ export async function getUsage() {
339
375
  const now = Date.now()
340
376
  const official = await readOfficial(now)
341
377
  const result = official || (await readEstimate(now))
378
+ const cacheMtime = await statuslineCacheMtime()
342
379
 
343
380
  const est = await latestJsonlContext(now)
344
381
  if (est) {
345
- const cacheMtime = await statuslineCacheMtime()
346
382
  // statusLine が jsonl と同程度以上に新しければ official.context が信頼できる
347
383
  // (ターミナルモード)。jsonl の方が新しい (チャットモード) なら推定で上書き。
348
384
  const officialFresh =
@@ -354,6 +390,33 @@ export async function getUsage() {
354
390
  result.context = est.percent
355
391
  }
356
392
  }
393
+
394
+ // チャット(SDK) の rate_limit_event が statusLine cache より新しければ、5h/7d を
395
+ // その実値 (utilization) で上書きする。ターミナルが statusLine を更新していない
396
+ // チャットモードでも 5h/7d がライブで動くようになる。
397
+ if (
398
+ chatRateLimits.updatedAtMs > 0 &&
399
+ (cacheMtime === null || chatRateLimits.updatedAtMs > cacheMtime)
400
+ ) {
401
+ if (chatRateLimits.five_hour) {
402
+ result.last5h = {
403
+ ...result.last5h,
404
+ percent: chatRateLimits.five_hour.percent,
405
+ resetAtMs: chatRateLimits.five_hour.resetAtMs ?? result.last5h?.resetAtMs ?? null,
406
+ }
407
+ }
408
+ if (chatRateLimits.seven_day) {
409
+ result.last7d = {
410
+ ...result.last7d,
411
+ percent: chatRateLimits.seven_day.percent,
412
+ resetAtMs: chatRateLimits.seven_day.resetAtMs ?? result.last7d?.resetAtMs ?? null,
413
+ }
414
+ }
415
+ if (chatRateLimits.five_hour || chatRateLimits.seven_day) {
416
+ // rate_limit_event は API 実値なので official 扱い。
417
+ result.source = "official"
418
+ }
419
+ }
357
420
  return result
358
421
  }
359
422