glimmervoid 0.42.0 → 0.42.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/README.md +2 -2
  2. package/dist/bin/glimmervoid.js +8 -8
  3. package/dist/chunks/{adapters-D2u85MEH.js → adapters-Bga1N6Ie.js} +2 -2
  4. package/dist/chunks/{adapters-C8GfnK2r.js → adapters-DeqV4wUv.js} +1 -1
  5. package/dist/chunks/{agent-api-cli-B-1MqjKc.js → agent-api-cli-DWVeqlLu.js} +1 -1
  6. package/dist/chunks/{agent-api-cli-D7JPxNso.js → agent-api-cli-YvQ8WxHA.js} +1 -1
  7. package/dist/chunks/{agent-api-core-PwO7fPoc.js → agent-api-core-DFYLP1bF.js} +1 -1
  8. package/dist/chunks/config-store-CGrS8s6x.js +1 -0
  9. package/dist/chunks/{config-store-BxCW2IwK.js → config-store-DOGtTJ1d.js} +2 -69
  10. package/dist/chunks/{contracts-LXlDbl4R.js → contracts-Ds_eevZs.js} +2 -12
  11. package/dist/chunks/{control-messages-88sdxt-U.js → control-messages-Dgap3JRR.js} +14 -45
  12. package/dist/chunks/{main-DZzDU4Q4.js → main-Ywst1s6Z.js} +326 -483
  13. package/dist/chunks/{pair-cli-3KKNhZ22.js → pair-cli-PCXITdEd.js} +2 -2
  14. package/dist/chunks/{remote-config-Ce5nN0zL.js → remote-config-DeWURqqr.js} +1 -1
  15. package/dist/chunks/{settings-cli-BYY991YC.js → settings-cli-xpR7vk9L.js} +4 -4
  16. package/dist/chunks/{settings-readout-core-CWAhwZKN.js → settings-readout-core-C2mOk9Yz.js} +1 -1
  17. package/dist/chunks/{ssh-sign-cli-BbqYIPwh.js → ssh-sign-cli-rKiqiNem.js} +1 -1
  18. package/dist/client/assets/index-D9A5nTKg.js +177 -0
  19. package/dist/client/assets/index-NhAGtpDY.css +2 -0
  20. package/dist/client/index.html +2 -3
  21. package/dist/server/index.js +1 -1
  22. package/package.json +2 -2
  23. package/dist/chunks/config-store-BL8LDBuM.js +0 -1
  24. package/dist/client/assets/index-C9wfiVqt.js +0 -176
  25. package/dist/client/assets/index-PnaYWtoe.css +0 -2
