@alook/daemon 0.1.28 → 0.1.29

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/README.md CHANGED
@@ -97,12 +97,14 @@ Pi declares the `in_process_sdk` transport and
97
97
  `midTurnDelivery: "steer"`. Its lane delegates prompt, steer, abort, and dispose
98
98
  to the SDK while preserving the same receipts, events, and terminal contract.
99
99
 
100
- ### Persistent queued runtime (Cursor)
100
+ ### Persistent steering runtime (Cursor)
101
101
 
102
102
  Cursor keeps one `cursor-agent acp` process and ACP session for the logical
103
- session. Each idle command is a `session/prompt` request; while one is active,
104
- later commands remain in the logical next-turn FIFO until its correlated
105
- response arrives. Interrupt sends `session/cancel` without killing the process.
103
+ session. A root command starts a `session/prompt`; busy commands steer the same
104
+ logical turn through concurrent prompt requests. Each request has its own
105
+ JSON-RPC id, while the root receipt remains the stable terminal owner. Old
106
+ responses cannot settle the current turn. Interrupt sends `session/cancel`
107
+ without killing the process.
106
108
 
107
109
  ### Persistent service runtime (OpenCode)
108
110
 
@@ -120,7 +122,7 @@ live stream handles permissions.
120
122
  | **claude** | persistent session | stream-json NDJSON | `safe_boundary_queue` | stdin user-message line | stream-json |
121
123
  | **codex** | persistent session | JSON-RPC 2.0 (`app-server --listen stdio://`) | `safe_boundary_queue` | `initialize` → `thread/start`/`resume` | JSON-RPC notifications |
122
124
  | **pi** | persistent session | `@earendil-works/pi-coding-agent` | `steer` | `session.prompt()` | SDK callback |
123
- | **cursor** | persistent session | ACP JSON-RPC 2.0 (`cursor-agent acp`) | `next_turn_queue` | `session/prompt` | `session/update` + correlated prompt response |
125
+ | **cursor** | persistent session | ACP JSON-RPC 2.0 (`cursor-agent acp`) | `steer` | `session/prompt` | `session/update` + correlated prompt response |
124
126
  | **opencode** | persistent session | authenticated HTTP + SSE (`opencode serve --pure`) | `steer` | v2 session prompt API | durable + live SSE |
125
127
 
126
128
  (Exact launch flags live in `agent-driver/src/adapters/<backend>/`.)
package/dist/cli/index.js CHANGED
@@ -16934,6 +16934,7 @@ var ALLOWED_ICON_MIME_TYPES = [
16934
16934
  ];
16935
16935
  var MAX_ICON_SOURCE_FILE_SIZE_BYTES = 15 * 1024 * 1024;
16936
16936
  // ../shared/src/provider-telemetry.ts
16937
+ var DAILY_TOKEN_USAGE_WINDOW_DAYS = 30;
16937
16938
  var safeToken = exports_external.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
16938
16939
  var boundedText = exports_external.string().min(1).refine((value) => new TextEncoder().encode(value).length <= 64, { message: "must be at most 64 UTF-8 bytes" });
16939
16940
  var DailyUsageMetricSchema = safeToken.nullable();
@@ -17665,7 +17666,7 @@ var AgentActivityMessageSchema = exports_external.object({
17665
17666
  state: exports_external.enum(["idle", "starting", "running", "stopping"]),
17666
17667
  usageTimeZone: exports_external.string().min(1).max(128).optional(),
17667
17668
  usageDay: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
17668
- dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(7).optional(),
17669
+ dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(DAILY_TOKEN_USAGE_WINDOW_DAYS).optional(),
17669
17670
  quota: ProviderQuotaSnapshotSchema.optional()
17670
17671
  });
