claude-code-session-manager 0.87.0 → 0.88.1
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-DyLWzZDf.js → AgentLibrary-kQF8_wAw.js} +1 -1
- package/dist/assets/{DataModel--mISIJ6h.js → DataModel-CY_mMnsr.js} +1 -1
- package/dist/assets/{History-C2ahUXTg.js → History-Sji2brb0.js} +1 -1
- package/dist/assets/{Hooks-BiC6oyR2.js → Hooks-CtqpO-p5.js} +1 -1
- package/dist/assets/{HostBilko-BPleEOld.js → HostBilko-DN0ydOCr.js} +1 -1
- package/dist/assets/{Library-Dc8Qst1R.js → Library-CoiLZTKh.js} +1 -1
- package/dist/assets/{ListDetail-DIXh-OLX.js → ListDetail-CmqiQ_io.js} +1 -1
- package/dist/assets/{MarkdownEditor-C90bkLXK.js → MarkdownEditor-HPoNeKhT.js} +1 -1
- package/dist/assets/{McpServers-DqcbLOLZ.js → McpServers-Bj1X3_NT.js} +1 -1
- package/dist/assets/{Memory-CW62MXlh.js → Memory-1PKjsM3b.js} +1 -1
- package/dist/assets/{Panel-Bw1FhRuF.js → Panel-I1s6ghPo.js} +1 -1
- package/dist/assets/{Permissions-BcUC-5y8.js → Permissions-BhiiZjHQ.js} +1 -1
- package/dist/assets/{Plugins-BnKx9flD.js → Plugins-gv3_fkPX.js} +2 -2
- package/dist/assets/{ProvenanceBadge-Bw5vNVPT.js → ProvenanceBadge-BGcgBd8V.js} +1 -1
- package/dist/assets/{SaveBar-CWr0O_w-.js → SaveBar-BGr9eZ4g.js} +1 -1
- package/dist/assets/{Scheduler-DYdLuUqq.js → Scheduler-Bdo_awPq.js} +7 -7
- package/dist/assets/{ScopeSwitcher-CrBLbg8s.js → ScopeSwitcher-ukl0qWaA.js} +1 -1
- package/dist/assets/{Settings-DluB-vN1.js → Settings-DJkFj22G.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-CHLSseay.js → SkillReferenceGraph-0t1Qblp6.js} +1 -1
- package/dist/assets/{Skills-gNdo_HNK.js → Skills-CeVdBGE-.js} +1 -1
- package/dist/assets/{SystemPrompt-Cru05-Ia.js → SystemPrompt-CfKvNHi3.js} +1 -1
- package/dist/assets/{TagLibrary-DNHY0xou.js → TagLibrary-DAzNaUPd.js} +1 -1
- package/dist/assets/{TiptapBody-I4lmbCgP.js → TiptapBody--S_rGXoA.js} +1 -1
- package/dist/assets/{Toggle-bWMHjmRh.js → Toggle-Dszm0xnp.js} +1 -1
- package/dist/assets/{index-DV3PorRY.css → index-BHTX4OTc.css} +1 -1
- package/dist/assets/{index-fc_JjdxL.js → index-Dp4Rc--q.js} +356 -356
- package/dist/assets/{settingsSchema-BfhtZnGD.js → settingsSchema-BLbssMYo.js} +1 -1
- 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 +10 -13
- 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 +28 -71
- package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
- 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 +2 -2
- 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 +15 -1
- package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
- package/src/main/__tests__/health-tick-liveness.test.cjs +27 -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__/opsErrorLogTelemetryTap.test.cjs +25 -1
- package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +33 -3
- 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__/prdLocationsArchived.test.cjs +20 -13
- 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 +9 -9
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
- package/src/main/__tests__/queueHistory.test.cjs +2 -2
- package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
- 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 +2 -2
- 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 +78 -97
- 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 +46 -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-launch-failure.test.cjs +3 -5
- package/src/main/__tests__/scheduler-looks-done.test.cjs +26 -7
- 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-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 +23 -4
- 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 +81 -1
- package/src/main/__tests__/scheduler-stall-per-project.test.cjs +14 -0
- package/src/main/__tests__/scheduler-starve-escalation.test.cjs +4 -4
- 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 +37 -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 +4 -2
- package/src/main/config.cjs +2 -3
- package/src/main/docEdit.cjs +4 -2
- package/src/main/health.cjs +276 -52
- package/src/main/heapSnapshot.cjs +2 -2
- package/src/main/historyAggregator.cjs +1 -1
- package/src/main/index.cjs +61 -10
- package/src/main/ipcSchemas.cjs +6 -17
- package/src/main/lib/__tests__/auditLog.test.cjs +38 -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 +1 -1
- package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
- package/src/main/lib/__tests__/gateFixtures.json +20 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +8 -1
- 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__/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__/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 +61 -0
- package/src/main/lib/__tests__/watchdog-helpers.test.cjs +63 -0
- package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +73 -0
- package/src/main/lib/activeIndexRebuild.cjs +1 -3
- package/src/main/lib/activeSessions.cjs +20 -88
- package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
- package/src/main/lib/agentModelResolve.cjs +33 -1
- package/src/main/lib/agentPersonaSchema.cjs +2 -2
- package/src/main/lib/auditLog.cjs +30 -5
- 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/dispatchLoop.cjs +40 -0
- package/src/main/lib/effectiveModelInfo.cjs +9 -10
- package/src/main/lib/ephemeralCwd.cjs +7 -28
- package/src/main/lib/gitWorktree.cjs +95 -8
- package/src/main/lib/guardShims.cjs +3 -3
- package/src/main/lib/historyRollup.cjs +6 -7
- package/src/main/lib/instanceLock.cjs +32 -5
- package/src/main/lib/jobSupervisorRecord.cjs +147 -0
- package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
- package/src/main/lib/localAdminHttp.cjs +9 -27
- package/src/main/lib/mcpToolCatalog.cjs +29 -77
- package/src/main/lib/opsOwnership.cjs +27 -19
- package/src/main/lib/prdAuthoringSeed.cjs +36 -0
- package/src/main/lib/prdLocations.cjs +48 -1
- 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/queueHistory.cjs +8 -7
- package/src/main/lib/queueStore.cjs +57 -37
- package/src/main/lib/quietMachineLease.cjs +19 -2
- 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 +2 -2
- package/src/main/lib/scheduleJobTransitions.cjs +29 -2
- package/src/main/lib/schedulerBatch.cjs +27 -2
- 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 +53 -6
- package/src/main/lib/watchdogHelpers.cjs +70 -37
- 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.cjs +1607 -383
- 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/usage.cjs +126 -62
- package/src/preload/api.d.ts +18 -64
- package/src/preload/index.cjs +2 -4
- package/src/seed/agents/project-home-builder.md +31 -48
- package/screenshots/.gitkeep +0 -0
- package/screenshots/README-screenshots.md +0 -13
- 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
- package/web/README.md +0 -41
- package/web/project-pages/logic/dist/logic.cjs +0 -4709
- package/web/project-pages/render.cjs +0 -70
- package/web/project-pages/renderer/dist/renderer.cjs +0 -18900
- package/web/project-pages/validate-summary.cjs +0 -62
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* adoptedRunSupervisor.cjs — re-arms the budget watchdog, idle-tail kill and
|
|
5
|
+
* deadman for an executor the boot SPARED (scheduler.cjs stamps
|
|
6
|
+
* `adoptedAtBoot`), from the durable `<slug>.supervisor.json` record.
|
|
7
|
+
*
|
|
8
|
+
* executeJob attaches those three timers to the child it spawned; an adopted
|
|
9
|
+
* executor belongs to a dead app process, so nothing supervised it (and a
|
|
10
|
+
* quietMachine row held no lease). Here the same three conditions are polled
|
|
11
|
+
* against the record's own numbers (budgetMs / startedAt, idleKillMs vs the
|
|
12
|
+
* log mtime, maxDurationMs) by the app process that owns the row. The
|
|
13
|
+
* signal goes to the recorded process group only after the pid is proven to
|
|
14
|
+
* still be the recorded process (procIdentity, fail-closed). It never mints
|
|
15
|
+
* a slot token: the adopted row stays `untrackedRunning` in pickNextBatch.
|
|
16
|
+
*
|
|
17
|
+
* Plain Node, no electron — every effect is injected so it is unit-testable
|
|
18
|
+
* with fake timers and without scheduler.cjs.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* evaluateAdoptedRun(record, { now, logMtimeMs, defaultMaxDurationMs })
|
|
23
|
+
* → { kind: 'deadman'|'budget'|'idle-tail', reason } | null
|
|
24
|
+
* Pure. Precedence deadman > budget > idle-tail (hardest kill first).
|
|
25
|
+
*/
|
|
26
|
+
function evaluateAdoptedRun(record, { now, logMtimeMs, defaultMaxDurationMs = 0 }) {
|
|
27
|
+
const started = Number.isFinite(record.startedAt) ? record.startedAt : null;
|
|
28
|
+
const elapsed = started === null ? null : now - started;
|
|
29
|
+
const deadmanMs = record.maxDurationMs > 0 ? record.maxDurationMs : defaultMaxDurationMs;
|
|
30
|
+
if (elapsed !== null && deadmanMs > 0 && elapsed >= deadmanMs) {
|
|
31
|
+
return { kind: 'deadman', reason: `deadman: ran ${Math.round(elapsed / 60_000)}m (>= ${Math.round(deadmanMs / 60_000)}m)` };
|
|
32
|
+
}
|
|
33
|
+
if (elapsed !== null && record.budgetMs > 0 && elapsed >= record.budgetMs) {
|
|
34
|
+
return {
|
|
35
|
+
kind: 'budget',
|
|
36
|
+
reason: `wall-clock budget exceeded: ran ${Math.round(elapsed / 60_000)}m against a ${Math.round(record.budgetMs / 60_000)}m budget (adopted after app restart)`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (record.idleKillMs > 0 && Number.isFinite(logMtimeMs) && now - logMtimeMs > record.idleKillMs) {
|
|
40
|
+
return { kind: 'idle-tail', reason: `idle-output: log mtime stalled ${Math.round((now - logMtimeMs) / 1000)}s (> ${Math.round(record.idleKillMs / 1000)}s)` };
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* superviseAdoptedRun(record, deps) → { cancel(), check() }
|
|
47
|
+
* One poll timer per adopted run; `check()` runs once immediately so a run
|
|
48
|
+
* already past its budget at boot is killed without waiting a full interval.
|
|
49
|
+
* deps: { logPath, now, statLogMtimeMs, pidAlive, identityOf, isDifferentProcess,
|
|
50
|
+
* killGroup(pid, signal), stampKill(kind, reason) → Promise, log(msg),
|
|
51
|
+
* checkIntervalMs, sigkillAfterMs, defaultMaxDurationMs }
|
|
52
|
+
*/
|
|
53
|
+
function superviseAdoptedRun(record, deps) {
|
|
54
|
+
const {
|
|
55
|
+
logPath, now = Date.now, statLogMtimeMs, pidAlive, identityOf, isDifferentProcess, killGroup,
|
|
56
|
+
stampKill, log = () => {}, checkIntervalMs, sigkillAfterMs, defaultMaxDurationMs = 0,
|
|
57
|
+
} = deps;
|
|
58
|
+
let done = false;
|
|
59
|
+
let timer = null;
|
|
60
|
+
let killTimer = null;
|
|
61
|
+
|
|
62
|
+
const isOurs = () => pidAlive(record.pid) && !isDifferentProcess(record.identity, identityOf(record.pid));
|
|
63
|
+
const cancel = () => {
|
|
64
|
+
done = true;
|
|
65
|
+
if (timer) clearInterval(timer);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
async function check() {
|
|
69
|
+
if (done) return null;
|
|
70
|
+
if (!isOurs()) { cancel(); return 'gone'; } // exited (reaper finalizes) or pid recycled — never signal
|
|
71
|
+
const verdict = evaluateAdoptedRun(record, {
|
|
72
|
+
now: now(), logMtimeMs: statLogMtimeMs(logPath), defaultMaxDurationMs,
|
|
73
|
+
});
|
|
74
|
+
if (!verdict) return null;
|
|
75
|
+
cancel();
|
|
76
|
+
try { await stampKill(verdict.kind, verdict.reason); } catch (e) { log(`stampKill failed: ${e?.message ?? e}`); }
|
|
77
|
+
if (!isOurs()) return 'gone'; // exited while the stamp was in flight
|
|
78
|
+
const first = verdict.kind === 'deadman' ? 'SIGKILL' : 'SIGTERM';
|
|
79
|
+
log(`adopted-run ${verdict.kind} watchdog: ${verdict.reason} — ${first} process group`);
|
|
80
|
+
killGroup(record.pgid || record.pid, first, record.pid);
|
|
81
|
+
if (first === 'SIGTERM') {
|
|
82
|
+
killTimer = setTimeout(() => {
|
|
83
|
+
if (isOurs()) killGroup(record.pgid || record.pid, 'SIGKILL', record.pid);
|
|
84
|
+
}, sigkillAfterMs);
|
|
85
|
+
if (killTimer.unref) killTimer.unref();
|
|
86
|
+
}
|
|
87
|
+
return verdict.kind;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
timer = setInterval(() => { check().catch((e) => log(`check failed: ${e?.message ?? e}`)); }, checkIntervalMs);
|
|
91
|
+
if (timer.unref) timer.unref();
|
|
92
|
+
return {
|
|
93
|
+
cancel() { cancel(); if (killTimer) clearTimeout(killTimer); },
|
|
94
|
+
check,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* superviseAdoptedRuns(jobs, ctx) → Promise<string[]> (slugs newly armed)
|
|
100
|
+
* Arms a supervisor for every `running` row stamped adoptedAtBoot that has
|
|
101
|
+
* none in `ctx.registry` (Map key `slug|runId`), drops registry entries whose
|
|
102
|
+
* row is no longer that run, re-acquires the quietMachine lease and stamps
|
|
103
|
+
* supervisedAt. ctx: { registry, runDir(row), readRecord(runDir, slug),
|
|
104
|
+
* lease: { acquire, holder }, markSupervised(row), makeDeps(row, record) }
|
|
105
|
+
* Complexity: O(jobs).
|
|
106
|
+
*/
|
|
107
|
+
async function superviseAdoptedRuns(jobs, ctx) {
|
|
108
|
+
const { registry, runDir, readRecord, lease, markSupervised, makeDeps } = ctx;
|
|
109
|
+
const live = new Set();
|
|
110
|
+
const armed = [];
|
|
111
|
+
for (const j of jobs) {
|
|
112
|
+
if (j.status !== 'running' || !j.adoptedAtBoot) continue;
|
|
113
|
+
const key = `${j.slug}|${j.runId ?? ''}`;
|
|
114
|
+
live.add(key);
|
|
115
|
+
if (registry.has(key)) continue;
|
|
116
|
+
const dir = runDir(j);
|
|
117
|
+
const record = dir ? readRecord(dir, j.slug) : null;
|
|
118
|
+
if (!record || !record.pid) continue; // no durable record → nothing to re-arm from
|
|
119
|
+
if (j.quietMachine === true && !lease.acquire(j.slug) && lease.holder() !== j.slug) {
|
|
120
|
+
console.warn(`[scheduler] adopted quietMachine run ${j.slug}: lease held by ${lease.holder()} — not re-acquired`);
|
|
121
|
+
}
|
|
122
|
+
const handle = superviseAdoptedRun(record, makeDeps(j, record));
|
|
123
|
+
registry.set(key, handle);
|
|
124
|
+
armed.push(j.slug);
|
|
125
|
+
await markSupervised(j);
|
|
126
|
+
handle.check().catch(() => {});
|
|
127
|
+
}
|
|
128
|
+
for (const [key, handle] of registry) {
|
|
129
|
+
if (live.has(key)) continue;
|
|
130
|
+
handle.cancel();
|
|
131
|
+
registry.delete(key);
|
|
132
|
+
}
|
|
133
|
+
return armed;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { evaluateAdoptedRun, superviseAdoptedRun, superviseAdoptedRuns };
|
|
@@ -27,6 +27,7 @@ const path = require('node:path');
|
|
|
27
27
|
const os = require('node:os');
|
|
28
28
|
const { splitFrontmatter } = require('./prdFrontmatter.cjs');
|
|
29
29
|
const { readActiveIndex, resolvePersonaPaths } = require('./epicMint.cjs');
|
|
30
|
+
const { PERSONA_NAME_RE } = require('../agentLibrary.cjs');
|
|
30
31
|
const configMgr = require('../config.cjs');
|
|
31
32
|
|
|
32
33
|
/** The hardcoded floor every branch of resolveEpicModel falls back to — --model must never be omitted (CLAUDE.md "Automation model pinning"). */
|
|
@@ -125,12 +126,42 @@ function readModelFromCandidatePaths(candidates, agentType, deps) {
|
|
|
125
126
|
* — never throws.
|
|
126
127
|
*/
|
|
127
128
|
function readOverlayAwarePersonaModel(agentType, deps = {}) {
|
|
128
|
-
|
|
129
|
+
// Same guard agentLibrary.cjs's getPersonaBody applies before it joins
|
|
130
|
+
// `name` into a path: without it, a caller-supplied agentType like
|
|
131
|
+
// "../../other-project/CLAUDE" survives resolvePersonaPaths' plain
|
|
132
|
+
// path.join and validatePath's allowed-roots check (which only confirms
|
|
133
|
+
// the resolved path stays under the home dir / an opened project), letting
|
|
134
|
+
// any readable .md under those roots be read on this --model spawn path.
|
|
135
|
+
if (!agentType || !PERSONA_NAME_RE.test(agentType)) return null;
|
|
129
136
|
const resolvePaths = deps.resolvePersonaPaths || resolvePersonaPaths;
|
|
130
137
|
const { projectPath, globalPath } = resolvePaths(deps.cwd, agentType, deps);
|
|
131
138
|
return readModelFromCandidatePaths([projectPath, globalPath], agentType, deps);
|
|
132
139
|
}
|
|
133
140
|
|
|
141
|
+
/**
|
|
142
|
+
* True when `personaPath` (an already-resolved persona file path, e.g. from
|
|
143
|
+
* agentLibrary.cjs's getPersonaBody) is the PROJECT overlay candidate rather
|
|
144
|
+
* than the global one — the same "which of resolvePersonaPaths' two
|
|
145
|
+
* candidates won" question readModelFromCandidatePaths above answers
|
|
146
|
+
* structurally by trying the project path first. Exported so a caller that
|
|
147
|
+
* already has a resolved persona path from a DIFFERENT reader (one that
|
|
148
|
+
* needs async I/O or richer provenance than this module's sync
|
|
149
|
+
* readOverlayAwarePersonaModel returns — e.g. effectiveModelInfo.cjs) can
|
|
150
|
+
* ask this module the overlay question instead of re-deriving its own
|
|
151
|
+
* resolvePersonaPaths-based comparison. Never throws; a resolution failure
|
|
152
|
+
* conservatively answers "not overlay" (assume global).
|
|
153
|
+
*/
|
|
154
|
+
function isProjectOverlayPersonaPath(cwd, agentType, personaPath, deps = {}) {
|
|
155
|
+
if (!personaPath) return false;
|
|
156
|
+
try {
|
|
157
|
+
const resolvePaths = deps.resolvePersonaPaths || resolvePersonaPaths;
|
|
158
|
+
const { projectPath } = resolvePaths(cwd, agentType, deps);
|
|
159
|
+
return Boolean(projectPath) && path.resolve(projectPath) === path.resolve(personaPath);
|
|
160
|
+
} catch {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
134
165
|
/**
|
|
135
166
|
* Resolves the `--model` value for an Epic-backed session launch. Never
|
|
136
167
|
* throws — any lookup failure (missing Epic, missing persona, unreadable
|
|
@@ -220,5 +251,6 @@ module.exports = {
|
|
|
220
251
|
resolveEpicModel,
|
|
221
252
|
findAgentTypeByClaudeSessionId,
|
|
222
253
|
readOverlayAwarePersonaModel,
|
|
254
|
+
isProjectOverlayPersonaPath,
|
|
223
255
|
resolvePrdPersonaForSpawn,
|
|
224
256
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Canonical runtime schema for an Agent persona WRITE payload, mirroring the
|
|
3
|
-
* TS `AgentPersonaSaveInput` interface
|
|
3
|
+
* TS `AgentPersonaSaveInput` interface in src/preload/api.d.ts
|
|
4
4
|
* field-for-field.
|
|
5
5
|
*
|
|
6
6
|
* Modelled on promptSessionSchema.cjs / scheduleJobSchema.cjs's "zod schema
|
|
@@ -39,7 +39,7 @@ const ProjectEntrySchema = z.string().refine(
|
|
|
39
39
|
`must be "${ALL_PROJECTS}" or an absolute path`,
|
|
40
40
|
);
|
|
41
41
|
|
|
42
|
-
// Mirrors AgentPersonaSaveInput (src/preload/api.d.ts
|
|
42
|
+
// Mirrors AgentPersonaSaveInput (src/preload/api.d.ts). `tags` is the
|
|
43
43
|
// Epic-mission WorkType union, NOT a free-form string list — the same
|
|
44
44
|
// concept as PromptSessionSchema's `tag` / scheduleRetagPrd's `tag`, just
|
|
45
45
|
// plural here since a persona can be associated with more than one mission.
|
|
@@ -16,10 +16,9 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const fs = require('node:fs');
|
|
19
|
-
const os = require('node:os');
|
|
20
19
|
const path = require('node:path');
|
|
21
20
|
|
|
22
|
-
const
|
|
21
|
+
const { auditLogPath } = require('./schedulerPaths.cjs');
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
24
|
* Best-effort identity of the process making this call — there is no
|
|
@@ -50,11 +49,37 @@ function appendAuditEvent(kind, fields = {}) {
|
|
|
50
49
|
...fields,
|
|
51
50
|
caller: callerContext(),
|
|
52
51
|
};
|
|
53
|
-
|
|
54
|
-
fs.
|
|
52
|
+
const target = auditLogPath();
|
|
53
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
54
|
+
fs.appendFileSync(target, `${JSON.stringify(record)}\n`);
|
|
55
55
|
} catch (e) {
|
|
56
56
|
console.error('[auditLog] failed to append audit event', e?.message ?? String(e));
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* readTail(maxBytes, target?) → string[] — the complete JSONL lines within the
|
|
62
|
+
* last `maxBytes` of the log, oldest first. O(maxBytes), never O(file): the
|
|
63
|
+
* audit log is a never-rotated provenance record and grows without bound. A
|
|
64
|
+
* line cut by the window's start is dropped. Missing/unreadable file → [].
|
|
65
|
+
*/
|
|
66
|
+
function readTail(maxBytes, target = auditLogPath()) {
|
|
67
|
+
let fd;
|
|
68
|
+
try {
|
|
69
|
+
fd = fs.openSync(target, 'r');
|
|
70
|
+
const { size } = fs.fstatSync(fd);
|
|
71
|
+
const readSize = Math.min(Math.max(0, maxBytes), size);
|
|
72
|
+
if (readSize === 0) return [];
|
|
73
|
+
const buf = Buffer.alloc(readSize);
|
|
74
|
+
fs.readSync(fd, buf, 0, readSize, size - readSize);
|
|
75
|
+
const lines = buf.toString('utf8').split('\n');
|
|
76
|
+
if (readSize < size) lines.shift(); // first element is a partial line
|
|
77
|
+
return lines.filter(Boolean);
|
|
78
|
+
} catch {
|
|
79
|
+
return [];
|
|
80
|
+
} finally {
|
|
81
|
+
if (fd !== undefined) { try { fs.closeSync(fd); } catch { /* */ } }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { appendAuditEvent, auditLogPath, readTail };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* buildIdentity.cjs — answers "which code produced this run" for both a
|
|
5
|
+
* published npx install (no .git shipped) and a dev checkout / job worktree
|
|
6
|
+
* (a real .git, but never the source of truth once a build has been baked).
|
|
7
|
+
*
|
|
8
|
+
* Resolution order, memoized after first call:
|
|
9
|
+
* 1. src/main/build-info.json (written by scripts/write-build-info.cjs at
|
|
10
|
+
* `prepack`/`prepublishOnly` time, from the PUBLISHING checkout) — the
|
|
11
|
+
* only artifact that is honest in production, since the npx cache the
|
|
12
|
+
* app actually runs from ships no .git directory at all.
|
|
13
|
+
* 2. `git rev-parse` against the package root, but ONLY when a .git
|
|
14
|
+
* directory exists AT that exact root — never git's own upward walk.
|
|
15
|
+
* Without this guard, a relocated/nested install whose npm cache
|
|
16
|
+
* happens to sit under an ancestor .git would silently stamp a FOREIGN
|
|
17
|
+
* sha (git -C only sets the starting point; it still searches upward
|
|
18
|
+
* for the nearest .git by default). Checking existence at the package
|
|
19
|
+
* root ourselves, and only then invoking git, means the first (and
|
|
20
|
+
* only) .git git can find IS the one we already verified.
|
|
21
|
+
* 3. null.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
const { execFileSync } = require('node:child_process');
|
|
27
|
+
const { resolveInstallChannel } = require('./machineProfile.cjs');
|
|
28
|
+
|
|
29
|
+
// src/main/lib -> src/main -> src -> package root (repo root in dev, or the
|
|
30
|
+
// npx-cached package dir in production).
|
|
31
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..', '..');
|
|
32
|
+
|
|
33
|
+
function loadBuildInfoJson(packageRoot) {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(packageRoot, 'src', 'main', 'build-info.json'), 'utf8'));
|
|
36
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolvePackageVersion(packageRoot) {
|
|
43
|
+
try {
|
|
44
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
45
|
+
return typeof pkg.version === 'string' ? pkg.version : null;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveGitCodeSha(packageRoot) {
|
|
52
|
+
if (!fs.existsSync(path.join(packageRoot, '.git'))) return null;
|
|
53
|
+
try {
|
|
54
|
+
return execFileSync('git', ['-C', packageRoot, 'rev-parse', '--short', 'HEAD'], {
|
|
55
|
+
timeout: 5000,
|
|
56
|
+
encoding: 'utf8',
|
|
57
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
58
|
+
}).trim();
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function computeBuildIdentity(packageRoot) {
|
|
65
|
+
const buildInfo = loadBuildInfoJson(packageRoot);
|
|
66
|
+
const version = (buildInfo && typeof buildInfo.version === 'string') ? buildInfo.version : resolvePackageVersion(packageRoot);
|
|
67
|
+
const codeSha = buildInfo
|
|
68
|
+
? ((typeof buildInfo.gitShortSha === 'string') ? buildInfo.gitShortSha : null)
|
|
69
|
+
: resolveGitCodeSha(packageRoot);
|
|
70
|
+
const builtAt = (buildInfo && typeof buildInfo.builtAt === 'string') ? buildInfo.builtAt : null;
|
|
71
|
+
return {
|
|
72
|
+
version,
|
|
73
|
+
codeSha,
|
|
74
|
+
builtAt,
|
|
75
|
+
appPath: packageRoot,
|
|
76
|
+
installChannel: resolveInstallChannel({ appPath: packageRoot, devFlag: !!process.env.SM_DEV }),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Keyed by resolved root: a real process only ever resolves ONE root (this
|
|
81
|
+
// module's own package root), so in production/dev this behaves as a plain
|
|
82
|
+
// singleton memo — the Map only grows past size 1 under test, where each
|
|
83
|
+
// case injects a distinct fixture packageRoot.
|
|
84
|
+
const identityCache = new Map();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {object} [opts]
|
|
88
|
+
* @param {string|null} [opts.bootedAt] - the caller's OWN already-captured
|
|
89
|
+
* process-boot timestamp (e.g. scheduler.cjs's SCHEDULER_BOOTED_AT). Never
|
|
90
|
+
* re-captured here — a memoized resolver that stamped its own "now" would
|
|
91
|
+
* drift from what SCHEDULER_BOOTED_AT was introduced to expose (PRD 812:
|
|
92
|
+
* process staleness relative to on-disk source).
|
|
93
|
+
* @param {string} [opts.packageRoot] - test-only override; production/dev
|
|
94
|
+
* callers never pass this, so it always resolves to this module's own
|
|
95
|
+
* package root.
|
|
96
|
+
*/
|
|
97
|
+
function resolveBuildIdentity(opts = {}) {
|
|
98
|
+
const packageRoot = opts.packageRoot || PACKAGE_ROOT;
|
|
99
|
+
if (!identityCache.has(packageRoot)) identityCache.set(packageRoot, computeBuildIdentity(packageRoot));
|
|
100
|
+
return { ...identityCache.get(packageRoot), bootedAt: opts.bootedAt ?? null };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* readInstalledBuildInfo() → the build-info.json currently ON DISK at this
|
|
105
|
+
* package root (fresh read, never the boot-time memo), or null. Compared with
|
|
106
|
+
* the running identity's codeSha to detect that an install/update already
|
|
107
|
+
* replaced the files under a live process.
|
|
108
|
+
*/
|
|
109
|
+
function readInstalledBuildInfo(packageRoot = PACKAGE_ROOT) {
|
|
110
|
+
return loadBuildInfoJson(packageRoot);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { resolveBuildIdentity, readInstalledBuildInfo };
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
const { spawn } = require('node:child_process');
|
|
19
|
-
const { resolveClaudeBin } = require('./claudeBin.cjs');
|
|
19
|
+
const { resolveClaudeBin, claudeSpawnTarget } = require('./claudeBin.cjs');
|
|
20
20
|
const { cleanChildEnv, pathWithUserBins } = require('./cleanEnv.cjs');
|
|
21
21
|
|
|
22
22
|
const CLASSIFY_TIMEOUT_MS = 30_000;
|
|
@@ -78,7 +78,8 @@ function classifyPromptTicket(text) {
|
|
|
78
78
|
|
|
79
79
|
try {
|
|
80
80
|
// stdin closed — `claude -p` otherwise blocks waiting for piped stdin.
|
|
81
|
-
|
|
81
|
+
const target = claudeSpawnTarget('aux', 'classify', claudeBin);
|
|
82
|
+
child = spawnImpl(target.command, args, { env: childEnv, stdio: ['ignore', 'pipe', 'pipe'], ...(target.argv0 ? { argv0: target.argv0 } : {}) });
|
|
82
83
|
} catch {
|
|
83
84
|
finish('inline');
|
|
84
85
|
return;
|
|
@@ -39,6 +39,40 @@ function resolveClaudeBin() {
|
|
|
39
39
|
return cached;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// ---------- process naming ----------
|
|
43
|
+
//
|
|
44
|
+
// Every `claude` spawn goes through claudeSpawnTarget so GNOME System Monitor
|
|
45
|
+
// shows a distinct comm per role (sm-claude-job / -chat / -aux) and a labelled
|
|
46
|
+
// argv0. See lib/procName.cjs for the mechanics. Invariants:
|
|
47
|
+
// - SM_CLAUDE_BIN set → aliasing SKIPPED, raw value used verbatim (tests point
|
|
48
|
+
// it at self-locating stubs; it must stay ONE binary, no path splitting).
|
|
49
|
+
// - aliasBinFor fails open (returns realBin) → no argv0 is set either, so the
|
|
50
|
+
// fallback is byte-identical to the pre-naming spawn.
|
|
51
|
+
// - Every alias and argv0 label contains the word `claude`, which the four
|
|
52
|
+
// /\bclaude\b/ cmdline gates (killOrphanClaudePid, claudePidAlive,
|
|
53
|
+
// findLiveProcessForJob x2) depend on.
|
|
54
|
+
const ROLE_ALIASES = { job: 'sm-claude-job', chat: 'sm-claude-chat', aux: 'sm-claude-aux' };
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {'job'|'chat'|'aux'} role
|
|
58
|
+
* @param {string} [detail] argv0 label detail (job slug / epic session id)
|
|
59
|
+
* @param {string} [realBin] defaults to resolveClaudeBin()
|
|
60
|
+
* @returns {{ command: string, argv0?: string }} spread `argv0` into spawn options
|
|
61
|
+
*/
|
|
62
|
+
function claudeSpawnTarget(role, detail, realBin = resolveClaudeBin()) {
|
|
63
|
+
if (process.env.SM_CLAUDE_BIN) return { command: realBin };
|
|
64
|
+
const alias = ROLE_ALIASES[role];
|
|
65
|
+
if (!alias) return { command: realBin };
|
|
66
|
+
try {
|
|
67
|
+
const { aliasBinFor, smArgv0 } = require('./procName.cjs');
|
|
68
|
+
const command = aliasBinFor(realBin, alias);
|
|
69
|
+
if (command === realBin) return { command: realBin };
|
|
70
|
+
return { command, argv0: smArgv0(role, detail) };
|
|
71
|
+
} catch {
|
|
72
|
+
return { command: realBin };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
42
76
|
// ---------- version probe ----------
|
|
43
77
|
//
|
|
44
78
|
// `claude --version`, cached. The scheduler's launch circuit breaker
|
|
@@ -75,7 +109,8 @@ function probeClaudeVersion({ now = Date.now(), execFileImpl } = {}) {
|
|
|
75
109
|
// it, and the binary must never be handed the caller's working tree —
|
|
76
110
|
// a stub `claude` (tests point SM_CLAUDE_BIN at one) that writes
|
|
77
111
|
// markers or commits into process.cwd() once did so in the real repo.
|
|
78
|
-
|
|
112
|
+
const target = claudeSpawnTarget('aux', 'version', bin);
|
|
113
|
+
execFile(target.command, ['--version'], { timeout: VERSION_PROBE_TIMEOUT_MS, windowsHide: true, cwd: os.tmpdir(), ...(target.argv0 ? { argv0: target.argv0 } : {}) }, (err, stdout) => {
|
|
79
114
|
if (err) return done(null);
|
|
80
115
|
const m = /(\d+\.\d+\.\d+[^\s]*)/.exec(String(stdout || ''));
|
|
81
116
|
done(m ? m[1] : (String(stdout || '').trim() || null));
|
|
@@ -92,4 +127,4 @@ function resetClaudeVersionCache() {
|
|
|
92
127
|
versionCache = { at: 0, value: null, inflight: null };
|
|
93
128
|
}
|
|
94
129
|
|
|
95
|
-
module.exports = { resolveClaudeBin, probeClaudeVersion, resetClaudeVersionCache };
|
|
130
|
+
module.exports = { resolveClaudeBin, claudeSpawnTarget, probeClaudeVersion, resetClaudeVersionCache };
|
|
@@ -35,8 +35,34 @@ function pathWithUserBins() {
|
|
|
35
35
|
return base ? `${base}:${userBinDirs().join(':')}` : userBinDirs().join(':');
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* SM_PROC_ROLE attributes a child (and, by inheritance, every MCP server the
|
|
40
|
+
* `claude` child spawns — we cannot rename those) via /proc/<pid>/environ.
|
|
41
|
+
* Read by scripts/sm-ps.cjs. NOT SM_PROC_ROOT (procIdentity.cjs's /proc-root
|
|
42
|
+
* test override) — unrelated variable.
|
|
43
|
+
*/
|
|
44
|
+
const PROC_ROLE_ENV = 'SM_PROC_ROLE';
|
|
45
|
+
|
|
46
|
+
/** Explicit `SM_PROC_ROLE` in `extra` wins; else inferred from the attribution
|
|
47
|
+
* vars each spawn site already sets (job slug → job, tab id → shell, chat
|
|
48
|
+
* session → chat); else `aux`. Inference keeps role stamping in ONE place. */
|
|
49
|
+
function inferProcRole(extra) {
|
|
50
|
+
if (extra[PROC_ROLE_ENV]) return String(extra[PROC_ROLE_ENV]);
|
|
51
|
+
if (extra.SM_SCHEDULER_JOB_SLUG) return 'job';
|
|
52
|
+
if (extra.SESSION_MANAGER_TAB_ID) return 'shell';
|
|
53
|
+
if (extra.SM_CHAT_SESSION_ID) return 'chat';
|
|
54
|
+
return 'aux';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Stamp SM_PROC_ROLE onto a raw env object (for sites that deliberately do not
|
|
58
|
+
* use cleanChildEnv's secret stripping, e.g. runClaudeP/docEdit). Returns a copy. */
|
|
59
|
+
function withProcRole(env, role) {
|
|
60
|
+
return { ...env, [PROC_ROLE_ENV]: role };
|
|
61
|
+
}
|
|
62
|
+
|
|
38
63
|
function cleanChildEnv(extra = {}) {
|
|
39
64
|
const env = { ...process.env, ...extra };
|
|
65
|
+
env[PROC_ROLE_ENV] = inferProcRole(extra);
|
|
40
66
|
for (const k of Object.keys(env)) {
|
|
41
67
|
if (
|
|
42
68
|
k === 'CLAUDE_EFFORT' ||
|
|
@@ -52,4 +78,4 @@ function cleanChildEnv(extra = {}) {
|
|
|
52
78
|
return env;
|
|
53
79
|
}
|
|
54
80
|
|
|
55
|
-
module.exports = { cleanChildEnv, userBinDirs, pathWithUserBins };
|
|
81
|
+
module.exports = { cleanChildEnv, withProcRole, inferProcRole, PROC_ROLE_ENV, userBinDirs, pathWithUserBins };
|
|
@@ -6,7 +6,7 @@ const path = require('node:path');
|
|
|
6
6
|
const os = require('node:os');
|
|
7
7
|
const { spawn, execFileSync } = require('node:child_process');
|
|
8
8
|
const { cleanChildEnv, pathWithUserBins } = require('./cleanEnv.cjs');
|
|
9
|
-
const { resolveClaudeBin } = require('./claudeBin.cjs');
|
|
9
|
+
const { resolveClaudeBin, claudeSpawnTarget } = require('./claudeBin.cjs');
|
|
10
10
|
|
|
11
11
|
const CREDS_PATH = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
12
12
|
|
|
@@ -188,7 +188,9 @@ function tryCliFallback() {
|
|
|
188
188
|
try {
|
|
189
189
|
// Resolve the absolute binary + inject Homebrew/user bins so this works
|
|
190
190
|
// even when Electron launched from Finder/Dock with a stripped PATH (mac).
|
|
191
|
-
|
|
191
|
+
const target = claudeSpawnTarget('aux', 'credcheck');
|
|
192
|
+
child = spawn(target.command, ['--version'], {
|
|
193
|
+
...(target.argv0 ? { argv0: target.argv0 } : {}),
|
|
192
194
|
stdio: 'ignore',
|
|
193
195
|
env: cleanChildEnv({ PATH: pathWithUserBins() }),
|
|
194
196
|
});
|