glimmervoid 0.44.2 → 0.45.1-next.45

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 (110) hide show
  1. package/dist/bin/glimmervoid.js +15 -15
  2. package/dist/chunks/{adapters-7AfJ6yb3.js → adapters-BE9BKVW0.js} +24 -24
  3. package/dist/chunks/{adapters-B_ss_kZh.js → adapters-CPaysZkO.js} +1 -1
  4. package/dist/chunks/{agent-api-cli-prUJ5ZGN.js → agent-api-cli-B0l8uLos.js} +1 -1
  5. package/dist/chunks/{agent-api-cli-zwuWZlwk.js → agent-api-cli-Bq9J0_D5.js} +148 -15
  6. package/dist/chunks/{agent-api-core-DQwd2ClI.js → agent-api-core-ClU9-5K3.js} +10 -4
  7. package/dist/chunks/{agent-setup-cli-7hG401Io.js → agent-setup-cli-BA_1AdhQ.js} +2 -2
  8. package/dist/chunks/{contracts-lxJ0sQEl.js → build-channel-qgKwNssc.js} +37 -3
  9. package/dist/chunks/{config-DlKkg6IX.js → config-BqJTnPae.js} +27 -34
  10. package/dist/chunks/{config-path-core-DFt-TemS.js → config-path-core-DUWk0Lui.js} +5 -1
  11. package/dist/chunks/config-store-5xpRLHFo.js +1 -0
  12. package/dist/chunks/{config-store-DIrwF5iQ.js → config-store-BGTg8oKO.js} +77 -78
  13. package/dist/chunks/{git-version-C3lznLS_.js → git-version-BpdkKH2j.js} +1 -1
  14. package/dist/chunks/{grok-Di4aQGre.js → grok-7jK8ZNtf.js} +1 -1
  15. package/dist/chunks/{json-file-DFCTvPjj.js → json-file-BjFK_eSH.js} +2 -7
  16. package/dist/chunks/{main-BCbulgNv.js → main-x9ygUnoL.js} +1226 -1012
  17. package/dist/chunks/{pair-cli-B3VRcrHm.js → pair-cli-Cph4TZ2R.js} +2 -2
  18. package/dist/chunks/{remote-config-C91HwjWq.js → remote-config-CmP36r_s.js} +2 -2
  19. package/dist/chunks/{rtk-resolver-BDHi6RMv.js → rtk-resolver-C_IReILP.js} +1 -1
  20. package/dist/chunks/rtk-resolver-DgnNb1or.js +1 -0
  21. package/dist/chunks/{session-CUc6d7aS.js → session-BwlLY_mv.js} +36 -14
  22. package/dist/chunks/{session-routes--hrsvv7a.js → session-routes-CviNvxcz.js} +9 -1
  23. package/dist/chunks/{settings-cli-C9eCc60b.js → settings-cli-Bete5oVk.js} +11 -10
  24. package/dist/chunks/{settings-readout-core-DK1m5TUq.js → settings-readout-core-CS8MPaIE.js} +14 -8
  25. package/dist/chunks/shown-files-core-soMzCQ1D.js +145 -0
  26. package/dist/chunks/{ssh-sign-cli-DCnHgHqo.js → ssh-sign-cli-DXhuqzuy.js} +2 -2
  27. package/dist/client/assets/action-status-BwqYb9eF.js +1 -0
  28. package/dist/client/assets/action-status-BwqYb9eF.js.br +0 -0
  29. package/dist/client/assets/benchmark-panel-BulIdLtq.js +1 -0
  30. package/dist/client/assets/benchmark-panel-BulIdLtq.js.br +0 -0
  31. package/dist/client/assets/card-dom-DkZgkeG9.js +1 -0
  32. package/dist/client/assets/card-dom-DkZgkeG9.js.br +0 -0
  33. package/dist/client/assets/control-ws-CAcwW-r9.js +4 -0
  34. package/dist/client/assets/control-ws-CAcwW-r9.js.br +0 -0
  35. package/dist/client/assets/dialogs-CCP1ipXD.js +79 -0
  36. package/dist/client/assets/dialogs-CCP1ipXD.js.br +0 -0
  37. package/dist/client/assets/dom-helpers-xJduxDcc.js +1 -0
  38. package/dist/client/assets/dom-helpers-xJduxDcc.js.br +0 -0
  39. package/dist/client/assets/hooks-panel-BWDeSHtM.js +1 -0
  40. package/dist/client/assets/hooks-panel-BWDeSHtM.js.br +0 -0
  41. package/dist/client/assets/index-D6m--U7B.js +84 -0
  42. package/dist/client/assets/index-D6m--U7B.js.br +0 -0
  43. package/dist/client/assets/index-DF3BLGk9.css +2 -0
  44. package/dist/client/assets/index-DF3BLGk9.css.br +0 -0
  45. package/dist/client/assets/issues-panel-B7Y1Lpyx.js +1 -0
  46. package/dist/client/assets/issues-panel-B7Y1Lpyx.js.br +0 -0
  47. package/dist/client/assets/modal-mWl2H7A_.js +1 -0
  48. package/dist/client/assets/modal-mWl2H7A_.js.br +0 -0
  49. package/dist/client/assets/plan-face-DpzZfVeG.js +3 -0
  50. package/dist/client/assets/plan-face-DpzZfVeG.js.br +0 -0
  51. package/dist/client/assets/{poll-ago-Dj148faJ.js → poll-ago-Bc-8mMFj.js} +1 -1
  52. package/dist/client/assets/poll-ago-Bc-8mMFj.js.br +0 -0
  53. package/dist/client/assets/pr-scope-tabs-DftvVYU8.js +1 -0
  54. package/dist/client/assets/pr-scope-tabs-DftvVYU8.js.br +0 -0
  55. package/dist/client/assets/radar-panel-hxjF7m9Z.js +1 -0
  56. package/dist/client/assets/radar-panel-hxjF7m9Z.js.br +0 -0
  57. package/dist/client/assets/review-queue-panel-8bP8Q7RR.js +10 -0
  58. package/dist/client/assets/review-queue-panel-8bP8Q7RR.js.br +0 -0
  59. package/dist/client/assets/roving-keys-core-BDobx2It.js +1 -0
  60. package/dist/client/assets/settings-panel-B50lNcrt.js +4 -0
  61. package/dist/client/assets/settings-panel-B50lNcrt.js.br +0 -0
  62. package/dist/client/assets/trace-panel-Dy-fgi9c.js +2 -0
  63. package/dist/client/assets/trace-panel-Dy-fgi9c.js.br +0 -0
  64. package/dist/client/assets/ui-prefs-CNf6fId2.js +1 -0
  65. package/dist/client/assets/ui-prefs-CNf6fId2.js.br +0 -0
  66. package/dist/client/assets/usage-panel-D2JuiQ4G.js +1 -0
  67. package/dist/client/assets/usage-panel-D2JuiQ4G.js.br +0 -0
  68. package/dist/client/assets/visions-panel-CI6CRn42.js +1 -0
  69. package/dist/client/assets/visions-panel-CI6CRn42.js.br +0 -0
  70. package/dist/client/index.html +14 -8
  71. package/dist/client/index.html.br +0 -0
  72. package/dist/server/index.js +99 -1
  73. package/dist/session/guardrails-open-launcher.js +28 -16
  74. package/dist/session/visions-relay.js +1 -1
  75. package/package.json +1 -1
  76. package/dist/chunks/config-store-LYSRHNQs.js +0 -1
  77. package/dist/chunks/rtk-resolver-Dr4iJsiS.js +0 -1
  78. package/dist/client/assets/action-status-DXcC95xv.js +0 -1
  79. package/dist/client/assets/action-status-DXcC95xv.js.br +0 -0
  80. package/dist/client/assets/benchmark-panel-Cssmnd3z.js +0 -1
  81. package/dist/client/assets/benchmark-panel-Cssmnd3z.js.br +0 -0
  82. package/dist/client/assets/dom-helpers-CcEFPh-F.js +0 -4
  83. package/dist/client/assets/dom-helpers-CcEFPh-F.js.br +0 -0
  84. package/dist/client/assets/hooks-panel-Cli8EeBB.js +0 -1
  85. package/dist/client/assets/hooks-panel-Cli8EeBB.js.br +0 -0
  86. package/dist/client/assets/index-C3nOrTwM.css +0 -2
  87. package/dist/client/assets/index-C3nOrTwM.css.br +0 -0
  88. package/dist/client/assets/index-C8u3ZjM3.js +0 -173
  89. package/dist/client/assets/index-C8u3ZjM3.js.br +0 -0
  90. package/dist/client/assets/issues-panel-Bev1wadi.js +0 -1
  91. package/dist/client/assets/issues-panel-Bev1wadi.js.br +0 -0
  92. package/dist/client/assets/modal-CvDX5-_2.js +0 -1
  93. package/dist/client/assets/modal-CvDX5-_2.js.br +0 -0
  94. package/dist/client/assets/poll-ago-Dj148faJ.js.br +0 -0
  95. package/dist/client/assets/pr-queue-columns-Jleg5z3W.js +0 -1
  96. package/dist/client/assets/pr-queue-columns-Jleg5z3W.js.br +0 -0
  97. package/dist/client/assets/radar-panel-Dtp4ZzT3.js +0 -1
  98. package/dist/client/assets/radar-panel-Dtp4ZzT3.js.br +0 -0
  99. package/dist/client/assets/review-queue-panel-BQvU_-xl.js +0 -2
  100. package/dist/client/assets/review-queue-panel-BQvU_-xl.js.br +0 -0
  101. package/dist/client/assets/settings-panel-DegKCsoZ.js +0 -3
  102. package/dist/client/assets/settings-panel-DegKCsoZ.js.br +0 -0
  103. package/dist/client/assets/trace-panel-DFqImX92.js +0 -1
  104. package/dist/client/assets/trace-panel-DFqImX92.js.br +0 -0
  105. package/dist/client/assets/ui-prefs-CavQQH-s.js +0 -1
  106. package/dist/client/assets/ui-prefs-CavQQH-s.js.br +0 -0
  107. package/dist/client/assets/usage-panel-CEJqXYIA.js +0 -1
  108. package/dist/client/assets/usage-panel-CEJqXYIA.js.br +0 -0
  109. package/dist/client/assets/visions-panel-Dm3iKlhv.js +0 -1
  110. package/dist/client/assets/visions-panel-Dm3iKlhv.js.br +0 -0
