glimmervoid 0.45.0 → 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-D99c4zCx.js → adapters-BE9BKVW0.js} +24 -24
  3. package/dist/chunks/{adapters-BGuI5U_-.js → adapters-CPaysZkO.js} +1 -1
  4. package/dist/chunks/{agent-api-cli-DdrR2vnB.js → agent-api-cli-B0l8uLos.js} +1 -1
  5. package/dist/chunks/{agent-api-cli-DIGH1v3_.js → agent-api-cli-Bq9J0_D5.js} +148 -15
  6. package/dist/chunks/{agent-api-core-D49OzKho.js → agent-api-core-ClU9-5K3.js} +9 -3
  7. package/dist/chunks/{agent-setup-cli-7hG401Io.js → agent-setup-cli-BA_1AdhQ.js} +2 -2
  8. package/dist/chunks/{contracts-CucdticH.js → build-channel-qgKwNssc.js} +37 -3
  9. package/dist/chunks/{config-BLeRKmn9.js → config-BqJTnPae.js} +16 -33
  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-CXLnauAa.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-CX9CkTDx.js → main-x9ygUnoL.js} +1030 -988
  17. package/dist/chunks/{pair-cli-BPyh48--.js → pair-cli-Cph4TZ2R.js} +2 -2
  18. package/dist/chunks/{remote-config-B0zBDBak.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-DzIasX94.js → settings-cli-Bete5oVk.js} +11 -10
  24. package/dist/chunks/{settings-readout-core-BOv6tqdK.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-C_QYhDvI.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-ItO-gOWA.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-DlaaBY9p.js +0 -1
  77. package/dist/chunks/rtk-resolver-Dr4iJsiS.js +0 -1
  78. package/dist/client/assets/action-status-DrMQzohk.js +0 -1
  79. package/dist/client/assets/action-status-DrMQzohk.js.br +0 -0
  80. package/dist/client/assets/benchmark-panel-D09qvrAY.js +0 -1
  81. package/dist/client/assets/benchmark-panel-D09qvrAY.js.br +0 -0
  82. package/dist/client/assets/dom-helpers-CZ8NZKxw.js +0 -4
  83. package/dist/client/assets/dom-helpers-CZ8NZKxw.js.br +0 -0
  84. package/dist/client/assets/hooks-panel-R-L7DuIz.js +0 -1
  85. package/dist/client/assets/hooks-panel-R-L7DuIz.js.br +0 -0
  86. package/dist/client/assets/index-B7TvZo7W.js +0 -173
  87. package/dist/client/assets/index-B7TvZo7W.js.br +0 -0
  88. package/dist/client/assets/index-C3nOrTwM.css +0 -2
  89. package/dist/client/assets/index-C3nOrTwM.css.br +0 -0
  90. package/dist/client/assets/issues-panel-CyB4oq9Z.js +0 -1
  91. package/dist/client/assets/issues-panel-CyB4oq9Z.js.br +0 -0
  92. package/dist/client/assets/modal-M_mJ8--V.js +0 -1
  93. package/dist/client/assets/modal-M_mJ8--V.js.br +0 -0
  94. package/dist/client/assets/poll-ago-ItO-gOWA.js.br +0 -0
  95. package/dist/client/assets/pr-queue-columns-Cu3-PADL.js +0 -1
  96. package/dist/client/assets/pr-queue-columns-Cu3-PADL.js.br +0 -0
  97. package/dist/client/assets/radar-panel-Cl3yPWed.js +0 -1
  98. package/dist/client/assets/radar-panel-Cl3yPWed.js.br +0 -0
  99. package/dist/client/assets/review-queue-panel-CVujzadC.js +0 -2
  100. package/dist/client/assets/review-queue-panel-CVujzadC.js.br +0 -0
  101. package/dist/client/assets/settings-panel-DW5An8lQ.js +0 -3
  102. package/dist/client/assets/settings-panel-DW5An8lQ.js.br +0 -0
  103. package/dist/client/assets/trace-panel-B6TiM5pb.js +0 -1
  104. package/dist/client/assets/trace-panel-B6TiM5pb.js.br +0 -0
  105. package/dist/client/assets/ui-prefs-CQHZFVx6.js +0 -1
  106. package/dist/client/assets/ui-prefs-CQHZFVx6.js.br +0 -0
  107. package/dist/client/assets/usage-panel-D2RX3LtG.js +0 -1
  108. package/dist/client/assets/usage-panel-D2RX3LtG.js.br +0 -0
  109. package/dist/client/assets/visions-panel-BV9EKCBw.js +0 -1
  110. package/dist/client/assets/visions-panel-BV9EKCBw.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-BLeRKmn9.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, 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-D49OzKho.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-CXLnauAa.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-CucdticH.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-D99c4zCx.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-B0zBDBak.js";
30
- import { n as configFileViewOf, u as readSettingsForAgent } from "./settings-readout-core-BOv6tqdK.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)
@@ -4740,8 +5071,12 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
4740
5071
  function markChildWorktreeDiscard(childId) {
4741
5072
  childIdsToDiscard.add(childId);
4742
5073
  }
5074
+ function refuseRole(session, verb) {
5075
+ return describeRoleRefusal(verb, roleFactsOf(session));
5076
+ }
4743
5077
  return {
4744
5078
  handle,
5079
+ refuseRole,
4745
5080
  markChildWorktreeDiscard
4746
5081
  };
4747
5082
  }
