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