crewly 1.20.301 → 1.20.303

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 (203) hide show
  1. package/config/roles/developer/prompt.md +7 -0
  2. package/config/roles/fullstack-dev/prompt.md +7 -0
  3. package/config/roles/product-manager/prompt.md +7 -0
  4. package/config/skills/agent/core/receive-file/SKILL.md +4 -0
  5. package/config/skills/agent/core/request-file/SKILL.md +74 -0
  6. package/config/skills/agent/core/request-file/execute.sh +89 -0
  7. package/config/skills/agent/core/request-file/execute.test.sh +77 -0
  8. package/config/skills/agent/core/slack-file/SKILL.md +1 -1
  9. package/config/skills/orchestrator/usage-categories/SKILL.md +59 -0
  10. package/config/skills/orchestrator/usage-categories/execute.sh +81 -0
  11. package/config/skills/orchestrator/usage-categories/execute.test.sh +45 -0
  12. package/config/skills/orchestrator/usage-report/SKILL.md +71 -0
  13. package/config/skills/orchestrator/usage-report/execute.sh +84 -0
  14. package/config/skills/orchestrator/usage-report/execute.test.sh +39 -0
  15. package/config/skills/team-leader/usage-report/SKILL.md +71 -0
  16. package/config/skills/team-leader/usage-report/execute.sh +84 -0
  17. package/config/skills/team-leader/usage-report/execute.test.sh +39 -0
  18. package/dist/backend/backend/src/constants.d.ts +24 -4
  19. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  20. package/dist/backend/backend/src/constants.js +29 -6
  21. package/dist/backend/backend/src/constants.js.map +1 -1
  22. package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts +9 -0
  23. package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts.map +1 -1
  24. package/dist/backend/backend/src/controllers/apps/apps.controller.js +21 -0
  25. package/dist/backend/backend/src/controllers/apps/apps.controller.js.map +1 -1
  26. package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts +2 -0
  27. package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts.map +1 -1
  28. package/dist/backend/backend/src/controllers/apps/apps.routes.js +7 -1
  29. package/dist/backend/backend/src/controllers/apps/apps.routes.js.map +1 -1
  30. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.d.ts +36 -0
  31. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.d.ts.map +1 -0
  32. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.js +158 -0
  33. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.js.map +1 -0
  34. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.d.ts +15 -0
  35. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.d.ts.map +1 -0
  36. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.js +26 -0
  37. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.js.map +1 -0
  38. package/dist/backend/backend/src/index.d.ts +10 -0
  39. package/dist/backend/backend/src/index.d.ts.map +1 -1
  40. package/dist/backend/backend/src/index.js +75 -17
  41. package/dist/backend/backend/src/index.js.map +1 -1
  42. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  43. package/dist/backend/backend/src/routes/api.routes.js +3 -0
  44. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  45. package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts +32 -0
  46. package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts.map +1 -1
  47. package/dist/backend/backend/src/services/agent/follow-through.wiring.js +42 -1
  48. package/dist/backend/backend/src/services/agent/follow-through.wiring.js.map +1 -1
  49. package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts +26 -6
  50. package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
  51. package/dist/backend/backend/src/services/agent/resource-mode.service.js +59 -5
  52. package/dist/backend/backend/src/services/agent/resource-mode.service.js.map +1 -1
  53. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  54. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
  55. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  56. package/dist/backend/backend/src/services/apps/apps.wiring.d.ts.map +1 -1
  57. package/dist/backend/backend/src/services/apps/apps.wiring.js +14 -0
  58. package/dist/backend/backend/src/services/apps/apps.wiring.js.map +1 -1
  59. package/dist/backend/backend/src/services/apps/file-request-watch.service.d.ts +88 -0
  60. package/dist/backend/backend/src/services/apps/file-request-watch.service.d.ts.map +1 -0
  61. package/dist/backend/backend/src/services/apps/file-request-watch.service.js +170 -0
  62. package/dist/backend/backend/src/services/apps/file-request-watch.service.js.map +1 -0
  63. package/dist/backend/backend/src/services/monitoring/agent-turn-state.d.ts +35 -0
  64. package/dist/backend/backend/src/services/monitoring/agent-turn-state.d.ts.map +1 -1
  65. package/dist/backend/backend/src/services/monitoring/agent-turn-state.js +101 -0
  66. package/dist/backend/backend/src/services/monitoring/agent-turn-state.js.map +1 -1
  67. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.d.ts +58 -0
  68. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.d.ts.map +1 -0
  69. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.js +100 -0
  70. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.js.map +1 -0
  71. package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
  72. package/dist/backend/backend/src/services/monitoring/token-usage.service.js +3 -0
  73. package/dist/backend/backend/src/services/monitoring/token-usage.service.js.map +1 -1
  74. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +7 -0
  75. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
  76. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +15 -0
  77. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
  78. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  79. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +51 -33
  80. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  81. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts +24 -0
  82. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
  83. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js +63 -1
  84. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
  85. package/dist/backend/backend/src/services/trace/trace-context.service.d.ts +42 -2
  86. package/dist/backend/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
  87. package/dist/backend/backend/src/services/trace/trace-context.service.js +66 -5
  88. package/dist/backend/backend/src/services/trace/trace-context.service.js.map +1 -1
  89. package/dist/backend/backend/src/services/trace/trace-recorder.d.ts.map +1 -1
  90. package/dist/backend/backend/src/services/trace/trace-recorder.js +11 -0
  91. package/dist/backend/backend/src/services/trace/trace-recorder.js.map +1 -1
  92. package/dist/backend/backend/src/services/usage-ledger/categories-param.d.ts +28 -0
  93. package/dist/backend/backend/src/services/usage-ledger/categories-param.d.ts.map +1 -0
  94. package/dist/backend/backend/src/services/usage-ledger/categories-param.js +67 -0
  95. package/dist/backend/backend/src/services/usage-ledger/categories-param.js.map +1 -0
  96. package/dist/backend/backend/src/services/usage-ledger/excerpt.d.ts +37 -0
  97. package/dist/backend/backend/src/services/usage-ledger/excerpt.d.ts.map +1 -0
  98. package/dist/backend/backend/src/services/usage-ledger/excerpt.js +49 -0
  99. package/dist/backend/backend/src/services/usage-ledger/excerpt.js.map +1 -0
  100. package/dist/backend/backend/src/services/usage-ledger/index.d.ts +14 -0
  101. package/dist/backend/backend/src/services/usage-ledger/index.d.ts.map +1 -0
  102. package/dist/backend/backend/src/services/usage-ledger/index.js +14 -0
  103. package/dist/backend/backend/src/services/usage-ledger/index.js.map +1 -0
  104. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.d.ts +44 -0
  105. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.d.ts.map +1 -0
  106. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.js +52 -0
  107. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.js.map +1 -0
  108. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.d.ts +100 -0
  109. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.d.ts.map +1 -0
  110. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.js +189 -0
  111. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.js.map +1 -0
  112. package/dist/backend/backend/src/services/usage-ledger/owner-spans.d.ts +41 -0
  113. package/dist/backend/backend/src/services/usage-ledger/owner-spans.d.ts.map +1 -0
  114. package/dist/backend/backend/src/services/usage-ledger/owner-spans.js +71 -0
  115. package/dist/backend/backend/src/services/usage-ledger/owner-spans.js.map +1 -0
  116. package/dist/backend/backend/src/services/usage-ledger/team-directory.d.ts +47 -0
  117. package/dist/backend/backend/src/services/usage-ledger/team-directory.d.ts.map +1 -0
  118. package/dist/backend/backend/src/services/usage-ledger/team-directory.js +106 -0
  119. package/dist/backend/backend/src/services/usage-ledger/team-directory.js.map +1 -0
  120. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.d.ts +101 -0
  121. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.d.ts.map +1 -0
  122. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.js +263 -0
  123. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.js.map +1 -0
  124. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.d.ts +96 -0
  125. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.d.ts.map +1 -0
  126. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.js +166 -0
  127. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.js.map +1 -0
  128. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.d.ts +90 -0
  129. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.d.ts.map +1 -0
  130. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.js +294 -0
  131. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.js.map +1 -0
  132. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.d.ts +102 -0
  133. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.d.ts.map +1 -0
  134. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.js +314 -0
  135. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.js.map +1 -0
  136. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.d.ts +62 -0
  137. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.d.ts.map +1 -0
  138. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.js +106 -0
  139. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.js.map +1 -0
  140. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.d.ts +52 -0
  141. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.d.ts.map +1 -0
  142. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.js +95 -0
  143. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.js.map +1 -0
  144. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.d.ts +127 -0
  145. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.d.ts.map +1 -0
  146. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.js +19 -0
  147. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.js.map +1 -0
  148. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.d.ts +124 -0
  149. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.d.ts.map +1 -0
  150. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.js +372 -0
  151. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.js.map +1 -0
  152. package/dist/backend/backend/src/types/slack.types.d.ts +8 -0
  153. package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
  154. package/dist/backend/backend/src/types/slack.types.js.map +1 -1
  155. package/dist/backend/build-info.json +2 -2
  156. package/dist/cli/backend/src/constants.d.ts +24 -4
  157. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  158. package/dist/cli/backend/src/constants.js +29 -6
  159. package/dist/cli/backend/src/constants.js.map +1 -1
  160. package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts +26 -6
  161. package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
  162. package/dist/cli/backend/src/services/agent/resource-mode.service.js +59 -5
  163. package/dist/cli/backend/src/services/agent/resource-mode.service.js.map +1 -1
  164. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  165. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
  166. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  167. package/dist/cli/backend/src/services/monitoring/agent-turn-state.d.ts +35 -0
  168. package/dist/cli/backend/src/services/monitoring/agent-turn-state.d.ts.map +1 -1
  169. package/dist/cli/backend/src/services/monitoring/agent-turn-state.js +101 -0
  170. package/dist/cli/backend/src/services/monitoring/agent-turn-state.js.map +1 -1
  171. package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
  172. package/dist/cli/backend/src/services/monitoring/token-usage.service.js +3 -0
  173. package/dist/cli/backend/src/services/monitoring/token-usage.service.js.map +1 -1
  174. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  175. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +51 -33
  176. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  177. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts +24 -0
  178. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
  179. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js +63 -1
  180. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
  181. package/dist/cli/backend/src/services/trace/trace-context.service.d.ts +42 -2
  182. package/dist/cli/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
  183. package/dist/cli/backend/src/services/trace/trace-context.service.js +66 -5
  184. package/dist/cli/backend/src/services/trace/trace-context.service.js.map +1 -1
  185. package/dist/cli/backend/src/services/trace/trace-recorder.d.ts.map +1 -1
  186. package/dist/cli/backend/src/services/trace/trace-recorder.js +11 -0
  187. package/dist/cli/backend/src/services/trace/trace-recorder.js.map +1 -1
  188. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.d.ts +44 -0
  189. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.d.ts.map +1 -0
  190. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.js +52 -0
  191. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.js.map +1 -0
  192. package/dist/cli/backend/src/services/worktree/low-disk-guard.d.ts +109 -0
  193. package/dist/cli/backend/src/services/worktree/low-disk-guard.d.ts.map +1 -0
  194. package/dist/cli/backend/src/services/worktree/low-disk-guard.js +148 -0
  195. package/dist/cli/backend/src/services/worktree/low-disk-guard.js.map +1 -0
  196. package/dist/cli/backend/src/services/worktree/worktree-janitor.git.d.ts +224 -0
  197. package/dist/cli/backend/src/services/worktree/worktree-janitor.git.d.ts.map +1 -0
  198. package/dist/cli/backend/src/services/worktree/worktree-janitor.git.js +449 -0
  199. package/dist/cli/backend/src/services/worktree/worktree-janitor.git.js.map +1 -0
  200. package/dist/cli/backend/src/types/slack.types.d.ts +8 -0
  201. package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
  202. package/dist/cli/backend/src/types/slack.types.js.map +1 -1
  203. package/package.json +1 -1
