@use-aistack/cli 0.12.2 → 0.13.0

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/dist/index.js CHANGED
@@ -348,7 +348,7 @@ function apiEquivalentCost(modelKey, t, atMs) {
348
348
  }
349
349
 
350
350
  // src/version.ts
351
- var CLI_VERSION = true ? "0.12.2" : "0.0.0-dev";
351
+ var CLI_VERSION = true ? "0.13.0" : "0.0.0-dev";
352
352
 
353
353
  // src/api.ts
354
354
  var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
@@ -976,11 +976,11 @@ function readText(path7) {
976
976
  return null;
977
977
  }
978
978
  }
979
- function readParsed(path7, parse) {
979
+ function readParsed(path7, parse2) {
980
980
  const raw = readText(path7);
981
981
  if (raw === null) return null;
982
982
  try {
983
- return parse(raw);
983
+ return parse2(raw);
984
984
  } catch {
985
985
  return null;
986
986
  }
@@ -2408,8 +2408,9 @@ async function hasRecentFile(roots, matches, sinceMs, opts = {}) {
2408
2408
  }
2409
2409
 
2410
2410
  // ../workflow-rules/src/daily.ts
2411
- var WORKFLOW_AGGREGATES_V2 = "workflow-aggregates/v2";
2411
+ var WORKFLOW_AGGREGATES_V3 = "workflow-aggregates/v3";
2412
2412
  var LOG_BUCKETS_V1 = "log-buckets/v1";
2413
+ var LOG_BUCKETS_V2 = "log-buckets/v2";
2413
2414
  var EMPTY_PHASE_TOTALS = Object.freeze({
2414
2415
  scout: 0,
2415
2416
  build: 0,
@@ -2463,6 +2464,10 @@ function medianBucket(buckets) {
2463
2464
  }
2464
2465
  return sorted[sorted.length - 1]?.bucket;
2465
2466
  }
2467
+ function logBucketV2(value) {
2468
+ if (!(value >= 1)) return 0;
2469
+ return Math.floor(2 * Math.log2(value)) + 1;
2470
+ }
2466
2471
  function median(values) {
2467
2472
  if (values.length === 0) return void 0;
2468
2473
  const sorted = [...values].sort((a, b) => a - b);
@@ -2505,6 +2510,52 @@ function foldModels(rows) {
2505
2510
  (row) => ({ ...row })
2506
2511
  ).sort((a, b) => b.tokens - a.tokens || a.model.localeCompare(b.model));
2507
2512
  }
2513
+ function foldCountBuckets(rows, field) {
2514
+ return sumBy(
2515
+ rows,
2516
+ (row) => String(row.bucket),
2517
+ (into, from) => {
2518
+ into[field] += from[field];
2519
+ },
2520
+ (row) => ({ ...row })
2521
+ ).sort((a, b) => a.bucket - b.bucket);
2522
+ }
2523
+ function foldContextDays(days) {
2524
+ const versions = [...new Set(days.map((d) => d.bucketRuleVersion))].sort().join(" \xB7 ");
2525
+ const windows = days.map((d) => d.window).filter((w) => w !== void 0);
2526
+ const window = windows[windows.length - 1];
2527
+ return {
2528
+ bucketRuleVersion: versions,
2529
+ calls: {
2530
+ main: foldCountBuckets(
2531
+ days.flatMap((d) => d.calls.main),
2532
+ "calls"
2533
+ ),
2534
+ subagents: foldCountBuckets(
2535
+ days.flatMap((d) => d.calls.subagents),
2536
+ "calls"
2537
+ )
2538
+ },
2539
+ firstCalls: {
2540
+ main: foldCountBuckets(
2541
+ days.flatMap((d) => d.firstCalls.main),
2542
+ "sessions"
2543
+ )
2544
+ },
2545
+ firstCallHarnessTokens: days.reduce(
2546
+ (sum, d) => sum + d.firstCallHarnessTokens,
2547
+ 0
2548
+ ),
2549
+ firstCallInstructionsTokens: days.reduce(
2550
+ (sum, d) => sum + d.firstCallInstructionsTokens,
2551
+ 0
2552
+ ),
2553
+ firstCallCount: days.reduce((sum, d) => sum + d.firstCallCount, 0),
2554
+ maxContext: Math.max(0, ...days.map((d) => d.maxContext)),
2555
+ compactions: days.reduce((sum, d) => sum + d.compactions, 0),
2556
+ ...window === void 0 ? {} : { window }
2557
+ };
2558
+ }
2508
2559
  function foldLengths(rows) {
2509
2560
  return sumBy(
2510
2561
  rows,
@@ -2641,6 +2692,8 @@ function foldHarnessDays(days) {
2641
2692
  0
2642
2693
  );
2643
2694
  }
2695
+ const contexts = days.flatMap((day) => day.context ? [day.context] : []);
2696
+ if (contexts.length > 0) out.context = foldContextDays(contexts);
2644
2697
  return out;
2645
2698
  }
2646
2699
  var MAX_CHANGED_LINES_PER_COMMIT = 4096;
@@ -2690,8 +2743,9 @@ function foldGitDays(days) {
2690
2743
  }
2691
2744
  function foldWorkflowDays(days, options) {
2692
2745
  if (days.length === 0) return void 0;
2746
+ const dated = [...days].sort((a, b) => a.date.localeCompare(b.date));
2693
2747
  const byHarness = /* @__PURE__ */ new Map();
2694
- for (const day of days) {
2748
+ for (const day of dated) {
2695
2749
  for (const harness of day.harnesses) {
2696
2750
  const held = byHarness.get(harness.harness) ?? [];
2697
2751
  held.push(harness);
@@ -3863,7 +3917,7 @@ function buildSyncBody(built, syncConfig, autoSync, trigger = "manual", measured
3863
3917
  }
3864
3918
 
3865
3919
  // src/workflow/reducer.ts
3866
- var WORKFLOW_AGGREGATE_VERSION = WORKFLOW_AGGREGATES_V2;
3920
+ var WORKFLOW_AGGREGATE_VERSION = WORKFLOW_AGGREGATES_V3;
3867
3921
  function createWorkflowLocalSources() {
3868
3922
  return { projectWorkspaces: /* @__PURE__ */ new Set(), activeProjectDays: /* @__PURE__ */ new Map() };
3869
3923
  }
@@ -3879,6 +3933,11 @@ var bump2 = (map, key2, amount = 1) => {
3879
3933
  map.set(key2, (map.get(key2) ?? 0) + amount);
3880
3934
  };
3881
3935
  var utcDateOf2 = (ms) => new Date(ms).toISOString().slice(0, 10);
3936
+ function asBuckets(map, field) {
3937
+ return [...map].map(
3938
+ ([bucket, count]) => ({ bucket, [field]: count })
3939
+ ).sort((a, b) => a.bucket - b.bucket);
3940
+ }
3882
3941
  var PHASE_RANK = {
3883
3942
  verify: 4,
3884
3943
  handoff: 3,
@@ -3976,13 +4035,25 @@ function dayState() {
3976
4035
  questions: { asked: 0, turns: 0 },
3977
4036
  hasQuestions: false,
3978
4037
  webSearches: 0,
3979
- hasWebSearches: false
4038
+ hasWebSearches: false,
4039
+ context: {
4040
+ calls: { main: /* @__PURE__ */ new Map(), subagents: /* @__PURE__ */ new Map() },
4041
+ firstCalls: /* @__PURE__ */ new Map(),
4042
+ firstCallHarnessTokens: 0,
4043
+ firstCallInstructionsTokens: 0,
4044
+ firstCallCount: 0,
4045
+ maxContext: 0,
4046
+ compactions: 0,
4047
+ window: void 0
4048
+ },
4049
+ hasContext: false
3980
4050
  };
3981
4051
  }
3982
4052
  function createHarnessWorkflowReducer(harness, localSources = createWorkflowLocalSources()) {
3983
4053
  const sessions = /* @__PURE__ */ new Map();
3984
4054
  const eventCells = /* @__PURE__ */ new Map();
3985
4055
  const webSearchesByDate = /* @__PURE__ */ new Map();
4056
+ const compactionsByDate = /* @__PURE__ */ new Map();
3986
4057
  const eventDates = /* @__PURE__ */ new Set();
3987
4058
  let finished;
3988
4059
  const getSession = (key2) => {
@@ -4023,22 +4094,39 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
4023
4094
  } else if (observation.type === "response") {
4024
4095
  const responseId = observation.responseId ?? `anonymous:${state.nextAnonymousResponse++}`;
4025
4096
  const duration = finiteNonnegative(observation.durationSec);
4097
+ const contextTokens = finiteNonnegative(observation.contextTokens);
4098
+ const contextWindow = finiteNonnegative(observation.contextWindow);
4026
4099
  const response = {
4100
+ tsMs: observation.tsMs,
4027
4101
  ...observation.model ? { model: observation.model } : {},
4028
4102
  ...observation.thinkingTokens !== void 0 ? { thinkingTokens: finiteNonnegative(observation.thinkingTokens) } : {},
4029
4103
  ...observation.responseTokens !== void 0 ? { responseTokens: finiteNonnegative(observation.responseTokens) } : {},
4030
4104
  ...observation.routingTokens !== void 0 ? { routingTokens: finiteNonnegative(observation.routingTokens) } : {},
4031
4105
  ...observation.effort ? { effort: observation.effort } : {},
4032
- ...duration > 0 ? { durationSec: duration } : {}
4106
+ ...duration > 0 ? { durationSec: duration } : {},
4107
+ ...observation.contextTokens !== void 0 ? { contextTokens } : {},
4108
+ ...contextWindow > 0 ? { contextWindow } : {},
4109
+ ...observation.firstCall ? {
4110
+ firstCall: {
4111
+ harnessTokens: finiteNonnegative(
4112
+ observation.firstCall.harnessTokens
4113
+ ),
4114
+ instructionsTokens: finiteNonnegative(
4115
+ observation.firstCall.instructionsTokens
4116
+ )
4117
+ }
4118
+ } : {}
4033
4119
  };
4034
4120
  const existing = state.responses.get(responseId);
4035
4121
  const magnitude = (value) => value.routingTokens ?? (value.thinkingTokens ?? 0) + (value.responseTokens ?? 0);
4036
4122
  if (!existing || magnitude(response) > magnitude(existing)) {
4037
4123
  state.responses.set(responseId, response);
4038
4124
  }
4039
- } else {
4125
+ } else if (observation.type === "turn") {
4040
4126
  const turnId = observation.turnId ?? `anonymous:${state.nextAnonymousTurn++}`;
4041
4127
  state.turns.set(turnId, observation.questionBack);
4128
+ } else {
4129
+ bump2(compactionsByDate, date);
4042
4130
  }
4043
4131
  },
4044
4132
  finish() {
@@ -4144,6 +4232,33 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
4144
4232
  Boolean
4145
4233
  ).length;
4146
4234
  }
4235
+ for (const response of responses) {
4236
+ if (response.contextTokens === void 0) continue;
4237
+ day.hasContext = true;
4238
+ const context = day.context;
4239
+ bump2(context.calls[routing], logBucketV2(response.contextTokens));
4240
+ context.maxContext = Math.max(
4241
+ context.maxContext,
4242
+ response.contextTokens
4243
+ );
4244
+ if (response.contextWindow !== void 0 && (context.window === void 0 || response.tsMs >= context.window.tsMs)) {
4245
+ context.window = {
4246
+ tsMs: response.tsMs,
4247
+ window: response.contextWindow
4248
+ };
4249
+ }
4250
+ if (response.firstCall && routing === "main") {
4251
+ bump2(context.firstCalls, logBucketV2(response.contextTokens));
4252
+ context.firstCallHarnessTokens += response.firstCall.harnessTokens;
4253
+ context.firstCallInstructionsTokens += response.firstCall.instructionsTokens;
4254
+ context.firstCallCount++;
4255
+ }
4256
+ }
4257
+ }
4258
+ for (const [date, compactions] of compactionsByDate) {
4259
+ const day = dayOf(date);
4260
+ day.hasContext = true;
4261
+ day.context.compactions += compactions;
4147
4262
  }
4148
4263
  for (const date of eventDates) {
4149
4264
  const day = dayOf(date);
@@ -4276,12 +4391,34 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
4276
4391
  }
4277
4392
  } : {},
4278
4393
  ...day.hasQuestions ? { questions: day.questions } : {},
4279
- ...day.hasWebSearches ? { webSearches: day.webSearches } : {}
4394
+ ...day.hasWebSearches ? { webSearches: day.webSearches } : {},
4395
+ ...day.hasContext ? {
4396
+ context: {
4397
+ bucketRuleVersion: LOG_BUCKETS_V2,
4398
+ calls: {
4399
+ main: asBuckets(day.context.calls.main, "calls"),
4400
+ subagents: asBuckets(
4401
+ day.context.calls.subagents,
4402
+ "calls"
4403
+ )
4404
+ },
4405
+ firstCalls: {
4406
+ main: asBuckets(day.context.firstCalls, "sessions")
4407
+ },
4408
+ firstCallHarnessTokens: day.context.firstCallHarnessTokens,
4409
+ firstCallInstructionsTokens: day.context.firstCallInstructionsTokens,
4410
+ firstCallCount: day.context.firstCallCount,
4411
+ maxContext: day.context.maxContext,
4412
+ compactions: day.context.compactions,
4413
+ ...day.context.window ? { window: day.context.window.window } : {}
4414
+ }
4415
+ } : {}
4280
4416
  }))
4281
4417
  };