@@ -4,9 +4,9 @@ import { a as sanitizeWorktreeName, i as safePathSegment, n as equalsIgnoringCas
4
4
  import { a as packageRoot, n as cliPath, o as relayPath, r as clientDir, s as resolvePackageBin, t as bundled } from "./runtime-paths-BkeftE60.js";
5
5
  import { $t as STATES, A as SIGN_URL_ENV, At as TeamReviewStatus, B as LinkedPr, Bt as shortSha, Ct as ReviewThreadId, Dt as TeamReviewCompareFiles, E as SESSION_DOC_BODY_CAP_BYTES, Ft as decisionAsIfApprovalRequired, G as MyPrKeepMergeableRequest, H as MyPrAutoRebase, Ht as NODE_ID_RE, I as SshSignRequest, J as MyPrMergeMethod, L as TASK_TITLE_CONTROL_CHARACTERS, Lt as hasStandingViewerApproval, Mt as TeamReviewThreadResult, Nt as TeamReviewThreadsRepository, O as SESSION_NAME_PATTERN, Ot as TeamReviewResolveResponse, Pt as canApproveAfterComment, Q as MyPrMergeWhenReadyRequest, Qt as RESTARTABLE_STATES, R as TaskTitle, S as ConversationQuestion, St as ReviewResult, Tt as TeamReviewActionRequest, U as MyPrCheckContext, Ut as REPO_SLUG_RE, V as MyPr, Vt as GH_SEGMENT, W as MyPrCheckContextsResponse, Wt as repoParts, Xt as KILLABLE_STATES, Y as MyPrMergeRequest, Yt as BADGE_LABELS, Z as MyPrMergeStateResponse, Zt as MERGEABLE_LIVE_STATES, _ as CONVERSATION_TOOL_RESULT_HOOK_EVENT, _t as QueuedReview, bt as ReviewDraft, dt as EarlierReview, ft as FindingSeverity, g as CONVERSATION_PERMISSION_HOOK_EVENT, gt as PrDetail, h as CONVERSATION_APPROVAL_SUMMARY_MAX_CHARS, ht as PostingPlan, it as MyPrsState, j as SSH_SIGN_BODY_CAP_BYTES, jt as TeamReviewThreadCommentsResponse, k as SESSION_NAME_RULE_TEXT, kt as TeamReviewStateEntry, lt as CommitSha, m as COHERENCE_HOOKS_INACTIVE, mt as GithubReviewState, n as AGENT_URL_ENV, nt as MyPrThreadsResponse, q as MyPrLaneEntry, r as ASK_USER_QUESTION_TOOL_NAME, rt as MyPrsSnapshot, tt as MyPrThreadNode, ut as DECIDING_REVIEW_STATES, w as RefinedTaskTitle, wt as ReviewVerdict, xt as ReviewFinding, yt as ReviewComment, zt as HEX_SHA_RE } from "./session-3eSP3t1i.js";
6
6
  import { n as execFileAsync, o as spawn, r as execFileSync, t as execFile } from "./child-process-safe-D4hyVIXj.js";
7
- import { At as planTitle, C as GithubPrsFrame, Ct as PLAN_HOOK_EVENT, D as WorkflowsSettings, Dt as PlanDecision, E as WorkflowLabelName, Et as PLAN_TOOL_NAME, Ft as IssuesSearchResponse, It as IssuesState, L as KEEP_MERGEABLE_TIMEOUT_MINUTES_RANGE, Lt as IssuesStatus, Mt as GithubIssueResponse, Nt as IssueKey, Ot as PlanRevision, Pt as IssueUpdate, Rt as issuePullRequestStateFromGithub, S as GithubPrLightSearchNode, St as PLAN_BODY_CAP_BYTES, T as WorkflowCommentBody, Tt as PLAN_RESULT_HOOK_EVENT, _ as MergedPrListing, _t as TraceCheckpoint, b as GithubPrDetailNode, bt as ChangeMap, c as pullRequestKeyFromReference, d as BenchmarkId, f as BenchmarkJudgement, g as CommitComparison, gt as MAX_RAW_LINE_CHARS, h as BenchmarkSuite, ht as SessionSetup, i as DIFF_ANNOTATION_NOTE_MAX_CHARS, jt as GithubIssuePullRequestsRepository, k as WorkflowsState, kt as parseExitPlanModeHookPayload, l as BenchmarkActionRequest, m as BenchmarkStatus, mt as UpdateJournal, n as ClientMessage, p as BenchmarkRun, rt as USAGE_INTEGER_RANGES, t as CONTROL_FRAME_MAX_BYTES, u as BenchmarkCase, v as MinedPrReviewData, vt as TraceRecord, w as comparablePrKey, wt as PLAN_HOOK_OUTPUT_MAX_CHARS, x as GithubPrKey, xt as changeMapFactId, y as PrCheckoutCaseInput, yt as CoherenceJournalOutput } from "./control-messages-88sdxt-U.js";
7
+ import { At as GithubIssueResponse, C as comparablePrKey, Ct as PLAN_RESULT_HOOK_EVENT, Dt as parseExitPlanModeHookPayload, E as WorkflowsSettings, Et as PlanRevision, F as KEEP_MERGEABLE_TIMEOUT_MINUTES_RANGE, Ft as IssuesStatus, It as issuePullRequestStateFromGithub, Mt as IssueUpdate, Nt as IssuesSearchResponse, O as WorkflowsState, Ot as planTitle, Pt as IssuesState, S as GithubPrsFrame, St as PLAN_HOOK_OUTPUT_MAX_CHARS, T as WorkflowLabelName, Tt as PlanDecision, _ as MinedPrReviewData, _t as CoherenceJournalOutput, b as GithubPrKey, bt as PLAN_BODY_CAP_BYTES, c as BenchmarkActionRequest, d as BenchmarkJudgement, f as BenchmarkRun, ft as UpdateJournal, g as MergedPrListing, gt as TraceRecord, h as CommitComparison, ht as TraceCheckpoint, jt as IssueKey, kt as GithubIssuePullRequestsRepository, l as BenchmarkCase, m as BenchmarkSuite, mt as MAX_RAW_LINE_CHARS, n as ClientMessage, p as BenchmarkStatus, pt as SessionSetup, s as pullRequestKeyFromReference, t as CONTROL_FRAME_MAX_BYTES, tt as USAGE_INTEGER_RANGES, u as BenchmarkId, v as PrCheckoutCaseInput, vt as ChangeMap, w as WorkflowCommentBody, wt as PLAN_TOOL_NAME, x as GithubPrLightSearchNode, xt as PLAN_HOOK_EVENT, y as GithubPrDetailNode, yt as changeMapFactId } from "./control-messages-Dgap3JRR.js";
8
8
  import { a as permitsHelpCommand, r as describeSessionLimits } from "./session-routes-B2oSzmQn.js";
9
- import { a as decideSpawnAllowance, c as describeKeptChildAttention, d as describeUnpushedCloseRefusal, h as projectReviews, i as decideAgentRequest, l as describePushLanding, m as projectMyPrs, n as REFUSAL_REASON, o as deriveChildSessionName, p as parseAgentVerb, s as describeChildEndNote, u as describeRoleRefusal } from "./agent-api-core-PwO7fPoc.js";
9
+ import { a as decideSpawnAllowance, c as describeKeptChildAttention, d as describeUnpushedCloseRefusal, h as projectReviews, i as decideAgentRequest, l as describePushLanding, m as projectMyPrs, n as REFUSAL_REASON, o as deriveChildSessionName, p as parseAgentVerb, s as describeChildEndNote, u as describeRoleRefusal } from "./agent-api-core-DFYLP1bF.js";
10
10
  import { n as glimmervoidHomeDir, t as decideConfigPath } from "./config-path-core-DFt-TemS.js";
11
11
  import { i as parseCommandLine, n as warnWhenGitIsTooOld } from "./git-version-DqagvnAZ.js";
12
12
  import { n as checkGuardrailsDependencies, t as allowGuardedSpawn } from "./guardrails-deps-BjPzk1rE.js";
@@ -21,13 +21,13 @@ import { n as DEFAULT_MAX_MS, r as nextBackoffMs, t as DEFAULT_BASE_MS } from ".
21
21
  import { n as unrefTimer, t as DEFAULT_TIMER_FNS } from "./timer-deps-u2DAQLok.js";
22
22
  import { n as shellQuote } from "./hook-command-core-qTNK-xMF.js";
23
23
  import { C as parseCgroupBytes, S as memoryReserveBytes, T as parseMemAvailableBytes, _ as createSpawnGate, a as jsonStateLoadError, b as cgroupLimitBytes, c as loadedJsonValue, d as writeJsonAtomicSync, f as writeTextAtomic, g as createSerialQueue, h as createMemoryAdmission, i as createJsonStateWriter, m as createKeyedSerialQueue, n as appendJsonLines, o as loadJsonStateFile, p as writeTextAtomicSync, r as createJsonStateStore, s as loadJsonStateFileSync, t as appendJsonLine, u as writeJsonAtomic, v as isQueueAdmissionTimeout, w as parseCgroupV2Path, x as headroomFromReadings, y as cgroupAncestorPaths } from "./json-file-DFCTvPjj.js";
24
- import { $ as DEFAULT_BRANCH_GC_PREFIXES, B as buildExceptionProperties, F as TELEMETRY_BATCH_URL, G as exceptionFingerprint, H as decideIsDevInstall, I as TELEMETRY_FLAGS_URL, J as resolveProjectToken, K as nodeMajorVersion, L as adapterBucket, P as FIRST_RUN_NOTICE, R as buildAiGenerationEvents, U as decideRemoteTelemetryState, V as classifySessionExit, W as decideTelemetryConsent, Z as stableConfigKey, a as generateProjectId, at as worktreeIntegrationTips, b as validateSettingsBlock, ct as isSameDirectoryPath, d as assetForPlatform, et as matchedPrefix, f as decideRtkInstall, g as verifyDigest, h as isRtkBinaryName, i as ensureProjectIds, it as usablePrefixes, m as installTargetPath, n as DEFAULT_CONFIG, nt as planWorktreeGc, o as glimmervoidHomeDir$1, ot as canonicalizePath, p as findEscapingArchiveMember, q as parsePosthogReleaseId, r as createConfigStore, rt as sessionIdFromBranch, st as comparableDirectoryPath, t as ABORT_CONFIG_SAVE, tt as planBranchGc, v as mergeSettingsBlock, y as settingsBlockSpecFor, z as buildBrowserExceptionProperties } from "./config-store-BxCW2IwK.js";
25
- import { E as USAGE_VENDOR_KEYS, T as USAGE_COST_MODES, _ as PersistedTaskTitle, a as parseDataClientMessage, b as configIssueMessage, c as PendingCrashReport, d as TelemetryState, g as ConfigUpdate, i as parseDataAttachHistory, l as TELEMETRY_EVENT_SCHEMAS, m as BrowserConfig, n as HookEnvelope, o as DashboardTelemetryReport, r as DEEP_HISTORY_SCROLLBACK_LINES, t as HTTP_URL_RE, v as RUNTIME_CONFIG_SCALAR_KEYS, y as TASK_TITLE_SOURCE_PRIORITY } from "./contracts-LXlDbl4R.js";
26
- import { A as dedupeCodexFiles, B as shouldPersistWarehouse, C as shortSha$1, D as codexSessionIdFromPath, E as codexRootCandidates, F as passOutcome, H as claudeCode, I as projectDirCandidates, L as resolveProjectsDirs, M as grokHomes, N as grokRootCandidates, O as continuationDelayMs, P as isUsageFile, R as scopedClaudeRoots, S as parseResolvedSha, T as codexHomes, V as splitLines$1, _ as isCheckFresh, a as describeAgentResolvability, b as parseRegistryDeprecation, c as isKnownAgentId, d as resolveAdapter, f as setCustomAgents, g as decideUpdateStatus, h as decideInstallFlavor, i as customAgentFingerprint, j as expandTilde, k as decideFileRead, l as listAgentIds, m as readHostCodexVersion, n as cachedAgentResolvability, o as getAdapter, p as codex, r as commandFor, s as hookProfileOf, t as DEFAULT_AGENT_ID, u as resetCommandCache, v as normalizeSha, w as codexFallbackRoots, x as parseRegistryLatest, y as normalizeUpdateChannel, z as shouldEvaluateDespiteIoFailures } from "./adapters-D2u85MEH.js";
24
+ import { $ as DEFAULT_BRANCH_GC_PREFIXES, B as buildExceptionProperties, F as TELEMETRY_BATCH_URL, G as exceptionFingerprint, H as decideIsDevInstall, I as TELEMETRY_FLAGS_URL, J as resolveProjectToken, K as nodeMajorVersion, L as adapterBucket, P as FIRST_RUN_NOTICE, R as buildAiGenerationEvents, U as decideRemoteTelemetryState, V as classifySessionExit, W as decideTelemetryConsent, Z as stableConfigKey, a as generateProjectId, at as worktreeIntegrationTips, b as validateSettingsBlock, ct as isSameDirectoryPath, d as assetForPlatform, et as matchedPrefix, f as decideRtkInstall, g as verifyDigest, h as isRtkBinaryName, i as ensureProjectIds, it as usablePrefixes, m as installTargetPath, n as DEFAULT_CONFIG, nt as planWorktreeGc, o as glimmervoidHomeDir$1, ot as canonicalizePath, p as findEscapingArchiveMember, q as parsePosthogReleaseId, r as createConfigStore, rt as sessionIdFromBranch, st as comparableDirectoryPath, t as ABORT_CONFIG_SAVE, tt as planBranchGc, v as mergeSettingsBlock, y as settingsBlockSpecFor, z as buildBrowserExceptionProperties } from "./config-store-DOGtTJ1d.js";
25
+ import { T as USAGE_VENDOR_KEYS, _ as PersistedTaskTitle, a as parseDataClientMessage, b as configIssueMessage, c as PendingCrashReport, d as TelemetryState, g as ConfigUpdate, i as parseDataAttachHistory, l as TELEMETRY_EVENT_SCHEMAS, m as BrowserConfig, n as HookEnvelope, o as DashboardTelemetryReport, r as DEEP_HISTORY_SCROLLBACK_LINES, t as HTTP_URL_RE, v as RUNTIME_CONFIG_SCALAR_KEYS, w as USAGE_COST_MODES, y as TASK_TITLE_SOURCE_PRIORITY } from "./contracts-Ds_eevZs.js";
26
+ import { A as dedupeCodexFiles, B as shouldPersistWarehouse, C as shortSha$1, D as codexSessionIdFromPath, E as codexRootCandidates, F as passOutcome, H as claudeCode, I as projectDirCandidates, L as resolveProjectsDirs, M as grokHomes, N as grokRootCandidates, O as continuationDelayMs, P as isUsageFile, R as scopedClaudeRoots, S as parseResolvedSha, T as codexHomes, V as splitLines$1, _ as isCheckFresh, a as describeAgentResolvability, b as parseRegistryDeprecation, c as isKnownAgentId, d as resolveAdapter, f as setCustomAgents, g as decideUpdateStatus, h as decideInstallFlavor, i as customAgentFingerprint, j as expandTilde, k as decideFileRead, l as listAgentIds, m as readHostCodexVersion, n as cachedAgentResolvability, o as getAdapter, p as codex, r as commandFor, s as hookProfileOf, t as DEFAULT_AGENT_ID, u as resetCommandCache, v as normalizeSha, w as codexFallbackRoots, x as parseRegistryLatest, y as normalizeUpdateChannel, z as shouldEvaluateDespiteIoFailures } from "./adapters-Bga1N6Ie.js";
27
27
  import { t as HOOK_TOOLS } from "./hook-tools-Dl7-r2Eb.js";
28
28
  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-Ce5nN0zL.js";
30
- import { n as configFileViewOf, u as readSettingsForAgent } from "./settings-readout-core-CWAhwZKN.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-DeWURqqr.js";
30
+ import { n as configFileViewOf, u as readSettingsForAgent } from "./settings-readout-core-C2mOk9Yz.js";
31
31
  import { _ as escapeMarkup, g as createLaneLog, n as createVisionsSetup } from "./visions-setup-DB2P8JUL.js";
32
32
  import { t as getRtkPath } from "./rtk-resolver-BDHi6RMv.js";
33
33
  import { createRequire } from "node:module";
@@ -3874,7 +3874,6 @@ function createBackendShutdown(dependencies) {
3874
3874
  stoppers.add("telegram-outbox", () => dependencies.telegramOutbox.idle());
3875
3875
  destroySessions([
3876
3876
  dependencies.visionsSessions,
3877
- dependencies.changeMapSessions,
3878
3877
  dependencies.taskTitleSessions ?? /* @__PURE__ */ new Map(),
3879
3878
  dependencies.benchmarkSessions ?? /* @__PURE__ */ new Map()
3880
3879
  ], pendingReaps);
@@ -20062,6 +20061,10 @@ var Session = class extends EventEmitter {
20062
20061
  this._output.push(data);
20063
20062
  if (this.listenerCount("data") > 0) this.emit("data", data);
20064
20063
  }
20064
+ _isExitHeadedForFailure(event) {
20065
+ if (this.state === STATES.FAILED) return false;
20066
+ return TRANSITIONS[this.state]?.[event] === STATES.FAILED;
20067
+ }
20065
20068
  _handleSpawnedPtyExit(guardrailsLaunch, exitCode, signal) {
20066
20069
  this._disposeGuardrailsLaunch(guardrailsLaunch);
20067
20070
  this._stopConversationBridge();
@@ -20081,6 +20084,11 @@ var Session = class extends EventEmitter {
20081
20084
  if (pid) this._exitReap = this._reapTreeOnce(pid);
20082
20085
  const { event, detail } = decideExitTransition(this.state, exitCode, signal, this._receivedFirstOutput);
20083
20086
  const reason = detail.reason || null;
20087
+ const pendingRemoteExitReason = this._isExitHeadedForFailure(event) ? this._getRemoteLaunch()?.describeExit().catch(() => null) : void 0;
20088
+ if (pendingRemoteExitReason) {
20089
+ const remoteExitReason = await pendingRemoteExitReason;
20090
+ if (remoteExitReason && this._isExitHeadedForFailure(event)) this.failureReason = remoteExitReason;
20091
+ }
20084
20092
  this.transition(event, detail);
20085
20093
  if (this._recorder) {
20086
20094
  this._recorder.writeFooter("pty_exit", exitCode);
@@ -29812,6 +29820,26 @@ function coderErrorDetail(stderr) {
29812
29820
  function isCoderSignedOutError(stderr) {
29813
29821
  return typeof stderr === "string" && stderr.includes("You are signed out or your session has expired");
29814
29822
  }
29823
+ function remoteWorkRefusal({ uncommittedStatus, unpushedLog, stashList, branch, workspace }) {
29824
+ if (uncommittedStatus.trim()) return `Branch ${branch} in Coder workspace ${workspace} has uncommitted changes`;
29825
+ if (unpushedLog.trim()) return `Branch ${branch} in Coder workspace ${workspace} has commits that are not pushed`;
29826
+ if (stashList.trim()) return `Branch ${branch} in Coder workspace ${workspace} has stashed changes`;
29827
+ return null;
29828
+ }
29829
+ var describeStoppedExit = (workspace) => `Coder workspace ${workspace} stopped, by its idle stop or its stop-after limit. Restart to start it again.`;
29830
+ var describeDeletedExit = (workspace) => `Coder workspace ${workspace} was deleted. Restart to get a fresh workspace.`;
29831
+ var REMOTE_EXIT_REASON_BY_STATUS = {
29832
+ stopped: describeStoppedExit,
29833
+ stopping: describeStoppedExit,
29834
+ deleting: describeDeletedExit,
29835
+ gone: describeDeletedExit,
29836
+ failed: (workspace) => `Coder workspace ${workspace} failed. Restart to get a fresh workspace.`
29837
+ };
29838
+ function remoteExitReason({ workspace, status }) {
29839
+ const describeExit = status === null ? void 0 : REMOTE_EXIT_REASON_BY_STATUS[status];
29840
+ if (describeExit) return describeExit(workspace);
29841
+ return `The connection to Coder workspace ${workspace} was lost. Restart to reconnect.`;
29842
+ }
29815
29843
  function coderNotSignedInReason(failure) {
29816
29844
  return `The coder CLI on this host is not signed in. Run coder login, then try again. (${failure})`;
29817
29845
  }
@@ -29904,6 +29932,10 @@ function createCoderEnvPoller({ listWorkspaces, stopWorkspace, deleteWorkspace,
29904
29932
  //#endregion
29905
29933
  //#region server/coder/coder-env-wiring.ts
29906
29934
  var CODER_UNAVAILABLE_REASON = "Coder environments are unavailable on this host";
29935
+ var REMOTE_SESSIONS_OFF_REFUSAL = {
29936
+ ok: false,
29937
+ reason: "Remote Coder sessions are off. Turn on Remote Coder sessions (experimental) in Settings."
29938
+ };
29907
29939
  function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {}, loadOwnedWorkspaces, saveOwnedWorkspaces, runCoder: executeCoder, env = process.env, platform = process.platform, fetch: fetchReady = globalThis.fetch, createPoller = createCoderEnvPoller, now = Date.now, log = console, ...pollerDeps }) {
29908
29940
  let binaryProbe = null;
29909
29941
  let isCoderAvailable = false;
@@ -29914,6 +29946,7 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
29914
29946
  const lastSeenStatusByWorkspace = /* @__PURE__ */ new Map();
29915
29947
  const provisionByProject = /* @__PURE__ */ new Map();
29916
29948
  const failedProvisionByProject = /* @__PURE__ */ new Map();
29949
+ const renewalByProject = /* @__PURE__ */ new Map();
29917
29950
  const setTimeoutFn = pollerDeps.setTimeoutFn ?? DEFAULT_TIMER_FNS.setTimeoutFn;
29918
29951
  let ownedWorkspaces = /* @__PURE__ */ new Set([...loadOwnedWorkspaces(), ...currentMappings().map((mapping) => mapping.workspace)]);
29919
29952
  let ownedSaveChain = Promise.resolve();
@@ -30001,18 +30034,29 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30001
30034
  if (workspaces !== null) await pruneOwned(workspaces);
30002
30035
  return listingJson;
30003
30036
  }
30004
- async function listWorkspaces() {
30037
+ async function listWorkspaces(timeoutMs) {
30005
30038
  const workspaces = recordListing((await command([
30006
30039
  "list",
30007
30040
  "-o",
30008
30041
  "json"
30009
- ])).stdout);
30042
+ ], timeoutMs)).stdout);
30010
30043
  if (workspaces === null) throw new Error("Invalid Coder workspace listing");
30011
30044
  return workspaces;
30012
30045
  }
30013
30046
  function workspaceStatus(workspace) {
30014
30047
  return lastSeenStatusByWorkspace.get(workspace) ?? null;
30015
30048
  }
30049
+ async function describeRemoteExit(workspace) {
30050
+ const workspaces = await listWorkspaces(1e4).catch(() => null);
30051
+ if (workspaces === null) return remoteExitReason({
30052
+ workspace,
30053
+ status: null
30054
+ });
30055
+ return remoteExitReason({
30056
+ workspace,
30057
+ status: workspaces.find((candidate) => candidate.name === workspace)?.status ?? "gone"
30058
+ });
30059
+ }
30016
30060
  function isConfigured() {
30017
30061
  return Boolean(config.coder?.environments?.length) && (platform === "linux" || platform === "darwin");
30018
30062
  }
@@ -30185,10 +30229,7 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30185
30229
  };
30186
30230
  }
30187
30231
  async function reserve({ projectId, environmentName, branch, liveCount }) {
30188
- if (config.remoteSessions !== true) return {
30189
- ok: false,
30190
- reason: "Remote Coder sessions are off. Turn on Remote Coder sessions (experimental) in Settings."
30191
- };
30232
+ if (config.remoteSessions !== true) return REMOTE_SESSIONS_OFF_REFUSAL;
30192
30233
  const environment = resolveEnvironment(environmentName);
30193
30234
  if (!environment) return {
30194
30235
  ok: false,
@@ -30211,6 +30252,12 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30211
30252
  ok: false,
30212
30253
  reason: unavailableReason
30213
30254
  };
30255
+ return freshMapping({
30256
+ environmentName,
30257
+ branch
30258
+ });
30259
+ }
30260
+ function freshMapping({ environmentName, branch }) {
30214
30261
  const workspace = workspaceNameFromHex(randomBytes(6).toString("hex"));
30215
30262
  if (workspace === null) return {
30216
30263
  ok: false,
@@ -30226,6 +30273,74 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30226
30273
  }
30227
30274
  };
30228
30275
  }