@@ -142,7 +142,10 @@ import { getRuntimeFallbackService } from './services/runtime-fallback/runtime-f
142
142
  import { getSlackAgentIdentityService } from './services/slack/slack-agent-identity.service.js';
143
143
  import { getChatV2Service } from './services/chat-v2/chat-v2.singleton.js';
144
144
  import { isOwnerStopped } from './services/agent/owner-stopped.registry.js';
145
- import { createFollowThrough, settleFollowThrough, idlePendingWorkCheck, releaseWorkForStop, owedWorkReminder } from './services/agent/follow-through.wiring.js';
145
+ import { pausedTeamOfSession } from './services/team/team-pause.registry.js';
146
+ import { getFollowThrough } from './services/agent/follow-through.service.js';
147
+ import { BackgroundWorkWatchdog } from './services/monitoring/background-work-watchdog.js';
148
+ import { createFollowThrough, settleFollowThrough, idlePendingWorkCheck, releaseWorkForStop, restoreStoppedOwingWork, owedWorkReminder, OWED_REMINDER_MARKER } from './services/agent/follow-through.wiring.js';
146
149
  import { isSessionPaused } from './services/team/team-pause.registry.js';
147
150
  import { findPackageRoot } from './utils/package-root.js';
148
151
  import { getLocalApiBaseUrl, setLocalApiPort } from './utils/local-api-url.utils.js';
