fleetlens 0.13.1 → 0.14.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.
Files changed (151) hide show
  1. package/app/apps/web/.next/BUILD_ID +1 -1
  2. package/app/apps/web/.next/app-path-routes-manifest.json +0 -1
  3. package/app/apps/web/.next/build-manifest.json +3 -3
  4. package/app/apps/web/.next/prerender-manifest.json +3 -3
  5. package/app/apps/web/.next/routes-manifest.json +0 -6
  6. package/app/apps/web/.next/server/app/_global-error.html +1 -1
  7. package/app/apps/web/.next/server/app/_global-error.rsc +1 -1
  8. package/app/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/app/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/app/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/app/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/app/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/app/apps/web/.next/server/app/_not-found/page.js +1 -1
  14. package/app/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/app/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/app/apps/web/.next/server/app/api/ask/route.js.nft.json +1 -1
  17. package/app/apps/web/.next/server/app/api/digest/day/[date]/route.js.nft.json +1 -1
  18. package/app/apps/web/.next/server/app/api/digest/month/[yearMonth]/route.js.nft.json +1 -1
  19. package/app/apps/web/.next/server/app/api/digest/month-index/route.js.nft.json +1 -1
  20. package/app/apps/web/.next/server/app/api/digest/week/[startDate]/route.js.nft.json +1 -1
  21. package/app/apps/web/.next/server/app/api/digest/week-index/route.js.nft.json +1 -1
  22. package/app/apps/web/.next/server/app/api/events/route.js.nft.json +1 -1
  23. package/app/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -1
  24. package/app/apps/web/.next/server/app/api/pdf/[key]/route.js.nft.json +1 -1
  25. package/app/apps/web/.next/server/app/api/runs/[runId]/route.js.nft.json +1 -1
  26. package/app/apps/web/.next/server/app/api/runs/[runId]/stream/route.js.nft.json +1 -1
  27. package/app/apps/web/.next/server/app/api/runs/route.js.nft.json +1 -1
  28. package/app/apps/web/.next/server/app/api/settings/route.js +3 -3
  29. package/app/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -1
  30. package/app/apps/web/.next/server/app/api/workflow-agent/route.js.nft.json +1 -1
  31. package/app/apps/web/.next/server/app/changelog/page.js +1 -1
  32. package/app/apps/web/.next/server/app/changelog/page.js.nft.json +1 -1
  33. package/app/apps/web/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
  34. package/app/apps/web/.next/server/app/day/[date]/page.js +1 -1
  35. package/app/apps/web/.next/server/app/day/[date]/page.js.nft.json +1 -1
  36. package/app/apps/web/.next/server/app/day/[date]/page_client-reference-manifest.js +1 -1
  37. package/app/apps/web/.next/server/app/day/page.js +1 -1
  38. package/app/apps/web/.next/server/app/day/page.js.nft.json +1 -1
  39. package/app/apps/web/.next/server/app/day/page_client-reference-manifest.js +1 -1
  40. package/app/apps/web/.next/server/app/digest/[date]/page.js +1 -1
  41. package/app/apps/web/.next/server/app/digest/[date]/page.js.nft.json +1 -1
  42. package/app/apps/web/.next/server/app/digest/[date]/page_client-reference-manifest.js +1 -1
  43. package/app/apps/web/.next/server/app/digest/page.js +1 -1
  44. package/app/apps/web/.next/server/app/digest/page.js.nft.json +1 -1
  45. package/app/apps/web/.next/server/app/digest/page_client-reference-manifest.js +1 -1
  46. package/app/apps/web/.next/server/app/insights/[key]/page.js +1 -1
  47. package/app/apps/web/.next/server/app/insights/[key]/page.js.nft.json +1 -1
  48. package/app/apps/web/.next/server/app/insights/[key]/page_client-reference-manifest.js +1 -1
  49. package/app/apps/web/.next/server/app/insights/page.js +1 -1
  50. package/app/apps/web/.next/server/app/insights/page.js.nft.json +1 -1
  51. package/app/apps/web/.next/server/app/insights/page_client-reference-manifest.js +1 -1
  52. package/app/apps/web/.next/server/app/page.js +2 -2
  53. package/app/apps/web/.next/server/app/page.js.nft.json +1 -1
  54. package/app/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  55. package/app/apps/web/.next/server/app/parallelism/page.js +1 -1
  56. package/app/apps/web/.next/server/app/parallelism/page.js.nft.json +1 -1
  57. package/app/apps/web/.next/server/app/parallelism/page_client-reference-manifest.js +1 -1
  58. package/app/apps/web/.next/server/app/print/[key]/page.js +1 -1
  59. package/app/apps/web/.next/server/app/print/[key]/page.js.nft.json +1 -1
  60. package/app/apps/web/.next/server/app/print/[key]/page_client-reference-manifest.js +1 -1
  61. package/app/apps/web/.next/server/app/projects/[slug]/page.js +2 -2
  62. package/app/apps/web/.next/server/app/projects/[slug]/page.js.nft.json +1 -1
  63. package/app/apps/web/.next/server/app/projects/[slug]/page_client-reference-manifest.js +1 -1
  64. package/app/apps/web/.next/server/app/projects/page.js +1 -1
  65. package/app/apps/web/.next/server/app/projects/page.js.nft.json +1 -1
  66. package/app/apps/web/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  67. package/app/apps/web/.next/server/app/runs/page.js +1 -1
  68. package/app/apps/web/.next/server/app/runs/page.js.nft.json +1 -1
  69. package/app/apps/web/.next/server/app/runs/page_client-reference-manifest.js +1 -1
  70. package/app/apps/web/.next/server/app/sessions/[id]/page.js +1 -1
  71. package/app/apps/web/.next/server/app/sessions/[id]/page.js.nft.json +1 -1
  72. package/app/apps/web/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
  73. package/app/apps/web/.next/server/app/sessions/page.js +1 -1
  74. package/app/apps/web/.next/server/app/sessions/page.js.nft.json +1 -1
  75. package/app/apps/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  76. package/app/apps/web/.next/server/app/settings/page.js +1 -1
  77. package/app/apps/web/.next/server/app/settings/page.js.nft.json +1 -1
  78. package/app/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  79. package/app/apps/web/.next/server/app/team/page.js +2 -2
  80. package/app/apps/web/.next/server/app/team/page.js.nft.json +1 -1
  81. package/app/apps/web/.next/server/app/team/page_client-reference-manifest.js +1 -1
  82. package/app/apps/web/.next/server/app/usage/page.js +1 -1
  83. package/app/apps/web/.next/server/app/usage/page.js.nft.json +1 -1
  84. package/app/apps/web/.next/server/app/usage/page_client-reference-manifest.js +1 -1
  85. package/app/apps/web/.next/server/app-paths-manifest.json +0 -1
  86. package/app/apps/web/.next/server/chunks/[externals]__0o43ejh._.js +3 -0
  87. package/app/apps/web/.next/server/chunks/node_modules_next_036my~r._.js +49 -0
  88. package/app/apps/web/.next/server/chunks/packages_parser_dist_06d9se1._.js +1 -1
  89. package/app/apps/web/.next/server/chunks/packages_parser_dist_10ihc3~._.js +1 -1
  90. package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0z7mg0b._.js → [root-of-the-server]__08l86e-._.js} +2 -2
  91. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0_hv6aq._.js +3 -0
  92. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0dh0q7b._.js +3 -0
  93. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0r4tks8._.js +1 -1
  94. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0yv0m0q._.js +3 -0
  95. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0~.f_3z._.js +3 -3
  96. package/app/apps/web/.next/server/chunks/ssr/{[root-of-the-server]__0o03ymy._.js → [root-of-the-server]__10~b4bf._.js} +2 -2
  97. package/app/apps/web/.next/server/chunks/ssr/_0xu2pvc._.js +1 -1
  98. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_day_[date]_day-view_tsx_0wjoe1u._.js +1 -1
  99. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0n7_p1d._.js +1 -1
  100. package/app/apps/web/.next/server/chunks/ssr/apps_web_lib_changelog_ts_0nb5_-s._.js +1 -1
  101. package/app/apps/web/.next/server/chunks/ssr/{node_modules_next_dist_0t-hj0x._.js → node_modules_0yrkyef._.js} +2 -2
  102. package/app/apps/web/.next/server/chunks/ssr/packages_parser_dist_0mx_509._.js +1 -1
  103. package/app/apps/web/.next/server/functions-config-manifest.json +0 -1
  104. package/app/apps/web/.next/server/middleware-build-manifest.js +3 -3
  105. package/app/apps/web/.next/server/middleware-manifest.json +5 -5
  106. package/app/apps/web/.next/server/pages/500.html +1 -1
  107. package/app/apps/web/.next/server/server-reference-manifest.js +1 -1
  108. package/app/apps/web/.next/server/server-reference-manifest.json +1 -1
  109. package/app/apps/web/.next/static/chunks/005ad7l-h1-.6.js +1 -0
  110. package/app/apps/web/.next/static/chunks/{0vz4~a~~hq399.js → 02xxpexzk3rf-.js} +1 -1
  111. package/app/apps/web/.next/static/chunks/0l4r0bqqtdju..css +1 -0
  112. package/app/apps/web/.next/static/chunks/0~qdw1b2uz._5.js +10 -0
  113. package/app/apps/web/app/parallelism/gantt-chart.tsx +8 -1
  114. package/app/apps/web/app/sessions/[id]/session-view/minimap.tsx +1587 -0
  115. package/app/apps/web/app/sessions/[id]/session-view/workflows-panel.tsx +944 -0
  116. package/app/apps/web/app/sessions/[id]/session-view.tsx +909 -3223
  117. package/app/apps/web/app/team/page.tsx +23 -75
  118. package/app/apps/web/components/previous-cycles-trend.tsx +4 -1
  119. package/app/apps/web/components/tail-mode.tsx +7 -1
  120. package/app/apps/web/lib/changelog-data.generated.ts +1 -1
  121. package/app/apps/web/lib/format.ts +17 -0
  122. package/app/apps/web/lib/team-config.ts +0 -2
  123. package/app/apps/web/package.json +1 -1
  124. package/app/apps/web/tsconfig.tsbuildinfo +1 -1
  125. package/dist/daemon-worker.js +1970 -1611
  126. package/dist/index.js +1182 -161
  127. package/package.json +1 -1
  128. package/app/apps/web/.next/server/app/api/team-config/route/app-paths-manifest.json +0 -3
  129. package/app/apps/web/.next/server/app/api/team-config/route/build-manifest.json +0 -9
  130. package/app/apps/web/.next/server/app/api/team-config/route/server-reference-manifest.json +0 -4
  131. package/app/apps/web/.next/server/app/api/team-config/route.js +0 -8
  132. package/app/apps/web/.next/server/app/api/team-config/route.js.map +0 -5
  133. package/app/apps/web/.next/server/app/api/team-config/route.js.nft.json +0 -1
  134. package/app/apps/web/.next/server/app/api/team-config/route_client-reference-manifest.js +0 -3
  135. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0atn.dt._.js +0 -3
  136. package/app/apps/web/.next/server/chunks/[root-of-the-server]__0b.b7~1._.js +0 -3
  137. package/app/apps/web/.next/server/chunks/_0euqdko._.js +0 -49
  138. package/app/apps/web/.next/server/chunks/apps_web__next-internal_server_app_api_team-config_route_actions_0ad2gak.js +0 -3
  139. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0exckd4._.js +0 -3
  140. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0n-ax-5._.js +0 -3
  141. package/app/apps/web/.next/server/chunks/ssr/[root-of-the-server]__0q9qy0z._.js +0 -3
  142. package/app/apps/web/.next/server/chunks/ssr/apps_web_app_team_team-sync-form_tsx_0choz0h._.js +0 -3
  143. package/app/apps/web/.next/static/chunks/02bv_ejzhhzvp.js +0 -10
  144. package/app/apps/web/.next/static/chunks/083~hzkv577.5.css +0 -1
  145. package/app/apps/web/.next/static/chunks/0q8dg4txlzno0.js +0 -1
  146. package/app/apps/web/.next/static/chunks/14joqbsr9ihhu.js +0 -1
  147. package/app/apps/web/app/api/team-config/route.ts +0 -39
  148. package/app/apps/web/app/team/team-sync-form.tsx +0 -144
  149. /package/app/apps/web/.next/static/{VEfemGVAQ0xCHjj15orZU → 3VbAChKD8EChlt8Tm5N5r}/_buildManifest.js +0 -0
  150. /package/app/apps/web/.next/static/{VEfemGVAQ0xCHjj15orZU → 3VbAChKD8EChlt8Tm5N5r}/_clientMiddlewareManifest.js +0 -0
  151. /package/app/apps/web/.next/static/{VEfemGVAQ0xCHjj15orZU → 3VbAChKD8EChlt8Tm5N5r}/_ssgManifest.js +0 -0
@@ -3977,13 +3977,138 @@ async function readJsonl2(filePath) {
3977
3977
  }
3978
3978
  return out;
3979
3979
  }