4282
4418
  sessions.clear();
4283
4419
  eventCells.clear();
4284
4420
  webSearchesByDate.clear();
4421
+ compactionsByDate.clear();
4285
4422
  eventDates.clear();
4286
4423
  return finished;
4287
4424
  }
@@ -4295,9 +4432,30 @@ function createAggregate2() {
4295
4432
  workflow: createHarnessWorkflowReducer("claude-code", workflowLocal),
4296
4433
  workflowLocal,
4297
4434
  workflowSeenCalls: /* @__PURE__ */ new Set(),
4298
- workflowSeenTurns: /* @__PURE__ */ new Set()
4435
+ workflowSeenTurns: /* @__PURE__ */ new Set(),
4436
+ contextFirstCall: /* @__PURE__ */ new Map()
4299
4437
  });
4300
4438
  }
4439
+ function workflowSessionKey(rec) {
4440
+ const baseSession = asStr(rec.sessionId);
4441
+ if (!baseSession) return null;
4442
+ if (rec.isSidechain !== true) return baseSession;
4443
+ return `${baseSession}:agent:${asStr(rec.agentId) ?? "unknown"}`;
4444
+ }
4445
+ function isApiCall(rec) {
4446
+ if (asStr(rec.type) !== "assistant") return false;
4447
+ const msg = asObj(rec.message);
4448
+ if (!msg || !asObj(msg.usage)) return false;
4449
+ return !(asName(msg.model) ?? "").startsWith("<");
4450
+ }
4451
+ function noteRecordBeforeWindow(agg, raw) {
4452
+ const rec = asObj(raw);
4453
+ if (!rec || !isApiCall(rec)) return;
4454
+ const session = workflowSessionKey(rec);
4455
+ if (session && !agg.contextFirstCall.has(session)) {
4456
+ agg.contextFirstCall.set(session, null);
4457
+ }
4458
+ }
4301
4459
  function projectWorkspaceDirectory(raw) {
4302
4460
  const rec = asObj(raw);
4303
4461
  return rec ? asStr(rec.cwd) : null;
@@ -4330,7 +4488,25 @@ function ingestRecord(agg, raw, ctx) {
4330
4488
  ingestClaudeWorkflow(agg, rec, ctx, tsMs);
4331
4489
  ingestAssistant(agg, rec, tsMs);
4332
4490
  } else if (type === "user") ingestUser(agg, rec);
4333
- else if (type === "system") ingestClaudeTurnDuration(agg, rec, ctx, tsMs);
4491
+ else if (type === "system") {
4492
+ ingestClaudeTurnDuration(agg, rec, ctx, tsMs);
4493
+ ingestClaudeCompaction(agg, rec, ctx, tsMs);
4494
+ }
4495
+ }
4496
+ function ingestClaudeCompaction(agg, rec, ctx, tsMs) {
4497
+ if (tsMs === null || asStr(rec.subtype) !== "compact_boundary") return;
4498
+ const session = workflowSessionKey(rec);
4499
+ if (!session) return;
4500
+ agg.workflow.ingest({
4501
+ type: "compaction",
4502
+ session,
4503
+ projectWorkspace: projectWorkspaceDirectory(rec) ?? ctx.projectDir,
4504
+ tsMs,
4505
+ ...rec.isSidechain === true ? { sidechain: true } : {}
4506
+ });
4507
+ }
4508
+ function contextOf(counts) {
4509
+ return counts.input + counts.cacheWrite5m + counts.cacheWrite1h + counts.cacheWriteUnsplit + counts.cacheRead;
4334
4510
  }