30276
+ function renew(request) {
30277
+ if (config.remoteSessions !== true) return Promise.resolve(REMOTE_SESSIONS_OFF_REFUSAL);
30278
+ const inFlightRenewal = renewalByProject.get(request.projectId);
30279
+ if (inFlightRenewal) return inFlightRenewal;
30280
+ const renewal = renewUnclaimed(request);
30281
+ renewalByProject.set(request.projectId, renewal);
30282
+ renewal.finally(() => renewalByProject.delete(request.projectId));
30283
+ return renewal;
30284
+ }
30285
+ async function renewUnclaimed({ projectId, repoRemoteUrl, baseBranch, liveCount }) {
30286
+ if (provisionByProject.has(projectId)) return {
30287
+ ok: true,
30288
+ renewed: false
30289
+ };
30290
+ const mapping = config.projects.find((project) => project.id === projectId)?.environment;
30291
+ if (!mapping) return {
30292
+ ok: false,
30293
+ reason: `Project ${projectId} has no Coder workspace mapping`
30294
+ };
30295
+ if (!failedProvisionByProject.has(projectId) && await isWorkspaceListed(mapping.workspace) && workspaceStatus(mapping.workspace) !== "failed") return {
30296
+ ok: true,
30297
+ renewed: false
30298
+ };
30299
+ const environment = resolveEnvironment(mapping.name);
30300
+ if (!environment) return {
30301
+ ok: false,
30302
+ reason: `Unknown Coder environment: ${mapping.name}`
30303
+ };
30304
+ const limitReason = liveLimitRefusal({
30305
+ liveCount,
30306
+ maxLive: environment.maxLive,
30307
+ environmentName: mapping.name
30308
+ });
30309
+ if (limitReason) return {
30310
+ ok: false,
30311
+ reason: limitReason
30312
+ };
30313
+ if (!await ensureAvailable()) return {
30314
+ ok: false,
30315
+ reason: unavailableReason
30316
+ };
30317
+ const fresh = freshMapping({
30318
+ environmentName: mapping.name,
30319
+ branch: mapping.branch
30320
+ });
30321
+ if (!fresh.ok) return fresh;
30322
+ try {
30323
+ await saveMapping(projectId, fresh.mapping);
30324
+ } catch (error) {
30325
+ return {
30326
+ ok: false,
30327
+ reason: `Could not save the new Coder workspace mapping: ${errorMessage(error)}`
30328
+ };
30329
+ }
30330
+ failedProvisionByProject.delete(projectId);
30331
+ provision({
30332
+ projectId,
30333
+ mapping: fresh.mapping,
30334
+ repoRemoteUrl,
30335
+ baseBranch
30336
+ }).then((provisioned) => {
30337
+ if (!provisioned.ok) log.warn(`[coder-env] fresh Coder workspace for project ${projectId} failed: ${provisioned.reason}`);
30338
+ });
30339
+ return {
30340
+ ok: true,
30341
+ renewed: true
30342
+ };
30343
+ }
30229
30344
  function provision({ projectId, mapping, repoRemoteUrl, baseBranch }) {
30230
30345
  const savedMapping = config.projects.find((candidate) => candidate.id === projectId)?.environment;
30231
30346
  if (JSON.stringify(savedMapping) !== JSON.stringify(mapping)) return Promise.resolve({
@@ -30407,6 +30522,88 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30407
30522
  }
30408
30523
  });