@@ -4830,11 +5165,7 @@ function createRemoteSetupWiring({ config, agentSessions, makeSession, wireSessi
4830
5165
  }
4831
5166
  //#endregion
4832
5167
  //#region server/core/control-replay-core.ts
4833
- var REPLAYABLE_EXACT = /* @__PURE__ */ new Set([
4834
- "notify",
4835
- "session-error",
4836
- "post-turn-result"
4837
- ]);
5168
+ var REPLAYABLE_EXACT = /* @__PURE__ */ new Set(["notify", "session-error"]);
4838
5169
  function isReplayable(type) {
4839
5170
  return typeof type === "string" && REPLAYABLE_EXACT.has(type);
4840
5171
  }
@@ -5059,7 +5390,7 @@ function planDeepSlices({ lineCount, rows, sliceRows, isWrapped }) {
5059
5390
  //#endregion
5060
5391
  //#region server/ws-sender.ts
5061
5392
  var OPEN = 1;
5062
- var DEFAULTS$1 = Object.freeze({
5393
+ var DEFAULTS = Object.freeze({
5063
5394
  maxSendBuffer: 65536,
5064
5395
  highWaterMark: 1 << 20,
5065
5396
  lowWaterMark: 1 << 18,
@@ -5067,10 +5398,10 @@ var DEFAULTS$1 = Object.freeze({
5067
5398
  });
5068
5399
  function createWsSender(ws, opts = {}) {
5069
5400
  const cfg = {
5070
- maxSendBuffer: opts.maxSendBuffer ?? DEFAULTS$1.maxSendBuffer,
5071
- highWaterMark: opts.highWaterMark ?? DEFAULTS$1.highWaterMark,
5072
- lowWaterMark: opts.lowWaterMark ?? DEFAULTS$1.lowWaterMark,
5073
- 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
5074
5405
  };
5075
5406
  const scheduleImmediate = opts.setImmediateFn || setImmediate;
5076
5407
  const setTimer = opts.setTimeoutFn || ((fn, ms) => setTimeout(fn, ms));
@@ -6367,605 +6698,6 @@ function compareEntries(a, b) {
6367
6698
  return laneKey(a.vendor, a.sessionId).localeCompare(laneKey(b.vendor, b.sessionId));
6368
6699
  }
6369
6700
  //#endregion
6370
- //#region session/core/slop-code-patterns.ts
6371
- var TS_EXTS = /* @__PURE__ */ new Set([
6372
- ".ts",
6373
- ".tsx",
6374
- ".mts",
6375
- ".cts"
6376
- ]);
6377
- var PY_EXTS = /* @__PURE__ */ new Set([".py"]);
6378
- function extOf(relPath) {
6379
- if (!relPath) return "";
6380
- const s = String(relPath).replace(/\\/g, "/");
6381
- const base = s.slice(s.lastIndexOf("/") + 1);
6382
- const dot = base.lastIndexOf(".");
6383
- return dot <= 0 ? "" : base.slice(dot).toLowerCase();
6384
- }
6385
- var SUBRULES = [
6386
- {
6387
- id: "swallowed-exception",
6388
- axis: "lies",
6389
- re: /catch\s*(?:\([^)]*\))?\s*\{\s*(?:\/\/[^\n]*\s*|\/\*[\s\S]*?\*\/\s*)?\}/g
6390
- },
6391
- {
6392
- id: "opener-comment",
6393
- axis: "noise",
6394
- 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
6395
- },
6396
- {
6397
- id: "placeholder",
6398
- axis: "lies",
6399
- 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
6400
- },
6401
- {
6402
- id: "debug-leftover",
6403
- axis: "noise",
6404
- re: /\bconsole\.(?:log|debug|info|trace)\s*\(|\bdebugger\b/g
6405
- },
6406
- {
6407
- id: "debug-print-py",
6408
- axis: "noise",
6409
- langs: PY_EXTS,
6410
- re: /^[ \t]*print\s*\(/gim
6411
- },
6412
- {
6413
- id: "hedge-comment",
6414
- axis: "soul",
6415
- 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
6416
- },
6417
- {
6418
- id: "type-escape",
6419
- axis: "soul",
6420
- langs: TS_EXTS,
6421
- re: /\bas\s+any\b|:\s*any(?:\b|\[)|@ts-(?:ignore|nocheck|expect-error)\b|eslint-disable(?:-next-line|-line)?\b/g
6422
- }
6423
- ];
6424
- var DEFAULT_FINDINGS_CAP = 200;
6425
- function detectCodeSlop(content, relPath, cap = DEFAULT_FINDINGS_CAP) {
6426
- if (typeof content !== "string" || content.length === 0) return [];
6427
- const ext = extOf(relPath);
6428
- const findings = [];
6429
- for (const sub of SUBRULES) {
6430
- if (sub.langs && !sub.langs.has(ext)) continue;
6431
- sub.re.lastIndex = 0;
6432
- for (;;) {
6433
- const m = sub.re.exec(content);
6434
- if (m === null) break;
6435
- findings.push({
6436
- subrule: sub.id,
6437
- axis: sub.axis,
6438
- index: m.index
6439
- });
6440
- if (findings.length >= cap) break;
6441
- }
6442
- if (findings.length >= cap) break;
6443
- }
6444
- findings.sort((a, b) => a.index - b.index);
6445
- return findings;
6446
- }
6447
- //#endregion
6448
- //#region session/core/post-turn-rules.ts
6449
- var BOM = String.fromCharCode(65279);
6450
- var NL = String.fromCharCode(10);
6451
- var CRLF = String.fromCharCode(13, 10);
6452
- function posAt(content, offset) {
6453
- let line = 1;
6454
- let col = 1;
6455
- const end = Math.min(offset, content.length);
6456
- for (let i = 0; i < end; i++) {
6457
- if (content[i] !== NL) {
6458
- col++;
6459
- continue;
6460
- }
6461
- line++;
6462
- col = 1;
6463
- }
6464
- return {
6465
- line,
6466
- col
6467
- };
6468
- }
6469
- function detectSlop(content, ctx) {
6470
- const relPath = ctx?.relPath;
6471
- const matches = detectCodeSlop(content, relPath);
6472
- const findings = [];
6473
- let pos = 0;
6474
- let line = 1;
6475
- let col = 1;
6476
- for (const m of matches) {
6477
- const target = m.index < content.length ? m.index : content.length;
6478
- while (pos < target) {
6479
- if (content.charCodeAt(pos) === 10) {
6480
- line++;
6481
- col = 1;
6482
- pos++;
6483
- continue;
6484
- }
6485
- col++;
6486
- pos++;
6487
- }
6488
- findings.push({
6489
- rule: "slop",
6490
- subrule: m.subrule,
6491
- axis: m.axis,
6492
- line,
6493
- col
6494
- });
6495
- }
6496
- return {
6497
- content,
6498
- findings
6499
- };
6500
- }
6501
- function fixTrailingWhitespace(content) {
6502
- const findings = [];
6503
- return {
6504
- content: content.replace(/[ \t]+(\r?\n|$)/g, (m, tail, offset) => {
6505
- const { line, col } = posAt(content, offset);
6506
- findings.push({
6507
- rule: "trailingWs",
6508
- line,
6509
- col,
6510
- before: m,
6511
- after: tail
6512
- });
6513
- return tail;
6514
- }),
6515
- findings
6516
- };
6517
- }
6518
- function fixFinalNewline(content) {
6519
- if (content === "" || content.charCodeAt(content.length - 1) === 10) return {
6520
- content,
6521
- findings: []
6522
- };
6523
- const nl = content.indexOf(CRLF) !== -1 ? CRLF : NL;
6524
- const { line, col } = posAt(content, content.length);
6525
- return {
6526
- content: content + nl,
6527
- findings: [{
6528
- rule: "finalNewline",
6529
- line,
6530
- col,
6531
- before: "",
6532
- after: nl
6533
- }]
6534
- };
6535
- }
6536
- function stripBom(content) {
6537
- if (content.charCodeAt(0) !== 65279) return {
6538
- content,
6539
- findings: []
6540
- };
6541
- return {
6542
- content: content.slice(1),
6543
- findings: [{
6544
- rule: "bom",
6545
- line: 1,
6546
- col: 1,
6547
- before: BOM,
6548
- after: ""
6549
- }]
6550
- };
6551
- }
6552
- var RULES = Object.freeze({
6553
- bom: { fix: stripBom },
6554
- trailingWs: { fix: fixTrailingWhitespace },
6555
- finalNewline: { fix: fixFinalNewline },
6556
- slop: { fix: detectSlop }
6557
- });
6558
- var RULE_ORDER = [
6559
- "bom",
6560
- "trailingWs",
6561
- "finalNewline",
6562
- "slop"
6563
- ];
6564
- var EXEMPT_MARKER = "glimmervoid-no-fix";
6565
- function exemptions(content) {
6566
- const head = content.slice(0, 4096);
6567
- if (head.indexOf(EXEMPT_MARKER) === -1) return {
6568
- all: false,
6569
- rules: /* @__PURE__ */ new Set()
6570
- };
6571
- const rules = /* @__PURE__ */ new Set();
6572
- const re = new RegExp(`${EXEMPT_MARKER}:([a-zA-Z]+)`, "g");
6573
- let m = re.exec(head);
6574
- while (m !== null) {
6575
- rules.add(m[1]);
6576
- m = re.exec(head);
6577
- }
6578
- return {
6579
- all: new RegExp(`${EXEMPT_MARKER}(?!:)`).test(head),
6580
- rules
6581
- };
6582
- }
6583
- function applyRules(content, rules, ctx) {
6584
- const ex = exemptions(content);
6585
- const findings = [];
6586
- let current = content;
6587
- for (const name of RULE_ORDER) {
6588
- const cfg = rules?.[name];
6589
- if (!cfg || !cfg.enabled) continue;
6590
- if (ex.all || ex.rules.has(name)) continue;
6591
- const res = RULES[name].fix(current, ctx);
6592
- if (res.findings.length === 0) continue;
6593
- for (const f of res.findings) findings.push(f);
6594
- if (cfg.mode === "fix") current = res.content;
6595
- }
6596
- return {
6597
- content: current,
6598
- findings,
6599
- changed: current !== content
6600
- };
6601
- }
6602
- var REGEX_METACHARACTERS = [
6603
- ".",
6604
- "+",
6605
- "^",
6606
- "$",
6607
- "{",
6608
- "}",
6609
- "(",
6610
- ")",
6611
- "|",
6612
- "[",
6613
- "]",
6614
- "\\"
6615
- ].join("");
6616
- function starSegment(glob, index) {
6617
- if (glob[index + 1] !== "*") return {
6618
- pattern: "[^/]*",
6619
- consumed: 0
6620
- };
6621
- if (glob[index + 2] === "/") return {
6622
- pattern: "(?:.*/)?",
6623
- consumed: 2
6624
- };
6625
- return {
6626
- pattern: ".*",
6627
- consumed: 1
6628
- };
6629
- }
6630
- function globToRegExp(glob) {
6631
- let re = "^";
6632
- for (let i = 0; i < glob.length; i++) {
6633
- const c = glob[i];
6634
- if (c === "*") {
6635
- const segment = starSegment(glob, i);
6636
- re += segment.pattern;
6637
- i += segment.consumed;
6638
- continue;
6639
- }
6640
- if (c === "?") {
6641
- re += "[^/]";
6642
- continue;
6643
- }
6644
- if (REGEX_METACHARACTERS.indexOf(c) !== -1) {
6645
- re += `\\${c}`;
6646
- continue;
6647
- }
6648
- re += c;
6649
- }
6650
- re += "$";
6651
- return new RegExp(re);
6652
- }
6653
- function shouldCheckPath(relPath, { include, exclude } = {}) {
6654
- const p = String(relPath).replace(/\\/g, "/");
6655
- const inc = include?.length ? include : ["**/*"];
6656
- const matchAny = (globs) => globs.some((g) => globToRegExp(g).test(p));
6657
- if (!matchAny(inc)) return false;
6658
- if (exclude?.length && matchAny(exclude)) return false;
6659
- return true;
6660
- }
6661
- function looksBinary(buf) {
6662
- const n = Math.min(buf.length, 8192);
6663
- const at = typeof buf === "string" ? (i) => buf.charCodeAt(i) : (i) => buf[i];
6664
- for (let i = 0; i < n; i++) if (at(i) === 0) return true;
6665
- return false;
6666
- }
6667
- //#endregion
6668
- //#region server/git/git-exec.ts
6669
- var DEFAULT_COMMAND_TIMEOUT_MS = 2e4;
6670
- function outputText(value) {
6671
- if (typeof value === "string") return value;
6672
- if (Buffer.isBuffer(value)) return value.toString("utf8");
6673
- return value == null ? "" : String(value);
6674
- }
6675
- function childEnv(env, replaceEnv) {
6676
- if (replaceEnv) return { env: replaceEnv };
6677
- if (env) return { env: {
6678
- ...process.env,
6679
- ...env
6680
- } };
6681
- return {};
6682
- }
6683
- async function runCommand(file, args, { cwd, timeoutMs = DEFAULT_COMMAND_TIMEOUT_MS, maxBuffer, env, replaceEnv, signal, input, trim = true, keepStdoutOnFailure = false, preferStderr = false, execFileFn = execFileAsync } = {}) {
6684
- const execOptions = {
6685
- encoding: "utf8",
6686
- timeout: timeoutMs,
6687
- ...cwd === void 0 ? {} : { cwd },
6688
- ...maxBuffer === void 0 ? {} : { maxBuffer },
6689
- ...signal ? { signal } : {},
6690
- ...input === void 0 ? {} : { input },
6691
- ...childEnv(env, replaceEnv)
6692
- };
6693
- try {
6694
- const { stdout, stderr } = await execFileFn(file, args, execOptions);
6695
- const out = outputText(stdout);
6696
- return {
6697
- ok: true,
6698
- out: trim ? out.trim() : out,
6699
- err: "",
6700
- stderr: outputText(stderr)
6701
- };
6702
- } catch (error) {
6703
- const failure = error ?? {};
6704
- const stdout = outputText(failure.stdout);
6705
- const stderr = outputText(failure.stderr);
6706
- const message = errorMessage(error);
6707
- return {
6708
- ok: false,
6709
- out: keepStdoutOnFailure ? trim ? stdout.trim() : stdout : "",
6710
- err: preferStderr ? stderr || message : message,
6711
- stderr,
6712
- error,
6713
- timedOut: failure.killed === true && Boolean(failure.signal)
6714
- };
6715
- }
6716
- }
6717
- function runGit(args, { gitPath = "git", env, replaceEnv, ...rest } = {}) {
6718
- const baseEnv = replaceEnv ?? (env ? {
6719
- ...process.env,
6720
- ...env
6721
- } : process.env);
6722
- return runCommand(gitPath, args, {
6723
- ...rest,
6724
- replaceEnv: {
6725
- ...baseEnv,
6726
- GIT_TERMINAL_PROMPT: "0"
6727
- }
6728
- });
6729
- }
6730
- function gitCommandFailure(args, command, timeoutMs = DEFAULT_COMMAND_TIMEOUT_MS) {
6731
- if (!command.timedOut) return command.error;
6732
- return new Error(`git ${args.join(" ")} timed out after ${timeoutMs}ms`, { cause: command.error });
6733
- }
6734
- function runGh(args, options = {}) {
6735
- return runCommand("gh", args, options);
6736
- }
6737
- //#endregion
6738
- //#region server/post-turn-checker.ts
6739
- var GIT_TIMEOUT_MS$2 = 5e3;
6740
- var DEFAULTS = Object.freeze({
6741
- enabled: true,
6742
- mode: "report",
6743
- rules: {
6744
- trailingWs: true,
6745
- finalNewline: true,
6746
- bom: true,
6747
- slop: false
6748
- },
6749
- include: ["**/*"],
6750
- exclude: [
6751
- "**/node_modules/**",
6752
- "**/dist/**",
6753
- "**/.git/**",
6754
- "**/.glimmervoid/**",
6755
- "**/*.lock",
6756
- "**/package-lock.json",
6757
- "**/pnpm-lock.yaml",
6758
- "**/yarn.lock",
6759
- "**/*.png",
6760
- "**/*.jpg",
6761
- "**/*.jpeg",
6762
- "**/*.gif",
6763
- "**/*.webp",
6764
- "**/*.ico",
6765
- "**/*.pdf",
6766
- "**/*.zip",
6767
- "**/*.gz",
6768
- "**/*.woff",
6769
- "**/*.woff2",
6770
- "**/*.ttf",
6771
- "**/*.eot"
6772
- ],
6773
- maxFiles: 200,
6774
- maxFileBytes: 524288,
6775
- debounceMs: 1500
6776
- });
6777
- function explicitMode(mode) {
6778
- return mode === "fix" ? "fix" : "report";
6779
- }
6780
- function normalizeRule(val, topMode) {
6781
- if (val === false) return {
6782
- enabled: false,
6783
- mode: topMode
6784
- };
6785
- if (val === true || val == null) return {
6786
- enabled: true,
6787
- mode: topMode
6788
- };
6789
- if (typeof val === "object") {
6790
- const source = val;
6791
- return {
6792
- enabled: source.enabled !== false,
6793
- mode: source.mode == null ? topMode : explicitMode(source.mode)
6794
- };
6795
- }
6796
- return {
6797
- enabled: true,
6798
- mode: topMode
6799
- };
6800
- }
6801
- var SCALAR_KEYS = [
6802
- "enabled",
6803
- "mode",
6804
- "maxFiles",
6805
- "maxFileBytes",
6806
- "debounceMs"
6807
- ];
6808
- function resolveCheckConfig(globalCfg, projectCfg) {
6809
- const base = JSON.parse(JSON.stringify(DEFAULTS));
6810
- const layers = [globalCfg, projectCfg].filter((x) => Boolean(x) && typeof x === "object");
6811
- for (const layer of layers) {
6812
- for (const k of SCALAR_KEYS) if (layer[k] != null) Object.assign(base, { [k]: layer[k] });
6813
- if (Array.isArray(layer.include)) base.include = layer.include.slice();
6814
- if (Array.isArray(layer.exclude)) base.exclude = layer.exclude.slice();
6815
- if (layer.rules && typeof layer.rules === "object") base.rules = Object.assign({}, base.rules, layer.rules);
6816
- }
6817
- const topMode = explicitMode(base.mode);
6818
- const rules = {};
6819
- for (const name of Object.keys(base.rules)) rules[name] = normalizeRule(base.rules[name], topMode);
6820
- return {
6821
- enabled: base.enabled !== false,
6822
- mode: topMode,
6823
- rules,
6824
- include: base.include,
6825
- exclude: base.exclude,
6826
- maxFiles: base.maxFiles,
6827
- maxFileBytes: base.maxFileBytes,
6828
- debounceMs: base.debounceMs
6829
- };
6830
- }
6831
- var GIT_MAX_BUFFER_BYTES$1 = 8388608;
6832
- async function execGit(args, cwd) {
6833
- const result = await runGit(args, {
6834
- cwd,
6835
- timeoutMs: GIT_TIMEOUT_MS$2,
6836
- maxBuffer: GIT_MAX_BUFFER_BYTES$1,
6837
- trim: false
6838
- });
6839
- if (!result.ok) throw result.error;
6840
- return result.out;
6841
- }
6842
- async function gitRoot(cwd) {
6843
- return (await execGit(["rev-parse", "--show-toplevel"], cwd)).toString().trim();
6844
- }
6845
- async function listChangedFiles(root) {
6846
- const lines = (await execGit([
6847
- "--no-optional-locks",
6848
- "-c",
6849
- "core.quotepath=false",
6850
- "status",
6851
- "--porcelain",
6852
- "-uall",
6853
- "--no-renames"
6854
- ], root)).toString().split(/\r?\n/);
6855
- const files = [];
6856
- for (const ln of lines) {
6857
- if (!ln) continue;
6858
- const xy = ln.slice(0, 2);
6859
- const p = ln.slice(3);
6860
- if (!p) continue;
6861
- if (xy.indexOf("D") !== -1) continue;
6862
- files.push(p);
6863
- }
6864
- return files;
6865
- }
6866
- async function runPostTurnChecks({ cwd, config, deps = {} } = {}) {
6867
- const started = Date.now();
6868
- const cfg = config || resolveCheckConfig();
6869
- const report = {
6870
- ok: true,
6871
- skipped: null,
6872
- mode: cfg.mode,
6873
- root: null,
6874
- filesScanned: 0,
6875
- filesFixed: 0,
6876
- findings: [],
6877
- errors: [],
6878
- durationMs: 0
6879
- };
6880
- const done = () => {
6881
- report.durationMs = Date.now() - started;
6882
- return report;
6883
- };
6884
- if (!cfg.enabled) {
6885
- report.skipped = "disabled";
6886
- return done();
6887
- }
6888
- const _gitRoot = deps.gitRoot || gitRoot;
6889
- const _list = deps.listChangedFiles || listChangedFiles;
6890
- const _readFile = deps.readFile || ((p) => fs.readFileSync(p));
6891
- const _writeFile = deps.writeFile || ((p, c) => fs.writeFileSync(p, c));
6892
- const _stat = deps.stat || ((p) => fs.statSync(p));
6893
- let root;
6894
- try {
6895
- root = await _gitRoot(cwd);
6896
- } catch {
6897
- report.skipped = "no-git";
6898
- return done();
6899
- }
6900
- report.root = root;
6901
- let files;
6902
- try {
6903
- files = await _list(root);
6904
- } catch (err) {
6905
- report.skipped = "no-git";
6906
- report.errors.push({
6907
- file: null,
6908
- message: errorMessage(err)
6909
- });
6910
- return done();
6911
- }
6912
- if (!files.length) {
6913
- report.skipped = "no-changes";
6914
- return done();
6915
- }
6916
- const eligible = files.filter((rel) => shouldCheckPath(rel, {
6917
- include: cfg.include,
6918
- exclude: cfg.exclude
6919
- })).slice(0, cfg.maxFiles);
6920
- let fileIndex = 0;
6921
- for (const rel of eligible) {
6922
- if (fileIndex++ > 0) await new Promise((resolve) => setImmediate(resolve));
6923
- const abs = path.join(root, rel);
6924
- try {
6925
- const before = _stat(abs);
6926
- if (!before) {
6927
- report.errors.push({
6928
- file: rel,
6929
- message: "skipped-race"
6930
- });
6931
- continue;
6932
- }
6933
- if (before && before.size != null && before.size > cfg.maxFileBytes) continue;
6934
- const buf = _readFile(abs);
6935
- if (looksBinary(buf)) continue;
6936
- const res = applyRules(buf.toString("utf8"), cfg.rules, { relPath: rel });
6937
- report.filesScanned++;
6938
- if (res.findings.length) {
6939
- const counts = {};
6940
- for (const f of res.findings) counts[f.rule] = (counts[f.rule] || 0) + 1;
6941
- for (const [rule, count] of Object.entries(counts)) report.findings.push({
6942
- file: rel,
6943
- rule,
6944
- count
6945
- });
6946
- }
6947
- if (res.changed) {
6948
- const after = _stat(abs);
6949
- if (after && (after.mtimeMs !== before.mtimeMs || after.size !== before.size)) {
6950
- report.errors.push({
6951
- file: rel,
6952
- message: "skipped-race"
6953
- });
6954
- continue;
6955
- }
6956
- _writeFile(abs, res.content);
6957
- report.filesFixed++;
6958
- }
6959
- } catch (err) {
6960
- report.errors.push({
6961
- file: rel,
6962
- message: errorMessage(err)
6963
- });
6964
- }
6965
- }
6966
- return done();
6967
- }
6968
- //#endregion
6969
6701
  //#region server/session/session-event-wiring.ts
6970
6702
  var IDLE_COMPACTION_CONFIRM_MS = 1e4;
6971
6703
  function waitingCopy(name, context) {
@@ -7000,7 +6732,6 @@ function createSessionEventWiring(dependencies) {
7000
6732
  dependencies.taskTitleRefiner?.attachSession(session);
7001
6733
  dependencies.coherenceFeed?.attachSession(session);
7002
6734
  dependencies.planReview?.attachSession(session);
7003
- let postTurnDebounce = null;
7004
6735
  let pendingPromptKind = null;
7005
6736
  let pendingAgentNote = null;
7006
6737
  const notifyGate = createNotifyGate();
@@ -7080,10 +6811,6 @@ function createSessionEventWiring(dependencies) {
7080
6811
  dependencies.telemetry?.captureException(error, { handled: true });
7081
6812
  });
7082
6813
  session.on("exit", ({ exitCode, signal, reason, exitedOnItsOwn }) => {
7083
- if (postTurnDebounce) {
7084
- clearTimeout(postTurnDebounce);
7085
- postTurnDebounce = null;
7086
- }
7087
6814
  clearCompactionConfirm();
7088
6815
  const reasonText = reason ? `, reason=${reason}` : "";
7089
6816
  dependencies.logger.log(`[${session.name}] exited (code=${exitCode}, signal=${signal}${reasonText})`);
@@ -7120,32 +6847,6 @@ function createSessionEventWiring(dependencies) {
7120
6847
  dependencies.getIngestLane()?.detachSessionTap(session);
7121
6848
  captureSessionEnded(cause);
7122
6849
  });
7123
- const resolvePostTurn = () => {
7124
- const project = dependencies.config.projects.find((candidate) => candidate.id === session.id);
7125
- return project ? resolveCheckConfig(dependencies.config.postTurnChecks, project.postTurnChecks) : null;
7126
- };
7127
- session.on("post-turn-check", () => {
7128
- const config = resolvePostTurn();
7129
- if (!config?.enabled) return;
7130
- if (postTurnDebounce) clearTimeout(postTurnDebounce);
7131
- postTurnDebounce = setTimeout(() => {
7132
- postTurnDebounce = null;
7133
- const runConfig = resolvePostTurn();
7134
- if (!runConfig?.enabled) return;
7135
- runPostTurnChecks({
7136
- cwd: session.effectiveCwd(),
7137
- config: runConfig
7138
- }).then((report) => {
7139
- dependencies.broadcastControl({
7140
- type: "post-turn-result",
7141
- id: session.id,
7142
- session: session.name,
7143
- ...report,
7144
- timestamp: Date.now()
7145
- });
7146
- }).catch((error) => dependencies.logger.warn(`[${session.name}] post-turn checks failed: ${errorMessage(error)}`));
7147
- }, config.debounceMs);
7148
- });
7149
6850
  session.on("post-turn-check", () => dependencies.usage.nudgeSession(liveTranscriptPath));
7150
6851
  session.on("state-change", ({ from, to, event, detail }) => {
7151
6852
  if (to === STATES.IDLE || to === STATES.COMPLETE) {
@@ -7245,9 +6946,14 @@ function createSessionEventWiring(dependencies) {
7245
6946
  timestamp: Date.now()
7246
6947
  });
7247
6948
  });
7248
- relay("wakeup-change", "session-wakeup");
7249
6949
  session.on("wakeup-change", persistWasWorking);
7250
- 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
+ }));
7251
6957
  relay("unseen-outcome-change", "session-unseen");
7252
6958
  relay("conversation-change", "session-conversation");
7253
6959
  session.on("merge-status", ({ mergeStatus, reason, parked }) => {
@@ -7410,10 +7116,7 @@ function runAutoResume(sessions, config, spawnGate, logger = console) {
7410
7116
  runs.push(spawnGate.run(() => {
7411
7117
  if (session.state !== STATES.DORMANT) return null;
7412
7118
  return session.start({ resumePrompt });
7413
- }, {
7414
- isStillWanted: () => session.state === STATES.DORMANT,
7415
- onMemoryWait: (isWaiting) => session.setWaitingForMemory(isWaiting)
7416
- }).catch((error) => {
7119
+ }, { isStillWanted: () => session.state === STATES.DORMANT }).catch((error) => {
7417
7120
  logger.warn(`[boot] auto-resume failed for ${session.name}: ${errorMessage(error)}`);
7418
7121
  }));
7419
7122
  }
@@ -7473,7 +7176,8 @@ function reconcileSessionWorktrees({ projects, sessions, gitWorkspaceSync, integ
7473
7176
  gitWorkspaceSync.removeWorktreeByPath({
7474
7177
  projectPath: project.path,
7475
7178
  cwd: worktree.cwd,
7476
- branch: worktree.branch
7179
+ branch: worktree.branch,
7180
+ cause: "boot-reconcile"
7477
7181
  });
7478
7182
  }
7479
7183
  }
@@ -7654,6 +7358,8 @@ function createSessionRegistry(dependencies) {
7654
7358
  //#endregion
7655
7359
  //#region shared/team-review-markdown.ts
7656
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.";
7657
7363
  var SEVERITY_ALTERNATION = FindingSeverity.options.join("|");
7658
7364
  var FINDING_HEADER_SOURCE = String.raw`\*\*(?:\[([^\]\n]+)\]\s+)?(${SEVERITY_ALTERNATION})\*\*`;
7659
7365
  function findingHeader$1(reviewer, severity) {
@@ -7672,7 +7378,12 @@ function parseLeadingFindingHeader(body) {
7672
7378
  }
7673
7379
  function withoutAutomatedNote(body) {
7674
7380
  const trimmed = body.trimStart();
7675
- 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();
7676
7387
  }
7677
7388
  var FOLDED_FINDING_MARKER = "(line not in the diff)";
7678
7389
  var FINDING_BULLET_SOURCE = String.raw`- ${FINDING_HEADER_SOURCE}`;
@@ -9538,7 +9249,7 @@ function readTeamReviewSettings(config) {
9538
9249
  const teams = readGithubTeams(config);
9539
9250
  const configuredOrg = typeof block?.org === "string" ? block.org.trim() : "";
9540
9251
  return {
9541
- enabled: block?.enabled === true,
9252
+ enabled: block?.enabled !== false,
9542
9253
  org: configuredOrg || teams[0]?.org || "",
9543
9254
  teams,
9544
9255
  reReviewAfterHours: positiveNumberOr(block?.reReviewAfterHours, 24),
@@ -9699,10 +9410,6 @@ function isDocsOrTests(filePath) {
9699
9410
  return /(?:^|\/)(?:test|tests|__tests__|spec)\//.test(normalizedPath) || /(?:\.test\.|\.spec\.|_test\.)/.test(fileName);
9700
9411
  }
9701
9412
  function triagePr(detail) {
9702
- if (detail.isCrossRepository) return {
9703
- tier: "skip",
9704
- reasons: ["fork"]
9705
- };
9706
9413
  const listedLines = detail.files.reduce((total, file) => total + file.additions + file.deletions, 0);
9707
9414
  if (detail.files.length >= 100 || listedLines < detail.additions + detail.deletions) return {
9708
9415
  tier: "full",
@@ -9745,20 +9452,26 @@ function isPostableStatus(draft, event) {
9745
9452
  function canPost(draft, clickedHead, currentHead, event) {
9746
9453
  return isPostableStatus(draft, event) && draft.reviewedHead === clickedHead && draft.reviewedHead === currentHead;
9747
9454
  }
9748
- 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
+ };
9749
9460
  function postedReviewBody(event, body) {
9750
- 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;
9751
9463
  const noteStart = body.indexOf(AUTOMATED_REVIEW_NOTE);
9464
+ if (noteStart === -1 && event !== "APPROVE") return body;
9752
9465
  return [
9753
9466
  noteStart === -1 ? body.trim() : body.slice(0, noteStart).trim(),
9754
- AUTOMATED_REVIEW_NOTE,
9755
- noteStart === -1 ? "" : body.slice(noteStart + AUTOMATED_REVIEW_NOTE.length).trim(),
9756
- HAND_APPROVAL_LINE
9467
+ postedNote,
9468
+ noteStart === -1 ? "" : body.slice(noteStart + AUTOMATED_REVIEW_NOTE.length).trim()
9757
9469
  ].filter(Boolean).join("\n\n");
9758
9470
  }
9759
9471
  function eventForAction(action) {
9760
9472
  if (action === "approve" || action === "approve-only") return "APPROVE";
9761
9473
  if (action === "comment") return "COMMENT";
9474
+ if (action === "request-changes") return "REQUEST_CHANGES";
9762
9475
  return null;
9763
9476
  }
9764
9477
  var HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
@@ -9768,6 +9481,16 @@ function diffHeaderPath(line, sidePrefix) {
9768
9481
  if (unquoted === "/dev/null") return null;
9769
9482
  return unquoted.startsWith(sidePrefix) ? unquoted.slice(sidePrefix.length) : unquoted;
9770
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
+ }
9771
9494
  function commentableLines(diffText) {
9772
9495
  const linesByPath = /* @__PURE__ */ new Map();
9773
9496
  let oldPath = null;
@@ -9776,25 +9499,44 @@ function commentableLines(diffText) {
9776
9499
  let newLine = 0;
9777
9500
  let oldRemaining = 0;
9778
9501
  let newRemaining = 0;
9502
+ let hunk = 0;
9779
9503
  for (const line of diffText.split("\n")) {
9780
9504
  if (oldRemaining > 0 || newRemaining > 0) {
9781
9505
  if (!currentFile) continue;
9782
9506
  const marker = line.charAt(0);
9507
+ const text = line.slice(1);
9783
9508
  if (marker === "\\") continue;
9784
9509
  if (marker === "-") {
9785
- currentFile.left.add(oldLine);
9510
+ addDiffRow(currentFile, {
9511
+ oldLine,
9512
+ newLine: null,
9513
+ kind: "del",
9514
+ text,
9515
+ hunk
9516
+ });
9786
9517
  oldLine += 1;
9787
9518
  oldRemaining -= 1;
9788
9519
  continue;
9789
9520
  }
9790
9521
  if (marker === "+") {
9791
- currentFile.right.add(newLine);
9522
+ addDiffRow(currentFile, {
9523
+ oldLine: null,
9524
+ newLine,
9525
+ kind: "add",
9526
+ text,
9527
+ hunk
9528
+ });
9792
9529
  newLine += 1;
9793
9530
  newRemaining -= 1;
9794
9531
  continue;
9795
9532
  }
9796
- currentFile.left.add(oldLine);
9797
- currentFile.right.add(newLine);
9533
+ addDiffRow(currentFile, {
9534
+ oldLine,
9535
+ newLine,
9536
+ kind: "context",
9537
+ text,
9538
+ hunk
9539
+ });
9798
9540
  oldLine += 1;
9799
9541
  newLine += 1;
9800
9542
  oldRemaining -= 1;
@@ -9813,21 +9555,41 @@ function commentableLines(diffText) {
9813
9555
  if (line.startsWith("+++ ")) {
9814
9556
  const filePath = diffHeaderPath(line, "b/") ?? oldPath;
9815
9557
  currentFile = filePath === null ? null : linesByPath.get(filePath) ?? {
9816
- left: /* @__PURE__ */ new Set(),
9817
- right: /* @__PURE__ */ new Set()
9558
+ left: /* @__PURE__ */ new Map(),
9559
+ right: /* @__PURE__ */ new Map(),
9560
+ rows: []
9818
9561
  };
9819
9562
  if (filePath !== null && currentFile) linesByPath.set(filePath, currentFile);
9820
9563
  continue;
9821
9564
  }
9822
- const hunk = HUNK_HEADER.exec(line);
9823
- if (!hunk || !currentFile) continue;
9824
- oldLine = Number(hunk[1]);
9825
- oldRemaining = hunk[2] === void 0 ? 1 : Number(hunk[2]);
9826
- newLine = Number(hunk[3]);
9827
- 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]);
9828
9572
  }
9829
9573
  return linesByPath;
9830
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
+ }
9831
9593
  function isLineCommentable(commentable, filePath, side, line) {
9832
9594
  const fileLines = commentable.get(filePath);
9833
9595
  if (!fileLines) return false;
@@ -9841,7 +9603,7 @@ function isSettledAtHead(entry, head) {
9841
9603
  if (entry?.draft?.status === "discarded") return true;
9842
9604
  if (!entry || entry.reviewedHead !== head) return false;
9843
9605
  if (entry.draft?.status === "error") return entry.reviewAttempts >= MAX_REVIEW_ATTEMPTS;
9844
- return entry.draft !== null || entry.skipReason !== null;
9606
+ return entry.draft !== null;
9845
9607
  }
9846
9608
  function shouldAutoReview(storedEntry, currentHead, nowMs, reReviewAfterMs) {
9847
9609
  if (!storedEntry) return true;
@@ -10001,24 +9763,7 @@ function presentedDraft(entry, draft) {
10001
9763
  function draftsNewestFirst(state) {
10002
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)] : []);
10003
9765
  }
10004
- var QUEUED_REVIEW_FIELDS = Object.keys(QueuedReview.shape);
10005
- function isSameQueuedReview(left, right) {
10006
- return JSON.stringify(left ?? null, QUEUED_REVIEW_FIELDS) === JSON.stringify(right, QUEUED_REVIEW_FIELDS);
10007
- }
10008
- function handReviewRows(state) {
10009
- return Object.values(state).flatMap((entry) => {
10010
- if (!entry.handReview || !entry.liveHead || entry.inFlight || entry.skipReason !== "fork" || entry.reviewedHead !== entry.liveHead) return [];
10011
- if (hasViewerReviewedAt(entry.githubReviews, entry.liveHead)) return [];
10012
- if (hasStandingViewerApproval({
10013
- githubReviews: entry.githubReviews,
10014
- reviewDecision: entry.reviewDecision,
10015
- liveHead: entry.liveHead,
10016
- requeuedHead: entry.requeuedHead
10017
- })) return [];
10018
- return [entry.handReview];
10019
- });
10020
- }
10021
- function teamReviewStatus({ ts, configured, reason = null, drafts = [], inFlight = [], queued = [], handReview = [], team }) {
9766
+ function teamReviewStatus({ ts, configured, reason = null, drafts = [], inFlight = [], queued = [], team }) {
10022
9767
  return {
10023
9768
  type: "team-review-status",
10024
9769
  ts,
@@ -10027,7 +9772,6 @@ function teamReviewStatus({ ts, configured, reason = null, drafts = [], inFlight
10027
9772
  drafts,
10028
9773
  inFlight,
10029
9774
  queued,
10030
- handReview,
10031
9775
  team
10032
9776
  };
10033
9777
  }
@@ -10475,7 +10219,7 @@ function readyDraft({ candidate, tier, reasons, result, reviewedBase, commentabl
10475
10219
  summary: result.summary,
10476
10220
  ...assessment ? { assessment } : {},
10477
10221
  body: rendered.body,
10478
- comments: rendered.comments,
10222
+ comments: rendered.comments.map((comment) => withExcerpt(comment, commentable)),
10479
10223
  status: "ready"
10480
10224
  };
10481
10225
  }
@@ -12976,8 +12720,9 @@ function createIssuesWiring({ config, gitWorkspace, broadcast, hasSession, store
12976
12720
  }
12977
12721
  const runner = createLaneRunner({
12978
12722
  tag: ISSUES_LANE_ID,
12979
- gate: () => ({ start: true }),
12723
+ gate: () => ({ start: config.issues?.enabled === true }),
12980
12724
  cfgKey: () => JSON.stringify({
12725
+ enabled: config.issues?.enabled === true,
12981
12726
  projects: config.projects.map((project) => ({
12982
12727
  id: project.id,
12983
12728
  path: project.path,
@@ -17124,6 +16869,76 @@ function createSessionHookLifecycle(options) {
17124
16869
  };
17125
16870
  }
17126
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
17127
16942
  //#region shared/coalesce-timer.ts
17128
16943
  function createCoalescedTimer({ mode, delayMs, run, setTimeoutFn = DEFAULT_TIMER_FNS.setTimeoutFn, clearTimeoutFn = DEFAULT_TIMER_FNS.clearTimeoutFn, unref = true }) {
17129
16944
  let timer = null;
@@ -17760,7 +17575,8 @@ function createSessionWorkspaceMembers({ plan, owner, sessionName, shareList, gi
17760
17575
  projectPath: member.repoPath,
17761
17576
  cwd: member.dir,
17762
17577
  shareList,
17763
- discardWork
17578
+ discardWork,
17579
+ cause: "session"
17764
17580
  });
17765
17581
  if (removed.ok) return null;
17766
17582
  return {
@@ -18284,7 +18100,8 @@ function createSessionWorktreeLifecycle({ id, projectPath, integrationBranch = n
18284
18100
  if (gitWorkspace && lifecycleState.workspace) try {
18285
18101
  await gitWorkspace.discard({
18286
18102
  projectPath: currentProjectPath(),
18287
- workspace: lifecycleState.workspace
18103
+ workspace: lifecycleState.workspace,
18104
+ cause: "session"
18288
18105
  });
18289
18106
  } catch {}
18290
18107
  clearWorktreeState("none");
@@ -19586,7 +19403,6 @@ var Session = class extends EventEmitter {
19586
19403
  _lastSignal;
19587
19404
  _compaction;
19588
19405
  _pendingPromptKind;
19589
- _isWaitingForMemory;
19590
19406
  _pendingPromptToolName;
19591
19407
  _pendingAttentionNotes;
19592
19408
  _titleQuiet;
@@ -19720,7 +19536,6 @@ var Session = class extends EventEmitter {
19720
19536
  this._lastSignal = null;
19721
19537
  this._compaction = createCompactionTracking();
19722
19538
  this._pendingPromptKind = null;
19723
- this._isWaitingForMemory = false;
19724
19539
  this._pendingPromptToolName = null;
19725
19540
  this._pendingAttentionNotes = [];
19726
19541
  this._titleQuiet = false;
@@ -20104,16 +19919,10 @@ var Session = class extends EventEmitter {
20104
19919
  });
20105
19920
  if (this._compaction.isCompacting !== wasCompacting) this._emitSessionDetail();
20106
19921
  }
20107
- setWaitingForMemory(isWaiting) {
20108
- if (isWaiting === this._isWaitingForMemory) return;
20109
- this._isWaitingForMemory = isWaiting;
20110
- this._emitSessionDetail();
20111
- }
20112
19922
  _emitSessionDetail() {
20113
19923
  this.emit("prompt-kind-change", {
20114
19924
  pendingPromptKind: this._pendingPromptKind,
20115
- isCompacting: this._compaction.isCompacting,
20116
- isWaitingForMemory: this._isWaitingForMemory
19925
+ isCompacting: this._compaction.isCompacting
20117
19926
  });
20118
19927
  }
20119
19928
  _finishCompaction() {
@@ -20499,13 +20308,10 @@ var Session = class extends EventEmitter {
20499
20308
  resumeSessionId: this._resumeSessionId,
20500
20309
  activeAgents: active,
20501
20310
  awaitingBackgroundTasks: this.backgroundTracking.awaitingBackgroundTasks(),
20502
- pendingWakeup: this.backgroundTracking.pendingWakeup(),
20503
20311
  pendingPromptKind: this._pendingPromptKind,
20504
20312
  conversationApproval: this._conversationApproval,
20505
20313
  hasConversationBridge: this._codexBridge !== null,
20506
20314
  isConversationViewOn: this._isConversationViewEnabled(),
20507
- isCompacting: this._compaction.isCompacting,
20508
- isWaitingForMemory: this._isWaitingForMemory,
20509
20315
  hasPlan: this._planReviewPort?.hasPlan(this.id) === true,
20510
20316
  mergeStatus: this.mergeStatus,
20511
20317
  mergeReason: this.mergeReason,
@@ -20608,7 +20414,6 @@ var Session = class extends EventEmitter {
20608
20414
  }
20609
20415
  async _startBody({ fresh = false, resumePrompt = null } = {}) {
20610
20416
  if (this._destroyed) return;
20611
- this.setWaitingForMemory(false);
20612
20417
  if (this.ptyProcess) {
20613
20418
  console.warn(`[session:${this.name}] start() called while PTY exists - killing previous PTY first`);
20614
20419
  const oldPid = this.ptyProcess.pid;
@@ -23522,7 +23327,7 @@ function createPrGh(cwd, commandRunner = run) {
23522
23327
  err: "invalid commit id",
23523
23328
  reviewId: null
23524
23329
  };
23525
- if (event !== "APPROVE" && event !== "COMMENT") return {
23330
+ if (!PostedReviewEvent.safeParse(event).success) return {
23526
23331
  ok: false,
23527
23332
  err: "invalid review event",
23528
23333
  reviewId: null
@@ -24125,7 +23930,6 @@ function createTeamReviewPoller(deps) {
24125
23930
  team: teamProfile,
24126
23931
  drafts: draftsNewestFirst(state),
24127
23932
  inFlight: inFlightReviews(),
24128
- handReview: handReviewRows(state),
24129
23933
  queued: waitingForSlot.filter((candidate) => !state[candidate.key]?.inFlight)
24130
23934
  }),
24131
23935
  error: pollingError,
@@ -24160,7 +23964,6 @@ function createTeamReviewPoller(deps) {
24160
23964
  draft: null,
24161
23965
  reviewedHead: null,
24162
23966
  inFlight: false,
24163
- skipReason: null,
24164
23967
  reviewAttempts: 0,
24165
23968
  updatedAt: now()
24166
23969
  };
@@ -24205,7 +24008,6 @@ function createTeamReviewPoller(deps) {
24205
24008
  ...wasRequeuedAtReviewedHead ? { requeuedHead: draft.reviewedHead } : {}
24206
24009
  };
24207
24010
  entry.reviewedHead = draft.reviewedHead;
24208
- entry.skipReason = null;
24209
24011
  entry.updatedAt = now();
24210
24012
  entry.reviewedAt = entry.updatedAt;
24211
24013
  entry.draft.reviewedAt = entry.reviewedAt;
@@ -24222,7 +24024,6 @@ function createTeamReviewPoller(deps) {
24222
24024
  status: "discarded"
24223
24025
  } : notMineDraft(args.candidate, args.detail.headRefOid);
24224
24026
  entry.reviewedHead = entry.draft.reviewedHead;
24225
- entry.skipReason = null;
24226
24027
  await settleNotMine(args.candidate.key, entry);
24227
24028
  if (waitingForSlot.length > 0 && !loop.isStopped()) loop.tick();
24228
24029
  }
@@ -24255,14 +24056,6 @@ function createTeamReviewPoller(deps) {
24255
24056
  entry.reviewedHead = isReviewedByViewer ? head : null;
24256
24057
  isDirty = true;
24257
24058
  }
24258
- if (entry.handReview && (entry.skipReason !== "fork" || entry.reviewedHead !== head)) {
24259
- delete entry.handReview;
24260
- isDirty = true;
24261
- }
24262
- if (entry.skipReason === "fork" && entry.reviewedHead === head && entry.draft?.status !== "discarded" && !isSameQueuedReview(entry.handReview, candidate)) {
24263
- entry.handReview = { ...candidate };
24264
- isDirty = true;
24265
- }
24266
24059
  if (entry.draft) {
24267
24060
  const priority = {
24268
24061
  requestSource: candidate.requestSource,
@@ -24520,18 +24313,7 @@ function createTeamReviewPoller(deps) {
24520
24313
  continue;
24521
24314
  }
24522
24315
  entry.updatedAt = now();
24523
- delete entry.handReview;
24524
24316
  isDirty = true;
24525
- if (triage.tier === "skip") {
24526
- if (resume) await discardResumable(resume);
24527
- entry.reviewedHead = detail.headRefOid;
24528
- entry.liveHead = detail.headRefOid;
24529
- entry.skipReason = triage.reasons.join(", ") || "skipped";
24530
- if (triage.reasons.includes("fork")) entry.handReview = { ...candidate };
24531
- delete entry.requeuedHead;
24532
- continue;
24533
- }
24534
- entry.skipReason = null;
24535
24317
  const priorReview = priorReviewFor(entry, detail.headRefOid) ?? void 0;
24536
24318
  const earlierReviewKept = earlierReviewToKeep(entry, detail.headRefOid);
24537
24319
  delete entry.priorReview;
@@ -24866,7 +24648,7 @@ function createTeamReviewPoller(deps) {
24866
24648
  };
24867
24649
  return settleNotMine(key, entry);
24868
24650
  }
24869
- 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;
24870
24652
  if (!candidate) return {
24871
24653
  ok: false,
24872
24654
  error: "That pull request is no longer in the review queue"
@@ -24889,7 +24671,6 @@ function createTeamReviewPoller(deps) {
24889
24671
  const notMineEntry = entryFor(key);
24890
24672
  notMineEntry.draft = notMineDraft(candidate, head);
24891
24673
  notMineEntry.reviewedHead = head;
24892
- delete notMineEntry.handReview;
24893
24674
  return settleNotMine(key, notMineEntry);
24894
24675
  }
24895
24676
  async function settleNotMine(key, entry) {
@@ -24921,10 +24702,6 @@ function createTeamReviewPoller(deps) {
24921
24702
  key,
24922
24703
  status: "running"
24923
24704
  };
24924
- if (entry.skipReason && entry.reviewedHead !== null && entry.reviewedHead === entry.liveHead) return {
24925
- ok: false,
24926
- error: `team review skips this pull request: ${entry.skipReason}`
24927
- };
24928
24705
  if (entry.draft && !isThreadPlaceholderDraft(entry.draft)) {
24929
24706
  if (!await requeue(key, entry.draft.reviewedHead)) return {
24930
24707
  ok: false,
@@ -25637,6 +25414,7 @@ function createGitWorkspace(opts = {}) {
25637
25414
  const rerereEnabled = opts.rerere !== false;
25638
25415
  const glimmervoidHome = opts.glimmervoidHome ?? glimmervoidHomeDir$1();
25639
25416
  const glimmervoidHomes = comparableGlimmervoidHomes(glimmervoidHome);
25417
+ const removalGuard = resolveRemovalGuard(opts.removalGuard, glimmervoidHome);
25640
25418
  const engineQueue = createSerialQueue();
25641
25419
  const isolatedCall = (body, args) => () => isolationContext.run({
25642
25420
  ...opts.isolation,
@@ -25712,6 +25490,54 @@ function createGitWorkspace(opts = {}) {
25712
25490
  if (await directoryExists$1(ownedPath)) return;
25713
25491
  await opts.ownedPaths?.forget(ownedPath);
25714
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
+ }
25715
25541
  async function recordOwnedFolder({ folderPath, owner }) {
25716
25542
  await opts.ownedPaths?.record({
25717
25543
  path: folderPath,
@@ -26021,11 +25847,7 @@ function createGitWorkspace(opts = {}) {
26021
25847
  isGit: false,
26022
25848
  reason: "no-base-branch"
26023
25849
  };
26024
- await run([
26025
- "branch",
26026
- "-D",
26027
- branch
26028
- ], projectPath);
25850
+ await deleteStaleSessionBranch(projectPath, branch);
26029
25851
  if (worktreeBase) try {
26030
25852
  fs.mkdirSync(worktreeBase, { recursive: true });
26031
25853
  } catch {}
@@ -26235,7 +26057,29 @@ function createGitWorkspace(opts = {}) {
26235
26057
  } catch {}
26236
26058
  }
26237
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
+ }
26238
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;
26239
26083
  removeWorktreeLinks(wt);
26240
26084
  await removeWorktreeEntry(runInProject(projectPath), wt, ["--force"]);
26241
26085
  await pruneWorktreeEntries(runInProject(projectPath));
@@ -26709,8 +26553,15 @@ function createGitWorkspace(opts = {}) {
26709
26553
  rerereReplayed
26710
26554
  };
26711
26555
  }
26712
- async function discardBody({ projectPath, workspace }) {
26556
+ async function discardBody({ projectPath, workspace, cause }) {
26713
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;
26714
26565
  if (workspace.cwd) {
26715
26566
  removeWorktreeLinks(workspace.cwd);
26716
26567
  await removeWorktreeEntry(runInProject(projectPath), workspace.cwd, ["--force"]);
@@ -26884,7 +26735,15 @@ function createGitWorkspace(opts = {}) {
26884
26735
  disableRepoCommands
26885
26736
  });
26886
26737
  }
26887
- 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);
26888
26747
  const isolation = {
26889
26748
  disableHooks,
26890
26749
  replaceEnv
@@ -26934,6 +26793,7 @@ function createGitWorkspace(opts = {}) {
26934
26793
  if (worktreePath === null) return removeWorktreeByPathBody({
26935
26794
  projectPath,
26936
26795
  branch,
26796
+ cause: "session",
26937
26797
  disableHooks,
26938
26798
  replaceEnv
26939
26799
  });
@@ -26947,6 +26807,7 @@ function createGitWorkspace(opts = {}) {
26947
26807
  projectPath,
26948
26808
  cwd: worktreePath,
26949
26809
  branch,
26810
+ cause: "session",
26950
26811
  disableHooks,
26951
26812
  replaceEnv
26952
26813
  });
@@ -26975,12 +26836,20 @@ function createGitWorkspace(opts = {}) {
26975
26836
  }).catch(() => fsp.rmdir(linkPath)).catch(() => {});
26976
26837
  }
26977
26838
  }
26978
- async function removeWorkspaceMemberBody({ projectPath, cwd, shareList, discardWork = false }) {
26839
+ async function removeWorkspaceMemberBody({ projectPath, cwd, branch, shareList, cause, discardWork = false }) {
26979
26840
  if (!cwd) return {
26980
26841
  ok: false,
26981
26842
  out: "",
26982
26843
  err: "a workspace member removal needs a path"
26983
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);
26984
26853
  await removeSharedLinks(cwd, shareList);
26985
26854
  const removed = await removeWorktreeEntry(runInProject(projectPath), cwd, discardWork ? ["--force", "--force"] : []);
26986
26855
  if (discardWork && await directoryExists$1(cwd)) await fsp.rm(cwd, {
@@ -27687,7 +27556,16 @@ function createGitWorkspace(opts = {}) {
27687
27556
  async function treeOid({ projectPath, sha, timeoutMs = MERGE_PROBE_TIMEOUT_MS }) {
27688
27557
  return run(["rev-parse", `${sha ?? ""}^{tree}`], projectPath, { timeout: timeoutMs });
27689
27558
  }
27690
- 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);
27691
27569
  const branchRef = `refs/heads/${name ?? ""}`;
27692
27570
  return run([
27693
27571
  "push",
@@ -27756,6 +27634,8 @@ function createGitWorkspace(opts = {}) {
27756
27634
  function createGitWorkspaceSync(opts = {}) {
27757
27635
  const glimmervoidHome = opts.glimmervoidHome ?? glimmervoidHomeDir$1();
27758
27636
  const glimmervoidHomes = comparableGlimmervoidHomes(glimmervoidHome);
27637
+ const removalGuard = resolveRemovalGuard(opts.removalGuard, glimmervoidHome);
27638
+ const log = opts.log || console;
27759
27639
  const git = opts.git || ((args, cwd) => execFileSync("git", args, {
27760
27640
  cwd,
27761
27641
  encoding: "utf8",
@@ -27880,7 +27760,61 @@ function createGitWorkspaceSync(opts = {}) {
27880
27760
  }
27881
27761
  return out;
27882
27762
  }
27883
- 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;
27884
27818
  if (cwd) {
27885
27819
  removeWorktreeLinks(cwd);
27886
27820
  if (isWorktreeHiddenFromGuardedSessions(cwd, glimmervoidHomes)) run([
@@ -27924,6 +27858,60 @@ function createGitWorkspaceSync(opts = {}) {
27924
27858
  removeWorktreeByPath
27925
27859
  };
27926
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
+ }
27927
27915
  function removeWorktreeLinks(wtDir) {
27928
27916
  if (!wtDir) return;
27929
27917
  let entries;
@@ -28062,7 +28050,8 @@ var UNMARKED_POST_WARNING = "The review was posted on GitHub, but its draft coul
28062
28050
  var SETTLED_OUTCOME_BY_ACTION = {
28063
28051
  approve: "approve",
28064
28052
  "approve-only": "approve-only",
28065
- comment: "comment"
28053
+ comment: "comment",
28054
+ "request-changes": "request-changes"
28066
28055
  };
28067
28056
  function teamReviewShouldStart(config) {
28068
28057
  const settings = readTeamReviewSettings(config);
@@ -28344,11 +28333,14 @@ function createTeamReviewDispatcher({ store, repoCache, gitWorkspace, spawnSessi
28344
28333
  async function removeCheckout({ projectPath, worktreePath }) {
28345
28334
  const removal = await gitWorkspace.removeWorktreeByPath({
28346
28335
  projectPath,
28347
- cwd: worktreePath
28336
+ cwd: worktreePath,
28337
+ cause: "team-review"
28348
28338
  }).catch((error) => ({
28349
28339
  ok: false,
28350
- err: errorMessage(error)
28340
+ err: errorMessage(error),
28341
+ removalKept: void 0
28351
28342
  }));
28343
+ if (removal.removalKept) return;
28352
28344
  if (removal.ok && !await pathExists(worktreePath)) return;
28353
28345
  log.warn(`[${TEAM_REVIEW_LANE_ID}] worktree removal failed for ${worktreePath}, deleting it directly: ${firstLine$1(removal.err ?? "") || "the directory is still present"}`);
28354
28346
  await deleteCheckout(gitWorkspace, worktreePath, log);
@@ -28920,9 +28912,9 @@ function createTeamReviewActions({ drafts, github, store, log = console, telemet
28920
28912
  reason: "comments-already-posted",
28921
28913
  error: "The inline comments were already posted, so the approval cannot carry them again"
28922
28914
  });
28923
- if (event === "COMMENT" && !body.trim() && comments.length === 0) return refusePost(timing, {
28915
+ if (event !== "APPROVE" && !body.trim() && comments.length === 0) return refusePost(timing, {
28924
28916
  reason: "empty-comment",
28925
- 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"
28926
28918
  });
28927
28919
  const liveHeadRead = await readStep(timing, "head-before-post", () => store.requestPrHead(draft.repo, draft.number, "priority"));
28928
28920
  if (liveHeadRead.isRateLimited) return refusePost(timing, {
@@ -30206,11 +30198,14 @@ function createBenchmarkWiring({ benchmarksRoot, runsRoot, isEnabled, broadcast,
30206
30198
  if (projectPath) {
30207
30199
  const removal = await gitWorkspace.removeWorktreeByPath({
30208
30200
  projectPath,
30209
- cwd: worktreePath
30201
+ cwd: worktreePath,
30202
+ cause: "benchmark"
30210
30203
  }).catch((error) => ({
30211
30204
  ok: false,
30212
- err: errorMessage(error)
30205
+ err: errorMessage(error),
30206
+ removalKept: void 0
30213
30207
  }));
30208
+ if (removal.removalKept) return;
30214
30209
  if (!removal.ok) log.warn(`[${BENCHMARK_LANE_ID}] could not remove leftover ${worktreePath}: ${firstLine(removal.err ?? "")}`);
30215
30210
  }
30216
30211
  await removeDirectoryOrWarn(worktreePath);
@@ -31679,8 +31674,24 @@ function createBranchGcPoller(deps) {
31679
31674
  const removed = await callGit(gitWorkspace.removeWorktreeByPath, {
31680
31675
  projectPath,
31681
31676
  cwd: decision.cwd,
31682
- branch: decision.branch
31677
+ branch: decision.branch,
31678
+ cause: "branch-gc"
31683
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
+ }
31684
31695
  if (!removed.ok) {
31685
31696
  noteGitError({
31686
31697
  projectPath,
@@ -31888,8 +31899,22 @@ function createBranchGcPoller(deps) {
31888
31899
  const deleted = await callGit(gitWorkspace.deleteRemoteBranch, {
31889
31900
  projectPath,
31890
31901
  name,
31891
- tipSha
31902
+ tipSha,
31903
+ cause: "branch-gc"
31892
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
+ }
31893
31918
  if (!deleted.ok) {
31894
31919
  noteGitError({
31895
31920
  projectPath,
@@ -38761,7 +38786,8 @@ function createRadarWiring({ config, investigationSessions, closeSessionDataClie
38761
38786
  if (resultFile) await resultFile.cleanup();
38762
38787
  await Promise.resolve(gitWorkspace.discard({
38763
38788
  projectPath: repoPath,
38764
- workspace
38789
+ workspace,
38790
+ cause: "radar"
38765
38791
  })).catch((e) => console.warn(`[posthog-poller] fix worktree discard failed: ${errorMessage(e)}`));
38766
38792
  }
38767
38793
  }
@@ -45444,6 +45470,131 @@ function createTraceChangeBroadcast({ source, broadcast, setTimeoutFunction = se
45444
45470
  return { stop };
45445
45471
  }
45446
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
45447
45598
  //#region server/uploads-wiring.ts
45448
45599
  var UPLOAD_RETAIN_DAYS = 7;
45449
45600
  var PRUNE_INTERVAL_MS = 864e5;
@@ -45452,8 +45603,8 @@ function createUploadsWiring({ configPath, liveSessionIds, setIntervalFn = DEFAU
45452
45603
  let timer = null;
45453
45604
  async function prune() {
45454
45605
  const retainedSessionIds = liveSessionIds();
45455
- await pruneAgedFiles({
45456
- directory: uploadsRoot,
45606
+ for (const directory of [uploadsRoot, configSiblingPath(configPath, SHOWN_FOLDER_NAME)]) await pruneAgedFiles({
45607
+ directory,
45457
45608
  suffixes: [""],
45458
45609
  retainDays: UPLOAD_RETAIN_DAYS,
45459
45610
  entryMode: "directory",
@@ -46970,14 +47121,21 @@ function createBackendLanes(dependencies) {
46970
47121
  });
46971
47122
  const spawnGate = createSpawnGate({ memoryAdmission });
46972
47123
  const ownedPaths = createOwnedPathsLedger({
46973
- filePath: ownedPathsLedgerPath(),
47124
+ filePath: configSiblingPath(configStore.configPath, "owned-paths.json"),
46974
47125
  log: logger
46975
47126
  });
47127
+ const protectedSessionIds = () => new Set([...config.projects.map((project) => project.id), ...allLiveSessions().map((session) => session.id)].flatMap((sessionId) => [sessionId, sanitizeWorktreeName(sessionId)]));
46976
47128
  const gitWorkspace = createGitWorkspace({
46977
47129
  rerere: config.worktreeRerere !== false,
46978
- ownedPaths
47130
+ ownedPaths,
47131
+ removalGuard: { protectedSessionIds },
47132
+ log: logger
47133
+ });
47134
+ const gitWorkspaceSync = createGitWorkspaceSync({
47135
+ ownedPaths,
47136
+ removalGuard: { protectedSessionIds },
47137
+ log: logger
46979
47138
  });
46980
- const gitWorkspaceSync = createGitWorkspaceSync({ ownedPaths });
46981
47139
  const laneLedger = createLaneLedger({
46982
47140
  ledgerPath: configSiblingPath(configStore.configPath, "usage-lanes.json"),
46983
47141
  retainDays: resolveUsageConfig(config.usage).warehouseRetainDays,
@@ -49987,6 +50145,7 @@ function summarizeCustomAgents(declarations) {
49987
50145
  function buildSettingsPayload({ configStore, rtkInstallStatus = null, telegramDelivery = null, coderEnvironments = null, agentLogins = null, resolveRtk = getRtkPath }) {
49988
50146
  return {
49989
50147
  ...configStore.getSettings(),
50148
+ isExperimentalBuild: configStore.isExperimentalBuild === true,
49990
50149
  rtkAvailable: !!resolveRtk(),
49991
50150
  saneYoloAvailable: !!resolvePackageBin("cc-safety-net", "cc-safety-net"),
49992
50151
  rtkInstall: rtkInstallStatus || { status: "idle" },
@@ -53822,6 +53981,7 @@ function createBackendControl(dependencies) {
53822
53981
  sendLaneSnapshotOnConnect("visions", laneAssembly.currentVisions, false);
53823
53982
  sendLaneSnapshotOnConnect("ingest", laneAssembly.currentIngest, true);
53824
53983
  sendLaneSnapshotOnConnect("session-docs", () => dependencies.sessionDocs, false);
53984
+ sendLaneSnapshotOnConnect("session-shown", () => dependencies.shownFiles, false);
53825
53985
  sendLaneSnapshotOnConnect("coherence-feed", () => dependencies.coherenceFeed, false);
53826
53986
  return { removeExitedSession: controlHandlers.removeExitedSession };
53827
53987
  }
@@ -54161,6 +54321,7 @@ function markInterrupted(journal, { reason, now }) {
54161
54321
  //#region server/update-check.ts
54162
54322
  var NPM_REGISTRY_PACKAGE_URL = "https://registry.npmjs.org/glimmervoid";
54163
54323
  var NPM_REGISTRY_LATEST_URL = `${NPM_REGISTRY_PACKAGE_URL}/latest`;
54324
+ var NPM_REGISTRY_EXPERIMENTAL_URL = `${NPM_REGISTRY_PACKAGE_URL}/next`;
54164
54325
  var REGISTRY_REQUEST_HEADERS = {
54165
54326
  Accept: "application/json",
54166
54327
  "User-Agent": "glimmervoid-update-check"
@@ -54168,10 +54329,6 @@ var REGISTRY_REQUEST_HEADERS = {
54168
54329
  var DEFAULT_TIMEOUT_MS = 8e3;
54169
54330
  var GIT_HEAD_TIMEOUT_MS = 3e3;
54170
54331
  var RELEASE_ANCESTRY_TIMEOUT_MS = 3e3;
54171
- var MAIN_CHANNEL_BUDGET_MS = DEFAULT_TIMEOUT_MS;
54172
- var MAIN_FETCH_TIMEOUT_MS = Math.floor(MAIN_CHANNEL_BUDGET_MS / 2);
54173
- var MAIN_REMOTE_TIP_TIMEOUT_MS = Math.floor(MAIN_CHANNEL_BUDGET_MS / 4);
54174
- var MAIN_BEHIND_COUNT_TIMEOUT_MS = Math.floor(MAIN_CHANNEL_BUDGET_MS / 4);
54175
54332
  var STATE_FILE_NAME = "update-check.json";
54176
54333
  var STATE_TTL_MS = 216e5;
54177
54334
  var RELEASE_SOURCE = "npm-registry";
@@ -54299,143 +54456,29 @@ async function resolveInstalledDeprecation({ currentVersion, fetchFn, signal })
54299
54456
  const versionUrl = `${NPM_REGISTRY_PACKAGE_URL}/${encodeURIComponent(currentVersion)}`;
54300
54457
  return parseRegistryDeprecation(await fetchRegistryDocument(versionUrl, fetchFn, signal));
54301
54458
  }
54302
- async function resolveLatestRegistryVersion({ currentVersion, fetchFn, signal }) {
54303
- const [latestDocument, deprecation] = await Promise.all([fetchRegistryDocument(NPM_REGISTRY_LATEST_URL, fetchFn, signal), resolveInstalledDeprecation({
54304
- currentVersion,
54305
- fetchFn,
54306
- signal
54307
- })]);
54308
- 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));
54309
54470
  if (!published) return {
54310
54471
  version: null,
54311
54472
  sha: null,
54312
- behindCount: null,
54313
54473
  reason: "release-check-failed",
54314
54474
  deprecation
54315
54475
  };
54316
54476
  return {
54317
54477
  ...published,
54318
- behindCount: null,
54319
54478
  reason: null,
54320
54479
  deprecation
54321
54480
  };
54322
54481
  }
54323
- function upstreamBranch(upstream) {
54324
- const slashIndex = upstream.indexOf("/");
54325
- if (slashIndex < 0 || slashIndex === upstream.length - 1) return null;
54326
- return upstream.slice(slashIndex + 1);
54327
- }
54328
- async function fetchMainRemote(packageRoot, remote, runCommand, fetchOrigin, signal) {
54329
- if (remote === "origin" && fetchOrigin) return fetchOrigin({
54330
- projectPath: packageRoot,
54331
- timeoutMs: MAIN_FETCH_TIMEOUT_MS
54332
- });
54333
- return runGitProbe(runCommand, [
54334
- "fetch",
54335
- "--prune",
54336
- remote
54337
- ], {
54338
- cwd: packageRoot,
54339
- timeout: MAIN_FETCH_TIMEOUT_MS,
54340
- signal
54341
- });
54342
- }
54343
- async function resolveLatestMain(installed, packageRoot, runCommand, fetchOrigin, signal) {
54344
- if (installed.flavor !== "clone") return {
54345
- version: null,
54346
- sha: null,
54347
- behindCount: null,
54348
- reason: "main-channel-requires-clone",
54349
- deprecation: null
54350
- };
54351
- if (!installed.installedBranch) return {
54352
- version: null,
54353
- sha: null,
54354
- behindCount: null,
54355
- reason: "no-branch",
54356
- deprecation: null
54357
- };
54358
- if (!installed.upstream) return {
54359
- version: null,
54360
- sha: null,
54361
- behindCount: null,
54362
- reason: "no-upstream",
54363
- deprecation: null
54364
- };
54365
- const remote = parseRemoteFromUpstream(installed.upstream);
54366
- const branch = upstreamBranch(installed.upstream);
54367
- if (!branch) return {
54368
- version: null,
54369
- sha: null,
54370
- behindCount: null,
54371
- reason: "no-upstream",
54372
- deprecation: null
54373
- };
54374
- if (!(await fetchMainRemote(packageRoot, remote, runCommand, fetchOrigin, signal)).ok) return {
54375
- version: null,
54376
- sha: null,
54377
- behindCount: null,
54378
- reason: "fetch-failed",
54379
- deprecation: null
54380
- };
54381
- if (signal.aborted) return {
54382
- version: null,
54383
- sha: null,
54384
- behindCount: null,
54385
- reason: "remote-tip-unavailable",
54386
- deprecation: null
54387
- };
54388
- const remoteTip = await runGitProbe(runCommand, [
54389
- "ls-remote",
54390
- remote,
54391
- `refs/heads/${branch}`
54392
- ], {
54393
- cwd: packageRoot,
54394
- timeout: MAIN_REMOTE_TIP_TIMEOUT_MS,
54395
- signal
54396
- });
54397
- const targetSha = normalizeSha(remoteTip.out.split(/\s+/)[0]);
54398
- if (!targetSha) return {
54399
- version: null,
54400
- sha: null,
54401
- behindCount: null,
54402
- reason: "remote-tip-unavailable",
54403
- deprecation: null
54404
- };
54405
- if (signal.aborted) return {
54406
- version: null,
54407
- sha: targetSha,
54408
- behindCount: null,
54409
- reason: "branch-count-unavailable",
54410
- deprecation: null
54411
- };
54412
- const countsProbe = await runGitProbe(runCommand, [
54413
- "rev-list",
54414
- "--left-right",
54415
- "--count",
54416
- `HEAD...${targetSha}`
54417
- ], {
54418
- cwd: packageRoot,
54419
- timeout: MAIN_BEHIND_COUNT_TIMEOUT_MS,
54420
- signal
54421
- });
54422
- const counts = countsProbe.ok ? parseLeftRightCount(countsProbe.out) : null;
54423
- if (!counts) return {
54424
- version: null,
54425
- sha: targetSha,
54426
- behindCount: null,
54427
- reason: "branch-count-unavailable",
54428
- deprecation: null
54429
- };
54430
- const reason = counts.behind > 0 && counts.ahead > 0 ? "branch-diverged" : null;
54431
- return {
54432
- version: null,
54433
- sha: targetSha,
54434
- behindCount: counts.ahead,
54435
- reason,
54436
- deprecation: null
54437
- };
54438
- }
54439
54482
  function readCheckState(statePath) {
54440
54483
  return readRecord(readJsonFile(statePath));
54441
54484
  }
@@ -54447,7 +54490,7 @@ function writeCheckState(statePath, state) {
54447
54490
  async function finish(installed, latestTarget, currentVersion, channel, platform, lastCheckAt, packageRoot, runCommand, signal) {
54448
54491
  let isLatestReleaseAncestorOfHead = false;
54449
54492
  const latestSha = normalizeSha(latestTarget.sha);
54450
- 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, [
54451
54494
  "merge-base",
54452
54495
  "--is-ancestor",
54453
54496
  latestSha,
@@ -54466,7 +54509,6 @@ async function finish(installed, latestTarget, currentVersion, channel, platform
54466
54509
  flavor: installed.flavor,
54467
54510
  platform,
54468
54511
  channel,
54469
- behindCount: latestTarget.behindCount,
54470
54512
  reason: latestTarget.reason,
54471
54513
  isLatestReleaseAncestorOfHead,
54472
54514
  deprecation: latestTarget.deprecation
@@ -54478,16 +54520,14 @@ async function finish(installed, latestTarget, currentVersion, channel, platform
54478
54520
  };
54479
54521
  }
54480
54522
  function targetFromCache(cached, currentVersion) {
54481
- const behindCount = Number.isInteger(cached.behindCount) && Number(cached.behindCount) >= 0 ? Number(cached.behindCount) : null;
54482
54523
  return {
54483
54524
  version: typeof cached.latestVersion === "string" ? cached.latestVersion : null,
54484
54525
  sha: typeof cached.latestSha === "string" ? cached.latestSha : null,
54485
- behindCount,
54486
54526
  reason: typeof cached.reason === "string" ? cached.reason : null,
54487
54527
  deprecation: cached.installedVersion === currentVersion && typeof cached.deprecation === "string" ? cached.deprecation : null
54488
54528
  };
54489
54529
  }
54490
- 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 } = {}) {
54491
54531
  const timer = setTimeout(() => abortController.abort(), timeoutMs);
54492
54532
  const signal = abortController.signal;
54493
54533
  let installed = {
@@ -54505,12 +54545,12 @@ async function checkForUpdate({ currentVersion, updateChannel = "release", fetch
54505
54545
  });
54506
54546
  const cached = readCheckState(statePath);
54507
54547
  const cacheOutrunByInstall = normalizeSha(cached?.latestSha) !== null && normalizeSha(cached?.latestSha) === installed.installedSha;
54508
- const expectedReleaseSource = updateChannel === "release" ? RELEASE_SOURCE : null;
54509
- const isCacheFromSameSource = expectedReleaseSource === null || cached?.releaseSource === expectedReleaseSource;
54548
+ const isCacheFromSameSource = cached?.releaseSource === RELEASE_SOURCE;
54510
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);
54511
54550
  signal.throwIfAborted();
54512
- const latestTarget = updateChannel === "main" ? await resolveLatestMain(installed, packageRoot$1, runCommand, fetchOrigin, signal) : await resolveLatestRegistryVersion({
54551
+ const latestTarget = await resolveLatestRegistryVersion({
54513
54552
  currentVersion,
54553
+ updateChannel,
54514
54554
  fetchFn,
54515
54555
  signal
54516
54556
  });
@@ -54519,8 +54559,7 @@ async function checkForUpdate({ currentVersion, updateChannel = "release", fetch
54519
54559
  channel: updateChannel,
54520
54560
  latestVersion: latestTarget.version,
54521
54561
  latestSha: normalizeSha(latestTarget.sha),
54522
- releaseSource: expectedReleaseSource,
54523
- behindCount: latestTarget.behindCount,
54562
+ releaseSource: RELEASE_SOURCE,
54524
54563
  reason: latestTarget.reason,
54525
54564
  installedVersion: currentVersion ?? null,
54526
54565
  deprecation: latestTarget.deprecation
@@ -54530,7 +54569,6 @@ async function checkForUpdate({ currentVersion, updateChannel = "release", fetch
54530
54569
  return finish(installed, {
54531
54570
  version: null,
54532
54571
  sha: null,
54533
- behindCount: null,
54534
54572
  reason: "update-check-failed",
54535
54573
  deprecation: null
54536
54574
  }, currentVersion, updateChannel, platform, now, packageRoot$1, runCommand, signal);
@@ -54594,15 +54632,7 @@ function createBackendUpdateCheck(dependencies) {
54594
54632
  reason: decision.reason,
54595
54633
  message: decision.message
54596
54634
  };
54597
- const fastForward = decideFastForward({
54598
- canFastForward: recorded.reason !== "branch-diverged",
54599
- isTargetAncestorOfHead: false
54600
- });
54601
- if (fastForward.ok) return null;
54602
- return {
54603
- reason: fastForward.reason,
54604
- message: fastForward.message
54605
- };
54635
+ return null;
54606
54636
  }
54607
54637
  function projectStatus(recorded) {
54608
54638
  const journal = dependencies.getUpdateJournal?.();
@@ -54699,7 +54729,6 @@ function createBackendUpdateCheck(dependencies) {
54699
54729
  currentVersion: dependencies.currentVersion,
54700
54730
  updateChannel: channel,
54701
54731
  abortController: updateAbort,
54702
- ...dependencies.fetchOrigin ? { fetchOrigin: dependencies.fetchOrigin } : {},
54703
54732
  ...ttlMs === void 0 ? {} : { ttlMs }
54704
54733
  }).catch(() => null).then((updateResult) => {
54705
54734
  if (stopped || channel !== normalizeUpdateChannel(dependencies.config.updateChannel)) return projectStatus({
@@ -56145,6 +56174,7 @@ function createUpdateApplyLane(dependencies) {
56145
56174
  stagedDescriptor = null;
56146
56175
  await dependencies.gitWorkspace.removeWorktreeByPath({
56147
56176
  projectPath: dependencies.packageRoot,
56177
+ cause: "update-apply",
56148
56178
  cwd: stagingPath
56149
56179
  });
56150
56180
  await publish(failRun(journal, {
@@ -56327,6 +56357,7 @@ function createUpdateApplyLane(dependencies) {
56327
56357
  }
56328
56358
  if ((await dependencies.gitWorkspace.removeWorktreeByPath({
56329
56359
  projectPath: dependencies.packageRoot,
56360
+ cause: "update-apply",
56330
56361
  cwd: stagingPath,
56331
56362
  admissionTimeoutMs: HANDOFF_QUEUE_ADMISSION_TIMEOUT_MS
56332
56363
  })).admissionRefused) refusedCleanupSteps.push("staging worktree removal");
@@ -56389,6 +56420,7 @@ function createUpdateApplyLane(dependencies) {
56389
56420
  }
56390
56421
  await dependencies.gitWorkspace.removeWorktreeByPath({
56391
56422
  projectPath: dependencies.packageRoot,
56423
+ cause: "update-apply",
56392
56424
  cwd: descriptor.stagingPath,
56393
56425
  admissionTimeoutMs: HANDOFF_QUEUE_ADMISSION_TIMEOUT_MS
56394
56426
  });
@@ -56404,6 +56436,7 @@ function createUpdateApplyLane(dependencies) {
56404
56436
  if (!descriptor || descriptor.state !== "staged" || inFlight) {
56405
56437
  await dependencies.gitWorkspace.removeWorktreeByPath({
56406
56438
  projectPath: dependencies.packageRoot,
56439
+ cause: "update-apply",
56407
56440
  cwd: stagingPath,
56408
56441
  admissionTimeoutMs: HANDOFF_QUEUE_ADMISSION_TIMEOUT_MS
56409
56442
  });
@@ -56477,6 +56510,7 @@ function createUpdateApplyLane(dependencies) {
56477
56510
  }), false);
56478
56511
  if (wasRunInFlight || await isStagingCheckoutPresent()) await dependencies.gitWorkspace.removeWorktreeByPath({
56479
56512
  projectPath: dependencies.packageRoot,
56513
+ cause: "update-apply",
56480
56514
  cwd: stagingPath
56481
56515
  });
56482
56516
  await fsPromises.rm(path.join(updatePath, QUARANTINED_DIST_NAME), {
@@ -57561,6 +57595,7 @@ function createCoherenceSessionFeed({ broadcastControl, debounceMs = 1500, now =
57561
57595
  //#region server/backend/backend.ts
57562
57596
  var AGENT_INGRESS_HOST = "127.0.0.1";
57563
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");
57564
57599
  const { staticDir = "auto", settingsDefaults } = options;
57565
57600
  const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
57566
57601
  const configStore = createConfigStore({
@@ -57610,6 +57645,7 @@ function createBackend(httpServer, options = {}) {
57610
57645
  let broadcastControl = null;
57611
57646
  let removeExitedSession = null;
57612
57647
  let agentApi = null;
57648
+ let shownFiles = null;
57613
57649
  let gitWorkspace = null;
57614
57650
  const guardrailsRefusal = checkGuardrailsDependencies({ log: console });
57615
57651
  warnWhenGitIsTooOld({ log: console });
@@ -57638,6 +57674,7 @@ function createBackend(httpServer, options = {}) {
57638
57674
  latestPlanTitle: laneAssembly.planReview?.latestPlanTitle(glimmervoidId) ?? null
57639
57675
  }),
57640
57676
  getAgentApi: () => agentApi,
57677
+ getShownFiles: () => shownFiles,
57641
57678
  sshSigner,
57642
57679
  recordOutcome,
57643
57680
  logger: console
@@ -57677,6 +57714,11 @@ function createBackend(httpServer, options = {}) {
57677
57714
  const { controlWss, dataWss, controlReplayLog, sessionDataClients, broadcastControl: broadcastControlFromWebSockets, broadcastLocalControl, closeSessionDataClients } = webSockets;
57678
57715
  broadcastControl = broadcastControlFromWebSockets;
57679
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;
57680
57722
  const coherenceFeed = createCoherenceSessionFeed({ broadcastControl: broadcastControlFromWebSockets });
57681
57723
  const sessions = /* @__PURE__ */ new Map();
57682
57724
  const agentSessions = /* @__PURE__ */ new Map();
@@ -57744,7 +57786,7 @@ function createBackend(httpServer, options = {}) {
57744
57786
  requestReview: (reference) => teamReview.requestReview(reference),
57745
57787
  teamReviewStatus: () => teamReview.getStatus(),
57746
57788
  myPrsStatus: () => myPrs.getStatus(),
57747
- readSettings: (request) => readSettingsForAgent(request, configFileViewOf(configStore.configPath, config, process.env))
57789
+ readSettings: (request) => readSettingsForAgent(request, configFileViewOf(configStore.configPath, config, process.env, configStore.isExperimentalBuild))
57748
57790
  },
57749
57791
  coderWorkspaceStatus: (workspace) => laneAssembly.coderEnv.workspaceStatus(workspace),
57750
57792
  logger: console
@@ -57862,7 +57904,6 @@ function createBackend(httpServer, options = {}) {
57862
57904
  currentVersion: packageJson.version,
57863
57905
  platform: process.platform,
57864
57906
  checkForUpdate: options.checkForUpdate || void 0,
57865
- fetchOrigin: (args) => assembledGitWorkspace.fetchOrigin(args),
57866
57907
  getUpdateJournal: () => updateApply.getJournal(),
57867
57908
  isRestartRequested: () => updateApply.isRestartRequested(),
57868
57909
  getControlClientCount: () => controlWss.clients.size,
@@ -58008,6 +58049,7 @@ function createBackend(httpServer, options = {}) {
58008
58049
  readPlanRevision: laneAssembly.planReview?.readPlanRevision ?? null,
58009
58050
  decidePlanReview: laneAssembly.planReview?.decide ?? null,
58010
58051
  sessionDocs,
58052
+ shownFiles: shownFilesWiring,
58011
58053
  coherenceFeed,
58012
58054
  serverBuild: () => serverBuild,
58013
58055
  telemetry,