4335
4511
  function ingestClaudeTurnDuration(agg, rec, ctx, tsMs) {
4336
4512
  if (tsMs === null || asStr(rec.subtype) !== "turn_duration" || asNum(rec.durationMs) <= 0) {
@@ -4362,6 +4538,24 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
4362
4538
  const messageId = asStr(msg.id);
4363
4539
  const newTurn = messageId ? !agg.workflowSeenTurns.has(messageId) : true;
4364
4540
  if (messageId) agg.workflowSeenTurns.add(messageId);
4541
+ let context = null;
4542
+ if (counts && isApiCall(rec)) {
4543
+ const held = agg.contextFirstCall.get(session);
4544
+ let first = false;
4545
+ if (held === void 0) {
4546
+ agg.contextFirstCall.set(session, messageId);
4547
+ first = true;
4548
+ } else if (held !== null && held === messageId) first = true;
4549
+ context = {
4550
+ contextTokens: contextOf(counts),
4551
+ ...first ? {
4552
+ firstCall: {
4553
+ harnessTokens: counts.cacheRead,
4554
+ instructionsTokens: contextOf(counts) - counts.cacheRead
4555
+ }
4556
+ } : {}
4557
+ };
4558
+ }
4365
4559
  agg.workflow.ingest({
4366
4560
  type: "response",
4367
4561
  session,
@@ -4373,7 +4567,8 @@ function ingestClaudeWorkflow(agg, rec, ctx, tsMs) {
4373
4567
  ...asName(msg.model) ? { model: asName(msg.model) } : {},
4374
4568
  ...counts ? { responseTokens: counts.output } : {},
4375
4569
  ...counts ? { routingTokens: countsTotal(counts) } : {},
4376
- ...asStr(rec.effort) ?? asStr(msg.effort) ? { effort: asStr(rec.effort) ?? asStr(msg.effort) } : {}
4570
+ ...asStr(rec.effort) ?? asStr(msg.effort) ? { effort: asStr(rec.effort) ?? asStr(msg.effort) } : {},
4571
+ ...context ?? {}
4377
4572
  });
4378
4573
  const tools = [];
4379
4574
  for (const rawBlock of asArr(msg.content)) {
@@ -4751,7 +4946,10 @@ async function ingestFile(agg, file, projectDir, projectWorkspaces, sinceMs) {
4751
4946
  }
4752
4947
  if (sinceMs !== void 0) {
4753
4948
  const ts = rec && typeof rec === "object" && "timestamp" in rec && typeof rec.timestamp === "string" ? Date.parse(rec.timestamp) : Number.NaN;
4754
- if (Number.isNaN(ts) || ts < sinceMs) continue;
4949
+ if (Number.isNaN(ts) || ts < sinceMs) {
4950
+ noteRecordBeforeWindow(agg, rec);
4951
+ continue;
4952
+ }
4755
4953
  }
4756
4954
  ingestRecord(agg, rec, { projectDir: projectWorkspace });
4757
4955
  }
@@ -4803,7 +5001,33 @@ function createFileState() {
4803
5001
  responseIndex: 0,
4804
5002
  currentQuestionBack: false,
4805
5003
  counted: false,
4806
- metaTsMs: null
5004
+ metaTsMs: null,
5005
+ forked: false,
5006
+ sawResponse: false
5007
+ };
5008
+ }
5009
+ function genuineDelta(payload, state, tsMs) {
5010
+ if (asStr(payload.type) !== "token_count") return null;
5011
+ const info = asObj(payload.info);
5012
+ const last = info ? asObj(info.last_token_usage) : null;
5013
+ if (!last) return null;
5014
+ const inputTotal = asNum(last.input_tokens);
5015
+ const cached = Math.min(asNum(last.cached_input_tokens), inputTotal);
5016
+ const counts = {
5017
+ input: inputTotal - cached,
5018
+ output: asNum(last.output_tokens),
5019
+ cacheWrite5m: 0,
5020
+ cacheWrite1h: 0,
5021
+ cacheWriteUnsplit: 0,
5022
+ cacheRead: cached
5023
+ };
5024
+ if (countsTotal(counts) === 0) return null;
5025
+ if (tsMs !== null && state.metaTsMs !== null && tsMs - state.metaTsMs < FORK_REPLAY_WINDOW_MS)
5026
+ return null;
5027
+ return {
5028
+ counts,
5029
+ last,
5030
+ contextWindow: info ? asNum(info.model_context_window) : 0
4807
5031
  };
4808
5032
  }
4809
5033
  var FORK_REPLAY_WINDOW_MS = 500;
@@ -4825,11 +5049,17 @@ function ingestLine(agg, raw, state, sinceMs) {
4825
5049
  state.cliVersion = asStr(payload.cli_version) ?? state.cliVersion;
4826
5050
  state.cwd = asStr(payload.cwd) ?? state.cwd;
4827
5051
  state.metaTsMs = tsMs ?? state.metaTsMs;
5052
+ state.forked = payload.forked_from_id !== void 0 && payload.forked_from_id !== null;
4828
5053
  } else if (type === "turn_context" && payload) {
4829
5054
  const model = asName(payload.model);
4830
5055
  if (model) state.modelKey = normalizeModel(model);
4831
5056
  state.effort = asStr(payload.effort) ?? state.effort;
4832
5057
  }
5058
+ let firstCall = false;
5059
+ if (type === "event_msg" && payload && genuineDelta(payload, state, tsMs)) {
5060
+ firstCall = !state.sawResponse;
5061
+ state.sawResponse = true;
5062
+ }
4833
5063
  if (!inWindow) return;
4834
5064
  if (tsMs !== null && timestamp) {
4835
5065
  agg.activeDays.add(timestamp.slice(0, 10));
@@ -4838,9 +5068,18 @@ function ingestLine(agg, raw, state, sinceMs) {
4838
5068
  }
4839
5069
  noteActivity(agg, state, tsMs);
4840
5070
  noteProjectDay(agg, state.cwd ?? "(unknown)", tsMs);
4841
- if (type === "event_msg" && payload) ingestEvent(agg, payload, state, tsMs);
5071
+ if (type === "event_msg" && payload)
5072
+ ingestEvent(agg, payload, state, tsMs, firstCall);
4842
5073
  else if (type === "response_item" && payload)
4843
5074
  ingestItem(agg, payload, state, tsMs);
5075
+ else if (type === "compacted" && tsMs !== null && state.sessionId) {
5076
+ agg.workflow.ingest({
5077
+ type: "compaction",
5078
+ session: state.sessionId,
5079
+ projectWorkspace: state.cwd ?? void 0,
5080
+ tsMs
5081
+ });
5082
+ }
4844
5083
  }
4845
5084
  function noteActivity(agg, state, tsMs) {
4846
5085
  if (state.sessionId) noteSessionStart(agg, state.sessionId, tsMs);
@@ -4850,25 +5089,11 @@ function noteActivity(agg, state, tsMs) {
4850
5089
  if (state.cliVersion) agg.ccVersions.add(cleanName(state.cliVersion));
4851
5090
  agg.projectDirs.add(state.cwd ?? "(unknown)");
4852
5091
  }
4853
- function ingestEvent(agg, payload, state, tsMs) {
4854
- if (asStr(payload.type) !== "token_count") return;
4855
- const info = asObj(payload.info);
4856
- const last = info ? asObj(info.last_token_usage) : null;
4857
- if (!last) return;
4858
- const inputTotal = asNum(last.input_tokens);
4859
- const cached = Math.min(asNum(last.cached_input_tokens), inputTotal);
4860
- const counts = {
4861
- input: inputTotal - cached,
4862
- output: asNum(last.output_tokens),
4863
- cacheWrite5m: 0,
4864
- cacheWrite1h: 0,
4865
- cacheWriteUnsplit: 0,
4866
- cacheRead: cached
4867
- };
5092
+ function ingestEvent(agg, payload, state, tsMs, firstCall) {
5093
+ const delta = genuineDelta(payload, state, tsMs);
5094
+ if (!delta) return;
5095
+ const { counts, last, contextWindow } = delta;
4868
5096
  const total = countsTotal(counts);
4869
- if (total === 0) return;
4870
- if (tsMs !== null && state.metaTsMs !== null && tsMs - state.metaTsMs < FORK_REPLAY_WINDOW_MS)
4871
- return;
4872
5097
  if (state.modelKey === null) return;
4873
5098
  if (tsMs === null) agg.untimestampedResponses++;
4874
5099
  agg.distinctResponses++;
@@ -4894,7 +5119,20 @@ function ingestEvent(agg, payload, state, tsMs) {
4894
5119
  responseTokens: counts.output,
4895
5120
  routingTokens: total,
4896
5121
  thinkingTokens: asNum(last.reasoning_output_tokens),
4897
- ...state.effort ? { effort: state.effort } : {}
5122
+ ...state.effort ? { effort: state.effort } : {},
5123
+ // The context at this call is `input_tokens` (cached is a subset).
5124
+ // On the first call the cached part is the harness (base
5125
+ // instructions and tool specs, cached by an earlier session) and
5126
+ // the fresh part is the instructions: AGENTS.md, environment,
5127
+ // skills and the first prompt. Same method as Claude Code (#358).
5128
+ contextTokens: counts.input + counts.cacheRead,
5129
+ ...contextWindow > 0 ? { contextWindow } : {},
5130
+ ...firstCall && !state.forked ? {
5131
+ firstCall: {
5132
+ harnessTokens: counts.cacheRead,
5133
+ instructionsTokens: counts.input
5134
+ }
5135
+ } : {}
4898
5136
  });
4899
5137
  agg.workflow.ingest({
4900
5138
  type: "turn",
@@ -6476,6 +6714,7 @@ import { createHash } from "node:crypto";
6476
6714
  import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "node:fs";
6477
6715
  import { homedir as homedir11 } from "node:os";
6478
6716
  import { dirname as dirname5, join as join8 } from "node:path";
6717
+ import { parse } from "smol-toml";
6479
6718
  function codexHome2() {
6480
6719
  return process.env.CODEX_HOME || join8(homedir11(), ".codex");
6481
6720
  }
@@ -6558,15 +6797,59 @@ function codexAutoSyncHookInstalled(file = codexHooksFile()) {
6558
6797
  if (!Array.isArray(sessionStart)) return false;
6559
6798
  return sessionStart.some((m) => (m.hooks ?? []).some((h) => isOurs(h)));
6560
6799
  }
6561
- function codexHookTrusted(configFile = codexConfigFile()) {
6562
- let text;
6800
+ function codexHookTrusted(configFile = codexConfigFile(), hooksFile = codexHooksFile()) {
6563
6801
  try {
6564
- text = readFileSync9(configFile, "utf-8");
6802
+ const config = parse(readFileSync9(configFile, "utf-8"));
6803
+ const read = readHooksJson(hooksFile);
6804
+ if ("error" in read) return null;
6805
+ const sessionStart = read.settings.hooks?.SessionStart;
6806
+ if (!Array.isArray(sessionStart)) return false;
6807
+ const matches = [];
6808
+ for (const [groupIndex, group] of sessionStart.entries()) {
6809
+ for (const [handlerIndex, handler] of (group.hooks ?? []).entries()) {
6810
+ if (!isOurs(handler) || typeof handler.command !== "string") continue;
6811
+ const normalizedHandler = {
6812
+ type: "command",
6813
+ command: handler.command,
6814
+ timeout: typeof handler.timeout === "number" ? Math.max(1, handler.timeout) : 600,
6815
+ async: handler.async === true
6816
+ };
6817
+ if (typeof handler.statusMessage === "string") {
6818
+ normalizedHandler.statusMessage = handler.statusMessage;
6819
+ }
6820
+ if (typeof handler.additionalContextLimit === "number" && handler.additionalContextLimit !== 2500) {
6821
+ normalizedHandler.additionalContextLimit = handler.additionalContextLimit;
6822
+ }
6823
+ const identity = {
6824
+ event_name: "session_start",
6825
+ hooks: [normalizedHandler]
6826
+ };
6827
+ if (typeof group.matcher === "string") identity.matcher = group.matcher;
6828
+ const currentHash = `sha256:${createHash("sha256").update(JSON.stringify(canonicalJson2(identity))).digest("hex")}`;
6829
+ const key2 = `${hooksFile}:session_start:${groupIndex}:${handlerIndex}`;
6830
+ matches.push(config.hooks?.state?.[key2]?.trusted_hash === currentHash);
6831
+ }
6832
+ }
6833
+ return matches.length > 0 && matches.every(Boolean);
6565
6834
  } catch {
6566
6835
  return null;
6567
6836
  }
6568
- const hash = createHash("sha256").update(CODEX_HOOK_COMMAND).digest("hex");
6569
- return text.includes(hash);
6837
+ }
6838
+ function canonicalJson2(value) {
6839
+ if (Array.isArray(value)) return value.map(canonicalJson2);
6840
+ if (value && typeof value === "object") {
6841
+ const sorted = {};
6842
+ for (const [key2, child] of Object.entries(value).sort(
6843
+ ([a], [b]) => a.localeCompare(b)
6844
+ )) {
6845
+ sorted[key2] = canonicalJson2(child);
6846
+ }
6847
+ return sorted;
6848
+ }
6849
+ if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
6850
+ return value;
6851
+ }
6852
+ throw new TypeError("hook identity is not JSON-serializable");
6570
6853
  }
6571
6854
 
6572
6855
  // src/autosync/optin.ts
@@ -6679,7 +6962,9 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
6679
6962
  if (names.has(CODEX_HARNESS_NAME)) {
6680
6963
  const codexResult = (deps.installCodexHook ?? installCodexAutoSyncHook)();
6681
6964
  if (!codexResult.ok) return codexResult;
6682
- trustLine = codexResult.message;
6965
+ if ((deps.codexHookTrustedImpl ?? codexHookTrusted)() !== true) {
6966
+ trustLine = codexResult.message;
6967
+ }
6683
6968
  }
6684
6969
  saveSettings(
6685
6970
  {
@@ -7387,7 +7672,7 @@ function buildWorkflowExtraction(harnessWorkflows, git, utcOffsetMinutes = machi
7387
7672
  ])
7388
7673
  ].sort();
7389
7674
  return {
7390
- aggregateVersion: WORKFLOW_AGGREGATES_V2,
7675
+ aggregateVersion: WORKFLOW_AGGREGATES_V3,
7391
7676
  utcOffsetMinutes,
7392
7677
  days: dates.map((date) => {
7393
7678
  const projects = projectDays.get(date)?.size;
@@ -7430,6 +7715,15 @@ function totalUSD(payload) {
7430
7715
  }
7431
7716
  return any ? sum : null;
7432
7717
  }
7718
+ function tokenBreakdown(payload) {
7719
+ let fresh = 0;
7720
+ let cached = 0;
7721
+ for (const model of payload.models) {
7722
+ fresh += model.tokens.input + model.tokens.output + model.tokens.cacheWrite;
7723
+ cached += model.tokens.cacheRead;
7724
+ }
7725
+ return fresh + cached === payload.activity.totalTokens ? { fresh, cached } : null;
7726
+ }
7433
7727
  function withheldCount(payload) {
7434
7728
  const w = payload.inventory.withheld;
7435
7729
  return w.builtinTools + w.mcpServers + w.skills + w.subagents + w.slashCommands;
@@ -7539,10 +7833,15 @@ function payloadBlock(payload, width, ownWindow, stats) {
7539
7833
  const label = `${harnessLabel2(payload.harness.name)}${payload.harness.version ? ` ${payload.harness.version}` : ""}`;
7540
7834
  const days = payload.activity.activeDayDates.length;
7541
7835
  const usd = totalUSD(payload);
7836
+ const breakdown = tokenBreakdown(payload);
7542
7837
  const totals = [
7543
7838
  `${payload.activity.sessions} session${payload.activity.sessions === 1 ? "" : "s"}`,
7544
7839
  `${days} active day${days === 1 ? "" : "s"}`,
7545
- `${fmtTokens(payload.activity.totalTokens)} tokens`
7840
+ `${fmtTokens(payload.activity.totalTokens)} tokens processed`,
7841
+ ...breakdown ? [
7842
+ `${fmtTokens(breakdown.fresh)} fresh`,
7843
+ `${fmtTokens(breakdown.cached)} cached`
7844
+ ] : []
7546
7845
  ];
7547
7846
  out.push(`${label.toUpperCase()} ${payload.activity.sessions}`);
7548
7847
  if (payload.activity.totalTokens === 0) {
@@ -7629,7 +7928,7 @@ var PHASE_ORDER = ["scout", "build", "verify", "handoff", "unknown"];
7629
7928
  function workflowBlock(workflowDays, utcOffsetMinutes, host) {
7630
7929
  const out = [];
7631
7930
  const folded = foldWorkflowDays(workflowDays, {
7632
- aggregateVersion: WORKFLOW_AGGREGATES_V2,
7931
+ aggregateVersion: WORKFLOW_AGGREGATES_V3,
7633
7932
  utcOffsetMinutes
7634
7933
  });
7635
7934
  const harnesses = folded?.harnesses ?? [];
@@ -7639,7 +7938,7 @@ function workflowBlock(workflowDays, utcOffsetMinutes, host) {
7639
7938
  ...new Set(withPlaybook.map((h) => h.phase?.ruleVersion ?? ""))
7640
7939
  ].filter(Boolean);
7641
7940
  out.push(
7642
- `workflow ${harnesses.length} harness${harnesses.length === 1 ? "" : "es"} \xB7 ${sessions} sessions \xB7 ${WORKFLOW_AGGREGATES_V2}`
7941
+ `workflow ${harnesses.length} harness${harnesses.length === 1 ? "" : "es"} \xB7 ${sessions} sessions \xB7 ${WORKFLOW_AGGREGATES_V3}`
7643
7942
  );
7644
7943
  const first = folded?.dates[0];
7645
7944
  const last = folded?.dates.at(-1);