30409
30524
  }
30525
+ async function uncheckedRemoteWorkReason(mapping) {
30526
+ const environment = resolveEnvironment(mapping.name);
30527
+ if (!environment) throw new Error(`Unknown Coder environment: ${mapping.name}`);
30528
+ if (!await ensureAvailable()) throw new Error(unavailableReason);
30529
+ const status = (await listWorkspaces()).find((candidate) => candidate.name === mapping.workspace)?.status ?? "gone";
30530
+ if (status === "gone") return null;
30531
+ if (status !== "running") throw new Error(`Coder workspace ${mapping.workspace} is ${status}`);
30532
+ const checkoutDirectory = remoteCheckoutDirectory({
30533
+ checkoutRoot: environment.checkoutRoot,
30534
+ workspace: mapping.workspace
30535
+ });
30536
+ const uncommittedStatus = (await command([
30537
+ "ssh",
30538
+ mapping.workspace,
30539
+ "--",
30540
+ remoteCommand([
30541
+ "git",
30542
+ "--no-optional-locks",
30543
+ "-C",
30544
+ checkoutDirectory,
30545
+ "status",
30546
+ "--porcelain"
30547
+ ])
30548
+ ])).stdout;
30549
+ const unpushedLog = (await command([
30550
+ "ssh",
30551
+ mapping.workspace,
30552
+ "--",
30553
+ remoteCommand([
30554
+ "git",
30555
+ "-C",
30556
+ checkoutDirectory,
30557
+ "log",
30558
+ "--branches",
30559
+ "HEAD",
30560
+ "--not",
30561
+ "--remotes",
30562
+ "--oneline"
30563
+ ])
30564
+ ])).stdout;
30565
+ const stashList = (await command([
30566
+ "ssh",
30567
+ mapping.workspace,
30568
+ "--",
30569
+ remoteCommand([
30570
+ "git",
30571
+ "-C",
30572
+ checkoutDirectory,
30573
+ "stash",
30574
+ "list"
30575
+ ])
30576
+ ])).stdout;
30577
+ return remoteWorkRefusal({
30578
+ uncommittedStatus,
30579
+ unpushedLog,
30580
+ stashList,
30581
+ branch: mapping.branch,
30582
+ workspace: mapping.workspace
30583
+ });
30584
+ }
30585
+ async function remoteWorkReason(mapping) {
30586
+ try {
30587
+ return await whileWorkspaceBusy(mapping.workspace, () => uncheckedRemoteWorkReason(mapping));
30588
+ } catch (error) {
30589
+ return `Could not check remote work on ${mapping.branch} in Coder workspace ${mapping.workspace}: ${errorMessage(error)}`;
30590
+ }
30591
+ }
30592
+ async function settleRemoval({ projectId, discardRemoteWork }) {
30593
+ await provisionByProject.get(projectId);
30594
+ const mapping = config.projects.find((project) => project.id === projectId)?.environment;
30595
+ if (!mapping) return { ok: true };
30596
+ const keptWorkReason = discardRemoteWork ? null : await remoteWorkReason(mapping);
30597
+ if (keptWorkReason) return {
30598
+ ok: false,
30599
+ reason: keptWorkReason,
30600
+ canDiscardRemoteWork: true
30601
+ };
30602
+ release({ projectId }).then((released) => {
30603
+ if (!released.ok) log.warn(`[coder-env] ${released.reason}; the orphan sweep will stop ${mapping.workspace}`);
30604
+ });
30605
+ return { ok: true };
30606
+ }
30410
30607
  async function uploadFile({ workspace, remotePath, contents, mode }) {
30411
30608
  if (workspaceNameFromHex(workspace.slice(3)) !== workspace) throw new Error("Invalid Coder workspace name");
30412
30609
  const fileMode = typeof mode === "number" ? mode.toString(8) : mode;
@@ -30436,10 +30633,13 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30436
30633
  availableEnvironments,
30437
30634
  reserve,
30438
30635
  provision,
30636
+ renew,
30439
30637
  resume,
30440
30638
  release,
30639
+ settleRemoval,
30441
30640
  uploadFile,
30442
- workspaceStatus
30641
+ workspaceStatus,
30642
+ describeRemoteExit
30443
30643
  };
30444
30644
  }
30445
30645
  var DEFAULT_INTERVAL_MS$1 = 216e5;
@@ -34590,319 +34790,6 @@ function createIngestLane({ config = null, broadcast = null, logger = console, l
34590
34790
  };
34591
34791
  }
34592
34792
  //#endregion