17671
17672
  var AgentTypingMessageSchema = exports_external.object({
@@ -20637,11 +20638,33 @@ function tryParseJsonLine(line) {
20637
20638
 
20638
20639
  // agent-driver/dist/adapters/claude/normalizer.js
20639
20640
  var API_ERROR_RE = /API Error:.*(?:Connection error|\b[45]\d{2}\b)/i;
20641
+ var CLAUDE_USAGE_COMPONENTS = [
20642
+ "inputTokens",
20643
+ "outputTokens",
20644
+ "cacheReadInputTokens",
20645
+ "cacheCreationInputTokens"
20646
+ ];
20647
+ function safeCount(value) {
20648
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
20649
+ }
20650
+ function safeMoney(value) {
20651
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
20652
+ }
20653
+ function safeAdd(left, right) {
20654
+ const total = left + right;
20655
+ return Number.isSafeInteger(total) ? total : null;
20656
+ }
20657
+ function equalCost(left, right) {
20658
+ return Math.abs(left - right) <= Math.max(0.000000000001, Math.abs(right) * 0.000000001);
20659
+ }
20640
20660
 
20641
20661
  class ClaudeEventNormalizer {
20642
20662
  turnProtocol;
20643
20663
  currentSession = null;
20644
20664
  usageProjector = new SettledUsageProjector;
20665
+ claudeUsageSession = null;
20666
+ claudeModelUsageHighWater = new Map;
20667
+ claudeUsageMode = "unknown";
20645
20668
  constructor(turnProtocol) {
20646
20669
  this.turnProtocol = turnProtocol;
20647
20670
  }
@@ -20755,14 +20778,39 @@ class ClaudeEventNormalizer {
20755
20778
  return this.turnProtocol?.acceptsTurnWork() ?? true;
20756
20779
  }
20757
20780
  buildUsageTelemetry(event, rootRequestId) {
20758
- const u = event?.usage;
20759
- if (!u)
20760
- return null;
20761
20781
  const backendSessionId = event.session_id ?? this.currentSession;
20762
20782
  if (typeof backendSessionId !== "string" || !backendSessionId)
20763
20783
  return null;
20764
20784
  const providerRecordId = rootRequestId ?? (typeof event.request_id === "string" ? event.request_id : "invocation-result");
20765
- return this.usageProjector.project({
20785
+ const cumulative = this.prepareClaudeModelUsage(event, backendSessionId);
20786
+ if (cumulative === null)
20787
+ return null;
20788
+ if (cumulative) {
20789
+ const hasDelta = cumulative.input > 0 || cumulative.output > 0 || cumulative.cacheRead > 0 || cumulative.cacheWrite > 0;
20790
+ if (!hasDelta) {
20791
+ this.commitClaudeModelUsage(backendSessionId, cumulative);
20792
+ return null;
20793
+ }
20794
+ const projected2 = this.usageProjector.project({
20795
+ runtime: "claude",
20796
+ backendSessionId,
20797
+ providerRecordId,
20798
+ source: "claude_result_model_usage",
20799
+ input: cumulative.input,
20800
+ output: cumulative.output,
20801
+ cacheRead: cumulative.cacheRead,
20802
+ cacheWrite: cumulative.cacheWrite,
20803
+ inputIncludesCache: false,
20804
+ outputIncludesReasoning: true
20805
+ });
20806
+ if (projected2)
20807
+ this.commitClaudeModelUsage(backendSessionId, cumulative);
20808
+ return projected2;
20809
+ }
20810
+ const u = event?.usage;
20811
+ if (!u)
20812
+ return null;
20813
+ const projected = this.usageProjector.project({
20766
20814
  runtime: "claude",
20767
20815
  backendSessionId,
20768
20816
  providerRecordId,
@@ -20774,6 +20822,102 @@ class ClaudeEventNormalizer {
20774
20822
  inputIncludesCache: false,
20775
20823
  outputIncludesReasoning: true
20776
20824
  });
20825
+ if (projected)
20826
+ this.claudeUsageMode = "legacy";
20827
+ return projected;
20828
+ }
20829
+ prepareClaudeModelUsage(event, backendSessionId) {
20830
+ if (this.claudeUsageMode === "invalid")
20831
+ return null;
20832
+ if (!Object.hasOwn(event ?? {}, "modelUsage")) {
20833
+ if (this.claudeUsageMode !== "model_usage")
20834
+ return;
20835
+ return event?.usage ? this.lockClaudeUsageMode() : null;
20836
+ }
20837
+ if (this.claudeUsageMode === "legacy")
20838
+ return this.lockClaudeUsageMode();
20839
+ if (this.claudeUsageMode === "unknown")
20840
+ this.claudeUsageMode = "model_usage";
20841
+ this.claudeUsageSession ??= backendSessionId;
20842
+ if (this.claudeUsageSession !== null && this.claudeUsageSession !== backendSessionId) {
20843
+ return this.lockClaudeUsageMode();
20844
+ }
20845
+ if (!event.modelUsage || typeof event.modelUsage !== "object" || Array.isArray(event.modelUsage)) {
20846
+ return null;
20847
+ }
20848
+ const entries = Object.entries(event.modelUsage);
20849
+ if (entries.length === 0)
20850
+ return null;
20851
+ const highWater = new Map;
20852
+ let input = 0;
20853
+ let output = 0;
20854
+ let cacheRead = 0;
20855
+ let cacheWrite = 0;
20856
+ let snapshotCost = 0;
20857
+ for (const [model, rawValue] of entries) {
20858
+ if (!model.trim() || !rawValue || typeof rawValue !== "object" || Array.isArray(rawValue)) {
20859
+ return null;
20860
+ }
20861
+ const raw = rawValue;
20862
+ const current = {
20863
+ inputTokens: safeCount(raw.inputTokens),
20864
+ outputTokens: safeCount(raw.outputTokens),
20865
+ cacheReadInputTokens: safeCount(raw.cacheReadInputTokens),
20866
+ cacheCreationInputTokens: safeCount(raw.cacheCreationInputTokens),
20867
+ costUSD: safeMoney(raw.costUSD)
20868
+ };
20869
+ if (Object.values(current).some((value) => value === null)) {
20870
+ return null;
20871
+ }
20872
+ const snapshot = current;
20873
+ const prior = this.claudeModelUsageHighWater.get(model) ?? {
20874
+ inputTokens: 0,
20875
+ outputTokens: 0,
20876
+ cacheReadInputTokens: 0,
20877
+ cacheCreationInputTokens: 0,
20878
+ costUSD: 0
20879
+ };
20880
+ for (const component of CLAUDE_USAGE_COMPONENTS) {
20881
+ if (snapshot[component] < prior[component])
20882
+ return null;
20883
+ }
20884
+ if (snapshot.costUSD < prior.costUSD)
20885
+ return null;
20886
+ const nextInput = safeAdd(input, snapshot.inputTokens - prior.inputTokens);
20887
+ const nextOutput = safeAdd(output, snapshot.outputTokens - prior.outputTokens);
20888
+ const nextCacheRead = safeAdd(cacheRead, snapshot.cacheReadInputTokens - prior.cacheReadInputTokens);
20889
+ const nextCacheWrite = safeAdd(cacheWrite, snapshot.cacheCreationInputTokens - prior.cacheCreationInputTokens);
20890
+ if (nextInput === null || nextOutput === null || nextCacheRead === null || nextCacheWrite === null) {
20891
+ return null;
20892
+ }
20893
+ input = nextInput;
20894
+ output = nextOutput;
20895
+ cacheRead = nextCacheRead;
20896
+ cacheWrite = nextCacheWrite;
20897
+ snapshotCost += snapshot.costUSD;
20898
+ if (!Number.isFinite(snapshotCost))
20899
+ return null;
20900
+ highWater.set(model, snapshot);
20901
+ }
20902
+ for (const model of this.claudeModelUsageHighWater.keys()) {
20903
+ if (!highWater.has(model))
20904
+ return null;
20905
+ }
20906
+ if (Object.hasOwn(event, "total_cost_usd")) {
20907
+ const totalCost = safeMoney(event.total_cost_usd);
20908
+ if (totalCost === null || !equalCost(snapshotCost, totalCost))
20909
+ return null;
20910
+ }
20911
+ return { highWater, input, output, cacheRead, cacheWrite };
20912
+ }
20913
+ commitClaudeModelUsage(backendSessionId, projection) {
20914
+ this.claudeUsageSession ??= backendSessionId;
20915
+ this.claudeModelUsageHighWater = projection.highWater;
20916
+ this.claudeUsageMode = "model_usage";
20917
+ }
20918
+ lockClaudeUsageMode() {
20919
+ this.claudeUsageMode = "invalid";
20920
+ return null;
20777
20921
  }
20778
20922
  }
20779
20923
 
@@ -22255,7 +22399,8 @@ class CursorAcpLane {
22255
22399
  started = false;
22256
22400
  ready = false;
22257
22401
  sessionId = null;
22258
- activePrompt = null;
22402
+ currentPromptRequestId = null;
22403
+ terminalOwner = null;
22259
22404
  requestedStopReason;
22260
22405
  spawnPromise;
22261
22406
  stopPromise;
@@ -22327,16 +22472,19 @@ class CursorAcpLane {
22327
22472
  async send(input) {
22328
22473
  if (!this.ready || !this.process || this.isClosed())
22329
22474
  return { ok: false, reason: "closed" };
22330
- if (input.mode !== "idle") {
22331
- return { ok: false, reason: "unsupported", error: "Cursor ACP does not support mid-turn steering" };
22475
+ if (input.mode === "busy") {
22476
+ if (this.currentPromptRequestId === null) {
22477
+ return { ok: false, reason: "runtime_busy", error: "Cursor ACP has no active prompt to steer" };
22478
+ }
22479
+ return this.admitPrompt(input.text, "steer");
22332
22480
  }
22333
- if (this.activePrompt) {
22481
+ if (this.currentPromptRequestId !== null) {
22334
22482
  return { ok: false, reason: "runtime_busy", error: "Cursor ACP prompt is still active" };
22335
22483
  }
22336
22484
  return this.admitPrompt(input.text);
22337
22485
  }
22338
22486
  async interrupt(_input) {
22339
- if (!this.ready || !this.sessionId || !this.activePrompt || this.isClosed())
22487
+ if (!this.ready || !this.sessionId || this.currentPromptRequestId === null || this.isClosed())
22340
22488
  return false;
22341
22489
  this.write({ jsonrpc: "2.0", method: "session/cancel", params: { sessionId: this.sessionId } });
22342
22490
  return true;
@@ -22344,7 +22492,8 @@ class CursorAcpLane {
22344
22492
  stop(input = {}) {
22345
22493
  this.requestedStopReason ??= input.reason ?? "requested_stop";
22346
22494
  this.ready = false;
22347
- this.activePrompt = null;
22495
+ this.currentPromptRequestId = null;
22496
+ this.terminalOwner = null;
22348
22497
  this.openToolCalls.clear();
22349
22498
  this.rejectAllPending(new Error("Cursor ACP lane stopped"));
22350
22499
  this.stopPromise ??= this.stopPhysicalOnce(input);
@@ -22450,19 +22599,22 @@ class CursorAcpLane {
22450
22599
  throw new CursorAcpIncompatibleError("Cursor ACP did not confirm the exact configured model");
22451
22600
  }
22452
22601
  }
22453
- admitPrompt(text2) {
22602
+ admitPrompt(text2, delivery = "prompt") {
22454
22603
  if (!this.sessionId)
22455
22604
  return { ok: false, reason: "not_ready", error: "Cursor ACP session is not ready" };
22456
- if (this.activePrompt)
22457
- return { ok: false, reason: "runtime_busy", error: "Cursor ACP prompt is still active" };
22458
22605
  const requestId = ++this.requestSequence;
22459
- const active = {
22606
+ const prompt = {
22460
22607
  requestId,
22461
22608
  receipt: `cursor:acp:${requestId}`
22462
22609
  };
22610
+ const previousPromptRequestId = this.currentPromptRequestId;
22611
+ const previousTerminalOwner = this.terminalOwner;
22612
+ const previousOpenToolCalls = new Set(this.openToolCalls);
22613
+ if (delivery === "prompt")
22614
+ this.terminalOwner = prompt.receipt;
22615
+ this.currentPromptRequestId = requestId;
22463
22616
  this.openToolCalls.clear();
22464
- this.activePrompt = active;
22465
- this.pending.set(requestId, { kind: "prompt", method: "session/prompt", active });
22617
+ this.pending.set(requestId, { kind: "prompt", method: "session/prompt", prompt });
22466
22618
  try {
22467
22619
  this.write({
22468
22620
  jsonrpc: "2.0",
@@ -22475,32 +22627,41 @@ class CursorAcpLane {
22475
22627
  });
22476
22628
  } catch (error51) {
22477
22629
  this.pending.delete(requestId);
22478
- if (this.activePrompt?.requestId === requestId)
22479
- this.activePrompt = null;
22630
+ if (this.currentPromptRequestId === requestId) {
22631
+ this.currentPromptRequestId = previousPromptRequestId;
22632
+ this.terminalOwner = previousTerminalOwner;
22633
+ this.openToolCalls.clear();
22634
+ for (const toolCallId of previousOpenToolCalls)
22635
+ this.openToolCalls.add(toolCallId);
22636
+ }
22480
22637
  throw error51;
22481
22638
  }
22482
- return { ok: true, acceptedAs: "prompt", receipt: active.receipt };
22639
+ return { ok: true, acceptedAs: delivery, receipt: prompt.receipt };
22483
22640
  }
22484
- completePrompt(active, value) {
22485
- if (this.activePrompt?.requestId !== active.requestId)
22641
+ completePrompt(prompt, value) {
22642
+ if (this.currentPromptRequestId !== prompt.requestId)
22486
22643
  return;
22487
22644
  const result = record3(value);
22488
22645
  if (!result || typeof result.stopReason !== "string" || !PROMPT_STOP_REASONS.has(result.stopReason)) {
22489
- this.failPrompt(active, new Error("Cursor ACP prompt response did not contain a supported stopReason"));
22646
+ this.failPrompt(prompt, new Error("Cursor ACP prompt response did not contain a supported stopReason"));
22490
22647
  return;
22491
22648
  }
22492
- this.activePrompt = null;
22649
+ const terminalOwner = this.terminalOwner ?? prompt.receipt;
22650
+ this.currentPromptRequestId = null;
22651
+ this.terminalOwner = null;
22493
22652
  this.openToolCalls.clear();
22494
22653
  this.events.emit("runtime_event", {
22495
22654
  kind: "turn_end",
22496
22655
  sessionId: this.sessionId ?? undefined,
22497
- turnOwner: active.receipt
22656
+ turnOwner: terminalOwner
22498
22657
  });
22499
22658
  }
22500
- failPrompt(active, error51) {
22501
- if (this.activePrompt?.requestId !== active.requestId)
22659
+ failPrompt(prompt, error51) {
22660
+ if (this.currentPromptRequestId !== prompt.requestId)
22502
22661
  return;
22503
- this.activePrompt = null;
22662
+ const terminalOwner = this.terminalOwner ?? prompt.receipt;
22663
+ this.currentPromptRequestId = null;
22664
+ this.terminalOwner = null;
22504
22665
  this.openToolCalls.clear();
22505
22666
  this.events.emit("runtime_event", {
22506
22667
  kind: "error",
@@ -22509,7 +22670,7 @@ class CursorAcpLane {
22509
22670
  this.events.emit("runtime_event", {
22510
22671
  kind: "turn_end",
22511
22672
  sessionId: this.sessionId ?? undefined,
22512
- turnOwner: active.receipt
22673
+ turnOwner: terminalOwner
22513
22674
  });
22514
22675
  }
22515
22676
  call(method, params) {
@@ -22595,7 +22756,8 @@ class CursorAcpLane {
22595
22756
  }
22596
22757
  this.processTerminal = true;
22597
22758
  this.ready = false;
22598
- this.activePrompt = null;
22759
+ this.currentPromptRequestId = null;
22760
+ this.terminalOwner = null;
22599
22761
  this.openToolCalls.clear();
22600
22762
  this.stopPromise ??= this.stopPhysicalOnce({ reason: "runtime_error", forceAfterMs: 0 });
22601
22763
  this.stopPromise.catch(() => {});
@@ -22613,7 +22775,8 @@ class CursorAcpLane {
22613
22775
  return;
22614
22776
  this.processTerminal = true;
22615
22777
  this.ready = false;
22616
- this.activePrompt = null;
22778
+ this.currentPromptRequestId = null;
22779
+ this.terminalOwner = null;
22617
22780
  this.openToolCalls.clear();
22618
22781
  this.rejectAllPending(new Error("Cursor ACP process exited"));
22619
22782
  if (this.suppressExit || !this.processActivated)
@@ -22655,11 +22818,11 @@ class CursorAcpLane {
22655
22818
  this.pending.delete(id);
22656
22819
  if (message2.error !== undefined) {
22657
22820
  const payload = record3(message2.error);
22658
- this.failPrompt(pending.active, new CursorAcpRpcError(pending.method, typeof payload?.code === "number" ? payload.code : undefined, rpcErrorMessage(message2.error)));
22821
+ this.failPrompt(pending.prompt, new CursorAcpRpcError(pending.method, typeof payload?.code === "number" ? payload.code : undefined, rpcErrorMessage(message2.error)));
22659
22822
  } else if (!("result" in message2)) {
22660
- this.failPrompt(pending.active, new Error("Cursor ACP response omitted result"));
22823
+ this.failPrompt(pending.prompt, new Error("Cursor ACP response omitted result"));
22661
22824
  } else {
22662
- this.completePrompt(pending.active, message2.result);
22825
+ this.completePrompt(pending.prompt, message2.result);
22663
22826
  }
22664
22827
  return;
22665
22828
  }
@@ -22700,7 +22863,7 @@ class CursorAcpLane {
22700
22863
  const sameSession = payload?.sessionId === this.sessionId;
22701
22864
  const options = Array.isArray(payload?.options) ? payload.options.map(record3).filter(Boolean) : [];
22702
22865
  const allowOnce = options.find((option) => option.kind === "allow_once" && typeof option.optionId === "string" && option.optionId.trim().length > 0);
22703
- if (!this.ready || !this.activePrompt || !sameSession || !allowOnce) {
22866
+ if (!this.ready || this.currentPromptRequestId === null || !sameSession || !allowOnce) {
22704
22867
  this.write({ jsonrpc: "2.0", id, result: { outcome: { outcome: "cancelled" } } });
22705
22868
  this.diagnostic("error", "Cursor ACP permission request was not allowed for the active prompt");
22706
22869
  return;
@@ -22724,7 +22887,7 @@ class CursorAcpLane {
22724
22887
  this.diagnostic("warning", "Cursor ACP emitted an update for a different session");
22725
22888
  return;
22726
22889
  }
22727
- if (!this.ready || !this.activePrompt) {
22890
+ if (!this.ready || this.currentPromptRequestId === null) {
22728
22891
  this.diagnostic("warning", "Cursor ACP emitted a session update without an active prompt");
22729
22892
  return;
22730
22893
  }
@@ -24652,7 +24815,7 @@ var capabilities = {
24652
24815
  commandOverride: true,
24653
24816
  resume: "by_id",
24654
24817
  sessionLifetime: "persistent",
24655
- midTurnDelivery: "next_turn_queue",
24818
+ midTurnDelivery: "steer",
24656
24819
  interrupt: true
24657
24820
  },
24658
24821
  opencode: {
@@ -32616,12 +32779,15 @@ class DaemonSelfSleepScheduler {
32616
32779
  import { chmod, mkdir, open, readFile as readFile2, rename, rm } from "node:fs/promises";
32617
32780
  import { dirname as dirname6, join as join14 } from "node:path";
32618
32781
  import { randomUUID as randomUUID7 } from "node:crypto";
32782
+ var TOKEN_USAGE_WINDOW_DAYS = 30;
32783
+ var TOKEN_USAGE_RECOVERY_DAYS = 2;
32784
+ var TOKEN_USAGE_RETENTION_DAYS = TOKEN_USAGE_WINDOW_DAYS + TOKEN_USAGE_RECOVERY_DAYS;
32619
32785
  function oldestRetainedDay(at, timeZone) {
32620
32786
  const today = dayKeyInTimeZone(at, timeZone);
32621
- return calendarDayKeyDaysAgo(today, 8);
32787
+ return calendarDayKeyDaysAgo(today, TOKEN_USAGE_RETENTION_DAYS - 1);
32622
32788
  }
32623
32789
  function oldestVisibleDay(today) {
32624
- return calendarDayKeyDaysAgo(today, 6);
32790
+ return calendarDayKeyDaysAgo(today, TOKEN_USAGE_WINDOW_DAYS - 1);
32625
32791
  }
32626
32792
  function isMetric(value) {
32627
32793
  return value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
package/dist/index.js CHANGED
@@ -1200,11 +1200,33 @@ function tryParseJsonLine(line) {
1200
1200
 
1201
1201
  // agent-driver/dist/adapters/claude/normalizer.js
1202
1202
  var API_ERROR_RE = /API Error:.*(?:Connection error|\b[45]\d{2}\b)/i;
1203
+ var CLAUDE_USAGE_COMPONENTS = [
1204
+ "inputTokens",
1205
+ "outputTokens",
1206
+ "cacheReadInputTokens",
1207
+ "cacheCreationInputTokens"
1208
+ ];
1209
+ function safeCount(value) {
1210
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
1211
+ }
1212
+ function safeMoney(value) {
1213
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
1214
+ }
1215
+ function safeAdd(left, right) {
1216
+ const total = left + right;
1217
+ return Number.isSafeInteger(total) ? total : null;
1218
+ }
1219
+ function equalCost(left, right) {
1220
+ return Math.abs(left - right) <= Math.max(0.000000000001, Math.abs(right) * 0.000000001);
1221
+ }
1203
1222
 
1204
1223
  class ClaudeEventNormalizer {
1205
1224
  turnProtocol;
1206
1225
  currentSession = null;
1207
1226
  usageProjector = new SettledUsageProjector;
1227
+ claudeUsageSession = null;
1228
+ claudeModelUsageHighWater = new Map;
1229
+ claudeUsageMode = "unknown";
1208
1230
  constructor(turnProtocol) {
1209
1231
  this.turnProtocol = turnProtocol;
1210
1232
  }
@@ -1318,14 +1340,39 @@ class ClaudeEventNormalizer {
1318
1340
  return this.turnProtocol?.acceptsTurnWork() ?? true;
1319
1341
  }
1320
1342
  buildUsageTelemetry(event, rootRequestId) {
1321
- const u = event?.usage;
1322
- if (!u)
1323
- return null;
1324
1343
  const backendSessionId = event.session_id ?? this.currentSession;
1325
1344
  if (typeof backendSessionId !== "string" || !backendSessionId)
1326
1345
  return null;
1327
1346
  const providerRecordId = rootRequestId ?? (typeof event.request_id === "string" ? event.request_id : "invocation-result");
1328
- return this.usageProjector.project({
1347
+ const cumulative = this.prepareClaudeModelUsage(event, backendSessionId);
1348
+ if (cumulative === null)
1349
+ return null;
1350
+ if (cumulative) {
1351
+ const hasDelta = cumulative.input > 0 || cumulative.output > 0 || cumulative.cacheRead > 0 || cumulative.cacheWrite > 0;
1352
+ if (!hasDelta) {
1353
+ this.commitClaudeModelUsage(backendSessionId, cumulative);
1354
+ return null;
1355
+ }
1356
+ const projected2 = this.usageProjector.project({
1357
+ runtime: "claude",
1358
+ backendSessionId,
1359
+ providerRecordId,
1360
+ source: "claude_result_model_usage",
1361
+ input: cumulative.input,
1362
+ output: cumulative.output,
1363
+ cacheRead: cumulative.cacheRead,
1364
+ cacheWrite: cumulative.cacheWrite,
1365
+ inputIncludesCache: false,
1366
+ outputIncludesReasoning: true
1367
+ });
1368
+ if (projected2)
1369
+ this.commitClaudeModelUsage(backendSessionId, cumulative);
1370
+ return projected2;
1371
+ }
1372
+ const u = event?.usage;
1373
+ if (!u)
1374
+ return null;
1375
+ const projected = this.usageProjector.project({
1329
1376
  runtime: "claude",
1330
1377
  backendSessionId,
1331
1378
  providerRecordId,
@@ -1337,6 +1384,102 @@ class ClaudeEventNormalizer {
1337
1384
  inputIncludesCache: false,
1338
1385
  outputIncludesReasoning: true
1339
1386
  });
1387
+ if (projected)
1388
+ this.claudeUsageMode = "legacy";
1389
+ return projected;
1390
+ }
1391
+ prepareClaudeModelUsage(event, backendSessionId) {
1392
+ if (this.claudeUsageMode === "invalid")
1393
+ return null;
1394
+ if (!Object.hasOwn(event ?? {}, "modelUsage")) {
1395
+ if (this.claudeUsageMode !== "model_usage")
1396
+ return;
1397
+ return event?.usage ? this.lockClaudeUsageMode() : null;
1398
+ }
1399
+ if (this.claudeUsageMode === "legacy")
1400
+ return this.lockClaudeUsageMode();
1401
+ if (this.claudeUsageMode === "unknown")
1402
+ this.claudeUsageMode = "model_usage";
1403
+ this.claudeUsageSession ??= backendSessionId;
1404
+ if (this.claudeUsageSession !== null && this.claudeUsageSession !== backendSessionId) {
1405
+ return this.lockClaudeUsageMode();
1406
+ }
1407
+ if (!event.modelUsage || typeof event.modelUsage !== "object" || Array.isArray(event.modelUsage)) {
1408
+ return null;
1409
+ }
1410
+ const entries = Object.entries(event.modelUsage);
1411
+ if (entries.length === 0)
1412
+ return null;
1413
+ const highWater = new Map;
1414
+ let input = 0;
1415
+ let output = 0;
1416
+ let cacheRead = 0;
1417
+ let cacheWrite = 0;
1418
+ let snapshotCost = 0;
1419
+ for (const [model, rawValue] of entries) {
1420
+ if (!model.trim() || !rawValue || typeof rawValue !== "object" || Array.isArray(rawValue)) {
1421
+ return null;
1422
+ }
1423
+ const raw = rawValue;
1424
+ const current = {
1425
+ inputTokens: safeCount(raw.inputTokens),
1426
+ outputTokens: safeCount(raw.outputTokens),
1427
+ cacheReadInputTokens: safeCount(raw.cacheReadInputTokens),
1428
+ cacheCreationInputTokens: safeCount(raw.cacheCreationInputTokens),
1429
+ costUSD: safeMoney(raw.costUSD)
1430
+ };
1431
+ if (Object.values(current).some((value) => value === null)) {
1432
+ return null;
1433
+ }
1434
+ const snapshot = current;
1435
+ const prior = this.claudeModelUsageHighWater.get(model) ?? {
1436
+ inputTokens: 0,
1437
+ outputTokens: 0,
1438
+ cacheReadInputTokens: 0,
1439
+ cacheCreationInputTokens: 0,
1440
+ costUSD: 0
1441
+ };
1442
+ for (const component of CLAUDE_USAGE_COMPONENTS) {
1443
+ if (snapshot[component] < prior[component])
1444
+ return null;
1445
+ }
1446
+ if (snapshot.costUSD < prior.costUSD)
1447
+ return null;
1448
+ const nextInput = safeAdd(input, snapshot.inputTokens - prior.inputTokens);
1449
+ const nextOutput = safeAdd(output, snapshot.outputTokens - prior.outputTokens);
1450
+ const nextCacheRead = safeAdd(cacheRead, snapshot.cacheReadInputTokens - prior.cacheReadInputTokens);
1451
+ const nextCacheWrite = safeAdd(cacheWrite, snapshot.cacheCreationInputTokens - prior.cacheCreationInputTokens);
1452
+ if (nextInput === null || nextOutput === null || nextCacheRead === null || nextCacheWrite === null) {
1453
+ return null;
1454
+ }
1455
+ input = nextInput;
1456
+ output = nextOutput;
1457
+ cacheRead = nextCacheRead;
1458
+ cacheWrite = nextCacheWrite;
1459
+ snapshotCost += snapshot.costUSD;
1460
+ if (!Number.isFinite(snapshotCost))
1461
+ return null;
1462
+ highWater.set(model, snapshot);
1463
+ }
1464
+ for (const model of this.claudeModelUsageHighWater.keys()) {
1465
+ if (!highWater.has(model))
1466
+ return null;
1467
+ }
1468
+ if (Object.hasOwn(event, "total_cost_usd")) {
1469
+ const totalCost = safeMoney(event.total_cost_usd);
1470
+ if (totalCost === null || !equalCost(snapshotCost, totalCost))
1471
+ return null;
1472
+ }
1473
+ return { highWater, input, output, cacheRead, cacheWrite };
1474
+ }
1475
+ commitClaudeModelUsage(backendSessionId, projection) {
1476
+ this.claudeUsageSession ??= backendSessionId;
1477
+ this.claudeModelUsageHighWater = projection.highWater;
1478
+ this.claudeUsageMode = "model_usage";
1479
+ }
1480
+ lockClaudeUsageMode() {
1481
+ this.claudeUsageMode = "invalid";
1482
+ return null;
1340
1483
  }
1341
1484
  }
1342
1485
 
@@ -2818,7 +2961,8 @@ class CursorAcpLane {
2818
2961
  started = false;
2819
2962
  ready = false;
2820
2963
  sessionId = null;
2821
- activePrompt = null;
2964
+ currentPromptRequestId = null;
2965
+ terminalOwner = null;
2822
2966
  requestedStopReason;
2823
2967
  spawnPromise;
2824
2968
  stopPromise;
@@ -2890,16 +3034,19 @@ class CursorAcpLane {
2890
3034
  async send(input) {
2891
3035
  if (!this.ready || !this.process || this.isClosed())
2892
3036
  return { ok: false, reason: "closed" };
2893
- if (input.mode !== "idle") {
2894
- return { ok: false, reason: "unsupported", error: "Cursor ACP does not support mid-turn steering" };
3037
+ if (input.mode === "busy") {
3038
+ if (this.currentPromptRequestId === null) {
3039
+ return { ok: false, reason: "runtime_busy", error: "Cursor ACP has no active prompt to steer" };
3040
+ }
3041
+ return this.admitPrompt(input.text, "steer");
2895
3042
  }
2896
- if (this.activePrompt) {
3043
+ if (this.currentPromptRequestId !== null) {
2897
3044
  return { ok: false, reason: "runtime_busy", error: "Cursor ACP prompt is still active" };
2898
3045
  }
2899
3046
  return this.admitPrompt(input.text);
2900
3047
  }
2901
3048
  async interrupt(_input) {
2902
- if (!this.ready || !this.sessionId || !this.activePrompt || this.isClosed())
3049
+ if (!this.ready || !this.sessionId || this.currentPromptRequestId === null || this.isClosed())
2903
3050
  return false;
2904
3051
  this.write({ jsonrpc: "2.0", method: "session/cancel", params: { sessionId: this.sessionId } });
2905
3052
  return true;
@@ -2907,7 +3054,8 @@ class CursorAcpLane {
2907
3054
  stop(input = {}) {
2908
3055
  this.requestedStopReason ??= input.reason ?? "requested_stop";
2909
3056
  this.ready = false;
2910
- this.activePrompt = null;
3057
+ this.currentPromptRequestId = null;
3058
+ this.terminalOwner = null;
2911
3059
  this.openToolCalls.clear();
2912
3060
  this.rejectAllPending(new Error("Cursor ACP lane stopped"));
2913
3061
  this.stopPromise ??= this.stopPhysicalOnce(input);
@@ -3013,19 +3161,22 @@ class CursorAcpLane {
3013
3161
  throw new CursorAcpIncompatibleError("Cursor ACP did not confirm the exact configured model");
3014
3162
  }
3015
3163
  }
3016
- admitPrompt(text) {
3164
+ admitPrompt(text, delivery = "prompt") {
3017
3165
  if (!this.sessionId)
3018
3166
  return { ok: false, reason: "not_ready", error: "Cursor ACP session is not ready" };
3019
- if (this.activePrompt)
3020
- return { ok: false, reason: "runtime_busy", error: "Cursor ACP prompt is still active" };
3021
3167
  const requestId = ++this.requestSequence;
3022
- const active = {
3168
+ const prompt = {
3023
3169
  requestId,
3024
3170
  receipt: `cursor:acp:${requestId}`
3025
3171
  };
3172
+ const previousPromptRequestId = this.currentPromptRequestId;
3173
+ const previousTerminalOwner = this.terminalOwner;
3174
+ const previousOpenToolCalls = new Set(this.openToolCalls);
3175
+ if (delivery === "prompt")
3176
+ this.terminalOwner = prompt.receipt;
3177
+ this.currentPromptRequestId = requestId;
3026
3178
  this.openToolCalls.clear();
3027
- this.activePrompt = active;
3028
- this.pending.set(requestId, { kind: "prompt", method: "session/prompt", active });
3179
+ this.pending.set(requestId, { kind: "prompt", method: "session/prompt", prompt });
3029
3180
  try {
3030
3181
  this.write({
3031
3182
  jsonrpc: "2.0",
@@ -3038,32 +3189,41 @@ class CursorAcpLane {
3038
3189
  });
3039
3190
  } catch (error) {
3040
3191
  this.pending.delete(requestId);
3041
- if (this.activePrompt?.requestId === requestId)
3042
- this.activePrompt = null;
3192
+ if (this.currentPromptRequestId === requestId) {
3193
+ this.currentPromptRequestId = previousPromptRequestId;
3194
+ this.terminalOwner = previousTerminalOwner;
3195
+ this.openToolCalls.clear();
3196
+ for (const toolCallId of previousOpenToolCalls)
3197
+ this.openToolCalls.add(toolCallId);
3198
+ }
3043
3199
  throw error;
3044
3200
  }
3045
- return { ok: true, acceptedAs: "prompt", receipt: active.receipt };
3201
+ return { ok: true, acceptedAs: delivery, receipt: prompt.receipt };
3046
3202
  }
3047
- completePrompt(active, value) {
3048
- if (this.activePrompt?.requestId !== active.requestId)
3203
+ completePrompt(prompt, value) {
3204
+ if (this.currentPromptRequestId !== prompt.requestId)
3049
3205
  return;
3050
3206
  const result = record2(value);
3051
3207
  if (!result || typeof result.stopReason !== "string" || !PROMPT_STOP_REASONS.has(result.stopReason)) {
3052
- this.failPrompt(active, new Error("Cursor ACP prompt response did not contain a supported stopReason"));
3208
+ this.failPrompt(prompt, new Error("Cursor ACP prompt response did not contain a supported stopReason"));
3053
3209
  return;
3054
3210
  }
3055
- this.activePrompt = null;
3211
+ const terminalOwner = this.terminalOwner ?? prompt.receipt;
3212
+ this.currentPromptRequestId = null;
3213
+ this.terminalOwner = null;
3056
3214
  this.openToolCalls.clear();
3057
3215
  this.events.emit("runtime_event", {
3058
3216
  kind: "turn_end",
3059
3217
  sessionId: this.sessionId ?? undefined,
3060
- turnOwner: active.receipt
3218
+ turnOwner: terminalOwner
3061
3219
  });
3062
3220
  }
3063
- failPrompt(active, error) {
3064
- if (this.activePrompt?.requestId !== active.requestId)
3221
+ failPrompt(prompt, error) {
3222
+ if (this.currentPromptRequestId !== prompt.requestId)
3065
3223
  return;
3066
- this.activePrompt = null;
3224
+ const terminalOwner = this.terminalOwner ?? prompt.receipt;
3225
+ this.currentPromptRequestId = null;
3226
+ this.terminalOwner = null;
3067
3227
  this.openToolCalls.clear();
3068
3228
  this.events.emit("runtime_event", {
3069
3229
  kind: "error",
@@ -3072,7 +3232,7 @@ class CursorAcpLane {
3072
3232
  this.events.emit("runtime_event", {
3073
3233
  kind: "turn_end",
3074
3234
  sessionId: this.sessionId ?? undefined,
3075
- turnOwner: active.receipt
3235
+ turnOwner: terminalOwner
3076
3236
  });
3077
3237
  }
3078
3238
  call(method, params) {
@@ -3158,7 +3318,8 @@ class CursorAcpLane {
3158
3318
  }
3159
3319
  this.processTerminal = true;
3160
3320
  this.ready = false;
3161
- this.activePrompt = null;
3321
+ this.currentPromptRequestId = null;
3322
+ this.terminalOwner = null;
3162
3323
  this.openToolCalls.clear();
3163
3324
  this.stopPromise ??= this.stopPhysicalOnce({ reason: "runtime_error", forceAfterMs: 0 });
3164
3325
  this.stopPromise.catch(() => {});
@@ -3176,7 +3337,8 @@ class CursorAcpLane {
3176
3337
  return;
3177
3338
  this.processTerminal = true;
3178
3339
  this.ready = false;
3179
- this.activePrompt = null;
3340
+ this.currentPromptRequestId = null;
3341
+ this.terminalOwner = null;
3180
3342
  this.openToolCalls.clear();
3181
3343
  this.rejectAllPending(new Error("Cursor ACP process exited"));
3182
3344
  if (this.suppressExit || !this.processActivated)
@@ -3218,11 +3380,11 @@ class CursorAcpLane {
3218
3380
  this.pending.delete(id);
3219
3381
  if (message.error !== undefined) {
3220
3382
  const payload = record2(message.error);
3221
- this.failPrompt(pending.active, new CursorAcpRpcError(pending.method, typeof payload?.code === "number" ? payload.code : undefined, rpcErrorMessage(message.error)));
3383
+ this.failPrompt(pending.prompt, new CursorAcpRpcError(pending.method, typeof payload?.code === "number" ? payload.code : undefined, rpcErrorMessage(message.error)));
3222
3384
  } else if (!("result" in message)) {
3223
- this.failPrompt(pending.active, new Error("Cursor ACP response omitted result"));
3385
+ this.failPrompt(pending.prompt, new Error("Cursor ACP response omitted result"));
3224
3386
  } else {
3225
- this.completePrompt(pending.active, message.result);
3387
+ this.completePrompt(pending.prompt, message.result);
3226
3388
  }
3227
3389
  return;
3228
3390
  }
@@ -3263,7 +3425,7 @@ class CursorAcpLane {
3263
3425
  const sameSession = payload?.sessionId === this.sessionId;
3264
3426
  const options = Array.isArray(payload?.options) ? payload.options.map(record2).filter(Boolean) : [];
3265
3427
  const allowOnce = options.find((option) => option.kind === "allow_once" && typeof option.optionId === "string" && option.optionId.trim().length > 0);
3266
- if (!this.ready || !this.activePrompt || !sameSession || !allowOnce) {
3428
+ if (!this.ready || this.currentPromptRequestId === null || !sameSession || !allowOnce) {
3267
3429
  this.write({ jsonrpc: "2.0", id, result: { outcome: { outcome: "cancelled" } } });
3268
3430
  this.diagnostic("error", "Cursor ACP permission request was not allowed for the active prompt");
3269
3431
  return;
@@ -3287,7 +3449,7 @@ class CursorAcpLane {
3287
3449
  this.diagnostic("warning", "Cursor ACP emitted an update for a different session");
3288
3450
  return;
3289
3451
  }
3290
- if (!this.ready || !this.activePrompt) {
3452
+ if (!this.ready || this.currentPromptRequestId === null) {
3291
3453
  this.diagnostic("warning", "Cursor ACP emitted a session update without an active prompt");
3292
3454
  return;
3293
3455
  }
@@ -5215,7 +5377,7 @@ var capabilities = {
5215
5377
  commandOverride: true,
5216
5378
  resume: "by_id",
5217
5379
  sessionLifetime: "persistent",
5218
- midTurnDelivery: "next_turn_queue",
5380
+ midTurnDelivery: "steer",
5219
5381
  interrupt: true
5220
5382
  },
5221
5383
  opencode: {
@@ -28394,6 +28556,7 @@ var ALLOWED_ICON_MIME_TYPES = [
28394
28556
  ];
28395
28557
  var MAX_ICON_SOURCE_FILE_SIZE_BYTES = 15 * 1024 * 1024;
28396
28558
  // ../shared/src/provider-telemetry.ts
28559
+ var DAILY_TOKEN_USAGE_WINDOW_DAYS = 30;
28397
28560
  var safeToken = exports_external.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
28398
28561
  var boundedText = exports_external.string().min(1).refine((value) => new TextEncoder().encode(value).length <= 64, { message: "must be at most 64 UTF-8 bytes" });
28399
28562
  var DailyUsageMetricSchema = safeToken.nullable();
@@ -29125,7 +29288,7 @@ var AgentActivityMessageSchema = exports_external.object({
29125
29288
  state: exports_external.enum(["idle", "starting", "running", "stopping"]),
29126
29289
  usageTimeZone: exports_external.string().min(1).max(128).optional(),
29127
29290
  usageDay: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
29128
- dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(7).optional(),
29291
+ dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(DAILY_TOKEN_USAGE_WINDOW_DAYS).optional(),
29129
29292
  quota: ProviderQuotaSnapshotSchema.optional()
29130
29293
  });
29131
29294
  var AgentTypingMessageSchema = exports_external.object({
@@ -32914,12 +33077,15 @@ class DaemonSelfSleepScheduler {
32914
33077
  import { chmod, mkdir, open, readFile as readFile2, rename, rm } from "node:fs/promises";
32915
33078
  import { dirname as dirname5, join as join14 } from "node:path";
32916
33079
  import { randomUUID as randomUUID7 } from "node:crypto";
33080
+ var TOKEN_USAGE_WINDOW_DAYS = 30;
33081
+ var TOKEN_USAGE_RECOVERY_DAYS = 2;
33082
+ var TOKEN_USAGE_RETENTION_DAYS = TOKEN_USAGE_WINDOW_DAYS + TOKEN_USAGE_RECOVERY_DAYS;
32917
33083
  function oldestRetainedDay(at, timeZone) {
32918
33084
  const today = dayKeyInTimeZone(at, timeZone);
32919
- return calendarDayKeyDaysAgo(today, 8);
33085
+ return calendarDayKeyDaysAgo(today, TOKEN_USAGE_RETENTION_DAYS - 1);
32920
33086
  }
32921
33087
  function oldestVisibleDay(today) {
32922
- return calendarDayKeyDaysAgo(today, 6);
33088
+ return calendarDayKeyDaysAgo(today, TOKEN_USAGE_WINDOW_DAYS - 1);
32923
33089
  }
32924
33090
  function isMetric(value) {
32925
33091
  return value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/daemon",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Alook agent daemon — host-side runtime backend, process manager, credential proxy, and control plane.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",