claude-code-session-manager 0.86.0 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -53
- package/dist/assets/AgentLibrary-BZ6IY2g1.js +3 -0
- package/dist/assets/{DataModel-Q4jhl24R.js → DataModel-dAoGAODX.js} +1 -1
- package/dist/assets/{History-Cj2FejEo.js → History-CF157HyX.js} +2 -2
- package/dist/assets/{Hooks-CaelQI6t.js → Hooks-CCPPipBk.js} +3 -3
- package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-Dlmz2m06.js} +1 -1
- package/dist/assets/{Library-DgI9oCCZ.js → Library-BaXcboWX.js} +1 -1
- package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-CoUCzG5S.js} +1 -1
- package/dist/assets/MarkdownEditor-DTg3JlOT.js +1 -0
- package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DdjY1sA3.js} +2 -2
- package/dist/assets/{Memory-C2qYp-3M.js → Memory-BJ3Jlw0C.js} +4 -4
- package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-ygRSgc41.js} +1 -1
- package/dist/assets/Permissions-m0KAnccF.js +3 -0
- package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-CJRl6B3Q.js} +2 -2
- package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-w8cnlVvW.js} +1 -1
- package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-BpyPcUIk.js} +1 -1
- package/dist/assets/Scheduler-D2ppxSfy.js +14 -0
- package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-EyuIwt-j.js} +1 -1
- package/dist/assets/Settings-BHSGGFEc.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-BNaZ2JCF.js} +1 -1
- package/dist/assets/{Skills-C_YHkAy-.js → Skills-Dl3q75QV.js} +2 -2
- package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Bg6F-Cyp.js} +1 -1
- package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-BSutflCy.js} +1 -1
- package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-CXih4xrc.js} +1 -1
- package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-2qtUHhA9.js} +1 -1
- package/dist/assets/{index-CXFQIPhO.js → index-B3emy2oI.js} +583 -583
- package/dist/assets/{index-CDo9xBR9.css → index-BHTX4OTc.css} +1 -1
- package/dist/assets/settingsSchema-CdzxaqwM.js +3 -0
- package/dist/assets/{whisperWorker-Dbia1OpC.js → whisperWorker-C7ZGQwKg.js} +7 -7
- package/dist/index.html +2 -2
- package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +106 -110
- package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +98 -98
- package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +99 -102
- package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
- package/dist/vad/ort-wasm-simd-threaded.mjs +46 -46
- package/dist/vad/ort-wasm-simd-threaded.wasm +0 -0
- package/package.json +14 -17
- package/plugins/CLAUDE.md +61 -0
- package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
- package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
- package/scripts/README.md +59 -0
- package/scripts/audit-ops-hygiene.cjs +350 -0
- package/scripts/hooks/guard-destructive-git.cjs +10 -34
- package/scripts/hooks/guard-inline-implementation.cjs +14 -8
- package/scripts/hooks/guard-prd-writes.cjs +7 -36
- package/scripts/hooks/guard-self-schedule.cjs +175 -0
- package/scripts/ops-sweep.cjs +355 -0
- package/scripts/scheduler-mcp-server.cjs +35 -71
- package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
- package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
- package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
- package/src/main/__tests__/chat-cancel-terminal.test.cjs +6 -9
- package/src/main/__tests__/chat-exit-close-race.test.cjs +3 -3
- package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +4 -5
- package/src/main/__tests__/chat-queue.test.cjs +2 -2
- package/src/main/__tests__/chat-stop-signal.test.cjs +2 -2
- package/src/main/__tests__/dep-orphan-archive-health.test.cjs +77 -0
- package/src/main/__tests__/dod-batchkey.test.cjs +2 -2
- package/src/main/__tests__/dod-drain-hook.test.cjs +2 -2
- package/src/main/__tests__/dod-report.test.cjs +2 -2
- package/src/main/__tests__/dod-reverify.test.cjs +2 -2
- package/src/main/__tests__/epicMint.test.cjs +4 -4
- package/src/main/__tests__/exchanges.test.cjs +2 -2
- package/src/main/__tests__/extractJson.test.cjs +2 -2
- package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
- package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +556 -0
- package/src/main/__tests__/health-build-freshness.test.cjs +39 -0
- package/src/main/__tests__/health-delegation-chain.test.cjs +17 -2
- package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
- package/src/main/__tests__/health-tick-liveness.test.cjs +11 -3
- package/src/main/__tests__/health-usage-poller.test.cjs +70 -23
- package/src/main/__tests__/historyRollup.test.cjs +2 -2
- package/src/main/__tests__/kg-augment.test.cjs +2 -2
- package/src/main/__tests__/mcpStatus.test.cjs +2 -2
- package/src/main/__tests__/memoryAggregate.test.cjs +1 -1
- package/src/main/__tests__/memoryStale.test.cjs +1 -1
- package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +28 -4
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +48 -4
- package/src/main/__tests__/prd-group-allocator.test.cjs +2 -2
- package/src/main/__tests__/prdAdminRouteParity.test.cjs +2 -0
- package/src/main/__tests__/prdAdminRoutes.test.cjs +14 -2
- package/src/main/__tests__/prdAuthoringSeed.test.cjs +39 -0
- package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
- package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
- package/src/main/__tests__/prdLocations.test.cjs +100 -2
- package/src/main/__tests__/prdLocationsArchived.test.cjs +50 -1
- package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
- package/src/main/__tests__/proc-role-env.test.cjs +125 -0
- package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +304 -0
- package/src/main/__tests__/procname-sm-processes.test.cjs +127 -0
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +81 -401
- package/src/main/__tests__/projectPages.test.cjs +63 -149
- package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
- package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
- package/src/main/__tests__/queueHistory.test.cjs +65 -2
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
- package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
- package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +181 -0
- package/src/main/__tests__/runVerify.test.cjs +5 -5
- package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +3 -3
- package/src/main/__tests__/scheduleJobTransitions.test.cjs +103 -3
- package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +143 -0
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +2 -2
- package/src/main/__tests__/scheduler-autopromote.test.cjs +2 -2
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +3 -5
- package/src/main/__tests__/scheduler-boot-orphans.test.cjs +79 -98
- package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
- package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
- package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +161 -0
- package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +58 -0
- package/src/main/__tests__/scheduler-epic-digest.test.cjs +3 -5
- package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +2 -2
- package/src/main/__tests__/scheduler-gate-shadow.test.cjs +119 -0
- package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +390 -0
- package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +80 -0
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +25 -18
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +4 -4
- package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +3 -5
- package/src/main/__tests__/scheduler-looks-done.test.cjs +119 -10
- package/src/main/__tests__/scheduler-manual-pause.test.cjs +118 -0
- package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +26 -3
- package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
- package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +17 -2
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +3 -5
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +41 -6
- package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +62 -6
- package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +3 -5
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +182 -3
- package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
- package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +100 -0
- package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +3 -3
- package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +4 -4
- package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +110 -0
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +168 -0
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +18 -8
- package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +245 -0
- package/src/main/__tests__/scheduler-supervisor-record.test.cjs +81 -0
- package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +2 -2
- package/src/main/__tests__/scheduler-tick-wedge.test.cjs +172 -0
- package/src/main/__tests__/scheduler-transient-failure.test.cjs +2 -2
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +51 -11
- package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +3 -5
- package/src/main/__tests__/usageSingleFlight.test.cjs +157 -0
- package/src/main/__tests__/workTypeLibrary.test.cjs +1 -1
- package/src/main/bilkoHost.cjs +16 -7
- package/src/main/build-info.json +8 -0
- package/src/main/chatRunner.cjs +12 -7
- package/src/main/config.cjs +2 -3
- package/src/main/docEdit.cjs +4 -2
- package/src/main/health.cjs +268 -52
- package/src/main/heapSnapshot.cjs +2 -2
- package/src/main/historyAggregator.cjs +6 -1
- package/src/main/index.cjs +152 -50
- package/src/main/ipcSchemas.cjs +53 -17
- package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
- package/src/main/lib/__tests__/auditLog.test.cjs +38 -0
- package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
- package/src/main/lib/__tests__/buildIdentity.test.cjs +121 -0
- package/src/main/lib/__tests__/cwdClassify.test.cjs +111 -0
- package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +95 -0
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +323 -44
- package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
- package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
- package/src/main/lib/__tests__/gateFixtures.json +20 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +97 -1
- package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
- package/src/main/lib/__tests__/instanceLock.test.cjs +93 -7
- package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +78 -0
- package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +11 -0
- package/src/main/lib/__tests__/localAdminHttp.test.cjs +1 -1
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +6 -1
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
- package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
- package/src/main/lib/__tests__/procIdentity.test.cjs +119 -0
- package/src/main/lib/__tests__/procName.test.cjs +92 -0
- package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +67 -0
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +6 -7
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +37 -204
- package/src/main/lib/__tests__/schedulerPaths.test.cjs +226 -0
- package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +94 -0
- package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +56 -0
- package/src/main/lib/__tests__/sessionSlots.test.cjs +45 -0
- package/src/main/lib/__tests__/telemetryBacklog.test.cjs +1 -1
- package/src/main/lib/__tests__/upgradeDrain.test.cjs +130 -0
- package/src/main/lib/__tests__/usageCircuit.test.cjs +285 -0
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +375 -0
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +266 -0
- package/src/main/lib/activeIndexRebuild.cjs +1 -3
- package/{scripts → src/main}/lib/activeSessions.cjs +67 -89
- package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
- package/src/main/lib/agentModelResolve.cjs +94 -24
- package/src/main/lib/agentPersonaSchema.cjs +2 -2
- package/src/main/lib/auditLog.cjs +30 -5
- package/src/main/lib/bootSelfHeal.cjs +88 -0
- package/src/main/lib/buildIdentity.cjs +113 -0
- package/src/main/lib/classifyPromptTicket.cjs +3 -2
- package/src/main/lib/claudeBin.cjs +37 -2
- package/src/main/lib/cleanEnv.cjs +27 -1
- package/src/main/lib/credentials.cjs +4 -2
- package/src/main/lib/cwdClassify.cjs +185 -0
- package/src/main/lib/definitionOfDone.cjs +296 -52
- package/src/main/lib/delegationReadiness.cjs +290 -225
- package/src/main/lib/dispatchLoop.cjs +40 -0
- package/src/main/lib/effectiveModelInfo.cjs +332 -0
- package/src/main/lib/ephemeralCwd.cjs +7 -28
- package/src/main/lib/epicMint.cjs +3 -3
- package/src/main/lib/gitWorktree.cjs +136 -19
- package/src/main/lib/guardShims.cjs +156 -0
- package/src/main/lib/historyRollup.cjs +6 -7
- package/src/main/lib/instanceLock.cjs +32 -5
- package/src/main/lib/jobDirtFilter.cjs +7 -2
- package/src/main/lib/jobSupervisorRecord.cjs +147 -0
- package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
- package/src/main/lib/launchFailure.cjs +2 -1
- package/src/main/lib/localAdminHttp.cjs +9 -27
- package/src/main/lib/mcpToolCatalog.cjs +33 -78
- package/src/main/lib/needsReviewLedger.cjs +205 -0
- package/src/main/lib/opsErrorLog.cjs +1 -1
- package/src/main/lib/opsOwnership.cjs +27 -19
- package/src/main/lib/prdAuthoringSeed.cjs +36 -0
- package/src/main/lib/prdCreate.cjs +56 -1
- package/src/main/lib/prdDisposition.cjs +199 -0
- package/src/main/lib/prdFrontmatter.cjs +8 -2
- package/src/main/lib/prdLocations.cjs +214 -45
- package/src/main/lib/procIdentity.cjs +126 -0
- package/src/main/lib/procName.cjs +98 -0
- package/src/main/lib/projectHomeAdminRoutes.cjs +56 -327
- package/src/main/lib/projectRootResolve.cjs +1 -1
- package/src/main/lib/queueHistory.cjs +27 -8
- package/src/main/lib/queueStore.cjs +63 -38
- package/src/main/lib/quietMachineLease.cjs +19 -2
- package/src/main/lib/reaperHelpers.cjs +181 -15
- package/src/main/lib/reservationExpiry.cjs +30 -0
- package/src/main/lib/runClaudeP.cjs +4 -2
- package/src/main/lib/runLogRetention.cjs +3 -2
- package/src/main/lib/scheduleJobSchema.cjs +10 -2
- package/src/main/lib/scheduleJobTransitions.cjs +60 -0
- package/src/main/lib/schedulerBatch.cjs +27 -2
- package/src/main/lib/schedulerConfig.cjs +24 -0
- package/src/main/lib/schedulerPaths.cjs +175 -0
- package/src/main/lib/schedulerRuntimeState.cjs +59 -0
- package/src/main/lib/sessionSlots.cjs +47 -9
- package/src/main/lib/smProcNames.cjs +51 -0
- package/src/main/lib/upgradeDrain.cjs +188 -0
- package/src/main/lib/usageCircuit.cjs +206 -0
- package/{scripts → src/main}/lib/watchdogHelpers.cjs +71 -38
- package/src/main/lib/withTimeout.cjs +33 -0
- package/src/main/mcpStatus.cjs +4 -2
- package/src/main/pluginInstall.cjs +6 -2
- package/src/main/projectPages.cjs +41 -145
- package/src/main/pty.cjs +8 -1
- package/src/main/queueOps.cjs +10 -10
- package/src/main/runVerify.cjs +69 -3
- package/src/main/scheduler/prdParser.cjs +13 -0
- package/src/main/scheduler.cjs +2841 -482
- package/src/main/seedAgentPersonas.cjs +1 -1
- package/src/main/seedDevPlugin.cjs +1 -1
- package/src/main/seedSchedulerMcp.cjs +7 -6
- package/src/main/seedStatus.cjs +1 -1
- package/src/main/supervisor.cjs +5 -3
- package/src/main/templates/PRD_AUTHORING.md +50 -0
- package/src/main/usage.cjs +140 -58
- package/src/preload/api.d.ts +110 -65
- package/src/preload/index.cjs +12 -4
- package/src/seed/agents/project-home-builder.md +31 -48
- package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
- package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
- package/dist/assets/Permissions-BiYZNGYW.js +0 -3
- package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
- package/dist/assets/Settings-Cv-pRyms.js +0 -3
- package/dist/assets/settingsSchema-BJVciriw.js +0 -3
- package/screenshots/.gitkeep +0 -0
- package/screenshots/README-screenshots.md +0 -13
- package/scripts/project-pages-logic/dist/logic.cjs +0 -4709
- package/scripts/render-project-pages/dist/renderer.cjs +0 -18900
- package/scripts/render-project-pages.cjs +0 -70
- package/scripts/validate-project-pages-summary.cjs +0 -62
- package/src/main/lib/projectPageSummarySchema.cjs +0 -181
- package/src/main/teams.cjs +0 -95
- package/src/main/templates/project-pages-catalog.json +0 -741
- package/src/main/templates/project-pages-default-home.html +0 -123
- package/src/main/templates/project-pages-pipeline.md +0 -417
- package/src/seed/prompts/code-review/ac-coverage-check.md +0 -8
- package/src/seed/prompts/code-review/correctness-only.md +0 -8
- package/src/seed/prompts/code-review/full-spectrum-high.md +0 -8
- package/src/seed/prompts/code-review/hallucination-check.md +0 -8
- package/src/seed/prompts/code-review/public-api-compat.md +0 -8
- package/src/seed/prompts/code-review/readability-naming.md +0 -8
- package/src/seed/prompts/debugging/bug-as-failing-test.md +0 -8
- package/src/seed/prompts/debugging/git-bisect-regression.md +0 -8
- package/src/seed/prompts/debugging/instrument-intermittent-bug.md +0 -8
- package/src/seed/prompts/debugging/localize-pipeline-failure.md +0 -8
- package/src/seed/prompts/debugging/reproduce-then-diagnose.md +0 -8
- package/src/seed/prompts/documentation/adr-from-change.md +0 -8
- package/src/seed/prompts/documentation/module-readme.md +0 -8
- package/src/seed/prompts/documentation/onboarding-plan.md +0 -8
- package/src/seed/prompts/documentation/refresh-claude-md.md +0 -8
- package/src/seed/prompts/documentation/tsdoc-public-exports.md +0 -8
- package/src/seed/prompts/git-pr/conventional-commit.md +0 -8
- package/src/seed/prompts/git-pr/draft-pr-title-body.md +0 -8
- package/src/seed/prompts/git-pr/pre-commit-safety-sweep.md +0 -8
- package/src/seed/prompts/git-pr/release-notes-block.md +0 -8
- package/src/seed/prompts/git-pr/split-large-pr.md +0 -8
- package/src/seed/prompts/performance/bundle-startup-audit.md +0 -8
- package/src/seed/prompts/performance/complexity-audit.md +0 -8
- package/src/seed/prompts/performance/cpu-profile-hot-path.md +0 -8
- package/src/seed/prompts/performance/db-query-plan-review.md +0 -8
- package/src/seed/prompts/performance/memory-leak-hunt.md +0 -8
- package/src/seed/prompts/qa/api-contract-tests.md +0 -8
- package/src/seed/prompts/qa/e2e-critical-path.md +0 -8
- package/src/seed/prompts/qa/failing-test-for-bug.md +0 -8
- package/src/seed/prompts/qa/find-missing-test-coverage.md +0 -8
- package/src/seed/prompts/qa/stabilize-flaky-test.md +0 -8
- package/src/seed/prompts/qa/tdd-red-first.md +0 -8
- package/src/seed/prompts/qa/visual-regression-review.md +0 -8
- package/src/seed/prompts/qa/wcag-axe-scan.md +0 -8
- package/src/seed/prompts/refactoring/dead-code-sweep.md +0 -8
- package/src/seed/prompts/refactoring/extract-duplicated-pattern.md +0 -8
- package/src/seed/prompts/refactoring/modernize-legacy-file.md +0 -8
- package/src/seed/prompts/refactoring/reduce-cyclomatic-complexity.md +0 -8
- package/src/seed/prompts/refactoring/tighten-module-boundaries.md +0 -8
- package/src/seed/prompts/security/authz-audit.md +0 -8
- package/src/seed/prompts/security/crypto-correctness.md +0 -8
- package/src/seed/prompts/security/cwe-top-25-hunt.md +0 -8
- package/src/seed/prompts/security/dependency-audit.md +0 -8
- package/src/seed/prompts/security/ipc-boundary-hardening.md +0 -8
- package/src/seed/prompts/security/owasp-top-10-staged-diff.md +0 -8
- package/src/seed/prompts/security/secret-credential-scan.md +0 -8
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* First-boot seeder for the bundled `session-manager-dev` plugin.
|
|
3
3
|
*
|
|
4
|
-
* The plugin (
|
|
4
|
+
* The plugin (the dev skills under plugins/session-manager-dev/skills) ships inside the npx distribution. To make it
|
|
5
5
|
* a true default, the app installs it on first launch from its own bundled
|
|
6
6
|
* marketplace (offline — no GitHub/registry). Idempotent two ways:
|
|
7
7
|
*
|
|
@@ -35,7 +35,7 @@ const fs = require('node:fs');
|
|
|
35
35
|
const path = require('node:path');
|
|
36
36
|
const os = require('node:os');
|
|
37
37
|
const { spawn } = require('node:child_process');
|
|
38
|
-
const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
|
|
38
|
+
const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claudeBin.cjs');
|
|
39
39
|
const { cleanChildEnv, pathWithUserBins } = require('./lib/cleanEnv.cjs');
|
|
40
40
|
const { writeJsonSync } = require('./config.cjs');
|
|
41
41
|
|
|
@@ -106,13 +106,14 @@ function scriptPathMissing() {
|
|
|
106
106
|
* No shell:true — argv array passed directly to spawn. */
|
|
107
107
|
function runClaudeMcpAdd(serverPath) {
|
|
108
108
|
return new Promise((resolve) => {
|
|
109
|
-
const
|
|
109
|
+
const target = claudeSpawnTarget('aux', 'mcp-add');
|
|
110
110
|
let proc;
|
|
111
111
|
try {
|
|
112
112
|
proc = spawn(
|
|
113
|
-
|
|
113
|
+
target.command,
|
|
114
114
|
['mcp', 'add', SERVER_NAME, '--scope', 'user', '--', 'node', serverPath],
|
|
115
115
|
{
|
|
116
|
+
...(target.argv0 ? { argv0: target.argv0 } : {}),
|
|
116
117
|
cwd: os.homedir(),
|
|
117
118
|
env: cleanChildEnv({ PATH: pathWithUserBins() }),
|
|
118
119
|
stdio: 'ignore',
|
|
@@ -130,13 +131,13 @@ function runClaudeMcpAdd(serverPath) {
|
|
|
130
131
|
/** Run `claude mcp remove session-manager-scheduler --scope user`, best-effort. */
|
|
131
132
|
function runClaudeMcpRemove() {
|
|
132
133
|
return new Promise((resolve) => {
|
|
133
|
-
const
|
|
134
|
+
const target = claudeSpawnTarget('aux', 'mcp-remove');
|
|
134
135
|
let proc;
|
|
135
136
|
try {
|
|
136
137
|
proc = spawn(
|
|
137
|
-
|
|
138
|
+
target.command,
|
|
138
139
|
['mcp', 'remove', SERVER_NAME, '--scope', 'user'],
|
|
139
|
-
{ cwd: os.homedir(), env: cleanChildEnv({ PATH: pathWithUserBins() }), stdio: 'ignore' }
|
|
140
|
+
{ cwd: os.homedir(), env: cleanChildEnv({ PATH: pathWithUserBins() }), stdio: 'ignore', ...(target.argv0 ? { argv0: target.argv0 } : {}) }
|
|
140
141
|
);
|
|
141
142
|
} catch (err) {
|
|
142
143
|
resolve({ ok: false, error: err?.message ?? String(err) });
|
package/src/main/seedStatus.cjs
CHANGED
|
@@ -43,7 +43,7 @@ const FIXES = {
|
|
|
43
43
|
'dev-plugin': 'Open Plugins → Library and click Install next to "session-manager-dev".',
|
|
44
44
|
'scheduler-mcp': () =>
|
|
45
45
|
`Run in a terminal: claude mcp add ${seedSchedulerMcp.SERVER_NAME} --scope user -- node ${seedSchedulerMcp.serverScriptPath()}`,
|
|
46
|
-
'agent-personas':
|
|
46
|
+
'agent-personas': `Copy ${seedAgentPersonas.PERSONAS.map((n) => `src/seed/agents/${n}.md`).join(', ')} into ~/.claude/agents/ (skip any that already exist there).`,
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
/** { 'dev-plugin': {status, fix}, 'scheduler-mcp': {status, fix}, 'agent-personas': {status, fix} } */
|
package/src/main/supervisor.cjs
CHANGED
|
@@ -16,7 +16,8 @@ const path = require('node:path');
|
|
|
16
16
|
const os = require('node:os');
|
|
17
17
|
const { spawn, execFileSync } = require('node:child_process');
|
|
18
18
|
const { ipcMain } = require('electron');
|
|
19
|
-
const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
|
|
19
|
+
const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claudeBin.cjs');
|
|
20
|
+
const { withProcRole } = require('./lib/cleanEnv.cjs');
|
|
20
21
|
|
|
21
22
|
const HOME = os.homedir();
|
|
22
23
|
const SUPERVISOR_LOG_PATH = path.join(HOME, '.claude', 'session-manager', 'supervisor.log');
|
|
@@ -207,7 +208,8 @@ function runProbe(claudeBin, prompt) {
|
|
|
207
208
|
return new Promise((resolve) => {
|
|
208
209
|
let child;
|
|
209
210
|
try {
|
|
210
|
-
|
|
211
|
+
const target = claudeSpawnTarget('aux', 'supervisor', claudeBin);
|
|
212
|
+
child = spawn(target.command, [
|
|
211
213
|
'-p', prompt,
|
|
212
214
|
'--model', 'claude-opus-4-7',
|
|
213
215
|
'--no-session-persistence',
|
|
@@ -215,7 +217,7 @@ function runProbe(claudeBin, prompt) {
|
|
|
215
217
|
'--max-budget-usd', '0.10',
|
|
216
218
|
'--dangerously-skip-permissions',
|
|
217
219
|
'--allowedTools', 'Bash',
|
|
218
|
-
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
220
|
+
], { env: withProcRole(process.env, 'aux'), stdio: ['ignore', 'pipe', 'pipe'], ...(target.argv0 ? { argv0: target.argv0 } : {}) });
|
|
219
221
|
} catch (e) {
|
|
220
222
|
console.error('[supervisor] probe spawn failed:', e?.message);
|
|
221
223
|
resolve({ verdict: 'ok', action: 'none', targetPid: null, reason: `spawn failed: ${e?.message}`, costUsd: null });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<!-- PRD_AUTHORING.md v2 -->
|
|
1
2
|
# PRD Authoring Guide — Scheduler Safety Rules
|
|
2
3
|
|
|
3
4
|
This guide codifies lessons from two stuck-job incidents (fizzpop poll-hang, etch-engine post-AC overrun) into enforceable rules every PRD MUST follow. Violating these rules costs real money and wastes hours waiting at a terminal.
|
|
@@ -148,6 +149,8 @@ estimateMinutes: 60
|
|
|
148
149
|
|
|
149
150
|
**Rule:** Include exact file paths, function signatures if they save a Read, library versions, and the name of any sibling PRD the executor must NOT duplicate. Do not reference "the conversation", "what we discussed", "the design doc", or any other external context. If the executor would need to search for something, include the answer.
|
|
150
151
|
|
|
152
|
+
**Epic context digest is additive, not a dependency (PRD 958):** when a job's `epicId` resolves to a known Epic in that project's `active-index.json`, the scheduler prepends a short digest of the Epic's own session (goal text + recent turns, built by `src/main/lib/epicContextDigest.cjs`'s `buildContextDigest`) to the `-p` prompt sent to the executor — the on-disk PRD `.md` file itself is never rewritten. This exists purely to orient the executor faster; it is never load-bearing. The digest is a silent no-op when the Epic doesn't resolve, and any failure building it is caught and logged, never blocking dispatch. Every PRD body must still stand on its own per the rule above — write it as if the digest will not be there.
|
|
153
|
+
|
|
151
154
|
---
|
|
152
155
|
|
|
153
156
|
## §8 Scope sizing — target ~15 min, ceiling 30 (data-driven, 2026-06)
|
|
@@ -448,3 +451,50 @@ intake`, commit `352b89c`). It syncs open GitHub issues into `session-manager-op
|
|
|
448
451
|
→ queue path the rest of this guide documents. An external project building a Slack (or any
|
|
449
452
|
other) adapter should follow the same source → triage → queue shape, ending at
|
|
450
453
|
`scheduler_create_prd` (app running) or a hand-written PRD file (app closed) as described above.
|
|
454
|
+
|
|
455
|
+
## §15 Resolving a `failed`/`needs_review` job whose target work is already done
|
|
456
|
+
|
|
457
|
+
**Symptom:** a job lands in `failed` or `needs_review` even though its actual objective was
|
|
458
|
+
already satisfied — either by a sibling/concurrent job that finished the same work first (a
|
|
459
|
+
duplicate PRD racing another one), or by an out-of-band actor (a human, another agent) reaching
|
|
460
|
+
the same target state before the scheduler's run even started. The job's own transcript may be
|
|
461
|
+
completely accurate (`SCHEDULER_VERDICT: PASS but no commit landed during the run window`) — the
|
|
462
|
+
"failure" is a stale queue entry, not broken work.
|
|
463
|
+
|
|
464
|
+
**Do NOT hand-edit `queue.json` to fix the status.** It's a live file the running Electron
|
|
465
|
+
scheduler process (and the external watchdog) both read and write; a manual edit races the app's
|
|
466
|
+
own save cycle and risks a torn write. There is also no supported IPC/CLI surface today to flip a
|
|
467
|
+
single job's `status` field directly.
|
|
468
|
+
|
|
469
|
+
**The safe, supported remediation — confirmed working live (2026-07-18):** archive the job's PRD
|
|
470
|
+
*source file*, not the queue entry. `reconcile()` (`src/main/scheduler.cjs`) runs on every queue
|
|
471
|
+
read/tick and drops any `queue.json` job entry whose PRD `.md` no longer exists in `prds/` — so
|
|
472
|
+
archiving the file is sufficient; you never touch `queue.json` yourself.
|
|
473
|
+
|
|
474
|
+
```js
|
|
475
|
+
// From this repo's root (session-manager), or anywhere queueOps.cjs is reachable:
|
|
476
|
+
const q = require('./src/main/queueOps.cjs');
|
|
477
|
+
await q.archiveMany(['<slug-of-the-stale-job>']);
|
|
478
|
+
// Atomic rename to prds-archived/<ISO>/<slug>.md — reversible, path-contained,
|
|
479
|
+
// no queue.json write. The next reconcile() (within one scheduler tick, ~10-15s
|
|
480
|
+
// observed) drops the matching queue.json job entry automatically.
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
This is the exact mechanism `schedule:clear-queue`'s IPC handler uses internally, just scoped to
|
|
484
|
+
one slug instead of "every non-running job" — safe to call from outside Electron (no admin API
|
|
485
|
+
needed) since `queueOps.cjs`'s `archiveMany` is a plain exported function, not IPC-gated.
|
|
486
|
+
|
|
487
|
+
**When to use this vs. requeueing a retry:** only when you've confirmed the target state is
|
|
488
|
+
already correct (read the failed job's own transcript/log — did it conclude "nothing left to
|
|
489
|
+
do"? did a sibling job's commit already land the same objective? does an independent check like
|
|
490
|
+
`gh pr view <n> --json mergeable` already show the desired end state?). If the target state is
|
|
491
|
+
genuinely NOT yet reached, fix and requeue instead — archiving does not fix an actual bug, it
|
|
492
|
+
only clears a stale status label for already-completed work.
|
|
493
|
+
|
|
494
|
+
**Known gap this doesn't cover:** the verifier's `pass_no_commit` classification (a run that
|
|
495
|
+
concludes "PASS, no code change needed" gets flagged `needs_review` as if it were suspicious)
|
|
496
|
+
does not yet special-case "another actor already satisfied this PRD's postcondition" for
|
|
497
|
+
externally-checkable targets (e.g. a `gh pr view`-mergeable branch). `RESCANNABLE_VERDICTS`
|
|
498
|
+
already includes `pass_no_commit` for one narrow case (fix-plan jobs, exempted 2026-07-12) but
|
|
499
|
+
not the general case. See `session-manager-operations/feedback/processed/` for the tracked
|
|
500
|
+
follow-up on softening this classification for merge-style PRDs with a checkable postcondition.
|
package/src/main/usage.cjs
CHANGED
|
@@ -23,6 +23,7 @@ const os = require('node:os');
|
|
|
23
23
|
const { ipcMain } = require('electron');
|
|
24
24
|
const { refreshIfNeeded, expiresAtMs } = require('./lib/credentials.cjs');
|
|
25
25
|
const { writeJson } = require('./config.cjs');
|
|
26
|
+
const { createUsageCircuit, singleFlight } = require('./lib/usageCircuit.cjs');
|
|
26
27
|
|
|
27
28
|
const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
28
29
|
|
|
@@ -116,6 +117,19 @@ const OK_CACHE_TTL_MS = 30_000;
|
|
|
116
117
|
*
|
|
117
118
|
* Returns one of: 'ok' | 'auth' | 'transient' | 'meter_rate_limited'
|
|
118
119
|
*/
|
|
120
|
+
/**
|
|
121
|
+
* Parses a `Retry-After` header value (delta-seconds or an HTTP-date) into a
|
|
122
|
+
* millisecond duration from now, or null if absent/unparseable.
|
|
123
|
+
*/
|
|
124
|
+
function parseRetryAfterMs(headerValue) {
|
|
125
|
+
if (!headerValue) return null;
|
|
126
|
+
const seconds = Number(headerValue);
|
|
127
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;
|
|
128
|
+
const dateMs = Date.parse(headerValue);
|
|
129
|
+
if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
119
133
|
function classifyUsageResponse(status, bodyText) {
|
|
120
134
|
if (status === 401 || status === 403) return { kind: 'auth', httpStatus: status };
|
|
121
135
|
if (status === 429) {
|
|
@@ -133,6 +147,18 @@ function classifyUsageResponse(status, bodyText) {
|
|
|
133
147
|
|
|
134
148
|
let cache = null;
|
|
135
149
|
let hydrationPromise = null;
|
|
150
|
+
// Retry-After suppression window: while `now < retryNotBeforeMs`, fetchUsage()
|
|
151
|
+
// refuses to issue another request and instead replays `lastRateLimitedResult`
|
|
152
|
+
// (annotated `suppressed: true`) — set only from a 429's Retry-After header.
|
|
153
|
+
let retryNotBeforeMs = 0;
|
|
154
|
+
let lastRateLimitedResult = null;
|
|
155
|
+
|
|
156
|
+
// Single shared circuit for the /api/oauth/usage meter. Both callers — the
|
|
157
|
+
// renderer's billing:fetch IPC handler and the scheduler's pollLoop — go
|
|
158
|
+
// through this same fetchUsage(), so a success recorded from EITHER caller
|
|
159
|
+
// clears the streak for both, and concurrent callers coalesce into one HTTP
|
|
160
|
+
// request via singleFlight.
|
|
161
|
+
const circuit = createUsageCircuit();
|
|
136
162
|
|
|
137
163
|
async function hydrateCache() {
|
|
138
164
|
try {
|
|
@@ -142,23 +168,17 @@ async function hydrateCache() {
|
|
|
142
168
|
}
|
|
143
169
|
}
|
|
144
170
|
|
|
171
|
+
function ensureHydrated() {
|
|
172
|
+
if (!hydrationPromise) hydrationPromise = hydrateCache();
|
|
173
|
+
return hydrationPromise;
|
|
174
|
+
}
|
|
175
|
+
|
|
145
176
|
async function persistCache(c) {
|
|
146
177
|
await writeJson(CACHE_PATH, c);
|
|
147
178
|
}
|
|
148
179
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// without hitting the real endpoint. Only active when SM_E2E=1 to prevent
|
|
152
|
-
// accidental use in production.
|
|
153
|
-
if (process.env.SM_E2E === '1' && process.env.SM_MOCK_BILLING_KIND) {
|
|
154
|
-
const kind = process.env.SM_MOCK_BILLING_KIND;
|
|
155
|
-
if (kind === 'meter_rate_limited') return { kind: 'meter_rate_limited', message: 'e2e stub', httpStatus: 429 };
|
|
156
|
-
if (kind === 'transient') return { kind: 'transient', message: 'e2e stub', httpStatus: 503 };
|
|
157
|
-
if (kind === 'auth') return { kind: 'auth', message: 'e2e stub', httpStatus: 401 };
|
|
158
|
-
// 'ok' stub returns a minimal valid payload.
|
|
159
|
-
return { kind: 'ok', data: { usage: { five_hour: { utilization: 10, resets_at: null }, seven_day: { utilization: 10, resets_at: null }, seven_day_sonnet: null, seven_day_opus: null, extra_usage: null }, subscriptionType: null, rateLimitTier: null, credentialsExpiresAt: null, fetchedAt: Date.now() } };
|
|
160
|
-
}
|
|
161
|
-
|
|
180
|
+
/** The actual network round-trip, coalesced across concurrent callers below. */
|
|
181
|
+
async function networkFetchUsage() {
|
|
162
182
|
// Check expiry and attempt proactive refresh before touching the network.
|
|
163
183
|
const refresh = await refreshIfNeeded();
|
|
164
184
|
if (refresh.kind === 'auth') {
|
|
@@ -168,6 +188,7 @@ async function fetchUsage() {
|
|
|
168
188
|
// 'ok' or 'unsupported' — creds present and not yet expired
|
|
169
189
|
const creds = refresh.creds;
|
|
170
190
|
|
|
191
|
+
let result;
|
|
171
192
|
let r;
|
|
172
193
|
try {
|
|
173
194
|
r = await fetch(USAGE_URL, {
|
|
@@ -179,59 +200,107 @@ async function fetchUsage() {
|
|
|
179
200
|
signal: AbortSignal.timeout(10_000),
|
|
180
201
|
});
|
|
181
202
|
} catch (e) {
|
|
182
|
-
|
|
203
|
+
result = { kind: 'transient', message: e.message || String(e), httpStatus: null };
|
|
183
204
|
}
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
205
|
+
if (!result) {
|
|
206
|
+
if (r.status === 401 || r.status === 403) {
|
|
207
|
+
const body = await r.text().catch(() => '');
|
|
208
|
+
const ms = expiresAtMs(creds);
|
|
209
|
+
result = { kind: 'auth', message: body.slice(0, 200) || `HTTP ${r.status}`, httpStatus: r.status, expiredAt: ms };
|
|
210
|
+
} else if (r.status === 408 || r.status >= 500) {
|
|
211
|
+
const body = await r.text().catch(() => '');
|
|
212
|
+
result = { kind: 'transient', message: body.slice(0, 200) || `HTTP ${r.status}`, httpStatus: r.status };
|
|
213
|
+
} else if (r.status === 429) {
|
|
214
|
+
const body = await r.text().catch(() => '');
|
|
215
|
+
let parsed = null;
|
|
216
|
+
try { parsed = JSON.parse(body); } catch { /* */ }
|
|
217
|
+
if (parsed?.error?.type === 'rate_limit_error') {
|
|
218
|
+
result = {
|
|
219
|
+
kind: 'meter_rate_limited',
|
|
220
|
+
message: body.slice(0, 200),
|
|
221
|
+
httpStatus: 429,
|
|
222
|
+
retryAfterMs: parseRetryAfterMs(r.headers.get('retry-after')),
|
|
223
|
+
};
|
|
224
|
+
} else {
|
|
225
|
+
result = { kind: 'transient', message: body.slice(0, 200) || 'HTTP 429', httpStatus: 429 };
|
|
226
|
+
}
|
|
227
|
+
} else if (!r.ok) {
|
|
228
|
+
const body = await r.text().catch(() => '');
|
|
229
|
+
result = { kind: 'transient', message: body.slice(0, 200) || `HTTP ${r.status}`, httpStatus: r.status };
|
|
230
|
+
} else {
|
|
231
|
+
const usage = await r.json();
|
|
232
|
+
result = {
|
|
233
|
+
kind: 'ok',
|
|
234
|
+
data: {
|
|
235
|
+
usage,
|
|
236
|
+
subscriptionType: creds.subscriptionType ?? null,
|
|
237
|
+
rateLimitTier: creds.rateLimitTier ?? null,
|
|
238
|
+
credentialsExpiresAt: creds.expiresAt ?? null,
|
|
239
|
+
fetchedAt: Date.now(),
|
|
240
|
+
},
|
|
241
|
+
};
|
|
199
242
|
}
|
|
200
|
-
return { kind: 'transient', message: body.slice(0, 200) || 'HTTP 429', httpStatus: 429 };
|
|
201
243
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
244
|
+
|
|
245
|
+
if (result.kind === 'ok') {
|
|
246
|
+
cache = { data: result.data, fetchedAt: Date.now(), sourceCredsExpiresAt: result.data.credentialsExpiresAt };
|
|
247
|
+
persistCache(cache).catch(() => {});
|
|
248
|
+
retryNotBeforeMs = 0;
|
|
249
|
+
lastRateLimitedResult = null;
|
|
250
|
+
circuit.recordSuccess(result.data);
|
|
251
|
+
} else if (result.kind === 'meter_rate_limited') {
|
|
252
|
+
lastRateLimitedResult = result;
|
|
253
|
+
if (Number.isFinite(result.retryAfterMs) && result.retryAfterMs > 0) {
|
|
254
|
+
retryNotBeforeMs = Date.now() + result.retryAfterMs;
|
|
255
|
+
}
|
|
256
|
+
circuit.recordFailure(result.kind);
|
|
257
|
+
} else if (result.kind === 'transient') {
|
|
258
|
+
circuit.recordFailure(result.kind);
|
|
205
259
|
}
|
|
206
|
-
|
|
207
|
-
return {
|
|
208
|
-
kind: 'ok',
|
|
209
|
-
data: {
|
|
210
|
-
usage,
|
|
211
|
-
subscriptionType: creds.subscriptionType ?? null,
|
|
212
|
-
rateLimitTier: creds.rateLimitTier ?? null,
|
|
213
|
-
credentialsExpiresAt: creds.expiresAt ?? null,
|
|
214
|
-
fetchedAt: Date.now(),
|
|
215
|
-
},
|
|
216
|
-
};
|
|
260
|
+
return result;
|
|
217
261
|
}
|
|
218
262
|
|
|
219
|
-
|
|
220
|
-
|
|
263
|
+
// Concurrent callers (the renderer's billing:fetch and the scheduler's
|
|
264
|
+
// pollLoop) coalesce into this one in-flight promise instead of each firing
|
|
265
|
+
// their own request against a rate-limited endpoint.
|
|
266
|
+
const singleFlightNetworkFetch = singleFlight(networkFetchUsage);
|
|
221
267
|
|
|
222
|
-
|
|
223
|
-
|
|
268
|
+
/**
|
|
269
|
+
* The single owner of caching, single-flight de-duplication and Retry-After
|
|
270
|
+
* suppression for the usage meter. Every caller — renderer IPC and scheduler
|
|
271
|
+
* pollLoop alike — must go through this function rather than hitting the
|
|
272
|
+
* network or the cache file directly.
|
|
273
|
+
*/
|
|
274
|
+
async function fetchUsage() {
|
|
275
|
+
// Test stub: SM_MOCK_BILLING_KIND lets e2e tests simulate billing API responses
|
|
276
|
+
// without hitting the real endpoint. Only active when SM_E2E=1 to prevent
|
|
277
|
+
// accidental use in production.
|
|
278
|
+
if (process.env.SM_E2E === '1' && process.env.SM_MOCK_BILLING_KIND) {
|
|
279
|
+
const kind = process.env.SM_MOCK_BILLING_KIND;
|
|
280
|
+
if (kind === 'meter_rate_limited') return { kind: 'meter_rate_limited', message: 'e2e stub', httpStatus: 429 };
|
|
281
|
+
if (kind === 'transient') return { kind: 'transient', message: 'e2e stub', httpStatus: 503 };
|
|
282
|
+
if (kind === 'auth') return { kind: 'auth', message: 'e2e stub', httpStatus: 401 };
|
|
283
|
+
// 'ok' stub returns a minimal valid payload.
|
|
284
|
+
return { kind: 'ok', data: { usage: { five_hour: { utilization: 10, resets_at: null }, seven_day: { utilization: 10, resets_at: null }, seven_day_sonnet: null, seven_day_opus: null, extra_usage: null }, subscriptionType: null, rateLimitTier: null, credentialsExpiresAt: null, fetchedAt: Date.now() } };
|
|
285
|
+
}
|
|
224
286
|
|
|
225
|
-
|
|
226
|
-
return { kind: 'ok', data: cache.data };
|
|
227
|
-
}
|
|
287
|
+
await ensureHydrated();
|
|
228
288
|
|
|
289
|
+
if (cache && cache.fetchedAt && Date.now() - cache.fetchedAt < OK_CACHE_TTL_MS) {
|
|
290
|
+
return { kind: 'ok', data: cache.data };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (lastRateLimitedResult && retryNotBeforeMs && Date.now() < retryNotBeforeMs) {
|
|
294
|
+
return { ...lastRateLimitedResult, suppressed: true };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return singleFlightNetworkFetch();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function registerBillingHandlers() {
|
|
301
|
+
ipcMain.handle('billing:fetch', async () => {
|
|
229
302
|
const r = await fetchUsage();
|
|
230
|
-
if (r.kind === 'ok') {
|
|
231
|
-
cache = { data: r.data, fetchedAt: Date.now(), sourceCredsExpiresAt: r.data.credentialsExpiresAt };
|
|
232
|
-
persistCache(cache).catch(() => {});
|
|
233
|
-
return { kind: 'ok', data: r.data };
|
|
234
|
-
}
|
|
303
|
+
if (r.kind === 'ok') return { kind: 'ok', data: r.data };
|
|
235
304
|
if (r.kind === 'auth') {
|
|
236
305
|
if (cache) return { kind: 'auth', message: r.message, httpStatus: r.httpStatus, expiredAt: r.expiredAt, cached: cache.data, staleSince: cache.fetchedAt };
|
|
237
306
|
return r;
|
|
@@ -241,11 +310,24 @@ function registerBillingHandlers() {
|
|
|
241
310
|
return r;
|
|
242
311
|
}
|
|
243
312
|
if (r.kind === 'meter_rate_limited') {
|
|
244
|
-
if (cache) return { kind: 'meter_rate_limited', message: r.message, httpStatus: r.httpStatus, cached: cache.data, staleSince: cache.fetchedAt };
|
|
313
|
+
if (cache) return { kind: 'meter_rate_limited', message: r.message, httpStatus: r.httpStatus, retryAfterMs: r.retryAfterMs, suppressed: r.suppressed, cached: cache.data, staleSince: cache.fetchedAt };
|
|
245
314
|
return r;
|
|
246
315
|
}
|
|
247
316
|
return r; // config
|
|
248
317
|
});
|
|
249
318
|
}
|
|
250
319
|
|
|
251
|
-
module.exports = {
|
|
320
|
+
module.exports = {
|
|
321
|
+
registerBillingHandlers,
|
|
322
|
+
fetchUsage,
|
|
323
|
+
classifyUsageResponse,
|
|
324
|
+
parseRetryAfterMs,
|
|
325
|
+
usageMeterApplicable,
|
|
326
|
+
readClaudeSettingsAuth,
|
|
327
|
+
// The single shared breaker instance — scheduler.cjs's pollLoop (and every
|
|
328
|
+
// other consumer) reads THIS object's state()/openedAt()/
|
|
329
|
+
// getConsecutiveFailures() rather than deriving its own, so a failure
|
|
330
|
+
// recorded via any path (poller, executor 429) opens the same circuit.
|
|
331
|
+
usageCircuit: circuit,
|
|
332
|
+
__usageCircuitForTest: circuit,
|
|
333
|
+
};
|