3980
+ function dedupeReplayedLines(lines) {
3981
+ const keepAt = /* @__PURE__ */ new Map();
3982
+ lines.forEach((line, i) => {
3983
+ const rec = line;
3984
+ if (typeof rec.uuid !== "string")
3985
+ return;
3986
+ const prev = keepAt.get(rec.uuid);
3987
+ if (prev === void 0) {
3988
+ keepAt.set(rec.uuid, i);
3989
+ return;
3990
+ }
3991
+ const prevHasTs = lines[prev].timestamp !== void 0;
3992
+ if (!prevHasTs && rec.timestamp !== void 0)
3993
+ keepAt.set(rec.uuid, i);
3994
+ });
3995
+ return lines.filter((line, i) => {
3996
+ const rec = line;
3997
+ return typeof rec.uuid !== "string" || keepAt.get(rec.uuid) === i;
3998
+ });
3999
+ }
4000
+ function textFromContent(content) {
4001
+ if (typeof content === "string")
4002
+ return content;
4003
+ if (Array.isArray(content)) {
4004
+ const parts = [];
4005
+ for (const b of content) {
4006
+ if (b && typeof b === "object" && b.type === "text") {
4007
+ const t = b.text;
4008
+ if (typeof t === "string")
4009
+ parts.push(t);
4010
+ }
4011
+ }
4012
+ if (parts.length)
4013
+ return parts.join("\n");
4014
+ }
4015
+ return void 0;
4016
+ }
4017
+ function partitionCoworkLines(lines) {
4018
+ const mainLines = [];
4019
+ const subagentGroups = /* @__PURE__ */ new Map();
4020
+ for (const line of lines) {
4021
+ const ptid = line.parent_tool_use_id;
4022
+ if (typeof ptid === "string") {
4023
+ const g = subagentGroups.get(ptid);
4024
+ if (g)
4025
+ g.push(line);
4026
+ else
4027
+ subagentGroups.set(ptid, [line]);
4028
+ } else {
4029
+ mainLines.push(line);
4030
+ }
4031
+ }
4032
+ return { mainLines, subagentGroups };
4033
+ }
4034
+ function buildCoworkSubagents(subagentGroups, allLines, sessionStartMs) {
4035
+ if (subagentGroups.size === 0)
4036
+ return [];
4037
+ const refs = /* @__PURE__ */ new Map();
4038
+ for (const line of allLines) {
4039
+ const rec = line;
4040
+ const content = rec.message?.content;
4041
+ if (!Array.isArray(content))
4042
+ continue;
4043
+ for (const b of content) {
4044
+ if (!b || typeof b !== "object")
4045
+ continue;
4046
+ const block = b;
4047
+ if (block.type === "tool_use" && block.name === "Agent" && typeof block.id === "string") {
4048
+ const input = block.input ?? {};
4049
+ const ref = refs.get(block.id) ?? {};
4050
+ if (typeof rec.uuid === "string")
4051
+ ref.parentUuid = rec.uuid;
4052
+ if (typeof input.description === "string")
4053
+ ref.description = input.description;
4054
+ if (typeof input.subagent_type === "string")
4055
+ ref.agentType = input.subagent_type;
4056
+ if (typeof input.prompt === "string")
4057
+ ref.prompt = input.prompt;
4058
+ refs.set(block.id, ref);
4059
+ } else if (block.type === "tool_result" && typeof block.tool_use_id === "string" && subagentGroups.has(block.tool_use_id)) {
4060
+ const ref = refs.get(block.tool_use_id) ?? {};
4061
+ ref.finalText = textFromContent(block.content) ?? ref.finalText;
4062
+ refs.set(block.tool_use_id, ref);
4063
+ }
4064
+ }
4065
+ }
4066
+ const runs = [];
4067
+ for (const [toolUseId, groupLines] of subagentGroups) {
4068
+ const summary = summarizeSubagentLines(groupLines);
4069
+ const ref = refs.get(toolUseId) ?? {};
4070
+ const finalText = ref.finalText ?? summary.finalText;
4071
+ const finalPreview = finalText ? finalText.replace(/\s+/g, " ").trim().slice(0, 240) : summary.finalPreview;
4072
+ const startTOffsetMs = sessionStartMs !== void 0 && summary.startMs !== void 0 ? Math.max(0, summary.startMs - sessionStartMs) : void 0;
4073
+ const endTOffsetMs = sessionStartMs !== void 0 && summary.endMs !== void 0 ? Math.max(0, summary.endMs - sessionStartMs) : void 0;
4074
+ const durationMs = summary.startMs !== void 0 && summary.endMs !== void 0 ? summary.endMs - summary.startMs : void 0;
4075
+ runs.push({
4076
+ agentId: toolUseId,
4077
+ agentType: ref.agentType ?? "unknown",
4078
+ description: ref.description ?? (ref.prompt ? ref.prompt.replace(/\s+/g, " ").trim().slice(0, 80) : "(no description)"),
4079
+ startMs: summary.startMs,
4080
+ endMs: summary.endMs,
4081
+ durationMs,
4082
+ startTOffsetMs,
4083
+ endTOffsetMs,
4084
+ eventCount: summary.eventCount,
4085
+ totalUsage: summary.totalUsage,
4086
+ parentUuid: ref.parentUuid,
4087
+ parentToolUseId: toolUseId,
4088
+ runInBackground: false,
4089
+ finalPreview,
4090
+ finalText,
4091
+ prompt: ref.prompt,
4092
+ model: summary.model,
4093
+ toolCalls: summary.toolCalls,
4094
+ toolCallCount: summary.toolCallCount,
4095
+ assistantMessageCount: summary.assistantMessageCount
4096
+ });
4097
+ }
4098
+ runs.sort((a, b) => (a.startMs ?? 0) - (b.startMs ?? 0));
4099
+ return runs;
4100
+ }
3980
4101
  async function parseCoworkSession(file) {
3981
4102
  const [lines, coworkMeta, spaceIdToPath] = await Promise.all([
3982
4103
  readJsonl2(file.auditPath),
3983
4104
  readCoworkMeta(file.metaPath),
3984
4105
  loadSpacesForWorkspace(file.workspaceDir)
3985
4106
  ]);
3986
- const { meta: baseMeta, events } = parseTranscript(lines);
4107
+ const deduped = dedupeReplayedLines(lines);
4108
+ const { mainLines, subagentGroups } = partitionCoworkLines(deduped);
4109
+ const { meta: baseMeta, events } = parseTranscript(mainLines);
4110
+ const sessionStartMs = baseMeta.firstTimestamp ? Date.parse(baseMeta.firstTimestamp) : void 0;
4111
+ const subagents = buildCoworkSubagents(subagentGroups, deduped, sessionStartMs);
3987
4112
  const { projectName, projectDir, cwd } = resolveProject(coworkMeta, spaceIdToPath);
3988
4113
  const meta = {
3989
4114
  ...baseMeta,
@@ -3996,7 +4121,7 @@ async function parseCoworkSession(file) {
3996
4121
  cwd: cwd ?? baseMeta.cwd,
3997
4122
  model: baseMeta.model ?? coworkMeta.model
3998
4123
  };
3999
- return { meta, events };
4124
+ return { meta, events, subagents };
4000
4125
  }
4001
4126
  function cacheKeyFor(file) {
4002
4127
  return {
@@ -4047,8 +4172,12 @@ async function getCoworkSession(id, opts = {}) {
4047
4172
  const cached = detailCache5.get(file.auditPath);
4048
4173
  if (cached && cacheKeyMatches(cached.key, key))
4049
4174
  return cached.detail;
4050
- const { meta, events } = await parseCoworkSession(file);
4051
- const detail = { ...meta, events };
4175
+ const { meta, events, subagents } = await parseCoworkSession(file);
4176
+ const detail = {
4177
+ ...meta,
4178
+ events,
4179
+ ...subagents.length ? { subagents } : {}
4180
+ };
4052
4181
  detailCache5.set(file.auditPath, { detail, key });
4053
4182
  return detail;
4054
4183
  }
@@ -4057,6 +4186,7 @@ var init_cowork = __esm({
4057
4186
  "../parser/dist/cowork.js"() {
4058
4187
  "use strict";
4059
4188
  init_analytics();
4189
+ init_claude_code();
4060
4190
  init_parser();
4061
4191
  DEFAULT_COWORK_ROOT = defaultCoworkRoot();
4062
4192
  SESSION_ID_RE = /^local_[0-9a-f-]+$/;
@@ -4375,9 +4505,14 @@ function buildCalibrationCurve(events, snapshots, tier = "pro-max-20x", granular
4375
4505
  curve.push({
4376
4506
  ts: new Date(cur).toISOString(),
4377
4507
  real_5h: real5,
4378
- pred_5h: Math.max(0, Math.min(200, p5)),
4508
+ // Predicted utilization LEGITIMATELY exceeds 200 on overage (the
4509
+ // extrapolated cycle close). The old min(200) clamp silently capped it,
4510
+ // so cyclePeaks.peakPct could never carry a *predicted* overage to the
4511
+ // team server (which now accepts up to 10000). Keep only a generous
4512
+ // finite ceiling as a corruption guard, matching the wire cap.
4513
+ pred_5h: Math.max(0, Math.min(1e4, p5)),
4379
4514
  real_7d: real7,
4380
- pred_7d: Math.max(0, Math.min(200, p7)),
4515
+ pred_7d: Math.max(0, Math.min(1e4, p7)),
4381
4516
  cycle_end_5h: r5k != null ? new Date(r5k).toISOString() : null,
4382
4517
  cycle_end_7d: r7k != null ? new Date(r7k).toISOString() : null
4383
4518
  });
@@ -4971,7 +5106,7 @@ __export(file_probe_exports, {
4971
5106
  probeArtifactSignals: () => probeArtifactSignals
4972
5107
  });
4973
5108
  import { createHash } from "node:crypto";
4974
- import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
5109
+ import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
4975
5110
  import { homedir as homedir2 } from "node:os";
4976
5111
  import { join as join7, relative, resolve as resolve2, sep } from "node:path";
4977
5112
  import { execFileSync as execFileSync2 } from "node:child_process";
@@ -5002,7 +5137,7 @@ function listMarkdownFiles(dir) {
5002
5137
  const full = join7(dir, name);
5003
5138
  let stat;
5004
5139
  try {
5005
- stat = statSync3(full);
5140
+ stat = statSync4(full);
5006
5141
  } catch {
5007
5142
  continue;
5008
5143
  }
@@ -5019,19 +5154,19 @@ function firstSeenDateOf(file) {
5019
5154
  const out = execFileSync2(
5020
5155
  "git",
5021
5156
  ["log", "--diff-filter=A", "--follow", "--format=%cs", "--", file],
5022
- { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname6(file) }
5157
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname7(file) }
5023
5158
  ).trim().split("\n").filter(Boolean);
5024
5159
  if (out.length > 0) return out[out.length - 1];
5025
5160
  } catch {
5026
5161
  }
5027
5162
  try {
5028
- const s = statSync3(file);
5029
- return new Date((s.birthtime || s.mtime).getTime()).toISOString().slice(0, 10);
5163
+ const s = statSync4(file);
5164
+ return localDay(s.birthtime || s.mtime);
5030
5165
  } catch {
5031
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5166
+ return todayLocal();
5032
5167
  }
5033
5168
  }
5034
- function dirname6(p) {
5169
+ function dirname7(p) {
5035
5170
  const i = p.lastIndexOf(sep);
5036
5171
  return i < 0 ? "." : p.slice(0, i);
5037
5172
  }
@@ -5046,13 +5181,15 @@ function detectGitEmail(cwd) {
5046
5181
  return null;
5047
5182
  }
5048
5183
  }
5049
- function todayLocal() {
5050
- const d = /* @__PURE__ */ new Date();
5184
+ function localDay(d) {
5051
5185
  const y = d.getFullYear();
5052
5186
  const m = String(d.getMonth() + 1).padStart(2, "0");
5053
5187
  const day = String(d.getDate()).padStart(2, "0");
5054
5188
  return `${y}-${m}-${day}`;
5055
5189
  }
5190
+ function todayLocal() {
5191
+ return localDay(/* @__PURE__ */ new Date());
5192
+ }
5056
5193
  function scanAuthored(roots, subdir, day) {
5057
5194
  const out = [];
5058
5195
  for (const r of roots) {
@@ -5077,11 +5214,11 @@ function scanEdited(roots, day) {
5077
5214
  if (firstSeen === day) continue;
5078
5215
  let stat;
5079
5216
  try {
5080
- stat = statSync3(f);
5217
+ stat = statSync4(f);
5081
5218
  } catch {
5082
5219
  continue;
5083
5220
  }
5084
- const mtimeDay = new Date(stat.mtime.getTime()).toISOString().slice(0, 10);
5221
+ const mtimeDay = localDay(stat.mtime);
5085
5222
  if (mtimeDay !== day) continue;
5086
5223
  const rel = relative(dir, f);
5087
5224
  out.push({ pathHash: pathHash(`${r.label}:skills:${rel}`), lineDelta: 0 });
@@ -5103,7 +5240,7 @@ function claudemdLineDelta(extraRoots, authorEmail, day) {
5103
5240
  const out = execFileSync2(
5104
5241
  "git",
5105
5242
  ["log", "--numstat", "--format=COMMIT %cs %ae", "--", file],
5106
- { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname6(file) }
5243
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], cwd: dirname7(file) }
5107
5244
  );
5108
5245
  let currentDate = "";
5109
5246
  let currentEmail = "";
@@ -5154,7 +5291,7 @@ var init_file_probe = __esm({
5154
5291
 
5155
5292
  // src/daemon-worker.ts
5156
5293
  import { spawn as spawn2 } from "node:child_process";
5157
- import { appendFileSync as appendFileSync5, closeSync, existsSync as existsSync13, mkdirSync as mkdirSync13, openSync, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "node:fs";
5294
+ import { closeSync, existsSync as existsSync13, mkdirSync as mkdirSync13, openSync, readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "node:fs";
5158
5295
  import { dirname as dirname10, join as join11 } from "node:path";
5159
5296
  import { fileURLToPath } from "node:url";
5160
5297
 
@@ -5300,6 +5437,21 @@ function normalizeExtra(raw) {
5300
5437
  init_storage();
5301
5438
  init_fs();
5302
5439
 
5440
+ // src/daemon-log.ts
5441
+ init_fs();
5442
+ import { appendFileSync as appendFileSync2 } from "node:fs";
5443
+ function appendDaemonLogLine(level, msg) {
5444
+ try {
5445
+ appendFileSync2(
5446
+ cclensPath("daemon.log"),
5447
+ `${(/* @__PURE__ */ new Date()).toISOString()} ${level.toUpperCase()} ${msg}
5448
+ `,
5449
+ "utf8"
5450
+ );
5451
+ } catch {
5452
+ }
5453
+ }
5454
+
5303
5455
  // src/usage/backoff.ts
5304
5456
  var BASE_INTERVAL_MS = 5 * 60 * 1e3;
5305
5457
  var MAX_INTERVAL_MS = 60 * 60 * 1e3;
@@ -5375,8 +5527,8 @@ function skippedTrivialEnrichment(generatedAt) {
5375
5527
  retry_count: 0
5376
5528
  };
5377
5529
  }
5378
- function entryKey(sessionId, localDay) {
5379
- return `${sessionId}__${localDay}`;
5530
+ function entryKey(sessionId, localDay2) {
5531
+ return `${sessionId}__${localDay2}`;
5380
5532
  }
5381
5533
  function parseEntryKey(key) {
5382
5534
  const m = /^([^_]+(?:_[^_]+)*)__(\d{4}-\d{2}-\d{2})$/.exec(key);
@@ -5504,8 +5656,8 @@ function entriesDir() {
5504
5656
  entriesDirCached = envOverride ?? cclensPath("entries");
5505
5657
  return entriesDirCached;
5506
5658
  }
5507
- function pathFor(sessionId, localDay) {
5508
- return join4(entriesDir(), `${entryKey(sessionId, localDay)}.json`);
5659
+ function pathFor(sessionId, localDay2) {
5660
+ return join4(entriesDir(), `${entryKey(sessionId, localDay2)}.json`);
5509
5661
  }
5510
5662
  function tmpPathFor(final) {
5511
5663
  return `${final}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
@@ -5551,24 +5703,27 @@ function writeEntryPreservingEnrichment(fresh) {
5551
5703
  writeFileSync3(tmp, JSON.stringify(toWrite, null, 2), { encoding: "utf8" });
5552
5704
  renameSync2(tmp, final);
5553
5705
  }
5554
- function readEntry(sessionId, localDay) {
5555
- const p = pathFor(sessionId, localDay);
5706
+ function readEntry(sessionId, localDay2) {
5707
+ const p = pathFor(sessionId, localDay2);
5556
5708
  if (!existsSync3(p))
5557
5709
  return null;
5558
5710
  const raw = readFileSync5(p, "utf8");
5559
5711
  return JSON.parse(raw);
5560
5712
  }
5713
+ function entryExists(sessionId, localDay2) {
5714
+ return existsSync3(pathFor(sessionId, localDay2));
5715
+ }
5561
5716
  function listEntryKeys() {
5562
5717
  const dir = entriesDir();
5563
5718
  if (!existsSync3(dir))
5564
5719
  return [];
5565
5720
  return readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -".json".length));
5566
5721
  }
5567
- function listEntriesForDay(localDay) {
5722
+ function listEntriesForDay(localDay2) {
5568
5723
  const out = [];
5569
5724
  for (const key of listEntryKeys()) {
5570
5725
  const parsed = parseEntryKey(key);
5571
- if (!parsed || parsed.local_day !== localDay)
5726
+ if (!parsed || parsed.local_day !== localDay2)
5572
5727
  continue;
5573
5728
  const e = readEntry(parsed.session_id, parsed.local_day);
5574
5729
  if (e)
@@ -5577,1657 +5732,1888 @@ function listEntriesForDay(localDay) {
5577
5732
  return out;
5578
5733
  }
5579
5734
 
5580
- // src/team/push.ts
5581
- init_storage();
5582
- var SNAPSHOT_FRESHNESS_MS = 10 * 60 * 1e3;
5583
- var POST_TIMEOUT_MS = 15e3;
5584
- function readLatestUsageSnapshotForWire(filePath, nowMs = Date.now()) {
5585
- const raw = latestClaudeCodeSnapshot(filePath);
5586
- if (!raw) return null;
5587
- const capturedMs = Date.parse(raw.captured_at);
5588
- if (Number.isNaN(capturedMs)) return null;
5589
- if (nowMs - capturedMs > SNAPSHOT_FRESHNESS_MS) return null;
5590
- const toWire = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
5591
- return {
5592
- capturedAt: raw.captured_at,
5593
- fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
5594
- sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
5595
- sevenDayOpus: toWire(raw.seven_day_opus),
5596
- sevenDaySonnet: toWire(raw.seven_day_sonnet),
5597
- sevenDayOauthApps: toWire(raw.seven_day_oauth_apps),
5598
- sevenDayCowork: toWire(raw.seven_day_cowork),
5599
- extraUsage: raw.extra_usage ? {
5600
- isEnabled: raw.extra_usage.is_enabled,
5601
- monthlyLimitUsd: raw.extra_usage.monthly_limit,
5602
- usedCreditsUsd: raw.extra_usage.used_credits,
5603
- utilization: raw.extra_usage.utilization
5604
- } : null
5605
- };
5606
- }
5607
- function bucketToRollup(b) {
5608
- return {
5609
- day: b.date,
5610
- agentTimeMs: b.airTimeMs,
5611
- sessions: b.sessions,
5612
- toolCalls: b.toolCalls,
5613
- turns: b.turns,
5614
- tokens: { ...b.tokens }
5615
- };
5616
- }
5617
- function buildRollupsForRange(sessions, sinceDay) {
5618
- const startCounts = /* @__PURE__ */ new Map();
5619
- for (const s of sessions) {
5620
- const d = sessionDay(s);
5621
- if (d) startCounts.set(d, (startCounts.get(d) ?? 0) + 1);
5622
- }
5623
- return dailyActivity(sessions).filter((b) => !sinceDay || b.date >= sinceDay).map((b) => ({ ...bucketToRollup(b), uniqueSessions: startCounts.get(b.date) ?? 0 }));
5735
+ // src/perception/build-entries.ts
5736
+ init_dist();
5737
+ import { statSync, readFileSync as readFileSync6 } from "node:fs";
5738
+ import { basename, dirname as dirname3 } from "node:path";
5739
+
5740
+ // ../entries/dist/signals.js
5741
+ init_dist();
5742
+ var TEAMMATE_RE = /^<teammate-message\b/;
5743
+ var SKILL_LOAD_RE = /^Base directory for this skill:/;
5744
+ var SLASH_COMMAND_RE2 = /^<command-name>|^<local-command-stdout>/;
5745
+ function classifyUserInputSource(text) {
5746
+ if (!text)
5747
+ return "human";
5748
+ const stripped = stripFrameworkBoilerplate(text);
5749
+ if (!stripped)
5750
+ return "system_instruction";
5751
+ if (TEAMMATE_RE.test(stripped))
5752
+ return "teammate";
5753
+ if (SKILL_LOAD_RE.test(stripped))
5754
+ return "skill_load";
5755
+ if (SLASH_COMMAND_RE2.test(stripped))
5756
+ return "slash_command";
5757
+ return "human";
5624
5758
  }
5625
- function dayBoundsMs(day) {
5626
- const [y, m, d] = day.split("-").map(Number);
5759
+ var HAPPY_RE = /\b(?:yay|yaay|yass|woohoo|nice|great|love(?:ly)?|perfect|amazing|awesome)\s*!|!!!/gi;
5760
+ var SATISFIED_RE = /\b(?:thanks|thank you|looks good|lgtm|works|that works|all good|sounds good)\b/gi;
5761
+ var DISSATISFIED_RE = /\b(?:that'?s (?:not|wrong)|try again|no, (?:that|this)|not quite|incorrect)\b/gi;
5762
+ var FRUSTRATED_RE = /\b(?:broken|stop|why (?:did|are|would) you|give up|ugh|argh|wtf)\b/gi;
5763
+ function countSatisfactionSignals(text) {
5764
+ if (!text)
5765
+ return { happy: 0, satisfied: 0, dissatisfied: 0, frustrated: 0 };
5627
5766
  return {
5628
- startMs: new Date(y, m - 1, d, 0, 0, 0, 0).getTime(),
5629
- endMs: new Date(y, m - 1, d + 1, 0, 0, 0, 0).getTime()
5767
+ happy: (text.match(HAPPY_RE) ?? []).length,
5768
+ satisfied: (text.match(SATISFIED_RE) ?? []).length,
5769
+ dissatisfied: (text.match(DISSATISFIED_RE) ?? []).length,
5770
+ frustrated: (text.match(FRUSTRATED_RE) ?? []).length
5630
5771
  };
5631
5772
  }
5632
- function clipSegmentsToDay(segments, bounds) {
5633
- if (!segments || segments.length === 0) return [];
5773
+ var INSTRUCTION_RE = /\b(?:can you|please|let's|let us|i need|could you|would you)\s+([^.?!\n]{5,200})/gi;
5774
+ function extractUserInstructions(text) {
5775
+ if (!text)
5776
+ return [];
5777
+ INSTRUCTION_RE.lastIndex = 0;
5634
5778
  const out = [];
5635
- for (const seg of segments) {
5636
- const s = Math.max(seg.startMs, bounds.startMs);
5637
- const e = Math.min(seg.endMs, bounds.endMs);
5638
- if (e > s) out.push({ startMs: s, endMs: e });
5779
+ const seen = /* @__PURE__ */ new Set();
5780
+ let m;
5781
+ while ((m = INSTRUCTION_RE.exec(text)) !== null) {
5782
+ const phrase = m[1].trim().replace(/\s+/g, " ");
5783
+ const lc = phrase.toLowerCase();
5784
+ if (seen.has(lc))
5785
+ continue;
5786
+ seen.add(lc);
5787
+ out.push(phrase);
5788
+ if (out.length >= 5)
5789
+ break;
5639
5790
  }
5640
5791
  return out;
5641
5792
  }
5642
- function sessionTouchesDay(s, day) {
5643
- const bounds = dayBoundsMs(day);
5644
- if (s.activeSegments && s.activeSegments.length > 0) {
5645
- return s.activeSegments.some((seg) => seg.endMs > bounds.startMs && seg.startMs < bounds.endMs);
5793
+ function classifySubagentRole(sa) {
5794
+ const text = `${sa.description} ${sa.prompt_preview}`.toLowerCase();
5795
+ if (/\b(re-?review|review[s]?|verify|audit|spec[- ]review|code[- ]quality|code[- ]reuse|efficiency review|sanity check|quick test)\b/.test(text)) {
5796
+ return "reviewer";
5646
5797
  }
5647
- return sessionDay(s) === day;
5798
+ if (/\b(implement|build chunk|build task|initialize|create the|add the)\b/.test(text)) {
5799
+ return "implementer";
5800
+ }
5801
+ if (/\bexplore\b|\binventory\b|\bmap\b.*\b(codebase|repo|branch)\b|\baudit\b.*coverage/.test(text)) {
5802
+ return "explorer";
5803
+ }
5804
+ if (/\b(investigate|research|reverse[- ]engineer|study|analy[sz]e|look up|tell me|brief me on|find out)\b/.test(text)) {
5805
+ return "researcher";
5806
+ }
5807
+ if (/\b(env(?:ironment)? setup|configure|setup|bootstrap)\b/.test(text)) {
5808
+ return "env-setup";
5809
+ }
5810
+ if (/\b(polish|cleanup|fix|refactor)\b/.test(text)) {
5811
+ return "polish";
5812
+ }
5813
+ return "other";
5648
5814
  }
5649
- function buildRichRollupBlocks(day, daySessions, entries, privateProjects, resolveRepo) {
5650
- const bounds = dayBoundsMs(day);
5651
- const clippedSessions = [];
5652
- for (const s of daySessions) {
5653
- const clipped = clipSegmentsToDay(s.activeSegments, bounds);
5654
- if (clipped.length === 0) continue;
5655
- clippedSessions.push({ ...s, activeSegments: clipped });
5815
+ var STOCK_SUBAGENT_PREFIXES = /^(general-purpose|Explore|Plan|claude-code-guide|playwright-qa-verifier|statusline-setup|frontend-design:|code-review:|code-simplifier:|codex:|superpowers:)/;
5816
+ function isStockSubagentType(type) {
5817
+ return STOCK_SUBAGENT_PREFIXES.test(type);
5818
+ }
5819
+ function inferWorkingShape(entry) {
5820
+ if (entry.numbers.turn_count < 2)
5821
+ return null;
5822
+ const subagents = entry.subagents ?? [];
5823
+ const roles = subagents.map(classifySubagentRole);
5824
+ const reviewerCount = roles.filter((r) => r === "reviewer").length;
5825
+ const implementerCount = roles.filter((r) => r === "implementer").length;
5826
+ const explorerOrResearcher = roles.filter((r) => r === "explorer" || r === "researcher").length;
5827
+ const hasBackground = subagents.some((sa) => sa.background);
5828
+ if (implementerCount >= 2) {
5829
+ const chunkRefs = subagents.filter((sa) => /\b(chunk|task)\s*\d+/i.test(`${sa.description} ${sa.prompt_preview}`));
5830
+ if (chunkRefs.length >= 2)
5831
+ return "chunk-implementation";
5656
5832
  }
5657
- const bursts = computeBurstsFromSessions(clippedSessions);
5658
- const stats = summarizeBursts(bursts);
5659
- const repoForEntry = (e) => {
5660
- if (resolveRepo) {
5661
- const own = resolveRepo(e.project);
5662
- if (own) return own;
5663
- const counts = /* @__PURE__ */ new Map();
5664
- for (const d of e.edited_dirs ?? []) {
5665
- const r = resolveRepo(d);
5666
- if (r) counts.set(r, (counts.get(r) ?? 0) + 1);
5667
- }
5668
- const dominant = [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
5669
- if (dominant) return dominant;
5833
+ if (reviewerCount >= 3 && implementerCount === 0) {
5834
+ const lensSigs = /* @__PURE__ */ new Set();
5835
+ for (let i = 0; i < subagents.length; i++) {
5836
+ if (roles[i] !== "reviewer")
5837
+ continue;
5838
+ const sig = subagents[i].description.toLowerCase().replace(/[^a-z0-9 ]/g, "").split(/\s+/).filter((w) => w.length > 2).slice(0, 3).join("|");
5839
+ lensSigs.add(sig);
5670
5840
  }
5671
- return e.github_repos?.length === 1 ? e.github_repos[0] : null;
5672
- };
5673
- const sessionRepo = /* @__PURE__ */ new Map();
5674
- for (const e of entries) {
5675
- if (privateProjects.has(projectRepoName(e.project)) || privateProjects.has(e.project)) continue;
5676
- const r = repoForEntry(e);
5677
- if (r) sessionRepo.set(e.session_id, r);
5841
+ if (lensSigs.size >= 3)
5842
+ return "reviewer-triad";
5678
5843
  }
5679
- const projects = /* @__PURE__ */ new Map();
5680
- for (const s of clippedSessions) {
5681
- const canonical = canonicalProjectName(s.projectName);
5682
- const name = projectRepoName(s.projectName);
5683
- if (privateProjects.has(name) || privateProjects.has(canonical)) continue;
5684
- const repo = resolveRepo?.(canonical) ?? sessionRepo.get(s.id);
5685
- const key = repo ?? name;
5686
- const ms = s.activeSegments.reduce((sum, seg) => sum + (seg.endMs - seg.startMs), 0);
5687
- const cur = projects.get(key) ?? { project: name, agentTimeMs: 0, sessions: 0, ...repo ? { resolved: repo } : {} };
5688
- cur.agentTimeMs += ms;
5689
- cur.sessions += 1;
5690
- projects.set(key, cur);
5844
+ if (reviewerCount >= 2)
5845
+ return "spec-review-loop";
5846
+ if (explorerOrResearcher >= 2 || explorerOrResearcher >= 1 && implementerCount >= 1) {
5847
+ return "research-then-build";
5691
5848
  }
5692
- const projectRepos = /* @__PURE__ */ new Map();
5693
- const workingShapes = /* @__PURE__ */ new Map();
5694
- const skills = /* @__PURE__ */ new Map();
5695
- const subagents = /* @__PURE__ */ new Map();
5696
- let toolErrors = 0;
5697
- let brainstorm = 0;
5698
- let planMode = 0;
5699
- let prs = 0;
5700
- let commits = 0;
5701
- let pushes = 0;
5702
- let longCount = 0;
5703
- let longTotalMin = 0;
5704
- let longMaxSingleMin = 0;
5705
- for (const e of entries) {
5706
- if (privateProjects.has(projectRepoName(e.project)) || privateProjects.has(e.project)) continue;
5707
- const shape = e.signals?.working_shape ?? null;
5708
- if (shape) {
5709
- const cur = workingShapes.get(shape) ?? { sessions: 0, agentTimeMs: 0 };
5710
- cur.sessions += 1;
5711
- cur.agentTimeMs += Math.round(e.numbers.active_min * 6e4);
5712
- workingShapes.set(shape, cur);
5713
- }
5714
- for (const [name, count] of Object.entries(e.skills)) {
5715
- if (count > 0) skills.set(name, (skills.get(name) ?? 0) + 1);
5716
- }
5717
- for (const sa of e.subagents) {
5718
- subagents.set(sa.type, (subagents.get(sa.type) ?? 0) + 1);
5719
- }
5720
- toolErrors += e.numbers.tool_errors;
5721
- if (e.signals?.brainstorm_warmup) brainstorm += 1;
5722
- if (e.numbers.exit_plan_calls > 0) planMode += 1;
5723
- prs += e.numbers.prs;
5724
- commits += e.numbers.commits;
5725
- pushes += e.numbers.pushes;
5726
- if (e.github_repos?.length) {
5727
- const key = sessionRepo.get(e.session_id) ?? projectRepoName(e.project);
5728
- const set = projectRepos.get(key) ?? /* @__PURE__ */ new Set();
5729
- for (const r of e.github_repos) set.add(r);
5730
- projectRepos.set(key, set);
5731
- }
5732
- if (e.flags.includes("long_autonomous")) {
5733
- longCount += 1;
5734
- longTotalMin += e.numbers.active_min;
5735
- if (e.numbers.active_min > longMaxSingleMin) longMaxSingleMin = e.numbers.active_min;
5736
- }
5849
+ if (hasBackground && subagents.length >= 1)
5850
+ return "background-coordinated";
5851
+ if (subagents.length === 0) {
5852
+ const first = (entry.first_user || "").trim().toLowerCase();
5853
+ if (/^continue\b/.test(first) || first === "continue.")
5854
+ return "solo-continuation";
5855
+ const skills = Object.keys(entry.skills ?? {});
5856
+ if (skills.some((s) => /brainstorm|writing-plans|brainstorming/i.test(s)))
5857
+ return "solo-design";
5858
+ return "solo-build";
5737
5859
  }
5738
- return {
5739
- projects: Array.from(projects.entries()).map(([key, v]) => {
5740
- const extras = [...projectRepos.get(key) ?? []].filter((r) => r !== v.resolved);
5741
- const repos = v.resolved ? [v.resolved, ...extras] : extras;
5742
- return {
5743
- project: v.project,
5744
- agentTimeMs: v.agentTimeMs,
5745
- sessions: v.sessions,
5746
- ...repos.length ? { githubRepos: repos.slice(0, 5) } : {}
5747
- };
5748
- }).sort((a, b) => b.agentTimeMs - a.agentTimeMs),
5749
- workingShapes: Array.from(workingShapes.entries()).map(([shape, v]) => ({ shape, ...v })).sort((a, b) => b.sessions - a.sessions),
5750
- concurrencyPeak: stats.peakConcurrent,
5751
- parallelMinutes: Math.round(stats.totalParallelMs / 6e4),
5752
- // Wire schema requires integer minutes (active_min is a float).
5753
- longAutonomous: { count: longCount, totalMin: Math.round(longTotalMin), maxSingleMin: Math.round(longMaxSingleMin) },
5754
- toolErrors,
5755
- skillsLoaded: Array.from(skills.entries()).map(([name, sessions]) => ({ name, sessions })).sort((a, b) => b.sessions - a.sessions),
5756
- subagentsDispatched: Array.from(subagents.entries()).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count),
5757
- brainstormWarmupSessions: brainstorm,
5758
- planModeUsed: planMode,
5759
- prs,
5760
- commits,
5761
- pushes
5762
- };
5860
+ return "solo-build";
5763
5861
  }
5764
- function buildEnrichedExtras(entries) {
5765
- const outcomeMix = {};
5766
- const helpfulnessMix = {};
5767
- const goalMix = {};
5768
- for (const e of entries) {
5769
- if (e.enrichment.status !== "done") continue;
5770
- if (e.enrichment.outcome) {
5771
- outcomeMix[e.enrichment.outcome] = (outcomeMix[e.enrichment.outcome] ?? 0) + 1;
5772
- }
5773
- if (e.enrichment.claude_helpfulness) {
5774
- helpfulnessMix[e.enrichment.claude_helpfulness] = (helpfulnessMix[e.enrichment.claude_helpfulness] ?? 0) + 1;
5775
- }
5776
- for (const [cat, mins] of Object.entries(e.enrichment.goal_categories)) {
5777
- if (typeof mins === "number") goalMix[cat] = (goalMix[cat] ?? 0) + mins;
5778
- }
5862
+ function detectPromptFrames(text) {
5863
+ if (!text)
5864
+ return [];
5865
+ const out = [];
5866
+ if (/<teammate-message\b/i.test(text))
5867
+ out.push("teammate");
5868
+ if (/<task-notification\b/i.test(text))
5869
+ out.push("task-notification");
5870
+ if (/<local-command-caveat\b/i.test(text))
5871
+ out.push("local-command-caveat");
5872
+ if (/<command-(message|name)\b/i.test(text))
5873
+ out.push("slash-command");
5874
+ if (/\[Image #\d+\]/.test(text))
5875
+ out.push("image-attached");
5876
+ if (/\b(here'?s the (handoff|handover) prompt|session-close summary|wrap-up summary|all wrapped up|all captured\.)\b/i.test(text) || /^##? (Wrap-up|Session conclusion|Handoff)\b/m.test(text) || /^# Handoff:/m.test(text)) {
5877
+ out.push("handoff-prose");
5779
5878
  }
5780
- return { outcomeMix, helpfulnessMix, goalMix };
5781
- }
5782
- function buildRichBlocksForDay(day, daySessions, privateProjects, enrichmentOptIn, resolveRepo) {
5783
- const entries = listEntriesForDay(day);
5784
- if (entries.length === 0) return void 0;
5785
- const rich = buildRichRollupBlocks(day, daySessions, entries, privateProjects, resolveRepo);
5786
- const enriched = enrichmentOptIn ? buildEnrichedExtras(entries) : void 0;
5787
- return enriched ? { rich, enriched } : { rich };
5788
- }
5789
- function buildIngestPayload(inputs) {
5790
- const richRollup = inputs.rollup && inputs.richExtras ? { ...inputs.rollup, ...inputs.richExtras } : void 0;
5791
- return {
5792
- ingestId: randomUUID(),
5793
- observedAt: (/* @__PURE__ */ new Date()).toISOString(),
5794
- cliVersion: "0.13.1",
5795
- ...richRollup || inputs.enrichedExtras || inputs.artifactSignals ? { schemaVersion: RICH_ROLLUP_SCHEMA_VERSION } : {},
5796
- ...inputs.rollup ? { dailyRollup: inputs.rollup } : {},
5797
- ...richRollup ? { richRollup } : {},
5798
- ...inputs.enrichedExtras ? { enrichedExtras: inputs.enrichedExtras } : {},
5799
- ...inputs.artifactSignals ? { artifactSignals: inputs.artifactSignals } : {},
5800
- ...inputs.usageSnapshot ? { usageSnapshot: inputs.usageSnapshot } : {},
5801
- ...inputs.planTier ? { planTier: inputs.planTier } : {},
5802
- ...inputs.cyclePeaks ? { cyclePeaks: inputs.cyclePeaks } : {}
5803
- };
5804
- }
5805
- async function pushToTeamServer(config, payload) {
5806
- const res = await fetch(`${config.serverUrl}/api/ingest/metrics`, {
5807
- method: "POST",
5808
- signal: AbortSignal.timeout(POST_TIMEOUT_MS),
5809
- headers: {
5810
- "Content-Type": "application/json",
5811
- Authorization: `Bearer ${config.bearerToken}`
5812
- },
5813
- body: JSON.stringify(payload)
5814
- });
5815
- const body = await res.json().catch(() => null);
5816
- return { ok: res.ok, status: res.status, body };
5879
+ return out;
5817
5880
  }
5818
-
5819
- // src/team/git-remote.ts
5820
- import { existsSync as existsSync4, readFileSync as readFileSync6, statSync } from "node:fs";
5821
- import { dirname as dirname3, isAbsolute, join as join5, resolve } from "node:path";
5822
- var GITHUB_URL_RE = /github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/;
5823
- function parseGithubRemote(url) {
5824
- const m = url.trim().match(GITHUB_URL_RE);
5825
- return m ? `${m[1]}/${m[2]}`.toLowerCase() : null;
5881
+ var STOCK_SKILL_PREFIXES = /^(superpowers|mcp__|frontend-design|code-review|codex|claude-code-guide|claude-api):/i;
5882
+ function classifySkill(name) {
5883
+ if (name.startsWith("(ToolSearch:"))
5884
+ return "infra";
5885
+ if (STOCK_SKILL_PREFIXES.test(name))
5886
+ return "stock";
5887
+ if (name === "using-superpowers")
5888
+ return "stock";
5889
+ return "user";
5826
5890
  }
5827
- function originUrl(configText) {
5828
- let inOrigin = false;
5829
- for (const line of configText.split("\n")) {
5830
- const t = line.trim();
5831
- if (t.startsWith("[")) {
5832
- inOrigin = /^\[remote\s+"origin"\]/.test(t);
5833
- continue;
5834
- }
5835
- if (!inOrigin) continue;
5836
- const m = t.match(/^url\s*=\s*(.+)$/);
5837
- if (m) return m[1].trim();
5891
+ var EXTERNAL_REF_PATTERNS = [
5892
+ { kind: "linear-kip", re: /\bKIP-\d+\b/i },
5893
+ { kind: "github-issue-pr", re: /\b(issue|pr|pull request)\s*#\d+\b|github\.com\/[^\s]+\/(issues|pull)\/\d+/i },
5894
+ { kind: "branch-ref", re: /\b(branch|feat|fix|chore|refactor)[/:]\s*[\w./-]+|on `[\w./-]+`/i },
5895
+ { kind: "url", re: /https?:\/\/\S+/ }
5896
+ ];
5897
+ function detectExternalRef(text) {
5898
+ if (!text)
5899
+ return null;
5900
+ if (/<teammate-message|<task-notification|<local-command-caveat/i.test(text.slice(0, 400)))
5901
+ return null;
5902
+ for (const { kind, re } of EXTERNAL_REF_PATTERNS) {
5903
+ const m = text.match(re);
5904
+ if (m)
5905
+ return { kind, preview: text.slice(0, 160) };
5838
5906
  }
5839
5907
  return null;
5840
5908
  }
5841
- function gitConfigPathFor(dir) {
5842
- const dotGit = join5(dir, ".git");
5843
- const st = statSync(dotGit, { throwIfNoEntry: false });
5844
- if (!st) return null;
5845
- if (st.isDirectory()) return join5(dotGit, "config");
5846
- const m = readFileSync6(dotGit, "utf8").match(/^gitdir:\s*(.+)\s*$/m);
5847
- if (!m) return null;
5848
- const gd = isAbsolute(m[1]) ? m[1] : resolve(dir, m[1]);
5849
- const commondirFile = join5(gd, "commondir");
5850
- if (existsSync4(commondirFile)) {
5851
- const common = readFileSync6(commondirFile, "utf8").trim();
5852
- return join5(isAbsolute(common) ? common : resolve(gd, common), "config");
5853
- }
5854
- return join5(gd, "config");
5909
+ function bucketVerbosity(n) {
5910
+ if (n < 100)
5911
+ return "short";
5912
+ if (n < 500)
5913
+ return "medium";
5914
+ if (n < 2e3)
5915
+ return "long";
5916
+ return "very_long";
5855
5917
  }
5856
- function createRepoResolver() {
5857
- const cache = /* @__PURE__ */ new Map();
5858
- return (dir) => {
5859
- if (!dir || !isAbsolute(dir)) return null;
5860
- const hit = cache.get(dir);
5861
- if (hit !== void 0) return hit;
5862
- let repo = null;
5863
- try {
5864
- for (let d = dir; ; d = dirname3(d)) {
5865
- const config = gitConfigPathFor(d);
5866
- if (config && existsSync4(config)) {
5867
- const url = originUrl(readFileSync6(config, "utf8"));
5868
- repo = url ? parseGithubRemote(url) : null;
5869
- break;
5870
- }
5871
- if (d === dirname3(d)) break;
5872
- }
5873
- } catch {
5874
- repo = null;
5875
- }
5876
- cache.set(dir, repo);
5877
- return repo;
5918
+ function computeEntrySignals(entry) {
5919
+ const fu = entry.first_user || "";
5920
+ const verbosity = bucketVerbosity(fu.length);
5921
+ const ext = detectExternalRef(fu);
5922
+ const external_refs = ext ? [ext] : [];
5923
+ const skillNames = Object.keys(entry.skills ?? {});
5924
+ const brainstorm_warmup = skillNames.some((s) => /brainstorm|writing-plans/i.test(s));
5925
+ const fuTrim = fu.trim().toLowerCase();
5926
+ const frames = detectPromptFrames(fu);
5927
+ const continuation_kind = /^continue\b/.test(fuTrim) || fuTrim === "continue." ? "literal-continue" : frames.includes("handoff-prose") ? "handoff-prose" : "none";
5928
+ return {
5929
+ working_shape: inferWorkingShape(entry),
5930
+ prompt_frames: frames,
5931
+ subagent_roles: (entry.subagents ?? []).map(classifySubagentRole),
5932
+ verbosity,
5933
+ external_refs,
5934
+ brainstorm_warmup,
5935
+ continuation_kind
5878
5936
  };
5879
5937
  }
5880
5938
 
5881
- // src/team/queue.ts
5882
- init_fs();
5883
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, appendFileSync as appendFileSync2, statSync as statSync2, mkdirSync as mkdirSync5 } from "node:fs";
5884
- import { dirname as dirname4 } from "node:path";
5885
- var QUEUE_PATH = cclensPath("ingest-queue.jsonl");
5886
- var MAX_SIZE_BYTES = 10 * 1024 * 1024;
5887
- var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
5888
- function enqueuePayload(payload, queuePath = QUEUE_PATH) {
5889
- mkdirSync5(dirname4(queuePath), { recursive: true });
5890
- const entry = { payload, enqueuedAt: (/* @__PURE__ */ new Date()).toISOString() };
5891
- appendFileSync2(queuePath, JSON.stringify(entry) + "\n", "utf8");
5892
- try {
5893
- const { size } = statSync2(queuePath);
5894
- if (size > MAX_SIZE_BYTES) {
5895
- pruneOldest(queuePath);
5896
- }
5897
- } catch {
5898
- }
5939
+ // ../entries/dist/trivial.js
5940
+ function isTrivial(n) {
5941
+ return n.active_min < 1 && n.turn_count < 3 && n.tools_total === 0;
5899
5942
  }
5900
- function dequeuePayloads(queuePath = QUEUE_PATH) {
5901
- let lines;
5902
- try {
5903
- lines = readFileSync7(queuePath, "utf8").trim().split("\n").filter(Boolean);
5904
- } catch {
5905
- return [];
5906
- }
5907
- const now = Date.now();
5908
- const valid = [];
5909
- for (const line of lines) {
5910
- try {
5911
- const entry = JSON.parse(line);
5912
- if (now - new Date(entry.enqueuedAt).getTime() < MAX_AGE_MS) {
5913
- valid.push(entry);
5914
- }
5915
- } catch {
5916
- }
5917
- }
5918
- writeFileSync4(queuePath, "", "utf8");
5919
- return valid.map((e) => e.payload);
5920
- }
5921
- function pruneOldest(queuePath) {
5922
- let lines;
5923
- try {
5924
- lines = readFileSync7(queuePath, "utf8").trim().split("\n").filter(Boolean);
5925
- } catch {
5926
- return;
5927
- }
5928
- const keep = lines.slice(Math.floor(lines.length / 2));
5929
- writeFileSync4(queuePath, keep.join("\n") + "\n", "utf8");
5930
- }
5931
-
5932
- // src/team/backfill.ts
5933
- init_storage();
5934
- import { randomUUID as randomUUID2 } from "node:crypto";
5935
5943
 
5936
- // src/usage/profile.ts
5937
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
5938
- import { dirname as dirname5 } from "node:path";
5939
- var PROFILE_ENDPOINT = "https://api.anthropic.com/api/oauth/profile";
5940
- var BETA_HEADER2 = "oauth-2025-04-20";
5941
- var PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
5942
- function mapRateLimitTier(rateLimitTier) {
5943
- switch (rateLimitTier) {
5944
- case "default_claude_pro":
5945
- return "pro";
5946
- case "default_claude_max":
5947
- case "default_claude_max_5x":
5948
- return "pro-max";
5949
- case "default_claude_max_20x":
5950
- return "pro-max-20x";
5951
- default:
5952
- return "custom";
5953
- }
5954
- }
5955
- async function fetchProfile(nowMs = Date.now()) {
5956
- const creds = readOAuthCredentials();
5957
- if (!creds) return null;
5958
- if (creds.expiresAt - 6e4 <= nowMs) return null;
5959
- let res;
5960
- try {
5961
- res = await fetch(PROFILE_ENDPOINT, {
5962
- headers: {
5963
- Authorization: `Bearer ${creds.accessToken}`,
5964
- "anthropic-beta": BETA_HEADER2
5965
- }
5966
- });
5967
- } catch {
5968
- return null;
5969
- }
5970
- if (!res.ok) return null;
5971
- const body = await res.json().catch(() => null);
5972
- if (!body) return null;
5973
- const rateLimitTier = body.organization?.rate_limit_tier ?? null;
5944
+ // ../entries/dist/build.js
5945
+ init_analytics();
5946
+ function viewEvent(ev) {
5947
+ let rawType;
5948
+ if (ev.role === "user" || ev.role === "tool-result")
5949
+ rawType = "user";
5950
+ else if (ev.role === "agent" || ev.role === "agent-thinking" || ev.role === "tool-call")
5951
+ rawType = "assistant";
5952
+ else if (ev.rawType === "summary")
5953
+ rawType = "summary";
5954
+ else
5955
+ rawType = "system";
5956
+ const rawCwd = ev.raw?.cwd;
5974
5957
  return {
5975
- planTier: mapRateLimitTier(rateLimitTier),
5976
- rateLimitTier,
5977
- organizationType: body.organization?.organization_type ?? null
5958
+ rawType,
5959
+ content: ev.blocks ?? [],
5960
+ model: ev.model,
5961
+ msgId: ev.messageId,
5962
+ usage: ev.usage ? {
5963
+ input_tokens: ev.usage.input,
5964
+ output_tokens: ev.usage.output,
5965
+ cache_read_input_tokens: ev.usage.cacheRead,
5966
+ cache_creation_input_tokens: ev.usage.cacheWrite
5967
+ } : void 0,
5968
+ cwd: rawCwd
5978
5969
  };
5979
5970
  }
5980
- function readCachedProfile(cachePath) {
5981
- if (!existsSync5(cachePath)) return null;
5982
- try {
5983
- const raw = JSON.parse(readFileSync8(cachePath, "utf8"));
5984
- if (typeof raw.fetchedAtMs !== "number" || !raw.profile) return null;
5985
- return raw;
5986
- } catch {
5987
- return null;
5988
- }
5971
+ var IDLE_GAP_MS4 = 3 * 60 * 1e3;
5972
+ var INTERRUPT_RE2 = /\[request interrupted|interrupted by user/i;
5973
+ var PR_TITLE_RE = /--title\s+["']([^"']+)["']/;
5974
+ var BASH_CHAIN_SPLIT_RE = /\|\||&&|;|\|/;
5975
+ var SHELL_NOISE = /* @__PURE__ */ new Set(["sudo", "time", "nohup", "nice", "env", "exec"]);
5976
+ var IMAGE_BLOCK_RE = /\[Image:\s*source:\s*[^\]]+\]/g;
5977
+ function parseMs(iso) {
5978
+ if (!iso)
5979
+ return void 0;
5980
+ const ms = Date.parse(iso);
5981
+ return Number.isNaN(ms) ? void 0 : ms;
5989
5982
  }
5990
- function writeCachedProfile(cachePath, profile, nowMs = Date.now()) {
5991
- mkdirSync6(dirname5(cachePath), { recursive: true });
5992
- writeFileSync5(
5993
- cachePath,
5994
- JSON.stringify({ fetchedAtMs: nowMs, profile }, null, 2),
5995
- { mode: 384 }
5996
- );
5983
+ function trunc(s, n) {
5984
+ if (!s)
5985
+ return "";
5986
+ const one = s.replace(/\s+/g, " ").trim();
5987
+ return one.length <= n ? one : one.slice(0, n - 1) + "\u2026";
5997
5988
  }
5998
- async function getPlanTier(cachePath, nowMs = Date.now(), fetcher = fetchProfile) {
5999
- const cached = readCachedProfile(cachePath);
6000
- if (cached && nowMs - cached.fetchedAtMs < PROFILE_CACHE_TTL_MS) {
6001
- return cached.profile.planTier;
5989
+ function minimizeImages(s) {
5990
+ if (!s)
5991
+ return s;
5992
+ let counter = 0;
5993
+ return s.replace(IMAGE_BLOCK_RE, () => `[Image #${++counter}]`);
5994
+ }
5995
+ function bashVerb(cmd) {
5996
+ if (!cmd)
5997
+ return "?";
5998
+ let firstSeg = cmd.split(BASH_CHAIN_SPLIT_RE, 1)[0].trim();
5999
+ if (firstSeg.startsWith("cd ") || firstSeg === "cd") {
6000
+ const parts = cmd.split(/&&|;/, 3);
6001
+ if (parts.length >= 2)
6002
+ firstSeg = parts[1].trim();
6002
6003
  }
6003
- const fresh = await fetcher(nowMs);
6004
- if (!fresh) {
6005
- return cached?.profile.planTier ?? null;
6004
+ const tokens = firstSeg.split(/\s+/).filter(Boolean);
6005
+ while (tokens.length && SHELL_NOISE.has(tokens[0]))
6006
+ tokens.shift();
6007
+ if (!tokens.length)
6008
+ return "?";
6009
+ let first = tokens[0];
6010
+ if (first.includes("=") && !first.startsWith("/")) {
6011
+ tokens.shift();
6012
+ if (!tokens.length)
6013
+ return "?";
6014
+ first = tokens[0];
6006
6015
  }
6007
- writeCachedProfile(cachePath, fresh, nowMs);
6008
- return fresh.planTier;
6009
- }
6010
-
6011
- // src/team/backfill.ts
6012
- init_fs();
6013
- var USAGE_LOG = cclensPath("usage.jsonl");
6014
- var PROFILE_CACHE = cclensPath("profile.json");
6015
- var BATCH_SIZE = 500;
6016
- var noopLog = () => {
6017
- };
6018
- function rawToWire(raw) {
6019
- const win = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
6020
- return {
6021
- capturedAt: raw.captured_at,
6022
- fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
6023
- sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
6024
- sevenDayOpus: win(raw.seven_day_opus),
6025
- sevenDaySonnet: win(raw.seven_day_sonnet),
6026
- sevenDayOauthApps: win(raw.seven_day_oauth_apps),
6027
- sevenDayCowork: win(raw.seven_day_cowork),
6028
- extraUsage: raw.extra_usage ? {
6029
- isEnabled: raw.extra_usage.is_enabled,
6030
- monthlyLimitUsd: raw.extra_usage.monthly_limit,
6031
- usedCreditsUsd: raw.extra_usage.used_credits,
6032
- utilization: raw.extra_usage.utilization
6033
- } : null
6034
- };
6035
- }
6036
- function chunk(items, size) {
6037
- if (size <= 0) return [items];
6038
- const out = [];
6039
- for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
6040
- return out;
6041
- }
6042
- async function postBatch(config, snapshots, planTier) {
6043
- const payload = {
6044
- ingestId: `backfill-${randomUUID2()}`,
6045
- observedAt: (/* @__PURE__ */ new Date()).toISOString(),
6046
- snapshotHistory: snapshots,
6047
- ...planTier ? { planTier } : {}
6048
- };
6049
- const result = await pushToTeamServer(config, payload);
6050
- if (!result.ok) {
6051
- return { inserted: 0, skipped: snapshots.length, status: result.status };
6016
+ if (first.includes("/") && !first.startsWith("./")) {
6017
+ first = first.replace(/\/+$/, "").split("/").pop();
6052
6018
  }
6053
- const body = result.body;
6054
- if (!body?.snapshotHistory) {
6055
- throw new Error(
6056
- "team server accepted snapshotHistory but did not return a result block \u2014 older image, upgrade required"
6057
- );
6019
+ return first.replace(/^["']|["']$/g, "") || "?";
6020
+ }
6021
+ function firstTextBlock(blocks) {
6022
+ if (!Array.isArray(blocks))
6023
+ return "";
6024
+ for (const b of blocks) {
6025
+ if (b && typeof b === "object" && b.type === "text") {
6026
+ return String(b.text ?? "");
6027
+ }
6058
6028
  }
6059
- return {
6060
- inserted: body.snapshotHistory.inserted ?? 0,
6061
- skipped: body.snapshotHistory.skipped ?? 0,
6062
- status: result.status
6063
- };
6029
+ return "";
6064
6030
  }
6065
- function isAfterSince(snapshot, sinceCapturedAt) {
6066
- if (!sinceCapturedAt) return true;
6067
- const sinceMs = Date.parse(sinceCapturedAt);
6068
- if (Number.isNaN(sinceMs)) return true;
6069
- const capturedMs = Date.parse(snapshot.captured_at);
6070
- return !Number.isNaN(capturedMs) && capturedMs > sinceMs;
6031
+ function blockText(blocks) {
6032
+ if (!blocks)
6033
+ return "";
6034
+ return blocks.filter((b) => !!b && typeof b === "object" && b.type === "text" && typeof b.text === "string").map((b) => b.text).join(" ");
6071
6035
  }
6072
- function capturedAtMs(snapshot) {
6073
- const ms = Date.parse(snapshot.captured_at);
6074
- return Number.isNaN(ms) ? Number.MAX_SAFE_INTEGER : ms;
6036
+ function groupEventsByLocalDay(events) {
6037
+ const byDay = /* @__PURE__ */ new Map();
6038
+ for (const ev of events) {
6039
+ if (!ev.timestamp)
6040
+ continue;
6041
+ const ms = Date.parse(ev.timestamp);
6042
+ if (Number.isNaN(ms))
6043
+ continue;
6044
+ const day = toLocalDay(ms);
6045
+ let bucket = byDay.get(day);
6046
+ if (!bucket) {
6047
+ bucket = [];
6048
+ byDay.set(day, bucket);
6049
+ }
6050
+ bucket.push(ev);
6051
+ }
6052
+ return byDay;
6075
6053
  }
6076
- function latestCapturedAt(snapshots) {
6077
- let latest = null;
6078
- for (const s of snapshots) {
6079
- const ms = Date.parse(s.capturedAt);
6080
- if (Number.isNaN(ms)) continue;
6081
- if (!latest || ms > latest.ms) latest = { at: s.capturedAt, ms };
6082
- }
6083
- return latest?.at;
6054
+ function newTurn(startMs, userInput = "") {
6055
+ return {
6056
+ startMs,
6057
+ lastMs: startMs,
6058
+ eventTs: startMs !== void 0 ? [startMs] : [],
6059
+ userInput,
6060
+ toolsTotal: 0,
6061
+ toolMix: /* @__PURE__ */ new Map(),
6062
+ bashVerbs: /* @__PURE__ */ new Map(),
6063
+ skills: /* @__PURE__ */ new Map(),
6064
+ subagents: [],
6065
+ taskOps: 0,
6066
+ toolErrors: 0,
6067
+ consecSameToolMax: 0,
6068
+ consec: 0,
6069
+ endedInInterrupt: false,
6070
+ models: /* @__PURE__ */ new Map(),
6071
+ tokensIn: 0,
6072
+ tokensOut: 0,
6073
+ tokensCacheR: 0,
6074
+ tokensCacheW: 0,
6075
+ seenMsgIds: /* @__PURE__ */ new Set()
6076
+ };
6084
6077
  }
6085
- async function runTeamBackfill(log3 = noopLog, filePath = USAGE_LOG, configOverride, options = {}) {
6086
- const config = configOverride === void 0 ? readTeamConfig() : configOverride;
6087
- if (!config) {
6088
- return { paired: false, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
6089
- }
6090
- const raw = readSnapshots(filePath);
6091
- const claudeOnly = raw.filter(
6092
- (s) => (!s.agent || s.agent === "claude-code") && isAfterSince(s, options.sinceCapturedAt)
6093
- ).sort((a, b) => capturedAtMs(a) - capturedAtMs(b));
6094
- if (claudeOnly.length === 0) {
6095
- log3("info", "team backfill: no usage snapshots to send");
6096
- return { paired: true, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
6078
+ function closeTurn(t, closed, cwd) {
6079
+ const ts = [...t.eventTs].sort((a, b) => a - b);
6080
+ let active = 0;
6081
+ for (let i = 1; i < ts.length; i++) {
6082
+ const dt = ts[i] - ts[i - 1];
6083
+ if (dt < IDLE_GAP_MS4)
6084
+ active += dt;
6097
6085
  }
6098
- const wire = claudeOnly.map(rawToWire);
6099
- const batches = chunk(wire, BATCH_SIZE);
6100
- const planTier = await getPlanTier(PROFILE_CACHE).catch(() => null) ?? void 0;
6101
- let inserted = 0;
6102
- let skipped = 0;
6103
- let lastSnapshotAt;
6104
- for (let i = 0; i < batches.length; i++) {
6105
- const batch = batches[i];
6106
- try {
6107
- const result = await postBatch(config, batch, planTier);
6108
- if (result.status >= 400) {
6109
- log3("warn", `team backfill: batch ${i + 1}/${batches.length} failed (${result.status})`);
6110
- return {
6111
- paired: true,
6112
- sentSnapshots: wire.length,
6113
- insertedSnapshots: inserted,
6114
- skippedSnapshots: skipped,
6115
- batches: i,
6116
- lastSnapshotAt,
6117
- error: `HTTP ${result.status}`
6118
- };
6086
+ closed.push({
6087
+ startMs: t.startMs ?? 0,
6088
+ activeMs: active,
6089
+ userInput: t.userInput,
6090
+ firstAgentText: t.firstAgentText,
6091
+ lastAgentText: t.lastAgentText,
6092
+ toolsTotal: t.toolsTotal,
6093
+ toolMix: t.toolMix,
6094
+ bashVerbs: t.bashVerbs,
6095
+ skills: t.skills,
6096
+ subagents: t.subagents,
6097
+ taskOps: t.taskOps,
6098
+ toolErrors: t.toolErrors,
6099
+ consecSameToolMax: t.consecSameToolMax,
6100
+ endedInInterrupt: t.endedInInterrupt,
6101
+ models: t.models,
6102
+ totalTokens: t.tokensIn + t.tokensOut + t.tokensCacheR + t.tokensCacheW,
6103
+ cwd
6104
+ });
6105
+ }
6106
+ function aggregateDay(dayEvents, _sessionFallbackProject) {
6107
+ const prTitles = [];
6108
+ let commits = 0;
6109
+ let pushes = 0;
6110
+ const gitToolUseIds = /* @__PURE__ */ new Set();
6111
+ const githubRepos = /* @__PURE__ */ new Set();
6112
+ const editedDirCounts = /* @__PURE__ */ new Map();
6113
+ const modelTurns = /* @__PURE__ */ new Map();
6114
+ let totalToolErrors = 0;
6115
+ let totalInterrupts = 0;
6116
+ let exitPlanCalls = 0;
6117
+ let taskOpsTotal = 0;
6118
+ let firstUser;
6119
+ let finalAgent;
6120
+ const cwdCounts = /* @__PURE__ */ new Map();
6121
+ const closed = [];
6122
+ let cur;
6123
+ const allTs = [];
6124
+ let lastEventCwd;
6125
+ for (const ev of dayEvents) {
6126
+ const tsMs = parseMs(ev.timestamp);
6127
+ if (tsMs !== void 0)
6128
+ allTs.push(tsMs);
6129
+ const view = viewEvent(ev);
6130
+ const rawType = view.rawType;
6131
+ const msg = { content: view.content, id: view.msgId, model: view.model, usage: view.usage };
6132
+ const content = view.content;
6133
+ const eventCwd = view.cwd;
6134
+ if (eventCwd)
6135
+ lastEventCwd = eventCwd;
6136
+ if (rawType === "user") {
6137
+ const isToolResultOnly = Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
6138
+ if (!isToolResultOnly) {
6139
+ const text = firstTextBlock(content) || (typeof content === "string" ? content : "");
6140
+ if (cur) {
6141
+ if (text && INTERRUPT_RE2.test(text)) {
6142
+ cur.endedInInterrupt = true;
6143
+ totalInterrupts++;
6144
+ }
6145
+ closeTurn(cur, closed, eventCwd);
6146
+ }
6147
+ cur = newTurn(tsMs, text);
6148
+ if (!firstUser && text && classifyUserInputSource(text) === "human") {
6149
+ firstUser = text;
6150
+ }
6151
+ } else {
6152
+ if (Array.isArray(content)) {
6153
+ for (const c of content) {
6154
+ if (!c || typeof c !== "object" || c.type !== "tool_result")
6155
+ continue;
6156
+ if (c.is_error) {
6157
+ totalToolErrors++;
6158
+ if (cur)
6159
+ cur.toolErrors++;
6160
+ }
6161
+ const tuId = c.tool_use_id;
6162
+ if (tuId && gitToolUseIds.has(tuId)) {
6163
+ const rc = c.content;
6164
+ const text = typeof rc === "string" ? rc : Array.isArray(rc) ? rc.map((b) => b && typeof b === "object" && typeof b.text === "string" ? b.text : "").join("\n") : "";
6165
+ for (const m of text.matchAll(/(?<![.\w])github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git\b|[/\s"'),]|$)/g)) {
6166
+ if (m[1].toLowerCase() === "user-attachments")
6167
+ continue;
6168
+ githubRepos.add(`${m[1]}/${m[2]}`.toLowerCase());
6169
+ }
6170
+ }
6171
+ }
6172
+ }
6173
+ if (cur && tsMs !== void 0) {
6174
+ cur.eventTs.push(tsMs);
6175
+ cur.lastMs = tsMs;
6176
+ }
6177
+ }
6178
+ } else if (rawType === "assistant") {
6179
+ if (cur && tsMs !== void 0) {
6180
+ cur.eventTs.push(tsMs);
6181
+ cur.lastMs = tsMs;
6182
+ }
6183
+ const model = msg.model;
6184
+ if (model) {
6185
+ modelTurns.set(model, (modelTurns.get(model) ?? 0) + 1);
6186
+ if (cur)
6187
+ cur.models.set(model, (cur.models.get(model) ?? 0) + 1);
6188
+ }
6189
+ const msgId = msg.id;
6190
+ const usage = msg.usage;
6191
+ if (cur && msgId && !cur.seenMsgIds.has(msgId) && usage) {
6192
+ cur.seenMsgIds.add(msgId);
6193
+ cur.tokensIn += usage.input_tokens ?? 0;
6194
+ cur.tokensOut += usage.output_tokens ?? 0;
6195
+ cur.tokensCacheR += usage.cache_read_input_tokens ?? 0;
6196
+ cur.tokensCacheW += usage.cache_creation_input_tokens ?? 0;
6197
+ }
6198
+ if (Array.isArray(content)) {
6199
+ for (const c of content) {
6200
+ if (!c || typeof c !== "object")
6201
+ continue;
6202
+ const ct = c;
6203
+ if (ct.type === "text" && ct.text) {
6204
+ finalAgent = ct.text;
6205
+ if (cur) {
6206
+ if (cur.firstAgentText === void 0)
6207
+ cur.firstAgentText = ct.text;
6208
+ cur.lastAgentText = ct.text;
6209
+ }
6210
+ } else if (ct.type === "tool_use") {
6211
+ const name = ct.name ?? "unknown";
6212
+ if (cur) {
6213
+ cur.toolsTotal++;
6214
+ cur.toolMix.set(name, (cur.toolMix.get(name) ?? 0) + 1);
6215
+ if (name === cur.lastTool)
6216
+ cur.consec++;
6217
+ else {
6218
+ cur.lastTool = name;
6219
+ cur.consec = 1;
6220
+ }
6221
+ if (cur.consec > cur.consecSameToolMax)
6222
+ cur.consecSameToolMax = cur.consec;
6223
+ if (name === "Skill") {
6224
+ const sk = String(ct.input?.skill ?? "unknown");
6225
+ cur.skills.set(sk, (cur.skills.get(sk) ?? 0) + 1);
6226
+ }
6227
+ if (name === "Agent") {
6228
+ const inp = ct.input ?? {};
6229
+ cur.subagents.push({
6230
+ type: String(inp.subagent_type ?? "general-purpose"),
6231
+ description: trunc(String(inp.description ?? ""), 80),
6232
+ prompt_preview: trunc(String(inp.prompt ?? ""), 240),
6233
+ background: Boolean(inp.run_in_background)
6234
+ });
6235
+ }
6236
+ if (name === "ExitPlanMode")
6237
+ exitPlanCalls++;
6238
+ if (name === "TodoWrite" || name === "TaskCreate" || name === "TaskUpdate") {
6239
+ cur.taskOps++;
6240
+ taskOpsTotal++;
6241
+ }
6242
+ }
6243
+ if (name === "Bash") {
6244
+ const cmd = String(ct.input?.command ?? "");
6245
+ if (cur)
6246
+ cur.bashVerbs.set(bashVerb(cmd), (cur.bashVerbs.get(bashVerb(cmd)) ?? 0) + 1);
6247
+ if (/\bgh\s+pr\s+create\b/.test(cmd)) {
6248
+ const m = cmd.match(PR_TITLE_RE);
6249
+ prTitles.push(m?.[1] ?? cmd.slice(0, 120));
6250
+ }
6251
+ if (/\bgit\s+commit\b/.test(cmd))
6252
+ commits++;
6253
+ if (/\bgit\s+push\b/.test(cmd))
6254
+ pushes++;
6255
+ if (ct.id && /\bgit\s+push\b|\bgh\s+pr\s+/.test(cmd))
6256
+ gitToolUseIds.add(ct.id);
6257
+ }
6258
+ if (eventCwd && (name === "Bash" || name === "Edit" || name === "Write" || name === "Read")) {
6259
+ cwdCounts.set(eventCwd, (cwdCounts.get(eventCwd) ?? 0) + 1);
6260
+ }
6261
+ if (name === "Edit" || name === "Write" || name === "MultiEdit" || name === "NotebookEdit") {
6262
+ const fp = String(ct.input?.file_path ?? ct.input?.notebook_path ?? "");
6263
+ const slash = fp.lastIndexOf("/");
6264
+ if (slash > 0) {
6265
+ const dir = fp.slice(0, slash);
6266
+ editedDirCounts.set(dir, (editedDirCounts.get(dir) ?? 0) + 1);
6267
+ }
6268
+ }
6269
+ }
6270
+ }
6119
6271
  }
6120
- inserted += result.inserted;
6121
- skipped += result.skipped;
6122
- lastSnapshotAt = latestCapturedAt(batch) ?? lastSnapshotAt;
6123
- } catch (err) {
6124
- log3("warn", `team backfill: batch ${i + 1}/${batches.length} error: ${err.message}`);
6125
- return {
6126
- paired: true,
6127
- sentSnapshots: wire.length,
6128
- insertedSnapshots: inserted,
6129
- skippedSnapshots: skipped,
6130
- batches: i,
6131
- lastSnapshotAt,
6132
- error: err.message
6133
- };
6134
6272
  }
6135
6273
  }
6136
- log3(
6137
- "info",
6138
- `team backfill: ${inserted} new, ${skipped} already-known across ${batches.length} batch${batches.length === 1 ? "" : "es"}`
6139
- );
6274
+ if (cur)
6275
+ closeTurn(cur, closed, lastEventCwd);
6276
+ const tsSorted = [...allTs].sort((a, b) => a - b);
6277
+ let activeMs2 = 0;
6278
+ for (let i = 1; i < tsSorted.length; i++) {
6279
+ const dt = tsSorted[i] - tsSorted[i - 1];
6280
+ if (dt < IDLE_GAP_MS4)
6281
+ activeMs2 += dt;
6282
+ }
6283
+ const tsSortedStrings = dayEvents.map((e) => e.timestamp).filter((t) => !!t).sort();
6284
+ const start_iso = tsSortedStrings[0] ?? "";
6285
+ const end_iso = tsSortedStrings[tsSortedStrings.length - 1] ?? "";
6140
6286
  return {
6141
- paired: true,
6142
- sentSnapshots: wire.length,
6143
- insertedSnapshots: inserted,
6144
- skippedSnapshots: skipped,
6145
- batches: batches.length,
6146
- lastSnapshotAt
6287
+ closed,
6288
+ firstUser,
6289
+ finalAgent,
6290
+ prTitles,
6291
+ commits,
6292
+ pushes,
6293
+ githubRepos: [...githubRepos].slice(0, 5),
6294
+ editedDirs: [...editedDirCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([dir]) => dir),
6295
+ modelTurns,
6296
+ totalToolErrors,
6297
+ totalInterrupts,
6298
+ exitPlanCalls,
6299
+ taskOpsTotal,
6300
+ cwdCounts,
6301
+ start_iso,
6302
+ end_iso,
6303
+ activeMs: activeMs2
6147
6304
  };
6148
6305
  }
6149
-
6150
- // src/team/last-push.ts
6151
- init_fs();
6152
- import { writeFileSync as writeFileSync6, unlinkSync as unlinkSync2, mkdirSync as mkdirSync7 } from "node:fs";
6153
- import { join as join6 } from "node:path";
6154
- var LAST_PUSH_FILE = "team-last-push.json";
6155
- function write(record, dir) {
6156
- const d = dir ?? cclensHome();
6157
- mkdirSync7(d, { recursive: true });
6158
- writeFileSync6(join6(d, LAST_PUSH_FILE), JSON.stringify(record, null, 2), { mode: 384 });
6159
- }
6160
- function writeLastPushSuccess(payload, dir) {
6161
- write({ pushedAt: (/* @__PURE__ */ new Date()).toISOString(), ok: true, payload }, dir);
6306
+ function buildTopTools(closed) {
6307
+ const toolMix = /* @__PURE__ */ new Map();
6308
+ const bashVerbsAll = /* @__PURE__ */ new Map();
6309
+ for (const t of closed) {
6310
+ for (const [k, v] of t.toolMix)
6311
+ toolMix.set(k, (toolMix.get(k) ?? 0) + v);
6312
+ for (const [k, v] of t.bashVerbs)
6313
+ bashVerbsAll.set(k, (bashVerbsAll.get(k) ?? 0) + v);
6314
+ }
6315
+ const top = [...toolMix.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
6316
+ return top.map(([name, count]) => {
6317
+ if (name === "Bash" && bashVerbsAll.size > 0) {
6318
+ const sub = [...bashVerbsAll.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([v, c]) => c > 1 ? `${v}\xD7${c}` : v).join(", ");
6319
+ return `Bash\xD7${count} (${sub})`;
6320
+ }
6321
+ return count > 1 ? `${name}\xD7${count}` : name;
6322
+ });
6162
6323
  }
6163
- function writeLastPushFailure(payload, error, dir) {
6164
- write({ pushedAt: (/* @__PURE__ */ new Date()).toISOString(), ok: false, payload, error }, dir);
6324
+ function buildEntries(sessionDetail) {
6325
+ const byDay = groupEventsByLocalDay(sessionDetail.events);
6326
+ const entries = [];
6327
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
6328
+ const sessionFallbackProject = canonicalProjectName(sessionDetail.projectName ?? "");
6329
+ for (const [local_day, dayEvents] of byDay) {
6330
+ if (dayEvents.length === 0)
6331
+ continue;
6332
+ const agg = aggregateDay(dayEvents, sessionFallbackProject);
6333
+ const { closed } = agg;
6334
+ const turnCount = closed.length;
6335
+ const toolsTotal = closed.reduce((s, t) => s + t.toolsTotal, 0);
6336
+ const tokensTotal = closed.reduce((s, t) => s + t.totalTokens, 0);
6337
+ const subagentCalls = closed.reduce((s, t) => s + t.subagents.length, 0);
6338
+ const skillsMap = /* @__PURE__ */ new Map();
6339
+ for (const t of closed) {
6340
+ for (const [k, v] of t.skills)
6341
+ skillsMap.set(k, (skillsMap.get(k) ?? 0) + v);
6342
+ }
6343
+ const skillCalls = [...skillsMap.values()].reduce((a, b) => a + b, 0);
6344
+ const taskOps = agg.taskOpsTotal;
6345
+ const interrupts = agg.totalInterrupts;
6346
+ const toolErrors = agg.totalToolErrors;
6347
+ const consecMax = Math.max(0, ...closed.map((t) => t.consecSameToolMax));
6348
+ const exitPlanCalls = agg.exitPlanCalls;
6349
+ const prs = agg.prTitles.length;
6350
+ const commits = agg.commits;
6351
+ const pushes = agg.pushes;
6352
+ const activeMin = Math.round(agg.activeMs / 6e3) / 10;
6353
+ const numbers = {
6354
+ active_min: activeMin,
6355
+ turn_count: turnCount,
6356
+ tools_total: toolsTotal,
6357
+ subagent_calls: subagentCalls,
6358
+ skill_calls: skillCalls,
6359
+ task_ops: taskOps,
6360
+ interrupts,
6361
+ tool_errors: toolErrors,
6362
+ consec_same_tool_max: consecMax,
6363
+ exit_plan_calls: exitPlanCalls,
6364
+ prs,
6365
+ commits,
6366
+ pushes,
6367
+ tokens_total: tokensTotal
6368
+ };
6369
+ const longestTurnActiveMs = Math.max(0, ...closed.map((t) => t.activeMs));
6370
+ const flags = [];
6371
+ if (interrupts >= 3)
6372
+ flags.push("interrupt_heavy");
6373
+ if (toolErrors >= 20)
6374
+ flags.push("high_errors");
6375
+ if (consecMax >= 8)
6376
+ flags.push("loop_suspected");
6377
+ if (agg.activeMs < 5 * 6e4 && prs >= 1)
6378
+ flags.push("fast_ship");
6379
+ if (exitPlanCalls > 0)
6380
+ flags.push("plan_used");
6381
+ const subagentTurns = closed.filter((t) => t.subagents.length >= 1).length;
6382
+ if (subagentTurns >= 3)
6383
+ flags.push("orchestrated");
6384
+ if (longestTurnActiveMs >= 20 * 6e4 && interrupts === 0)
6385
+ flags.push("long_autonomous");
6386
+ const primaryModel = [...agg.modelTurns.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]?.[0] ?? null;
6387
+ const modelMix = Object.fromEntries(agg.modelTurns);
6388
+ const firstUserRaw = agg.firstUser ?? "";
6389
+ const first_user = trunc(minimizeImages(firstUserRaw), 400);
6390
+ const finalAgentRaw = agg.finalAgent ?? "";
6391
+ const final_agent = trunc(minimizeImages(finalAgentRaw), 400);
6392
+ let project = sessionFallbackProject;
6393
+ if (agg.cwdCounts.size > 0) {
6394
+ const best = [...agg.cwdCounts.entries()].sort((a, b) => b[1] - a[1])[0];
6395
+ project = canonicalProjectName(best[0]);
6396
+ }
6397
+ const top_tools = buildTopTools(closed);
6398
+ const skills = Object.fromEntries(skillsMap);
6399
+ const subagents = closed.flatMap((t) => t.subagents);
6400
+ const humanText = dayEvents.filter((ev) => {
6401
+ const v = viewEvent(ev);
6402
+ if (v.rawType !== "user")
6403
+ return false;
6404
+ const content = v.content;
6405
+ const isToolResultOnly = content.length > 0 && content.every((c) => c.type === "tool_result");
6406
+ if (isToolResultOnly)
6407
+ return false;
6408
+ const text = blockText(ev.blocks) || ev.preview || "";
6409
+ return classifyUserInputSource(text) === "human";
6410
+ }).map((ev) => blockText(ev.blocks) || ev.preview || "").join("\n");
6411
+ const satisfaction_signals = countSatisfactionSignals(humanText);
6412
+ const user_input_sources = {
6413
+ human: 0,
6414
+ teammate: 0,
6415
+ skill_load: 0,
6416
+ slash_command: 0,
6417
+ system_instruction: 0
6418
+ };
6419
+ for (const ev of dayEvents) {
6420
+ const v = viewEvent(ev);
6421
+ if (v.rawType !== "user")
6422
+ continue;
6423
+ const content = v.content;
6424
+ const isToolResultOnly = content.length > 0 && content.every((c) => c.type === "tool_result");
6425
+ if (isToolResultOnly)
6426
+ continue;
6427
+ const text = blockText(ev.blocks) || ev.preview || "";
6428
+ const src = classifyUserInputSource(text);
6429
+ user_input_sources[src]++;
6430
+ }
6431
+ const pendingUserInstructions = extractUserInstructions(humanText).slice(0, 5);
6432
+ const trivial = isTrivial(numbers);
6433
+ const entry = {
6434
+ version: CURRENT_ENTRY_SCHEMA_VERSION,
6435
+ agent: sessionDetail.agent,
6436
+ session_id: sessionDetail.id,
6437
+ local_day,
6438
+ project,
6439
+ start_iso: agg.start_iso,
6440
+ end_iso: agg.end_iso,
6441
+ numbers,
6442
+ flags,
6443
+ primary_model: primaryModel,
6444
+ model_mix: modelMix,
6445
+ first_user,
6446
+ final_agent,
6447
+ pr_titles: agg.prTitles,
6448
+ ...agg.githubRepos.length ? { github_repos: agg.githubRepos } : {},
6449
+ ...agg.editedDirs.length ? { edited_dirs: agg.editedDirs } : {},
6450
+ top_tools,
6451
+ skills,
6452
+ subagents,
6453
+ satisfaction_signals,
6454
+ user_input_sources,
6455
+ enrichment: trivial ? skippedTrivialEnrichment(generatedAt) : { ...pendingEnrichment(), user_instructions: pendingUserInstructions },
6456
+ generated_at: generatedAt,
6457
+ source_jsonl: sessionDetail.filePath ?? "",
6458
+ source_checkpoint: { byte_offset: 0, last_event_ts: agg.end_iso || null }
6459
+ };
6460
+ entry.signals = computeEntrySignals(entry);
6461
+ entries.push(entry);
6462
+ }
6463
+ entries.sort((a, b) => a.local_day.localeCompare(b.local_day));
6464
+ return entries;
6165
6465
  }
6166
6466
 
6167
- // src/team/commands.ts
6168
- async function dispatchCommand(command, config, log3) {
6169
- switch (command.type) {
6170
- case "backfill-activity":
6171
- return runActivityBackfill(command, config, log3);
6172
- default:
6173
- return {
6174
- id: command.id,
6175
- ok: false,
6176
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6177
- error: `Unknown command type: ${command.type}`
6178
- };
6467
+ // src/perception/build-entries.ts
6468
+ function decodeProjectDirName(projectDir) {
6469
+ if (projectDir.startsWith("-")) {
6470
+ return "/" + projectDir.slice(1).replace(/-/g, "/");
6179
6471
  }
6472
+ return projectDir.replace(/-/g, "/");
6180
6473
  }
6181
- async function runActivityBackfill(command, config, log3) {
6182
- const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
6183
- const { toLocalDay: toLocalDay3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
6184
- const { probeArtifactSignals: probeArtifactSignals2 } = await Promise.resolve().then(() => (init_file_probe(), file_probe_exports));
6185
- const days = command.params.days;
6186
- const todayMs = Date.now();
6187
- const targetDayMs = todayMs - (days - 1) * 24 * 60 * 60 * 1e3;
6188
- const targetDay = toLocalDay3(targetDayMs);
6189
- log3("info", `command ${command.id}: backfill-activity from ${targetDay} (${days} days)`);
6190
- let sessions;
6191
- try {
6192
- sessions = await listSessions2({ limit: 1e4 });
6193
- } catch (err) {
6194
- return {
6195
- id: command.id,
6196
- ok: false,
6197
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6198
- error: `Failed to read sessions: ${err.message}`
6199
- };
6200
- }
6201
- const rollups = buildRollupsForRange(sessions, targetDay);
6202
- if (rollups.length === 0) {
6203
- return {
6204
- id: command.id,
6205
- ok: true,
6206
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6207
- summary: { pushed: 0, fromDay: targetDay }
6208
- };
6209
- }
6210
- const privateProjects = new Set(config.privateProjects ?? []);
6211
- const enrichmentOptIn = !!config.enrichmentOptIn;
6212
- const resolveRepo = createRepoResolver();
6213
- let pushed = 0;
6214
- for (const rollup of rollups) {
6215
- const daySessions = sessions.filter((s) => sessionTouchesDay(s, rollup.day));
6216
- const richBlocks = buildRichBlocksForDay(rollup.day, daySessions, privateProjects, enrichmentOptIn, resolveRepo);
6217
- let artifactSignals = null;
6474
+ function buildEntriesForFile(filePath) {
6475
+ const stat = statSync(filePath);
6476
+ const raw = readFileSync6(filePath, "utf8");
6477
+ const rawLines = raw.split("\n").filter(Boolean).map((l) => {
6218
6478
  try {
6219
- artifactSignals = probeArtifactSignals2({ day: rollup.day, extraRoots: [process.cwd()] });
6479
+ return JSON.parse(l);
6220
6480
  } catch {
6481
+ return null;
6221
6482
  }
6222
- const payload = buildIngestPayload({
6223
- rollup,
6224
- richExtras: richBlocks?.rich,
6225
- enrichedExtras: richBlocks?.enriched,
6226
- artifactSignals: artifactSignals ?? void 0
6227
- });
6228
- const result = await pushToTeamServer(config, payload);
6229
- if (!result.ok) {
6230
- return {
6231
- id: command.id,
6232
- ok: false,
6233
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6234
- error: `Push failed on ${rollup.day} (HTTP ${result.status}); pushed ${pushed}/${rollups.length} before failing`
6235
- };
6236
- }
6237
- pushed++;
6483
+ }).filter((x) => x !== null);
6484
+ if (rawLines.length === 0) return null;
6485
+ const { meta, events } = parseTranscript(rawLines);
6486
+ const fileName = basename(filePath);
6487
+ const projectDir = basename(dirname3(filePath));
6488
+ const sessionId = fileName.replace(/\.jsonl$/, "");
6489
+ const sd = {
6490
+ ...meta,
6491
+ id: sessionId,
6492
+ filePath,
6493
+ projectDir,
6494
+ projectName: meta.cwd ?? decodeProjectDirName(projectDir),
6495
+ events
6496
+ };
6497
+ const built = buildEntries(sd);
6498
+ for (const e of built) {
6499
+ e.source_checkpoint.byte_offset = stat.size;
6238
6500
  }
6239
- log3("info", `command ${command.id}: backfill-activity ok \u2014 pushed ${pushed} day${pushed === 1 ? "" : "s"}`);
6501
+ return built;
6502
+ }
6503
+
6504
+ // src/team/push.ts
6505
+ init_storage();
6506
+ var SNAPSHOT_FRESHNESS_MS = 10 * 60 * 1e3;
6507
+ var POST_TIMEOUT_MS = 15e3;
6508
+ function readLatestUsageSnapshotForWire(filePath, nowMs = Date.now()) {
6509
+ const raw = latestClaudeCodeSnapshot(filePath);
6510
+ if (!raw) return null;
6511
+ const capturedMs = Date.parse(raw.captured_at);
6512
+ if (Number.isNaN(capturedMs)) return null;
6513
+ if (nowMs - capturedMs > SNAPSHOT_FRESHNESS_MS) return null;
6514
+ const toWire = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
6240
6515
  return {
6241
- id: command.id,
6242
- ok: true,
6243
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6244
- summary: { pushed, fromDay: targetDay }
6516
+ capturedAt: raw.captured_at,
6517
+ fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
6518
+ sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
6519
+ sevenDayOpus: toWire(raw.seven_day_opus),
6520
+ sevenDaySonnet: toWire(raw.seven_day_sonnet),
6521
+ sevenDayOauthApps: toWire(raw.seven_day_oauth_apps),
6522
+ sevenDayCowork: toWire(raw.seven_day_cowork),
6523
+ extraUsage: raw.extra_usage ? {
6524
+ isEnabled: raw.extra_usage.is_enabled,
6525
+ monthlyLimitUsd: raw.extra_usage.monthly_limit,
6526
+ usedCreditsUsd: raw.extra_usage.used_credits,
6527
+ utilization: raw.extra_usage.utilization
6528
+ } : null
6245
6529
  };
6246
6530
  }
6247
-
6248
- // src/team/sync.ts
6249
- init_fs();
6250
- var inFlightCommands = /* @__PURE__ */ new Set();
6251
- var USAGE_LOG2 = cclensPath("usage.jsonl");
6252
- var PROFILE_CACHE2 = cclensPath("profile.json");
6253
- var noopLog2 = () => {
6254
- };
6255
- async function runTeamSync(log3 = noopLog2, configOverride, options = {}) {
6256
- const config = configOverride === void 0 ? readTeamConfig() : configOverride;
6257
- if (!config) return { paired: false, pushed: 0, queued: 0, queuedDrained: 0 };
6258
- try {
6259
- const { listSessions: listSessions2, loadCalibrationCurve: loadCalibrationCurve2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
6260
- const { toLocalDay: toLocalDay3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
6261
- const today = toLocalDay3(Date.now());
6262
- let nextConfig = { ...config };
6263
- const persistConfig = (patch) => {
6264
- nextConfig = { ...nextConfig, ...patch };
6265
- writeTeamConfig(nextConfig);
6266
- };
6267
- const collectedCommands = /* @__PURE__ */ new Map();
6268
- const collectCommands = (commands) => {
6269
- if (!commands) return;
6270
- for (const cmd of commands) {
6271
- if (!collectedCommands.has(cmd.id)) collectedCommands.set(cmd.id, cmd);
6272
- }
6273
- };
6274
- const dispatchAndReport = async () => {
6275
- try {
6276
- if (collectedCommands.size === 0) return;
6277
- const results = [];
6278
- for (const cmd of collectedCommands.values()) {
6279
- if (inFlightCommands.has(cmd.id)) continue;
6280
- inFlightCommands.add(cmd.id);
6281
- try {
6282
- const result = await dispatchCommand(cmd, config, log3);
6283
- results.push(result);
6284
- } finally {
6285
- inFlightCommands.delete(cmd.id);
6286
- }
6287
- }
6288
- if (results.length === 0) return;
6289
- const resultsPayload = {
6290
- ingestId: randomUUID3(),
6291
- observedAt: (/* @__PURE__ */ new Date()).toISOString(),
6292
- commandResults: results
6293
- };
6294
- const r = await pushToTeamServer(config, resultsPayload);
6295
- if (!r.ok) {
6296
- log3("warn", `team commandResults push failed (${r.status}); will retry on next sync`);
6297
- } else {
6298
- log3("info", `team commandResults push ok: ${results.length} result${results.length === 1 ? "" : "s"}`);
6299
- }
6300
- } catch (err) {
6301
- log3("warn", `team command dispatch error: ${err.message}`);
6531
+ function bucketToRollup(b) {
6532
+ return {
6533
+ day: b.date,
6534
+ agentTimeMs: b.airTimeMs,
6535
+ sessions: b.sessions,
6536
+ toolCalls: b.toolCalls,
6537
+ turns: b.turns,
6538
+ tokens: { ...b.tokens }
6539
+ };
6540
+ }
6541
+ function buildRollupsForRange(sessions, sinceDay) {
6542
+ const startCounts = /* @__PURE__ */ new Map();
6543
+ for (const s of sessions) {
6544
+ const d = sessionDay(s);
6545
+ if (d) startCounts.set(d, (startCounts.get(d) ?? 0) + 1);
6546
+ }
6547
+ return dailyActivity(sessions).filter((b) => !sinceDay || b.date >= sinceDay).map((b) => ({ ...bucketToRollup(b), uniqueSessions: startCounts.get(b.date) ?? 0 }));
6548
+ }
6549
+ function dayBoundsMs(day) {
6550
+ const [y, m, d] = day.split("-").map(Number);
6551
+ return {
6552
+ startMs: new Date(y, m - 1, d, 0, 0, 0, 0).getTime(),
6553
+ endMs: new Date(y, m - 1, d + 1, 0, 0, 0, 0).getTime()
6554
+ };
6555
+ }
6556
+ function clipSegmentsToDay(segments, bounds) {
6557
+ if (!segments || segments.length === 0) return [];
6558
+ const out = [];
6559
+ for (const seg of segments) {
6560
+ const s = Math.max(seg.startMs, bounds.startMs);
6561
+ const e = Math.min(seg.endMs, bounds.endMs);
6562
+ if (e > s) out.push({ startMs: s, endMs: e });
6563
+ }
6564
+ return out;
6565
+ }
6566
+ function sessionTouchesDay(s, day) {
6567
+ const bounds = dayBoundsMs(day);
6568
+ if (s.activeSegments && s.activeSegments.length > 0) {
6569
+ return s.activeSegments.some((seg) => seg.endMs > bounds.startMs && seg.startMs < bounds.endMs);
6570
+ }
6571
+ return sessionDay(s) === day;
6572
+ }
6573
+ function buildRichRollupBlocks(day, daySessions, entries, resolveRepo) {
6574
+ const bounds = dayBoundsMs(day);
6575
+ const clippedSessions = [];
6576
+ for (const s of daySessions) {
6577
+ const clipped = clipSegmentsToDay(s.activeSegments, bounds);
6578
+ if (clipped.length === 0) continue;
6579
+ clippedSessions.push({ ...s, activeSegments: clipped });
6580
+ }
6581
+ const bursts = computeBurstsFromSessions(clippedSessions);
6582
+ const stats = summarizeBursts(bursts);
6583
+ const repoForEntry = (e) => {
6584
+ if (resolveRepo) {
6585
+ const own = resolveRepo(e.project);
6586
+ if (own) return own;
6587
+ const counts = /* @__PURE__ */ new Map();
6588
+ for (const d of e.edited_dirs ?? []) {
6589
+ const r = resolveRepo(d);
6590
+ if (r) counts.set(r, (counts.get(r) ?? 0) + 1);
6302
6591
  }
6303
- };
6304
- const usageBackfill = await runTeamBackfill(log3, USAGE_LOG2, config, {
6305
- sinceCapturedAt: options.forceUsageBackfill ? void 0 : config.lastSyncedUsageSnapshotAt
6306
- });
6307
- if (usageBackfill.lastSnapshotAt) {
6308
- persistConfig({ lastSyncedUsageSnapshotAt: usageBackfill.lastSnapshotAt });
6592
+ const dominant = [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
6593
+ if (dominant) return dominant;
6594
+ }
6595
+ return e.github_repos?.length === 1 ? e.github_repos[0] : null;
6596
+ };
6597
+ const sessionRepo = /* @__PURE__ */ new Map();
6598
+ for (const e of entries) {
6599
+ const r = repoForEntry(e);
6600
+ if (r) sessionRepo.set(e.session_id, r);
6601
+ }
6602
+ const projects = /* @__PURE__ */ new Map();
6603
+ for (const s of clippedSessions) {
6604
+ const canonical = canonicalProjectName(s.projectName);
6605
+ const name = projectRepoName(s.projectName);
6606
+ const repo = resolveRepo?.(canonical) ?? sessionRepo.get(s.id);
6607
+ const key = repo ?? name;
6608
+ const ms = s.activeSegments.reduce((sum, seg) => sum + (seg.endMs - seg.startMs), 0);
6609
+ const cur = projects.get(key) ?? { project: name, agentTimeMs: 0, sessions: 0, ...repo ? { resolved: repo } : {} };
6610
+ cur.agentTimeMs += ms;
6611
+ cur.sessions += 1;
6612
+ projects.set(key, cur);
6613
+ }
6614
+ const projectRepos = /* @__PURE__ */ new Map();
6615
+ const workingShapes = /* @__PURE__ */ new Map();
6616
+ const skills = /* @__PURE__ */ new Map();
6617
+ const subagents = /* @__PURE__ */ new Map();
6618
+ let toolErrors = 0;
6619
+ let brainstorm = 0;
6620
+ let planMode = 0;
6621
+ let prs = 0;
6622
+ let commits = 0;
6623
+ let pushes = 0;
6624
+ let longCount = 0;
6625
+ let longTotalMin = 0;
6626
+ let longMaxSingleMin = 0;
6627
+ for (const e of entries) {
6628
+ const shape = e.signals?.working_shape ?? null;
6629
+ if (shape) {
6630
+ const cur = workingShapes.get(shape) ?? { sessions: 0, agentTimeMs: 0 };
6631
+ cur.sessions += 1;
6632
+ cur.agentTimeMs += Math.round(e.numbers.active_min * 6e4);
6633
+ workingShapes.set(shape, cur);
6309
6634
  }
6310
- const sessions = await listSessions2({ limit: 1e4 });
6311
- const rollups = buildRollupsForRange(sessions, config.lastSyncedDay);
6312
- const usageSnapshot = readLatestUsageSnapshotForWire(USAGE_LOG2) ?? void 0;
6313
- const planTier = await getPlanTier(PROFILE_CACHE2).catch(() => null) ?? void 0;
6314
- const cyclePeaks = await buildCyclePeaksForPush(planTier, loadCalibrationCurve2);
6315
- if (rollups.length === 0) {
6316
- const hasLiveData = Boolean(usageSnapshot || planTier || cyclePeaks);
6317
- if (!hasLiveData) {
6318
- if (usageBackfill.sentSnapshots === 0) log3("info", "team push: nothing to sync");
6319
- return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, usageBackfill };
6320
- }
6321
- const payload = buildIngestPayload({ usageSnapshot, planTier, cyclePeaks });
6322
- const result = await pushToTeamServer(config, payload);
6323
- if (!result.ok) {
6324
- const errLine = `team push failed (${result.status})`;
6325
- log3("warn", `${errLine}; queueing`);
6326
- writeLastPushFailure(payload, errLine);
6327
- enqueuePayload(payload);
6328
- return { paired: true, pushed: 0, queued: 1, queuedDrained: 0, usageBackfill };
6329
- }
6330
- writeLastPushSuccess(payload);
6331
- collectCommands(result.body?.commands);
6332
- let queuedDrained2 = 0;
6333
- const backlog = dequeuePayloads();
6334
- for (let i = 0; i < backlog.length; i++) {
6335
- const qResult = await pushToTeamServer(config, backlog[i]);
6336
- if (!qResult.ok) {
6337
- for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
6338
- break;
6339
- }
6340
- collectCommands(qResult.body?.commands);
6341
- queuedDrained2++;
6342
- }
6343
- log3("info", `team push ok: live-only (no new daily activity)` + (queuedDrained2 ? `, ${queuedDrained2} queued retried` : ""));
6344
- await dispatchAndReport();
6345
- return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2, usageBackfill };
6635
+ for (const [name, count] of Object.entries(e.skills)) {
6636
+ if (count > 0) skills.set(name, (skills.get(name) ?? 0) + 1);
6346
6637
  }
6347
- let pushed = 0;
6348
- let queued = 0;
6349
- let failedDay;
6350
- let lastPushedDay;
6351
- const privateProjects = new Set(config.privateProjects ?? []);
6352
- const enrichmentOptIn = !!config.enrichmentOptIn;
6353
- const resolveRepo = createRepoResolver();
6354
- const { probeArtifactSignals: probeArtifactSignals2 } = await Promise.resolve().then(() => (init_file_probe(), file_probe_exports));
6355
- for (let i = 0; i < rollups.length; i++) {
6356
- const rollup = rollups[i];
6357
- const isLatest = i === rollups.length - 1;
6358
- const daySessions = sessions.filter((s) => sessionTouchesDay(s, rollup.day));
6359
- const richBlocks = buildRichBlocksForDay(
6360
- rollup.day,
6361
- daySessions,
6362
- privateProjects,
6363
- enrichmentOptIn,
6364
- resolveRepo
6365
- );
6366
- let artifactSignals = null;
6367
- try {
6368
- artifactSignals = probeArtifactSignals2({
6369
- day: rollup.day,
6370
- extraRoots: [process.cwd()]
6371
- });
6372
- } catch {
6373
- }
6374
- const payload = buildIngestPayload({
6375
- rollup,
6376
- richExtras: richBlocks?.rich,
6377
- enrichedExtras: richBlocks?.enriched,
6378
- artifactSignals: artifactSignals ?? void 0,
6379
- usageSnapshot: isLatest ? usageSnapshot : void 0,
6380
- planTier,
6381
- // Same rationale as usageSnapshot — current cycle data only on the
6382
- // latest rollup so older days don't get tagged with today's peaks.
6383
- cyclePeaks: isLatest ? cyclePeaks : void 0
6384
- });
6385
- const result = await pushToTeamServer(config, payload);
6386
- if (!result.ok) {
6387
- const errLine = `team push failed on ${rollup.day} (${result.status})`;
6388
- log3("warn", `${errLine}; queueing`);
6389
- writeLastPushFailure(payload, errLine);
6390
- enqueuePayload(payload);
6391
- queued++;
6392
- failedDay = rollup.day;
6393
- break;
6394
- }
6395
- writeLastPushSuccess(payload);
6396
- collectCommands(result.body?.commands);
6397
- pushed++;
6398
- lastPushedDay = rollup.day;
6638
+ for (const sa of e.subagents) {
6639
+ subagents.set(sa.type, (subagents.get(sa.type) ?? 0) + 1);
6399
6640
  }
6400
- if (failedDay) {
6401
- if (lastPushedDay) persistConfig({ lastSyncedDay: lastPushedDay });
6402
- } else {
6403
- persistConfig({ lastSyncedDay: today });
6641
+ toolErrors += e.numbers.tool_errors;
6642
+ if (e.signals?.brainstorm_warmup) brainstorm += 1;
6643
+ if (e.numbers.exit_plan_calls > 0) planMode += 1;
6644
+ prs += e.numbers.prs;
6645
+ commits += e.numbers.commits;
6646
+ pushes += e.numbers.pushes;
6647
+ if (e.github_repos?.length) {
6648
+ const key = sessionRepo.get(e.session_id) ?? projectRepoName(e.project);
6649
+ const set = projectRepos.get(key) ?? /* @__PURE__ */ new Set();
6650
+ for (const r of e.github_repos) set.add(r);
6651
+ projectRepos.set(key, set);
6404
6652
  }
6405
- let queuedDrained = 0;
6406
- if (!failedDay) {
6407
- const backlog = dequeuePayloads();
6408
- for (let i = 0; i < backlog.length; i++) {
6409
- const qResult = await pushToTeamServer(config, backlog[i]);
6410
- if (!qResult.ok) {
6411
- for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
6412
- break;
6413
- }
6414
- collectCommands(qResult.body?.commands);
6415
- queuedDrained++;
6416
- }
6653
+ if (e.flags.includes("long_autonomous")) {
6654
+ longCount += 1;
6655
+ longTotalMin += e.numbers.active_min;
6656
+ if (e.numbers.active_min > longMaxSingleMin) longMaxSingleMin = e.numbers.active_min;
6657
+ }
6658
+ }
6659
+ return {
6660
+ projects: Array.from(projects.entries()).map(([key, v]) => {
6661
+ const extras = [...projectRepos.get(key) ?? []].filter((r) => r !== v.resolved);
6662
+ const repos = v.resolved ? [v.resolved, ...extras] : extras;
6663
+ return {
6664
+ project: v.project,
6665
+ agentTimeMs: v.agentTimeMs,
6666
+ sessions: v.sessions,
6667
+ ...repos.length ? { githubRepos: repos.slice(0, 5) } : {}
6668
+ };
6669
+ }).sort((a, b) => b.agentTimeMs - a.agentTimeMs),
6670
+ workingShapes: Array.from(workingShapes.entries()).map(([shape, v]) => ({ shape, ...v })).sort((a, b) => b.sessions - a.sessions),
6671
+ concurrencyPeak: stats.peakConcurrent,
6672
+ parallelMinutes: Math.round(stats.totalParallelMs / 6e4),
6673
+ // Wire schema requires integer minutes (active_min is a float).
6674
+ longAutonomous: { count: longCount, totalMin: Math.round(longTotalMin), maxSingleMin: Math.round(longMaxSingleMin) },
6675
+ toolErrors,
6676
+ skillsLoaded: Array.from(skills.entries()).map(([name, sessions]) => ({ name, sessions })).sort((a, b) => b.sessions - a.sessions),
6677
+ subagentsDispatched: Array.from(subagents.entries()).map(([type, count]) => ({ type, count })).sort((a, b) => b.count - a.count),
6678
+ brainstormWarmupSessions: brainstorm,
6679
+ planModeUsed: planMode,
6680
+ prs,
6681
+ commits,
6682
+ pushes
6683
+ };
6684
+ }
6685
+ function buildEnrichedExtras(entries) {
6686
+ const outcomeMix = {};
6687
+ const helpfulnessMix = {};
6688
+ const goalMix = {};
6689
+ for (const e of entries) {
6690
+ if (e.enrichment.status !== "done") continue;
6691
+ if (e.enrichment.outcome) {
6692
+ outcomeMix[e.enrichment.outcome] = (outcomeMix[e.enrichment.outcome] ?? 0) + 1;
6417
6693
  }
6418
- if (pushed > 0) {
6419
- log3("info", `team push ok: ${pushed} day${pushed === 1 ? "" : "s"} pushed` + (queuedDrained ? `, ${queuedDrained} queued retried` : "") + (queued ? `, ${queued} queued for retry` : ""));
6694
+ if (e.enrichment.claude_helpfulness) {
6695
+ helpfulnessMix[e.enrichment.claude_helpfulness] = (helpfulnessMix[e.enrichment.claude_helpfulness] ?? 0) + 1;
6696
+ }
6697
+ for (const [cat, mins] of Object.entries(e.enrichment.goal_categories)) {
6698
+ if (typeof mins === "number") goalMix[cat] = (goalMix[cat] ?? 0) + mins;
6420
6699
  }
6421
- await dispatchAndReport();
6422
- return { paired: true, pushed, queued, queuedDrained, usageBackfill, failedDay };
6423
- } catch (err) {
6424
- const message = err.message;
6425
- log3("warn", `team push error: ${message}`);
6426
- writeLastPushFailure(
6427
- { ingestId: "n/a", observedAt: (/* @__PURE__ */ new Date()).toISOString() },
6428
- `team push error: ${message}`
6429
- );
6430
- return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, error: message };
6431
6700
  }
6701
+ return { outcomeMix, helpfulnessMix, goalMix };
6432
6702
  }
6433
- async function buildCyclePeaksForPush(planTier, loadCurve) {
6434
- const validTiers = ["pro", "pro-max", "pro-max-20x", "custom"];
6435
- const tier = validTiers.find((t) => t === planTier) ?? "pro-max-20x";
6436
- const dump = await loadCurve(tier).catch(() => null);
6437
- if (!dump || dump.curve.length === 0) return void 0;
6438
- const HOUR = 36e5;
6439
- const nowMs = Date.now();
6440
- const peaksFor = (cycleKey, realKey, predKey, maxCycles) => {
6441
- const TOLERANCE_MS = cycleKey === "cycle_end_7d" ? 12 * HOUR : 0;
6442
- const byCycle = /* @__PURE__ */ new Map();
6443
- for (const p of dump.curve) {
6444
- const k = p[cycleKey];
6445
- if (!k) continue;
6446
- const ms = Date.parse(k);
6447
- if (Number.isNaN(ms)) continue;
6448
- const bucket = Math.round(ms / HOUR) * HOUR;
6449
- const arr = byCycle.get(bucket) ?? [];
6450
- arr.push(p);
6451
- byCycle.set(bucket, arr);
6703
+ var ensuredSessions = /* @__PURE__ */ new Set();
6704
+ function resetEnsuredSessions() {
6705
+ ensuredSessions.clear();
6706
+ }
6707
+ function ensureEntriesForDay(day, daySessions) {
6708
+ for (const s of daySessions) {
6709
+ if (ensuredSessions.has(s.id)) continue;
6710
+ if ((s.agent ?? "claude-code") !== "claude-code") continue;
6711
+ if (entryExists(s.id, day)) continue;
6712
+ ensuredSessions.add(s.id);
6713
+ try {
6714
+ for (const e of buildEntriesForFile(s.filePath) ?? []) writeEntryPreservingEnrichment(e);
6715
+ } catch {
6452
6716
  }
6453
- const merged = [];
6454
- for (const [endMs, points] of Array.from(byCycle.entries()).sort((a, b) => a[0] - b[0])) {
6455
- const last = merged[merged.length - 1];
6456
- if (last && endMs - last.endMs <= TOLERANCE_MS) {
6457
- last.endMs = endMs;
6458
- last.points.push(...points);
6459
- } else {
6460
- merged.push({ endMs, points: [...points] });
6461
- }
6717
+ }
6718
+ }
6719
+ function buildRichBlocksForDay(day, daySessions, resolveRepo) {
6720
+ ensureEntriesForDay(day, daySessions);
6721
+ const entries = listEntriesForDay(day);
6722
+ if (entries.length === 0) return void 0;
6723
+ const rich = buildRichRollupBlocks(day, daySessions, entries, resolveRepo);
6724
+ const enriched = buildEnrichedExtras(entries);
6725
+ return { rich, enriched };
6726
+ }
6727
+ function buildIngestPayload(inputs) {
6728
+ const richRollup = inputs.rollup && inputs.richExtras ? { ...inputs.rollup, ...inputs.richExtras } : void 0;
6729
+ return {
6730
+ ingestId: randomUUID(),
6731
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
6732
+ cliVersion: "0.14.0",
6733
+ ...richRollup || inputs.enrichedExtras || inputs.artifactSignals ? { schemaVersion: RICH_ROLLUP_SCHEMA_VERSION } : {},
6734
+ ...inputs.rollup ? { dailyRollup: inputs.rollup } : {},
6735
+ ...richRollup ? { richRollup } : {},
6736
+ ...inputs.enrichedExtras ? { enrichedExtras: inputs.enrichedExtras } : {},
6737
+ ...inputs.artifactSignals ? { artifactSignals: inputs.artifactSignals } : {},
6738
+ ...inputs.usageSnapshot ? { usageSnapshot: inputs.usageSnapshot } : {},
6739
+ ...inputs.planTier ? { planTier: inputs.planTier } : {},
6740
+ ...inputs.cyclePeaks ? { cyclePeaks: inputs.cyclePeaks } : {},
6741
+ ...inputs.syncLog && inputs.syncLog.length ? { syncLog: inputs.syncLog } : {}
6742
+ };
6743
+ }
6744
+ async function pushToTeamServer(config, payload) {
6745
+ const res = await fetch(`${config.serverUrl}/api/ingest/metrics`, {
6746
+ method: "POST",
6747
+ signal: AbortSignal.timeout(POST_TIMEOUT_MS),
6748
+ headers: {
6749
+ "Content-Type": "application/json",
6750
+ Authorization: `Bearer ${config.bearerToken}`
6751
+ },
6752
+ body: JSON.stringify(payload)
6753
+ });
6754
+ const body = await res.json().catch(() => null);
6755
+ return { ok: res.ok, status: res.status, body };
6756
+ }
6757
+
6758
+ // src/team/git-remote.ts
6759
+ import { existsSync as existsSync4, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
6760
+ import { dirname as dirname4, isAbsolute, join as join5, resolve } from "node:path";
6761
+ var GITHUB_URL_RE = /github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/;
6762
+ function parseGithubRemote(url) {
6763
+ const m = url.trim().match(GITHUB_URL_RE);
6764
+ return m ? `${m[1]}/${m[2]}`.toLowerCase() : null;
6765
+ }
6766
+ function originUrl(configText) {
6767
+ let inOrigin = false;
6768
+ for (const line of configText.split("\n")) {
6769
+ const t = line.trim();
6770
+ if (t.startsWith("[")) {
6771
+ inOrigin = /^\[remote\s+"origin"\]/.test(t);
6772
+ continue;
6462
6773
  }
6463
- const out = [];
6464
- for (const { endMs, points } of merged) {
6465
- let peak = 0;
6466
- let source = "predicted";
6467
- for (const p of points) {
6468
- const r = p[realKey];
6469
- if (typeof r === "number" && r > peak) {
6470
- peak = r;
6471
- source = "real";
6472
- }
6473
- const v = p[predKey] ?? 0;
6474
- if (v > peak) {
6475
- peak = v;
6476
- source = "predicted";
6774
+ if (!inOrigin) continue;
6775
+ const m = t.match(/^url\s*=\s*(.+)$/);
6776
+ if (m) return m[1].trim();
6777
+ }
6778
+ return null;
6779
+ }
6780
+ function gitConfigPathFor(dir) {
6781
+ const dotGit = join5(dir, ".git");
6782
+ const st = statSync2(dotGit, { throwIfNoEntry: false });
6783
+ if (!st) return null;
6784
+ if (st.isDirectory()) return join5(dotGit, "config");
6785
+ const m = readFileSync7(dotGit, "utf8").match(/^gitdir:\s*(.+)\s*$/m);
6786
+ if (!m) return null;
6787
+ const gd = isAbsolute(m[1]) ? m[1] : resolve(dir, m[1]);
6788
+ const commondirFile = join5(gd, "commondir");
6789
+ if (existsSync4(commondirFile)) {
6790
+ const common = readFileSync7(commondirFile, "utf8").trim();
6791
+ return join5(isAbsolute(common) ? common : resolve(gd, common), "config");
6792
+ }
6793
+ return join5(gd, "config");
6794
+ }
6795
+ function createRepoResolver() {
6796
+ const cache = /* @__PURE__ */ new Map();
6797
+ return (dir) => {
6798
+ if (!dir || !isAbsolute(dir)) return null;
6799
+ const hit = cache.get(dir);
6800
+ if (hit !== void 0) return hit;
6801
+ let repo = null;
6802
+ try {
6803
+ for (let d = dir; ; d = dirname4(d)) {
6804
+ const config = gitConfigPathFor(d);
6805
+ if (config && existsSync4(config)) {
6806
+ const url = originUrl(readFileSync7(config, "utf8"));
6807
+ repo = url ? parseGithubRemote(url) : null;
6808
+ break;
6477
6809
  }
6810
+ if (d === dirname4(d)) break;
6478
6811
  }
6479
- out.push({
6480
- endsAt: new Date(endMs).toISOString(),
6481
- peakPct: Math.round(peak * 10) / 10,
6482
- source,
6483
- current: endMs > nowMs
6484
- });
6812
+ } catch {
6813
+ repo = null;
6485
6814
  }
6486
- return out.slice(-maxCycles);
6487
- };
6488
- return {
6489
- fiveHour: peaksFor("cycle_end_5h", "real_5h", "pred_5h", 24),
6490
- sevenDay: peaksFor("cycle_end_7d", "real_7d", "pred_7d", 12)
6815
+ cache.set(dir, repo);
6816
+ return repo;
6491
6817
  };
6492
6818
  }
6493
6819
 
6494
- // src/perception/worker.ts
6495
- init_dist();
6820
+ // src/team/queue.ts
6496
6821
  init_fs();
6497
- import { statSync as statSync4, readFileSync as readFileSync10 } from "node:fs";
6498
- import { basename, dirname as dirname8 } from "node:path";
6499
-
6500
- // ../entries/dist/signals.js
6501
- init_dist();
6502
- var TEAMMATE_RE = /^<teammate-message\b/;
6503
- var SKILL_LOAD_RE = /^Base directory for this skill:/;
6504
- var SLASH_COMMAND_RE2 = /^<command-name>|^<local-command-stdout>/;
6505
- function classifyUserInputSource(text) {
6506
- if (!text)
6507
- return "human";
6508
- const stripped = stripFrameworkBoilerplate(text);
6509
- if (!stripped)
6510
- return "system_instruction";
6511
- if (TEAMMATE_RE.test(stripped))
6512
- return "teammate";
6513
- if (SKILL_LOAD_RE.test(stripped))
6514
- return "skill_load";
6515
- if (SLASH_COMMAND_RE2.test(stripped))
6516
- return "slash_command";
6517
- return "human";
6518
- }
6519
- var HAPPY_RE = /\b(?:yay|yaay|yass|woohoo|nice|great|love(?:ly)?|perfect|amazing|awesome)\s*!|!!!/gi;
6520
- var SATISFIED_RE = /\b(?:thanks|thank you|looks good|lgtm|works|that works|all good|sounds good)\b/gi;
6521
- var DISSATISFIED_RE = /\b(?:that'?s (?:not|wrong)|try again|no, (?:that|this)|not quite|incorrect)\b/gi;
6522
- var FRUSTRATED_RE = /\b(?:broken|stop|why (?:did|are|would) you|give up|ugh|argh|wtf)\b/gi;
6523
- function countSatisfactionSignals(text) {
6524
- if (!text)
6525
- return { happy: 0, satisfied: 0, dissatisfied: 0, frustrated: 0 };
6526
- return {
6527
- happy: (text.match(HAPPY_RE) ?? []).length,
6528
- satisfied: (text.match(SATISFIED_RE) ?? []).length,
6529
- dissatisfied: (text.match(DISSATISFIED_RE) ?? []).length,
6530
- frustrated: (text.match(FRUSTRATED_RE) ?? []).length
6531
- };
6532
- }
6533
- var INSTRUCTION_RE = /\b(?:can you|please|let's|let us|i need|could you|would you)\s+([^.?!\n]{5,200})/gi;
6534
- function extractUserInstructions(text) {
6535
- if (!text)
6536
- return [];
6537
- INSTRUCTION_RE.lastIndex = 0;
6538
- const out = [];
6539
- const seen = /* @__PURE__ */ new Set();
6540
- let m;
6541
- while ((m = INSTRUCTION_RE.exec(text)) !== null) {
6542
- const phrase = m[1].trim().replace(/\s+/g, " ");
6543
- const lc = phrase.toLowerCase();
6544
- if (seen.has(lc))
6545
- continue;
6546
- seen.add(lc);
6547
- out.push(phrase);
6548
- if (out.length >= 5)
6549
- break;
6550
- }
6551
- return out;
6552
- }
6553
- function classifySubagentRole(sa) {
6554
- const text = `${sa.description} ${sa.prompt_preview}`.toLowerCase();
6555
- if (/\b(re-?review|review[s]?|verify|audit|spec[- ]review|code[- ]quality|code[- ]reuse|efficiency review|sanity check|quick test)\b/.test(text)) {
6556
- return "reviewer";
6557
- }
6558
- if (/\b(implement|build chunk|build task|initialize|create the|add the)\b/.test(text)) {
6559
- return "implementer";
6560
- }
6561
- if (/\bexplore\b|\binventory\b|\bmap\b.*\b(codebase|repo|branch)\b|\baudit\b.*coverage/.test(text)) {
6562
- return "explorer";
6563
- }
6564
- if (/\b(investigate|research|reverse[- ]engineer|study|analy[sz]e|look up|tell me|brief me on|find out)\b/.test(text)) {
6565
- return "researcher";
6566
- }
6567
- if (/\b(env(?:ironment)? setup|configure|setup|bootstrap)\b/.test(text)) {
6568
- return "env-setup";
6569
- }
6570
- if (/\b(polish|cleanup|fix|refactor)\b/.test(text)) {
6571
- return "polish";
6822
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync4, appendFileSync as appendFileSync3, statSync as statSync3, mkdirSync as mkdirSync5 } from "node:fs";
6823
+ import { dirname as dirname5 } from "node:path";
6824
+ var QUEUE_PATH = cclensPath("ingest-queue.jsonl");
6825
+ var MAX_SIZE_BYTES = 10 * 1024 * 1024;
6826
+ var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
6827
+ function enqueuePayload(payload, queuePath = QUEUE_PATH) {
6828
+ mkdirSync5(dirname5(queuePath), { recursive: true });
6829
+ const entry = { payload, enqueuedAt: (/* @__PURE__ */ new Date()).toISOString() };
6830
+ appendFileSync3(queuePath, JSON.stringify(entry) + "\n", "utf8");
6831
+ try {
6832
+ const { size } = statSync3(queuePath);
6833
+ if (size > MAX_SIZE_BYTES) {
6834
+ pruneOldest(queuePath);
6835
+ }
6836
+ } catch {
6572
6837
  }
6573
- return "other";
6574
- }
6575
- var STOCK_SUBAGENT_PREFIXES = /^(general-purpose|Explore|Plan|claude-code-guide|playwright-qa-verifier|statusline-setup|frontend-design:|code-review:|code-simplifier:|codex:|superpowers:)/;
6576
- function isStockSubagentType(type) {
6577
- return STOCK_SUBAGENT_PREFIXES.test(type);
6578
6838
  }
6579
- function inferWorkingShape(entry) {
6580
- if (entry.numbers.turn_count < 2)
6581
- return null;
6582
- const subagents = entry.subagents ?? [];
6583
- const roles = subagents.map(classifySubagentRole);
6584
- const reviewerCount = roles.filter((r) => r === "reviewer").length;
6585
- const implementerCount = roles.filter((r) => r === "implementer").length;
6586
- const explorerOrResearcher = roles.filter((r) => r === "explorer" || r === "researcher").length;
6587
- const hasBackground = subagents.some((sa) => sa.background);
6588
- if (implementerCount >= 2) {
6589
- const chunkRefs = subagents.filter((sa) => /\b(chunk|task)\s*\d+/i.test(`${sa.description} ${sa.prompt_preview}`));
6590
- if (chunkRefs.length >= 2)
6591
- return "chunk-implementation";
6839
+ function dequeuePayloads(queuePath = QUEUE_PATH) {
6840
+ let lines;
6841
+ try {
6842
+ lines = readFileSync8(queuePath, "utf8").trim().split("\n").filter(Boolean);
6843
+ } catch {
6844
+ return [];
6592
6845
  }
6593
- if (reviewerCount >= 3 && implementerCount === 0) {
6594
- const lensSigs = /* @__PURE__ */ new Set();
6595
- for (let i = 0; i < subagents.length; i++) {
6596
- if (roles[i] !== "reviewer")
6597
- continue;
6598
- const sig = subagents[i].description.toLowerCase().replace(/[^a-z0-9 ]/g, "").split(/\s+/).filter((w) => w.length > 2).slice(0, 3).join("|");
6599
- lensSigs.add(sig);
6846
+ const now = Date.now();
6847
+ const valid = [];
6848
+ for (const line of lines) {
6849
+ try {
6850
+ const entry = JSON.parse(line);
6851
+ if (now - new Date(entry.enqueuedAt).getTime() < MAX_AGE_MS) {
6852
+ valid.push(entry);
6853
+ }
6854
+ } catch {
6600
6855
  }
6601
- if (lensSigs.size >= 3)
6602
- return "reviewer-triad";
6603
- }
6604
- if (reviewerCount >= 2)
6605
- return "spec-review-loop";
6606
- if (explorerOrResearcher >= 2 || explorerOrResearcher >= 1 && implementerCount >= 1) {
6607
- return "research-then-build";
6608
6856
  }
6609
- if (hasBackground && subagents.length >= 1)
6610
- return "background-coordinated";
6611
- if (subagents.length === 0) {
6612
- const first = (entry.first_user || "").trim().toLowerCase();
6613
- if (/^continue\b/.test(first) || first === "continue.")
6614
- return "solo-continuation";
6615
- const skills = Object.keys(entry.skills ?? {});
6616
- if (skills.some((s) => /brainstorm|writing-plans|brainstorming/i.test(s)))
6617
- return "solo-design";
6618
- return "solo-build";
6857
+ writeFileSync4(queuePath, "", "utf8");
6858
+ return valid.map((e) => e.payload);
6859
+ }
6860
+ function pruneOldest(queuePath) {
6861
+ let lines;
6862
+ try {
6863
+ lines = readFileSync8(queuePath, "utf8").trim().split("\n").filter(Boolean);
6864
+ } catch {
6865
+ return;
6619
6866
  }
6620
- return "solo-build";
6867
+ const keep = lines.slice(Math.floor(lines.length / 2));
6868
+ writeFileSync4(queuePath, keep.join("\n") + "\n", "utf8");
6621
6869
  }
6622
- function detectPromptFrames(text) {
6623
- if (!text)
6624
- return [];
6625
- const out = [];
6626
- if (/<teammate-message\b/i.test(text))
6627
- out.push("teammate");
6628
- if (/<task-notification\b/i.test(text))
6629
- out.push("task-notification");
6630
- if (/<local-command-caveat\b/i.test(text))
6631
- out.push("local-command-caveat");
6632
- if (/<command-(message|name)\b/i.test(text))
6633
- out.push("slash-command");
6634
- if (/\[Image #\d+\]/.test(text))
6635
- out.push("image-attached");
6636
- if (/\b(here'?s the (handoff|handover) prompt|session-close summary|wrap-up summary|all wrapped up|all captured\.)\b/i.test(text) || /^##? (Wrap-up|Session conclusion|Handoff)\b/m.test(text) || /^# Handoff:/m.test(text)) {
6637
- out.push("handoff-prose");
6870
+
6871
+ // src/team/backfill.ts
6872
+ init_storage();
6873
+ import { randomUUID as randomUUID2 } from "node:crypto";
6874
+
6875
+ // src/usage/profile.ts
6876
+ import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
6877
+ import { dirname as dirname6 } from "node:path";
6878
+ var PROFILE_ENDPOINT = "https://api.anthropic.com/api/oauth/profile";
6879
+ var BETA_HEADER2 = "oauth-2025-04-20";
6880
+ var PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
6881
+ function mapRateLimitTier(rateLimitTier) {
6882
+ switch (rateLimitTier) {
6883
+ case "default_claude_pro":
6884
+ return "pro";
6885
+ case "default_claude_max":
6886
+ case "default_claude_max_5x":
6887
+ return "pro-max";
6888
+ case "default_claude_max_20x":
6889
+ return "pro-max-20x";
6890
+ default:
6891
+ return "custom";
6638
6892
  }
6639
- return out;
6640
6893
  }
6641
- var STOCK_SKILL_PREFIXES = /^(superpowers|mcp__|frontend-design|code-review|codex|claude-code-guide|claude-api):/i;
6642
- function classifySkill(name) {
6643
- if (name.startsWith("(ToolSearch:"))
6644
- return "infra";
6645
- if (STOCK_SKILL_PREFIXES.test(name))
6646
- return "stock";
6647
- if (name === "using-superpowers")
6648
- return "stock";
6649
- return "user";
6894
+ async function fetchProfile(nowMs = Date.now()) {
6895
+ const creds = readOAuthCredentials();
6896
+ if (!creds) return null;
6897
+ if (creds.expiresAt - 6e4 <= nowMs) return null;
6898
+ let res;
6899
+ try {
6900
+ res = await fetch(PROFILE_ENDPOINT, {
6901
+ headers: {
6902
+ Authorization: `Bearer ${creds.accessToken}`,
6903
+ "anthropic-beta": BETA_HEADER2
6904
+ }
6905
+ });
6906
+ } catch {
6907
+ return null;
6908
+ }
6909
+ if (!res.ok) return null;
6910
+ const body = await res.json().catch(() => null);
6911
+ if (!body) return null;
6912
+ const rateLimitTier = body.organization?.rate_limit_tier ?? null;
6913
+ return {
6914
+ planTier: mapRateLimitTier(rateLimitTier),
6915
+ rateLimitTier,
6916
+ organizationType: body.organization?.organization_type ?? null
6917
+ };
6650
6918
  }
6651
- var EXTERNAL_REF_PATTERNS = [
6652
- { kind: "linear-kip", re: /\bKIP-\d+\b/i },
6653
- { kind: "github-issue-pr", re: /\b(issue|pr|pull request)\s*#\d+\b|github\.com\/[^\s]+\/(issues|pull)\/\d+/i },
6654
- { kind: "branch-ref", re: /\b(branch|feat|fix|chore|refactor)[/:]\s*[\w./-]+|on `[\w./-]+`/i },
6655
- { kind: "url", re: /https?:\/\/\S+/ }
6656
- ];
6657
- function detectExternalRef(text) {
6658
- if (!text)
6659
- return null;
6660
- if (/<teammate-message|<task-notification|<local-command-caveat/i.test(text.slice(0, 400)))
6919
+ function readCachedProfile(cachePath) {
6920
+ if (!existsSync5(cachePath)) return null;
6921
+ try {
6922
+ const raw = JSON.parse(readFileSync9(cachePath, "utf8"));
6923
+ if (typeof raw.fetchedAtMs !== "number" || !raw.profile) return null;
6924
+ return raw;
6925
+ } catch {
6661
6926
  return null;
6662
- for (const { kind, re } of EXTERNAL_REF_PATTERNS) {
6663
- const m = text.match(re);
6664
- if (m)
6665
- return { kind, preview: text.slice(0, 160) };
6666
6927
  }
6667
- return null;
6668
6928
  }
6669
- function bucketVerbosity(n) {
6670
- if (n < 100)
6671
- return "short";
6672
- if (n < 500)
6673
- return "medium";
6674
- if (n < 2e3)
6675
- return "long";
6676
- return "very_long";
6929
+ function writeCachedProfile(cachePath, profile, nowMs = Date.now()) {
6930
+ mkdirSync6(dirname6(cachePath), { recursive: true });
6931
+ writeFileSync5(
6932
+ cachePath,
6933
+ JSON.stringify({ fetchedAtMs: nowMs, profile }, null, 2),
6934
+ { mode: 384 }
6935
+ );
6677
6936
  }
6678
- function computeEntrySignals(entry) {
6679
- const fu = entry.first_user || "";
6680
- const verbosity = bucketVerbosity(fu.length);
6681
- const ext = detectExternalRef(fu);
6682
- const external_refs = ext ? [ext] : [];
6683
- const skillNames = Object.keys(entry.skills ?? {});
6684
- const brainstorm_warmup = skillNames.some((s) => /brainstorm|writing-plans/i.test(s));
6685
- const fuTrim = fu.trim().toLowerCase();
6686
- const frames = detectPromptFrames(fu);
6687
- const continuation_kind = /^continue\b/.test(fuTrim) || fuTrim === "continue." ? "literal-continue" : frames.includes("handoff-prose") ? "handoff-prose" : "none";
6937
+ async function getPlanTier(cachePath, nowMs = Date.now(), fetcher = fetchProfile) {
6938
+ const cached = readCachedProfile(cachePath);
6939
+ if (cached && nowMs - cached.fetchedAtMs < PROFILE_CACHE_TTL_MS) {
6940
+ return cached.profile.planTier;
6941
+ }
6942
+ const fresh = await fetcher(nowMs);
6943
+ if (!fresh) {
6944
+ return cached?.profile.planTier ?? null;
6945
+ }
6946
+ writeCachedProfile(cachePath, fresh, nowMs);
6947
+ return fresh.planTier;
6948
+ }
6949
+
6950
+ // src/team/backfill.ts
6951
+ init_fs();
6952
+ var USAGE_LOG = cclensPath("usage.jsonl");
6953
+ var PROFILE_CACHE = cclensPath("profile.json");
6954
+ var BATCH_SIZE = 500;
6955
+ var noopLog = () => {
6956
+ };
6957
+ function rawToWire(raw) {
6958
+ const win = (w) => w ? { utilization: w.utilization, resetsAt: w.resets_at } : null;
6688
6959
  return {
6689
- working_shape: inferWorkingShape(entry),
6690
- prompt_frames: frames,
6691
- subagent_roles: (entry.subagents ?? []).map(classifySubagentRole),
6692
- verbosity,
6693
- external_refs,
6694
- brainstorm_warmup,
6695
- continuation_kind
6960
+ capturedAt: raw.captured_at,
6961
+ fiveHour: { utilization: raw.five_hour.utilization, resetsAt: raw.five_hour.resets_at },
6962
+ sevenDay: { utilization: raw.seven_day.utilization, resetsAt: raw.seven_day.resets_at },
6963
+ sevenDayOpus: win(raw.seven_day_opus),
6964
+ sevenDaySonnet: win(raw.seven_day_sonnet),
6965
+ sevenDayOauthApps: win(raw.seven_day_oauth_apps),
6966
+ sevenDayCowork: win(raw.seven_day_cowork),
6967
+ extraUsage: raw.extra_usage ? {
6968
+ isEnabled: raw.extra_usage.is_enabled,
6969
+ monthlyLimitUsd: raw.extra_usage.monthly_limit,
6970
+ usedCreditsUsd: raw.extra_usage.used_credits,
6971
+ utilization: raw.extra_usage.utilization
6972
+ } : null
6696
6973
  };
6697
6974
  }
6698
-
6699
- // ../entries/dist/trivial.js
6700
- function isTrivial(n) {
6701
- return n.active_min < 1 && n.turn_count < 3 && n.tools_total === 0;
6975
+ function chunk(items, size) {
6976
+ if (size <= 0) return [items];
6977
+ const out = [];
6978
+ for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
6979
+ return out;
6702
6980
  }
6703
-
6704
- // ../entries/dist/build.js
6705
- init_analytics();
6706
- function viewEvent(ev) {
6707
- let rawType;
6708
- if (ev.role === "user" || ev.role === "tool-result")
6709
- rawType = "user";
6710
- else if (ev.role === "agent" || ev.role === "agent-thinking" || ev.role === "tool-call")
6711
- rawType = "assistant";
6712
- else if (ev.rawType === "summary")
6713
- rawType = "summary";
6714
- else
6715
- rawType = "system";
6716
- const rawCwd = ev.raw?.cwd;
6981
+ async function postBatch(config, snapshots, planTier) {
6982
+ const payload = {
6983
+ ingestId: `backfill-${randomUUID2()}`,
6984
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
6985
+ snapshotHistory: snapshots,
6986
+ ...planTier ? { planTier } : {}
6987
+ };
6988
+ const result = await pushToTeamServer(config, payload);
6989
+ if (!result.ok) {
6990
+ return { inserted: 0, skipped: snapshots.length, status: result.status };
6991
+ }
6992
+ const body = result.body;
6993
+ if (!body?.snapshotHistory) {
6994
+ throw new Error(
6995
+ "team server accepted snapshotHistory but did not return a result block \u2014 older image, upgrade required"
6996
+ );
6997
+ }
6717
6998
  return {
6718
- rawType,
6719
- content: ev.blocks ?? [],
6720
- model: ev.model,
6721
- msgId: ev.messageId,
6722
- usage: ev.usage ? {
6723
- input_tokens: ev.usage.input,
6724
- output_tokens: ev.usage.output,
6725
- cache_read_input_tokens: ev.usage.cacheRead,
6726
- cache_creation_input_tokens: ev.usage.cacheWrite
6727
- } : void 0,
6728
- cwd: rawCwd
6999
+ inserted: body.snapshotHistory.inserted ?? 0,
7000
+ skipped: body.snapshotHistory.skipped ?? 0,
7001
+ status: result.status
6729
7002
  };
6730
7003
  }
6731
- var IDLE_GAP_MS4 = 3 * 60 * 1e3;
6732
- var INTERRUPT_RE2 = /\[request interrupted|interrupted by user/i;
6733
- var PR_TITLE_RE = /--title\s+["']([^"']+)["']/;
6734
- var BASH_CHAIN_SPLIT_RE = /\|\||&&|;|\|/;
6735
- var SHELL_NOISE = /* @__PURE__ */ new Set(["sudo", "time", "nohup", "nice", "env", "exec"]);
6736
- var IMAGE_BLOCK_RE = /\[Image:\s*source:\s*[^\]]+\]/g;
6737
- function parseMs(iso) {
6738
- if (!iso)
6739
- return void 0;
6740
- const ms = Date.parse(iso);
6741
- return Number.isNaN(ms) ? void 0 : ms;
6742
- }
6743
- function trunc(s, n) {
6744
- if (!s)
6745
- return "";
6746
- const one = s.replace(/\s+/g, " ").trim();
6747
- return one.length <= n ? one : one.slice(0, n - 1) + "\u2026";
7004
+ function isAfterSince(snapshot, sinceCapturedAt) {
7005
+ if (!sinceCapturedAt) return true;
7006
+ const sinceMs = Date.parse(sinceCapturedAt);
7007
+ if (Number.isNaN(sinceMs)) return true;
7008
+ const capturedMs = Date.parse(snapshot.captured_at);
7009
+ return !Number.isNaN(capturedMs) && capturedMs > sinceMs;
6748
7010
  }
6749
- function minimizeImages(s) {
6750
- if (!s)
6751
- return s;
6752
- let counter = 0;
6753
- return s.replace(IMAGE_BLOCK_RE, () => `[Image #${++counter}]`);
7011
+ function capturedAtMs(snapshot) {
7012
+ const ms = Date.parse(snapshot.captured_at);
7013
+ return Number.isNaN(ms) ? Number.MAX_SAFE_INTEGER : ms;
6754
7014
  }
6755
- function bashVerb(cmd) {
6756
- if (!cmd)
6757
- return "?";
6758
- let firstSeg = cmd.split(BASH_CHAIN_SPLIT_RE, 1)[0].trim();
6759
- if (firstSeg.startsWith("cd ") || firstSeg === "cd") {
6760
- const parts = cmd.split(/&&|;/, 3);
6761
- if (parts.length >= 2)
6762
- firstSeg = parts[1].trim();
6763
- }
6764
- const tokens = firstSeg.split(/\s+/).filter(Boolean);
6765
- while (tokens.length && SHELL_NOISE.has(tokens[0]))
6766
- tokens.shift();
6767
- if (!tokens.length)
6768
- return "?";
6769
- let first = tokens[0];
6770
- if (first.includes("=") && !first.startsWith("/")) {
6771
- tokens.shift();
6772
- if (!tokens.length)
6773
- return "?";
6774
- first = tokens[0];
6775
- }
6776
- if (first.includes("/") && !first.startsWith("./")) {
6777
- first = first.replace(/\/+$/, "").split("/").pop();
7015
+ function latestCapturedAt(snapshots) {
7016
+ let latest = null;
7017
+ for (const s of snapshots) {
7018
+ const ms = Date.parse(s.capturedAt);
7019
+ if (Number.isNaN(ms)) continue;
7020
+ if (!latest || ms > latest.ms) latest = { at: s.capturedAt, ms };
6778
7021
  }
6779
- return first.replace(/^["']|["']$/g, "") || "?";
7022
+ return latest?.at;
6780
7023
  }
6781
- function firstTextBlock(blocks) {
6782
- if (!Array.isArray(blocks))
6783
- return "";
6784
- for (const b of blocks) {
6785
- if (b && typeof b === "object" && b.type === "text") {
6786
- return String(b.text ?? "");
6787
- }
7024
+ async function runTeamBackfill(log3 = noopLog, filePath = USAGE_LOG, configOverride, options = {}) {
7025
+ const config = configOverride === void 0 ? readTeamConfig() : configOverride;
7026
+ if (!config) {
7027
+ return { paired: false, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
6788
7028
  }
6789
- return "";
6790
- }
6791
- function blockText(blocks) {
6792
- if (!blocks)
6793
- return "";
6794
- return blocks.filter((b) => !!b && typeof b === "object" && b.type === "text" && typeof b.text === "string").map((b) => b.text).join(" ");
6795
- }
6796
- function groupEventsByLocalDay(events) {
6797
- const byDay = /* @__PURE__ */ new Map();
6798
- for (const ev of events) {
6799
- if (!ev.timestamp)
6800
- continue;
6801
- const ms = Date.parse(ev.timestamp);
6802
- if (Number.isNaN(ms))
6803
- continue;
6804
- const day = toLocalDay(ms);
6805
- let bucket = byDay.get(day);
6806
- if (!bucket) {
6807
- bucket = [];
6808
- byDay.set(day, bucket);
7029
+ const raw = readSnapshots(filePath);
7030
+ const claudeOnly = raw.filter(
7031
+ (s) => (!s.agent || s.agent === "claude-code") && isAfterSince(s, options.sinceCapturedAt)
7032
+ ).sort((a, b) => capturedAtMs(a) - capturedAtMs(b));
7033
+ if (claudeOnly.length === 0) {
7034
+ log3("info", "team backfill: no usage snapshots to send");
7035
+ return { paired: true, sentSnapshots: 0, insertedSnapshots: 0, skippedSnapshots: 0, batches: 0 };
7036
+ }
7037
+ const wire = claudeOnly.map(rawToWire);
7038
+ const batches = chunk(wire, BATCH_SIZE);
7039
+ const planTier = await getPlanTier(PROFILE_CACHE).catch(() => null) ?? void 0;
7040
+ let inserted = 0;
7041
+ let skipped = 0;
7042
+ let lastSnapshotAt;
7043
+ for (let i = 0; i < batches.length; i++) {
7044
+ const batch = batches[i];
7045
+ try {
7046
+ const result = await postBatch(config, batch, planTier);
7047
+ if (result.status >= 400) {
7048
+ log3("warn", `team backfill: batch ${i + 1}/${batches.length} failed (${result.status})`);
7049
+ return {
7050
+ paired: true,
7051
+ sentSnapshots: wire.length,
7052
+ insertedSnapshots: inserted,
7053
+ skippedSnapshots: skipped,
7054
+ batches: i,
7055
+ lastSnapshotAt,
7056
+ error: `HTTP ${result.status}`
7057
+ };
7058
+ }
7059
+ inserted += result.inserted;
7060
+ skipped += result.skipped;
7061
+ lastSnapshotAt = latestCapturedAt(batch) ?? lastSnapshotAt;
7062
+ } catch (err) {
7063
+ log3("warn", `team backfill: batch ${i + 1}/${batches.length} error: ${err.message}`);
7064
+ return {
7065
+ paired: true,
7066
+ sentSnapshots: wire.length,
7067
+ insertedSnapshots: inserted,
7068
+ skippedSnapshots: skipped,
7069
+ batches: i,
7070
+ lastSnapshotAt,
7071
+ error: err.message
7072
+ };
6809
7073
  }
6810
- bucket.push(ev);
6811
7074
  }
6812
- return byDay;
6813
- }
6814
- function newTurn(startMs, userInput = "") {
7075
+ log3(
7076
+ "info",
7077
+ `team backfill: ${inserted} new, ${skipped} already-known across ${batches.length} batch${batches.length === 1 ? "" : "es"}`
7078
+ );
6815
7079
  return {
6816
- startMs,
6817
- lastMs: startMs,
6818
- eventTs: startMs !== void 0 ? [startMs] : [],
6819
- userInput,
6820
- toolsTotal: 0,
6821
- toolMix: /* @__PURE__ */ new Map(),
6822
- bashVerbs: /* @__PURE__ */ new Map(),
6823
- skills: /* @__PURE__ */ new Map(),
6824
- subagents: [],
6825
- taskOps: 0,
6826
- toolErrors: 0,
6827
- consecSameToolMax: 0,
6828
- consec: 0,
6829
- endedInInterrupt: false,
6830
- models: /* @__PURE__ */ new Map(),
6831
- tokensIn: 0,
6832
- tokensOut: 0,
6833
- tokensCacheR: 0,
6834
- tokensCacheW: 0,
6835
- seenMsgIds: /* @__PURE__ */ new Set()
7080
+ paired: true,
7081
+ sentSnapshots: wire.length,
7082
+ insertedSnapshots: inserted,
7083
+ skippedSnapshots: skipped,
7084
+ batches: batches.length,
7085
+ lastSnapshotAt
6836
7086
  };
6837
7087
  }
6838
- function closeTurn(t, closed, cwd) {
6839
- const ts = [...t.eventTs].sort((a, b) => a - b);
6840
- let active = 0;
6841
- for (let i = 1; i < ts.length; i++) {
6842
- const dt = ts[i] - ts[i - 1];
6843
- if (dt < IDLE_GAP_MS4)
6844
- active += dt;
7088
+
7089
+ // src/team/sync-log.ts
7090
+ init_fs();
7091
+ import { readFileSync as readFileSync10 } from "node:fs";
7092
+ var MAX_LINES = 300;
7093
+ var MAX_MSG_CHARS = 1900;
7094
+ function readPendingSyncLog(watermark) {
7095
+ let raw;
7096
+ try {
7097
+ raw = readFileSync10(cclensPath("daemon.log"), "utf8");
7098
+ } catch {
7099
+ return { lines: [] };
6845
7100
  }
6846
- closed.push({
6847
- startMs: t.startMs ?? 0,
6848
- activeMs: active,
6849
- userInput: t.userInput,
6850
- firstAgentText: t.firstAgentText,
6851
- lastAgentText: t.lastAgentText,
6852
- toolsTotal: t.toolsTotal,
6853
- toolMix: t.toolMix,
6854
- bashVerbs: t.bashVerbs,
6855
- skills: t.skills,
6856
- subagents: t.subagents,
6857
- taskOps: t.taskOps,
6858
- toolErrors: t.toolErrors,
6859
- consecSameToolMax: t.consecSameToolMax,
6860
- endedInInterrupt: t.endedInInterrupt,
6861
- models: t.models,
6862
- totalTokens: t.tokensIn + t.tokensOut + t.tokensCacheR + t.tokensCacheW,
6863
- cwd
6864
- });
6865
- }
6866
- function aggregateDay(dayEvents, _sessionFallbackProject) {
6867
- const prTitles = [];
6868
- let commits = 0;
6869
- let pushes = 0;
6870
- const gitToolUseIds = /* @__PURE__ */ new Set();
6871
- const githubRepos = /* @__PURE__ */ new Set();
6872
- const editedDirCounts = /* @__PURE__ */ new Map();
6873
- const modelTurns = /* @__PURE__ */ new Map();
6874
- let totalToolErrors = 0;
6875
- let totalInterrupts = 0;
6876
- let exitPlanCalls = 0;
6877
- let taskOpsTotal = 0;
6878
- let firstUser;
6879
- let finalAgent;
6880
- const cwdCounts = /* @__PURE__ */ new Map();
6881
- const closed = [];
6882
- let cur;
6883
- const allTs = [];
6884
- let lastEventCwd;
6885
- for (const ev of dayEvents) {
6886
- const tsMs = parseMs(ev.timestamp);
6887
- if (tsMs !== void 0)
6888
- allTs.push(tsMs);
6889
- const view = viewEvent(ev);
6890
- const rawType = view.rawType;
6891
- const msg = { content: view.content, id: view.msgId, model: view.model, usage: view.usage };
6892
- const content = view.content;
6893
- const eventCwd = view.cwd;
6894
- if (eventCwd)
6895
- lastEventCwd = eventCwd;
6896
- if (rawType === "user") {
6897
- const isToolResultOnly = Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
6898
- if (!isToolResultOnly) {
6899
- const text = firstTextBlock(content) || (typeof content === "string" ? content : "");
6900
- if (cur) {
6901
- if (text && INTERRUPT_RE2.test(text)) {
6902
- cur.endedInInterrupt = true;
6903
- totalInterrupts++;
6904
- }
6905
- closeTurn(cur, closed, eventCwd);
6906
- }
6907
- cur = newTurn(tsMs, text);
6908
- if (!firstUser && text && classifyUserInputSource(text) === "human") {
6909
- firstUser = text;
6910
- }
6911
- } else {
6912
- if (Array.isArray(content)) {
6913
- for (const c of content) {
6914
- if (!c || typeof c !== "object" || c.type !== "tool_result")
6915
- continue;
6916
- if (c.is_error) {
6917
- totalToolErrors++;
6918
- if (cur)
6919
- cur.toolErrors++;
6920
- }
6921
- const tuId = c.tool_use_id;
6922
- if (tuId && gitToolUseIds.has(tuId)) {
6923
- const rc = c.content;
6924
- const text = typeof rc === "string" ? rc : Array.isArray(rc) ? rc.map((b) => b && typeof b === "object" && typeof b.text === "string" ? b.text : "").join("\n") : "";
6925
- for (const m of text.matchAll(/(?<![.\w])github\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git\b|[/\s"'),]|$)/g)) {
6926
- if (m[1].toLowerCase() === "user-attachments")
6927
- continue;
6928
- githubRepos.add(`${m[1]}/${m[2]}`.toLowerCase());
6929
- }
6930
- }
6931
- }
6932
- }
6933
- if (cur && tsMs !== void 0) {
6934
- cur.eventTs.push(tsMs);
6935
- cur.lastMs = tsMs;
6936
- }
6937
- }
6938
- } else if (rawType === "assistant") {
6939
- if (cur && tsMs !== void 0) {
6940
- cur.eventTs.push(tsMs);
6941
- cur.lastMs = tsMs;
6942
- }
6943
- const model = msg.model;
6944
- if (model) {
6945
- modelTurns.set(model, (modelTurns.get(model) ?? 0) + 1);
6946
- if (cur)
6947
- cur.models.set(model, (cur.models.get(model) ?? 0) + 1);
6948
- }
6949
- const msgId = msg.id;
6950
- const usage = msg.usage;
6951
- if (cur && msgId && !cur.seenMsgIds.has(msgId) && usage) {
6952
- cur.seenMsgIds.add(msgId);
6953
- cur.tokensIn += usage.input_tokens ?? 0;
6954
- cur.tokensOut += usage.output_tokens ?? 0;
6955
- cur.tokensCacheR += usage.cache_read_input_tokens ?? 0;
6956
- cur.tokensCacheW += usage.cache_creation_input_tokens ?? 0;
6957
- }
6958
- if (Array.isArray(content)) {
6959
- for (const c of content) {
6960
- if (!c || typeof c !== "object")
6961
- continue;
6962
- const ct = c;
6963
- if (ct.type === "text" && ct.text) {
6964
- finalAgent = ct.text;
6965
- if (cur) {
6966
- if (cur.firstAgentText === void 0)
6967
- cur.firstAgentText = ct.text;
6968
- cur.lastAgentText = ct.text;
6969
- }
6970
- } else if (ct.type === "tool_use") {
6971
- const name = ct.name ?? "unknown";
6972
- if (cur) {
6973
- cur.toolsTotal++;
6974
- cur.toolMix.set(name, (cur.toolMix.get(name) ?? 0) + 1);
6975
- if (name === cur.lastTool)
6976
- cur.consec++;
6977
- else {
6978
- cur.lastTool = name;
6979
- cur.consec = 1;
6980
- }
6981
- if (cur.consec > cur.consecSameToolMax)
6982
- cur.consecSameToolMax = cur.consec;
6983
- if (name === "Skill") {
6984
- const sk = String(ct.input?.skill ?? "unknown");
6985
- cur.skills.set(sk, (cur.skills.get(sk) ?? 0) + 1);
6986
- }
6987
- if (name === "Agent") {
6988
- const inp = ct.input ?? {};
6989
- cur.subagents.push({
6990
- type: String(inp.subagent_type ?? "general-purpose"),
6991
- description: trunc(String(inp.description ?? ""), 80),
6992
- prompt_preview: trunc(String(inp.prompt ?? ""), 240),
6993
- background: Boolean(inp.run_in_background)
6994
- });
6995
- }
6996
- if (name === "ExitPlanMode")
6997
- exitPlanCalls++;
6998
- if (name === "TodoWrite" || name === "TaskCreate" || name === "TaskUpdate") {
6999
- cur.taskOps++;
7000
- taskOpsTotal++;
7001
- }
7002
- }
7003
- if (name === "Bash") {
7004
- const cmd = String(ct.input?.command ?? "");
7005
- if (cur)
7006
- cur.bashVerbs.set(bashVerb(cmd), (cur.bashVerbs.get(bashVerb(cmd)) ?? 0) + 1);
7007
- if (/\bgh\s+pr\s+create\b/.test(cmd)) {
7008
- const m = cmd.match(PR_TITLE_RE);
7009
- prTitles.push(m?.[1] ?? cmd.slice(0, 120));
7010
- }
7011
- if (/\bgit\s+commit\b/.test(cmd))
7012
- commits++;
7013
- if (/\bgit\s+push\b/.test(cmd))
7014
- pushes++;
7015
- if (ct.id && /\bgit\s+push\b|\bgh\s+pr\s+/.test(cmd))
7016
- gitToolUseIds.add(ct.id);
7017
- }
7018
- if (eventCwd && (name === "Bash" || name === "Edit" || name === "Write" || name === "Read")) {
7019
- cwdCounts.set(eventCwd, (cwdCounts.get(eventCwd) ?? 0) + 1);
7020
- }
7021
- if (name === "Edit" || name === "Write" || name === "MultiEdit" || name === "NotebookEdit") {
7022
- const fp = String(ct.input?.file_path ?? ct.input?.notebook_path ?? "");
7023
- const slash = fp.lastIndexOf("/");
7024
- if (slash > 0) {
7025
- const dir = fp.slice(0, slash);
7026
- editedDirCounts.set(dir, (editedDirCounts.get(dir) ?? 0) + 1);
7027
- }
7028
- }
7029
- }
7030
- }
7031
- }
7032
- }
7101
+ const lines = [];
7102
+ for (const line of raw.split("\n")) {
7103
+ const m = line.match(/^(\S+) (INFO|WARN|ERROR) (.*)$/);
7104
+ if (!m) continue;
7105
+ const [, ts, levelRaw, msg] = m;
7106
+ if (!msg.startsWith("[sync] ")) continue;
7107
+ if (watermark && ts <= watermark) continue;
7108
+ const clipped = msg.length > MAX_MSG_CHARS ? msg.slice(0, MAX_MSG_CHARS) + "\u2026" : msg;
7109
+ lines.push({ ts, level: levelRaw.toLowerCase(), msg: clipped });
7033
7110
  }
7034
- if (cur)
7035
- closeTurn(cur, closed, lastEventCwd);
7036
- const tsSorted = [...allTs].sort((a, b) => a - b);
7037
- let activeMs2 = 0;
7038
- for (let i = 1; i < tsSorted.length; i++) {
7039
- const dt = tsSorted[i] - tsSorted[i - 1];
7040
- if (dt < IDLE_GAP_MS4)
7041
- activeMs2 += dt;
7111
+ const capped = lines.slice(0, MAX_LINES);
7112
+ return { lines: capped, watermark: capped.length ? capped[capped.length - 1].ts : void 0 };
7113
+ }
7114
+
7115
+ // src/team/last-push.ts
7116
+ init_fs();
7117
+ import { writeFileSync as writeFileSync6, unlinkSync as unlinkSync2, mkdirSync as mkdirSync7 } from "node:fs";
7118
+ import { join as join6 } from "node:path";
7119
+ var LAST_PUSH_FILE = "team-last-push.json";
7120
+ function write(record, dir) {
7121
+ const d = dir ?? cclensHome();
7122
+ mkdirSync7(d, { recursive: true });
7123
+ writeFileSync6(join6(d, LAST_PUSH_FILE), JSON.stringify(record, null, 2), { mode: 384 });
7124
+ }
7125
+ function writeLastPushSuccess(payload, dir) {
7126
+ write({ pushedAt: (/* @__PURE__ */ new Date()).toISOString(), ok: true, payload }, dir);
7127
+ }
7128
+ function writeLastPushFailure(payload, error, dir) {
7129
+ write({ pushedAt: (/* @__PURE__ */ new Date()).toISOString(), ok: false, payload, error }, dir);
7130
+ }
7131
+
7132
+ // src/team/commands.ts
7133
+ async function dispatchCommand(command, config, log3) {
7134
+ switch (command.type) {
7135
+ case "backfill-activity":
7136
+ return runActivityBackfill(command, config, log3);
7137
+ default:
7138
+ return {
7139
+ id: command.id,
7140
+ ok: false,
7141
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7142
+ error: `Unknown command type: ${command.type}`
7143
+ };
7042
7144
  }
7043
- const tsSortedStrings = dayEvents.map((e) => e.timestamp).filter((t) => !!t).sort();
7044
- const start_iso = tsSortedStrings[0] ?? "";
7045
- const end_iso = tsSortedStrings[tsSortedStrings.length - 1] ?? "";
7145
+ }
7146
+ async function runActivityBackfill(command, config, log3) {
7147
+ const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
7148
+ const { toLocalDay: toLocalDay3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
7149
+ const { probeArtifactSignals: probeArtifactSignals2 } = await Promise.resolve().then(() => (init_file_probe(), file_probe_exports));
7150
+ const days = command.params.days;
7151
+ const todayMs = Date.now();
7152
+ const targetDayMs = todayMs - (days - 1) * 24 * 60 * 60 * 1e3;
7153
+ const targetDay = toLocalDay3(targetDayMs);
7154
+ log3("info", `command ${command.id}: backfill-activity from ${targetDay} (${days} days)`);
7155
+ let sessions;
7156
+ try {
7157
+ sessions = await listSessions2({ limit: 1e4 });
7158
+ } catch (err) {
7159
+ return {
7160
+ id: command.id,
7161
+ ok: false,
7162
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7163
+ error: `Failed to read sessions: ${err.message}`
7164
+ };
7165
+ }
7166
+ const rollups = buildRollupsForRange(sessions, targetDay);
7167
+ if (rollups.length === 0) {
7168
+ return {
7169
+ id: command.id,
7170
+ ok: true,
7171
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7172
+ summary: { pushed: 0, fromDay: targetDay }
7173
+ };
7174
+ }
7175
+ const resolveRepo = createRepoResolver();
7176
+ let pushed = 0;
7177
+ for (const rollup of rollups) {
7178
+ const daySessions = sessions.filter((s) => sessionTouchesDay(s, rollup.day));
7179
+ const richBlocks = buildRichBlocksForDay(rollup.day, daySessions, resolveRepo);
7180
+ let artifactSignals = null;
7181
+ try {
7182
+ artifactSignals = probeArtifactSignals2({ day: rollup.day, extraRoots: [process.cwd()] });
7183
+ } catch {
7184
+ }
7185
+ const payload = buildIngestPayload({
7186
+ rollup,
7187
+ richExtras: richBlocks?.rich,
7188
+ enrichedExtras: richBlocks?.enriched,
7189
+ artifactSignals: artifactSignals ?? void 0
7190
+ });
7191
+ const result = await pushToTeamServer(config, payload);
7192
+ if (!result.ok) {
7193
+ return {
7194
+ id: command.id,
7195
+ ok: false,
7196
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7197
+ error: `Push failed on ${rollup.day} (HTTP ${result.status}); pushed ${pushed}/${rollups.length} before failing`
7198
+ };
7199
+ }
7200
+ pushed++;
7201
+ }
7202
+ log3("info", `command ${command.id}: backfill-activity ok \u2014 pushed ${pushed} day${pushed === 1 ? "" : "s"}`);
7046
7203
  return {
7047
- closed,
7048
- firstUser,
7049
- finalAgent,
7050
- prTitles,
7051
- commits,
7052
- pushes,
7053
- githubRepos: [...githubRepos].slice(0, 5),
7054
- editedDirs: [...editedDirCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([dir]) => dir),
7055
- modelTurns,
7056
- totalToolErrors,
7057
- totalInterrupts,
7058
- exitPlanCalls,
7059
- taskOpsTotal,
7060
- cwdCounts,
7061
- start_iso,
7062
- end_iso,
7063
- activeMs: activeMs2
7204
+ id: command.id,
7205
+ ok: true,
7206
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
7207
+ summary: { pushed, fromDay: targetDay }
7064
7208
  };
7065
7209
  }
7066
- function buildTopTools(closed) {
7067
- const toolMix = /* @__PURE__ */ new Map();
7068
- const bashVerbsAll = /* @__PURE__ */ new Map();
7069
- for (const t of closed) {
7070
- for (const [k, v] of t.toolMix)
7071
- toolMix.set(k, (toolMix.get(k) ?? 0) + v);
7072
- for (const [k, v] of t.bashVerbs)
7073
- bashVerbsAll.set(k, (bashVerbsAll.get(k) ?? 0) + v);
7210
+
7211
+ // src/team/sync.ts
7212
+ init_fs();
7213
+ var inFlightCommands = /* @__PURE__ */ new Set();
7214
+ var USAGE_LOG2 = cclensPath("usage.jsonl");
7215
+ var PROFILE_CACHE2 = cclensPath("profile.json");
7216
+ var noopLog2 = () => {
7217
+ };
7218
+ function syncLogDelivered(body) {
7219
+ const blocks = body?.blocks;
7220
+ return !blocks || blocks.accepted.includes("syncLog");
7221
+ }
7222
+ function levelForStatus(s) {
7223
+ if (s === "ok" || s === "idle") return "info";
7224
+ if (s === "degraded" || s === "failed") return "warn";
7225
+ return "error";
7226
+ }
7227
+ function fmtDuration(ms) {
7228
+ return ms >= 1e3 ? `${(ms / 1e3).toFixed(1)}s` : `${ms}ms`;
7229
+ }
7230
+ function liveSnapshotStr(snap) {
7231
+ if (!snap) return void 0;
7232
+ const f = snap.fiveHour?.utilization;
7233
+ const s = snap.sevenDay?.utilization;
7234
+ const parts = [
7235
+ typeof f === "number" ? `5h ${Math.round(f)}%` : null,
7236
+ typeof s === "number" ? `7d ${Math.round(s)}%` : null
7237
+ ].filter(Boolean);
7238
+ return parts.length ? parts.join(" / ") : void 0;
7239
+ }
7240
+ function buildSyncLine(status, trigger, s, elapsedMs, nextSyncMs) {
7241
+ const parts = [`[sync] ${status}`, trigger];
7242
+ if (s.pushedDays.length) {
7243
+ parts.push(
7244
+ s.pushedDays.length === 1 ? `pushed 1 day (${s.pushedDays[0]})` : `pushed ${s.pushedDays.length} days (${s.pushedDays[0]}\u2192${s.pushedDays[s.pushedDays.length - 1]})`
7245
+ );
7074
7246
  }
7075
- const top = [...toolMix.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
7076
- return top.map(([name, count]) => {
7077
- if (name === "Bash" && bashVerbsAll.size > 0) {
7078
- const sub = [...bashVerbsAll.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([v, c]) => c > 1 ? `${v}\xD7${c}` : v).join(", ");
7079
- return `Bash\xD7${count} (${sub})`;
7080
- }
7081
- return count > 1 ? `${name}\xD7${count}` : name;
7082
- });
7247
+ if (s.queued) {
7248
+ parts.push(
7249
+ s.queuedDay ? `queued ${s.queued} for retry (${s.queuedDay}, HTTP ${s.queuedStatus})` : `queued ${s.queued} for retry`
7250
+ );
7251
+ }
7252
+ if (s.droppedDays.length) {
7253
+ parts.push(`dropped ${s.droppedDays.length} unrecoverable (${s.droppedDays.join(",")})`);
7254
+ }
7255
+ if (s.recoveredDays?.length) {
7256
+ parts.push(
7257
+ `recovered ${s.recoveredDays.length} dropped day${s.recoveredDays.length === 1 ? "" : "s"} (${s.recoveredDays.join(",")})`
7258
+ );
7259
+ }
7260
+ if (s.queuedDrained) parts.push(`drained ${s.queuedDrained} from backlog`);
7261
+ if (s.idleReason) parts.push(s.idleReason);
7262
+ if (s.live) parts.push(`live ${s.live}`);
7263
+ if (s.usageSnapshots > 0) {
7264
+ parts.push(`usage +${s.usageSnapshots} snapshot${s.usageSnapshots === 1 ? "" : "s"}`);
7265
+ }
7266
+ if (s.accepted?.length) parts.push(`server accepted ${s.accepted.join(",")}`);
7267
+ if (s.skipped && Object.keys(s.skipped).length) {
7268
+ parts.push(
7269
+ `server SKIPPED ${Object.entries(s.skipped).map(([k, v]) => `${k} (${v})`).join("; ")}`
7270
+ );
7271
+ }
7272
+ if (s.errorMsg) parts.push(s.errorMsg);
7273
+ parts.push(fmtDuration(elapsedMs));
7274
+ if (nextSyncMs) parts.push(`next ~${Math.round(nextSyncMs / 6e4)}m`);
7275
+ return parts.join(" \xB7 ");
7083
7276
  }
7084
- function buildEntries(sessionDetail) {
7085
- const byDay = groupEventsByLocalDay(sessionDetail.events);
7086
- const entries = [];
7087
- const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
7088
- const sessionFallbackProject = canonicalProjectName(sessionDetail.projectName ?? "");
7089
- for (const [local_day, dayEvents] of byDay) {
7090
- if (dayEvents.length === 0)
7091
- continue;
7092
- const agg = aggregateDay(dayEvents, sessionFallbackProject);
7093
- const { closed } = agg;
7094
- const turnCount = closed.length;
7095
- const toolsTotal = closed.reduce((s, t) => s + t.toolsTotal, 0);
7096
- const tokensTotal = closed.reduce((s, t) => s + t.totalTokens, 0);
7097
- const subagentCalls = closed.reduce((s, t) => s + t.subagents.length, 0);
7098
- const skillsMap = /* @__PURE__ */ new Map();
7099
- for (const t of closed) {
7100
- for (const [k, v] of t.skills)
7101
- skillsMap.set(k, (skillsMap.get(k) ?? 0) + v);
7277
+ async function runTeamSync(log3 = noopLog2, configOverride, options = {}) {
7278
+ const config = configOverride === void 0 ? readTeamConfig() : configOverride;
7279
+ if (!config) return { paired: false, pushed: 0, queued: 0, queuedDrained: 0 };
7280
+ const startedAt = Date.now();
7281
+ const trigger = options.trigger ?? "auto";
7282
+ const summary = {
7283
+ pushedDays: [],
7284
+ droppedDays: [],
7285
+ queued: 0,
7286
+ queuedDrained: 0,
7287
+ usageSnapshots: 0
7288
+ };
7289
+ const finish = (status) => {
7290
+ log3(
7291
+ levelForStatus(status),
7292
+ buildSyncLine(status, trigger, summary, Date.now() - startedAt, options.nextSyncMs)
7293
+ );
7294
+ };
7295
+ const recordVerdict = (body) => {
7296
+ if (body?.blocks) {
7297
+ summary.accepted = [.../* @__PURE__ */ new Set([...summary.accepted ?? [], ...body.blocks.accepted])];
7298
+ summary.skipped = { ...summary.skipped, ...body.blocks.skipped };
7102
7299
  }
7103
- const skillCalls = [...skillsMap.values()].reduce((a, b) => a + b, 0);
7104
- const taskOps = agg.taskOpsTotal;
7105
- const interrupts = agg.totalInterrupts;
7106
- const toolErrors = agg.totalToolErrors;
7107
- const consecMax = Math.max(0, ...closed.map((t) => t.consecSameToolMax));
7108
- const exitPlanCalls = agg.exitPlanCalls;
7109
- const prs = agg.prTitles.length;
7110
- const commits = agg.commits;
7111
- const pushes = agg.pushes;
7112
- const activeMin = Math.round(agg.activeMs / 6e3) / 10;
7113
- const numbers = {
7114
- active_min: activeMin,
7115
- turn_count: turnCount,
7116
- tools_total: toolsTotal,
7117
- subagent_calls: subagentCalls,
7118
- skill_calls: skillCalls,
7119
- task_ops: taskOps,
7120
- interrupts,
7121
- tool_errors: toolErrors,
7122
- consec_same_tool_max: consecMax,
7123
- exit_plan_calls: exitPlanCalls,
7124
- prs,
7125
- commits,
7126
- pushes,
7127
- tokens_total: tokensTotal
7300
+ };
7301
+ try {
7302
+ resetEnsuredSessions();
7303
+ const { listSessions: listSessions2, loadCalibrationCurve: loadCalibrationCurve2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
7304
+ const { toLocalDay: toLocalDay3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
7305
+ const today = toLocalDay3(Date.now());
7306
+ let nextConfig = { ...config };
7307
+ const persistConfig = (patch) => {
7308
+ nextConfig = { ...nextConfig, ...patch };
7309
+ writeTeamConfig(nextConfig);
7310
+ };
7311
+ const pendingLog = readPendingSyncLog(config.lastSyncedLogAt);
7312
+ const collectedCommands = /* @__PURE__ */ new Map();
7313
+ const collectCommands = (commands) => {
7314
+ if (!commands) return;
7315
+ for (const cmd of commands) {
7316
+ if (!collectedCommands.has(cmd.id)) collectedCommands.set(cmd.id, cmd);
7317
+ }
7318
+ };
7319
+ const dispatchAndReport = async () => {
7320
+ try {
7321
+ if (collectedCommands.size === 0) return;
7322
+ const results = [];
7323
+ for (const cmd of collectedCommands.values()) {
7324
+ if (inFlightCommands.has(cmd.id)) continue;
7325
+ inFlightCommands.add(cmd.id);
7326
+ try {
7327
+ const result = await dispatchCommand(cmd, config, log3);
7328
+ results.push(result);
7329
+ } finally {
7330
+ inFlightCommands.delete(cmd.id);
7331
+ }
7332
+ }
7333
+ if (results.length === 0) return;
7334
+ const resultsPayload = {
7335
+ ingestId: randomUUID3(),
7336
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
7337
+ commandResults: results
7338
+ };
7339
+ const r = await pushToTeamServer(config, resultsPayload);
7340
+ if (!r.ok) {
7341
+ log3("warn", `team commandResults push failed (${r.status}); will retry on next sync`);
7342
+ } else {
7343
+ log3("info", `team commandResults push ok: ${results.length} result${results.length === 1 ? "" : "s"}`);
7344
+ }
7345
+ } catch (err) {
7346
+ log3("warn", `team command dispatch error: ${err.message}`);
7347
+ }
7348
+ };
7349
+ const isValidationPoison = (status2) => status2 === 400 || status2 === 422;
7350
+ const drainBacklog = async () => {
7351
+ let drained = 0;
7352
+ const backlog = dequeuePayloads();
7353
+ for (let i = 0; i < backlog.length; i++) {
7354
+ const qResult = await pushToTeamServer(config, backlog[i]);
7355
+ if (!qResult.ok) {
7356
+ if (isValidationPoison(qResult.status)) {
7357
+ log3("warn", `team push: queued payload rejected (HTTP ${qResult.status}); dropping unrecoverable item`);
7358
+ continue;
7359
+ }
7360
+ for (const remaining of backlog.slice(i)) enqueuePayload(remaining);
7361
+ break;
7362
+ }
7363
+ collectCommands(qResult.body?.commands);
7364
+ drained++;
7365
+ }
7366
+ return drained;
7128
7367
  };
7129
- const longestTurnActiveMs = Math.max(0, ...closed.map((t) => t.activeMs));
7130
- const flags = [];
7131
- if (interrupts >= 3)
7132
- flags.push("interrupt_heavy");
7133
- if (toolErrors >= 20)
7134
- flags.push("high_errors");
7135
- if (consecMax >= 8)
7136
- flags.push("loop_suspected");
7137
- if (agg.activeMs < 5 * 6e4 && prs >= 1)
7138
- flags.push("fast_ship");
7139
- if (exitPlanCalls > 0)
7140
- flags.push("plan_used");
7141
- const subagentTurns = closed.filter((t) => t.subagents.length >= 1).length;
7142
- if (subagentTurns >= 3)
7143
- flags.push("orchestrated");
7144
- if (longestTurnActiveMs >= 20 * 6e4 && interrupts === 0)
7145
- flags.push("long_autonomous");
7146
- const primaryModel = [...agg.modelTurns.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]?.[0] ?? null;
7147
- const modelMix = Object.fromEntries(agg.modelTurns);
7148
- const firstUserRaw = agg.firstUser ?? "";
7149
- const first_user = trunc(minimizeImages(firstUserRaw), 400);
7150
- const finalAgentRaw = agg.finalAgent ?? "";
7151
- const final_agent = trunc(minimizeImages(finalAgentRaw), 400);
7152
- let project = sessionFallbackProject;
7153
- if (agg.cwdCounts.size > 0) {
7154
- const best = [...agg.cwdCounts.entries()].sort((a, b) => b[1] - a[1])[0];
7155
- project = canonicalProjectName(best[0]);
7368
+ const usageBackfill = await runTeamBackfill(log3, USAGE_LOG2, config, {
7369
+ sinceCapturedAt: options.forceUsageBackfill ? void 0 : config.lastSyncedUsageSnapshotAt
7370
+ });
7371
+ if (usageBackfill.lastSnapshotAt) {
7372
+ persistConfig({ lastSyncedUsageSnapshotAt: usageBackfill.lastSnapshotAt });
7156
7373
  }
7157
- const top_tools = buildTopTools(closed);
7158
- const skills = Object.fromEntries(skillsMap);
7159
- const subagents = closed.flatMap((t) => t.subagents);
7160
- const humanText = dayEvents.filter((ev) => {
7161
- const v = viewEvent(ev);
7162
- if (v.rawType !== "user")
7163
- return false;
7164
- const content = v.content;
7165
- const isToolResultOnly = content.length > 0 && content.every((c) => c.type === "tool_result");
7166
- if (isToolResultOnly)
7167
- return false;
7168
- const text = blockText(ev.blocks) || ev.preview || "";
7169
- return classifyUserInputSource(text) === "human";
7170
- }).map((ev) => blockText(ev.blocks) || ev.preview || "").join("\n");
7171
- const satisfaction_signals = countSatisfactionSignals(humanText);
7172
- const user_input_sources = {
7173
- human: 0,
7174
- teammate: 0,
7175
- skill_load: 0,
7176
- slash_command: 0,
7177
- system_instruction: 0
7178
- };
7179
- for (const ev of dayEvents) {
7180
- const v = viewEvent(ev);
7181
- if (v.rawType !== "user")
7182
- continue;
7183
- const content = v.content;
7184
- const isToolResultOnly = content.length > 0 && content.every((c) => c.type === "tool_result");
7185
- if (isToolResultOnly)
7186
- continue;
7187
- const text = blockText(ev.blocks) || ev.preview || "";
7188
- const src = classifyUserInputSource(text);
7189
- user_input_sources[src]++;
7374
+ const sessions = await listSessions2({ limit: 1e4 });
7375
+ const allRollups = buildRollupsForRange(sessions);
7376
+ const rollups = allRollups.filter((r) => !config.lastSyncedDay || r.day >= config.lastSyncedDay);
7377
+ const usageSnapshot = readLatestUsageSnapshotForWire(USAGE_LOG2) ?? void 0;
7378
+ const planTier = await getPlanTier(PROFILE_CACHE2).catch(() => null) ?? void 0;
7379
+ const cyclePeaks = await buildCyclePeaksForPush(planTier, loadCalibrationCurve2);
7380
+ if (rollups.length === 0) {
7381
+ const hasLiveData = Boolean(usageSnapshot || planTier || cyclePeaks);
7382
+ if (!hasLiveData) {
7383
+ summary.usageSnapshots = usageBackfill.sentSnapshots;
7384
+ if (usageBackfill.sentSnapshots > 0) {
7385
+ finish("ok");
7386
+ } else {
7387
+ summary.idleReason = "nothing to sync";
7388
+ finish("idle");
7389
+ }
7390
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, usageBackfill };
7391
+ }
7392
+ summary.usageSnapshots = usageBackfill.sentSnapshots;
7393
+ summary.live = liveSnapshotStr(usageSnapshot);
7394
+ const payload = buildIngestPayload({ usageSnapshot, planTier, cyclePeaks, syncLog: pendingLog.lines });
7395
+ const result = await pushToTeamServer(config, payload);
7396
+ if (!result.ok) {
7397
+ if (isValidationPoison(result.status)) {
7398
+ const errLine2 = `team push: live-only payload rejected (HTTP ${result.status}); dropping unrecoverable payload`;
7399
+ writeLastPushFailure(payload, errLine2);
7400
+ summary.errorMsg = `live-snapshot push HTTP ${result.status} (validation) \u2014 dropped unrecoverable`;
7401
+ finish("error");
7402
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, usageBackfill };
7403
+ }
7404
+ const errLine = `team push failed (${result.status})`;
7405
+ writeLastPushFailure(payload, errLine);
7406
+ enqueuePayload(payload);
7407
+ summary.queued = 1;
7408
+ summary.errorMsg = `live-snapshot push HTTP ${result.status} \u2014 queued for retry`;
7409
+ finish("failed");
7410
+ return { paired: true, pushed: 0, queued: 1, queuedDrained: 0, usageBackfill };
7411
+ }
7412
+ writeLastPushSuccess(payload);
7413
+ if (pendingLog.watermark && syncLogDelivered(result.body)) {
7414
+ persistConfig({ lastSyncedLogAt: pendingLog.watermark });
7415
+ }
7416
+ collectCommands(result.body?.commands);
7417
+ recordVerdict(result.body);
7418
+ const queuedDrained2 = await drainBacklog();
7419
+ summary.queuedDrained = queuedDrained2;
7420
+ summary.idleReason = "no new daily activity";
7421
+ finish(summary.skipped && Object.keys(summary.skipped).length ? "degraded" : "idle");
7422
+ await dispatchAndReport();
7423
+ return { paired: true, pushed: 1, queued: 0, queuedDrained: queuedDrained2, usageBackfill };
7190
7424
  }
7191
- const pendingUserInstructions = extractUserInstructions(humanText).slice(0, 5);
7192
- const trivial = isTrivial(numbers);
7193
- const entry = {
7194
- version: CURRENT_ENTRY_SCHEMA_VERSION,
7195
- agent: sessionDetail.agent,
7196
- session_id: sessionDetail.id,
7197
- local_day,
7198
- project,
7199
- start_iso: agg.start_iso,
7200
- end_iso: agg.end_iso,
7201
- numbers,
7202
- flags,
7203
- primary_model: primaryModel,
7204
- model_mix: modelMix,
7205
- first_user,
7206
- final_agent,
7207
- pr_titles: agg.prTitles,
7208
- ...agg.githubRepos.length ? { github_repos: agg.githubRepos } : {},
7209
- ...agg.editedDirs.length ? { edited_dirs: agg.editedDirs } : {},
7210
- top_tools,
7211
- skills,
7212
- subagents,
7213
- satisfaction_signals,
7214
- user_input_sources,
7215
- enrichment: trivial ? skippedTrivialEnrichment(generatedAt) : { ...pendingEnrichment(), user_instructions: pendingUserInstructions },
7216
- generated_at: generatedAt,
7217
- source_jsonl: sessionDetail.filePath ?? "",
7218
- source_checkpoint: { byte_offset: 0, last_event_ts: agg.end_iso || null }
7425
+ let pushed = 0;
7426
+ let queued = 0;
7427
+ let failedDay;
7428
+ let lastResolvedDay;
7429
+ const droppedDaysBefore = config.droppedDays ?? [];
7430
+ const workingDropped = [...droppedDaysBefore];
7431
+ let syncLogWasDelivered = false;
7432
+ const resolveRepo = createRepoResolver();
7433
+ const { probeArtifactSignals: probeArtifactSignals2 } = await Promise.resolve().then(() => (init_file_probe(), file_probe_exports));
7434
+ const buildDayPayload = (rollup, opts) => {
7435
+ const daySessions = sessions.filter((s) => sessionTouchesDay(s, rollup.day));
7436
+ const richBlocks = buildRichBlocksForDay(rollup.day, daySessions, resolveRepo);
7437
+ let artifactSignals = null;
7438
+ try {
7439
+ artifactSignals = probeArtifactSignals2({ day: rollup.day, extraRoots: [process.cwd()] });
7440
+ } catch {
7441
+ }
7442
+ return buildIngestPayload({
7443
+ rollup,
7444
+ richExtras: richBlocks?.rich,
7445
+ enrichedExtras: richBlocks?.enriched,
7446
+ artifactSignals: artifactSignals ?? void 0,
7447
+ // Current-cycle data only on the latest day so older days aren't tagged
7448
+ // with today's snapshot / peaks.
7449
+ usageSnapshot: opts.latest ? usageSnapshot : void 0,
7450
+ planTier,
7451
+ cyclePeaks: opts.latest ? cyclePeaks : void 0,
7452
+ syncLog: opts.syncLog
7453
+ });
7219
7454
  };
7220
- entry.signals = computeEntrySignals(entry);
7221
- entries.push(entry);
7455
+ for (let i = 0; i < rollups.length; i++) {
7456
+ const rollup = rollups[i];
7457
+ const isLatest = i === rollups.length - 1;
7458
+ const payload = buildDayPayload(rollup, {
7459
+ latest: isLatest,
7460
+ syncLog: i === 0 ? pendingLog.lines : void 0
7461
+ });
7462
+ const result = await pushToTeamServer(config, payload);
7463
+ if (!result.ok) {
7464
+ if (isValidationPoison(result.status)) {
7465
+ const errLine2 = `team push: ${rollup.day} rejected (HTTP ${result.status}); skipping past unrecoverable day`;
7466
+ writeLastPushFailure(payload, errLine2);
7467
+ summary.droppedDays.push(rollup.day);
7468
+ if (!workingDropped.includes(rollup.day)) workingDropped.push(rollup.day);
7469
+ lastResolvedDay = rollup.day;
7470
+ continue;
7471
+ }
7472
+ const errLine = `team push failed on ${rollup.day} (${result.status})`;
7473
+ writeLastPushFailure(payload, errLine);
7474
+ enqueuePayload(payload);
7475
+ queued++;
7476
+ summary.queuedDay = rollup.day;
7477
+ summary.queuedStatus = result.status;
7478
+ failedDay = rollup.day;
7479
+ break;
7480
+ }
7481
+ writeLastPushSuccess(payload);
7482
+ collectCommands(result.body?.commands);
7483
+ recordVerdict(result.body);
7484
+ if (i === 0) syncLogWasDelivered = syncLogDelivered(result.body);
7485
+ pushed++;
7486
+ summary.pushedDays.push(rollup.day);
7487
+ lastResolvedDay = rollup.day;
7488
+ }
7489
+ if (!failedDay) {
7490
+ const retryDay = [...droppedDaysBefore].sort().find((d) => !summary.droppedDays.includes(d));
7491
+ const retryRollup = retryDay ? allRollups.find((r) => r.day === retryDay) : void 0;
7492
+ if (retryDay && retryRollup) {
7493
+ const payload = buildDayPayload(retryRollup, { latest: false });
7494
+ const result = await pushToTeamServer(config, payload);
7495
+ if (result.ok) {
7496
+ writeLastPushSuccess(payload);
7497
+ collectCommands(result.body?.commands);
7498
+ recordVerdict(result.body);
7499
+ const idx = workingDropped.indexOf(retryDay);
7500
+ if (idx >= 0) workingDropped.splice(idx, 1);
7501
+ summary.recoveredDays = [retryDay];
7502
+ } else {
7503
+ writeLastPushFailure(payload, `team push: dropped-day retry ${retryDay} failed (HTTP ${result.status})`);
7504
+ }
7505
+ }
7506
+ }
7507
+ const nextDropped = [...new Set(workingDropped)].sort().slice(-90);
7508
+ const prevDropped = [...new Set(droppedDaysBefore)].sort();
7509
+ const droppedChanged = nextDropped.length !== prevDropped.length || nextDropped.some((d, i) => d !== prevDropped[i]);
7510
+ const droppedPatch = droppedChanged ? { droppedDays: nextDropped } : {};
7511
+ if (failedDay) {
7512
+ if (lastResolvedDay || droppedChanged) {
7513
+ persistConfig({ ...lastResolvedDay ? { lastSyncedDay: lastResolvedDay } : {}, ...droppedPatch });
7514
+ }
7515
+ } else {
7516
+ persistConfig({ lastSyncedDay: today, ...droppedPatch });
7517
+ }
7518
+ if (syncLogWasDelivered && pendingLog.watermark) {
7519
+ persistConfig({ lastSyncedLogAt: pendingLog.watermark });
7520
+ }
7521
+ const queuedDrained = !failedDay ? await drainBacklog() : 0;
7522
+ summary.queued = queued;
7523
+ summary.queuedDrained = queuedDrained;
7524
+ summary.usageSnapshots = usageBackfill.sentSnapshots;
7525
+ let status;
7526
+ if (failedDay) status = "failed";
7527
+ else if (summary.droppedDays.length || summary.skipped && Object.keys(summary.skipped).length) status = "degraded";
7528
+ else status = "ok";
7529
+ finish(status);
7530
+ await dispatchAndReport();
7531
+ return { paired: true, pushed, queued, queuedDrained, usageBackfill, failedDay };
7532
+ } catch (err) {
7533
+ const message = err.message;
7534
+ summary.errorMsg = `sync aborted: ${message}`;
7535
+ finish("error");
7536
+ writeLastPushFailure(
7537
+ { ingestId: "n/a", observedAt: (/* @__PURE__ */ new Date()).toISOString() },
7538
+ `team push error: ${message}`
7539
+ );
7540
+ return { paired: true, pushed: 0, queued: 0, queuedDrained: 0, error: message };
7222
7541
  }
7223
- entries.sort((a, b) => a.local_day.localeCompare(b.local_day));
7224
- return entries;
7225
7542
  }
7543
+ async function buildCyclePeaksForPush(planTier, loadCurve) {
7544
+ const validTiers = ["pro", "pro-max", "pro-max-20x", "custom"];
7545
+ const tier = validTiers.find((t) => t === planTier) ?? "pro-max-20x";
7546
+ const dump = await loadCurve(tier).catch(() => null);
7547
+ if (!dump || dump.curve.length === 0) return void 0;
7548
+ const HOUR = 36e5;
7549
+ const nowMs = Date.now();
7550
+ const peaksFor = (cycleKey, realKey, predKey, maxCycles) => {
7551
+ const TOLERANCE_MS = cycleKey === "cycle_end_7d" ? 12 * HOUR : 0;
7552
+ const byCycle = /* @__PURE__ */ new Map();
7553
+ for (const p of dump.curve) {
7554
+ const k = p[cycleKey];
7555
+ if (!k) continue;
7556
+ const ms = Date.parse(k);
7557
+ if (Number.isNaN(ms)) continue;
7558
+ const bucket = Math.round(ms / HOUR) * HOUR;
7559
+ const arr = byCycle.get(bucket) ?? [];
7560
+ arr.push(p);
7561
+ byCycle.set(bucket, arr);
7562
+ }
7563
+ const merged = [];
7564
+ for (const [endMs, points] of Array.from(byCycle.entries()).sort((a, b) => a[0] - b[0])) {
7565
+ const last = merged[merged.length - 1];
7566
+ if (last && endMs - last.endMs <= TOLERANCE_MS) {
7567
+ last.endMs = endMs;
7568
+ last.points.push(...points);
7569
+ } else {
7570
+ merged.push({ endMs, points: [...points] });
7571
+ }
7572
+ }
7573
+ const out = [];
7574
+ for (const { endMs, points } of merged) {
7575
+ let peak = 0;
7576
+ let source = "predicted";
7577
+ for (const p of points) {
7578
+ const r = p[realKey];
7579
+ if (typeof r === "number" && r > peak) {
7580
+ peak = r;
7581
+ source = "real";
7582
+ }
7583
+ const v = p[predKey] ?? 0;
7584
+ if (v > peak) {
7585
+ peak = v;
7586
+ source = "predicted";
7587
+ }
7588
+ }
7589
+ out.push({
7590
+ endsAt: new Date(endMs).toISOString(),
7591
+ peakPct: Math.round(peak * 10) / 10,
7592
+ source,
7593
+ current: endMs > nowMs
7594
+ });
7595
+ }
7596
+ return out.slice(-maxCycles);
7597
+ };
7598
+ return {
7599
+ // 7d cycle history cap (26 ≈ six months) MUST stay in lock-step with two
7600
+ // downstream sites or the strip silently truncates: the server read
7601
+ // (loadMembership7dCyclePeaks maxCyclesPerMember in plan-queries.ts) and
7602
+ // the renderer (CyclePeaksStrip maxBars in member-plan-block.tsx). The 5h
7603
+ // cap (24) is independent — leave it.
7604
+ fiveHour: peaksFor("cycle_end_5h", "real_5h", "pred_5h", 24),
7605
+ sevenDay: peaksFor("cycle_end_7d", "real_7d", "pred_7d", 26)
7606
+ };
7607
+ }
7608
+
7609
+ // src/perception/worker.ts
7610
+ import { statSync as statSync5 } from "node:fs";
7611
+ init_fs();
7226
7612
 
7227
7613
  // src/perception/state.ts
7228
7614
  init_fs();
7229
- import { existsSync as existsSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, renameSync as renameSync3 } from "node:fs";
7230
- import { dirname as dirname7 } from "node:path";
7615
+ import { existsSync as existsSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, renameSync as renameSync3 } from "node:fs";
7616
+ import { dirname as dirname8 } from "node:path";
7231
7617
  var STALE_MS = 15 * 60 * 1e3;
7232
7618
  var pathCached = null;
7233
7619
  function statePath() {
@@ -7245,12 +7631,12 @@ function readState() {
7245
7631
  file_checkpoints: {}
7246
7632
  };
7247
7633
  }
7248
- const raw = readFileSync9(p, "utf8");
7634
+ const raw = readFileSync11(p, "utf8");
7249
7635
  return JSON.parse(raw);
7250
7636
  }
7251
7637
  function writeState(s) {
7252
7638
  const p = statePath();
7253
- mkdirSync8(dirname7(p), { recursive: true });
7639
+ mkdirSync8(dirname8(p), { recursive: true });
7254
7640
  const tmp = `${p}.tmp`;
7255
7641
  writeFileSync7(tmp, JSON.stringify(s, null, 2), "utf8");
7256
7642
  renameSync3(tmp, p);
@@ -7306,12 +7692,6 @@ async function listAllSessionJsonls(root) {
7306
7692
  function log(msg) {
7307
7693
  console.error(`[perception] ${msg}`);
7308
7694
  }
7309
- function decodeProjectDirName(projectDir) {
7310
- if (projectDir.startsWith("-")) {
7311
- return "/" + projectDir.slice(1).replace(/-/g, "/");
7312
- }
7313
- return projectDir.replace(/-/g, "/");
7314
- }
7315
7695
  async function runPerceptionSweep(opts = {}) {
7316
7696
  const state = readState();
7317
7697
  if (state.sweep_in_progress && !isSweepStale()) {
@@ -7325,33 +7705,12 @@ async function runPerceptionSweep(opts = {}) {
7325
7705
  const files = await listAllSessionJsonls(opts.projectsRoot);
7326
7706
  for (const f of files) {
7327
7707
  try {
7328
- const stat = statSync4(f);
7708
+ const stat = statSync5(f);
7329
7709
  const prev = state.file_checkpoints[f];
7330
7710
  if (prev && prev.byte_offset >= stat.size) continue;
7331
- const raw = readFileSync10(f, "utf8");
7332
- const rawLines = raw.split("\n").filter(Boolean).map((l) => {
7333
- try {
7334
- return JSON.parse(l);
7335
- } catch {
7336
- return null;
7337
- }
7338
- }).filter((x) => x !== null);
7339
- if (rawLines.length === 0) continue;
7340
- const { meta, events } = parseTranscript(rawLines);
7341
- const fileName = basename(f);
7342
- const projectDir = basename(dirname8(f));
7343
- const sessionId = fileName.replace(/\.jsonl$/, "");
7344
- const sd = {
7345
- ...meta,
7346
- id: sessionId,
7347
- filePath: f,
7348
- projectDir,
7349
- projectName: meta.cwd ?? decodeProjectDirName(projectDir),
7350
- events
7351
- };
7352
- const built = buildEntries(sd);
7711
+ const built = buildEntriesForFile(f);
7712
+ if (built === null) continue;
7353
7713
  for (const e of built) {
7354
- e.source_checkpoint.byte_offset = stat.size;
7355
7714
  writeEntryPreservingEnrichment(e);
7356
7715
  entries++;
7357
7716
  }
@@ -7378,7 +7737,7 @@ async function runPerceptionSweep(opts = {}) {
7378
7737
  const built = buildEntries(detail);
7379
7738
  for (const e of built) {
7380
7739
  try {
7381
- const stat = statSync4(meta.filePath);
7740
+ const stat = statSync5(meta.filePath);
7382
7741
  e.source_checkpoint.byte_offset = stat.size;
7383
7742
  } catch {
7384
7743
  }
@@ -11535,12 +11894,12 @@ var ENRICHMENT_SYSTEM_PROMPT = SYSTEM_PROMPT;
11535
11894
  // ../entries/dist/llm-runner.js
11536
11895
  init_fs();
11537
11896
  import { spawn } from "node:child_process";
11538
- import { mkdirSync as mkdirSync10, appendFileSync as appendFileSync3, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "node:fs";
11897
+ import { mkdirSync as mkdirSync10, appendFileSync as appendFileSync4, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "node:fs";
11539
11898
  import { join as join10 } from "node:path";
11540
11899
  import { randomUUID as randomUUID4 } from "node:crypto";
11541
11900
 
11542
11901
  // ../entries/dist/tmux-runner.js
11543
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, existsSync as existsSync8, readdirSync as readdirSync5, realpathSync, readFileSync as readFileSync11, unlinkSync as unlinkSync3, renameSync as renameSync4 } from "node:fs";
11902
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, existsSync as existsSync8, readdirSync as readdirSync5, realpathSync, readFileSync as readFileSync12, unlinkSync as unlinkSync3, renameSync as renameSync4 } from "node:fs";
11544
11903
  import { join as join9 } from "node:path";
11545
11904
  import { homedir as homedir4 } from "node:os";
11546
11905
  import { spawnSync } from "node:child_process";
@@ -11579,7 +11938,7 @@ function ensureRuntimeCwdTrusted(cwd) {
11579
11938
  const cfgPath = join9(homedir4(), ".claude.json");
11580
11939
  let cfg;
11581
11940
  try {
11582
- cfg = JSON.parse(readFileSync11(cfgPath, "utf8"));
11941
+ cfg = JSON.parse(readFileSync12(cfgPath, "utf8"));
11583
11942
  } catch {
11584
11943
  return;
11585
11944
  }
@@ -11731,7 +12090,7 @@ function snapshotAllTranscripts() {
11731
12090
  function readJsonlCwd(path7, maxLines = 8) {
11732
12091
  let raw;
11733
12092
  try {
11734
- raw = readFileSync11(path7, "utf8");
12093
+ raw = readFileSync12(path7, "utf8");
11735
12094
  } catch {
11736
12095
  return void 0;
11737
12096
  }
@@ -11788,7 +12147,7 @@ async function discoverTranscript(targetCwd, existingBefore, startMs, timeoutMs,
11788
12147
  function parseTranscript2(path7, defaultModel) {
11789
12148
  let raw;
11790
12149
  try {
11791
- raw = readFileSync11(path7, "utf8");
12150
+ raw = readFileSync12(path7, "utf8");
11792
12151
  } catch {
11793
12152
  return { assembled: "", inputTokens: 0, outputTokens: 0, modelUsed: defaultModel, done: false };
11794
12153
  }
@@ -11896,7 +12255,7 @@ function tracePath(runId) {
11896
12255
  }
11897
12256
  function safeAppend(path7, line) {
11898
12257
  try {
11899
- appendFileSync3(path7, line);
12258
+ appendFileSync4(path7, line);
11900
12259
  } catch {
11901
12260
  }
11902
12261
  }
@@ -12291,7 +12650,7 @@ function applyEnrichmentError(entry, errorMessage, model, costUsd, generatedAt,
12291
12650
 
12292
12651
  // ../entries/dist/budget.js
12293
12652
  init_fs();
12294
- import { appendFileSync as appendFileSync4, readFileSync as readFileSync12, existsSync as existsSync10, mkdirSync as mkdirSync11 } from "node:fs";
12653
+ import { appendFileSync as appendFileSync5, readFileSync as readFileSync13, existsSync as existsSync10, mkdirSync as mkdirSync11 } from "node:fs";
12295
12654
  import { dirname as dirname9 } from "node:path";
12296
12655
  var spendPathCached = null;
12297
12656
  function spendPath() {
@@ -12303,7 +12662,7 @@ function spendPath() {
12303
12662
  function appendSpend(record) {
12304
12663
  const p = spendPath();
12305
12664
  mkdirSync11(dirname9(p), { recursive: true });
12306
- appendFileSync4(p, JSON.stringify(record) + "\n", { encoding: "utf8" });
12665
+ appendFileSync5(p, JSON.stringify(record) + "\n", { encoding: "utf8" });
12307
12666
  }
12308
12667
  function monthToDateSpend(now = /* @__PURE__ */ new Date()) {
12309
12668
  const p = spendPath();
@@ -12312,7 +12671,7 @@ function monthToDateSpend(now = /* @__PURE__ */ new Date()) {
12312
12671
  const year = now.getUTCFullYear();
12313
12672
  const month = now.getUTCMonth();
12314
12673
  let total = 0;
12315
- const raw = readFileSync12(p, "utf8");
12674
+ const raw = readFileSync13(p, "utf8");
12316
12675
  for (const line of raw.split("\n")) {
12317
12676
  if (!line)
12318
12677
  continue;
@@ -12334,7 +12693,7 @@ function monthToDateSpend(now = /* @__PURE__ */ new Date()) {
12334
12693
 
12335
12694
  // ../entries/dist/pipeline-lock.js
12336
12695
  init_fs();
12337
- import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync4, existsSync as existsSync11, readFileSync as readFileSync13, statSync as statSync5 } from "node:fs";
12696
+ import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync4, existsSync as existsSync11, readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
12338
12697
  var STALE_MS2 = 60 * 1e3;
12339
12698
  var HEARTBEAT_MS = 30 * 1e3;
12340
12699
  var lockPathCached = null;
@@ -12383,10 +12742,10 @@ function interactiveLockFresh(nowMs) {
12383
12742
  if (!existsSync11(p))
12384
12743
  return false;
12385
12744
  try {
12386
- const mtime = statSync5(p).mtimeMs;
12745
+ const mtime = statSync6(p).mtimeMs;
12387
12746
  if (nowMs - mtime > STALE_MS2)
12388
12747
  return false;
12389
- const pid = Number(readFileSync13(p, "utf8").trim());
12748
+ const pid = Number(readFileSync14(p, "utf8").trim());
12390
12749
  if (!Number.isFinite(pid) || pid <= 0)
12391
12750
  return false;
12392
12751
  if (!pidAlive(pid))
@@ -12401,7 +12760,7 @@ function interactiveLockFresh(nowMs) {
12401
12760
  var THIRTY_MIN_MS = 30 * 60 * 1e3;
12402
12761
 
12403
12762
  // ../entries/dist/settings.js
12404
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, renameSync as renameSync5, chmodSync as chmodSync2, mkdirSync as mkdirSync12, existsSync as existsSync12 } from "node:fs";
12763
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, renameSync as renameSync5, chmodSync as chmodSync2, mkdirSync as mkdirSync12, existsSync as existsSync12 } from "node:fs";
12405
12764
  init_fs();
12406
12765
  var DEFAULT_SETTINGS = {
12407
12766
  ai_features: {
@@ -12436,7 +12795,7 @@ function readSettings() {
12436
12795
  if (!existsSync12(p))
12437
12796
  return DEFAULT_SETTINGS;
12438
12797
  try {
12439
- const raw = readFileSync14(p, "utf8");
12798
+ const raw = readFileSync15(p, "utf8");
12440
12799
  return fromDisk(JSON.parse(raw));
12441
12800
  } catch {
12442
12801
  return DEFAULT_SETTINGS;
@@ -15053,8 +15412,8 @@ function toLocalDay2(ms) {
15053
15412
  const day = String(d.getDate()).padStart(2, "0");
15054
15413
  return `${y}-${m}-${day}`;
15055
15414
  }
15056
- function mondayOf(localDay) {
15057
- const d = /* @__PURE__ */ new Date(`${localDay}T00:00:00`);
15415
+ function mondayOf(localDay2) {
15416
+ const d = /* @__PURE__ */ new Date(`${localDay2}T00:00:00`);
15058
15417
  const dow = d.getDay();
15059
15418
  const offset = dow === 0 ? -6 : 1 - dow;
15060
15419
  d.setDate(d.getDate() + offset);
@@ -15193,14 +15552,14 @@ async function getUpdateAvailability() {
15193
15552
  if (isDevMode()) return null;
15194
15553
  const latest = await fetchLatestVersion();
15195
15554
  if (latest === null) return {
15196
- current: "0.13.1",
15555
+ current: "0.14.0",
15197
15556
  latest: null,
15198
15557
  updateAvailable: false
15199
15558
  };
15200
15559
  return {
15201
- current: "0.13.1",
15560
+ current: "0.14.0",
15202
15561
  latest,
15203
- updateAvailable: shouldUpdate("0.13.1", latest)
15562
+ updateAvailable: shouldUpdate("0.14.0", latest)
15204
15563
  };
15205
15564
  }
15206
15565
 
@@ -15212,15 +15571,11 @@ var WATCHDOG_INTERVAL_MS = 5 * 1e3;
15212
15571
  var AUTO_UPDATE_INTERVAL_MS = 6 * 60 * 60 * 1e3;
15213
15572
  mkdirSync13(dirname10(USAGE_LOG3), { recursive: true });
15214
15573
  function log2(level, message) {
15215
- const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${level.toUpperCase()} ${message}
15216
- `;
15217
- try {
15218
- appendFileSync5(DAEMON_LOG, line, "utf8");
15219
- } catch {
15220
- }
15574
+ appendDaemonLogLine(level, message);
15221
15575
  }
15222
15576
  var nextPollAtMs = 0;
15223
15577
  var nextTeamSyncAtMs = 0;
15578
+ var firstTeamSync = true;
15224
15579
  var currentIntervalMs = BASE_INTERVAL_MS;
15225
15580
  var waitingForRefresh = false;
15226
15581
  var updateCheckInFlight = false;
@@ -15252,7 +15607,7 @@ var perceptionHandle = setInterval(async () => {
15252
15607
  }, PERCEPTION_INTERVAL_MS);
15253
15608
  function readUpdateState() {
15254
15609
  try {
15255
- return JSON.parse(readFileSync15(UPDATE_STATE, "utf8"));
15610
+ return JSON.parse(readFileSync16(UPDATE_STATE, "utf8"));
15256
15611
  } catch {
15257
15612
  return null;
15258
15613
  }
@@ -15429,7 +15784,11 @@ async function runLoop() {
15429
15784
  }
15430
15785
  const teamNow = Date.now();
15431
15786
  if (teamNow >= nextTeamSyncAtMs) {
15432
- await runTeamSync(log2);
15787
+ await runTeamSync(log2, void 0, {
15788
+ trigger: firstTeamSync ? "boot" : "auto",
15789
+ nextSyncMs: BASE_INTERVAL_MS
15790
+ });
15791
+ firstTeamSync = false;
15433
15792
  nextTeamSyncAtMs = Date.now() + BASE_INTERVAL_MS;
15434
15793
  }
15435
15794
  if (Date.now() >= nextUpdateCheckAtMs) {