34593
- //#region server/change-map/core/change-narrative-core.ts
34594
- var FACT_GROUPS = [
34595
- "files",
34596
- "subsystems",
34597
- "coChangeGaps",
34598
- "hotspots",
34599
- "blastRadius",
34600
- "untestedFiles",
34601
- "collisions",
34602
- "links"
34603
- ];
34604
- function narrativeFacts(map) {
34605
- return { repos: map.repos.map((repo) => ({
34606
- name: repo.name,
34607
- base: repo.base,
34608
- totalCounts: Object.fromEntries(FACT_GROUPS.map((group) => [group, repo[group].length])),
34609
- files: capFacts(repo.files, compareFactIds),
34610
- subsystems: capFacts(repo.subsystems, compareFactIds).map(capSubsystemPaths),
34611
- coChangeGaps: capFacts(repo.coChangeGaps, compareFactIds),
34612
- hotspots: capFacts(repo.hotspots, compareFactIds),
34613
- blastRadius: capFacts(repo.blastRadius, compareBlastRadius),
34614
- untestedFiles: capFacts(repo.untestedFiles, compareFactIds),
34615
- collisions: capFacts(repo.collisions, compareFactIds),
34616
- links: capFacts(repo.links, compareLinks)
34617
- })).sort((left, right) => left.name.localeCompare(right.name)) };
34618
- }
34619
- function capFacts(facts, compare) {
34620
- return [...facts].sort(compare).slice(0, 20);
34621
- }
34622
- function capSubsystemPaths(subsystem) {
34623
- return {
34624
- ...subsystem,
34625
- paths: subsystem.paths.slice(0, 20),
34626
- pathCount: subsystem.paths.length
34627
- };
34628
- }
34629
- function compareFactIds(left, right) {
34630
- return left.factId.localeCompare(right.factId);
34631
- }
34632
- function compareBlastRadius(left, right) {
34633
- return right.transitiveDependentCount - left.transitiveDependentCount || compareFactIds(left, right);
34634
- }
34635
- function compareLinks(left, right) {
34636
- return right.changedImporterCount - left.changedImporterCount || right.importerCount - left.importerCount || compareFactIds(left, right);
34637
- }
34638
- function factsHashInput(map) {
34639
- return JSON.stringify(narrativeFacts(map), (_key, value) => {
34640
- if (!value || typeof value !== "object" || Array.isArray(value)) return value;
34641
- return Object.fromEntries(Object.entries(value).sort(([left], [right]) => {
34642
- if (left < right) return -1;
34643
- if (left > right) return 1;
34644
- return 0;
34645
- }));
34646
- });
34647
- }
34648
- function hasNarratableFacts(map) {
34649
- return map.repos.some((repo) => repo.files.length > 0);
34650
- }
34651
- function knownNarrativeFactIds(map) {
34652
- const knownFactIds = /* @__PURE__ */ new Set();
34653
- for (const repo of narrativeFacts(map).repos) for (const group of FACT_GROUPS) for (const fact of repo[group]) knownFactIds.add(fact.factId);
34654
- return knownFactIds;
34655
- }
34656
- function buildNarrativePrompt({ facts, resultPath }) {
34657
- const deliveryLines = resultPath ? ["Use no tools except Write to the exact result file path below.", "Write"] : ["Answer with the JSON as your final message and write no files.", "Return"];
34658
- return [
34659
- "The facts below are your only source of truth. Read nothing else.",
34660
- deliveryLines[0],
34661
- `${deliveryLines[1]} JSON { "claims": [{ "text": string, "factIds": string[] }] } with at most 12 claims.`,
34662
- "Each claim must be one or two plain-English sentences about how a change connects to the codebase or what risk it carries.",
34663
- "Every claim must cite one or more factIds copied exactly from the facts. Do not invent facts or identifiers.",
34664
- "When links are present, lead with how a change in one repository reaches another through them.",
34665
- `Each fact list and subsystem path list holds at most 20 entries; totalCounts and pathCount give the full sizes.`,
34666
- `Keep each claim within 600 characters.`,
34667
- ...resultPath ? [`Result file: ${resultPath}`] : [],
34668
- `Facts JSON: ${JSON.stringify(facts)}`
34669
- ].join("\n");
34670
- }
34671
- function validateNarrative({ raw, knownFactIds, factsHash, model }) {
34672
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
34673
- const offeredClaims = "claims" in raw ? raw.claims : null;
34674
- if (!Array.isArray(offeredClaims)) return null;
34675
- const claims = [];
34676
- for (const offeredClaim of offeredClaims) {
34677
- if (!offeredClaim || typeof offeredClaim !== "object" || Array.isArray(offeredClaim)) continue;
34678
- const text = "text" in offeredClaim ? offeredClaim.text : null;
34679
- const factIds = "factIds" in offeredClaim ? offeredClaim.factIds : null;
34680
- if (typeof text !== "string" || text.length > 600) continue;
34681
- if (!Array.isArray(factIds)) continue;
34682
- const cleanText = text.replaceAll(String.fromCharCode(8212), "-").replaceAll(String.fromCharCode(8211), "-").replaceAll(String.fromCharCode(8230), "...").trim();
34683
- if (!cleanText || cleanText.length > 600) continue;
34684
- const citedFactIds = [...new Set(factIds.filter((factId) => typeof factId === "string" && knownFactIds.has(factId)))];
34685
- if (citedFactIds.length === 0) continue;
34686
- claims.push({
34687
- text: cleanText,
34688
- factIds: citedFactIds
34689
- });
34690
- if (claims.length >= 12) break;
34691
- }
34692
- if (claims.length === 0) return null;
34693
- return {
34694
- factsHash,
34695
- model,
34696
- claims
34697
- };
34698
- }
34699
- //#endregion
34700
- //#region server/change-map/change-narrator.ts
34701
- var MAX_CACHED_HASHES = 64;
34702
- var PROMPT_FILE$2 = "change-narrative-prompt.txt";
34703
- var RESULT_FILE$2 = "change-narrative-result.json";
34704
- var SCHEMA_FILE = "change-narrative-schema.json";
34705
- var BOOTSTRAP_PROMPT$1 = `Read ${PROMPT_FILE$2} and follow its instructions`;
34706
- var CODEX_BOOTSTRAP_PROMPT = `Read ${PROMPT_FILE$2} in your cwd and answer only with the JSON`;
34707
- var CODEX_OUTPUT_SCHEMA = {
34708
- type: "object",
34709
- additionalProperties: false,
34710
- required: ["claims"],
34711
- properties: { claims: {
34712
- type: "array",
34713
- maxItems: 12,
34714
- items: {
34715
- type: "object",
34716
- additionalProperties: false,
34717
- required: ["text", "factIds"],
34718
- properties: {
34719
- text: { type: "string" },
34720
- factIds: {
34721
- type: "array",
34722
- items: { type: "string" }
34723
- }
34724
- }
34725
- }
34726
- } }
34727
- };
34728
- async function codexLaunch(workDir, resultPath, codexModel) {
34729
- const schemaPath = path.join(workDir, SCHEMA_FILE);
34730
- await fsp.writeFile(schemaPath, JSON.stringify(CODEX_OUTPUT_SCHEMA), "utf8");
34731
- return {
34732
- promptResultPath: null,
34733
- bootstrapPrompt: CODEX_BOOTSTRAP_PROMPT,
34734
- agent: "codex",
34735
- extraArgs: [
34736
- "exec",
34737
- "--ephemeral",
34738
- "--skip-git-repo-check",
34739
- "--output-schema",
34740
- schemaPath,
34741
- "-o",
34742
- resultPath,
34743
- ...codexModel ? ["-m", codexModel] : []
34744
- ],
34745
- model: null
34746
- };
34747
- }
34748
- function claudeLaunch(resultPath, model) {
34749
- return {
34750
- promptResultPath: resultPath,
34751
- bootstrapPrompt: BOOTSTRAP_PROMPT$1,
34752
- agent: void 0,
34753
- extraArgs: [],
34754
- model
34755
- };
34756
- }
34757
- function makeNarrationWorkDir() {
34758
- return fsp.mkdtemp(path.join(os.tmpdir(), "glimmervoid-change-narrative-"));
34759
- }
34760
- async function spawnNarrationWithSession({ map, factsHash, engine, model, codexModel, timeoutSeconds }, spawn, nowFn) {
34761
- const workDir = await makeNarrationWorkDir();
34762
- const promptPath = path.join(workDir, PROMPT_FILE$2);
34763
- const resultPath = path.join(workDir, RESULT_FILE$2);
34764
- const controller = new AbortController();
34765
- const timeout = setTimeout(() => controller.abort(), timeoutSeconds * 1e3);
34766
- timeout.unref();
34767
- try {
34768
- const launch = engine === "codex" ? await codexLaunch(workDir, resultPath, codexModel) : claudeLaunch(resultPath, model);
34769
- await fsp.writeFile(promptPath, buildNarrativePrompt({
34770
- facts: narrativeFacts(map),
34771
- resultPath: launch.promptResultPath
34772
- }), "utf8");
34773
- await spawn({
34774
- id: `change-narrative:${nowFn()}:${crypto.randomUUID()}`,
34775
- name: "Change map narrative",
34776
- cwd: workDir,
34777
- prompt: launch.bootstrapPrompt,
34778
- agent: launch.agent,
34779
- extraArgs: launch.extraArgs,
34780
- model: launch.model,
34781
- signal: controller.signal
34782
- });
34783
- if (controller.signal.aborted) return null;
34784
- return validateNarrative({
34785
- raw: await readLaneResultFile(resultPath),
34786
- knownFactIds: knownNarrativeFactIds(map),
34787
- factsHash,
34788
- model
34789
- });
34790
- } finally {
34791
- clearTimeout(timeout);
34792
- await fsp.rm(workDir, {
34793
- recursive: true,
34794
- force: true
34795
- });
34796
- }
34797
- }
34798
- function createChangeNarrator({ getConfig, spawnNarration, spawnLane = createLaneSpawn({ laneName: "change-map" }), nowFn = () => Date.now() }) {
34799
- const runNarration = spawnNarration ?? ((request) => spawnNarrationWithSession(request, spawnLane, nowFn));
34800
- const cachedByHash = /* @__PURE__ */ new Map();
34801
- const queuedBySessionId = /* @__PURE__ */ new Map();
34802
- let active = null;
34803
- function narratorSettings() {
34804
- const parsed = BrowserConfig.shape.changeMap.safeParse(getConfig().changeMap);
34805
- if (!parsed.success) return null;
34806
- return parsed.data?.narrator ?? null;
34807
- }
34808
- function cache(factsHash, narrative) {
34809
- cachedByHash.delete(factsHash);
34810
- cachedByHash.set(factsHash, narrative);
34811
- if (cachedByHash.size <= MAX_CACHED_HASHES) return;
34812
- const oldestHash = cachedByHash.keys().next().value;
34813
- if (oldestHash) cachedByHash.delete(oldestHash);
34814
- }
34815
- function start(request) {
34816
- const callbacksBySessionId = /* @__PURE__ */ new Map([[request.sessionId, request.onSettled]]);
34817
- active = {
34818
- factsHash: request.factsHash,
34819
- callbacksBySessionId
34820
- };
34821
- Promise.resolve().then(() => runNarration(request)).then((narrative) => finish(request.factsHash, narrative), () => finish(request.factsHash, null));
34822
- }
34823
- function startNext() {
34824
- if (active) return;
34825
- for (const [sessionId, request] of queuedBySessionId) {
34826
- queuedBySessionId.delete(sessionId);
34827
- if (!narratorSettings()?.enabled || cachedByHash.has(request.factsHash)) {
34828
- request.onSettled();
34829
- continue;
34830
- }
34831
- start(request);
34832
- break;
34833
- }
34834
- }
34835
- function finish(factsHash, narrative) {
34836
- if (!active || active.factsHash !== factsHash) return;
34837
- const callbacks = [...active.callbacksBySessionId.values()];
34838
- cache(factsHash, narrative);
34839
- active = null;
34840
- startNext();
34841
- for (const callback of callbacks) callback();
34842
- }
34843
- function narrationFor(map, onSettled, { mayStart = true } = {}) {
34844
- const config = narratorSettings();
34845
- if (!config?.enabled || !hasNarratableFacts(map)) {
34846
- queuedBySessionId.delete(map.sessionId);
34847
- return {
34848
- narrative: null,
34849
- narratorState: "disabled"
34850
- };
34851
- }
34852
- const engine = config.engine ?? "claude";
34853
- const configuredModel = config.model?.trim() || "";
34854
- const model = configuredModel || (engine === "claude" ? "haiku" : "codex default");
34855
- const factsHash = crypto.createHash("sha256").update(JSON.stringify([
34856
- factsHashInput(map),
34857
- engine,
34858
- configuredModel
34859
- ])).digest("hex");
34860
- if (cachedByHash.has(factsHash)) {
34861
- queuedBySessionId.delete(map.sessionId);
34862
- const narrative = cachedByHash.get(factsHash) ?? null;
34863
- return {
34864
- narrative,
34865
- narratorState: narrative ? "ready" : "failed"
34866
- };
34867
- }
34868
- if (active?.factsHash === factsHash) {
34869
- active.callbacksBySessionId.set(map.sessionId, onSettled);
34870
- queuedBySessionId.delete(map.sessionId);
34871
- return {
34872
- narrative: null,
34873
- narratorState: "pending"
34874
- };
34875
- }
34876
- if (!mayStart) return {
34877
- narrative: null,
34878
- narratorState: queuedBySessionId.has(map.sessionId) ? "pending" : "disabled"
34879
- };
34880
- const request = {
34881
- map,
34882
- factsHash,
34883
- sessionId: map.sessionId,
34884
- onSettled,
34885
- engine,
34886
- model,
34887
- codexModel: engine === "codex" ? configuredModel || null : null,
34888
- timeoutSeconds: config.timeoutSeconds ?? 90
34889
- };
34890
- if (active) {
34891
- queuedBySessionId.set(map.sessionId, request);
34892
- return {
34893
- narrative: null,
34894
- narratorState: "pending"
34895
- };
34896
- }
34897
- start(request);
34898
- return {
34899
- narrative: null,
34900
- narratorState: "pending"
34901
- };
34902
- }
34903
- return { narrationFor };
34904
- }
34905
- //#endregion
34906
34793
  //#region server/task-title-refiner.ts