@@ -2,14 +2,14 @@ import { i as sanitizeOneLine, n as requireExecutableSpawnHelper, r as firstLine
2
2
  import { a as isMissingFileError, i as errorText$1, r as errorMessage, t as errorCode } from "./text-DoirglWV.js";
3
3
  import { a as sanitizeWorktreeName, i as safePathSegment, n as equalsIgnoringCaseOnWindows, r as isPathInside, t as caseFoldedPathKey } from "./paths-Bu4eH1Yd.js";
4
4
  import { c as resolvePackageBin, i as clientDir, n as bundled, o as packageRoot, r as cliPath, s as relayPath, t as assetRoot } from "./runtime-paths-ghcH9CeW.js";
5
- import { $ as MyPrMergeWhenReadyRequest, $t as RESTARTABLE_STATES, A as SESSION_NAME_RULE_TEXT, At as TeamReviewStateEntry, Bt as HEX_SHA_RE, C as ConversationQuestion, Ct as ReviewResult, D as SESSION_DOC_BODY_CAP_BYTES, Et as TeamReviewActionRequest, Ft as canApproveAfterComment, G as MyPrCheckContextsResponse, Gt as repoParts, H as MyPr, Ht as GH_SEGMENT, It as decisionAsIfApprovalRequired, J as MyPrLaneEntry, K as MyPrKeepMergeableRequest, L as SshSignRequest, M as SSH_SIGN_BODY_CAP_BYTES, Mt as TeamReviewThreadCommentsResponse, Nt as TeamReviewThreadResult, Ot as TeamReviewCompareFiles, Pt as TeamReviewThreadsRepository, Q as MyPrMergeStateResponse, Qt as MERGEABLE_LIVE_STATES, Rt as hasStandingViewerApproval, St as ReviewFinding, Tt as ReviewVerdict, U as MyPrAutoRebase, Ut as NODE_ID_RE, V as LinkedPr, Vt as shortSha, W as MyPrCheckContext, Wt as REPO_SLUG_RE, X as MyPrMergeRequest, Xt as BADGE_LABELS, Y as MyPrMergeMethod, Zt as KILLABLE_STATES, _ as CONVERSATION_PERMISSION_HOOK_EVENT, _t as PrDetail, at as MyPrsState, bt as ReviewComment, dt as DECIDING_REVIEW_STATES, en as STATES, ft as EarlierReview, g as CONVERSATION_APPROVAL_SUMMARY_MAX_CHARS, gt as PostingPlan, h as COHERENCE_HOOKS_INACTIVE, ht as GithubReviewState, it as MyPrsSnapshot, j as SIGN_URL_ENV, jt as TeamReviewStatus, k as SESSION_NAME_PATTERN, kt as TeamReviewResolveResponse, n as AGENT_URL_ENV, nt as MyPrThreadNode, pt as FindingSeverity, r as ASK_USER_QUESTION_TOOL_NAME, rt as MyPrThreadsResponse, ut as CommitSha, v as CONVERSATION_TOOL_RESULT_HOOK_EVENT, vt as QueuedReview, wt as ReviewThreadId, xt as ReviewDraft, z as TaskTitle } from "./session-CUc6d7aS.js";
5
+ import { n as glimmervoidHomeDir, r as isOperatorHomeUnderTest, t as decideConfigPath } from "./config-path-core-DUWk0Lui.js";
6
+ import { $ as MyPrMergeStateResponse, $t as MERGEABLE_LIVE_STATES, A as SHOWN_FILE_CAP_BYTES, At as TeamReviewResolveResponse, B as TaskTitle, C as ConversationQuestion, Ct as ReviewFinding, Dt as TeamReviewActionRequest, E as SESSION_DOC_BODY_CAP_BYTES, Et as ReviewVerdict, Ft as TeamReviewThreadsRepository, G as MyPrCheckContext, Gt as REPO_SLUG_RE, H as LinkedPr, Ht as shortSha, I as ShownFile, It as canApproveAfterComment, K as MyPrCheckContextsResponse, Kt as repoParts, Lt as decisionAsIfApprovalRequired, M as SSH_SIGN_BODY_CAP_BYTES, Mt as TeamReviewStatus, Nt as TeamReviewThreadCommentsResponse, O as SESSION_NAME_PATTERN, Pt as TeamReviewThreadResult, Qt as KILLABLE_STATES, R as SshSignRequest, St as ReviewDraft, Tt as ReviewThreadId, U as MyPr, Ut as GH_SEGMENT, Vt as HEX_SHA_RE, W as MyPrAutoRebase, Wt as NODE_ID_RE, X as MyPrMergeMethod, Y as MyPrLaneEntry, Z as MyPrMergeRequest, Zt as BADGE_LABELS, _ as CONVERSATION_PERMISSION_HOOK_EVENT, _t as PostedReviewEvent, at as MyPrsSnapshot, dt as CommitSha, en as RESTARTABLE_STATES, et as MyPrMergeWhenReadyRequest, ft as DECIDING_REVIEW_STATES, g as CONVERSATION_APPROVAL_SUMMARY_MAX_CHARS, gt as GithubReviewState, h as COHERENCE_HOOKS_INACTIVE, it as MyPrThreadsResponse, j as SIGN_URL_ENV, jt as TeamReviewStateEntry, k as SESSION_NAME_RULE_TEXT, kt as TeamReviewCompareFiles, mt as FindingSeverity, n as AGENT_URL_ENV, ot as MyPrsState, pt as EarlierReview, q as MyPrKeepMergeableRequest, r as ASK_USER_QUESTION_TOOL_NAME, rt as MyPrThreadNode, tn as STATES, v as CONVERSATION_TOOL_RESULT_HOOK_EVENT, vt as PostingPlan, wt as ReviewResult, xt as ReviewComment, yt as PrDetail, zt as hasStandingViewerApproval } from "./session-BwlLY_mv.js";
6
7
  import { n as execFileAsync, o as spawn, r as execFileSync, t as execFile } from "./child-process-safe-D4hyVIXj.js";
7
- import { A as BenchmarkId, At as parseExitPlanModeHookPayload, B as GithubPrKey, Ct as PLAN_BODY_CAP_BYTES, D as pullRequestKeyFromReference, Dt as PLAN_TOOL_NAME, Et as PLAN_RESULT_HOOK_EVENT, F as CommitComparison, Ft as IssueUpdate, G as WorkflowLabelName, H as GithubPrsFrame, I as MergedPrListing, It as IssuesSearchResponse, K as WorkflowsSettings, L as MinedPrReviewData, Lt as IssuesState, M as BenchmarkRun, Mt as GithubIssuePullRequestsRepository, N as BenchmarkStatus, Nt as GithubIssueResponse, O as BenchmarkActionRequest, Ot as PlanDecision, P as BenchmarkSuite, Pt as IssueKey, Q as KEEP_MERGEABLE_TIMEOUT_MINUTES_RANGE, R as PrCheckoutCaseInput, Rt as IssuesStatus, St as changeMapFactId, Tt as PLAN_HOOK_OUTPUT_MAX_CHARS, U as comparablePrKey, V as GithubPrLightSearchNode, W as WorkflowCommentBody, _t as MAX_RAW_LINE_CHARS, a as ConfigUpdate, b as ClientMessage, bt as CoherenceJournalOutput, g as CHANGES_POLL_INTERVAL_MS, gt as SessionSetup, ht as UpdateJournal, j as BenchmarkJudgement, jt as planTitle, k as BenchmarkCase, kt as PlanRevision, l as configIssueMessage, m as USAGE_VENDOR_KEYS, p as USAGE_COST_MODES, q as WorkflowsState, r as BrowserConfig, s as RUNTIME_CONFIG_SCALAR_KEYS, ut as USAGE_INTEGER_RANGES, v as CLIENT_LOG_TEXT_MAX_CHARS, vt as TraceCheckpoint, wt as PLAN_HOOK_EVENT, xt as ChangeMap, y as CONTROL_FRAME_MAX_BYTES, yt as TraceRecord, z as GithubPrDetailNode, zt as issuePullRequestStateFromGithub } from "./config-DlKkg6IX.js";
8
- import { a as permitsHelpCommand, r as describeSessionLimits } from "./session-routes--hrsvv7a.js";
9
- import { A as seedTaskTitleSources, B as listRepoWorktreePaths, C as extractPromptTaskTitle, D as parseRefinedTaskTitle, E as parseRefinedSessionName, F as resumePromptFor, I as shouldAdoptReportedResumeId, L as claudeProjectsDir, M as RESUME_ID_RE, N as pickAutoResume, O as resolveRefocusTaskTitle, P as resolveResumeTarget, R as encodeProjectDir, S as extractOscTaskTitle, T as isSubstantivePrompt, V as isAutoNameOf, _ as buildTaskTitleRefinementPrompt, a as decideSpawnAllowance, b as decideTaskTitleRefinement, c as describeKeptChildAttention, d as describeUnpushedCloseRefusal, g as buildPersistedTaskTitle, h as projectReviews, i as decideAgentRequest, j as AUTO_CONTINUE_PROMPT, k as resolveTaskTitle, l as describePushLanding, m as projectMyPrs, n as REFUSAL_REASON, o as deriveChildSessionName, p as parseAgentVerb, s as describeChildEndNote, u as describeRoleRefusal, v as chooseRefinedSessionName, w as isSelfWakePrompt, x as extractAiTitle, y as cleanTaskPrompt, z as listRepoConversations } from "./agent-api-core-DQwd2ClI.js";
8
+ import { A as BenchmarkRun, At as parseExitPlanModeHookPayload, B as GithubPrsFrame, Ct as PLAN_BODY_CAP_BYTES, D as BenchmarkCase, Dt as PLAN_TOOL_NAME, E as BenchmarkActionRequest, Et as PLAN_RESULT_HOOK_EVENT, F as MinedPrReviewData, Ft as IssueUpdate, G as WorkflowsState, H as WorkflowCommentBody, I as PrCheckoutCaseInput, It as IssuesSearchResponse, L as GithubPrDetailNode, Lt as IssuesState, M as BenchmarkSuite, Mt as GithubIssuePullRequestsRepository, N as CommitComparison, Nt as GithubIssueResponse, O as BenchmarkId, Ot as PlanDecision, P as MergedPrListing, Pt as IssueKey, R as GithubPrKey, Rt as IssuesStatus, St as changeMapFactId, T as pullRequestKeyFromReference, Tt as PLAN_HOOK_OUTPUT_MAX_CHARS, U as WorkflowLabelName, V as comparablePrKey, W as WorkflowsSettings, X as KEEP_MERGEABLE_TIMEOUT_MINUTES_RANGE, _ as CONTROL_FRAME_MAX_BYTES, _t as MAX_RAW_LINE_CHARS, a as ConfigUpdate, bt as CoherenceJournalOutput, ct as USAGE_INTEGER_RANGES, d as USAGE_COST_MODES, f as USAGE_VENDOR_KEYS, g as CLIENT_LOG_TEXT_MAX_CHARS, gt as SessionSetup, ht as UpdateJournal, j as BenchmarkStatus, jt as planTitle, k as BenchmarkJudgement, kt as PlanRevision, l as configIssueMessage, m as CHANGES_POLL_INTERVAL_MS, r as BrowserConfig, s as RUNTIME_CONFIG_SCALAR_KEYS, v as ClientMessage, vt as TraceCheckpoint, wt as PLAN_HOOK_EVENT, xt as ChangeMap, yt as TraceRecord, z as GithubPrLightSearchNode, zt as issuePullRequestStateFromGithub } from "./config-BqJTnPae.js";
9
+ import { a as permitsHelpCommand, r as describeSessionLimits } from "./session-routes-CviNvxcz.js";
10
+ import { A as seedTaskTitleSources, B as listRepoWorktreePaths, C as extractPromptTaskTitle, D as parseRefinedTaskTitle, E as parseRefinedSessionName, F as resumePromptFor, H as nextSuggestedName, I as shouldAdoptReportedResumeId, L as claudeProjectsDir, M as RESUME_ID_RE, N as pickAutoResume, O as resolveRefocusTaskTitle, P as resolveResumeTarget, R as encodeProjectDir, S as extractOscTaskTitle, T as isSubstantivePrompt, V as isAutoNameOf, _ as buildTaskTitleRefinementPrompt, a as decideSpawnAllowance, b as decideTaskTitleRefinement, c as describeKeptChildAttention, d as describeUnpushedCloseRefusal, g as buildPersistedTaskTitle, h as projectReviews, i as decideAgentRequest, j as AUTO_CONTINUE_PROMPT, k as resolveTaskTitle, l as describePushLanding, m as projectMyPrs, n as REFUSAL_REASON, o as deriveChildSessionName, p as parseAgentVerb, s as describeChildEndNote, u as describeRoleRefusal, v as chooseRefinedSessionName, w as isSelfWakePrompt, x as extractAiTitle, y as cleanTaskPrompt, z as listRepoConversations } from "./agent-api-core-ClU9-5K3.js";
10
11
  import { a as positiveIntOr, c as textOr, i as numberOr, n as isRecord, o as positiveNumberOr, r as nonNegativeIntOr, s as rawTextOr, t as coercedNumberOr } from "./coerce-NSst8oRP.js";
11
- import { n as glimmervoidHomeDir, t as decideConfigPath } from "./config-path-core-DFt-TemS.js";
12
- import { i as parseCommandLine, n as warnWhenGitIsTooOld } from "./git-version-C3lznLS_.js";
12
+ import { i as parseCommandLine, n as warnWhenGitIsTooOld } from "./git-version-BpdkKH2j.js";
13
13
  import { n as checkGuardrailsDependencies, t as allowGuardedSpawn } from "./guardrails-deps-BjPzk1rE.js";
14
14
  import { t as HOOK_URL_ENV } from "./hook-relay-core-DA_bz2KA.js";
15
15
  import { t as HOOK_VIA_GUARDRAILS_PROXY_ENV } from "./guardrails-proxy-core-BI3rdY-m.js";
@@ -19,17 +19,18 @@ import { s as saneYoloFiles } from "./rtk-hook-core-B-MpzPin.js";
19
19
  import { _ as lineStartOffsets, a as projectForUri, b as replacedSpanOfWholeTextChange, c as visionsLaneReloadSignature, d as applyDidOpen, f as createDocStore, g as lineOfOffset, h as getDoc, i as isUriInProjects, l as applyDidChange, m as formatRange, n as applyEditorNotification, o as resolveVisionsScopeProjects, p as detectBlankLineBoundary, r as createEditorState, s as scopePathsOf, t as ACTIVITY_METHOD, u as applyDidClose, v as listDocs, x as uriOfParams, y as offsetOfPosition } from "./ingest-editor-core-B2tycW_m.js";
20
20
  import { n as DEFAULT_MAX_MS, r as nextBackoffMs, t as DEFAULT_BASE_MS } from "./backoff-DjVpfHk7.js";
21
21
  import { n as unrefTimer, t as DEFAULT_TIMER_FNS } from "./timer-deps-u2DAQLok.js";
22
+ import { a as readPageTitle, i as filesAfterRetention, n as decideShownKind, o as shownMimeForFilename, r as describeOverCap, t as SHOWN_SNIFF_BYTES } from "./shown-files-core-soMzCQ1D.js";
22
23
  import { n as shellQuote } from "./hook-command-core-qTNK-xMF.js";
23
- import { C as parseCgroupBytes, S as memoryReserveBytes, T as parseMemAvailableBytes, _ as createSpawnGate, a as jsonStateLoadError, b as cgroupLimitBytes, c as loadedJsonValue, d as writeJsonAtomicSync, f as writeTextAtomic, g as createSerialQueue, h as createMemoryAdmission, i as createJsonStateWriter, m as createKeyedSerialQueue, n as appendJsonLines, o as loadJsonStateFile, p as writeTextAtomicSync, r as createJsonStateStore, s as loadJsonStateFileSync, t as appendJsonLine, u as writeJsonAtomic, v as isQueueAdmissionTimeout, w as parseCgroupV2Path, x as headroomFromReadings, y as cgroupAncestorPaths } from "./json-file-DFCTvPjj.js";
24
- import { B as buildExceptionProperties, F as TELEMETRY_BATCH_URL, G as decideIsDevInstall, H as classifySessionExit, I as TELEMETRY_FLAGS_URL, J as exceptionFingerprint, K as decideRemoteTelemetryState, L as adapterBucket, P as FIRST_RUN_NOTICE, R as buildAiGenerationEvents, U as claudeAccountUuidOf, V as buildPersonProperties, W as codexUserIdOf, X as parsePosthogReleaseId, Y as nodeMajorVersion, Z as resolveProjectToken, a as generateProjectId, at as planWorktreeGc, b as validateSettingsBlock, ct as worktreeIntegrationTips, d as assetForPlatform, dt as isSameDirectoryPath, et as stableConfigKey, f as decideRtkInstall, g as verifyDigest, h as isRtkBinaryName, i as ensureProjectIds, it as planBranchGc, lt as canonicalizePath, m as installTargetPath, n as DEFAULT_CONFIG, nt as DEFAULT_BRANCH_GC_PREFIXES, o as glimmervoidHomeDir$1, ot as sessionIdFromBranch, p as findEscapingArchiveMember, q as decideTelemetryConsent, r as createConfigStore, rt as matchedPrefix, st as usablePrefixes, t as ABORT_CONFIG_SAVE, ut as comparableDirectoryPath, v as mergeSettingsBlock, y as settingsBlockSpecFor, z as buildBrowserExceptionProperties } from "./config-store-DIrwF5iQ.js";
25
- import { a as parseDataClientMessage, c as PendingCrashReport, d as TelemetryState, i as parseDataAttach, l as TELEMETRY_EVENT_SCHEMAS, n as HookEnvelope, o as DashboardTelemetryReport, r as DEEP_HISTORY_SCROLLBACK_LINES, t as HTTP_URL_RE } from "./contracts-lxJ0sQEl.js";
26
- import { A as dedupeCodexFiles, B as shouldPersistWarehouse, C as shortSha$1, D as codexSessionIdFromPath, E as codexRootCandidates, F as passOutcome, H as claudeCode, I as projectDirCandidates, L as resolveProjectsDirs, M as grokHomes, N as grokRootCandidates, O as continuationDelayMs, P as isUsageFile, R as scopedClaudeRoots, S as parseResolvedSha, T as codexHomes, V as splitLines$1, _ as isCheckFresh, a as describeAgentResolvability, b as parseRegistryDeprecation, c as isKnownAgentId, d as resolveAdapter, f as setCustomAgents, g as decideUpdateStatus, h as decideInstallFlavor, i as customAgentFingerprint, j as expandTilde, k as decideFileRead, l as listAgentIds, m as readHostCodexVersion, n as cachedAgentResolvability, o as getAdapter, p as codex, r as commandFor, s as hookProfileOf, t as DEFAULT_AGENT_ID, u as resetCommandCache, v as normalizeSha, w as codexFallbackRoots, x as parseRegistryLatest, y as normalizeUpdateChannel, z as shouldEvaluateDespiteIoFailures } from "./adapters-7AfJ6yb3.js";
24
+ import { C as parseCgroupBytes, S as memoryReserveBytes, T as parseMemAvailableBytes, _ as createSpawnGate, a as jsonStateLoadError, b as cgroupLimitBytes, c as loadedJsonValue, d as writeJsonAtomicSync, f as writeTextAtomic, g as createSerialQueue, h as createMemoryAdmission, i as createJsonStateWriter, m as createKeyedSerialQueue, n as appendJsonLines, o as loadJsonStateFile, p as writeTextAtomicSync, r as createJsonStateStore, s as loadJsonStateFileSync, t as appendJsonLine, u as writeJsonAtomic, v as isQueueAdmissionTimeout, w as parseCgroupV2Path, x as headroomFromReadings, y as cgroupAncestorPaths } from "./json-file-BjFK_eSH.js";
25
+ import { $ as resolveProjectToken, B as buildAiGenerationEvents, G as claudeAccountUuidOf, H as buildExceptionProperties, I as FIRST_RUN_NOTICE, J as decideRemoteTelemetryState, K as codexUserIdOf, L as TELEMETRY_BATCH_URL, Q as parsePosthogReleaseId, R as TELEMETRY_FLAGS_URL, S as validateSettingsBlock, U as buildPersonProperties, V as buildBrowserExceptionProperties, W as classifySessionExit, X as exceptionFingerprint, Y as decideTelemetryConsent, Z as nodeMajorVersion, _ as isRtkBinaryName, a as ensureProjectIds, at as matchedPrefix, b as mergeSettingsBlock, ct as sessionIdFromBranch, dt as canonicalizePath, ft as comparableDirectoryPath, g as installTargetPath, h as findEscapingArchiveMember, i as createConfigStore, it as DEFAULT_BRANCH_GC_PREFIXES, lt as usablePrefixes, m as decideRtkInstall, n as DEFAULT_CONFIG, nt as stableConfigKey, o as generateProjectId, ot as planBranchGc, p as assetForPlatform, pt as isSameDirectoryPath, q as decideIsDevInstall, s as glimmervoidHomeDir$1, st as planWorktreeGc, t as ABORT_CONFIG_SAVE, ut as worktreeIntegrationTips, v as verifyDigest, x as settingsBlockSpecFor, z as adapterBucket } from "./config-store-BGTg8oKO.js";
26
+ import { a as DEEP_HISTORY_SCROLLBACK_LINES, c as DashboardTelemetryReport, d as TELEMETRY_EVENT_SCHEMAS, i as HookEnvelope, o as parseDataAttach, p as TelemetryState, r as HTTP_URL_RE, s as parseDataClientMessage, u as PendingCrashReport } from "./build-channel-qgKwNssc.js";
27
+ import { A as decideFileRead, B as shouldEvaluateDespiteIoFailures, C as pickNewestPublished, D as codexRootCandidates, E as codexHomes, F as isUsageFile, H as splitLines$1, I as passOutcome, L as projectDirCandidates, M as expandTilde, N as grokHomes, O as codexSessionIdFromPath, P as grokRootCandidates, R as resolveProjectsDirs, S as parseResolvedSha, T as codexFallbackRoots, U as claudeCode, V as shouldPersistWarehouse, _ as isCheckFresh, a as describeAgentResolvability, b as parseRegistryDeprecation, c as isKnownAgentId, d as resolveAdapter, f as setCustomAgents, g as decideUpdateStatus, h as decideInstallFlavor, i as customAgentFingerprint, j as dedupeCodexFiles, k as continuationDelayMs, l as listAgentIds, m as readHostCodexVersion, n as cachedAgentResolvability, o as getAdapter, p as codex, r as commandFor, s as hookProfileOf, t as DEFAULT_AGENT_ID, u as resetCommandCache, v as normalizeSha, w as shortSha$1, x as parseRegistryLatest, y as normalizeUpdateChannel, z as scopedClaudeRoots } from "./adapters-BE9BKVW0.js";
27
28
  import { t as HOOK_TOOLS } from "./hook-tools-Dl7-r2Eb.js";
28
29
  import { a as formatSshPublicKey, c as orderAgentSockets, i as commitSigningShimText, l as parseSshPublicKey, n as NO_AGENT_HOLDS_KEY, o as isForwardedAgentDirName, r as agentListsKey, s as isSignableGitObject, t as COMMIT_SIGNING_SHIM_NAME } from "./ssh-signing-core-CRhVVWNW.js";
29
- import { a as configSiblingPath, c as defaultPairingsPath, d as decideDeviceAuth, f as deviceNameFromUserAgent, l as defaultSeenPath, n as normalizeRemoteConfig, o as createPairingsStore, p as hashSecret, r as validateRemoteConfig, s as createSeenStore, t as decideBindHost, u as DEFAULT_DEVICE_MAX_AGE_MS } from "./remote-config-C91HwjWq.js";
30
- import { n as configFileViewOf, u as readSettingsForAgent } from "./settings-readout-core-DK1m5TUq.js";
30
+ import { a as configSiblingPath, c as defaultPairingsPath, d as decideDeviceAuth, f as deviceNameFromUserAgent, l as defaultSeenPath, n as normalizeRemoteConfig, o as createPairingsStore, p as hashSecret, r as validateRemoteConfig, s as createSeenStore, t as decideBindHost, u as DEFAULT_DEVICE_MAX_AGE_MS } from "./remote-config-CmP36r_s.js";
31
+ import { n as configFileViewOf, u as readSettingsForAgent } from "./settings-readout-core-CS8MPaIE.js";
31
32
  import { _ as escapeMarkup, g as createLaneLog, n as createVisionsSetup } from "./visions-setup-VMMyqOpl.js";
32
- import { t as getRtkPath } from "./rtk-resolver-BDHi6RMv.js";
33
+ import { t as getRtkPath } from "./rtk-resolver-C_IReILP.js";
33
34
  import { createRequire } from "node:module";
34
35
  import fs, { closeSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, unlinkSync } from "node:fs";
35
36
  import path, { join, resolve } from "node:path";
@@ -38,9 +39,9 @@ import { z } from "zod";
38
39
  import crypto, { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
39
40
  import WebSocket$1, { WebSocket, WebSocketServer } from "ws";
40
41
  import http from "node:http";
42
+ import { pipeline } from "node:stream";
41
43
  import net, { isIPv6 } from "node:net";
42
44
  import fs$1, { access, chmod, copyFile, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
43
- import { pipeline } from "node:stream";
44
45
  import express from "express";
45
46
  import { EventEmitter } from "node:events";
46
47
  import pty from "node-pty";
@@ -49,6 +50,95 @@ import { AsyncLocalStorage } from "node:async_hooks";
49
50
  import https from "node:https";
50
51
  import { StringDecoder } from "node:string_decoder";
51
52
  import { monitorEventLoopDelay } from "node:perf_hooks";
53
+ //#region server/removal-journal.ts
54
+ function removalsJournalPath(glimmervoidHome) {
55
+ return path.join(glimmervoidHome, "logs", "removals.jsonl");
56
+ }
57
+ async function appendRemovalRecord(journalPath, record, log = console) {
58
+ return appendJsonLine(journalPath, record, { mkdir: true }).then(() => true, (error) => {
59
+ log.warn(`[removals] kept ${record.path ?? record.branch} because its removal record could not be written: ${errorMessage(error)}`);
60
+ return false;
61
+ });
62
+ }
63
+ function appendRemovalRecordSync(journalPath, record, log = console) {
64
+ try {
65
+ fs.mkdirSync(path.dirname(journalPath), { recursive: true });
66
+ fs.appendFileSync(journalPath, `${JSON.stringify(record)}\n`, "utf8");
67
+ return true;
68
+ } catch (error) {
69
+ log.warn(`[removals] kept ${record.path ?? record.branch} because its removal record could not be written: ${errorMessage(error)}`);
70
+ return false;
71
+ }
72
+ }
73
+ async function recordSweptPath({ cause, kind, path: sweptPath, checksPassed, sessionId = null, journalPath = removalsJournalPath(glimmervoidHomeDir$1()), log = console }) {
74
+ return appendRemovalRecord(journalPath, {
75
+ ts: Date.now(),
76
+ cause,
77
+ kind,
78
+ path: sweptPath,
79
+ branch: null,
80
+ projectPath: null,
81
+ sessionId,
82
+ checksPassed,
83
+ pid: process.pid
84
+ }, log);
85
+ }
86
+ //#endregion
87
+ //#region server/core/removal-guard-core.ts
88
+ var SESSION_WORK_RECENCY_FLOOR_MS = 864e5;
89
+ var AUTOMATIC_REMOVAL_CAUSES = /* @__PURE__ */ new Set([
90
+ "branch-gc",
91
+ "boot-reconcile",
92
+ "owned-paths"
93
+ ]);
94
+ var SESSION_OWNER_PREFIX$1 = "session:";
95
+ function sessionIdFromOwner(owner) {
96
+ if (owner === null || !owner.startsWith(SESSION_OWNER_PREFIX$1)) return null;
97
+ return owner.slice(8);
98
+ }
99
+ function isRecencyProbeNeeded(facts) {
100
+ return decideKeepByOwnership(facts) === null;
101
+ }
102
+ function decideKeepByOwnership(facts) {
103
+ if (!AUTOMATIC_REMOVAL_CAUSES.has(facts.cause)) return {
104
+ keep: false,
105
+ checksPassed: ["explicit-cause"]
106
+ };
107
+ if (facts.sessionId === null && facts.ledgerOwnerSessionId === null) return {
108
+ keep: false,
109
+ checksPassed: ["not-session-work"]
110
+ };
111
+ if (facts.sessionId !== null && facts.protectedSessionIds.has(facts.sessionId)) return {
112
+ keep: true,
113
+ reason: "protected-session"
114
+ };
115
+ if (facts.ledgerOwnerSessionId !== null && facts.protectedSessionIds.has(facts.ledgerOwnerSessionId)) return {
116
+ keep: true,
117
+ reason: "ledger-owned"
118
+ };
119
+ return null;
120
+ }
121
+ function decideRemovalKeep(facts) {
122
+ const byOwnership = decideKeepByOwnership(facts);
123
+ if (byOwnership !== null) return byOwnership;
124
+ if (facts.lastTouchedMs === null) return {
125
+ keep: true,
126
+ reason: "touch-probe-failed"
127
+ };
128
+ if (facts.nowMs - facts.lastTouchedMs < SESSION_WORK_RECENCY_FLOOR_MS) return {
129
+ keep: true,
130
+ reason: "recently-touched"
131
+ };
132
+ return {
133
+ keep: false,
134
+ checksPassed: [
135
+ "not-protected",
136
+ "not-ledger-owned",
137
+ "untouched-24h"
138
+ ]
139
+ };
140
+ }
141
+ //#endregion
52
142
  //#region shared/contracts/owned-paths.ts
53
143
  var OwnedPathOwner = z.string().regex(/^(session|lane):[A-Za-z0-9._:#-]+$/);
54
144
  var OwnedPathEntry = z.object({
@@ -82,9 +172,6 @@ function holdsKeptPath(folderPath, keptPaths) {
82
172
  }
83
173
  //#endregion
84
174
  //#region server/owned-paths-ledger.ts
85
- function ownedPathsLedgerPath(glimmervoidHome = glimmervoidHomeDir$1()) {
86
- return path.join(glimmervoidHome, "owned-paths.json");
87
- }
88
175
  function ledgerKey(ownedPath) {
89
176
  return caseFoldedPathKey(path.resolve(ownedPath));
90
177
  }
@@ -140,6 +227,10 @@ function createOwnedPathsLedger({ filePath, log = console }) {
140
227
  if (!isLoaded || !entriesByPath.delete(ledgerKey(await canonicalOwnedPath(ownedPath)))) return;
141
228
  await persist();
142
229
  }
230
+ async function ownerOf(ownedPath) {
231
+ await store.load();
232
+ return entriesByPath.get(ledgerKey(await canonicalOwnedPath(ownedPath)))?.owner ?? null;
233
+ }
143
234
  async function loadEarlierRunEntries() {
144
235
  await store.load();
145
236
  if (!isLoaded) return { ok: false };
@@ -152,6 +243,7 @@ function createOwnedPathsLedger({ filePath, log = console }) {
152
243
  loadEarlierRunEntries,
153
244
  record,
154
245
  forget,
246
+ ownerOf,
155
247
  idle: () => store.idle()
156
248
  };
157
249
  }
@@ -173,14 +265,28 @@ async function removeOrphanedWorktree(entry, git, shareList) {
173
265
  const removed = await git.removeWorkspaceMember({
174
266
  projectPath: entry.projectPath,
175
267
  cwd: entry.path,
176
- shareList
268
+ branch: entry.branch,
269
+ shareList,
270
+ cause: "owned-paths"
177
271
  });
272
+ if (removed.removalKept) return `the removal guard kept it: ${removed.removalKept}`;
178
273
  if (!removed.ok || await pathExists$1(entry.path)) return `git could not remove it: ${removed.err || "the folder is still there"}`;
179
274
  return null;
180
275
  }
181
- async function removeOrphanedFolder(entry, ledger, keptPaths) {
276
+ async function removeOrphanedFolder(entry, ledger, keptPaths, journalPath, log) {
182
277
  if (holdsKeptPath(entry.path, keptPaths)) return "it holds a checkout that was kept";
183
278
  if (await holdsCheckout(entry.path)) return "it still holds a checkout";
279
+ if (!await appendRemovalRecord(journalPath, {
280
+ ts: Date.now(),
281
+ cause: "owned-paths",
282
+ kind: "folder",
283
+ path: entry.path,
284
+ branch: null,
285
+ projectPath: entry.projectPath,
286
+ sessionId: sessionIdFromOwner(entry.owner),
287
+ checksPassed: ["session-not-configured", "holds-no-checkout"],
288
+ pid: process.pid
289
+ }, log)) return "its removal record could not be written";
184
290
  const removal = await fs$1.rm(entry.path, {
185
291
  recursive: true,
186
292
  force: true
@@ -189,7 +295,7 @@ async function removeOrphanedFolder(entry, ledger, keptPaths) {
189
295
  await ledger.forget(entry.path);
190
296
  return null;
191
297
  }
192
- async function reconcileOwnedPaths({ ledger, configuredSessionIds, git, shareList, log = console }) {
298
+ async function reconcileOwnedPaths({ ledger, configuredSessionIds, git, shareList, journalPath = removalsJournalPath(glimmervoidHomeDir$1()), log = console }) {
193
299
  const loaded = await ledger.loadEarlierRunEntries();
194
300
  if (!loaded.ok) {
195
301
  log.warn("[owned-paths] deleted nothing at startup because the owned paths ledger could not be read");
@@ -201,7 +307,7 @@ async function reconcileOwnedPaths({ ledger, configuredSessionIds, git, shareLis
201
307
  await ledger.forget(entry.path);
202
308
  continue;
203
309
  }
204
- const keepReason = entry.kind === "folder" ? await removeOrphanedFolder(entry, ledger, keptPaths) : await removeOrphanedWorktree(entry, git, shareList);
310
+ const keepReason = entry.kind === "folder" ? await removeOrphanedFolder(entry, ledger, keptPaths, journalPath, log) : await removeOrphanedWorktree(entry, git, shareList);
205
311
  if (!keepReason) {
206
312
  log.log(`[owned-paths] deleted ${entry.path}, left by ${entry.owner}`);
207
313
  continue;
@@ -462,13 +568,22 @@ async function listScratchFolderNames(scratchRoot) {
462
568
  async function sweepOrphanedHostScratch() {
463
569
  const scratchRoot = hostScratchRoot();
464
570
  const orphanedFolderNames = decideOrphanedScratchFolders(await listScratchFolderNames(scratchRoot), HOST_SCRATCH_PREFIXES, folderNamesCreatedByThisProcess);
465
- for (const folderName of orphanedFolderNames) try {
466
- await rm(path.join(scratchRoot, folderName), {
467
- recursive: true,
468
- force: true
469
- });
470
- } catch (error) {
471
- console.warn(`Could not remove leftover scratch folder ${folderName}: ${error instanceof Error ? error.message : String(error)}`);
571
+ for (const folderName of orphanedFolderNames) {
572
+ const folderPath = path.join(scratchRoot, folderName);
573
+ if (!await recordSweptPath({
574
+ cause: "host-scratch",
575
+ kind: "folder",
576
+ path: folderPath,
577
+ checksPassed: ["not-made-by-this-process"]
578
+ })) continue;
579
+ try {
580
+ await rm(folderPath, {
581
+ recursive: true,
582
+ force: true
583
+ });
584
+ } catch (error) {
585
+ console.warn(`Could not remove leftover scratch folder ${folderName}: ${error instanceof Error ? error.message : String(error)}`);
586
+ }
472
587
  }
473
588
  }
474
589
  //#endregion
@@ -1773,53 +1888,98 @@ async function sweepSessionUploads(dir, justWritten) {
1773
1888
  await fs$1.unlink(path.join(dir, name));
1774
1889
  } catch {}
1775
1890
  }
1891
+ function streamRequestToFile({ req, savedPath, capBytes }) {
1892
+ return new Promise((resolve) => {
1893
+ const writeStream = fs.createWriteStream(savedPath, { mode: 384 });
1894
+ let bytesReceived = 0;
1895
+ let settled = false;
1896
+ const settle = (outcome) => {
1897
+ if (settled) return;
1898
+ settled = true;
1899
+ resolve(outcome);
1900
+ };
1901
+ let discardWanted = false;
1902
+ let streamClosed = false;
1903
+ const unlinkPartial = () => {
1904
+ fs$1.unlink(savedPath).catch(() => {});
1905
+ };
1906
+ const discardPartial = () => {
1907
+ discardWanted = true;
1908
+ if (streamClosed) unlinkPartial();
1909
+ };
1910
+ writeStream.on("close", () => {
1911
+ streamClosed = true;
1912
+ if (discardWanted) unlinkPartial();
1913
+ });
1914
+ writeStream.on("error", () => {
1915
+ settle({
1916
+ ok: false,
1917
+ reason: "write-failed"
1918
+ });
1919
+ discardPartial();
1920
+ });
1921
+ req.on("data", (chunk) => {
1922
+ bytesReceived += chunk.length;
1923
+ if (!exceedsUploadCap(bytesReceived, capBytes)) return;
1924
+ settle({
1925
+ ok: false,
1926
+ reason: "over-cap"
1927
+ });
1928
+ req.destroy();
1929
+ writeStream.destroy();
1930
+ discardPartial();
1931
+ });
1932
+ pipeline(req, writeStream, (error) => {
1933
+ if (error) {
1934
+ settle({
1935
+ ok: false,
1936
+ reason: "aborted"
1937
+ });
1938
+ discardPartial();
1939
+ return;
1940
+ }
1941
+ if (settled) {
1942
+ discardPartial();
1943
+ return;
1944
+ }
1945
+ settle({
1946
+ ok: true,
1947
+ sizeBytes: bytesReceived
1948
+ });
1949
+ });
1950
+ });
1951
+ }
1952
+ var UPLOAD_FAILURE_REPLIES = {
1953
+ "over-cap": {
1954
+ status: 413,
1955
+ error: "file is too large"
1956
+ },
1957
+ "write-failed": {
1958
+ status: 500,
1959
+ error: "could not store the upload"
1960
+ },
1961
+ aborted: {
1962
+ status: 400,
1963
+ error: "upload failed"
1964
+ }
1965
+ };
1776
1966
  function receiveUpload({ req, res, sess, dir, filename, savedPath }) {
1777
- const writeStream = fs.createWriteStream(savedPath);
1778
- let bytesReceived = 0;
1779
- let settled = false;
1780
1967
  const answer = (status, body) => {
1781
- if (settled) return;
1782
- settled = true;
1783
1968
  if (!res.headersSent) res.status(status).json(body);
1784
1969
  };
1785
- let discardWanted = false;
1786
- let streamClosed = false;
1787
- const unlinkPartial = () => {
1788
- fs$1.unlink(savedPath).catch(() => {});
1789
- };
1790
- const discardPartial = () => {
1791
- discardWanted = true;
1792
- if (streamClosed) unlinkPartial();
1793
- };
1794
- writeStream.on("close", () => {
1795
- streamClosed = true;
1796
- if (discardWanted) unlinkPartial();
1797
- });
1798
- writeStream.on("error", () => {
1799
- answer(500, { error: "could not store the upload" });
1800
- discardPartial();
1801
- });
1802
- req.on("data", (chunk) => {
1803
- bytesReceived += chunk.length;
1804
- if (!exceedsUploadCap(bytesReceived)) return;
1805
- answer(413, { error: "file is too large" });
1806
- req.destroy();
1807
- writeStream.destroy();
1808
- discardPartial();
1809
- });
1810
- pipeline(req, writeStream, (error) => {
1811
- if (error) {
1812
- answer(400, { error: "upload failed" });
1813
- discardPartial();
1814
- return;
1815
- }
1816
- if (settled) {
1817
- discardPartial();
1970
+ streamRequestToFile({
1971
+ req,
1972
+ savedPath,
1973
+ capBytes: MAX_UPLOAD_BYTES
1974
+ }).then((outcome) => {
1975
+ if (!outcome.ok) {
1976
+ const failure = UPLOAD_FAILURE_REPLIES[outcome.reason];
1977
+ answer(failure.status, { error: failure.error });
1818
1978
  return;
1819
1979
  }
1820
1980
  if (!sess.hasLivePty) {
1821
1981
  answer(409, { error: "session has no live terminal" });
1822
- discardPartial();
1982
+ fs$1.unlink(savedPath).catch(() => {});
1823
1983
  return;
1824
1984
  }
1825
1985
  sess.write(framePathPaste(savedPath));
@@ -1831,6 +1991,112 @@ function receiveUpload({ req, res, sess, dir, filename, savedPath }) {
1831
1991
  sweepSessionUploads(dir, filename);
1832
1992
  });
1833
1993
  }
1994
+ function decodeHeaderText(header) {
1995
+ if (typeof header !== "string" || header.length === 0) return null;
1996
+ try {
1997
+ return decodeURIComponent(header);
1998
+ } catch {
1999
+ return null;
2000
+ }
2001
+ }
2002
+ function declaredContentLength(req) {
2003
+ const declared = Number(req.headers["content-length"]);
2004
+ return Number.isFinite(declared) && declared >= 0 ? declared : null;
2005
+ }
2006
+ function answerShow({ req, res, session, shownFiles }) {
2007
+ const name = path.basename(decodeHeaderText(req.headers["x-glimmervoid-show-name"]) ?? "");
2008
+ if (!name) {
2009
+ res.status(400).json({
2010
+ ok: false,
2011
+ error: "show needs the file name in x-glimmervoid-show-name"
2012
+ });
2013
+ return;
2014
+ }
2015
+ const caption = decodeHeaderText(req.headers["x-glimmervoid-show-caption"])?.slice(0, 500) ?? null;
2016
+ const declaredBytes = declaredContentLength(req);
2017
+ if (declaredBytes !== null && declaredBytes > 209715200) {
2018
+ res.status(413).json({
2019
+ ok: false,
2020
+ error: describeOverCap(name, declaredBytes)
2021
+ });
2022
+ req.resume();
2023
+ return;
2024
+ }
2025
+ const answer = (status, body) => {
2026
+ if (!res.headersSent) res.status(status).json(body);
2027
+ };
2028
+ shownFiles.reserveIncomingPath(session.id).then(async (incomingPath) => {
2029
+ const outcome = await streamRequestToFile({
2030
+ req,
2031
+ savedPath: incomingPath,
2032
+ capBytes: SHOWN_FILE_CAP_BYTES
2033
+ });
2034
+ if (!outcome.ok) {
2035
+ const failure = UPLOAD_FAILURE_REPLIES[outcome.reason];
2036
+ answer(failure.status, {
2037
+ ok: false,
2038
+ error: outcome.reason === "over-cap" ? describeOverCap(name, declaredBytes) : failure.error
2039
+ });
2040
+ return;
2041
+ }
2042
+ const admitted = await shownFiles.admit(session, {
2043
+ incomingPath,
2044
+ name,
2045
+ caption,
2046
+ sizeBytes: outcome.sizeBytes
2047
+ });
2048
+ if (!admitted.ok) {
2049
+ answer(admitted.status, {
2050
+ ok: false,
2051
+ error: admitted.error
2052
+ });
2053
+ return;
2054
+ }
2055
+ answer(200, {
2056
+ ok: true,
2057
+ shown: {
2058
+ name: admitted.file.name,
2059
+ kind: admitted.file.kind
2060
+ }
2061
+ });
2062
+ }).catch(() => {
2063
+ answer(500, {
2064
+ ok: false,
2065
+ error: "could not store the file"
2066
+ });
2067
+ });
2068
+ }
2069
+ var SHOWN_FILE_SANDBOX_POLICY = "sandbox allow-scripts allow-popups allow-modals allow-forms";
2070
+ function serveShownFile(req, res, shownFiles) {
2071
+ const servable = shownFiles?.findServable(req.params.sessionId, req.params.filename) ?? null;
2072
+ if (!servable) {
2073
+ res.status(404).end();
2074
+ return;
2075
+ }
2076
+ res.setHeader("Content-Security-Policy", SHOWN_FILE_SANDBOX_POLICY);
2077
+ res.setHeader("X-Content-Type-Options", "nosniff");
2078
+ res.setHeader("Cache-Control", "private, max-age=604800, immutable");
2079
+ res.setHeader("Accept-Ranges", "bytes");
2080
+ const byteRange = decideByteRange(req.headers.range, servable.sizeBytes);
2081
+ if (byteRange.kind === "unsatisfiable") {
2082
+ res.status(416).setHeader("Content-Range", `bytes */${servable.sizeBytes}`);
2083
+ res.end();
2084
+ return;
2085
+ }
2086
+ res.setHeader("Content-Type", servable.mime);
2087
+ if (byteRange.kind === "partial") {
2088
+ res.status(206);
2089
+ res.setHeader("Content-Range", `bytes ${byteRange.start}-${byteRange.end}/${servable.sizeBytes}`);
2090
+ res.setHeader("Content-Length", String(byteRange.end - byteRange.start + 1));
2091
+ pipeline(fs.createReadStream(servable.path, {
2092
+ start: byteRange.start,
2093
+ end: byteRange.end
2094
+ }), res, () => {});
2095
+ return;
2096
+ }
2097
+ res.setHeader("Content-Length", String(servable.sizeBytes));
2098
+ pipeline(fs.createReadStream(servable.path), res, () => {});
2099
+ }
1834
2100
  function refuseAgentRequest(res) {
1835
2101
  res.status(404).json({
1836
2102
  ok: false,
@@ -1897,7 +2163,7 @@ function mountAgentIngressRoutes(app, dependencies) {
1897
2163
  const { hookRouter, getSession, getUsage, getPlanReview = () => null, refocusContextFor = () => ({
1898
2164
  taskTitle: null,
1899
2165
  latestPlanTitle: null
1900
- }), getAgentApi = () => null, recordOutcome = () => {}, logger = console } = dependencies;
2166
+ }), getAgentApi = () => null, getShownFiles = () => null, recordOutcome = () => {}, logger = console } = dependencies;
1901
2167
  app.post("/hook/:glimmervoidId/:event", (req, res) => {
1902
2168
  if (!isLoopbackRequest(req)) {
1903
2169
  recordOutcome("hookRejectedNonLoopback");
@@ -1962,6 +2228,33 @@ function mountAgentIngressRoutes(app, dependencies) {
1962
2228
  });
1963
2229
  });
1964
2230
  });
2231
+ app.post("/agent/:glimmervoidId/show", (req, res) => {
2232
+ const agentApi = getAgentApi();
2233
+ const shownFiles = getShownFiles();
2234
+ const session = getSession(req.params.glimmervoidId);
2235
+ if (!decideAgentRequest({
2236
+ sessionId: req.params.glimmervoidId,
2237
+ presentedToken: presentedToken(req),
2238
+ expectedToken: session?.agentToken ?? null,
2239
+ isLoopback: isLoopbackRequest(req)
2240
+ }).ok || !session || !agentApi || !shownFiles) {
2241
+ req.resume();
2242
+ refuseAgentRequest(res);
2243
+ return;
2244
+ }
2245
+ const roleRefusal = agentApi.refuseRole(session, "show");
2246
+ if (roleRefusal) {
2247
+ req.resume();
2248
+ res.status(roleRefusal.status).json(roleRefusal.body);
2249
+ return;
2250
+ }
2251
+ answerShow({
2252
+ req,
2253
+ res,
2254
+ session,
2255
+ shownFiles
2256
+ });
2257
+ });
1965
2258
  app.post("/agent/:glimmervoidId/:verb", (req, res) => {
1966
2259
  const agentApi = getAgentApi();
1967
2260
  const session = getSession(req.params.glimmervoidId);
@@ -2150,6 +2443,9 @@ function createBackendHttpApp(dependencies) {
2150
2443
  if (!res.headersSent) res.status(404).end();
2151
2444
  });
2152
2445
  });
2446
+ app.get("/shown/:sessionId/:filename", (req, res) => {
2447
+ serveShownFile(req, res, dependencies.getShownFiles?.() ?? null);
2448
+ });
2153
2449
  mountCustomSoundRoutes(app, { soundsDir: customSoundsDir });
2154
2450
  if (staticDir === "auto") {
2155
2451
  if (!fs.existsSync(path.join(clientDir, "index.html"))) throw Object.assign(/* @__PURE__ */ new Error("Dashboard build not found (dist/client). Run `npm run build` first, or use `npm run dev`."), { glimmervoidBoot: true });
@@ -2313,9 +2609,6 @@ function projectSessionSnapshots(source) {
2313
2609
  resumeSessionId: source.resumeSessionId,
2314
2610
  activeAgents: source.activeAgents,
2315
2611
  awaitingBackgroundTasks: source.awaitingBackgroundTasks,
2316
- isCompacting: source.isCompacting ?? false,
2317
- isWaitingForMemory: source.isWaitingForMemory === true,
2318
- pendingWakeup: source.pendingWakeup,
2319
2612
  pendingPromptKind: source.pendingPromptKind,
2320
2613
  conversationApproval: source.conversationApproval ?? null,
2321
2614
  hasConversationBridge: source.hasConversationBridge === true,
@@ -2882,6 +3175,7 @@ var AGENT_LOGINS_SIBLING_ENTRY = "agent-logins";
2882
3175
  var GLIMMERVOID_CONFIG_SIBLING_ENTRIES = Object.freeze([
2883
3176
  AGENT_LOGINS_SIBLING_ENTRY,
2884
3177
  "bin",
3178
+ "owned-paths.json",
2885
3179
  "pairings.json",
2886
3180
  "pairings-seen.json",
2887
3181
  "plans",
@@ -3206,7 +3500,8 @@ function buildGuardrailsLaunchPlan(role, host) {
3206
3500
  ...filesystemWrites(role, host, writablePaths)
3207
3501
  },
3208
3502
  terminalScope: isStandardLevel(role) ? "any" : "own",
3209
- allowAppleEvents: isStandardLevel(role)
3503
+ allowAppleEvents: isStandardLevel(role),
3504
+ allowMachRegister: isStandardLevel(role)
3210
3505
  },
3211
3506
  launchEnv: Object.assign({ CLAUDE_CODE_TMPDIR: host.tempDir }, ...role.vendors.map((vendor) => VENDOR_PROFILES[vendor].launchEnv), launchHomeEnv(role, host)),
3212
3507
  guardedEnv: guardedEnvFor(role, host),
@@ -3244,6 +3539,12 @@ async function pruneAgedFiles({ directory, suffixes, retainDays, now = Date.now(
3244
3539
  const filePath = path.join(directory, entry);
3245
3540
  try {
3246
3541
  if ((await fsPromises.stat(filePath)).mtimeMs >= cutoff) continue;
3542
+ if (!await recordSweptPath({
3543
+ cause: "aged-files",
3544
+ kind: entryMode === "directory" ? "folder" : "file",
3545
+ path: filePath,
3546
+ checksPassed: [`untouched-${retainDays}d`]
3547
+ })) continue;
3247
3548
  if (entryMode === "directory") await fsPromises.rm(filePath, {
3248
3549
  recursive: true,
3249
3550
  force: true
@@ -3626,12 +3927,15 @@ async function sweepOrphanedGuardrailsLaunches(log = console) {
3626
3927
  const ownerPath = path.join(policyDir, entryName);
3627
3928
  const owner = await readOwnerFile(ownerPath);
3628
3929
  if (owner === null || !isOwnerProcessGone(owner)) continue;
3930
+ const launchHomeDir = path.join(glimmervoidHome, LAUNCH_HOME_FOLDER, launchId);
3931
+ const ownedTempDir = owner.ownsTempDir ? path.join(glimmervoidHome, GUARDRAILS_TEMP_FOLDER, launchId) : null;
3932
+ if (!await recordSweptLaunchFolders([launchHomeDir, ownedTempDir], log)) continue;
3629
3933
  try {
3630
3934
  await removeLaunch({
3631
3935
  policyPath: path.join(policyDir, `${launchId}.json`),
3632
3936
  ownerPath,
3633
- launchHomeDir: path.join(glimmervoidHome, LAUNCH_HOME_FOLDER, launchId),
3634
- ownedTempDir: owner.ownsTempDir ? path.join(glimmervoidHome, GUARDRAILS_TEMP_FOLDER, launchId) : null
3937
+ launchHomeDir,
3938
+ ownedTempDir
3635
3939
  }, owner, launchId, { onlyNewerLogins: true });
3636
3940
  removedLaunchCount += 1;
3637
3941
  } catch (error) {
@@ -3643,6 +3947,19 @@ async function sweepOrphanedGuardrailsLaunches(log = console) {
3643
3947
  log.warn(`[guardrails] could not sweep leftover launch folders: ${errorMessage(error)}`);
3644
3948
  }
3645
3949
  }
3950
+ async function recordSweptLaunchFolders(folderPaths, log) {
3951
+ for (const folderPath of folderPaths) {
3952
+ if (folderPath === null) continue;
3953
+ if (!await recordSweptPath({
3954
+ cause: "guardrails-temp",
3955
+ kind: "folder",
3956
+ path: folderPath,
3957
+ checksPassed: ["owner-process-gone"],
3958
+ log
3959
+ })) return false;
3960
+ }
3961
+ return true;
3962
+ }
3646
3963
  function sessionIdsWithLockFilesIn(entryNames) {
3647
3964
  return new Set(entryNames.map(sessionIdOfLockFile).filter((sessionId) => sessionId !== null));
3648
3965
  }
@@ -3655,6 +3972,19 @@ async function deleteSessionTempFolderOnceUnheld(policyDir, sessionId, sessionTe
3655
3972
  return;
3656
3973
  }
3657
3974
  if (trashFolder === null) return;
3975
+ if (!await recordSweptPath({
3976
+ cause: "guardrails-temp",
3977
+ kind: "folder",
3978
+ path: sessionTempFolder,
3979
+ sessionId,
3980
+ checksPassed: ["no-lock-held"],
3981
+ log
3982
+ })) {
3983
+ await rename(trashFolder, sessionTempFolder).catch((error) => {
3984
+ log.warn(`[guardrails] could not move session temp folder ${trashFolder} back to ${sessionTempFolder}: ${errorMessage(error)}`);
3985
+ });
3986
+ return;
3987
+ }
3658
3988
  await rm(trashFolder, {
3659
3989
  recursive: true,
3660
3990
  force: true
@@ -4704,6 +5034,7 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
4704
5034
  }),
4705
5035
  push,
4706
5036
  edit,
5037
+ show: () => refusal(400, "show sends the file itself; run glimmervoid show <path>"),
4707
5038
  limits: (session) => ({
4708
5039
  status: 200,
4709
5040
  body: limits(session)
@@ -4737,7 +5068,17 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
4737
5068
  if (roleRefusal) return roleRefusal;
4738
5069
  return dispatch(session, parsed.verb, parsed.request);
4739
5070
  }
4740
- return { handle };
5071
+ function markChildWorktreeDiscard(childId) {
5072
+ childIdsToDiscard.add(childId);
5073
+ }
5074
+ function refuseRole(session, verb) {
5075
+ return describeRoleRefusal(verb, roleFactsOf(session));
5076
+ }
5077
+ return {
5078
+ handle,
5079
+ refuseRole,
5080
+ markChildWorktreeDiscard
5081
+ };
4741
5082
  }
4742
5083
  //#endregion
4743
5084
  //#region server/remote-setup-wiring.ts
@@ -4824,11 +5165,7 @@ function createRemoteSetupWiring({ config, agentSessions, makeSession, wireSessi
4824
5165
  }
4825
5166
  //#endregion
4826
5167
  //#region server/core/control-replay-core.ts
4827
- var REPLAYABLE_EXACT = /* @__PURE__ */ new Set([
4828
- "notify",
4829
- "session-error",
4830
- "post-turn-result"
4831
- ]);
5168
+ var REPLAYABLE_EXACT = /* @__PURE__ */ new Set(["notify", "session-error"]);
4832
5169
  function isReplayable(type) {
4833
5170
  return typeof type === "string" && REPLAYABLE_EXACT.has(type);
4834
5171
  }
@@ -5053,7 +5390,7 @@ function planDeepSlices({ lineCount, rows, sliceRows, isWrapped }) {
5053
5390
  //#endregion
5054
5391
  //#region server/ws-sender.ts
5055
5392
  var OPEN = 1;
5056
- var DEFAULTS$1 = Object.freeze({
5393
+ var DEFAULTS = Object.freeze({
5057
5394
  maxSendBuffer: 65536,
5058
5395
  highWaterMark: 1 << 20,
5059
5396
  lowWaterMark: 1 << 18,
@@ -5061,10 +5398,10 @@ var DEFAULTS$1 = Object.freeze({
5061
5398
  });
5062
5399
  function createWsSender(ws, opts = {}) {
5063
5400
  const cfg = {
5064
- maxSendBuffer: opts.maxSendBuffer ?? DEFAULTS$1.maxSendBuffer,
5065
- highWaterMark: opts.highWaterMark ?? DEFAULTS$1.highWaterMark,
5066
- lowWaterMark: opts.lowWaterMark ?? DEFAULTS$1.lowWaterMark,
5067
- stallCloseMs: opts.stallCloseMs ?? DEFAULTS$1.stallCloseMs
5401
+ maxSendBuffer: opts.maxSendBuffer ?? DEFAULTS.maxSendBuffer,
5402
+ highWaterMark: opts.highWaterMark ?? DEFAULTS.highWaterMark,
5403
+ lowWaterMark: opts.lowWaterMark ?? DEFAULTS.lowWaterMark,
5404
+ stallCloseMs: opts.stallCloseMs ?? DEFAULTS.stallCloseMs
5068
5405
  };
5069
5406
  const scheduleImmediate = opts.setImmediateFn || setImmediate;
5070
5407
  const setTimer = opts.setTimeoutFn || ((fn, ms) => setTimeout(fn, ms));
@@ -5984,6 +6321,15 @@ function decideAcknowledge(isAcknowledgeDeferred, from, event, isIdleCompactionA
5984
6321
  };
5985
6322
  }
5986
6323
  //#endregion
6324
+ //#region session/core/exit-close.ts
6325
+ var UNMERGED_MERGE_STATUSES = /* @__PURE__ */ new Set(["pending-review", "parked"]);
6326
+ function decideExitClose(facts) {
6327
+ if (!facts.exitedOnItsOwn || !facts.isConfiguredSession) return "keep";
6328
+ if (facts.isWorkspace || facts.hasRemoteEnvironment) return "confirm";
6329
+ if (UNMERGED_MERGE_STATUSES.has(facts.mergeStatus)) return "confirm";
6330
+ return "remove";
6331
+ }
6332
+ //#endregion
5987
6333
  //#region session/core/claude-approval-core.ts
5988
6334
  var MAIN_AGENT_KEY$1 = "";
5989
6335
  var SESSION_DESTINATION = "session";
@@ -6352,605 +6698,6 @@ function compareEntries(a, b) {
6352
6698
  return laneKey(a.vendor, a.sessionId).localeCompare(laneKey(b.vendor, b.sessionId));
6353
6699
  }
6354
6700
  //#endregion
6355
- //#region session/core/slop-code-patterns.ts
6356
- var TS_EXTS = /* @__PURE__ */ new Set([
6357
- ".ts",
6358
- ".tsx",
6359
- ".mts",
6360
- ".cts"
6361
- ]);
6362
- var PY_EXTS = /* @__PURE__ */ new Set([".py"]);
6363
- function extOf(relPath) {
6364
- if (!relPath) return "";
6365
- const s = String(relPath).replace(/\\/g, "/");
6366
- const base = s.slice(s.lastIndexOf("/") + 1);
6367
- const dot = base.lastIndexOf(".");
6368
- return dot <= 0 ? "" : base.slice(dot).toLowerCase();
6369
- }
6370
- var SUBRULES = [
6371
- {
6372
- id: "swallowed-exception",
6373
- axis: "lies",
6374
- re: /catch\s*(?:\([^)]*\))?\s*\{\s*(?:\/\/[^\n]*\s*|\/\*[\s\S]*?\*\/\s*)?\}/g
6375
- },
6376
- {
6377
- id: "opener-comment",
6378
- axis: "noise",
6379
- re: /^[ \t]*(?:\/\/+|#|\*)[ \t]*(?:(?:now|first|next|then|here)[, ]+(?:we|let)\b|let'?s\b|we\s+(?:now|then|will|can|need|first)\b|this\s+(?:function|method|class|code|section|block|module|file)\b|step\s+\d+\s*:)/gim
6380
- },
6381
- {
6382
- id: "placeholder",
6383
- axis: "lies",
6384
- re: /(?:in\s+a\s+real\s+(?:implementation|app|application|world|system|scenario|project)|in\s+production(?:[,\s]|\b)|for\s+now[, ]+(?:we|just|this|i'?ll|return|use)|this\s+is\s+(?:a\s+)?simplified|simplified\s+(?:version|example|implementation)|for\s+(?:demonstration|illustration|example)\s+purposes|placeholder\s+(?:for|implementation|value)|you\s+(?:would|could|might|may)\s+(?:want\s+to\s+)?(?:add|implement|replace|put))/gi
6385
- },
6386
- {
6387
- id: "debug-leftover",
6388
- axis: "noise",
6389
- re: /\bconsole\.(?:log|debug|info|trace)\s*\(|\bdebugger\b/g
6390
- },
6391
- {
6392
- id: "debug-print-py",
6393
- axis: "noise",
6394
- langs: PY_EXTS,
6395
- re: /^[ \t]*print\s*\(/gim
6396
- },
6397
- {
6398
- id: "hedge-comment",
6399
- axis: "soul",
6400
- re: /(?:\/\/+|#)[^\n]*\b(?:should\s+(?:work|be\s+fine|do\s+it|handle)|not\s+(?:entirely\s+)?sure|probably\s+(?:works?|fine|ok|okay)|hopefully|i\s+think\s+this|might\s+(?:work|break|fail)|not\s+sure\s+(?:why|if|how))\b/gi
6401
- },
6402
- {
6403
- id: "type-escape",
6404
- axis: "soul",
6405
- langs: TS_EXTS,
6406
- re: /\bas\s+any\b|:\s*any(?:\b|\[)|@ts-(?:ignore|nocheck|expect-error)\b|eslint-disable(?:-next-line|-line)?\b/g
6407
- }
6408
- ];
6409
- var DEFAULT_FINDINGS_CAP = 200;
6410
- function detectCodeSlop(content, relPath, cap = DEFAULT_FINDINGS_CAP) {
6411
- if (typeof content !== "string" || content.length === 0) return [];
6412
- const ext = extOf(relPath);
6413
- const findings = [];
6414
- for (const sub of SUBRULES) {
6415
- if (sub.langs && !sub.langs.has(ext)) continue;
6416
- sub.re.lastIndex = 0;
6417
- for (;;) {
6418
- const m = sub.re.exec(content);
6419
- if (m === null) break;
6420
- findings.push({
6421
- subrule: sub.id,
6422
- axis: sub.axis,
6423
- index: m.index
6424
- });
6425
- if (findings.length >= cap) break;
6426
- }
6427
- if (findings.length >= cap) break;
6428
- }
6429
- findings.sort((a, b) => a.index - b.index);
6430
- return findings;
6431
- }
6432
- //#endregion
6433
- //#region session/core/post-turn-rules.ts
6434
- var BOM = String.fromCharCode(65279);
6435
- var NL = String.fromCharCode(10);
6436
- var CRLF = String.fromCharCode(13, 10);
6437
- function posAt(content, offset) {
6438
- let line = 1;
6439
- let col = 1;
6440
- const end = Math.min(offset, content.length);
6441
- for (let i = 0; i < end; i++) {
6442
- if (content[i] !== NL) {
6443
- col++;
6444
- continue;
6445
- }
6446
- line++;
6447
- col = 1;
6448
- }
6449
- return {
6450
- line,
6451
- col
6452
- };
6453
- }
6454
- function detectSlop(content, ctx) {
6455
- const relPath = ctx?.relPath;
6456
- const matches = detectCodeSlop(content, relPath);
6457
- const findings = [];
6458
- let pos = 0;
6459
- let line = 1;
6460
- let col = 1;
6461
- for (const m of matches) {
6462
- const target = m.index < content.length ? m.index : content.length;
6463
- while (pos < target) {
6464
- if (content.charCodeAt(pos) === 10) {
6465
- line++;
6466
- col = 1;
6467
- pos++;
6468
- continue;
6469
- }
6470
- col++;
6471
- pos++;
6472
- }
6473
- findings.push({
6474
- rule: "slop",
6475
- subrule: m.subrule,
6476
- axis: m.axis,
6477
- line,
6478
- col
6479
- });
6480
- }
6481
- return {
6482
- content,
6483
- findings
6484
- };
6485
- }
6486
- function fixTrailingWhitespace(content) {
6487
- const findings = [];
6488
- return {
6489
- content: content.replace(/[ \t]+(\r?\n|$)/g, (m, tail, offset) => {
6490
- const { line, col } = posAt(content, offset);
6491
- findings.push({
6492
- rule: "trailingWs",
6493
- line,
6494
- col,
6495
- before: m,
6496
- after: tail
6497
- });
6498
- return tail;
6499
- }),
6500
- findings
6501
- };
6502
- }
6503
- function fixFinalNewline(content) {
6504
- if (content === "" || content.charCodeAt(content.length - 1) === 10) return {
6505
- content,
6506
- findings: []
6507
- };
6508
- const nl = content.indexOf(CRLF) !== -1 ? CRLF : NL;
6509
- const { line, col } = posAt(content, content.length);
6510
- return {
6511
- content: content + nl,
6512
- findings: [{
6513
- rule: "finalNewline",
6514
- line,
6515
- col,
6516
- before: "",
6517
- after: nl
6518
- }]
6519
- };
6520
- }
6521
- function stripBom(content) {
6522
- if (content.charCodeAt(0) !== 65279) return {
6523
- content,
6524
- findings: []
6525
- };
6526
- return {
6527
- content: content.slice(1),
6528
- findings: [{
6529
- rule: "bom",
6530
- line: 1,
6531
- col: 1,
6532
- before: BOM,
6533
- after: ""
6534
- }]
6535
- };
6536
- }
6537
- var RULES = Object.freeze({
6538
- bom: { fix: stripBom },
6539
- trailingWs: { fix: fixTrailingWhitespace },
6540
- finalNewline: { fix: fixFinalNewline },
6541
- slop: { fix: detectSlop }
6542
- });
6543
- var RULE_ORDER = [
6544
- "bom",
6545
- "trailingWs",
6546
- "finalNewline",
6547
- "slop"
6548
- ];
6549
- var EXEMPT_MARKER = "glimmervoid-no-fix";
6550
- function exemptions(content) {
6551
- const head = content.slice(0, 4096);
6552
- if (head.indexOf(EXEMPT_MARKER) === -1) return {
6553
- all: false,
6554
- rules: /* @__PURE__ */ new Set()
6555
- };
6556
- const rules = /* @__PURE__ */ new Set();
6557
- const re = new RegExp(`${EXEMPT_MARKER}:([a-zA-Z]+)`, "g");
6558
- let m = re.exec(head);
6559
- while (m !== null) {
6560
- rules.add(m[1]);
6561
- m = re.exec(head);
6562
- }
6563
- return {
6564
- all: new RegExp(`${EXEMPT_MARKER}(?!:)`).test(head),
6565
- rules
6566
- };
6567
- }
6568
- function applyRules(content, rules, ctx) {
6569
- const ex = exemptions(content);
6570
- const findings = [];
6571
- let current = content;
6572
- for (const name of RULE_ORDER) {
6573
- const cfg = rules?.[name];
6574
- if (!cfg || !cfg.enabled) continue;
6575
- if (ex.all || ex.rules.has(name)) continue;
6576
- const res = RULES[name].fix(current, ctx);
6577
- if (res.findings.length === 0) continue;
6578
- for (const f of res.findings) findings.push(f);
6579
- if (cfg.mode === "fix") current = res.content;
6580
- }
6581
- return {
6582
- content: current,
6583
- findings,
6584
- changed: current !== content
6585
- };
6586
- }
6587
- var REGEX_METACHARACTERS = [
6588
- ".",
6589
- "+",
6590
- "^",
6591
- "$",
6592
- "{",
6593
- "}",
6594
- "(",
6595
- ")",
6596
- "|",
6597
- "[",
6598
- "]",
6599
- "\\"
6600
- ].join("");
6601
- function starSegment(glob, index) {
6602
- if (glob[index + 1] !== "*") return {
6603
- pattern: "[^/]*",
6604
- consumed: 0
6605
- };
6606
- if (glob[index + 2] === "/") return {
6607
- pattern: "(?:.*/)?",
6608
- consumed: 2
6609
- };
6610
- return {
6611
- pattern: ".*",
6612
- consumed: 1
6613
- };
6614
- }
6615
- function globToRegExp(glob) {
6616
- let re = "^";
6617
- for (let i = 0; i < glob.length; i++) {
6618
- const c = glob[i];
6619
- if (c === "*") {
6620
- const segment = starSegment(glob, i);
6621
- re += segment.pattern;
6622
- i += segment.consumed;
6623
- continue;
6624
- }
6625
- if (c === "?") {
6626
- re += "[^/]";
6627
- continue;
6628
- }
6629
- if (REGEX_METACHARACTERS.indexOf(c) !== -1) {
6630
- re += `\\${c}`;
6631
- continue;
6632
- }
6633
- re += c;
6634
- }
6635
- re += "$";
6636
- return new RegExp(re);
6637
- }
6638
- function shouldCheckPath(relPath, { include, exclude } = {}) {
6639
- const p = String(relPath).replace(/\\/g, "/");
6640
- const inc = include?.length ? include : ["**/*"];
6641
- const matchAny = (globs) => globs.some((g) => globToRegExp(g).test(p));
6642
- if (!matchAny(inc)) return false;
6643
- if (exclude?.length && matchAny(exclude)) return false;
6644
- return true;
6645
- }
6646
- function looksBinary(buf) {
6647
- const n = Math.min(buf.length, 8192);
6648
- const at = typeof buf === "string" ? (i) => buf.charCodeAt(i) : (i) => buf[i];
6649
- for (let i = 0; i < n; i++) if (at(i) === 0) return true;
6650
- return false;
6651
- }
6652
- //#endregion
6653
- //#region server/git/git-exec.ts
6654
- var DEFAULT_COMMAND_TIMEOUT_MS = 2e4;
6655
- function outputText(value) {
6656
- if (typeof value === "string") return value;
6657
- if (Buffer.isBuffer(value)) return value.toString("utf8");
6658
- return value == null ? "" : String(value);
6659
- }
6660
- function childEnv(env, replaceEnv) {
6661
- if (replaceEnv) return { env: replaceEnv };
6662
- if (env) return { env: {
6663
- ...process.env,
6664
- ...env
6665
- } };
6666
- return {};
6667
- }
6668
- async function runCommand(file, args, { cwd, timeoutMs = DEFAULT_COMMAND_TIMEOUT_MS, maxBuffer, env, replaceEnv, signal, input, trim = true, keepStdoutOnFailure = false, preferStderr = false, execFileFn = execFileAsync } = {}) {
6669
- const execOptions = {
6670
- encoding: "utf8",
6671
- timeout: timeoutMs,
6672
- ...cwd === void 0 ? {} : { cwd },
6673
- ...maxBuffer === void 0 ? {} : { maxBuffer },
6674
- ...signal ? { signal } : {},
6675
- ...input === void 0 ? {} : { input },
6676
- ...childEnv(env, replaceEnv)
6677
- };
6678
- try {
6679
- const { stdout, stderr } = await execFileFn(file, args, execOptions);
6680
- const out = outputText(stdout);
6681
- return {
6682
- ok: true,
6683
- out: trim ? out.trim() : out,
6684
- err: "",
6685
- stderr: outputText(stderr)
6686
- };
6687
- } catch (error) {
6688
- const failure = error ?? {};
6689
- const stdout = outputText(failure.stdout);
6690
- const stderr = outputText(failure.stderr);
6691
- const message = errorMessage(error);
6692
- return {
6693
- ok: false,
6694
- out: keepStdoutOnFailure ? trim ? stdout.trim() : stdout : "",
6695
- err: preferStderr ? stderr || message : message,
6696
- stderr,
6697
- error,
6698
- timedOut: failure.killed === true && Boolean(failure.signal)
6699
- };
6700
- }
6701
- }
6702
- function runGit(args, { gitPath = "git", env, replaceEnv, ...rest } = {}) {
6703
- const baseEnv = replaceEnv ?? (env ? {
6704
- ...process.env,
6705
- ...env
6706
- } : process.env);
6707
- return runCommand(gitPath, args, {
6708
- ...rest,
6709
- replaceEnv: {
6710
- ...baseEnv,
6711
- GIT_TERMINAL_PROMPT: "0"
6712
- }
6713
- });
6714
- }
6715
- function gitCommandFailure(args, command, timeoutMs = DEFAULT_COMMAND_TIMEOUT_MS) {
6716
- if (!command.timedOut) return command.error;
6717
- return new Error(`git ${args.join(" ")} timed out after ${timeoutMs}ms`, { cause: command.error });
6718
- }
6719
- function runGh(args, options = {}) {
6720
- return runCommand("gh", args, options);
6721
- }
6722
- //#endregion
6723
- //#region server/post-turn-checker.ts
6724
- var GIT_TIMEOUT_MS$2 = 5e3;
6725
- var DEFAULTS = Object.freeze({
6726
- enabled: true,
6727
- mode: "report",
6728
- rules: {
6729
- trailingWs: true,
6730
- finalNewline: true,
6731
- bom: true,
6732
- slop: false
6733
- },
6734
- include: ["**/*"],
6735
- exclude: [
6736
- "**/node_modules/**",
6737
- "**/dist/**",
6738
- "**/.git/**",
6739
- "**/.glimmervoid/**",
6740
- "**/*.lock",
6741
- "**/package-lock.json",
6742
- "**/pnpm-lock.yaml",
6743
- "**/yarn.lock",
6744
- "**/*.png",
6745
- "**/*.jpg",
6746
- "**/*.jpeg",
6747
- "**/*.gif",
6748
- "**/*.webp",
6749
- "**/*.ico",
6750
- "**/*.pdf",
6751
- "**/*.zip",
6752
- "**/*.gz",
6753
- "**/*.woff",
6754
- "**/*.woff2",
6755
- "**/*.ttf",
6756
- "**/*.eot"
6757
- ],
6758
- maxFiles: 200,
6759
- maxFileBytes: 524288,
6760
- debounceMs: 1500
6761
- });
6762
- function explicitMode(mode) {
6763
- return mode === "fix" ? "fix" : "report";
6764
- }
6765
- function normalizeRule(val, topMode) {
6766
- if (val === false) return {
6767
- enabled: false,
6768
- mode: topMode
6769
- };
6770
- if (val === true || val == null) return {
6771
- enabled: true,
6772
- mode: topMode
6773
- };
6774
- if (typeof val === "object") {
6775
- const source = val;
6776
- return {
6777
- enabled: source.enabled !== false,
6778
- mode: source.mode == null ? topMode : explicitMode(source.mode)
6779
- };
6780
- }
6781
- return {
6782
- enabled: true,
6783
- mode: topMode
6784
- };
6785
- }
6786
- var SCALAR_KEYS = [
6787
- "enabled",
6788
- "mode",
6789
- "maxFiles",
6790
- "maxFileBytes",
6791
- "debounceMs"
6792
- ];
6793
- function resolveCheckConfig(globalCfg, projectCfg) {
6794
- const base = JSON.parse(JSON.stringify(DEFAULTS));
6795
- const layers = [globalCfg, projectCfg].filter((x) => Boolean(x) && typeof x === "object");
6796
- for (const layer of layers) {
6797
- for (const k of SCALAR_KEYS) if (layer[k] != null) Object.assign(base, { [k]: layer[k] });
6798
- if (Array.isArray(layer.include)) base.include = layer.include.slice();
6799
- if (Array.isArray(layer.exclude)) base.exclude = layer.exclude.slice();
6800
- if (layer.rules && typeof layer.rules === "object") base.rules = Object.assign({}, base.rules, layer.rules);
6801
- }
6802
- const topMode = explicitMode(base.mode);
6803
- const rules = {};
6804
- for (const name of Object.keys(base.rules)) rules[name] = normalizeRule(base.rules[name], topMode);
6805
- return {
6806
- enabled: base.enabled !== false,
6807
- mode: topMode,
6808
- rules,
6809
- include: base.include,
6810
- exclude: base.exclude,
6811
- maxFiles: base.maxFiles,
6812
- maxFileBytes: base.maxFileBytes,
6813
- debounceMs: base.debounceMs
6814
- };
6815
- }
6816
- var GIT_MAX_BUFFER_BYTES$1 = 8388608;
6817
- async function execGit(args, cwd) {
6818
- const result = await runGit(args, {
6819
- cwd,
6820
- timeoutMs: GIT_TIMEOUT_MS$2,
6821
- maxBuffer: GIT_MAX_BUFFER_BYTES$1,
6822
- trim: false
6823
- });
6824
- if (!result.ok) throw result.error;
6825
- return result.out;
6826
- }
6827
- async function gitRoot(cwd) {
6828
- return (await execGit(["rev-parse", "--show-toplevel"], cwd)).toString().trim();
6829
- }
6830
- async function listChangedFiles(root) {
6831
- const lines = (await execGit([
6832
- "--no-optional-locks",
6833
- "-c",
6834
- "core.quotepath=false",
6835
- "status",
6836
- "--porcelain",
6837
- "-uall",
6838
- "--no-renames"
6839
- ], root)).toString().split(/\r?\n/);
6840
- const files = [];
6841
- for (const ln of lines) {
6842
- if (!ln) continue;
6843
- const xy = ln.slice(0, 2);
6844
- const p = ln.slice(3);
6845
- if (!p) continue;
6846
- if (xy.indexOf("D") !== -1) continue;
6847
- files.push(p);
6848
- }
6849
- return files;
6850
- }
6851
- async function runPostTurnChecks({ cwd, config, deps = {} } = {}) {
6852
- const started = Date.now();
6853
- const cfg = config || resolveCheckConfig();
6854
- const report = {
6855
- ok: true,
6856
- skipped: null,
6857
- mode: cfg.mode,
6858
- root: null,
6859
- filesScanned: 0,
6860
- filesFixed: 0,
6861
- findings: [],
6862
- errors: [],
6863
- durationMs: 0
6864
- };
6865
- const done = () => {
6866
- report.durationMs = Date.now() - started;
6867
- return report;
6868
- };
6869
- if (!cfg.enabled) {
6870
- report.skipped = "disabled";
6871
- return done();
6872
- }
6873
- const _gitRoot = deps.gitRoot || gitRoot;
6874
- const _list = deps.listChangedFiles || listChangedFiles;
6875
- const _readFile = deps.readFile || ((p) => fs.readFileSync(p));
6876
- const _writeFile = deps.writeFile || ((p, c) => fs.writeFileSync(p, c));
6877
- const _stat = deps.stat || ((p) => fs.statSync(p));
6878
- let root;
6879
- try {
6880
- root = await _gitRoot(cwd);
6881
- } catch {
6882
- report.skipped = "no-git";
6883
- return done();
6884
- }
6885
- report.root = root;
6886
- let files;
6887
- try {
6888
- files = await _list(root);
6889
- } catch (err) {
6890
- report.skipped = "no-git";
6891
- report.errors.push({
6892
- file: null,
6893
- message: errorMessage(err)
6894
- });
6895
- return done();
6896
- }
6897
- if (!files.length) {
6898
- report.skipped = "no-changes";
6899
- return done();
6900
- }
6901
- const eligible = files.filter((rel) => shouldCheckPath(rel, {
6902
- include: cfg.include,
6903
- exclude: cfg.exclude
6904
- })).slice(0, cfg.maxFiles);
6905
- let fileIndex = 0;
6906
- for (const rel of eligible) {
6907
- if (fileIndex++ > 0) await new Promise((resolve) => setImmediate(resolve));
6908
- const abs = path.join(root, rel);
6909
- try {
6910
- const before = _stat(abs);
6911
- if (!before) {
6912
- report.errors.push({
6913
- file: rel,
6914
- message: "skipped-race"
6915
- });
6916
- continue;
6917
- }
6918
- if (before && before.size != null && before.size > cfg.maxFileBytes) continue;
6919
- const buf = _readFile(abs);
6920
- if (looksBinary(buf)) continue;
6921
- const res = applyRules(buf.toString("utf8"), cfg.rules, { relPath: rel });
6922
- report.filesScanned++;
6923
- if (res.findings.length) {
6924
- const counts = {};
6925
- for (const f of res.findings) counts[f.rule] = (counts[f.rule] || 0) + 1;
6926
- for (const [rule, count] of Object.entries(counts)) report.findings.push({
6927
- file: rel,
6928
- rule,
6929
- count
6930
- });
6931
- }
6932
- if (res.changed) {
6933
- const after = _stat(abs);
6934
- if (after && (after.mtimeMs !== before.mtimeMs || after.size !== before.size)) {
6935
- report.errors.push({
6936
- file: rel,
6937
- message: "skipped-race"
6938
- });
6939
- continue;
6940
- }
6941
- _writeFile(abs, res.content);
6942
- report.filesFixed++;
6943
- }
6944
- } catch (err) {
6945
- report.errors.push({
6946
- file: rel,
6947
- message: errorMessage(err)
6948
- });
6949
- }
6950
- }
6951
- return done();
6952
- }
6953
- //#endregion
6954
6701
  //#region server/session/session-event-wiring.ts
6955
6702
  var IDLE_COMPACTION_CONFIRM_MS = 1e4;
6956
6703
  function waitingCopy(name, context) {
@@ -6985,7 +6732,6 @@ function createSessionEventWiring(dependencies) {
6985
6732
  dependencies.taskTitleRefiner?.attachSession(session);
6986
6733
  dependencies.coherenceFeed?.attachSession(session);
6987
6734
  dependencies.planReview?.attachSession(session);
6988
- let postTurnDebounce = null;
6989
6735
  let pendingPromptKind = null;
6990
6736
  let pendingAgentNote = null;
6991
6737
  const notifyGate = createNotifyGate();
@@ -7064,11 +6810,7 @@ function createSessionEventWiring(dependencies) {
7064
6810
  dependencies.logger.error(`[${session.name}] error: ${errorMessage(error)}`);
7065
6811
  dependencies.telemetry?.captureException(error, { handled: true });
7066
6812
  });
7067
- session.on("exit", ({ exitCode, signal, reason }) => {
7068
- if (postTurnDebounce) {
7069
- clearTimeout(postTurnDebounce);
7070
- postTurnDebounce = null;
7071
- }
6813
+ session.on("exit", ({ exitCode, signal, reason, exitedOnItsOwn }) => {
7072
6814
  clearCompactionConfirm();
7073
6815
  const reasonText = reason ? `, reason=${reason}` : "";
7074
6816
  dependencies.logger.log(`[${session.name}] exited (code=${exitCode}, signal=${signal}${reasonText})`);
@@ -7077,39 +6819,34 @@ function createSessionEventWiring(dependencies) {
7077
6819
  signal,
7078
6820
  reason
7079
6821
  }));
6822
+ closeSessionAfterExit(exitedOnItsOwn === true);
7080
6823
  });
6824
+ function closeSessionAfterExit(exitedOnItsOwn) {
6825
+ const project = dependencies.config.projects.find((candidate) => candidate.id === session.id);
6826
+ const exitClose = decideExitClose({
6827
+ exitedOnItsOwn,
6828
+ isConfiguredSession: project !== void 0,
6829
+ mergeStatus: session.mergeStatus,
6830
+ isWorkspace: session.isWorkspace,
6831
+ hasRemoteEnvironment: Boolean(project?.environment)
6832
+ });
6833
+ if (exitClose === "remove") {
6834
+ dependencies.logger.log(`[${session.name}] agent quit on its own, removing the session`);
6835
+ dependencies.removeExitedSession?.(session.id);
6836
+ return;
6837
+ }
6838
+ if (exitClose === "confirm") dependencies.broadcastControl({
6839
+ type: "session-close-confirm",
6840
+ id: session.id,
6841
+ mergeStatus: session.mergeStatus
6842
+ });
6843
+ }
7081
6844
  session.on("teardown", ({ cause }) => {
7082
6845
  clearCompactionConfirm();
7083
6846
  isAcknowledgeDeferredForCompaction = false;
7084
6847
  dependencies.getIngestLane()?.detachSessionTap(session);
7085
6848
  captureSessionEnded(cause);
7086
6849
  });
7087
- const resolvePostTurn = () => {
7088
- const project = dependencies.config.projects.find((candidate) => candidate.id === session.id);
7089
- return project ? resolveCheckConfig(dependencies.config.postTurnChecks, project.postTurnChecks) : null;
7090
- };
7091
- session.on("post-turn-check", () => {
7092
- const config = resolvePostTurn();
7093
- if (!config?.enabled) return;
7094
- if (postTurnDebounce) clearTimeout(postTurnDebounce);
7095
- postTurnDebounce = setTimeout(() => {
7096
- postTurnDebounce = null;
7097
- const runConfig = resolvePostTurn();
7098
- if (!runConfig?.enabled) return;
7099
- runPostTurnChecks({
7100
- cwd: session.effectiveCwd(),
7101
- config: runConfig
7102
- }).then((report) => {
7103
- dependencies.broadcastControl({
7104
- type: "post-turn-result",
7105
- id: session.id,
7106
- session: session.name,
7107
- ...report,
7108
- timestamp: Date.now()
7109
- });
7110
- }).catch((error) => dependencies.logger.warn(`[${session.name}] post-turn checks failed: ${errorMessage(error)}`));
7111
- }, config.debounceMs);
7112
- });
7113
6850
  session.on("post-turn-check", () => dependencies.usage.nudgeSession(liveTranscriptPath));
7114
6851
  session.on("state-change", ({ from, to, event, detail }) => {
7115
6852
  if (to === STATES.IDLE || to === STATES.COMPLETE) {
@@ -7209,9 +6946,14 @@ function createSessionEventWiring(dependencies) {
7209
6946
  timestamp: Date.now()
7210
6947
  });
7211
6948
  });
7212
- relay("wakeup-change", "session-wakeup");
7213
6949
  session.on("wakeup-change", persistWasWorking);
7214
- relay("prompt-kind-change", "session-prompt");
6950
+ session.on("prompt-kind-change", ({ pendingPromptKind: nextKind }) => dependencies.broadcastControl({
6951
+ type: "session-prompt",
6952
+ id: session.id,
6953
+ session: session.name,
6954
+ pendingPromptKind: nextKind,
6955
+ timestamp: Date.now()
6956
+ }));
7215
6957
  relay("unseen-outcome-change", "session-unseen");
7216
6958
  relay("conversation-change", "session-conversation");
7217
6959
  session.on("merge-status", ({ mergeStatus, reason, parked }) => {
@@ -7374,10 +7116,7 @@ function runAutoResume(sessions, config, spawnGate, logger = console) {
7374
7116
  runs.push(spawnGate.run(() => {
7375
7117
  if (session.state !== STATES.DORMANT) return null;
7376
7118
  return session.start({ resumePrompt });
7377
- }, {
7378
- isStillWanted: () => session.state === STATES.DORMANT,
7379
- onMemoryWait: (isWaiting) => session.setWaitingForMemory(isWaiting)
7380
- }).catch((error) => {
7119
+ }, { isStillWanted: () => session.state === STATES.DORMANT }).catch((error) => {
7381
7120
  logger.warn(`[boot] auto-resume failed for ${session.name}: ${errorMessage(error)}`);
7382
7121
  }));
7383
7122
  }
@@ -7437,7 +7176,8 @@ function reconcileSessionWorktrees({ projects, sessions, gitWorkspaceSync, integ
7437
7176
  gitWorkspaceSync.removeWorktreeByPath({
7438
7177
  projectPath: project.path,
7439
7178
  cwd: worktree.cwd,
7440
- branch: worktree.branch
7179
+ branch: worktree.branch,
7180
+ cause: "boot-reconcile"
7441
7181
  });
7442
7182
  }
7443
7183
  }
@@ -7618,6 +7358,8 @@ function createSessionRegistry(dependencies) {
7618
7358
  //#endregion
7619
7359
  //#region shared/team-review-markdown.ts
7620
7360
  var AUTOMATED_REVIEW_NOTE = "> [!NOTE]\n> Automated review. Not written by a human.";
7361
+ var CREDITED_REVIEW_NOTE = "> [!NOTE]\n> Automated review by [Glimmervoid](https://glimmervoid.dev). Not written by a human.";
7362
+ var HAND_APPROVED_REVIEW_NOTE = "> [!NOTE]\n> Automated review by [Glimmervoid](https://glimmervoid.dev), not written by a human. Approved by hand after checking it.";
7621
7363
  var SEVERITY_ALTERNATION = FindingSeverity.options.join("|");
7622
7364
  var FINDING_HEADER_SOURCE = String.raw`\*\*(?:\[([^\]\n]+)\]\s+)?(${SEVERITY_ALTERNATION})\*\*`;
7623
7365
  function findingHeader$1(reviewer, severity) {
@@ -7636,7 +7378,12 @@ function parseLeadingFindingHeader(body) {
7636
7378
  }
7637
7379
  function withoutAutomatedNote(body) {
7638
7380
  const trimmed = body.trimStart();
7639
- return trimmed.startsWith("> [!NOTE]\n> Automated review. Not written by a human.") ? trimmed.slice(53).trim() : body.trim();
7381
+ const leadingNote = [
7382
+ AUTOMATED_REVIEW_NOTE,
7383
+ CREDITED_REVIEW_NOTE,
7384
+ HAND_APPROVED_REVIEW_NOTE
7385
+ ].find((note) => trimmed.startsWith(note));
7386
+ return leadingNote ? trimmed.slice(leadingNote.length).trim() : body.trim();
7640
7387
  }
7641
7388
  var FOLDED_FINDING_MARKER = "(line not in the diff)";
7642
7389
  var FINDING_BULLET_SOURCE = String.raw`- ${FINDING_HEADER_SOURCE}`;
@@ -9502,7 +9249,7 @@ function readTeamReviewSettings(config) {
9502
9249
  const teams = readGithubTeams(config);
9503
9250
  const configuredOrg = typeof block?.org === "string" ? block.org.trim() : "";
9504
9251
  return {
9505
- enabled: block?.enabled === true,
9252
+ enabled: block?.enabled !== false,
9506
9253
  org: configuredOrg || teams[0]?.org || "",
9507
9254
  teams,
9508
9255
  reReviewAfterHours: positiveNumberOr(block?.reReviewAfterHours, 24),
@@ -9663,10 +9410,6 @@ function isDocsOrTests(filePath) {
9663
9410
  return /(?:^|\/)(?:test|tests|__tests__|spec)\//.test(normalizedPath) || /(?:\.test\.|\.spec\.|_test\.)/.test(fileName);
9664
9411
  }
9665
9412
  function triagePr(detail) {
9666
- if (detail.isCrossRepository) return {
9667
- tier: "skip",
9668
- reasons: ["fork"]
9669
- };
9670
9413
  const listedLines = detail.files.reduce((total, file) => total + file.additions + file.deletions, 0);
9671
9414
  if (detail.files.length >= 100 || listedLines < detail.additions + detail.deletions) return {
9672
9415
  tier: "full",
@@ -9709,20 +9452,26 @@ function isPostableStatus(draft, event) {
9709
9452
  function canPost(draft, clickedHead, currentHead, event) {
9710
9453
  return isPostableStatus(draft, event) && draft.reviewedHead === clickedHead && draft.reviewedHead === currentHead;
9711
9454
  }
9712
- var HAND_APPROVAL_LINE = "Approved by hand after checking the automated review.";
9455
+ var POSTED_NOTE_BY_EVENT = {
9456
+ APPROVE: HAND_APPROVED_REVIEW_NOTE,
9457
+ COMMENT: CREDITED_REVIEW_NOTE,
9458
+ REQUEST_CHANGES: CREDITED_REVIEW_NOTE
9459
+ };
9713
9460
  function postedReviewBody(event, body) {
9714
- if (event !== "APPROVE" || body.trimEnd().endsWith(HAND_APPROVAL_LINE)) return body;
9461
+ const postedNote = POSTED_NOTE_BY_EVENT[event];
9462
+ if (body.includes(postedNote)) return body;
9715
9463
  const noteStart = body.indexOf(AUTOMATED_REVIEW_NOTE);
9464
+ if (noteStart === -1 && event !== "APPROVE") return body;
9716
9465
  return [
9717
9466
  noteStart === -1 ? body.trim() : body.slice(0, noteStart).trim(),
9718
- AUTOMATED_REVIEW_NOTE,
9719
- noteStart === -1 ? "" : body.slice(noteStart + AUTOMATED_REVIEW_NOTE.length).trim(),
9720
- HAND_APPROVAL_LINE
9467
+ postedNote,
9468
+ noteStart === -1 ? "" : body.slice(noteStart + AUTOMATED_REVIEW_NOTE.length).trim()
9721
9469
  ].filter(Boolean).join("\n\n");
9722
9470
  }
9723
9471
  function eventForAction(action) {
9724
9472
  if (action === "approve" || action === "approve-only") return "APPROVE";
9725
9473
  if (action === "comment") return "COMMENT";
9474
+ if (action === "request-changes") return "REQUEST_CHANGES";
9726
9475
  return null;
9727
9476
  }
9728
9477
  var HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
@@ -9732,6 +9481,16 @@ function diffHeaderPath(line, sidePrefix) {
9732
9481
  if (unquoted === "/dev/null") return null;
9733
9482
  return unquoted.startsWith(sidePrefix) ? unquoted.slice(sidePrefix.length) : unquoted;
9734
9483
  }
9484
+ var MAX_EXCERPT_LINE_CHARS = 400;
9485
+ function addDiffRow(file, row) {
9486
+ const rowIndex = file.rows.length;
9487
+ file.rows.push({
9488
+ ...row,
9489
+ text: row.text.replace(/\r$/, "").slice(0, MAX_EXCERPT_LINE_CHARS)
9490
+ });
9491
+ if (row.oldLine !== null) file.left.set(row.oldLine, rowIndex);
9492
+ if (row.newLine !== null) file.right.set(row.newLine, rowIndex);
9493
+ }
9735
9494
  function commentableLines(diffText) {
9736
9495
  const linesByPath = /* @__PURE__ */ new Map();
9737
9496
  let oldPath = null;
@@ -9740,25 +9499,44 @@ function commentableLines(diffText) {
9740
9499
  let newLine = 0;
9741
9500
  let oldRemaining = 0;
9742
9501
  let newRemaining = 0;
9502
+ let hunk = 0;
9743
9503
  for (const line of diffText.split("\n")) {
9744
9504
  if (oldRemaining > 0 || newRemaining > 0) {
9745
9505
  if (!currentFile) continue;
9746
9506
  const marker = line.charAt(0);
9507
+ const text = line.slice(1);
9747
9508
  if (marker === "\\") continue;
9748
9509
  if (marker === "-") {
9749
- currentFile.left.add(oldLine);
9510
+ addDiffRow(currentFile, {
9511
+ oldLine,
9512
+ newLine: null,
9513
+ kind: "del",
9514
+ text,
9515
+ hunk
9516
+ });
9750
9517
  oldLine += 1;
9751
9518
  oldRemaining -= 1;
9752
9519
  continue;
9753
9520
  }
9754
9521
  if (marker === "+") {
9755
- currentFile.right.add(newLine);
9522
+ addDiffRow(currentFile, {
9523
+ oldLine: null,
9524
+ newLine,
9525
+ kind: "add",
9526
+ text,
9527
+ hunk
9528
+ });
9756
9529
  newLine += 1;
9757
9530
  newRemaining -= 1;
9758
9531
  continue;
9759
9532
  }
9760
- currentFile.left.add(oldLine);
9761
- currentFile.right.add(newLine);
9533
+ addDiffRow(currentFile, {
9534
+ oldLine,
9535
+ newLine,
9536
+ kind: "context",
9537
+ text,
9538
+ hunk
9539
+ });
9762
9540
  oldLine += 1;
9763
9541
  newLine += 1;
9764
9542
  oldRemaining -= 1;
@@ -9777,21 +9555,41 @@ function commentableLines(diffText) {
9777
9555
  if (line.startsWith("+++ ")) {
9778
9556
  const filePath = diffHeaderPath(line, "b/") ?? oldPath;
9779
9557
  currentFile = filePath === null ? null : linesByPath.get(filePath) ?? {
9780
- left: /* @__PURE__ */ new Set(),
9781
- right: /* @__PURE__ */ new Set()
9558
+ left: /* @__PURE__ */ new Map(),
9559
+ right: /* @__PURE__ */ new Map(),
9560
+ rows: []
9782
9561
  };
9783
9562
  if (filePath !== null && currentFile) linesByPath.set(filePath, currentFile);
9784
9563
  continue;
9785
9564
  }
9786
- const hunk = HUNK_HEADER.exec(line);
9787
- if (!hunk || !currentFile) continue;
9788
- oldLine = Number(hunk[1]);
9789
- oldRemaining = hunk[2] === void 0 ? 1 : Number(hunk[2]);
9790
- newLine = Number(hunk[3]);
9791
- newRemaining = hunk[4] === void 0 ? 1 : Number(hunk[4]);
9565
+ const hunkHeader = HUNK_HEADER.exec(line);
9566
+ if (!hunkHeader || !currentFile) continue;
9567
+ hunk += 1;
9568
+ oldLine = Number(hunkHeader[1]);
9569
+ oldRemaining = hunkHeader[2] === void 0 ? 1 : Number(hunkHeader[2]);
9570
+ newLine = Number(hunkHeader[3]);
9571
+ newRemaining = hunkHeader[4] === void 0 ? 1 : Number(hunkHeader[4]);
9792
9572
  }
9793
9573
  return linesByPath;
9794
9574
  }
9575
+ function diffExcerpt(commentable, filePath, side, line, radius = 3) {
9576
+ const fileLines = commentable.get(filePath);
9577
+ const rowIndex = fileLines ? (side === "LEFT" ? fileLines.left : fileLines.right).get(line) : void 0;
9578
+ if (!fileLines || rowIndex === void 0) return null;
9579
+ const citedHunk = fileLines.rows[rowIndex]?.hunk;
9580
+ return fileLines.rows.slice(Math.max(0, rowIndex - radius), rowIndex + radius + 1).filter((row) => row.hunk === citedHunk).map((row) => ({
9581
+ line: side === "LEFT" ? row.oldLine : row.newLine,
9582
+ kind: row.kind,
9583
+ text: row.text
9584
+ }));
9585
+ }
9586
+ function withExcerpt(comment, commentable) {
9587
+ const excerpt = commentable ? diffExcerpt(commentable, comment.path, comment.side, comment.line) : null;
9588
+ return excerpt ? {
9589
+ ...comment,
9590
+ excerpt
9591
+ } : comment;
9592
+ }
9795
9593
  function isLineCommentable(commentable, filePath, side, line) {
9796
9594
  const fileLines = commentable.get(filePath);
9797
9595
  if (!fileLines) return false;
@@ -9805,7 +9603,7 @@ function isSettledAtHead(entry, head) {
9805
9603
  if (entry?.draft?.status === "discarded") return true;
9806
9604
  if (!entry || entry.reviewedHead !== head) return false;
9807
9605
  if (entry.draft?.status === "error") return entry.reviewAttempts >= MAX_REVIEW_ATTEMPTS;
9808
- return entry.draft !== null || entry.skipReason !== null;
9606
+ return entry.draft !== null;
9809
9607
  }
9810
9608
  function shouldAutoReview(storedEntry, currentHead, nowMs, reReviewAfterMs) {
9811
9609
  if (!storedEntry) return true;
@@ -9965,24 +9763,7 @@ function presentedDraft(entry, draft) {
9965
9763
  function draftsNewestFirst(state) {
9966
9764
  return Object.values(state).filter((entry) => entry.draft !== null).sort((left, right) => right.updatedAt - left.updatedAt).flatMap((entry) => entry.draft ? [presentedDraft(entry, entry.draft)] : []);
9967
9765
  }
9968
- var QUEUED_REVIEW_FIELDS = Object.keys(QueuedReview.shape);
9969
- function isSameQueuedReview(left, right) {
9970
- return JSON.stringify(left ?? null, QUEUED_REVIEW_FIELDS) === JSON.stringify(right, QUEUED_REVIEW_FIELDS);
9971
- }
9972
- function handReviewRows(state) {
9973
- return Object.values(state).flatMap((entry) => {
9974
- if (!entry.handReview || !entry.liveHead || entry.inFlight || entry.skipReason !== "fork" || entry.reviewedHead !== entry.liveHead) return [];
9975
- if (hasViewerReviewedAt(entry.githubReviews, entry.liveHead)) return [];
9976
- if (hasStandingViewerApproval({
9977
- githubReviews: entry.githubReviews,
9978
- reviewDecision: entry.reviewDecision,
9979
- liveHead: entry.liveHead,
9980
- requeuedHead: entry.requeuedHead
9981
- })) return [];
9982
- return [entry.handReview];
9983
- });
9984
- }
9985
- function teamReviewStatus({ ts, configured, reason = null, drafts = [], inFlight = [], queued = [], handReview = [], team }) {
9766
+ function teamReviewStatus({ ts, configured, reason = null, drafts = [], inFlight = [], queued = [], team }) {
9986
9767
  return {
9987
9768
  type: "team-review-status",
9988
9769
  ts,
@@ -9991,7 +9772,6 @@ function teamReviewStatus({ ts, configured, reason = null, drafts = [], inFlight
9991
9772
  drafts,
9992
9773
  inFlight,
9993
9774
  queued,
9994
- handReview,
9995
9775
  team
9996
9776
  };
9997
9777
  }
@@ -10439,7 +10219,7 @@ function readyDraft({ candidate, tier, reasons, result, reviewedBase, commentabl
10439
10219
  summary: result.summary,
10440
10220
  ...assessment ? { assessment } : {},
10441
10221
  body: rendered.body,
10442
- comments: rendered.comments,
10222
+ comments: rendered.comments.map((comment) => withExcerpt(comment, commentable)),
10443
10223
  status: "ready"
10444
10224
  };
10445
10225
  }
@@ -12940,8 +12720,9 @@ function createIssuesWiring({ config, gitWorkspace, broadcast, hasSession, store
12940
12720
  }
12941
12721
  const runner = createLaneRunner({
12942
12722
  tag: ISSUES_LANE_ID,
12943
- gate: () => ({ start: true }),
12723
+ gate: () => ({ start: config.issues?.enabled === true }),
12944
12724
  cfgKey: () => JSON.stringify({
12725
+ enabled: config.issues?.enabled === true,
12945
12726
  projects: config.projects.map((project) => ({
12946
12727
  id: project.id,
12947
12728
  path: project.path,
@@ -17088,6 +16869,76 @@ function createSessionHookLifecycle(options) {
17088
16869
  };
17089
16870
  }
17090
16871
  //#endregion
16872
+ //#region server/git/git-exec.ts
16873
+ var DEFAULT_COMMAND_TIMEOUT_MS = 2e4;
16874
+ function outputText(value) {
16875
+ if (typeof value === "string") return value;
16876
+ if (Buffer.isBuffer(value)) return value.toString("utf8");
16877
+ return value == null ? "" : String(value);
16878
+ }
16879
+ function childEnv(env, replaceEnv) {
16880
+ if (replaceEnv) return { env: replaceEnv };
16881
+ if (env) return { env: {
16882
+ ...process.env,
16883
+ ...env
16884
+ } };
16885
+ return {};
16886
+ }
16887
+ async function runCommand(file, args, { cwd, timeoutMs = DEFAULT_COMMAND_TIMEOUT_MS, maxBuffer, env, replaceEnv, signal, input, trim = true, keepStdoutOnFailure = false, preferStderr = false, execFileFn = execFileAsync } = {}) {
16888
+ const execOptions = {
16889
+ encoding: "utf8",
16890
+ timeout: timeoutMs,
16891
+ ...cwd === void 0 ? {} : { cwd },
16892
+ ...maxBuffer === void 0 ? {} : { maxBuffer },
16893
+ ...signal ? { signal } : {},
16894
+ ...input === void 0 ? {} : { input },
16895
+ ...childEnv(env, replaceEnv)
16896
+ };
16897
+ try {
16898
+ const { stdout, stderr } = await execFileFn(file, args, execOptions);
16899
+ const out = outputText(stdout);
16900
+ return {
16901
+ ok: true,
16902
+ out: trim ? out.trim() : out,
16903
+ err: "",
16904
+ stderr: outputText(stderr)
16905
+ };
16906
+ } catch (error) {
16907
+ const failure = error ?? {};
16908
+ const stdout = outputText(failure.stdout);
16909
+ const stderr = outputText(failure.stderr);
16910
+ const message = errorMessage(error);
16911
+ return {
16912
+ ok: false,
16913
+ out: keepStdoutOnFailure ? trim ? stdout.trim() : stdout : "",
16914
+ err: preferStderr ? stderr || message : message,
16915
+ stderr,
16916
+ error,
16917
+ timedOut: failure.killed === true && Boolean(failure.signal)
16918
+ };
16919
+ }
16920
+ }
16921
+ function runGit(args, { gitPath = "git", env, replaceEnv, ...rest } = {}) {
16922
+ const baseEnv = replaceEnv ?? (env ? {
16923
+ ...process.env,
16924
+ ...env
16925
+ } : process.env);
16926
+ return runCommand(gitPath, args, {
16927
+ ...rest,
16928
+ replaceEnv: {
16929
+ ...baseEnv,
16930
+ GIT_TERMINAL_PROMPT: "0"
16931
+ }
16932
+ });
16933
+ }
16934
+ function gitCommandFailure(args, command, timeoutMs = DEFAULT_COMMAND_TIMEOUT_MS) {
16935
+ if (!command.timedOut) return command.error;
16936
+ return new Error(`git ${args.join(" ")} timed out after ${timeoutMs}ms`, { cause: command.error });
16937
+ }
16938
+ function runGh(args, options = {}) {
16939
+ return runCommand("gh", args, options);
16940
+ }
16941
+ //#endregion
17091
16942
  //#region shared/coalesce-timer.ts
17092
16943
  function createCoalescedTimer({ mode, delayMs, run, setTimeoutFn = DEFAULT_TIMER_FNS.setTimeoutFn, clearTimeoutFn = DEFAULT_TIMER_FNS.clearTimeoutFn, unref = true }) {
17093
16944
  let timer = null;
@@ -17724,7 +17575,8 @@ function createSessionWorkspaceMembers({ plan, owner, sessionName, shareList, gi
17724
17575
  projectPath: member.repoPath,
17725
17576
  cwd: member.dir,
17726
17577
  shareList,
17727
- discardWork
17578
+ discardWork,
17579
+ cause: "session"
17728
17580
  });
17729
17581
  if (removed.ok) return null;
17730
17582
  return {
@@ -18248,7 +18100,8 @@ function createSessionWorktreeLifecycle({ id, projectPath, integrationBranch = n
18248
18100
  if (gitWorkspace && lifecycleState.workspace) try {
18249
18101
  await gitWorkspace.discard({
18250
18102
  projectPath: currentProjectPath(),
18251
- workspace: lifecycleState.workspace
18103
+ workspace: lifecycleState.workspace,
18104
+ cause: "session"
18252
18105
  });
18253
18106
  } catch {}
18254
18107
  clearWorktreeState("none");
@@ -19550,7 +19403,6 @@ var Session = class extends EventEmitter {
19550
19403
  _lastSignal;
19551
19404
  _compaction;
19552
19405
  _pendingPromptKind;
19553
- _isWaitingForMemory;
19554
19406
  _pendingPromptToolName;
19555
19407
  _pendingAttentionNotes;
19556
19408
  _titleQuiet;
@@ -19684,7 +19536,6 @@ var Session = class extends EventEmitter {
19684
19536
  this._lastSignal = null;
19685
19537
  this._compaction = createCompactionTracking();
19686
19538
  this._pendingPromptKind = null;
19687
- this._isWaitingForMemory = false;
19688
19539
  this._pendingPromptToolName = null;
19689
19540
  this._pendingAttentionNotes = [];
19690
19541
  this._titleQuiet = false;
@@ -20068,16 +19919,10 @@ var Session = class extends EventEmitter {
20068
19919
  });
20069
19920
  if (this._compaction.isCompacting !== wasCompacting) this._emitSessionDetail();
20070
19921
  }
20071
- setWaitingForMemory(isWaiting) {
20072
- if (isWaiting === this._isWaitingForMemory) return;
20073
- this._isWaitingForMemory = isWaiting;
20074
- this._emitSessionDetail();
20075
- }
20076
19922
  _emitSessionDetail() {
20077
19923
  this.emit("prompt-kind-change", {
20078
19924
  pendingPromptKind: this._pendingPromptKind,
20079
- isCompacting: this._compaction.isCompacting,
20080
- isWaitingForMemory: this._isWaitingForMemory
19925
+ isCompacting: this._compaction.isCompacting
20081
19926
  });
20082
19927
  }
20083
19928
  _finishCompaction() {
@@ -20463,13 +20308,10 @@ var Session = class extends EventEmitter {
20463
20308
  resumeSessionId: this._resumeSessionId,
20464
20309
  activeAgents: active,
20465
20310
  awaitingBackgroundTasks: this.backgroundTracking.awaitingBackgroundTasks(),
20466
- pendingWakeup: this.backgroundTracking.pendingWakeup(),
20467
20311
  pendingPromptKind: this._pendingPromptKind,
20468
20312
  conversationApproval: this._conversationApproval,
20469
20313
  hasConversationBridge: this._codexBridge !== null,
20470
20314
  isConversationViewOn: this._isConversationViewEnabled(),
20471
- isCompacting: this._compaction.isCompacting,
20472
- isWaitingForMemory: this._isWaitingForMemory,
20473
20315
  hasPlan: this._planReviewPort?.hasPlan(this.id) === true,
20474
20316
  mergeStatus: this.mergeStatus,
20475
20317
  mergeReason: this.mergeReason,
@@ -20572,7 +20414,6 @@ var Session = class extends EventEmitter {
20572
20414
  }
20573
20415
  async _startBody({ fresh = false, resumePrompt = null } = {}) {
20574
20416
  if (this._destroyed) return;
20575
- this.setWaitingForMemory(false);
20576
20417
  if (this.ptyProcess) {
20577
20418
  console.warn(`[session:${this.name}] start() called while PTY exists - killing previous PTY first`);
20578
20419
  const oldPid = this.ptyProcess.pid;
@@ -21206,7 +21047,7 @@ var Session = class extends EventEmitter {
21206
21047
  const remoteExitReason = await pendingRemoteExitReason;
21207
21048
  if (remoteExitReason && this._isExitHeadedForFailure(event)) this.failureReason = remoteExitReason;
21208
21049
  }
21209
- this.transition(event, detail);
21050
+ const isUnsignalledCleanExit = this.transition(event, detail) && event === "process_exit_ok" && !signal;
21210
21051
  if (this._recorder) {
21211
21052
  this._recorder.writeFooter("pty_exit", exitCode);
21212
21053
  this._recorder.finishRecordingFile();
@@ -21214,10 +21055,12 @@ var Session = class extends EventEmitter {
21214
21055
  try {
21215
21056
  await this._settleWorktreeOnExit();
21216
21057
  } catch {}
21058
+ const exitedOnItsOwn = isUnsignalledCleanExit && this.state === STATES.DONE && !this._teardownPending() && !this._destroyed;
21217
21059
  this.emit("exit", {
21218
21060
  exitCode,
21219
21061
  signal,
21220
- reason
21062
+ reason,
21063
+ exitedOnItsOwn
21221
21064
  });
21222
21065
  }
21223
21066
  getScreenSnapshot() {
@@ -23484,7 +23327,7 @@ function createPrGh(cwd, commandRunner = run) {
23484
23327
  err: "invalid commit id",
23485
23328
  reviewId: null
23486
23329
  };
23487
- if (event !== "APPROVE" && event !== "COMMENT") return {
23330
+ if (!PostedReviewEvent.safeParse(event).success) return {
23488
23331
  ok: false,
23489
23332
  err: "invalid review event",
23490
23333
  reviewId: null
@@ -24087,7 +23930,6 @@ function createTeamReviewPoller(deps) {
24087
23930
  team: teamProfile,
24088
23931
  drafts: draftsNewestFirst(state),
24089
23932
  inFlight: inFlightReviews(),
24090
- handReview: handReviewRows(state),
24091
23933
  queued: waitingForSlot.filter((candidate) => !state[candidate.key]?.inFlight)
24092
23934
  }),
24093
23935
  error: pollingError,
@@ -24122,7 +23964,6 @@ function createTeamReviewPoller(deps) {
24122
23964
  draft: null,
24123
23965
  reviewedHead: null,
24124
23966
  inFlight: false,
24125
- skipReason: null,
24126
23967
  reviewAttempts: 0,
24127
23968
  updatedAt: now()
24128
23969
  };
@@ -24167,7 +24008,6 @@ function createTeamReviewPoller(deps) {
24167
24008
  ...wasRequeuedAtReviewedHead ? { requeuedHead: draft.reviewedHead } : {}
24168
24009
  };
24169
24010
  entry.reviewedHead = draft.reviewedHead;
24170
- entry.skipReason = null;
24171
24011
  entry.updatedAt = now();
24172
24012
  entry.reviewedAt = entry.updatedAt;
24173
24013
  entry.draft.reviewedAt = entry.reviewedAt;
@@ -24184,7 +24024,6 @@ function createTeamReviewPoller(deps) {
24184
24024
  status: "discarded"
24185
24025
  } : notMineDraft(args.candidate, args.detail.headRefOid);
24186
24026
  entry.reviewedHead = entry.draft.reviewedHead;
24187
- entry.skipReason = null;
24188
24027
  await settleNotMine(args.candidate.key, entry);
24189
24028
  if (waitingForSlot.length > 0 && !loop.isStopped()) loop.tick();
24190
24029
  }
@@ -24217,14 +24056,6 @@ function createTeamReviewPoller(deps) {
24217
24056
  entry.reviewedHead = isReviewedByViewer ? head : null;
24218
24057
  isDirty = true;
24219
24058
  }
24220
- if (entry.handReview && (entry.skipReason !== "fork" || entry.reviewedHead !== head)) {
24221
- delete entry.handReview;
24222
- isDirty = true;
24223
- }
24224
- if (entry.skipReason === "fork" && entry.reviewedHead === head && entry.draft?.status !== "discarded" && !isSameQueuedReview(entry.handReview, candidate)) {
24225
- entry.handReview = { ...candidate };
24226
- isDirty = true;
24227
- }
24228
24059
  if (entry.draft) {
24229
24060
  const priority = {
24230
24061
  requestSource: candidate.requestSource,
@@ -24482,18 +24313,7 @@ function createTeamReviewPoller(deps) {
24482
24313
  continue;
24483
24314
  }
24484
24315
  entry.updatedAt = now();
24485
- delete entry.handReview;
24486
24316
  isDirty = true;
24487
- if (triage.tier === "skip") {
24488
- if (resume) await discardResumable(resume);
24489
- entry.reviewedHead = detail.headRefOid;
24490
- entry.liveHead = detail.headRefOid;
24491
- entry.skipReason = triage.reasons.join(", ") || "skipped";
24492
- if (triage.reasons.includes("fork")) entry.handReview = { ...candidate };
24493
- delete entry.requeuedHead;
24494
- continue;
24495
- }
24496
- entry.skipReason = null;
24497
24317
  const priorReview = priorReviewFor(entry, detail.headRefOid) ?? void 0;
24498
24318
  const earlierReviewKept = earlierReviewToKeep(entry, detail.headRefOid);
24499
24319
  delete entry.priorReview;
@@ -24828,7 +24648,7 @@ function createTeamReviewPoller(deps) {
24828
24648
  };
24829
24649
  return settleNotMine(key, entry);
24830
24650
  }
24831
- const candidate = waitingForSlot.find((queued) => queued.key === key) ?? entry?.handReview ?? entry?.draft ?? void 0;
24651
+ const candidate = waitingForSlot.find((queued) => queued.key === key) ?? entry?.draft ?? void 0;
24832
24652
  if (!candidate) return {
24833
24653
  ok: false,
24834
24654
  error: "That pull request is no longer in the review queue"
@@ -24851,7 +24671,6 @@ function createTeamReviewPoller(deps) {
24851
24671
  const notMineEntry = entryFor(key);
24852
24672
  notMineEntry.draft = notMineDraft(candidate, head);
24853
24673
  notMineEntry.reviewedHead = head;
24854
- delete notMineEntry.handReview;
24855
24674
  return settleNotMine(key, notMineEntry);
24856
24675
  }
24857
24676
  async function settleNotMine(key, entry) {
@@ -24883,10 +24702,6 @@ function createTeamReviewPoller(deps) {
24883
24702
  key,
24884
24703
  status: "running"
24885
24704
  };
24886
- if (entry.skipReason && entry.reviewedHead !== null && entry.reviewedHead === entry.liveHead) return {
24887
- ok: false,
24888
- error: `team review skips this pull request: ${entry.skipReason}`
24889
- };
24890
24705
  if (entry.draft && !isThreadPlaceholderDraft(entry.draft)) {
24891
24706
  if (!await requeue(key, entry.draft.reviewedHead)) return {
24892
24707
  ok: false,
@@ -25599,6 +25414,7 @@ function createGitWorkspace(opts = {}) {
25599
25414
  const rerereEnabled = opts.rerere !== false;
25600
25415
  const glimmervoidHome = opts.glimmervoidHome ?? glimmervoidHomeDir$1();
25601
25416
  const glimmervoidHomes = comparableGlimmervoidHomes(glimmervoidHome);
25417
+ const removalGuard = resolveRemovalGuard(opts.removalGuard, glimmervoidHome);
25602
25418
  const engineQueue = createSerialQueue();
25603
25419
  const isolatedCall = (body, args) => () => isolationContext.run({
25604
25420
  ...opts.isolation,
@@ -25674,6 +25490,54 @@ function createGitWorkspace(opts = {}) {
25674
25490
  if (await directoryExists$1(ownedPath)) return;
25675
25491
  await opts.ownedPaths?.forget(ownedPath);
25676
25492
  }
25493
+ async function admitRemoval(target) {
25494
+ const facts = removalOwnershipFacts(target, target.path === null ? null : await opts.ownedPaths?.ownerOf(target.path) ?? null, removalGuard.protectedSessionIds());
25495
+ const lastTouchedMs = isRecencyProbeNeeded(facts) ? await probeLastTouchedMs(target) : null;
25496
+ const verdict = decideRemovalKeep({
25497
+ ...facts,
25498
+ lastTouchedMs,
25499
+ nowMs: removalGuard.now()
25500
+ });
25501
+ if (verdict.keep) {
25502
+ log.warn(`[removals] kept ${describeRemovalTarget(target)} from a ${target.cause} removal because of ${verdict.reason}`);
25503
+ return {
25504
+ admitted: false,
25505
+ reason: verdict.reason
25506
+ };
25507
+ }
25508
+ const record = buildRemovalRecord(target, facts, verdict.checksPassed, removalGuard.now());
25509
+ if (!await appendRemovalRecord(removalGuard.journalPath, record, log)) return {
25510
+ admitted: false,
25511
+ reason: "removal-record-unwritten"
25512
+ };
25513
+ return { admitted: true };
25514
+ }
25515
+ async function probeLastTouchedMs({ projectPath, path: targetPath, branch, tipSha }) {
25516
+ const touchedTimesMs = [];
25517
+ const targetStat = targetPath === null ? "missing" : await statUnlessMissing(targetPath);
25518
+ if (targetStat === null) return null;
25519
+ if (targetPath !== null && targetStat !== "missing") {
25520
+ touchedTimesMs.push(targetStat.mtimeMs);
25521
+ const adminDir = await run(["rev-parse", "--absolute-git-dir"], targetPath);
25522
+ const adminStat = adminDir.ok ? await fsp.stat(adminDir.out).catch(() => null) : null;
25523
+ if (adminStat === null) return null;
25524
+ touchedTimesMs.push(adminStat.mtimeMs);
25525
+ }
25526
+ const commitRef = tipSha ?? (branch ? `refs/heads/${branch}` : null);
25527
+ if (commitRef !== null) {
25528
+ const commitTime = await run([
25529
+ "log",
25530
+ "-1",
25531
+ "--format=%ct",
25532
+ commitRef
25533
+ ], projectPath);
25534
+ const commitTimeMs = commitTime.ok ? parseCommitTimeMs(commitTime.out) : null;
25535
+ if (commitTimeMs === null) return null;
25536
+ touchedTimesMs.push(commitTimeMs);
25537
+ }
25538
+ if (touchedTimesMs.length === 0) return null;
25539
+ return Math.max(...touchedTimesMs);
25540
+ }
25677
25541
  async function recordOwnedFolder({ folderPath, owner }) {
25678
25542
  await opts.ownedPaths?.record({
25679
25543
  path: folderPath,
@@ -25983,11 +25847,7 @@ function createGitWorkspace(opts = {}) {
25983
25847
  isGit: false,
25984
25848
  reason: "no-base-branch"
25985
25849
  };
25986
- await run([
25987
- "branch",
25988
- "-D",
25989
- branch
25990
- ], projectPath);
25850
+ await deleteStaleSessionBranch(projectPath, branch);
25991
25851
  if (worktreeBase) try {
25992
25852
  fs.mkdirSync(worktreeBase, { recursive: true });
25993
25853
  } catch {}
@@ -26197,7 +26057,29 @@ function createGitWorkspace(opts = {}) {
26197
26057
  } catch {}
26198
26058
  }
26199
26059
  }
26060
+ async function deleteStaleSessionBranch(projectPath, branch) {
26061
+ if (!(await readLocalBranchSha(projectPath, branch)).out) return;
26062
+ if (!(await admitRemoval({
26063
+ cause: "session-create",
26064
+ kind: "local-branch",
26065
+ projectPath,
26066
+ path: null,
26067
+ branch
26068
+ })).admitted) return;
26069
+ await run([
26070
+ "branch",
26071
+ "-D",
26072
+ branch
26073
+ ], projectPath);
26074
+ }
26200
26075
  async function tearDownWorktree(projectPath, wt, branch) {
26076
+ if (!(await admitRemoval({
26077
+ cause: "merge",
26078
+ kind: "worktree",
26079
+ projectPath,
26080
+ path: wt,
26081
+ branch: branch ?? null
26082
+ })).admitted) return;
26201
26083
  removeWorktreeLinks(wt);
26202
26084
  await removeWorktreeEntry(runInProject(projectPath), wt, ["--force"]);
26203
26085
  await pruneWorktreeEntries(runInProject(projectPath));
@@ -26671,8 +26553,15 @@ function createGitWorkspace(opts = {}) {
26671
26553
  rerereReplayed
26672
26554
  };
26673
26555
  }
26674
- async function discardBody({ projectPath, workspace }) {
26556
+ async function discardBody({ projectPath, workspace, cause }) {
26675
26557
  if (!workspace || !workspace.isGit) return;
26558
+ if (!(await admitRemoval({
26559
+ cause,
26560
+ kind: "worktree",
26561
+ projectPath,
26562
+ path: workspace.cwd || null,
26563
+ branch: workspace.branch || null
26564
+ })).admitted) return;
26676
26565
  if (workspace.cwd) {
26677
26566
  removeWorktreeLinks(workspace.cwd);
26678
26567
  await removeWorktreeEntry(runInProject(projectPath), workspace.cwd, ["--force"]);
@@ -26846,7 +26735,15 @@ function createGitWorkspace(opts = {}) {
26846
26735
  disableRepoCommands
26847
26736
  });
26848
26737
  }
26849
- async function removeWorktreeByPathBody({ projectPath, cwd, branch, disableHooks, replaceEnv }) {
26738
+ async function removeWorktreeByPathBody({ projectPath, cwd, branch, cause, disableHooks, replaceEnv }) {
26739
+ const admission = await admitRemoval({
26740
+ cause,
26741
+ kind: cwd ? "worktree" : "local-branch",
26742
+ projectPath,
26743
+ path: cwd || null,
26744
+ branch: branch || null
26745
+ });
26746
+ if (!admission.admitted) return keptRemovalResult(admission.reason);
26850
26747
  const isolation = {
26851
26748
  disableHooks,
26852
26749
  replaceEnv
@@ -26896,6 +26793,7 @@ function createGitWorkspace(opts = {}) {
26896
26793
  if (worktreePath === null) return removeWorktreeByPathBody({
26897
26794
  projectPath,
26898
26795
  branch,
26796
+ cause: "session",
26899
26797
  disableHooks,
26900
26798
  replaceEnv
26901
26799
  });
@@ -26909,6 +26807,7 @@ function createGitWorkspace(opts = {}) {
26909
26807
  projectPath,
26910
26808
  cwd: worktreePath,
26911
26809
  branch,
26810
+ cause: "session",
26912
26811
  disableHooks,
26913
26812
  replaceEnv
26914
26813
  });
@@ -26937,12 +26836,20 @@ function createGitWorkspace(opts = {}) {
26937
26836
  }).catch(() => fsp.rmdir(linkPath)).catch(() => {});
26938
26837
  }
26939
26838
  }
26940
- async function removeWorkspaceMemberBody({ projectPath, cwd, shareList, discardWork = false }) {
26839
+ async function removeWorkspaceMemberBody({ projectPath, cwd, branch, shareList, cause, discardWork = false }) {
26941
26840
  if (!cwd) return {
26942
26841
  ok: false,
26943
26842
  out: "",
26944
26843
  err: "a workspace member removal needs a path"
26945
26844
  };
26845
+ const admission = await admitRemoval({
26846
+ cause,
26847
+ kind: "worktree",
26848
+ projectPath,
26849
+ path: cwd,
26850
+ branch: branch || null
26851
+ });
26852
+ if (!admission.admitted) return keptRemovalResult(admission.reason);
26946
26853
  await removeSharedLinks(cwd, shareList);
26947
26854
  const removed = await removeWorktreeEntry(runInProject(projectPath), cwd, discardWork ? ["--force", "--force"] : []);
26948
26855
  if (discardWork && await directoryExists$1(cwd)) await fsp.rm(cwd, {
@@ -27649,7 +27556,16 @@ function createGitWorkspace(opts = {}) {
27649
27556
  async function treeOid({ projectPath, sha, timeoutMs = MERGE_PROBE_TIMEOUT_MS }) {
27650
27557
  return run(["rev-parse", `${sha ?? ""}^{tree}`], projectPath, { timeout: timeoutMs });
27651
27558
  }
27652
- async function deleteRemoteBranchBody({ projectPath, name, tipSha }) {
27559
+ async function deleteRemoteBranchBody({ projectPath, name, tipSha, cause }) {
27560
+ const admission = await admitRemoval({
27561
+ cause,
27562
+ kind: "remote-branch",
27563
+ projectPath,
27564
+ path: null,
27565
+ branch: name ?? null,
27566
+ tipSha: tipSha ?? null
27567
+ });
27568
+ if (!admission.admitted) return keptRemovalResult(admission.reason);
27653
27569
  const branchRef = `refs/heads/${name ?? ""}`;
27654
27570
  return run([
27655
27571
  "push",
@@ -27718,6 +27634,8 @@ function createGitWorkspace(opts = {}) {
27718
27634
  function createGitWorkspaceSync(opts = {}) {
27719
27635
  const glimmervoidHome = opts.glimmervoidHome ?? glimmervoidHomeDir$1();
27720
27636
  const glimmervoidHomes = comparableGlimmervoidHomes(glimmervoidHome);
27637
+ const removalGuard = resolveRemovalGuard(opts.removalGuard, glimmervoidHome);
27638
+ const log = opts.log || console;
27721
27639
  const git = opts.git || ((args, cwd) => execFileSync("git", args, {
27722
27640
  cwd,
27723
27641
  encoding: "utf8",
@@ -27842,7 +27760,61 @@ function createGitWorkspaceSync(opts = {}) {
27842
27760
  }
27843
27761
  return out;
27844
27762
  }
27845
- function removeWorktreeByPath({ projectPath, cwd, branch }) {
27763
+ function admitRemovalSync(target) {
27764
+ const facts = removalOwnershipFacts(target, null, removalGuard.protectedSessionIds());
27765
+ const lastTouchedMs = isRecencyProbeNeeded(facts) ? probeLastTouchedMsSync(target) : null;
27766
+ const verdict = decideRemovalKeep({
27767
+ ...facts,
27768
+ lastTouchedMs,
27769
+ nowMs: removalGuard.now()
27770
+ });
27771
+ if (verdict.keep) {
27772
+ log.warn(`[removals] kept ${describeRemovalTarget(target)} from a ${target.cause} removal because of ${verdict.reason}`);
27773
+ return {
27774
+ admitted: false,
27775
+ reason: verdict.reason
27776
+ };
27777
+ }
27778
+ const record = buildRemovalRecord(target, facts, verdict.checksPassed, removalGuard.now());
27779
+ if (!appendRemovalRecordSync(removalGuard.journalPath, record, log)) return {
27780
+ admitted: false,
27781
+ reason: "removal-record-unwritten"
27782
+ };
27783
+ return { admitted: true };
27784
+ }
27785
+ function probeLastTouchedMsSync({ projectPath, path: targetPath, branch }) {
27786
+ const touchedTimesMs = [];
27787
+ const targetStat = targetPath === null ? "missing" : statUnlessMissingSync(targetPath);
27788
+ if (targetStat === null) return null;
27789
+ if (targetPath !== null && targetStat !== "missing") {
27790
+ touchedTimesMs.push(targetStat.mtimeMs);
27791
+ const adminDir = run(["rev-parse", "--absolute-git-dir"], targetPath);
27792
+ const adminStat = adminDir.ok ? statUnlessMissingSync(adminDir.out) : null;
27793
+ if (adminStat === null || adminStat === "missing") return null;
27794
+ touchedTimesMs.push(adminStat.mtimeMs);
27795
+ }
27796
+ if (branch) {
27797
+ const commitTime = run([
27798
+ "log",
27799
+ "-1",
27800
+ "--format=%ct",
27801
+ `refs/heads/${branch}`
27802
+ ], projectPath);
27803
+ const commitTimeMs = commitTime.ok ? parseCommitTimeMs(commitTime.out) : null;
27804
+ if (commitTimeMs === null) return null;
27805
+ touchedTimesMs.push(commitTimeMs);
27806
+ }
27807
+ if (touchedTimesMs.length === 0) return null;
27808
+ return Math.max(...touchedTimesMs);
27809
+ }
27810
+ function removeWorktreeByPath({ projectPath, cwd, branch, cause }) {
27811
+ if (!admitRemovalSync({
27812
+ cause,
27813
+ kind: cwd ? "worktree" : "local-branch",
27814
+ projectPath,
27815
+ path: cwd || null,
27816
+ branch: branch || null
27817
+ }).admitted) return;
27846
27818
  if (cwd) {
27847
27819
  removeWorktreeLinks(cwd);
27848
27820
  if (isWorktreeHiddenFromGuardedSessions(cwd, glimmervoidHomes)) run([
@@ -27886,6 +27858,60 @@ function createGitWorkspaceSync(opts = {}) {
27886
27858
  removeWorktreeByPath
27887
27859
  };
27888
27860
  }
27861
+ function resolveRemovalGuard(guard, glimmervoidHome) {
27862
+ return {
27863
+ protectedSessionIds: guard?.protectedSessionIds ?? (() => /* @__PURE__ */ new Set()),
27864
+ journalPath: guard?.journalPath ?? removalsJournalPath(glimmervoidHome),
27865
+ now: guard?.now ?? Date.now
27866
+ };
27867
+ }
27868
+ function removalOwnershipFacts(target, ledgerOwner, protectedSessionIds) {
27869
+ return {
27870
+ cause: target.cause,
27871
+ sessionId: target.branch === null ? null : sessionIdFromBranch(target.branch),
27872
+ ledgerOwnerSessionId: sessionIdFromOwner(ledgerOwner),
27873
+ protectedSessionIds
27874
+ };
27875
+ }
27876
+ function buildRemovalRecord(target, facts, checksPassed, nowMs) {
27877
+ return {
27878
+ ts: nowMs,
27879
+ cause: target.cause,
27880
+ kind: target.kind,
27881
+ path: target.path,
27882
+ branch: target.branch,
27883
+ projectPath: target.projectPath,
27884
+ sessionId: facts.sessionId ?? facts.ledgerOwnerSessionId,
27885
+ checksPassed,
27886
+ pid: process.pid
27887
+ };
27888
+ }
27889
+ function describeRemovalTarget(target) {
27890
+ return target.path ?? `${target.kind} ${target.branch ?? ""}`;
27891
+ }
27892
+ function keptRemovalResult(reason) {
27893
+ return {
27894
+ ok: false,
27895
+ out: "",
27896
+ err: `the removal guard kept it: ${reason}`,
27897
+ removalKept: reason
27898
+ };
27899
+ }
27900
+ function parseCommitTimeMs(commitTimeOutput) {
27901
+ const commitSeconds = Number.parseInt(commitTimeOutput, 10);
27902
+ if (!Number.isFinite(commitSeconds)) return null;
27903
+ return commitSeconds * 1e3;
27904
+ }
27905
+ async function statUnlessMissing(targetPath) {
27906
+ return fsp.stat(targetPath).then((stats) => stats, (error) => isMissingFileError(error) ? "missing" : null);
27907
+ }
27908
+ function statUnlessMissingSync(targetPath) {
27909
+ try {
27910
+ return fs.statSync(targetPath);
27911
+ } catch (error) {
27912
+ return isMissingFileError(error) ? "missing" : null;
27913
+ }
27914
+ }
27889
27915
  function removeWorktreeLinks(wtDir) {
27890
27916
  if (!wtDir) return;
27891
27917
  let entries;
@@ -28024,7 +28050,8 @@ var UNMARKED_POST_WARNING = "The review was posted on GitHub, but its draft coul
28024
28050
  var SETTLED_OUTCOME_BY_ACTION = {
28025
28051
  approve: "approve",
28026
28052
  "approve-only": "approve-only",
28027
- comment: "comment"
28053
+ comment: "comment",
28054
+ "request-changes": "request-changes"
28028
28055
  };
28029
28056
  function teamReviewShouldStart(config) {
28030
28057
  const settings = readTeamReviewSettings(config);
@@ -28306,11 +28333,14 @@ function createTeamReviewDispatcher({ store, repoCache, gitWorkspace, spawnSessi
28306
28333
  async function removeCheckout({ projectPath, worktreePath }) {
28307
28334
  const removal = await gitWorkspace.removeWorktreeByPath({
28308
28335
  projectPath,
28309
- cwd: worktreePath
28336
+ cwd: worktreePath,
28337
+ cause: "team-review"
28310
28338
  }).catch((error) => ({
28311
28339
  ok: false,
28312
- err: errorMessage(error)
28340
+ err: errorMessage(error),
28341
+ removalKept: void 0
28313
28342
  }));
28343
+ if (removal.removalKept) return;
28314
28344
  if (removal.ok && !await pathExists(worktreePath)) return;
28315
28345
  log.warn(`[${TEAM_REVIEW_LANE_ID}] worktree removal failed for ${worktreePath}, deleting it directly: ${firstLine$1(removal.err ?? "") || "the directory is still present"}`);
28316
28346
  await deleteCheckout(gitWorkspace, worktreePath, log);
@@ -28882,9 +28912,9 @@ function createTeamReviewActions({ drafts, github, store, log = console, telemet
28882
28912
  reason: "comments-already-posted",
28883
28913
  error: "The inline comments were already posted, so the approval cannot carry them again"
28884
28914
  });
28885
- if (event === "COMMENT" && !body.trim() && comments.length === 0) return refusePost(timing, {
28915
+ if (event !== "APPROVE" && !body.trim() && comments.length === 0) return refusePost(timing, {
28886
28916
  reason: "empty-comment",
28887
- error: "A comment review needs a body or an inline comment"
28917
+ error: "A comment or change request needs a body or an inline comment"
28888
28918
  });
28889
28919
  const liveHeadRead = await readStep(timing, "head-before-post", () => store.requestPrHead(draft.repo, draft.number, "priority"));
28890
28920
  if (liveHeadRead.isRateLimited) return refusePost(timing, {
@@ -30168,11 +30198,14 @@ function createBenchmarkWiring({ benchmarksRoot, runsRoot, isEnabled, broadcast,
30168
30198
  if (projectPath) {
30169
30199
  const removal = await gitWorkspace.removeWorktreeByPath({
30170
30200
  projectPath,
30171
- cwd: worktreePath
30201
+ cwd: worktreePath,
30202
+ cause: "benchmark"
30172
30203
  }).catch((error) => ({
30173
30204
  ok: false,
30174
- err: errorMessage(error)
30205
+ err: errorMessage(error),
30206
+ removalKept: void 0
30175
30207
  }));
30208
+ if (removal.removalKept) return;
30176
30209
  if (!removal.ok) log.warn(`[${BENCHMARK_LANE_ID}] could not remove leftover ${worktreePath}: ${firstLine(removal.err ?? "")}`);
30177
30210
  }
30178
30211
  await removeDirectoryOrWarn(worktreePath);
@@ -31641,8 +31674,24 @@ function createBranchGcPoller(deps) {
31641
31674
  const removed = await callGit(gitWorkspace.removeWorktreeByPath, {
31642
31675
  projectPath,
31643
31676
  cwd: decision.cwd,
31644
- branch: decision.branch
31677
+ branch: decision.branch,
31678
+ cause: "branch-gc"
31645
31679
  });
31680
+ if (removed.removalKept) {
31681
+ summary.worktreesKept.push({
31682
+ cwd: decision.cwd,
31683
+ branch: decision.branch,
31684
+ reason: removed.removalKept
31685
+ });
31686
+ trace({
31687
+ projectPath,
31688
+ name: decision.branch,
31689
+ cwd: decision.cwd,
31690
+ decision: "kept",
31691
+ reason: removed.removalKept
31692
+ });
31693
+ return;
31694
+ }
31646
31695
  if (!removed.ok) {
31647
31696
  noteGitError({
31648
31697
  projectPath,
@@ -31850,8 +31899,22 @@ function createBranchGcPoller(deps) {
31850
31899
  const deleted = await callGit(gitWorkspace.deleteRemoteBranch, {
31851
31900
  projectPath,
31852
31901
  name,
31853
- tipSha
31902
+ tipSha,
31903
+ cause: "branch-gc"
31854
31904
  });
31905
+ if (deleted.removalKept) {
31906
+ summary.kept.push({
31907
+ name,
31908
+ reason: deleted.removalKept
31909
+ });
31910
+ trace({
31911
+ projectPath,
31912
+ name,
31913
+ decision: "kept",
31914
+ reason: deleted.removalKept
31915
+ });
31916
+ continue;
31917
+ }
31855
31918
  if (!deleted.ok) {
31856
31919
  noteGitError({
31857
31920
  projectPath,
@@ -38723,7 +38786,8 @@ function createRadarWiring({ config, investigationSessions, closeSessionDataClie
38723
38786
  if (resultFile) await resultFile.cleanup();
38724
38787
  await Promise.resolve(gitWorkspace.discard({
38725
38788
  projectPath: repoPath,
38726
- workspace
38789
+ workspace,
38790
+ cause: "radar"
38727
38791
  })).catch((e) => console.warn(`[posthog-poller] fix worktree discard failed: ${errorMessage(e)}`));
38728
38792
  }
38729
38793
  }
@@ -45406,6 +45470,131 @@ function createTraceChangeBroadcast({ source, broadcast, setTimeoutFunction = se
45406
45470
  return { stop };
45407
45471
  }
45408
45472
  //#endregion
45473
+ //#region server/session/shown-files-wiring.ts
45474
+ var SHOWN_FOLDER_NAME = "shown";
45475
+ var INDEX_FILENAME = "index.json";
45476
+ var INCOMING_PREFIX = ".incoming-";
45477
+ var ShownIndex = z.array(ShownFile);
45478
+ async function readFirstChunk(filePath) {
45479
+ const handle = await fs$1.open(filePath, "r");
45480
+ try {
45481
+ const buffer = Buffer.alloc(SHOWN_SNIFF_BYTES);
45482
+ const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, 0);
45483
+ return buffer.subarray(0, bytesRead);
45484
+ } finally {
45485
+ await handle.close().catch(() => {});
45486
+ }
45487
+ }
45488
+ async function readSessionIndex(indexPath) {
45489
+ const text = await fs$1.readFile(indexPath, "utf8").catch(() => null);
45490
+ if (text === null) return [];
45491
+ const parsed = ShownIndex.safeParse(parseJsonOrNull(text));
45492
+ return parsed.success ? parsed.data : [];
45493
+ }
45494
+ async function writeSessionIndex(indexPath, files) {
45495
+ const pendingPath = `${indexPath}.${crypto.randomBytes(4).toString("hex")}.tmp`;
45496
+ await fs$1.writeFile(pendingPath, JSON.stringify(files), { mode: 384 });
45497
+ await fs$1.rename(pendingPath, indexPath);
45498
+ }
45499
+ function createShownFilesWiring({ shownRoot, broadcastControl, now = Date.now }) {
45500
+ const filesBySessionId = /* @__PURE__ */ new Map();
45501
+ const indexQueue = createKeyedSerialQueue();
45502
+ function sessionDir(sessionId) {
45503
+ return path.join(shownRoot, sessionId);
45504
+ }
45505
+ async function loadIndexes() {
45506
+ const sessionIds = await fs$1.readdir(shownRoot).catch(() => []);
45507
+ for (const sessionId of sessionIds) {
45508
+ if (!isSafePathSegment(sessionId)) continue;
45509
+ const files = await readSessionIndex(path.join(sessionDir(sessionId), INDEX_FILENAME));
45510
+ if (files.length > 0 && !filesBySessionId.has(sessionId)) filesBySessionId.set(sessionId, files);
45511
+ }
45512
+ }
45513
+ async function reserveIncomingPath(sessionId) {
45514
+ const dir = sessionDir(sessionId);
45515
+ await fs$1.mkdir(dir, {
45516
+ recursive: true,
45517
+ mode: 448
45518
+ });
45519
+ return path.join(dir, `${INCOMING_PREFIX}${crypto.randomBytes(8).toString("hex")}`);
45520
+ }
45521
+ async function recordAdmitted(sessionId, file) {
45522
+ const dir = sessionDir(sessionId);
45523
+ const withNewest = [...filesBySessionId.get(sessionId) ?? await readSessionIndex(path.join(dir, INDEX_FILENAME)), file];
45524
+ const droppedIds = planUploadRetention(withNewest.map((shown) => shown.id), {
45525
+ keep: 50,
45526
+ justWritten: file.id
45527
+ });
45528
+ const kept = filesAfterRetention(withNewest, droppedIds);
45529
+ await writeSessionIndex(path.join(dir, INDEX_FILENAME), kept);
45530
+ filesBySessionId.set(sessionId, kept);
45531
+ await Promise.all(droppedIds.map((droppedId) => fs$1.unlink(path.join(dir, droppedId)).catch(() => {})));
45532
+ }
45533
+ async function admit(session, arrival) {
45534
+ const firstChunk = await readFirstChunk(arrival.incomingPath);
45535
+ const verdict = decideShownKind(firstChunk, arrival.name);
45536
+ if (!verdict.ok) {
45537
+ await fs$1.unlink(arrival.incomingPath).catch(() => {});
45538
+ return {
45539
+ ok: false,
45540
+ status: 415,
45541
+ error: verdict.error
45542
+ };
45543
+ }
45544
+ const shownAt = now();
45545
+ const fileId = buildUploadFilename({
45546
+ now: shownAt,
45547
+ randomSuffix: crypto.randomBytes(4).toString("hex"),
45548
+ extension: verdict.extension
45549
+ });
45550
+ await fs$1.rename(arrival.incomingPath, path.join(sessionDir(session.id), fileId));
45551
+ const file = {
45552
+ id: fileId,
45553
+ name: arrival.name,
45554
+ caption: arrival.caption,
45555
+ kind: verdict.kind,
45556
+ mime: verdict.mime,
45557
+ sizeBytes: arrival.sizeBytes,
45558
+ pageTitle: verdict.kind === "page" ? readPageTitle(firstChunk) : null,
45559
+ shownAt
45560
+ };
45561
+ await indexQueue.run(session.id, () => recordAdmitted(session.id, file));
45562
+ broadcastControl({
45563
+ type: "session-shown",
45564
+ id: session.id,
45565
+ files: [file]
45566
+ });
45567
+ return {
45568
+ ok: true,
45569
+ file
45570
+ };
45571
+ }
45572
+ function findServable(sessionId, fileId) {
45573
+ if (!isSafePathSegment(sessionId) || !isSafePathSegment(fileId)) return null;
45574
+ const file = filesBySessionId.get(sessionId)?.find((shown) => shown.id === fileId);
45575
+ const mime = shownMimeForFilename(fileId);
45576
+ if (!file || !mime) return null;
45577
+ return {
45578
+ path: path.join(sessionDir(sessionId), fileId),
45579
+ mime,
45580
+ sizeBytes: file.sizeBytes
45581
+ };
45582
+ }
45583
+ function snapshotMessage() {
45584
+ return {
45585
+ type: "session-shown-snapshot",
45586
+ sessions: Object.fromEntries(filesBySessionId)
45587
+ };
45588
+ }
45589
+ return {
45590
+ reserveIncomingPath,
45591
+ admit,
45592
+ findServable,
45593
+ snapshotMessage,
45594
+ loaded: loadIndexes()
45595
+ };
45596
+ }
45597
+ //#endregion
45409
45598
  //#region server/uploads-wiring.ts
45410
45599
  var UPLOAD_RETAIN_DAYS = 7;
45411
45600
  var PRUNE_INTERVAL_MS = 864e5;
@@ -45414,8 +45603,8 @@ function createUploadsWiring({ configPath, liveSessionIds, setIntervalFn = DEFAU
45414
45603
  let timer = null;
45415
45604
  async function prune() {
45416
45605
  const retainedSessionIds = liveSessionIds();
45417
- await pruneAgedFiles({
45418
- directory: uploadsRoot,
45606
+ for (const directory of [uploadsRoot, configSiblingPath(configPath, SHOWN_FOLDER_NAME)]) await pruneAgedFiles({
45607
+ directory,
45419
45608
  suffixes: [""],
45420
45609
  retainDays: UPLOAD_RETAIN_DAYS,
45421
45610
  entryMode: "directory",
@@ -46932,14 +47121,21 @@ function createBackendLanes(dependencies) {
46932
47121
  });
46933
47122
  const spawnGate = createSpawnGate({ memoryAdmission });
46934
47123
  const ownedPaths = createOwnedPathsLedger({
46935
- filePath: ownedPathsLedgerPath(),
47124
+ filePath: configSiblingPath(configStore.configPath, "owned-paths.json"),
46936
47125
  log: logger
46937
47126
  });
47127
+ const protectedSessionIds = () => new Set([...config.projects.map((project) => project.id), ...allLiveSessions().map((session) => session.id)].flatMap((sessionId) => [sessionId, sanitizeWorktreeName(sessionId)]));
46938
47128
  const gitWorkspace = createGitWorkspace({
46939
47129
  rerere: config.worktreeRerere !== false,
46940
- ownedPaths
47130
+ ownedPaths,
47131
+ removalGuard: { protectedSessionIds },
47132
+ log: logger
47133
+ });
47134
+ const gitWorkspaceSync = createGitWorkspaceSync({
47135
+ ownedPaths,
47136
+ removalGuard: { protectedSessionIds },
47137
+ log: logger
46941
47138
  });
46942
- const gitWorkspaceSync = createGitWorkspaceSync({ ownedPaths });
46943
47139
  const laneLedger = createLaneLedger({
46944
47140
  ledgerPath: configSiblingPath(configStore.configPath, "usage-lanes.json"),
46945
47141
  retainDays: resolveUsageConfig(config.usage).warehouseRetainDays,
@@ -49887,6 +50083,55 @@ function deriveIssueSessionName(issue) {
49887
50083
  return sanitizeSessionName(`issue-${issueNumber}-${String(issue.title ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "untitled"}`) || `issue-${issueNumber}`;
49888
50084
  }
49889
50085
  //#endregion
50086
+ //#region server/session/core/quick-session-core.ts
50087
+ var COPIED_RUN_SETTINGS = [
50088
+ "baseBranch",
50089
+ "codexBypassHookTrust",
50090
+ "conversationView"
50091
+ ];
50092
+ var AUTO_NAME_SUFFIX = / \(\d+\)$/;
50093
+ function autoNameBase(name) {
50094
+ return name.replace(AUTO_NAME_SUFFIX, "");
50095
+ }
50096
+ function runSettingsOf(project) {
50097
+ const runSettings = {};
50098
+ for (const field of COPIED_RUN_SETTINGS) if (project[field] !== void 0) Object.assign(runSettings, { [field]: project[field] });
50099
+ return runSettings;
50100
+ }
50101
+ function requestFromLiveSession(live, name) {
50102
+ const repos = live.workspaceRepos && live.workspaceRepos.length >= 2 ? [...live.workspaceRepos] : void 0;
50103
+ return {
50104
+ name,
50105
+ path: repos?.[0] ?? live.path,
50106
+ repos,
50107
+ agent: live.agentId,
50108
+ dangerouslySkipPermissions: live.dangerouslySkipPermissions,
50109
+ unguarded: live.isMarkedUnguarded && !live.isGuardrailsReleased
50110
+ };
50111
+ }
50112
+ function requestFromProject(project, live, name) {
50113
+ return {
50114
+ name,
50115
+ path: project.repos?.[0] ?? project.path,
50116
+ repos: project.repos ? [...project.repos] : void 0,
50117
+ agent: project.agent,
50118
+ dangerouslySkipPermissions: project.dangerouslySkipPermissions,
50119
+ unguarded: project.unguarded === true && !live.isGuardrailsReleased,
50120
+ environment: project.environment?.name
50121
+ };
50122
+ }
50123
+ function planQuickSession({ live, project, sessionNames }) {
50124
+ const name = nextSuggestedName(autoNameBase(live.name), sessionNames);
50125
+ if (!project) return {
50126
+ request: requestFromLiveSession(live, name),
50127
+ runSettings: {}
50128
+ };
50129
+ return {
50130
+ request: requestFromProject(project, live, name),
50131
+ runSettings: runSettingsOf(project)
50132
+ };
50133
+ }
50134
+ //#endregion
49890
50135
  //#region server/settings-payload.ts
49891
50136
  function summarizeCustomAgents(declarations) {
49892
50137
  return declarations.map((declaration) => ({
@@ -49900,6 +50145,7 @@ function summarizeCustomAgents(declarations) {
49900
50145
  function buildSettingsPayload({ configStore, rtkInstallStatus = null, telegramDelivery = null, coderEnvironments = null, agentLogins = null, resolveRtk = getRtkPath }) {
49901
50146
  return {
49902
50147
  ...configStore.getSettings(),
50148
+ isExperimentalBuild: configStore.isExperimentalBuild === true,
49903
50149
  rtkAvailable: !!resolveRtk(),
49904
50150
  saneYoloAvailable: !!resolvePackageBin("cc-safety-net", "cc-safety-net"),
49905
50151
  rtkInstall: rtkInstallStatus || { status: "idle" },
@@ -51466,6 +51712,12 @@ function requestValidationErrorReply(msg, message) {
51466
51712
  ok: false,
51467
51713
  error: message
51468
51714
  }),
51715
+ "quick-session": () => ({
51716
+ type: "add-session-result",
51717
+ requestId,
51718
+ ok: false,
51719
+ error: message
51720
+ }),
51469
51721
  "remove-session": () => ({
51470
51722
  type: "remove-session-result",
51471
51723
  requestId,
@@ -51538,7 +51790,7 @@ function parseSinceParam(url) {
51538
51790
  return Number.isFinite(n) ? n : null;
51539
51791
  }
51540
51792
  function registerControlHandlers(controlWss, deps) {
51541
- const { sessions, agentSessions = /* @__PURE__ */ new Map(), config, configStore, broadcastControl, generateProjectId, applyConfigReload, applySettingsReload, requestShutdown, requestRestart, handleClientFocus, buildHealthSnapshot, getUpdateStatus, getUpdateJournal = null, getPostTurnLeftoversNotice = null, recheckPostTurnLeftovers = null, checkNow = null, applyUpdate = null, noteRestartRequested = null, getRadarStatus, posthogReportsDir = null, posthogSetIssueStatus = null, radarArchiveInvestigation = null, getTeamReviewStatus = null, getMyPrsStatus = null, getIssuesStatus = null, githubStore = null, issues = null, teamReview = null, myPrs = null, getBenchmarkStatus = null, benchmarks = null, requestIssue = async () => ({
51793
+ const { sessions, agentSessions = /* @__PURE__ */ new Map(), markChildWorktreeDiscard = null, config, configStore, broadcastControl, generateProjectId, applyConfigReload, applySettingsReload, requestShutdown, requestRestart, handleClientFocus, buildHealthSnapshot, getUpdateStatus, getUpdateJournal = null, getPostTurnLeftoversNotice = null, recheckPostTurnLeftovers = null, checkNow = null, applyUpdate = null, noteRestartRequested = null, getRadarStatus, posthogReportsDir = null, posthogSetIssueStatus = null, radarArchiveInvestigation = null, getTeamReviewStatus = null, getMyPrsStatus = null, getIssuesStatus = null, githubStore = null, issues = null, teamReview = null, myPrs = null, getBenchmarkStatus = null, benchmarks = null, requestIssue = async () => ({
51542
51794
  isRateLimited: false,
51543
51795
  value: {
51544
51796
  ok: false,
@@ -51604,6 +51856,27 @@ function registerControlHandlers(controlWss, deps) {
51604
51856
  }
51605
51857
  return createAddedSessionOnceWorktreeCapable(msg, ws);
51606
51858
  }
51859
+ function handleQuickSession(msg, ws) {
51860
+ const source = findSession(msg);
51861
+ if (!source) {
51862
+ replyTo(ws, msg, "add-session-result", {
51863
+ ok: false,
51864
+ error: "The session to copy no longer exists."
51865
+ });
51866
+ return;
51867
+ }
51868
+ const plan = planQuickSession({
51869
+ live: source,
51870
+ project: config.projects.find((project) => project.id === source.id) ?? null,
51871
+ sessionNames: [...sessions.values(), ...agentSessions.values()].map((session) => session.name)
51872
+ });
51873
+ return handleAddSession({
51874
+ type: "add-session",
51875
+ requestId: msg.requestId,
51876
+ ...plan.request,
51877
+ runSettings: plan.runSettings
51878
+ }, ws);
51879
+ }
51607
51880
  function needsOwnWorktreeCheck(msg) {
51608
51881
  if (msg.repos || msg.runInPlace === true) return false;
51609
51882
  const projectPath = (msg.path || "").trim();
@@ -51635,7 +51908,8 @@ function registerControlHandlers(controlWss, deps) {
51635
51908
  dangerouslySkipPermissions: skipPerms,
51636
51909
  unguarded: msg.unguarded === true,
51637
51910
  requireExistingPath: !repos,
51638
- environment: remote?.environment
51911
+ environment: remote?.environment,
51912
+ runSettings: msg.runSettings
51639
51913
  });
51640
51914
  if (!created.ok) {
51641
51915
  refuseAddSession(msg, ws, { error: created.error });
@@ -51643,7 +51917,8 @@ function registerControlHandlers(controlWss, deps) {
51643
51917
  }
51644
51918
  if (msg.requestId) replyTo(ws, msg, "add-session-result", {
51645
51919
  ok: true,
51646
- error: null
51920
+ error: null,
51921
+ sessionId: created.project.id
51647
51922
  });
51648
51923
  console.log(`[control] Added session via UI: ${name}${skipPerms ? " (skip permissions)" : " (permission prompts)"}${remote ? ` (Coder environment ${remote.environment.name})` : ""}`);
51649
51924
  return created.project;
@@ -51802,37 +52077,64 @@ function registerControlHandlers(controlWss, deps) {
51802
52077
  if (sess.state !== STATES.DORMANT) return;
51803
52078
  sess.start();
51804
52079
  }
52080
+ const lastRemovalRefusalBySessionId = /* @__PURE__ */ new Map();
52081
+ function refuseRemoval(error, keptPaths) {
52082
+ return keptPaths ? {
52083
+ ok: false,
52084
+ error,
52085
+ canDiscardWork: true,
52086
+ keptPaths
52087
+ } : {
52088
+ ok: false,
52089
+ error,
52090
+ canDiscardWork: true
52091
+ };
52092
+ }
52093
+ function keptWorkRefusal(sess, firstKept, otherKeptCount, keptPaths) {
52094
+ const more = otherKeptCount > 0 ? ` and ${otherKeptCount} more` : "";
52095
+ return refuseRemoval(`"${sess.name}" has work not saved in ${firstKept.path} (${firstKept.reason})${more}. Removing it deletes that work.`, keptPaths);
52096
+ }
52097
+ async function findLocalRemovalRefusal(sess, confirmedKeptPaths) {
52098
+ if (!sess.isWorkspace && await sess.hasUnmergedWork()) return refuseRemoval(`"${sess.name}" has uncommitted changes or commits not merged into its base branch. Removing it deletes them.`);
52099
+ const keptPaths = sess.isWorkspace ? await sess.findWorkspaceKeptWork() : [];
52100
+ const [firstUnconfirmedKept, ...otherUnconfirmedKept] = keptPaths.filter((kept) => !confirmedKeptPaths.has(kept.path));
52101
+ if (firstUnconfirmedKept) return keptWorkRefusal(sess, firstUnconfirmedKept, otherUnconfirmedKept.length, keptPaths);
52102
+ if (sess.state === STATES.RUNNING) return refuseRemoval(`"${sess.name}" is working. Removing it stops the agent mid-task.`);
52103
+ return null;
52104
+ }
51805
52105
  async function removeSession(msg) {
51806
52106
  const sess = findSession(msg);
51807
52107
  if (!sess) return {
51808
52108
  ok: false,
51809
52109
  error: "Session not found"
51810
52110
  };
52111
+ const isDiscardingWork = msg.discardWork === true;
52112
+ const isDiscardingWorkspaceWork = sess.isWorkspace && isDiscardingWork;
52113
+ if (isDiscardingWorkspaceWork) sess.killSession();
52114
+ const refusal = await findLocalRemovalRefusal(sess, new Set(isDiscardingWorkspaceWork ? msg.confirmedKeptPaths ?? [] : []));
52115
+ const isRefusalConfirmed = isDiscardingWork && refusal?.error === lastRemovalRefusalBySessionId.get(sess.id);
52116
+ if (refusal && !isRefusalConfirmed) {
52117
+ lastRemovalRefusalBySessionId.set(sess.id, refusal.error);
52118
+ return refusal;
52119
+ }
51811
52120
  if (agentSessions.get(sess.id) === sess) {
52121
+ if (isDiscardingWork) markChildWorktreeDiscard?.(sess.id);
52122
+ lastRemovalRefusalBySessionId.delete(sess.id);
51812
52123
  sess.destroy();
51813
52124
  console.log(`[control] Removed spawned session via UI: ${sess.name}`);
51814
52125
  return { ok: true };
51815
52126
  }
51816
- const isDiscardingWorkspaceWork = sess.isWorkspace && msg.discardWork === true;
51817
- if (isDiscardingWorkspaceWork) sess.killSession();
51818
- const keptPaths = sess.isWorkspace ? await sess.findWorkspaceKeptWork() : [];
51819
- const confirmedKeptPaths = new Set(isDiscardingWorkspaceWork ? msg.confirmedKeptPaths ?? [] : []);
51820
- if (keptPaths.some((kept) => !confirmedKeptPaths.has(kept.path))) return {
51821
- ok: false,
51822
- error: "Workspace members hold work that is not saved",
51823
- canDiscardWork: true,
51824
- keptPaths
51825
- };
51826
52127
  if (config.projects.some((project) => project.id === sess.id && project.environment) && coderEnv) {
51827
52128
  sess.killSession();
51828
52129
  const settlement = await coderEnv.settleRemoval({
51829
52130
  projectId: sess.id,
51830
- discardRemoteWork: msg.discardWork === true
52131
+ discardRemoteWork: isDiscardingWork
51831
52132
  });
52133
+ if (!settlement.ok && settlement.canDiscardRemoteWork) return refuseRemoval(`${settlement.reason}. Removing "${sess.name}" deletes its Coder workspace and that work with it.`);
51832
52134
  if (!settlement.ok) return {
51833
52135
  ok: false,
51834
52136
  error: settlement.reason,
51835
- canDiscardWork: settlement.canDiscardRemoteWork
52137
+ canDiscardWork: false
51836
52138
  };
51837
52139
  }
51838
52140
  const freshConfig = configStore.save((cfg) => {
@@ -51843,6 +52145,7 @@ function registerControlHandlers(controlWss, deps) {
51843
52145
  error: `Could not save the removal of session "${sess.name}"`
51844
52146
  };
51845
52147
  if (isDiscardingWorkspaceWork) sess.confirmWorkspaceWorkDiscard();
52148
+ lastRemovalRefusalBySessionId.delete(sess.id);
51846
52149
  applyConfigReload(freshConfig);
51847
52150
  console.log(`[control] Removed session via UI: ${sess.name}`);
51848
52151
  return { ok: true };
@@ -52353,7 +52656,7 @@ function registerControlHandlers(controlWss, deps) {
52353
52656
  }
52354
52657
  return entries;
52355
52658
  }
52356
- function createProjectSession({ id = generateProjectId(), name, path: projectPath, repos, baseBranch, agent, dangerouslySkipPermissions, unguarded, requireExistingPath, environment }) {
52659
+ function createProjectSession({ id = generateProjectId(), name, path: projectPath, repos, baseBranch, agent, dangerouslySkipPermissions, unguarded, requireExistingPath, environment, runSettings }) {
52357
52660
  for (const [, session] of sessions) {
52358
52661
  if (session.name !== name) continue;
52359
52662
  return {
@@ -52387,6 +52690,7 @@ function registerControlHandlers(controlWss, deps) {
52387
52690
  error: missingFolderError(projectPath)
52388
52691
  };
52389
52692
  const project = {
52693
+ ...runSettings,
52390
52694
  id,
52391
52695
  name,
52392
52696
  path: resolvedPath
@@ -53065,6 +53369,7 @@ function registerControlHandlers(controlWss, deps) {
53065
53369
  }
53066
53370
  const handlers = {
53067
53371
  "add-session": handleAddSession,
53372
+ "quick-session": handleQuickSession,
53068
53373
  "list-conversations": handleListConversations,
53069
53374
  "resume-conversation": handleResumeConversation,
53070
53375
  "remove-session": handleRemoveSession,
@@ -53577,15 +53882,37 @@ function registerControlHandlers(controlWss, deps) {
53577
53882
  });
53578
53883
  });
53579
53884
  });
53580
- return { buildSnapshot };
53885
+ function removeExitedSession(id) {
53886
+ removeSession({
53887
+ type: "remove-session",
53888
+ id
53889
+ }).then((removal) => {
53890
+ if (removal.ok) return;
53891
+ const sess = sessions.get(id);
53892
+ if (removal.canDiscardWork && sess) {
53893
+ broadcastControl({
53894
+ type: "session-close-confirm",
53895
+ id,
53896
+ mergeStatus: sess.mergeStatus
53897
+ });
53898
+ return;
53899
+ }
53900
+ console.warn(`[control] Could not remove session ${id} after its agent quit: ${removal.error}`);
53901
+ });
53902
+ }
53903
+ return {
53904
+ buildSnapshot,
53905
+ removeExitedSession
53906
+ };
53581
53907
  }
53582
53908
  //#endregion
53583
53909
  //#region server/backend/backend-control.ts
53584
53910
  function createBackendControl(dependencies) {
53585
53911
  const { controlWss, sessions, config, configStore, broadcastControl, controlReplayLog, laneAssembly, posthog, teamReview, myPrs, issues, benchmarks, usage, logger } = dependencies;
53586
- registerControlHandlers(controlWss, {
53912
+ const controlHandlers = registerControlHandlers(controlWss, {
53587
53913
  sessions,
53588
53914
  agentSessions: dependencies.agentSessions,
53915
+ markChildWorktreeDiscard: dependencies.markChildWorktreeDiscard,
53589
53916
  config,
53590
53917
  configStore,
53591
53918
  broadcastControl,
@@ -53654,7 +53981,9 @@ function createBackendControl(dependencies) {
53654
53981
  sendLaneSnapshotOnConnect("visions", laneAssembly.currentVisions, false);
53655
53982
  sendLaneSnapshotOnConnect("ingest", laneAssembly.currentIngest, true);
53656
53983
  sendLaneSnapshotOnConnect("session-docs", () => dependencies.sessionDocs, false);
53984
+ sendLaneSnapshotOnConnect("session-shown", () => dependencies.shownFiles, false);
53657
53985
  sendLaneSnapshotOnConnect("coherence-feed", () => dependencies.coherenceFeed, false);
53986
+ return { removeExitedSession: controlHandlers.removeExitedSession };
53658
53987
  }
53659
53988
  //#endregion
53660
53989
  //#region server/core/update-apply-core.ts
@@ -53992,6 +54321,7 @@ function markInterrupted(journal, { reason, now }) {
53992
54321
  //#region server/update-check.ts
53993
54322
  var NPM_REGISTRY_PACKAGE_URL = "https://registry.npmjs.org/glimmervoid";
53994
54323
  var NPM_REGISTRY_LATEST_URL = `${NPM_REGISTRY_PACKAGE_URL}/latest`;
54324
+ var NPM_REGISTRY_EXPERIMENTAL_URL = `${NPM_REGISTRY_PACKAGE_URL}/next`;
53995
54325
  var REGISTRY_REQUEST_HEADERS = {
53996
54326
  Accept: "application/json",
53997
54327
  "User-Agent": "glimmervoid-update-check"
@@ -53999,10 +54329,6 @@ var REGISTRY_REQUEST_HEADERS = {
53999
54329
  var DEFAULT_TIMEOUT_MS = 8e3;
54000
54330
  var GIT_HEAD_TIMEOUT_MS = 3e3;
54001
54331
  var RELEASE_ANCESTRY_TIMEOUT_MS = 3e3;
54002
- var MAIN_CHANNEL_BUDGET_MS = DEFAULT_TIMEOUT_MS;
54003
- var MAIN_FETCH_TIMEOUT_MS = Math.floor(MAIN_CHANNEL_BUDGET_MS / 2);
54004
- var MAIN_REMOTE_TIP_TIMEOUT_MS = Math.floor(MAIN_CHANNEL_BUDGET_MS / 4);
54005
- var MAIN_BEHIND_COUNT_TIMEOUT_MS = Math.floor(MAIN_CHANNEL_BUDGET_MS / 4);
54006
54332
  var STATE_FILE_NAME = "update-check.json";
54007
54333
  var STATE_TTL_MS = 216e5;
54008
54334
  var RELEASE_SOURCE = "npm-registry";
@@ -54130,143 +54456,29 @@ async function resolveInstalledDeprecation({ currentVersion, fetchFn, signal })
54130
54456
  const versionUrl = `${NPM_REGISTRY_PACKAGE_URL}/${encodeURIComponent(currentVersion)}`;
54131
54457
  return parseRegistryDeprecation(await fetchRegistryDocument(versionUrl, fetchFn, signal));
54132
54458
  }
54133
- async function resolveLatestRegistryVersion({ currentVersion, fetchFn, signal }) {
54134
- const [latestDocument, deprecation] = await Promise.all([fetchRegistryDocument(NPM_REGISTRY_LATEST_URL, fetchFn, signal), resolveInstalledDeprecation({
54135
- currentVersion,
54136
- fetchFn,
54137
- signal
54138
- })]);
54139
- const published = parseRegistryLatest(latestDocument);
54459
+ async function resolveLatestRegistryVersion({ currentVersion, updateChannel, fetchFn, signal }) {
54460
+ const [latestDocument, experimentalDocument, deprecation] = await Promise.all([
54461
+ fetchRegistryDocument(NPM_REGISTRY_LATEST_URL, fetchFn, signal),
54462
+ updateChannel === "experimental" ? fetchRegistryDocument(NPM_REGISTRY_EXPERIMENTAL_URL, fetchFn, signal) : null,
54463
+ resolveInstalledDeprecation({
54464
+ currentVersion,
54465
+ fetchFn,
54466
+ signal
54467
+ })
54468
+ ]);
54469
+ const published = pickNewestPublished(parseRegistryLatest(latestDocument), parseRegistryLatest(experimentalDocument));
54140
54470
  if (!published) return {
54141
54471
  version: null,
54142
54472
  sha: null,
54143
- behindCount: null,
54144
54473
  reason: "release-check-failed",
54145
54474
  deprecation
54146
54475
  };
54147
54476
  return {
54148
54477
  ...published,
54149
- behindCount: null,
54150
54478
  reason: null,
54151
54479
  deprecation
54152
54480
  };
54153
54481
  }
54154
- function upstreamBranch(upstream) {
54155
- const slashIndex = upstream.indexOf("/");
54156
- if (slashIndex < 0 || slashIndex === upstream.length - 1) return null;
54157
- return upstream.slice(slashIndex + 1);
54158
- }
54159
- async function fetchMainRemote(packageRoot, remote, runCommand, fetchOrigin, signal) {
54160
- if (remote === "origin" && fetchOrigin) return fetchOrigin({
54161
- projectPath: packageRoot,
54162
- timeoutMs: MAIN_FETCH_TIMEOUT_MS
54163
- });
54164
- return runGitProbe(runCommand, [
54165
- "fetch",
54166
- "--prune",
54167
- remote
54168
- ], {
54169
- cwd: packageRoot,
54170
- timeout: MAIN_FETCH_TIMEOUT_MS,
54171
- signal
54172
- });
54173
- }
54174
- async function resolveLatestMain(installed, packageRoot, runCommand, fetchOrigin, signal) {
54175
- if (installed.flavor !== "clone") return {
54176
- version: null,
54177
- sha: null,
54178
- behindCount: null,
54179
- reason: "main-channel-requires-clone",
54180
- deprecation: null
54181
- };
54182
- if (!installed.installedBranch) return {
54183
- version: null,
54184
- sha: null,
54185
- behindCount: null,
54186
- reason: "no-branch",
54187
- deprecation: null
54188
- };
54189
- if (!installed.upstream) return {
54190
- version: null,
54191
- sha: null,
54192
- behindCount: null,
54193
- reason: "no-upstream",
54194
- deprecation: null
54195
- };
54196
- const remote = parseRemoteFromUpstream(installed.upstream);
54197
- const branch = upstreamBranch(installed.upstream);
54198
- if (!branch) return {
54199
- version: null,
54200
- sha: null,
54201
- behindCount: null,
54202
- reason: "no-upstream",
54203
- deprecation: null
54204
- };
54205
- if (!(await fetchMainRemote(packageRoot, remote, runCommand, fetchOrigin, signal)).ok) return {
54206
- version: null,
54207
- sha: null,
54208
- behindCount: null,
54209
- reason: "fetch-failed",
54210
- deprecation: null
54211
- };
54212
- if (signal.aborted) return {
54213
- version: null,
54214
- sha: null,
54215
- behindCount: null,
54216
- reason: "remote-tip-unavailable",
54217
- deprecation: null
54218
- };
54219
- const remoteTip = await runGitProbe(runCommand, [
54220
- "ls-remote",
54221
- remote,
54222
- `refs/heads/${branch}`
54223
- ], {
54224
- cwd: packageRoot,
54225
- timeout: MAIN_REMOTE_TIP_TIMEOUT_MS,
54226
- signal
54227
- });
54228
- const targetSha = normalizeSha(remoteTip.out.split(/\s+/)[0]);
54229
- if (!targetSha) return {
54230
- version: null,
54231
- sha: null,
54232
- behindCount: null,
54233
- reason: "remote-tip-unavailable",
54234
- deprecation: null
54235
- };
54236
- if (signal.aborted) return {
54237
- version: null,
54238
- sha: targetSha,
54239
- behindCount: null,
54240
- reason: "branch-count-unavailable",
54241
- deprecation: null
54242
- };
54243
- const countsProbe = await runGitProbe(runCommand, [
54244
- "rev-list",
54245
- "--left-right",
54246
- "--count",
54247
- `HEAD...${targetSha}`
54248
- ], {
54249
- cwd: packageRoot,
54250
- timeout: MAIN_BEHIND_COUNT_TIMEOUT_MS,
54251
- signal
54252
- });
54253
- const counts = countsProbe.ok ? parseLeftRightCount(countsProbe.out) : null;
54254
- if (!counts) return {
54255
- version: null,
54256
- sha: targetSha,
54257
- behindCount: null,
54258
- reason: "branch-count-unavailable",
54259
- deprecation: null
54260
- };
54261
- const reason = counts.behind > 0 && counts.ahead > 0 ? "branch-diverged" : null;
54262
- return {
54263
- version: null,
54264
- sha: targetSha,
54265
- behindCount: counts.ahead,
54266
- reason,
54267
- deprecation: null
54268
- };
54269
- }
54270
54482
  function readCheckState(statePath) {
54271
54483
  return readRecord(readJsonFile(statePath));
54272
54484
  }
@@ -54278,7 +54490,7 @@ function writeCheckState(statePath, state) {
54278
54490
  async function finish(installed, latestTarget, currentVersion, channel, platform, lastCheckAt, packageRoot, runCommand, signal) {
54279
54491
  let isLatestReleaseAncestorOfHead = false;
54280
54492
  const latestSha = normalizeSha(latestTarget.sha);
54281
- if (channel === "release" && installed.flavor === "clone" && installed.installedSha && latestSha) isLatestReleaseAncestorOfHead = (await runGitProbe(runCommand, [
54493
+ if (installed.flavor === "clone" && installed.installedSha && latestSha) isLatestReleaseAncestorOfHead = (await runGitProbe(runCommand, [
54282
54494
  "merge-base",
54283
54495
  "--is-ancestor",
54284
54496
  latestSha,
@@ -54297,7 +54509,6 @@ async function finish(installed, latestTarget, currentVersion, channel, platform
54297
54509
  flavor: installed.flavor,
54298
54510
  platform,
54299
54511
  channel,
54300
- behindCount: latestTarget.behindCount,
54301
54512
  reason: latestTarget.reason,
54302
54513
  isLatestReleaseAncestorOfHead,
54303
54514
  deprecation: latestTarget.deprecation
@@ -54309,16 +54520,14 @@ async function finish(installed, latestTarget, currentVersion, channel, platform
54309
54520
  };
54310
54521
  }
54311
54522
  function targetFromCache(cached, currentVersion) {
54312
- const behindCount = Number.isInteger(cached.behindCount) && Number(cached.behindCount) >= 0 ? Number(cached.behindCount) : null;
54313
54523
  return {
54314
54524
  version: typeof cached.latestVersion === "string" ? cached.latestVersion : null,
54315
54525
  sha: typeof cached.latestSha === "string" ? cached.latestSha : null,
54316
- behindCount,
54317
54526
  reason: typeof cached.reason === "string" ? cached.reason : null,
54318
54527
  deprecation: cached.installedVersion === currentVersion && typeof cached.deprecation === "string" ? cached.deprecation : null
54319
54528
  };
54320
54529
  }
54321
- async function checkForUpdate({ currentVersion, updateChannel = "release", fetchFn = fetch, timeoutMs = DEFAULT_TIMEOUT_MS, abortController = new AbortController(), packageRoot: packageRoot$1 = packageRoot, runCommand = execFileAsync, fetchOrigin, statePath = defaultStatePath(), ttlMs = STATE_TTL_MS, now = Date.now(), platform = process.platform } = {}) {
54530
+ async function checkForUpdate({ currentVersion, updateChannel = "release", fetchFn = fetch, timeoutMs = DEFAULT_TIMEOUT_MS, abortController = new AbortController(), packageRoot: packageRoot$1 = packageRoot, runCommand = execFileAsync, statePath = defaultStatePath(), ttlMs = STATE_TTL_MS, now = Date.now(), platform = process.platform } = {}) {
54322
54531
  const timer = setTimeout(() => abortController.abort(), timeoutMs);
54323
54532
  const signal = abortController.signal;
54324
54533
  let installed = {
@@ -54336,12 +54545,12 @@ async function checkForUpdate({ currentVersion, updateChannel = "release", fetch
54336
54545
  });
54337
54546
  const cached = readCheckState(statePath);
54338
54547
  const cacheOutrunByInstall = normalizeSha(cached?.latestSha) !== null && normalizeSha(cached?.latestSha) === installed.installedSha;
54339
- const expectedReleaseSource = updateChannel === "release" ? RELEASE_SOURCE : null;
54340
- const isCacheFromSameSource = expectedReleaseSource === null || cached?.releaseSource === expectedReleaseSource;
54548
+ const isCacheFromSameSource = cached?.releaseSource === RELEASE_SOURCE;
54341
54549
  if (cached?.channel === updateChannel && isCacheFromSameSource && !cacheOutrunByInstall && isCheckFresh(cached.lastCheckAt, now, ttlMs)) return finish(installed, targetFromCache(cached, currentVersion), currentVersion, updateChannel, platform, now, packageRoot$1, runCommand, signal);
54342
54550
  signal.throwIfAborted();
54343
- const latestTarget = updateChannel === "main" ? await resolveLatestMain(installed, packageRoot$1, runCommand, fetchOrigin, signal) : await resolveLatestRegistryVersion({
54551
+ const latestTarget = await resolveLatestRegistryVersion({
54344
54552
  currentVersion,
54553
+ updateChannel,
54345
54554
  fetchFn,
54346
54555
  signal
54347
54556
  });
@@ -54350,8 +54559,7 @@ async function checkForUpdate({ currentVersion, updateChannel = "release", fetch
54350
54559
  channel: updateChannel,
54351
54560
  latestVersion: latestTarget.version,
54352
54561
  latestSha: normalizeSha(latestTarget.sha),
54353
- releaseSource: expectedReleaseSource,
54354
- behindCount: latestTarget.behindCount,
54562
+ releaseSource: RELEASE_SOURCE,
54355
54563
  reason: latestTarget.reason,
54356
54564
  installedVersion: currentVersion ?? null,
54357
54565
  deprecation: latestTarget.deprecation
@@ -54361,7 +54569,6 @@ async function checkForUpdate({ currentVersion, updateChannel = "release", fetch
54361
54569
  return finish(installed, {
54362
54570
  version: null,
54363
54571
  sha: null,
54364
- behindCount: null,
54365
54572
  reason: "update-check-failed",
54366
54573
  deprecation: null
54367
54574
  }, currentVersion, updateChannel, platform, now, packageRoot$1, runCommand, signal);
@@ -54425,15 +54632,7 @@ function createBackendUpdateCheck(dependencies) {
54425
54632
  reason: decision.reason,
54426
54633
  message: decision.message
54427
54634
  };
54428
- const fastForward = decideFastForward({
54429
- canFastForward: recorded.reason !== "branch-diverged",
54430
- isTargetAncestorOfHead: false
54431
- });
54432
- if (fastForward.ok) return null;
54433
- return {
54434
- reason: fastForward.reason,
54435
- message: fastForward.message
54436
- };
54635
+ return null;
54437
54636
  }
54438
54637
  function projectStatus(recorded) {
54439
54638
  const journal = dependencies.getUpdateJournal?.();
@@ -54530,7 +54729,6 @@ function createBackendUpdateCheck(dependencies) {
54530
54729
  currentVersion: dependencies.currentVersion,
54531
54730
  updateChannel: channel,
54532
54731
  abortController: updateAbort,
54533
- ...dependencies.fetchOrigin ? { fetchOrigin: dependencies.fetchOrigin } : {},
54534
54732
  ...ttlMs === void 0 ? {} : { ttlMs }
54535
54733
  }).catch(() => null).then((updateResult) => {
54536
54734
  if (stopped || channel !== normalizeUpdateChannel(dependencies.config.updateChannel)) return projectStatus({
@@ -55976,6 +56174,7 @@ function createUpdateApplyLane(dependencies) {
55976
56174
  stagedDescriptor = null;
55977
56175
  await dependencies.gitWorkspace.removeWorktreeByPath({
55978
56176
  projectPath: dependencies.packageRoot,
56177
+ cause: "update-apply",
55979
56178
  cwd: stagingPath
55980
56179
  });
55981
56180
  await publish(failRun(journal, {
@@ -56158,6 +56357,7 @@ function createUpdateApplyLane(dependencies) {
56158
56357
  }
56159
56358
  if ((await dependencies.gitWorkspace.removeWorktreeByPath({
56160
56359
  projectPath: dependencies.packageRoot,
56360
+ cause: "update-apply",
56161
56361
  cwd: stagingPath,
56162
56362
  admissionTimeoutMs: HANDOFF_QUEUE_ADMISSION_TIMEOUT_MS
56163
56363
  })).admissionRefused) refusedCleanupSteps.push("staging worktree removal");
@@ -56220,6 +56420,7 @@ function createUpdateApplyLane(dependencies) {
56220
56420
  }
56221
56421
  await dependencies.gitWorkspace.removeWorktreeByPath({
56222
56422
  projectPath: dependencies.packageRoot,
56423
+ cause: "update-apply",
56223
56424
  cwd: descriptor.stagingPath,
56224
56425
  admissionTimeoutMs: HANDOFF_QUEUE_ADMISSION_TIMEOUT_MS
56225
56426
  });
@@ -56235,6 +56436,7 @@ function createUpdateApplyLane(dependencies) {
56235
56436
  if (!descriptor || descriptor.state !== "staged" || inFlight) {
56236
56437
  await dependencies.gitWorkspace.removeWorktreeByPath({
56237
56438
  projectPath: dependencies.packageRoot,
56439
+ cause: "update-apply",
56238
56440
  cwd: stagingPath,
56239
56441
  admissionTimeoutMs: HANDOFF_QUEUE_ADMISSION_TIMEOUT_MS
56240
56442
  });
@@ -56308,6 +56510,7 @@ function createUpdateApplyLane(dependencies) {
56308
56510
  }), false);
56309
56511
  if (wasRunInFlight || await isStagingCheckoutPresent()) await dependencies.gitWorkspace.removeWorktreeByPath({
56310
56512
  projectPath: dependencies.packageRoot,
56513
+ cause: "update-apply",
56311
56514
  cwd: stagingPath
56312
56515
  });
56313
56516
  await fsPromises.rm(path.join(updatePath, QUARANTINED_DIST_NAME), {
@@ -57392,6 +57595,7 @@ function createCoherenceSessionFeed({ broadcastControl, debounceMs = 1500, now =
57392
57595
  //#region server/backend/backend.ts
57393
57596
  var AGENT_INGRESS_HOST = "127.0.0.1";
57394
57597
  function createBackend(httpServer, options = {}) {
57598
+ if (isOperatorHomeUnderTest(os.homedir(), process.env)) throw new Error("createBackend refused to boot on the operator's Glimmervoid home under node:test; run tests through npm test or npm run test:files, which isolate GLIMMERVOID_HOME");
57395
57599
  const { staticDir = "auto", settingsDefaults } = options;
57396
57600
  const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
57397
57601
  const configStore = createConfigStore({
@@ -57439,7 +57643,9 @@ function createBackend(httpServer, options = {}) {
57439
57643
  const outcomes = createOutcomesLane();
57440
57644
  const recordOutcome = outcomes.record;
57441
57645
  let broadcastControl = null;
57646
+ let removeExitedSession = null;
57442
57647
  let agentApi = null;
57648
+ let shownFiles = null;
57443
57649
  let gitWorkspace = null;
57444
57650
  const guardrailsRefusal = checkGuardrailsDependencies({ log: console });
57445
57651
  warnWhenGitIsTooOld({ log: console });
@@ -57468,6 +57674,7 @@ function createBackend(httpServer, options = {}) {
57468
57674
  latestPlanTitle: laneAssembly.planReview?.latestPlanTitle(glimmervoidId) ?? null
57469
57675
  }),
57470
57676
  getAgentApi: () => agentApi,
57677
+ getShownFiles: () => shownFiles,
57471
57678
  sshSigner,
57472
57679
  recordOutcome,
57473
57680
  logger: console
@@ -57507,6 +57714,11 @@ function createBackend(httpServer, options = {}) {
57507
57714
  const { controlWss, dataWss, controlReplayLog, sessionDataClients, broadcastControl: broadcastControlFromWebSockets, broadcastLocalControl, closeSessionDataClients } = webSockets;
57508
57715
  broadcastControl = broadcastControlFromWebSockets;
57509
57716
  const sessionDocs = createSessionDocsWiring({ broadcastControl: broadcastControlFromWebSockets });
57717
+ const shownFilesWiring = createShownFilesWiring({
57718
+ shownRoot: configSiblingPath(configStore.configPath, SHOWN_FOLDER_NAME),
57719
+ broadcastControl: broadcastControlFromWebSockets
57720
+ });
57721
+ shownFiles = shownFilesWiring;
57510
57722
  const coherenceFeed = createCoherenceSessionFeed({ broadcastControl: broadcastControlFromWebSockets });
57511
57723
  const sessions = /* @__PURE__ */ new Map();
57512
57724
  const agentSessions = /* @__PURE__ */ new Map();
@@ -57574,7 +57786,7 @@ function createBackend(httpServer, options = {}) {
57574
57786
  requestReview: (reference) => teamReview.requestReview(reference),
57575
57787
  teamReviewStatus: () => teamReview.getStatus(),
57576
57788
  myPrsStatus: () => myPrs.getStatus(),
57577
- readSettings: (request) => readSettingsForAgent(request, configFileViewOf(configStore.configPath, config, process.env))
57789
+ readSettings: (request) => readSettingsForAgent(request, configFileViewOf(configStore.configPath, config, process.env, configStore.isExperimentalBuild))
57578
57790
  },
57579
57791
  coderWorkspaceStatus: (workspace) => laneAssembly.coderEnv.workspaceStatus(workspace),
57580
57792
  logger: console
@@ -57606,6 +57818,7 @@ function createBackend(httpServer, options = {}) {
57606
57818
  coherenceFeed,
57607
57819
  planReview: laneAssembly.planReview,
57608
57820
  closeSessionDataClients,
57821
+ removeExitedSession: (id) => removeExitedSession?.(id),
57609
57822
  telemetry,
57610
57823
  logger: console
57611
57824
  });
@@ -57691,7 +57904,6 @@ function createBackend(httpServer, options = {}) {
57691
57904
  currentVersion: packageJson.version,
57692
57905
  platform: process.platform,
57693
57906
  checkForUpdate: options.checkForUpdate || void 0,
57694
- fetchOrigin: (args) => assembledGitWorkspace.fetchOrigin(args),
57695
57907
  getUpdateJournal: () => updateApply.getJournal(),
57696
57908
  isRestartRequested: () => updateApply.isRestartRequested(),
57697
57909
  getControlClientCount: () => controlWss.clients.size,
@@ -57794,10 +58006,11 @@ function createBackend(httpServer, options = {}) {
57794
58006
  spawn,
57795
58007
  beforeHandOff: updateApply.handOffStagedUpdate
57796
58008
  });
57797
- createBackendControl({
58009
+ ({removeExitedSession} = createBackendControl({
57798
58010
  controlWss,
57799
58011
  sessions,
57800
58012
  agentSessions,
58013
+ markChildWorktreeDiscard: (childId) => agentApi?.markChildWorktreeDiscard(childId),
57801
58014
  config,
57802
58015
  configStore,
57803
58016
  broadcastControl,
@@ -57836,11 +58049,12 @@ function createBackend(httpServer, options = {}) {
57836
58049
  readPlanRevision: laneAssembly.planReview?.readPlanRevision ?? null,
57837
58050
  decidePlanReview: laneAssembly.planReview?.decide ?? null,
57838
58051
  sessionDocs,
58052
+ shownFiles: shownFilesWiring,
57839
58053
  coherenceFeed,
57840
58054
  serverBuild: () => serverBuild,
57841
58055
  telemetry,
57842
58056
  logger: console
57843
- });
58057
+ }));
57844
58058
  webSockets.attachDataConnection();
57845
58059
  const { handleUpgrade } = webSockets;
57846
58060
  httpServer.on("upgrade", handleUpgrade);