linkshell-cli 0.5.16 → 0.5.17

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": "linkshell-cli",
3
- "version": "0.5.16",
3
+ "version": "0.5.17",
4
4
  "description": "Remote terminal bridge for Claude Code / Codex / Gemini / Copilot — control from your phone",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,8 +36,8 @@
36
36
  "qrcode-terminal": "^0.12.0",
37
37
  "ws": "^8.18.1",
38
38
  "zod": "^3.24.3",
39
- "@linkshell/gateway": "0.4.35",
40
- "@linkshell/protocol": "0.4.7"
39
+ "@linkshell/protocol": "0.4.7",
40
+ "@linkshell/gateway": "0.4.36"
41
41
  },
42
42
  "optionalDependencies": {
43
43
  "@anthropic-ai/claude-agent-sdk": "^0.2.128",
@@ -1709,6 +1709,7 @@ export class AgentWorkspaceProxy {
1709
1709
  case "agent.v2.capabilities.request":
1710
1710
  await this.initialize();
1711
1711
  this.sendCapabilities();
1712
+ this.sendUsageReport();
1712
1713
  break;
1713
1714
  case "agent.v2.conversation.open": {
1714
1715
  const payload = parseTypedPayload("agent.v2.conversation.open", envelope.payload);
@@ -1775,22 +1776,7 @@ export class AgentWorkspaceProxy {
1775
1776
  break;
1776
1777
  }
1777
1778
  case "agent.v2.usage.request": {
1778
- // Aggregate ALL on-disk transcripts (decoupled from live turns). The
1779
- // scan is synchronous (~3s over a large corpus); acceptable for an
1780
- // explicit, on-demand dashboard open. Errors must never break the
1781
- // session, so swallow + send an empty report.
1782
- try {
1783
- const report = aggregateUsage();
1784
- this.input.send(createEnvelope({
1785
- type: "agent.v2.usage.report",
1786
- sessionId: this.input.sessionId,
1787
- payload: report,
1788
- }));
1789
- } catch (error) {
1790
- if (this.input.verbose) {
1791
- process.stderr.write(`[agent:v2] usage aggregation failed: ${error instanceof Error ? error.message : String(error)}\n`);
1792
- }
1793
- }
1779
+ this.sendUsageReport();
1794
1780
  break;
1795
1781
  }
1796
1782
  case "agent.v2.prompt": {
@@ -4282,6 +4268,27 @@ export class AgentWorkspaceProxy {
4282
4268
  }
4283
4269
  return firstString(raw, ["turnId", "id"]);
4284
4270
  }
4271
+
4272
+ // Fire-and-forget usage report — sent on init so the session list page shows
4273
+ // summary cards without anyone clicking a button. First call scans disk (~3s)
4274
+ // so it's deferred off the handler's critical path; cached (60s TTL)
4275
+ // thereafter. Errors silently swallowed (best-effort, never blocks).
4276
+ private sendUsageReport(): void {
4277
+ setTimeout(() => {
4278
+ try {
4279
+ const report = aggregateUsage();
4280
+ this.input.send(createEnvelope({
4281
+ type: "agent.v2.usage.report",
4282
+ sessionId: this.input.sessionId,
4283
+ payload: report,
4284
+ }));
4285
+ } catch (error) {
4286
+ if (this.input.verbose) {
4287
+ process.stderr.write(`[agent:v2] usage aggregation failed: ${error instanceof Error ? error.message : String(error)}\n`);
4288
+ }
4289
+ }
4290
+ }, 0);
4291
+ }
4285
4292
  }
4286
4293
 
4287
4294
  function parsePermissionOptions(value: unknown): AgentPermission["options"] {
@@ -328,8 +328,15 @@ function ingestCodex(acc: Accumulator): number {
328
328
  return sessions;
329
329
  }
330
330
 
331
+ let _cachedReport: UsageReport | null = null;
332
+ let _cachedAt = 0;
333
+
331
334
  export function aggregateUsage(): UsageReport {
335
+ const now = Date.now();
336
+ if (_cachedReport && now - _cachedAt < 60_000) return _cachedReport;
332
337
  const acc = new Accumulator();
333
338
  const sessions = ingestClaude(acc) + ingestCodex(acc);
334
- return acc.finish(sessions, Date.now());
339
+ _cachedReport = acc.finish(sessions, now);
340
+ _cachedAt = now;
341
+ return _cachedReport;
335
342
  }