34907
34794
  var PROMPT_FILE$1 = "task-title-prompt.txt";
34908
34795
  var RESULT_FILE$1 = "task-title-result.json";
@@ -46275,7 +46162,6 @@ function createBackendLanes(dependencies) {
46275
46162
  reviewSessions,
46276
46163
  investigationSessions,
46277
46164
  visionsSessions,
46278
- changeMapSessions,
46279
46165
  taskTitleSessions,
46280
46166
  benchmarkSessions
46281
46167
  ];
@@ -46556,21 +46442,6 @@ function createBackendLanes(dependencies) {
46556
46442
  ...notice
46557
46443
  });
46558
46444
  });
46559
- const changeMapSessions = /* @__PURE__ */ new Map();
46560
- const changeMapNarrator = createChangeNarrator({
46561
- getConfig: () => configStore.config,
46562
- spawnLane: createLaneSpawn({
46563
- sessions: changeMapSessions,
46564
- closeSessionDataClients,
46565
- hookRouter,
46566
- getHookPort,
46567
- spawnGate,
46568
- recordLane,
46569
- replayBufferKB: config.replayBufferKB,
46570
- laneName: "change-map",
46571
- guardrailsRefusal
46572
- })
46573
- });
46574
46445
  const taskTitleSessions = /* @__PURE__ */ new Map();