@@ -184,6 +187,7 @@ import { sessionsToRestore } from './services/agent/restore-filter.js';
184
187
  import { ProjectMemoryService } from './services/memory/project-memory.service.js';
185
188
  import { TaskHistorySubscriber } from './services/memory/task-history.subscriber.js';
186
189
  import { TeamHealthWatchdogService, LiveTeamHealthDataProvider, loadTeamHealthConfig, setTeamHealthWatchdogSingleton, getTeamHealthWatchdogSingleton, } from './services/team-health/index.js';
190
+ import { getUsageLedger } from './services/usage-ledger/usage-ledger.runtime.js';
187
191
  import { traceHarness, traceTurnActivity } from './services/trace/trace-recorder.js';
188
192
  import { inputCircuitStats, setInputCircuitOpenListener } from './services/session/input-circuit-breaker.js';
189
193
  import { looksLikeShellPrompt } from './utils/screen-excerpt.js';
@@ -350,6 +354,7 @@ export class CrewlyServer {
350
354
  /** Owner promises past due and never nudged at boot; their agents are restored and reminded once */
351
355
  openCommitmentsAtBoot = [];
352
356
  healthMonitoringInterval = null;
357
+ backgroundWatchdog = null;
353
358
  constructor(config) {
354
359
  // Resolve ~ to actual home directory
355
360
  const resolveHomePath = (inputPath) => {
@@ -1297,7 +1302,14 @@ export class CrewlyServer {
1297
1302
  setSlackImageService(slackImageService);
1298
1303
  // Wire agent:idle events to thread status queue for delegation completion
1299
1304
  this.eventBusService.on('eventPublished', (event) => {
1305
+ if (event.type === 'agent:busy' && event.sessionName)
1306
+ getSlackTypingPlaceholderService()?.releaseHold(event.sessionName);
1307
+ if ((event.type === 'agent:idle' || event.type === 'agent:busy') && event.sessionName) {
1308
+ ResourceModeService.getInstance().noteWorkingStatus(event.sessionName, event.type === 'agent:idle');
1309
+ }
1300
1310
  if (event.type === 'agent:inactive' && event.sessionName) {
1311
+ // A runtime that exited while the agent owed work: same hook as the other stop paths.
1312
+ void this.restoreStoppedOwing(event.sessionName, 'runtime exited');
1301
1313
  this.wakeIfMessagesQueued(event.sessionName);
1302
1314
  }
1303
1315
  // A turn started: the harness's "working on it" watch may be waiting
@@ -1385,6 +1397,7 @@ export class CrewlyServer {
1385
1397
  // 2026-05-16: queued WIs for inactive Atlas could not get woken
1386
1398
  // because the floor was held by idle product/marketing agents).
1387
1399
  liveDataProvider.setAgentRegistrationService(this.apiController.agentRegistrationService);
1400
+ liveDataProvider.setAgentStoppedHook((name, why) => { void this.restoreStoppedOwing(name, why); });
1388
1401
  // Initialize Reconciler Service (V2 — system truth recomputation)
1389
1402
  {
1390
1403
  const reconcilerLogger = LoggerService.getInstance().createComponentLogger('ReconcilerInit');
@@ -1960,6 +1973,14 @@ export class CrewlyServer {
1960
1973
  crewlyHome: this.config.crewlyHome,
1961
1974
  sendToAgent: (session, text) => this.apiController.agentRegistrationService.sendMessageToAgent(session, text),
1962
1975
  });
1976
+ // Background subagents / forks that stopped writing are not busy work:
1977
+ // the agent is told and no longer counted busy (2026-10-12, Pia).
1978
+ this.backgroundWatchdog?.stop();
1979
+ this.backgroundWatchdog = new BackgroundWorkWatchdog({
1980
+ turnState: AgentTurnStateService.getInstance(),
1981
+ sendToAgent: (session, text) => this.apiController.agentRegistrationService.sendMessageToAgent(session, text),
1982
+ });
1983
+ this.backgroundWatchdog.start();
1963
1984
  // An agent that owes work — a WorkItem (queued, accepted or running),
1964
1985
  // an assigned ticket, a promise to the owner, an unfulfilled "doing X
1965
1986
  // now" — is never idle-stopped. Under memory pressure it still can
@@ -1970,20 +1991,7 @@ export class CrewlyServer {
1970
1991
  // a slot frees, with a reminder of what it promised (the work itself is
1971
1992
  // already queued for it by the releaser).
1972
1993
  idleDetection.setOwedStopHook((sessionName) => {
1973
- void (async () => {
1974
- const reminder = owedWorkReminder(await TaskPoolService.getInstance().getAllItems(), sessionName);
1975
- if (!reminder)
1976
- return;
1977
- SubAgentMessageQueue.getInstance().enqueue(sessionName, reminder);
1978
- this.logger.info('Stopped agent owes work — it will be restarted when a slot frees', { sessionName });
1979
- const { scheduleDeferredStartRetry } = await import('./services/messaging/deferred-start-retry.js');
1980
- const { activateAgentBySession } = await import('./controllers/team/team.controller.js');
1981
- await scheduleDeferredStartRetry(sessionName, {
1982
- activate: (name) => activateAgentBySession(this.apiController, name),
1983
- hasPending: (name) => SubAgentMessageQueue.getInstance().hasPending(name),
1984
- firstPauseMs: 60_000,
1985
- });
1986
- })().catch(() => undefined);
1994
+ void this.restoreStoppedOwing(sessionName, 'idle stop under memory pressure');
1987
1995
  });
1988
1996
  idleDetection.start();
1989
1997
  // Adaptive agent limits (pressure mode cap + start queue).
@@ -2031,12 +2039,21 @@ export class CrewlyServer {
2031
2039
  // An agent mid-conversation with the owner is the last one stopped
2032
2040
  // for a slot, and its owner thread hears when it is
2033
2041
  // (specs/2026-10-08-owner-thread-sentinel.md).
2034
- owesOwnerThread: (name) => getOwnerThreadSentinel()?.owesRecently(name) === true,
2035
- onStoppedForSlot: (name) => reportOwnerThreadBlocking(name, { kind: 'stopped', why: 'slot' }),
2042
+ owesOwnerThread: (name) => getOwnerThreadSentinel()?.owesOwner(name) === true || getFollowThrough()?.holdsIntent(name) === true,
2043
+ onStoppedForSlot: (name) => {
2044
+ reportOwnerThreadBlocking(name, { kind: 'stopped', why: 'slot' });
2045
+ // Same hook as every other stop path: what it owes goes back to the queue and it is brought back.
2046
+ void this.restoreStoppedOwing(name, 'stopped to free a running-agent slot');
2047
+ },
2036
2048
  onStartDeferred: (name) => reportOwnerThreadBlocking(name, { kind: 'start_deferred' }),
2037
2049
  stopAgent: async (name, role) => {
2038
2050
  // Its work survives the stop: back in the queue, redelivered on the next start.
2039
2051
  await releaseWorkForStop(name, 'stopped to free a running-agent slot');
2052
+ // An agent that still owes the answer keeps its "will pick this up" placeholder:
2053
+ // the turn-end settle the stop triggers must not call the thread answered.
2054
+ const owed = TaskPoolService.getInstance().getAllItems().then((items) => owedWorkReminder(items, name) !== null);
2055
+ if (await owed.catch(() => false))
2056
+ await getSlackTypingPlaceholderService()?.holdForRestart(name);
2040
2057
  await agentRegistration.terminateAgentSession(name, role);
2041
2058
  await StorageService.getInstance().updateAgentStatus(name, CREWLY_CONSTANTS.AGENT_STATUSES.INACTIVE, 'idle_exit');
2042
2059
  },
@@ -3844,6 +3861,9 @@ export class CrewlyServer {
3844
3861
  const tokenUsageService = TokenUsageService.getInstance();
3845
3862
  await tokenUsageService.loadFromDisk();
3846
3863
  tokenUsageService.startPeriodicFlush();
3864
+ // Per-ticket time / token accounting (specs/2026-10-11-usage-ledger.md):
3865
+ // records facts from here on and rebuilds history once, shortly after boot.
3866
+ await getUsageLedger().start();
3847
3867
  // Read Claude Code's own session transcripts on an interval so
3848
3868
  // the Usage dashboard reflects claude-code agents, and so the
3849
3869
  // context monitor gets a real context size for them (Claude
@@ -6238,6 +6258,34 @@ export class CrewlyServer {
6238
6258
  });
6239
6259
  }
6240
6260
  }
6261
+ /**
6262
+ * The one hook every stop path runs: what the stopped agent owes goes back
6263
+ * in the queue and the agent is brought back with a reminder.
6264
+ *
6265
+ * @param sessionName - The agent that stopped
6266
+ * @param why - Reason text
6267
+ * @returns True when the agent is being brought back
6268
+ */
6269
+ restoreStoppedOwing(sessionName, why) {
6270
+ if (sessionName === ORCHESTRATOR_SESSION_NAME)
6271
+ return Promise.resolve(false);
6272
+ return restoreStoppedOwingWork(sessionName, why, {
6273
+ listWorkItems: () => TaskPoolService.getInstance().getAllItems(),
6274
+ isHeldBack: (name) => isOwnerStopped(name) || !!pausedTeamOfSession(name),
6275
+ hasReminder: (name) => SubAgentMessageQueue.getInstance().peek(name).some((m) => m.data.startsWith(OWED_REMINDER_MARKER)),
6276
+ enqueue: (name, text) => SubAgentMessageQueue.getInstance().enqueue(name, text),
6277
+ holdPlaceholders: (name) => getSlackTypingPlaceholderService()?.holdForRestart(name) ?? Promise.resolve(0),
6278
+ restart: async (name) => {
6279
+ const { scheduleDeferredStartRetry } = await import('./services/messaging/deferred-start-retry.js');
6280
+ const { activateAgentBySession } = await import('./controllers/team/team.controller.js');
6281
+ void scheduleDeferredStartRetry(name, {
6282
+ activate: (n) => activateAgentBySession(this.apiController, n),
6283
+ hasPending: (n) => SubAgentMessageQueue.getInstance().hasPending(n),
6284
+ firstPauseMs: 60_000,
6285
+ });
6286
+ },
6287
+ });
6288
+ }
6241
6289
  /**
6242
6290
  * An agent went down with messages still queued for it: start it again so
6243
6291
  * they are delivered (registration drains the queue). Without this the
@@ -6708,6 +6756,7 @@ export class CrewlyServer {
6708
6756
  }, timeoutMs);
6709
6757
  try {
6710
6758
  // Clear health monitoring interval first
6759
+ this.backgroundWatchdog?.stop();
6711
6760
  if (this.healthMonitoringInterval) {
6712
6761
  clearInterval(this.healthMonitoringInterval);
6713
6762
  this.healthMonitoringInterval = null;
@@ -6935,6 +6984,15 @@ export class CrewlyServer {
6935
6984
  WorktreeJanitorService.getInstance().stop();
6936
6985
  // Stop auditor scheduler
6937
6986
  AuditorSchedulerService.getInstance().stop();
6987
+ // Write the usage ledger's buffered buckets
6988
+ try {
6989
+ await getUsageLedger().stop();
6990
+ }
6991
+ catch (ledgerErr) {
6992
+ this.logger.warn('Usage ledger flush on shutdown failed (non-fatal)', {
6993
+ error: ledgerErr instanceof Error ? ledgerErr.message : String(ledgerErr),
6994
+ });
6995
+ }
6938
6996
  // Persist the token ledger: it flushes every 5 minutes, so a restart
6939
6997
  // used to drop everything recorded since the last tick (2026-09-18).
6940
6998
  try {