46575
46446
  const taskTitleRefiner = createTaskTitleRefiner({
46576
46447
  getConfig: () => configStore.config,
@@ -46758,13 +46629,11 @@ function createBackendLanes(dependencies) {
46758
46629
  branchGc,
46759
46630
  coderActivity,
46760
46631
  coderEnv,
46761
- changeMapNarrator,
46762
46632
  taskTitleRefiner,
46763
46633
  taskTitleSessions,
46764
46634
  current,
46765
46635
  currentIngest,
46766
46636
  currentVisions,
46767
- changeMapSessions,
46768
46637
  gitWorkspace,
46769
46638
  gitWorkspaceSync,
46770
46639
  githubStore,
@@ -49108,6 +48977,7 @@ function createSessionFactory(dependencies) {
49108
48977
  }),
49109
48978
  nodePath: environment?.nodePath ?? "node",
49110
48979
  ensureRunning: async () => coderEnv ? coderEnv.resume({ projectId }) : refusal,
48980
+ describeExit: async () => coderEnv ? coderEnv.describeRemoteExit(mapping.workspace) : null,
49111
48981
  uploadFile: async (file) => {
49112
48982
  if (!coderEnv) throw new Error(refusal.reason);
49113
48983
  await coderEnv.uploadFile({
@@ -49189,34 +49059,6 @@ function createSessionFactory(dependencies) {
49189
49059
  };
49190
49060
  }
49191
49061
  //#endregion
49192
- //#region server/core/diff-annotations-core.ts
49193
- var DIFF_ANNOTATION_HEADER = "Operator review notes on the current worktree diff:";
49194
- var DIFF_ANNOTATION_CLOSING = "Address each note, then reply with what changed.";
49195
- var REMOVED_LINE_MARKER = "(removed line)";
49196
- function sortAnnotations(list) {
49197
- return [...list].sort((left, right) => {
49198
- if (left.path !== right.path) return left.path < right.path ? -1 : 1;
49199
- if (left.line !== right.line) return left.line - right.line;
49200
- if (left.side === right.side) return 0;
49201
- return left.side < right.side ? -1 : 1;
49202
- });
49203
- }
49204
- function formatDiffAnnotationMessage(annotations) {
49205
- const entries = sortAnnotations(annotations).flatMap((annotation) => {
49206
- const note = scrubForPaste(annotation.note, DIFF_ANNOTATION_NOTE_MAX_CHARS);
49207
- const path = scrubForPaste(annotation.path, 400);
49208
- if (!note || !path) return [];
49209
- const marker = annotation.side === "old" ? ` ${REMOVED_LINE_MARKER}` : "";
49210
- return [`- ${path}:${annotation.line} (${annotation.section})${marker} ${note}`];
49211
- });
49212
- if (entries.length === 0) return "";
49213
- return [
49214
- DIFF_ANNOTATION_HEADER,
49215
- ...entries,
49216
- DIFF_ANNOTATION_CLOSING
49217
- ].join("\n");
49218
- }
49219
- //#endregion
49220
49062
  //#region server/github/core/github-issues-core.ts
49221
49063
  var GITHUB_ISSUE_BODY_MAX_CHARS = 12e3;
49222
49064
  function fencedBody(body) {
@@ -50204,7 +50046,7 @@ function changedFilesOf(scope) {
50204
50046
  isCommitted: false
50205
50047
  }));
50206
50048
  }
50207
- function createChangeMapService({ sessions, narrator = null, onNarrationSettled = () => {}, runGit = runGitAsync, nowFn = Date.now }) {
50049
+ function createChangeMapService({ sessions, runGit = runGitAsync, nowFn = Date.now }) {
50208
50050
  const specifierCache = createFileFactCache(extractImportSpecifiers, []);
50209
50051
  const pythonSpecifierCache = createFileFactCache(extractPythonImportSpecifiers, []);
50210
50052
  const tsconfigCache = createFileFactCache((text) => ({ config: parseTsconfigJsonc(text) }), { config: null });
@@ -50355,7 +50197,7 @@ function createChangeMapService({ sessions, narrator = null, onNarrationSettled
50355
50197
  };
50356
50198
  }
50357
50199
  }
50358
- async function assemble(session, mayStartNarration) {
50200
+ async function assemble(session) {
50359
50201
  forgetEndedSiblings();
50360
50202
  const scopes = await session.getChangeScopes();
50361
50203
  const inputsByRoot = /* @__PURE__ */ new Map();
@@ -50436,44 +50278,31 @@ function createChangeMapService({ sessions, narrator = null, onNarrationSettled
50436
50278
  for (const repo of repos) repo.links = [];
50437
50279
  }
50438
50280
  }
50439
- const factsOnly = {
50281
+ return ChangeMap.parse({
50440
50282
  sessionId: session.id,
50441
50283
  sig: hashText(JSON.stringify(scopes)),
50442
50284
  generatedAt: nowFn(),
50443
- repos,
50444
- narrative: null,
50445
- narratorState: "disabled"
50446
- };
50447
- const narration = narrator ? narrator.narrationFor(factsOnly, () => onNarrationSettled(session.id), { mayStart: mayStartNarration }) : null;
50448
- return ChangeMap.parse(narration ? {
50449
- ...factsOnly,
50450
- ...narration
50451
- } : factsOnly);
50285
+ repos
50286
+ });
50452
50287
  }
50453
- function startAssembly(session, mayStartNarration) {
50454
- const running = assemble(session, mayStartNarration).finally(() => {
50288
+ function startAssembly(session) {
50289
+ const running = assemble(session).finally(() => {
50455
50290
  if (runningBySessionId.get(session.id) === running) runningBySessionId.delete(session.id);
50456
50291
  });
50457
50292
  runningBySessionId.set(session.id, running);
50458
50293
  return running;
50459
50294
  }
50460
- function build(session, { mayStartNarration = true } = {}) {
50295
+ function build(session) {
50461
50296
  const queued = queuedBySessionId.get(session.id);
50462
- if (queued) {
50463
- queued.mayStartNarration ||= mayStartNarration;
50464
- return queued.promise;
50465
- }
50297
+ if (queued) return queued;
50466
50298
  const running = runningBySessionId.get(session.id);
50467
- if (!running) return startAssembly(session, mayStartNarration);
50468
- const followUp = {
50469
- mayStartNarration,
50470
- promise: running.then(ignoreOutcome, ignoreOutcome).then(() => {
50471
- queuedBySessionId.delete(session.id);
50472
- return startAssembly(session, followUp.mayStartNarration);
50473
- })
50474
- };
50299
+ if (!running) return startAssembly(session);
50300
+ const followUp = running.then(ignoreOutcome, ignoreOutcome).then(() => {
50301
+ queuedBySessionId.delete(session.id);
50302
+ return startAssembly(session);
50303
+ });
50475
50304
  queuedBySessionId.set(session.id, followUp);
50476
- return followUp.promise;
50305
+ return followUp;
50477
50306
  }
50478
50307
  return { build };
50479
50308
  }
@@ -50755,14 +50584,14 @@ function requestValidationErrorReply(msg, message) {
50755
50584
  ok: false,
50756
50585
  error: message
50757
50586
  }),
50758
- "delete-hook": () => ({
50759
- type: "delete-hook-result",
50587
+ "remove-session": () => ({
50588
+ type: "remove-session-result",
50760
50589
  requestId,
50761
50590
  ok: false,
50762
50591
  error: message
50763
50592
  }),
50764
- "send-diff-annotations": () => ({
50765
- type: "send-diff-annotations-result",
50593
+ "delete-hook": () => ({
50594
+ type: "delete-hook-result",
50766
50595
  requestId,
50767
50596
  ok: false,
50768
50597
  error: message
@@ -50834,25 +50663,8 @@ function registerControlHandlers(controlWss, deps) {
50834
50663
  issue: null,
50835
50664
  error: "Issues polling is not running."
50836
50665
  }
50837
- }), issueProjectRepos = null, serverBuild = () => null, getUsageSessions = null, getUsageReport = null, requestUsageReport = null, getPlanLimits = null, controlReplayLog = null, getRtkInstallStatus = () => null, telegramDelivery = null, resolveRtkPath = () => getRtkPath(), conversationFs = fs, conversationGit = runGitForConversationHistory, conversationProjectsDir = claudeProjectsDir(process.env, os.homedir()), readTracePage = null, readPlanRevision = null, decidePlanReview = null, sessionDocs = null, coherenceFeed = null, changeMapNarrator = null, coderEnv = null, remoteRepoGit = null, agentConnect = null } = deps;
50838
- const changeMaps = createChangeMapService({
50839
- sessions,
50840
- narrator: changeMapNarrator,
50841
- onNarrationSettled: (sessionId) => {
50842
- broadcastChangeMap(sessionId).catch((error) => {
50843
- console.warn(`[control] change-map broadcast failed: id=${sessionId}: ${errorMessage(error)}`);
50844
- });
50845
- }
50846
- });
50847
- async function broadcastChangeMap(sessionId) {
50848
- const session = sessions.get(sessionId);
50849
- if (!session) return;
50850
- broadcastControl({
50851
- type: "change-map",
50852
- id: session.id,
50853
- map: await changeMaps.build(session, { mayStartNarration: false })
50854
- });
50855
- }
50666
+ }), issueProjectRepos = null, serverBuild = () => null, getUsageSessions = null, getUsageReport = null, requestUsageReport = null, getPlanLimits = null, controlReplayLog = null, getRtkInstallStatus = () => null, telegramDelivery = null, resolveRtkPath = () => getRtkPath(), conversationFs = fs, conversationGit = runGitForConversationHistory, conversationProjectsDir = claudeProjectsDir(process.env, os.homedir()), readTracePage = null, readPlanRevision = null, decidePlanReview = null, sessionDocs = null, coherenceFeed = null, coderEnv = null, remoteRepoGit = null, agentConnect = null } = deps;
50667
+ const changeMaps = createChangeMapService({ sessions });
50856
50668
  function buildSettingsPayload$1() {
50857
50669
  return buildSettingsPayload({
50858
50670
  configStore,
@@ -50950,9 +50762,56 @@ function registerControlHandlers(controlWss, deps) {
50950
50762
  console.log(`[control] Added session via UI: ${name}${skipPerms ? " (skip permissions)" : " (permission prompts)"}${remote ? ` (Coder environment ${remote.environment.name})` : ""}`);
50951
50763
  return created.project;
50952
50764
  }
50953
- function liveWorkspaceCount(environmentName) {
50765
+ async function remoteRepoSource(projectPath) {
50766
+ if (!remoteRepoGit) return {
50767
+ ok: false,
50768
+ error: "Coder environments are unavailable on this host"
50769
+ };
50770
+ const repoRemoteUrl = await remoteRepoGit.originUrl({ projectPath });
50771
+ if (!repoRemoteUrl) return {
50772
+ ok: false,
50773
+ error: `${projectPath} has no origin remote, so a Coder workspace cannot clone it.`
50774
+ };
50775
+ const baseBranch = configuredIntegrationBranch(config) ?? await remoteRepoGit.detectDefaultBranch({ projectPath });
50776
+ if (!baseBranch) return {
50777
+ ok: false,
50778
+ error: `Could not find the default branch of ${projectPath} on origin.`
50779
+ };
50780
+ return {
50781
+ ok: true,
50782
+ repoRemoteUrl,
50783
+ baseBranch
50784
+ };
50785
+ }
50786
+ async function restartSession(msg, ws, restart) {
50787
+ const sess = findSession(msg);
50788
+ if (!sess) return;
50789
+ const project = config.projects.find((candidate) => candidate.id === sess.id);
50790
+ if (sess.state !== STATES.FAILED || !project?.environment || !coderEnv) {
50791
+ restart(sess);
50792
+ return;
50793
+ }
50794
+ const source = await remoteRepoSource(project.path);
50795
+ if (!source.ok) {
50796
+ sendError(ws, source.error, { id: sess.id });
50797
+ return;
50798
+ }
50799
+ const renewal = await coderEnv.renew({
50800
+ projectId: project.id,
50801
+ repoRemoteUrl: source.repoRemoteUrl,
50802
+ baseBranch: source.baseBranch,
50803
+ liveCount: liveWorkspaceCount(project.environment.name, project.id)
50804
+ });
50805
+ if (!renewal.ok) {
50806
+ sendError(ws, renewal.reason, { id: sess.id });
50807
+ return;
50808
+ }
50809
+ restart(sess);
50810
+ }
50811
+ function liveWorkspaceCount(environmentName, excludedProjectId) {
50954
50812
  return config.projects.filter((project) => {
50955
50813
  if (project.environment?.name !== environmentName) return false;
50814
+ if (project.id === excludedProjectId) return false;
50956
50815
  const session = sessions.get(project.id);
50957
50816
  return session !== void 0 && session.state !== STATES.DORMANT;
50958
50817
  }).length;
@@ -50972,17 +50831,12 @@ function registerControlHandlers(controlWss, deps) {
50972
50831
  refuseAddSession(msg, ws, { error: "Coder environments are unavailable on this host" });
50973
50832
  return;
50974
50833
  }
50975
- const projectPath = path.resolve((msg.path || "").trim());
50976
- const repoRemoteUrl = await remoteRepoGit.originUrl({ projectPath });
50977
- if (!repoRemoteUrl) {
50978
- refuseAddSession(msg, ws, { error: `${projectPath} has no origin remote, so a Coder workspace cannot clone it.` });
50979
- return;
50980
- }
50981
- const baseBranch = configuredIntegrationBranch(config) ?? await remoteRepoGit.detectDefaultBranch({ projectPath });
50982
- if (!baseBranch) {
50983
- refuseAddSession(msg, ws, { error: `Could not find the default branch of ${projectPath} on origin.` });
50834
+ const source = await remoteRepoSource(path.resolve((msg.path || "").trim()));
50835
+ if (!source.ok) {
50836
+ refuseAddSession(msg, ws, { error: source.error });
50984
50837
  return;
50985
50838
  }
50839
+ const { repoRemoteUrl, baseBranch } = source;
50986
50840
  const id = generateProjectId();
50987
50841
  const reserved = await coderEnv.reserve({
50988
50842
  projectId: id,
@@ -51041,22 +50895,46 @@ function registerControlHandlers(controlWss, deps) {
51041
50895
  if (sess.state !== STATES.DORMANT) return;
51042
50896
  sess.start();
51043
50897
  }
51044
- function handleRemoveSession(msg, ws) {
50898
+ async function removeSession(msg) {
51045
50899
  const sess = findSession(msg);
51046
- if (!sess) {
51047
- sendError(ws, "Session not found");
51048
- return;
51049
- }
50900
+ if (!sess) return {
50901
+ ok: false,
50902
+ error: "Session not found"
50903
+ };
51050
50904
  if (agentSessions.get(sess.id) === sess) {
51051
50905
  sess.destroy();
51052
50906
  console.log(`[control] Removed spawned session via UI: ${sess.name}`);
51053
- return;
50907
+ return { ok: true };
50908
+ }
50909
+ if (config.projects.some((project) => project.id === sess.id && project.environment) && coderEnv) {
50910
+ sess.killSession();
50911
+ const settlement = await coderEnv.settleRemoval({
50912
+ projectId: sess.id,
50913
+ discardRemoteWork: msg.discardRemoteWork === true
50914
+ });
50915
+ if (!settlement.ok) return {
50916
+ ok: false,
50917
+ error: settlement.reason,
50918
+ canDiscardRemoteWork: settlement.canDiscardRemoteWork
50919
+ };
51054
50920
  }
51055
50921
  const freshConfig = configStore.save((cfg) => {
51056
50922
  cfg.projects = cfg.projects.filter((p) => p.id !== sess.id);
51057
50923
  });
51058
50924
  if (freshConfig) applyConfigReload(freshConfig);
51059
50925
  console.log(`[control] Removed session via UI: ${sess.name}`);
50926
+ return { ok: true };
50927
+ }
50928
+ async function handleRemoveSession(msg, ws) {
50929
+ const removal = await removeSession(msg);
50930
+ if (msg.requestId) {
50931
+ replyTo(ws, msg, "remove-session-result", removal.ok ? {
50932
+ ok: true,
50933
+ error: null
50934
+ } : removal);
50935
+ return;
50936
+ }
50937
+ if (!removal.ok) sendError(ws, removal.error);
51060
50938
  }
51061
50939
  function handleRenameSession(msg, ws) {
51062
50940
  const sess = findSession(msg);
@@ -52063,33 +51941,6 @@ function registerControlHandlers(controlWss, deps) {
52063
51941
  error: res.error || null
52064
51942
  });
52065
51943
  }
52066
- function handleSendDiffAnnotations(msg, ws) {
52067
- const reply = (payload) => replyTo(ws, msg, "send-diff-annotations-result", {
52068
- ok: false,
52069
- error: null,
52070
- ...payload
52071
- });
52072
- const session = findSession(msg);
52073
- if (!session) {
52074
- reply({ error: "Session not found" });
52075
- return;
52076
- }
52077
- const text = formatDiffAnnotationMessage(msg.annotations ?? []);
52078
- if (!text) {
52079
- reply({ error: "No review notes to send" });
52080
- return;
52081
- }
52082
- const pasted = session.pasteTextWhenReady(text);
52083
- if (!pasted.ok) {
52084
- reply({ error: `Could not write to "${session.name}" (${pasted.reason})` });
52085
- return;
52086
- }
52087
- session.noteOperatorDraft();
52088
- reply({
52089
- ok: true,
52090
- pending: pasted.deferred === true
52091
- });
52092
- }
52093
51944
  async function handleRequestUsageReport(msg, ws) {
52094
51945
  if (!requestUsageReport) {
52095
51946
  ws.send(JSON.stringify({
@@ -52289,20 +52140,17 @@ function registerControlHandlers(controlWss, deps) {
52289
52140
  "request-hooks-report": handleRequestHooksReport,
52290
52141
  "save-hook": handleSaveHook,
52291
52142
  "delete-hook": handleDeleteHook,
52292
- "send-diff-annotations": handleSendDiffAnnotations,
52293
52143
  "kill": (msg) => {
52294
52144
  const s = findSession(msg);
52295
52145
  if (s) s.killSession();
52296
52146
  },
52297
52147
  "start-session": handleStartSession,
52298
- "restart": (msg) => {
52299
- const s = findSession(msg);
52300
- if (s) s.restart({ fresh: msg.fresh === true });
52301
- },
52302
- "force-restart": (msg) => {
52303
- const s = findSession(msg);
52304
- if (s) s.forceRestart({ fresh: msg.fresh === true });
52305
- },
52148
+ "restart": (msg, ws) => restartSession(msg, ws, (s) => {
52149
+ s.restart({ fresh: msg.fresh === true });
52150
+ }),
52151
+ "force-restart": (msg, ws) => restartSession(msg, ws, (s) => {
52152
+ s.forceRestart({ fresh: msg.fresh === true });
52153
+ }),
52306
52154
  "dismiss": (msg) => {
52307
52155
  const session = findSession(msg);
52308
52156
  if (!session) return;
@@ -52360,8 +52208,6 @@ function registerControlHandlers(controlWss, deps) {
52360
52208
  sig: null,
52361
52209
  generatedAt: Date.now(),
52362
52210
  repos: [],
52363
- narrative: null,
52364
- narratorState: "disabled",
52365
52211
  error: `Change map could not be built: ${errorMessage(error)}`
52366
52212
  };
52367
52213
  });
@@ -52818,7 +52664,6 @@ function createBackendControl(dependencies) {
52818
52664
  decidePlanReview: dependencies.decidePlanReview,
52819
52665
  sessionDocs: dependencies.sessionDocs,
52820
52666
  coherenceFeed: dependencies.coherenceFeed,
52821
- changeMapNarrator: dependencies.changeMapNarrator,
52822
52667
  coderEnv: laneAssembly.coderEnv,
52823
52668
  remoteRepoGit: laneAssembly.gitWorkspace,
52824
52669
  agentConnect: dependencies.agentConnect
@@ -56633,7 +56478,7 @@ function createBackend(httpServer, options = {}) {
56633
56478
  logger: console,
56634
56479
  renameSessionFromTaskTitle
56635
56480
  });
56636
- const { allLiveSessions, branchGc, coderActivity, changeMapSessions, gitWorkspace: assembledGitWorkspace, gitWorkspaceSync, posthog, recordLane, spawnGate, tapIngestForSession, teamReview, myPrs, issues, traceChangeBroadcast, traceWiring, uploadsWiring, usage, visionsSessions, visionsSetup } = laneAssembly;
56481
+ const { allLiveSessions, branchGc, coderActivity, gitWorkspace: assembledGitWorkspace, gitWorkspaceSync, posthog, recordLane, spawnGate, tapIngestForSession, teamReview, myPrs, issues, traceChangeBroadcast, traceWiring, uploadsWiring, usage, visionsSessions, visionsSetup } = laneAssembly;
56637
56482
  gitWorkspace = assembledGitWorkspace;
56638
56483
  agentApi = createAgentApiWiring({
56639
56484
  config,
@@ -56821,7 +56666,6 @@ function createBackend(httpServer, options = {}) {
56821
56666
  reviewSessions,
56822
56667
  investigationSessions,
56823
56668
  visionsSessions,
56824
- changeMapSessions,
56825
56669
  taskTitleSessions: laneAssembly.taskTitleSessions,
56826
56670
  benchmarkSessions: laneAssembly.benchmarkSessions,
56827
56671
  branchGc,
@@ -56897,7 +56741,6 @@ function createBackend(httpServer, options = {}) {
56897
56741
  decidePlanReview: laneAssembly.planReview?.decide ?? null,
56898
56742
  sessionDocs,
56899
56743
  coherenceFeed,
56900
- changeMapNarrator: laneAssembly.changeMapNarrator,
56901
56744
  serverBuild: () => serverBuild,
56902
56745
  telemetry,
56903
56746
  logger: console