glimmervoid 0.41.0 → 0.42.0

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 (29) hide show
  1. package/dist/bin/glimmervoid.js +13 -13
  2. package/dist/chunks/{adapters-JDV45XpZ.js → adapters-C8GfnK2r.js} +1 -1
  3. package/dist/chunks/{adapters-yVOK5me4.js → adapters-D2u85MEH.js} +30 -15
  4. package/dist/chunks/{agent-api-cli-BSY2jR0P.js → agent-api-cli-B-1MqjKc.js} +1 -1
  5. package/dist/chunks/{agent-api-cli-1xk9WryE.js → agent-api-cli-D7JPxNso.js} +10 -5
  6. package/dist/chunks/{agent-api-core-syEPHs7j.js → agent-api-core-PwO7fPoc.js} +19 -5
  7. package/dist/chunks/{agent-setup-cli-py1gUMvJ.js → agent-setup-cli-C9qDV2g5.js} +1 -1
  8. package/dist/chunks/config-store-BL8LDBuM.js +1 -0
  9. package/dist/chunks/{config-store-BEaaXTd5.js → config-store-BxCW2IwK.js} +2 -2
  10. package/dist/chunks/{contracts-V_hfT23R.js → contracts-LXlDbl4R.js} +2 -2
  11. package/dist/chunks/{control-messages-Ci4g6gD1.js → control-messages-88sdxt-U.js} +9 -2
  12. package/dist/chunks/{git-version-D_iXgYTZ.js → git-version-DqagvnAZ.js} +1 -1
  13. package/dist/chunks/{grok-BUGgQVE3.js → grok-DwrAcNHC.js} +1 -1
  14. package/dist/chunks/{main-Bbh8ky2x.js → main-DZzDU4Q4.js} +403 -123
  15. package/dist/chunks/{pair-cli-DkItUGpT.js → pair-cli-3KKNhZ22.js} +2 -2
  16. package/dist/chunks/{remote-config-DFwmHWed.js → remote-config-Ce5nN0zL.js} +1 -1
  17. package/dist/chunks/{session-C8IpFmGQ.js → session-3eSP3t1i.js} +8 -2
  18. package/dist/chunks/{session-routes-nySS7DUE.js → session-routes-B2oSzmQn.js} +1 -1
  19. package/dist/chunks/{settings-cli-CaYd3bUH.js → settings-cli-BYY991YC.js} +5 -5
  20. package/dist/chunks/{settings-readout-core-Du7n-zyD.js → settings-readout-core-CWAhwZKN.js} +2 -2
  21. package/dist/chunks/{ssh-sign-cli-BLzmQ3Vh.js → ssh-sign-cli-BbqYIPwh.js} +2 -2
  22. package/dist/client/assets/index-C9wfiVqt.js +176 -0
  23. package/dist/client/assets/index-PnaYWtoe.css +2 -0
  24. package/dist/client/index.html +2 -2
  25. package/dist/server/index.js +1 -1
  26. package/package.json +1 -1
  27. package/dist/chunks/config-store-DE_r4DP8.js +0 -1
  28. package/dist/client/assets/index-D7QFo-gy.js +0 -176
  29. package/dist/client/assets/index-DKnOk67Z.css +0 -2
@@ -2,13 +2,13 @@ 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 { a as packageRoot, n as cliPath, o as relayPath, r as clientDir, s as resolvePackageBin, t as bundled } from "./runtime-paths-BkeftE60.js";
5
- import { A as SIGN_URL_ENV, At as TeamReviewThreadCommentsResponse, B as MyPr, Bt as GH_SEGMENT, Ct as ReviewVerdict, Dt as TeamReviewResolveResponse, E as SESSION_DOC_BODY_CAP_BYTES, Et as TeamReviewCompareFiles, H as MyPrCheckContext, Ht as REPO_SLUG_RE, I as SshSignRequest, It as hasStandingViewerApproval, J as MyPrMergeRequest, Jt as BADGE_LABELS, K as MyPrLaneEntry, L as TASK_TITLE_CONTROL_CHARACTERS, Mt as TeamReviewThreadsRepository, Nt as canApproveAfterComment, O as SESSION_NAME_PATTERN, Ot as TeamReviewStateEntry, Pt as decisionAsIfApprovalRequired, Qt as STATES, R as TaskTitle, Rt as HEX_SHA_RE, S as ConversationQuestion, St as ReviewThreadId, U as MyPrCheckContextsResponse, Ut as repoParts, V as MyPrAutoRebase, Vt as NODE_ID_RE, W as MyPrKeepMergeableRequest, X as MyPrMergeStateResponse, Xt as MERGEABLE_LIVE_STATES, Yt as KILLABLE_STATES, Z as MyPrMergeWhenReadyRequest, Zt as RESTARTABLE_STATES, _ as CONVERSATION_TOOL_RESULT_HOOK_EVENT, bt as ReviewFinding, ct as CommitSha, dt as FindingSeverity, et as MyPrThreadNode, g as CONVERSATION_PERMISSION_HOOK_EVENT, gt as QueuedReview, h as CONVERSATION_APPROVAL_SUMMARY_MAX_CHARS, ht as PrDetail, j as SSH_SIGN_BODY_CAP_BYTES, jt as TeamReviewThreadResult, k as SESSION_NAME_RULE_TEXT, kt as TeamReviewStatus, lt as DECIDING_REVIEW_STATES, m as COHERENCE_HOOKS_INACTIVE, mt as PostingPlan, n as AGENT_URL_ENV, nt as MyPrsSnapshot, pt as GithubReviewState, q as MyPrMergeMethod, r as ASK_USER_QUESTION_TOOL_NAME, rt as MyPrsState, tt as MyPrThreadsResponse, ut as EarlierReview, vt as ReviewComment, w as RefinedTaskTitle, wt as TeamReviewActionRequest, xt as ReviewResult, yt as ReviewDraft, z as LinkedPr, zt as shortSha } from "./session-C8IpFmGQ.js";
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-Ci4g6gD1.js";
8
- import { a as permitsHelpCommand, r as describeSessionLimits } from "./session-routes-nySS7DUE.js";
9
- import { a as decideSpawnAllowance, c as describeRoleRefusal, d as projectMyPrs, f as projectReviews, i as decideAgentRequest, n as REFUSAL_REASON, o as deriveChildSessionName, s as describePushLanding, u as parseAgentVerb } from "./agent-api-core-syEPHs7j.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";
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";
10
10
  import { n as glimmervoidHomeDir, t as decideConfigPath } from "./config-path-core-DFt-TemS.js";
11
- import { i as parseCommandLine, n as warnWhenGitIsTooOld } from "./git-version-D_iXgYTZ.js";
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";
13
13
  import { t as HOOK_URL_ENV } from "./hook-relay-core-DA_bz2KA.js";
14
14
  import { t as HOOK_VIA_GUARDRAILS_PROXY_ENV } from "./guardrails-proxy-core-BI3rdY-m.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-BEaaXTd5.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-V_hfT23R.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-yVOK5me4.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";
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-DFwmHWed.js";
30
- import { n as configFileViewOf, u as readSettingsForAgent } from "./settings-readout-core-Du7n-zyD.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";
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";
@@ -2040,6 +2040,7 @@ function projectSessionCard(source, { id, name }) {
2040
2040
  guardrailsReleased: source.isGuardrailsReleased === true,
2041
2041
  resumeSessionId: source.resumeSessionId || null,
2042
2042
  ...source.isWorkspace ? { workspace: true } : {},
2043
+ ...source.remoteWorkspace ? { remoteWorkspace: source.remoteWorkspace } : {},
2043
2044
  ...source.linkedPrs?.length ? { linkedPrs: source.linkedPrs } : {},
2044
2045
  ...source.ownerId ? { ownerId: source.ownerId } : {}
2045
2046
  };
@@ -2065,6 +2066,7 @@ function projectSessionSnapshots(source) {
2065
2066
  ownerId: source.ownerId ?? null,
2066
2067
  isWorktree: source.isWorktree,
2067
2068
  isWorkspace: source.isWorkspace,
2069
+ remoteWorkspace: source.remoteWorkspace ?? null,
2068
2070
  isGuarded: source.isGuarded === true,
2069
2071
  isGuardrailsReleased: source.isGuardrailsReleased === true,
2070
2072
  resumeSessionId: source.resumeSessionId,
@@ -2082,6 +2084,7 @@ function projectSessionSnapshots(source) {
2082
2084
  mergeReason: source.mergeReason,
2083
2085
  worktreeNotice: source.worktreeNotice,
2084
2086
  failureReason: source.failureReason ?? null,
2087
+ unseenOutcome: source.unseenOutcome ?? null,
2085
2088
  effectiveBase: source.effectiveBase ?? null,
2086
2089
  linkedPrs: source.linkedPrs,
2087
2090
  coherenceHooks: source.coherenceHooks ?? null,
@@ -2561,6 +2564,7 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
2561
2564
  const parentsWithChannelCleanup = /* @__PURE__ */ new WeakSet();
2562
2565
  const sessionsWithOperatorWatch = /* @__PURE__ */ new WeakSet();
2563
2566
  const lastOperatorInputAtBySession = /* @__PURE__ */ new WeakMap();
2567
+ const childIdsToDiscard = /* @__PURE__ */ new Set();
2564
2568
  function forgetLink(childId) {
2565
2569
  const link = linksByChildId.get(childId);
2566
2570
  if (link) for (const timer of link.retryTimersBySide.values()) clearTimeoutFn(timer);
@@ -2695,6 +2699,45 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
2695
2699
  parent.off("teardown", closeChildWithParent);
2696
2700
  };
2697
2701
  }
2702
+ async function keepOrDiscardChildWorktree(child, worktree, isParentRemoved) {
2703
+ const isDiscardRequested = childIdsToDiscard.delete(child.id);
2704
+ if (!worktree) return false;
2705
+ if (isDiscardRequested || isParentRemoved) {
2706
+ await child.discardWorktree();
2707
+ return false;
2708
+ }
2709
+ return !await child.discardWorktreeIfClean();
2710
+ }
2711
+ async function settleChildWorktree(parentId, child, childName, startAttempt, worktreeAtStart) {
2712
+ try {
2713
+ await startAttempt.catch(() => {});
2714
+ await child._awaitPendingReaps();
2715
+ const worktree = child.getWorktreeCarry();
2716
+ const registeredParent = listAllSessions().find((session) => session.id === parentId);
2717
+ const isKept = await keepOrDiscardChildWorktree(child, worktree, !registeredParent);
2718
+ if (registeredParent && worktree && isKept) {
2719
+ logger.warn(`[${AGENT_LANE_TAG} ${childName}] kept a worktree with unpushed work: ${worktree.worktreeDir}`);
2720
+ registeredParent.noteAttention(describeKeptChildAttention(childName, worktree));
2721
+ post(child.id, "toParent", {
2722
+ author: "glimmervoid",
2723
+ text: describeChildEndNote(childName, {
2724
+ kind: "kept",
2725
+ worktree
2726
+ }),
2727
+ at: now()
2728
+ });
2729
+ return;
2730
+ }
2731
+ const kind = worktree || worktreeAtStart ? "discarded" : "none";
2732
+ post(child.id, "toParent", {
2733
+ author: "glimmervoid",
2734
+ text: describeChildEndNote(childName, { kind }),
2735
+ at: now()
2736
+ });
2737
+ } catch (error) {
2738
+ logger.warn(`[${AGENT_LANE_TAG} ${childName}] could not settle the worktree: ${errorMessage(error)}`);
2739
+ }
2740
+ }
2698
2741
  function post(childId, side, entry) {
2699
2742
  const channel = channelsByChildId.get(childId);
2700
2743
  if (!channel) return;
@@ -2796,6 +2839,8 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
2796
2839
  unguarded: !parent.isGuarded
2797
2840
  };
2798
2841
  let child = null;
2842
+ let worktreeAtStart = null;
2843
+ let startAttempt = Promise.resolve();
2799
2844
  let released = false;
2800
2845
  let detachFromParent = () => {};
2801
2846
  const releaseChildSlot = () => {
@@ -2808,6 +2853,7 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
2808
2853
  id,
2809
2854
  session: name
2810
2855
  });
2856
+ if (child) settleChildWorktree(parent.id, child, name, startAttempt, worktreeAtStart);
2811
2857
  };
2812
2858
  const abandonChild = (reason) => {
2813
2859
  logger.warn(`[${AGENT_LANE_TAG} ${name}] ${reason}`);
@@ -2843,7 +2889,7 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
2843
2889
  });
2844
2890
  spawned.on("exit", releaseChildSlot);
2845
2891
  spawned.on("teardown", releaseChildSlot);
2846
- await spawnGate.run(() => {
2892
+ startAttempt = spawnGate.run(() => {
2847
2893
  if (spawned._destroyed) return void 0;
2848
2894
  broadcastControl({
2849
2895
  type: "session-added",
@@ -2854,7 +2900,9 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
2854
2900
  });
2855
2901
  return spawned.start();
2856
2902
  });
2903
+ await startAttempt;
2857
2904
  if (!spawned.hasLivePty) return abandonChild("the child never reached a live terminal");
2905
+ worktreeAtStart = spawned.getWorktreeCarry();
2858
2906
  return {
2859
2907
  ok: true,
2860
2908
  sessionId: id,
@@ -2981,7 +3029,7 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
2981
3029
  error: outcome.error
2982
3030
  }
2983
3031
  };
2984
- const note = `this session owns ${outcome.name} until the task is done: glimmervoid read ${outcome.sessionId} reads its messages, glimmervoid send ${outcome.sessionId} <text> steers it, and glimmervoid close ${outcome.sessionId} ends it`;
3032
+ const note = `this session owns ${outcome.name} until the task is done: glimmervoid read ${outcome.sessionId} reads its messages, glimmervoid send ${outcome.sessionId} <text> steers it, and glimmervoid close ${outcome.sessionId} ends it and discards its worktree, refusing while it holds work that is not on the integration branch unless you add --discard`;
2985
3033
  return {
2986
3034
  status: 200,
2987
3035
  body: {
@@ -3050,11 +3098,16 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
3050
3098
  }
3051
3099
  };
3052
3100
  }
3053
- function close(session, { child }) {
3101
+ async function close(session, { child, discard = false }) {
3054
3102
  const owned = findChildOf(session, child);
3055
3103
  if (!owned.ok) return refusal(404, owned.error);
3056
3104
  const childSession = agentSessions.get(owned.channel.childId);
3057
- if (!childSession) return refusal(409, `${owned.channel.childName} has already ended`);
3105
+ const alreadyEnded = refusal(409, `${owned.channel.childName} has already ended`);
3106
+ if (!childSession) return alreadyEnded;
3107
+ const worktree = childSession.getWorktreeCarry();
3108
+ if (!discard && worktree && await childSession.hasUnmergedWork()) return refusal(409, describeUnpushedCloseRefusal(owned.channel.childName, owned.channel.childId, worktree));
3109
+ if (agentSessions.get(owned.channel.childId) !== childSession) return alreadyEnded;
3110
+ if (discard) childIdsToDiscard.add(childSession.id);
3058
3111
  childSession.destroy();
3059
3112
  return {
3060
3113
  status: 200,
@@ -4113,7 +4166,7 @@ function explainNotification(to, gate, event, opts) {
4113
4166
  };
4114
4167
  }
4115
4168
  if (to === STATES.RUNNING) {
4116
- if (event === "user_input" || Boolean(opts && opts.signal === "resume") || !opts || !opts.hookSeen) {
4169
+ if (event === "user_input" || !opts || !opts.hookSeen) {
4117
4170
  gate.reset();
4118
4171
  return {
4119
4172
  category: null,
@@ -5371,10 +5424,10 @@ function createSessionEventWiring(dependencies) {
5371
5424
  session.on("agent-attention", ({ note }) => {
5372
5425
  pendingAgentNote = pendingAgentNote === null ? note : `${pendingAgentNote} | ${note}`;
5373
5426
  });
5374
- session.on("user-prompt", () => {
5427
+ session.on("user-prompt", ({ isSelfWake }) => {
5375
5428
  markTurnWorking();
5376
5429
  pendingAgentNote = null;
5377
- notifyGate.reset();
5430
+ if (!isSelfWake) notifyGate.reset();
5378
5431
  releaseDeferredAcknowledge();
5379
5432
  });
5380
5433
  session.on("waiting-input-acknowledged", () => dependencies.notificationManager.acknowledge(session.id));
@@ -5405,6 +5458,7 @@ function createSessionEventWiring(dependencies) {
5405
5458
  relay("wakeup-change", "session-wakeup");
5406
5459
  session.on("wakeup-change", persistWasWorking);
5407
5460
  relay("prompt-kind-change", "session-prompt");
5461
+ relay("unseen-outcome-change", "session-unseen");
5408
5462
  relay("conversation-change", "session-conversation");
5409
5463
  session.on("merge-status", ({ mergeStatus, reason, parked }) => {
5410
5464
  dependencies.broadcastControl({
@@ -6569,6 +6623,18 @@ function formatTouchedRanges(ranges) {
6569
6623
  return (Array.isArray(ranges) ? ranges : []).map((range) => range.start === range.end ? `${range.start}` : `${range.start}-${range.end}`).join(", ");
6570
6624
  }
6571
6625
  //#endregion
6626
+ //#region shared/fnv-hash.ts
6627
+ var FNV_OFFSET_BASIS = 2166136261;
6628
+ var FNV_PRIME = 16777619;
6629
+ function fnv1a32(text) {
6630
+ let hash = FNV_OFFSET_BASIS;
6631
+ for (let index = 0; index < text.length; index += 1) {
6632
+ hash ^= text.charCodeAt(index);
6633
+ hash = Math.imul(hash, FNV_PRIME) >>> 0;
6634
+ }
6635
+ return hash;
6636
+ }
6637
+ //#endregion
6572
6638
  //#region server/visions/core/visions-dispatch-core.ts
6573
6639
  var DEFAULT_QUIET_MS = 3e4;
6574
6640
  var DEFAULT_COOLDOWN_MS = 3e5;
@@ -6647,12 +6713,7 @@ function resolveVisionsConfig(raw) {
6647
6713
  }
6648
6714
  function hashText$1(text) {
6649
6715
  const value = typeof text === "string" ? text : "";
6650
- let hash = 2166136261;
6651
- for (let index = 0; index < value.length; index += 1) {
6652
- hash ^= value.charCodeAt(index);
6653
- hash = Math.imul(hash, 16777619) >>> 0;
6654
- }
6655
- return `${value.length.toString(36)}-${hash.toString(36)}`;
6716
+ return `${value.length.toString(36)}-${fnv1a32(value).toString(36)}`;
6656
6717
  }
6657
6718
  function createDispatchState(dispatchTimes = []) {
6658
6719
  return {
@@ -12813,7 +12874,13 @@ function seedTaskTitleSources({ persistedTitle, resumeSessionId, customTitle, in
12813
12874
  customTitle
12814
12875
  };
12815
12876
  }
12816
- var PROMPT_ENVELOPE_PATTERNS = ["pasted_content", "system-reminder"].map((tag) => new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*?</${tag}\\s*>`, "gi"));
12877
+ var envelopePattern = (tag) => new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*?</${tag}\\s*>`, "gi");
12878
+ var TASK_NOTIFICATION_ENVELOPE_PATTERN = envelopePattern("task-notification");
12879
+ var PROMPT_ENVELOPE_PATTERNS = [
12880
+ envelopePattern("pasted_content"),
12881
+ envelopePattern("system-reminder"),
12882
+ TASK_NOTIFICATION_ENVELOPE_PATTERN
12883
+ ];
12817
12884
  var MIN_SUBSTANTIVE_WORDS = 3;
12818
12885
  var CONTINUATION_PROMPTS = new Set([
12819
12886
  "yes please continue",
@@ -12833,6 +12900,10 @@ function cleanTaskPrompt(prompt) {
12833
12900
  for (const pattern of PROMPT_ENVELOPE_PATTERNS) cleanedPrompt = cleanedPrompt.replace(pattern, " ");
12834
12901
  return cleanedPrompt.replace(/\s+/g, " ").trim();
12835
12902
  }
12903
+ function isSelfWakePrompt(prompt) {
12904
+ if (typeof prompt !== "string") return false;
12905
+ return prompt.replace(TASK_NOTIFICATION_ENVELOPE_PATTERN, " ").trim() === "" && prompt.trim() !== "";
12906
+ }
12836
12907
  function isSubstantivePrompt(prompt) {
12837
12908
  if (typeof prompt !== "string") return false;
12838
12909
  const cleanedPrompt = cleanTaskPrompt(prompt);
@@ -13391,6 +13462,7 @@ function coderCreateArgs({ workspace, template, stopAfter, parameters = {} }) {
13391
13462
  "--stop-after",
13392
13463
  stopAfter,
13393
13464
  ...parameterArgs,
13465
+ "--use-parameter-defaults",
13394
13466
  "--yes"
13395
13467
  ];
13396
13468
  }
@@ -14464,6 +14536,13 @@ var ENTRY_HOOKS = {
14464
14536
  session.emit("session-done", { name: session.name });
14465
14537
  }
14466
14538
  };
14539
+ var UNSEEN_OUTCOME_ON_ENTRY = /* @__PURE__ */ new Map([
14540
+ [STATES.COMPLETE, "finished"],
14541
+ [STATES.FAILED, "failed"],
14542
+ [STATES.INITIALIZING, null],
14543
+ [STATES.DORMANT, null],
14544
+ [STATES.DONE, null]
14545
+ ]);
14467
14546
  var EXIT_HOOKS = { [STATES.WAITING](session) {
14468
14547
  session.emit("attention-cleared", { name: session.name });
14469
14548
  } };
@@ -15882,7 +15961,8 @@ function createSessionHookLifecycle(options) {
15882
15961
  const args = injection.buildHookArgs({
15883
15962
  bypassHookTrust: isTrustBypassed,
15884
15963
  hookTools: options.hookTools,
15885
- userHooks: glimmervoidSuppliedHooks()
15964
+ userHooks: glimmervoidSuppliedHooks(),
15965
+ observeToolCalls: options.observeToolCalls
15886
15966
  });
15887
15967
  if (!args) {
15888
15968
  console.warn(`[session:${options.name}] hook injection skipped: the relay path cannot be expressed for ${options.agentId} - falling back to OSC title only`);
@@ -18343,6 +18423,7 @@ var Session = class extends EventEmitter {
18343
18423
  state;
18344
18424
  stateSince;
18345
18425
  failureReason;
18426
+ unseenOutcome;
18346
18427
  ephemeral;
18347
18428
  agentId;
18348
18429
  usageVendor;
@@ -18450,6 +18531,7 @@ var Session = class extends EventEmitter {
18450
18531
  this.state = STATES.DORMANT;
18451
18532
  this.stateSince = Date.now();
18452
18533
  this.failureReason = null;
18534
+ this.unseenOutcome = null;
18453
18535
  this._receivedFirstOutput = false;
18454
18536
  this._killPollTimer = null;
18455
18537
  this._killReapTimer = null;
@@ -18690,13 +18772,8 @@ var Session = class extends EventEmitter {
18690
18772
  get recentTaskPrompts() {
18691
18773
  return [...this._recentTaskPrompts];
18692
18774
  }
18693
- setPendingTaskTitle(prompt) {
18694
- this._taskTitleSources.pendingPromptTitle = extractPromptTaskTitle(prompt);
18695
- this._updateTaskTitle();
18696
- }
18697
18775
  applyTaskTitleRefinement(refinement) {
18698
18776
  if (refinement.action === "replace") this._taskTitleSources.refinedTitle = refinement.title;
18699
- this._taskTitleSources.pendingPromptTitle = null;
18700
18777
  this._updateTaskTitle();
18701
18778
  }
18702
18779
  _updateTaskTitle() {
@@ -18823,7 +18900,8 @@ var Session = class extends EventEmitter {
18823
18900
  this.emit("user-prompt", {
18824
18901
  state: this.state,
18825
18902
  stateSince: this.stateSince,
18826
- ts: Date.now()
18903
+ ts: Date.now(),
18904
+ isSelfWake: isSelfWakePrompt(raw.payload?.prompt)
18827
18905
  });
18828
18906
  }
18829
18907
  if (raw && raw.signal === "ready" && raw.source === "hook") this.backgroundTracking.applyBackgroundTasks(raw.payload);
@@ -19147,6 +19225,9 @@ var Session = class extends EventEmitter {
19147
19225
  get isWorkspace() {
19148
19226
  return this.worktreeLifecycle.snapshot().isWorkspace;
19149
19227
  }
19228
+ get remoteWorkspace() {
19229
+ return this._getRemoteLaunch()?.workspace ?? null;
19230
+ }
19150
19231
  getWorktreeCarry() {
19151
19232
  return this.worktreeLifecycle.getCarry();
19152
19233
  }
@@ -19268,6 +19349,7 @@ var Session = class extends EventEmitter {
19268
19349
  ownerId: this.ownerId,
19269
19350
  isWorktree: this.isWorktree,
19270
19351
  isWorkspace: this.isWorkspace,
19352
+ remoteWorkspace: this.remoteWorkspace,
19271
19353
  isGuarded: this.isGuarded,
19272
19354
  isGuardrailsReleased: this.isGuardrailsReleased,
19273
19355
  resumeSessionId: this._resumeSessionId,
@@ -19285,6 +19367,7 @@ var Session = class extends EventEmitter {
19285
19367
  mergeReason: this.mergeReason,
19286
19368
  worktreeNotice: this.worktreeNotice,
19287
19369
  failureReason: this.failureReason,
19370
+ unseenOutcome: this.unseenOutcome,
19288
19371
  effectiveBase: this.worktreeLifecycle.snapshot().effectiveBase,
19289
19372
  linkedPrs: this.linkedPrs,
19290
19373
  coherenceHooks: this.coherenceHooks,
@@ -19329,6 +19412,8 @@ var Session = class extends EventEmitter {
19329
19412
  this.state = to;
19330
19413
  if (event === "spawn_fail") this.failureReason = describeSpawnFailure(detail);
19331
19414
  if (to === STATES.INITIALIZING) this.failureReason = null;
19415
+ const unseenOutcomeOnEntry = UNSEEN_OUTCOME_ON_ENTRY.get(to);
19416
+ if (unseenOutcomeOnEntry !== void 0 && !isCompactionRestoreEvent(event)) this._setUnseenOutcome(unseenOutcomeOnEntry);
19332
19417
  if (to === STATES.IDLE || to === STATES.COMPLETE) this._observeCompaction("settled");
19333
19418
  const entryHook = ENTRY_HOOKS[to];
19334
19419
  if (entryHook && !isCompactionRestoreEvent(event)) entryHook(this);
@@ -20176,9 +20261,16 @@ var Session = class extends EventEmitter {
20176
20261
  this._armTimer("_killPollTimer", KILL_POLL_INTERVAL_MS, poll);
20177
20262
  }
20178
20263
  dismiss() {
20179
- if (!DISMISSIBLE_STATES.has(this.state)) return false;
20264
+ const hadUnseenOutcome = this.unseenOutcome !== null;
20265
+ this._setUnseenOutcome(null);
20266
+ if (!DISMISSIBLE_STATES.has(this.state)) return hadUnseenOutcome;
20180
20267
  return this.transition("user_dismiss");
20181
20268
  }
20269
+ _setUnseenOutcome(nextUnseenOutcome) {
20270
+ if (this.unseenOutcome === nextUnseenOutcome) return;
20271
+ this.unseenOutcome = nextUnseenOutcome;
20272
+ this.emit("unseen-outcome-change", { unseenOutcome: nextUnseenOutcome });
20273
+ }
20182
20274
  _teardownPending() {
20183
20275
  return this._pendingRestart || this._finishing;
20184
20276
  }
@@ -23737,25 +23829,30 @@ function createTeamReviewPoller(deps) {
23737
23829
  }
23738
23830
  return false;
23739
23831
  }
23740
- function closedPostedKeys(candidateKeys) {
23741
- const departedPosted = Object.entries(state).filter(([key, entry]) => !candidateKeys.has(key) && !entry.inFlight && entry.draft?.status === "posted");
23742
- store.setWatchedKeys("team-review-departed", departedPosted.flatMap(([, entry]) => entry.draft ? [comparablePrKey(entry.draft)] : []));
23832
+ function closedEntryKeys() {
23743
23833
  const prs = store.getSnapshot().prs;
23744
- return new Set(departedPosted.filter(([, entry]) => {
23745
- const pr = entry.draft ? prs[comparablePrKey(entry.draft)] : void 0;
23746
- return pr !== void 0 && pr.state !== "OPEN";
23747
- }).map(([key]) => key));
23834
+ return new Set(Object.keys(state).filter((key) => {
23835
+ const pr = prs[key.toLowerCase()];
23836
+ return !state[key]?.inFlight && pr !== void 0 && pr.state !== "OPEN";
23837
+ }));
23838
+ }
23839
+ function watchDeparted(candidateKeys) {
23840
+ const departedKeys = Object.keys(state).filter((key) => !candidateKeys.has(key) && !state[key]?.inFlight);
23841
+ store.setWatchedKeys("team-review-departed", departedKeys.map((key) => key.toLowerCase()));
23748
23842
  }
23749
- async function pruneDeparted(candidateKeys) {
23843
+ async function pruneDeparted(candidateKeys, isComplete) {
23750
23844
  let isDirty = false;
23751
- const closedKeys = closedPostedKeys(candidateKeys);
23845
+ watchDeparted(candidateKeys);
23846
+ const closedKeys = closedEntryKeys();
23752
23847
  for (const [key, entry] of Object.entries(state)) {
23753
- if (!candidateKeys.has(key) && entry.resumable) {
23848
+ const isClosed = closedKeys.has(key);
23849
+ if (candidateKeys.has(key) || !isComplete && !isClosed) continue;
23850
+ if (entry.resumable) {
23754
23851
  await discardResumable(entry.resumable);
23755
23852
  entry.resumable = null;
23756
23853
  isDirty = true;
23757
23854
  }
23758
- if (!shouldPruneEntry(entry, candidateKeys.has(key), now(), closedKeys.has(key))) continue;
23855
+ if (!shouldPruneEntry(entry, false, now(), isClosed)) continue;
23759
23856
  delete state[key];
23760
23857
  isDirty = true;
23761
23858
  }
@@ -23996,6 +24093,10 @@ function createTeamReviewPoller(deps) {
23996
24093
  const planned = headsToReview(candidates);
23997
24094
  if (planned.isDirty) loop.track(persist());
23998
24095
  emitStatus();
24096
+ if (closedEntryKeys().size > 0) {
24097
+ loop.tickSoon();
24098
+ return;
24099
+ }
23999
24100
  if (planned.queue.length === 0 || !canStartReview()) return;
24000
24101
  const queueSignature = queuedHeadsSignature(planned.queue);
24001
24102
  if (queueSignature === lastRebuildQueueSignature) return;
@@ -24049,7 +24150,7 @@ function createTeamReviewPoller(deps) {
24049
24150
  retryAfterMs: rateLimitWaitMs
24050
24151
  };
24051
24152
  }
24052
- const isPruned = isComplete ? await pruneDeparted(new Set(candidates.map((candidate) => candidate.key))) : false;
24153
+ const isPruned = await pruneDeparted(new Set(candidates.map((candidate) => candidate.key)), isComplete);
24053
24154
  if (isPruned) watchAskedFor();
24054
24155
  const planned = headsToReview(candidates);
24055
24156
  const isStarted = await startReviewsUnlessGuardrailsRefused(planned.queue);
@@ -29579,7 +29680,9 @@ var GLIMMERVOID_WORKSPACE_RE = /^gv-[0-9a-f]{12}$/;
29579
29680
  var IDLE_STOP_STATES = /* @__PURE__ */ new Set([
29580
29681
  STATES.IDLE,
29581
29682
  STATES.COMPLETE,
29582
- STATES.DORMANT
29683
+ STATES.DORMANT,
29684
+ STATES.FAILED,
29685
+ STATES.DONE
29583
29686
  ]);
29584
29687
  var CODER_ENVIRONMENT_DEFAULTS = Object.freeze({
29585
29688
  idleStopMinutes: 30,
@@ -29664,19 +29767,24 @@ function shouldIdleStop({ sessionState, idleSinceMs, nowMs, idleStopMinutes }) {
29664
29767
  if (!IDLE_STOP_STATES.has(sessionState) || idleSinceMs === null) return false;
29665
29768
  return nowMs - idleSinceMs >= idleStopMinutes * 6e4;
29666
29769
  }
29667
- function planReconcile({ workspaces, mappings, busyWorkspaces, nowMs, deleteAfterStoppedDays }) {
29770
+ function planReconcile({ workspaces, mappings, busyWorkspaces, ownedWorkspaces, nowMs, deleteAfterStoppedDays }) {
29668
29771
  const mappedNames = new Set(mappings.map((mapping) => mapping.workspace));
29669
29772
  const listedNames = new Set(workspaces.map((workspace) => workspace.name));
29670
29773
  const presentMappings = mappings.filter((mapping) => !busyWorkspaces.has(mapping.workspace)).filter((mapping) => listedNames.has(mapping.workspace));
29671
- const unmapped = workspaces.filter((workspace) => !mappedNames.has(workspace.name) && !busyWorkspaces.has(workspace.name) && workspace.status !== "deleting");
29774
+ const unmapped = workspaces.filter((workspace) => !mappedNames.has(workspace.name) && !busyWorkspaces.has(workspace.name) && workspace.status !== "deleting" && ownedWorkspaces.has(workspace.name));
29672
29775
  const isStale = (workspace) => workspace.stoppedAtMs !== null && nowMs - workspace.stoppedAtMs >= deleteAfterStoppedDays * DAY_MS;
29673
29776
  return {
29674
29777
  resume: presentMappings.filter((mapping) => mapping.wasActive),
29675
29778
  stop: presentMappings.filter((mapping) => !mapping.wasActive),
29676
29779
  delete: unmapped.filter(isStale).map((workspace) => workspace.name),
29677
- keptUnmapped: unmapped.filter((workspace) => !isStale(workspace)).map((workspace) => workspace.name)
29780
+ stopUnmapped: unmapped.filter((workspace) => workspace.status === "running").map((workspace) => workspace.name)
29678
29781
  };
29679
29782
  }
29783
+ function retainedOwnedWorkspaces({ ownedWorkspaces, workspaces, mappings, busyWorkspaces }) {
29784
+ const listedNames = new Set(workspaces.map((workspace) => workspace.name));
29785
+ const mappedNames = new Set(mappings.map((mapping) => mapping.workspace));
29786
+ return new Set([...ownedWorkspaces].filter((workspace) => listedNames.has(workspace) || mappedNames.has(workspace) || busyWorkspaces.has(workspace)));
29787
+ }
29680
29788
  function failedProvisionCleanup({ didCreate, didStart }) {
29681
29789
  if (didCreate) return "delete";
29682
29790
  if (didStart) return "stop";
@@ -29690,13 +29798,34 @@ function liveLimitRefusal({ liveCount, maxLive, environmentName }) {
29690
29798
  if (liveCount < maxLive) return null;
29691
29799
  return `${environmentName} already has ${liveCount} of ${maxLive} live workspaces; close one first`;
29692
29800
  }
29801
+ var CODER_ERROR_DETAIL_LIMIT = 200;
29693
29802
  function remoteCheckoutDirectory({ checkoutRoot, workspace }) {
29694
29803
  return path.posix.join(checkoutRoot, workspace);
29695
29804
  }
29805
+ function coderErrorDetail(stderr) {
29806
+ if (typeof stderr !== "string") return null;
29807
+ const lines = stderr.split("\n").map((line) => line.trim());
29808
+ const errorLineIndex = lines.findLastIndex((line) => line.startsWith("error:"));
29809
+ if (errorLineIndex === -1) return null;
29810
+ return lines.slice(errorLineIndex).filter(Boolean).join(" ").slice(6).trim().slice(0, CODER_ERROR_DETAIL_LIMIT) || null;
29811
+ }
29812
+ function isCoderSignedOutError(stderr) {
29813
+ return typeof stderr === "string" && stderr.includes("You are signed out or your session has expired");
29814
+ }
29815
+ function coderNotSignedInReason(failure) {
29816
+ return `The coder CLI on this host is not signed in. Run coder login, then try again. (${failure})`;
29817
+ }
29696
29818
  //#endregion
29697
29819
  //#region server/coder/coder-env-poller.ts
29698
- function createCoderEnvPoller({ listWorkspaces, stopWorkspace, deleteWorkspace, sessionsForIdleCheck, mappings, busyWorkspaces, isReconcilePending, deleteAfterStoppedDays, onReconciled, now = Date.now, log = console, ...loopOptions }) {
29820
+ function createCoderEnvPoller({ listWorkspaces, stopWorkspace, deleteWorkspace, sessionsForIdleCheck, mappings, busyWorkspaces, ownedWorkspaces, isReconcilePending, deleteAfterStoppedDays, onReconciled, now = Date.now, log = console, ...loopOptions }) {
29699
29821
  const stoppedIdlePeriods = /* @__PURE__ */ new Map();
29822
+ async function mutateUnmappedWorkspace(mutate, workspace) {
29823
+ try {
29824
+ await mutate(workspace);
29825
+ } catch (error) {
29826
+ log.warn(`[coder-env] ${errorMessage(error)}; skipping ${workspace}`);
29827
+ }
29828
+ }
29700
29829
  async function runTick() {
29701
29830
  try {
29702
29831
  const workspaces = parseGlimmervoidWorkspaces(await listWorkspaces());
@@ -29704,20 +29833,24 @@ function createCoderEnvPoller({ listWorkspaces, stopWorkspace, deleteWorkspace,
29704
29833
  if (loop.isStopped()) return;
29705
29834
  const workspaceByName = new Map(workspaces.map((workspace) => [workspace.name, workspace]));
29706
29835
  const stoppedThisTick = /* @__PURE__ */ new Set();
29836
+ const plan = planReconcile({
29837
+ workspaces,
29838
+ mappings: mappings(),
29839
+ busyWorkspaces: busyWorkspaces(),
29840
+ ownedWorkspaces: ownedWorkspaces(),
29841
+ nowMs: now(),
29842
+ deleteAfterStoppedDays: deleteAfterStoppedDays()
29843
+ });
29844
+ for (const workspace of plan.delete) {
29845
+ if (loop.isStopped()) return;
29846
+ await mutateUnmappedWorkspace(deleteWorkspace, workspace);
29847
+ }
29848
+ for (const workspace of plan.stopUnmapped) {
29849
+ if (loop.isStopped()) return;
29850
+ log.warn(`[coder-env] stopping unmapped workspace ${workspace}`);
29851
+ await mutateUnmappedWorkspace(stopWorkspace, workspace);
29852
+ }
29707
29853
  if (isReconcilePending()) {
29708
- const staleAfterDays = deleteAfterStoppedDays();
29709
- const plan = planReconcile({
29710
- workspaces,
29711
- mappings: mappings(),
29712
- busyWorkspaces: busyWorkspaces(),
29713
- nowMs: now(),
29714
- deleteAfterStoppedDays: staleAfterDays
29715
- });
29716
- if (plan.keptUnmapped.length > 0) log.warn(`[coder-env] keeping unmapped workspaces until stopped for ${staleAfterDays} days: ${plan.keptUnmapped.join(", ")}`);
29717
- for (const workspace of plan.delete) {
29718
- if (loop.isStopped()) return;
29719
- await deleteWorkspace(workspace);
29720
- }
29721
29854
  for (const mapping of plan.stop) {
29722
29855
  if (loop.isStopped()) return;
29723
29856
  const status = workspaceByName.get(mapping.workspace)?.status;
@@ -29770,15 +29903,50 @@ function createCoderEnvPoller({ listWorkspaces, stopWorkspace, deleteWorkspace,
29770
29903
  }
29771
29904
  //#endregion
29772
29905
  //#region server/coder/coder-env-wiring.ts
29773
- function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {}, runCoder: executeCoder, env = process.env, platform = process.platform, fetch: fetchReady = globalThis.fetch, createPoller = createCoderEnvPoller, now = Date.now, log = console, ...pollerDeps }) {
29906
+ var CODER_UNAVAILABLE_REASON = "Coder environments are unavailable on this host";
29907
+ 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 }) {
29774
29908
  let binaryProbe = null;
29775
29909
  let isCoderAvailable = false;
29910
+ let unavailableReason = CODER_UNAVAILABLE_REASON;
29911
+ const signedOutErrors = /* @__PURE__ */ new WeakSet();
29776
29912
  let hasReconciled = false;
29777
29913
  const operationCountByWorkspace = /* @__PURE__ */ new Map();
29778
29914
  const lastSeenStatusByWorkspace = /* @__PURE__ */ new Map();
29779
29915
  const provisionByProject = /* @__PURE__ */ new Map();
29780
29916
  const failedProvisionByProject = /* @__PURE__ */ new Map();
29781
29917
  const setTimeoutFn = pollerDeps.setTimeoutFn ?? DEFAULT_TIMER_FNS.setTimeoutFn;
29918
+ let ownedWorkspaces = /* @__PURE__ */ new Set([...loadOwnedWorkspaces(), ...currentMappings().map((mapping) => mapping.workspace)]);
29919
+ let ownedSaveChain = Promise.resolve();
29920
+ function currentMappings() {
29921
+ return config.projects.flatMap((project) => project.id && project.environment ? [{
29922
+ projectId: project.id,
29923
+ workspace: project.environment.workspace,
29924
+ wasActive: project.wasActive === true
29925
+ }] : []);
29926
+ }
29927
+ function busyWorkspaces() {
29928
+ return new Set(operationCountByWorkspace.keys());
29929
+ }
29930
+ function saveOwned() {
29931
+ const save = ownedSaveChain.then(() => saveOwnedWorkspaces([...ownedWorkspaces]));
29932
+ ownedSaveChain = save.catch(() => {});
29933
+ return save;
29934
+ }
29935
+ async function pruneOwned(workspaces) {
29936
+ const retained = retainedOwnedWorkspaces({
29937
+ ownedWorkspaces,
29938
+ workspaces,
29939
+ mappings: currentMappings(),
29940
+ busyWorkspaces: busyWorkspaces()
29941
+ });
29942
+ if (retained.size === ownedWorkspaces.size) return;
29943
+ ownedWorkspaces = retained;
29944
+ try {
29945
+ await saveOwned();
29946
+ } catch (error) {
29947
+ log.warn(`[coder-env] could not save owned Coder workspaces: ${errorMessage(error)}`);
29948
+ }
29949
+ }
29782
29950
  async function runCoder(args, { timeoutMs, input = "", env: commandEnv }) {
29783
29951
  try {
29784
29952
  const options = {
@@ -29797,10 +29965,22 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
29797
29965
  encoding: "utf8",
29798
29966
  maxBuffer: 16777216
29799
29967
  });
29800
- } catch {
29801
- throw new Error(`Coder ${args[0]} failed`);
29968
+ } catch (error) {
29969
+ const stderr = error instanceof Error && "stderr" in error ? error.stderr : null;
29970
+ const detail = coderErrorDetail(stderr);
29971
+ const failure = detail ? `Coder ${args[0]} failed: ${detail}` : `Coder ${args[0]} failed`;
29972
+ if (!isCoderSignedOutError(stderr)) throw new Error(failure);
29973
+ throw markSignedOut(failure);
29802
29974
  }
29803
29975
  }
29976
+ function markSignedOut(failure) {
29977
+ binaryProbe = null;
29978
+ isCoderAvailable = false;
29979
+ unavailableReason = coderNotSignedInReason(failure);
29980
+ const signedOutError = new Error(unavailableReason);
29981
+ signedOutErrors.add(signedOutError);
29982
+ return signedOutError;
29983
+ }
29804
29984
  function command(args, timeoutMs = 3e4) {
29805
29985
  return runCoder(args, { timeoutMs });
29806
29986
  }
@@ -29817,7 +29997,8 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
29817
29997
  "-o",
29818
29998
  "json"
29819
29999
  ])).stdout;
29820
- recordListing(listingJson);
30000
+ const workspaces = recordListing(listingJson);
30001
+ if (workspaces !== null) await pruneOwned(workspaces);
29821
30002
  return listingJson;
29822
30003
  }
29823
30004
  async function listWorkspaces() {
@@ -29838,12 +30019,20 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
29838
30019
  async function probeBinary() {
29839
30020
  try {
29840
30021
  await command(["version"], 5e3);
29841
- isCoderAvailable = true;
29842
- return true;
29843
30022
  } catch {
30023
+ unavailableReason = CODER_UNAVAILABLE_REASON;
29844
30024
  log.warn("[coder-env] coder binary is unavailable");
29845
30025
  return false;
29846
30026
  }
30027
+ try {
30028
+ await command(["whoami"], 15e3);
30029
+ } catch (error) {
30030
+ if (!(error instanceof Error && signedOutErrors.has(error))) unavailableReason = `${CODER_UNAVAILABLE_REASON}: ${errorMessage(error)}`;
30031
+ log.warn(`[coder-env] ${errorMessage(error)}`);
30032
+ return false;
30033
+ }
30034
+ isCoderAvailable = true;
30035
+ return true;
29847
30036
  }
29848
30037
  async function ensureAvailable() {
29849
30038
  if (!isConfigured()) return false;
@@ -29887,12 +30076,9 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
29887
30076
  "--yes"
29888
30077
  ]);
29889
30078
  },
29890
- mappings: () => config.projects.flatMap((project) => project.id && project.environment ? [{
29891
- projectId: project.id,
29892
- workspace: project.environment.workspace,
29893
- wasActive: project.wasActive === true
29894
- }] : []),
29895
- busyWorkspaces: () => new Set(operationCountByWorkspace.keys()),
30079
+ mappings: currentMappings,
30080
+ busyWorkspaces,
30081
+ ownedWorkspaces: () => ownedWorkspaces,
29896
30082
  isReconcilePending: () => !hasReconciled,
29897
30083
  deleteAfterStoppedDays: () => Math.max(...(config.coder?.environments ?? []).map((environment) => resolveCoderEnvironment(environment).deleteAfterStoppedDays)),
29898
30084
  onReconciled: async (plan) => {
@@ -29938,8 +30124,16 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
29938
30124
  }
29939
30125
  async function waitForReady(workspaceName, environment) {
29940
30126
  const startedAtMs = now();
30127
+ let consecutiveListFailures = 0;
29941
30128
  for (;;) {
29942
- const workspace = (await listWorkspaces()).find((candidate) => candidate.name === workspaceName);
30129
+ const workspace = (await listWorkspaces().then((listed) => {
30130
+ consecutiveListFailures = 0;
30131
+ return listed;
30132
+ }, (listError) => {
30133
+ consecutiveListFailures += 1;
30134
+ if (consecutiveListFailures >= 3) throw listError;
30135
+ return [];
30136
+ })).find((candidate) => candidate.name === workspaceName);
29943
30137
  let isReadyUrlUp = null;
29944
30138
  if (environment.readyUrl) {
29945
30139
  isReadyUrlUp = false;
@@ -29967,6 +30161,9 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
29967
30161
  ])).stdout);
29968
30162
  if (reason) throw new Error(reason);
29969
30163
  }
30164
+ async function isWorkspaceListed(workspace) {
30165
+ return (await listWorkspaces().catch(() => null))?.some((candidate) => candidate.name === workspace) ?? true;
30166
+ }
29970
30167
  async function failAttempt({ workspace, didCreate, didStart, reason }) {
29971
30168
  const cleanup = failedProvisionCleanup({
29972
30169
  didCreate,
@@ -29978,9 +30175,9 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
29978
30175
  cleanup,
29979
30176
  workspace,
29980
30177
  "--yes"
29981
- ]);
30178
+ ], 12e5);
29982
30179
  } catch {
29983
- reasons.push(`Coder cleanup ${cleanup} failed for ${workspace}`);
30180
+ if (await isWorkspaceListed(workspace)) reasons.push(`Coder cleanup ${cleanup} failed for ${workspace}`);
29984
30181
  }
29985
30182
  return {
29986
30183
  ok: false,
@@ -30012,7 +30209,7 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30012
30209
  };
30013
30210
  if (!await ensureAvailable()) return {
30014
30211
  ok: false,
30015
- reason: "Coder environments are unavailable on this host"
30212
+ reason: unavailableReason
30016
30213
  };
30017
30214
  const workspace = workspaceNameFromHex(randomBytes(6).toString("hex"));
30018
30215
  if (workspace === null) return {
@@ -30057,7 +30254,7 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30057
30254
  try {
30058
30255
  const environment = resolveEnvironment(mapping.name);
30059
30256
  if (!environment) throw new Error(`Unknown Coder environment: ${mapping.name}`);
30060
- if (!await ensureAvailable()) throw new Error("Coder environments are unavailable on this host");
30257
+ if (!await ensureAvailable()) throw new Error(unavailableReason);
30061
30258
  const createArgs = coderCreateArgs({
30062
30259
  workspace,
30063
30260
  template: environment.template,
@@ -30069,8 +30266,14 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30069
30266
  checkoutRoot: environment.checkoutRoot,
30070
30267
  workspace
30071
30268
  });
30269
+ ownedWorkspaces.add(workspace);
30270
+ await saveOwned().catch((saveError) => {
30271
+ throw new Error(`Could not record Coder workspace ${workspace} as owned by this host: ${errorMessage(saveError)}`);
30272
+ });
30072
30273
  didCreate = true;
30073
- await command(createArgs, 12e5);
30274
+ await command(createArgs, 12e5).catch(async (createError) => {
30275
+ if (!(await listWorkspaces().catch(() => [])).some((candidate) => candidate.name === workspace)) throw createError;
30276
+ });
30074
30277
  await waitForReady(workspace, environment);
30075
30278
  await checkSharing(workspace);
30076
30279
  const nodeVersion = await command([
@@ -30144,7 +30347,7 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30144
30347
  };
30145
30348
  if (!await ensureAvailable()) return {
30146
30349
  ok: false,
30147
- reason: "Coder environments are unavailable on this host"
30350
+ reason: unavailableReason
30148
30351
  };
30149
30352
  return whileWorkspaceBusy(mapping.workspace, () => startWorkspace({
30150
30353
  mapping,
@@ -30185,7 +30388,7 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30185
30388
  if (!mapping) return { ok: true };
30186
30389
  if (!await ensureAvailable()) return {
30187
30390
  ok: false,
30188
- reason: "Coder environments are unavailable on this host"
30391
+ reason: unavailableReason
30189
30392
  };
30190
30393
  return whileWorkspaceBusy(mapping.workspace, async () => {
30191
30394
  try {
@@ -30209,7 +30412,7 @@ function createCoderEnvWiring({ config, persistMapping, onReconciled = () => {},
30209
30412
  const fileMode = typeof mode === "number" ? mode.toString(8) : mode;
30210
30413
  if (!/^[0-7]{3,4}$/.test(fileMode)) throw new Error("Invalid remote file mode");
30211
30414
  if (!path.posix.isAbsolute(remotePath)) throw new Error("Remote upload path must be absolute");
30212
- if (!await ensureAvailable()) throw new Error("Coder environments are unavailable on this host");
30415
+ if (!await ensureAvailable()) throw new Error(unavailableReason);
30213
30416
  await runCoder([
30214
30417
  "ssh",
30215
30418
  workspace,
@@ -30853,6 +31056,12 @@ function createBranchGcWiring({ config, gitWorkspace, liveSessionIds, liveWorktr
30853
31056
  };
30854
31057
  }
30855
31058
  //#endregion
31059
+ //#region shared/contracts/coder-owned-workspaces.ts
31060
+ var CoderOwnedWorkspaces = z.object({
31061
+ version: z.literal(1),
31062
+ workspaces: z.array(z.string().regex(/^gv-[0-9a-f]{12}$/))
31063
+ });
31064
+ //#endregion
30856
31065
  //#region shared/display-text.ts
30857
31066
  var SECONDS_PER_MINUTE = 60;
30858
31067
  var HOURS_PER_DAY = 24;
@@ -34767,9 +34976,6 @@ function createTaskTitleRefiner({ getConfig, spawnLane, renameSession, nowFn = (
34767
34976
  function canRefine(session) {
34768
34977
  return !isStopped && !session._destroyed && !session.ephemeral && !session.customTitle && settings().enabled;
34769
34978
  }
34770
- function clearPending(session) {
34771
- session.setPendingTaskTitle(null);
34772
- }
34773
34979
  function clearRecheck(session) {
34774
34980
  const state = statesBySession.get(session);
34775
34981
  if (!state?.recheckTimer) return;
@@ -34804,11 +35010,8 @@ function createTaskTitleRefiner({ getConfig, spawnLane, renameSession, nowFn = (
34804
35010
  for (const [session, request] of queuedBySession) {
34805
35011
  queuedBySession.delete(session);
34806
35012
  if (session.taskTitlePromptRevision !== request.revision) continue;
34807
- if (!canRefine(session)) {
34808
- clearPending(session);
34809
- continue;
34810
- }
34811
- if (session.state !== STATES.IDLE && session.state !== STATES.COMPLETE) {
35013
+ if (!canRefine(session)) continue;
35014
+ if (request.currentTitle !== null && session.state !== STATES.IDLE && session.state !== STATES.COMPLETE) {
34812
35015
  scheduleRecheck(session);
34813
35016
  continue;
34814
35017
  }
@@ -34863,7 +35066,6 @@ function createTaskTitleRefiner({ getConfig, spawnLane, renameSession, nowFn = (
34863
35066
  clearRecheck(session);
34864
35067
  if (!canRefine(session)) {
34865
35068
  queuedBySession.delete(session);
34866
- clearPending(session);
34867
35069
  return;
34868
35070
  }
34869
35071
  const decision = decisionFor(session);
@@ -34891,13 +35093,9 @@ function createTaskTitleRefiner({ getConfig, spawnLane, renameSession, nowFn = (
34891
35093
  detach
34892
35094
  };
34893
35095
  statesBySession.set(session, state);
34894
- function onPrompt({ prompt }) {
35096
+ function onPrompt() {
34895
35097
  state.substantivePromptsSinceRefinement++;
34896
- if (!canRefine(session)) {
34897
- clearPending(session);
34898
- return;
34899
- }
34900
- session.setPendingTaskTitle(prompt);
35098
+ if (session.refinedTaskTitle === null) onTurnEnd(session);
34901
35099
  }
34902
35100
  function onReset() {
34903
35101
  clearRecheck(session);
@@ -34911,7 +35109,6 @@ function createTaskTitleRefiner({ getConfig, spawnLane, renameSession, nowFn = (
34911
35109
  clearRecheck(session);
34912
35110
  queuedBySession.delete(session);
34913
35111
  if (active?.request.session === session) active.controller.abort();
34914
- clearPending(session);
34915
35112
  }
34916
35113
  function detach() {
34917
35114
  session.off("task-prompt", onPrompt);
@@ -34930,10 +35127,7 @@ function createTaskTitleRefiner({ getConfig, spawnLane, renameSession, nowFn = (
34930
35127
  }
34931
35128
  async function stop() {
34932
35129
  isStopped = true;
34933
- for (const [session, state] of statesBySession) {
34934
- state.detach();
34935
- clearPending(session);
34936
- }
35130
+ for (const state of statesBySession.values()) state.detach();
34937
35131
  queuedBySession.clear();
34938
35132
  active?.controller.abort();
34939
35133
  await active?.settled;
@@ -41312,17 +41506,19 @@ function resetsAtMs(value) {
41312
41506
  if (numeric > SECONDS_CEILING) return Math.round(numeric);
41313
41507
  return Math.round(numeric * 1e3);
41314
41508
  }
41315
- function normalizeWindow(raw) {
41316
- if (!isRecord(raw)) return null;
41317
- const window = raw;
41318
- const pct = roundPct(window.used_percentage);
41319
- const resetsAt = resetsAtMs(window.resets_at);
41320
- if (pct === null && resetsAt === null) return null;
41509
+ function rateLimitWindow(usedPct, resetsAt) {
41510
+ const pct = roundPct(usedPct);
41511
+ const resetsAtMsValue = resetsAtMs(resetsAt);
41512
+ if (pct === null && resetsAtMsValue === null) return null;
41321
41513
  return {
41322
41514
  pct,
41323
- resetsAtMs: resetsAt
41515
+ resetsAtMs: resetsAtMsValue
41324
41516
  };
41325
41517
  }
41518
+ function normalizeWindow(raw) {
41519
+ if (!isRecord(raw)) return null;
41520
+ return rateLimitWindow(raw.used_percentage, raw.resets_at);
41521
+ }
41326
41522
  function normalizeRateLimits(raw) {
41327
41523
  if (!isRecord(raw)) return null;
41328
41524
  const limits = raw;
@@ -41362,14 +41558,19 @@ function shouldBroadcastPlanLimits(previous, next) {
41362
41558
  if (nextSignature === null) return false;
41363
41559
  return nextSignature !== planLimitsSignature(previous);
41364
41560
  }
41365
- function buildPlanLimitsMessage(snapshot) {
41561
+ var PLAN_LIMITS_SOURCE_BY_VENDOR = Object.freeze({
41562
+ claude: "statusline",
41563
+ codex: "rollout"
41564
+ });
41565
+ function buildPlanLimitsMessage(snapshot, vendor = "claude") {
41366
41566
  if (!snapshot?.rateLimits) return null;
41367
41567
  return {
41368
41568
  type: "plan-limits",
41369
41569
  ts: snapshot.ts,
41370
41570
  fiveHour: snapshot.rateLimits.fiveHour,
41371
41571
  sevenDay: snapshot.rateLimits.sevenDay,
41372
- source: "statusline"
41572
+ source: PLAN_LIMITS_SOURCE_BY_VENDOR[vendor],
41573
+ vendor
41373
41574
  };
41374
41575
  }
41375
41576
  function selectOfficialSessionCost(costMode, recordedCostUSD) {
@@ -41728,6 +41929,39 @@ function parseCodexUsageLine(line, state = createCodexUsageState(), { sessionId
41728
41929
  vendor: "codex"
41729
41930
  });
41730
41931
  }
41932
+ var RATE_LIMITS_MARKER = "\"rate_limits\"";
41933
+ var CODEX_PLAN_LIMIT_ID = "codex";
41934
+ var FIVE_HOUR_WINDOW_MINUTES = 300;
41935
+ var SEVEN_DAY_WINDOW_MINUTES = 10080;
41936
+ function codexPlanWindows(rawWindows) {
41937
+ const windowsByMinutes = /* @__PURE__ */ new Map();
41938
+ for (const rawWindow of rawWindows) {
41939
+ if (!isRecord(rawWindow)) continue;
41940
+ windowsByMinutes.set(numberOr(rawWindow.window_minutes, 0), rateLimitWindow(rawWindow.used_percent, rawWindow.resets_at));
41941
+ }
41942
+ return {
41943
+ fiveHour: windowsByMinutes.get(FIVE_HOUR_WINDOW_MINUTES) ?? null,
41944
+ sevenDay: windowsByMinutes.get(SEVEN_DAY_WINDOW_MINUTES) ?? null
41945
+ };
41946
+ }
41947
+ function parseCodexRateLimitsLine(line) {
41948
+ if (typeof line !== "string" || !line.includes(RATE_LIMITS_MARKER)) return null;
41949
+ const raw = parseJsonLine(line);
41950
+ if (!raw) return null;
41951
+ const parsed = raw;
41952
+ if (parsed.payload?.type !== "token_count") return null;
41953
+ const rateLimits = parsed.payload.rate_limits;
41954
+ if (!isRecord(rateLimits)) return null;
41955
+ if (rateLimits.limit_id != null && rateLimits.limit_id !== CODEX_PLAN_LIMIT_ID) return null;
41956
+ const ts = Date.parse(String(parsed.timestamp));
41957
+ if (!Number.isFinite(ts)) return null;
41958
+ const windows = codexPlanWindows([rateLimits.primary, rateLimits.secondary]);
41959
+ if (!windows.fiveHour && !windows.sevenDay) return null;
41960
+ return {
41961
+ rateLimits: windows,
41962
+ ts
41963
+ };
41964
+ }
41731
41965
  function codexDedupIdentity(entry) {
41732
41966
  if (!entry) return null;
41733
41967
  return [
@@ -42193,6 +42427,7 @@ function createUsageScanner(deps = {}) {
42193
42427
  let lastPartial = false;
42194
42428
  let lastOutcome = null;
42195
42429
  let lastIoFailures = 0;
42430
+ let latestCodexRateLimitSnapshot = null;
42196
42431
  let activePass = null;
42197
42432
  let pendingForce = false;
42198
42433
  let pendingScopeDirs = null;
@@ -42333,6 +42568,14 @@ function createUsageScanner(deps = {}) {
42333
42568
  markDirty();
42334
42569
  return false;
42335
42570
  }
42571
+ function noteCodexRateLimits(snapshot) {
42572
+ if (!snapshot) return;
42573
+ if (latestCodexRateLimitSnapshot && snapshot.ts < latestCodexRateLimitSnapshot.ts) return;
42574
+ latestCodexRateLimitSnapshot = snapshot;
42575
+ }
42576
+ function latestCodexRateLimits() {
42577
+ return latestCodexRateLimitSnapshot;
42578
+ }
42336
42579
  function ingestVendorLine(parsed, file) {
42337
42580
  if (!parsed) return 0;
42338
42581
  return storeEntry(stripIngestFields(priceEntry({
@@ -42342,6 +42585,7 @@ function createUsageScanner(deps = {}) {
42342
42585
  }
42343
42586
  function ingestLine({ line, file, vendor, vendorState, dirs: claudeRoots, lineOrdinal }) {
42344
42587
  if (vendor === "codex") {
42588
+ noteCodexRateLimits(parseCodexRateLimitsLine(line));
42345
42589
  const parsedCodexEntry = parseCodexUsageLine(line, vendorState ?? createCodexUsageState());
42346
42590
  if (!parsedCodexEntry) return 0;
42347
42591
  return ingestVendorLine({
@@ -42903,6 +43147,7 @@ function createUsageScanner(deps = {}) {
42903
43147
  sessionTotals,
42904
43148
  stats,
42905
43149
  budgetSpend,
43150
+ latestCodexRateLimits,
42906
43151
  _entriesForTest: () => entries.map((entry) => entry)
42907
43152
  };
42908
43153
  Object.defineProperty(api, "_entriesForTest", { enumerable: false });
@@ -43019,6 +43264,7 @@ function createUsageWiring({ config, sessions = /* @__PURE__ */ new Map(), broad
43019
43264
  let ioFailureStreak = 0;
43020
43265
  let ioFailureStreakId = 0;
43021
43266
  let planLimits = null;
43267
+ let codexPlanLimits = null;
43022
43268
  const officialCostByClaudeId = /* @__PURE__ */ new Map();
43023
43269
  let budgetFiredState = {
43024
43270
  daily: {},
@@ -43119,13 +43365,16 @@ function createUsageWiring({ config, sessions = /* @__PURE__ */ new Map(), broad
43119
43365
  passInFlight = false;
43120
43366
  }
43121
43367
  if (stopped) return result;
43122
- if (result) laneLog.debugNote(() => "pass complete", () => ({
43123
- files: result.files,
43124
- entries: result.entries,
43125
- newEntries: result.newEntries,
43126
- durationMs: result.durationMs,
43127
- outcome: result.outcome
43128
- }));
43368
+ if (result) {
43369
+ ingestCodexRateLimits(scanner.latestCodexRateLimits());
43370
+ laneLog.debugNote(() => "pass complete", () => ({
43371
+ files: result.files,
43372
+ entries: result.entries,
43373
+ newEntries: result.newEntries,
43374
+ durationMs: result.durationMs,
43375
+ outcome: result.outcome
43376
+ }));
43377
+ }
43129
43378
  if (result?.outcome === "io-failed") {
43130
43379
  ioFailureStreak += 1;
43131
43380
  laneLog.warnOnce(`scan-io:${ioFailureStreakId}`, "scan pass lost files to io errors", {
@@ -43267,8 +43516,18 @@ function createUsageWiring({ config, sessions = /* @__PURE__ */ new Map(), broad
43267
43516
  const message = buildPlanLimitsMessage(snapshot);
43268
43517
  if (message) broadcast(message);
43269
43518
  }
43270
- function getPlanLimitsMessage() {
43519
+ function ingestCodexRateLimits(snapshot) {
43520
+ if (stopped || !cfg.enabled || !cfg.planLimits || !snapshot) return;
43521
+ if (codexPlanLimits && snapshot.ts < codexPlanLimits.ts) return;
43522
+ const changed = shouldBroadcastPlanLimits(codexPlanLimits, snapshot);
43523
+ codexPlanLimits = snapshot;
43524
+ if (!changed) return;
43525
+ const message = buildPlanLimitsMessage(snapshot, "codex");
43526
+ if (message) broadcast(message);
43527
+ }
43528
+ function getPlanLimitsMessage(vendor = "claude") {
43271
43529
  if (!cfg.enabled || !cfg.planLimits) return null;
43530
+ if (vendor === "codex") return buildPlanLimitsMessage(codexPlanLimits, "codex");
43272
43531
  return buildPlanLimitsMessage(planLimits);
43273
43532
  }
43274
43533
  function loadBudgetState() {
@@ -46041,8 +46300,29 @@ function createBackendLanes(dependencies) {
46041
46300
  log: logger
46042
46301
  });
46043
46302
  const laneTelemetry = telemetry;
46303
+ const coderOwnedWorkspacesPath = path.join(glimmervoidHomeDir$1(), "coder-owned-workspaces.json");
46044
46304
  const coderEnv = createCoderEnvWiring({
46045
46305
  config,
46306
+ loadOwnedWorkspaces: () => {
46307
+ const outcome = loadJsonStateFileSync({
46308
+ filePath: coderOwnedWorkspacesPath,
46309
+ fsSync: fs,
46310
+ parse: (raw) => CoderOwnedWorkspaces.safeParse(raw).data?.workspaces ?? null,
46311
+ quarantine: false,
46312
+ includeNotDir: false
46313
+ });
46314
+ if (outcome.status === "loaded") return outcome.value;
46315
+ if (outcome.status === "missing") return [];
46316
+ logger.warn(`[coder-env] ignoring ${coderOwnedWorkspacesPath}: ${errorMessage(jsonStateLoadError(outcome))}`);
46317
+ return [];
46318
+ },
46319
+ saveOwnedWorkspaces: (workspaces) => writeJsonAtomic(coderOwnedWorkspacesPath, {
46320
+ version: 1,
46321
+ workspaces
46322
+ }, {
46323
+ mode: 384,
46324
+ mkdir: true
46325
+ }),
46046
46326
  persistMapping: (projectId, mapping) => persistSessionField(configStore, config, projectId, "environment", mapping),
46047
46327
  sessionsForIdleCheck: () => {
46048
46328
  const environmentsByName = new Map((config.coder?.environments ?? []).map((environment) => [environment.name, resolveCoderEnvironment(environment)]));
@@ -52449,8 +52729,8 @@ function registerControlHandlers(controlWss, deps) {
52449
52729
  if (usageSessions) ws.send(JSON.stringify(usageSessions));
52450
52730
  const usageReport = typeof getUsageReport === "function" ? getUsageReport() : null;
52451
52731
  if (usageReport) ws.send(JSON.stringify(usageReport));
52452
- const planLimits = typeof getPlanLimits === "function" ? getPlanLimits() : null;
52453
- if (planLimits) ws.send(JSON.stringify(planLimits));
52732
+ const planLimitsMessages = typeof getPlanLimits === "function" ? getPlanLimits() : [];
52733
+ for (const planLimits of planLimitsMessages) if (planLimits) ws.send(JSON.stringify(planLimits));
52454
52734
  const since = parseSinceParam(req?.url);
52455
52735
  if (controlReplayLog && since !== null) {
52456
52736
  const { entries, evicted } = controlReplayLog.entriesSince(since);
@@ -52532,7 +52812,7 @@ function createBackendControl(dependencies) {
52532
52812
  getUsageSessions: () => usage.getSessionsMessage(),
52533
52813
  getUsageReport: () => usage.getCachedReport(),
52534
52814
  requestUsageReport: (args) => usage.requestReport(args),
52535
- getPlanLimits: () => usage.getPlanLimitsMessage(),
52815
+ getPlanLimits: () => [usage.getPlanLimitsMessage("claude"), usage.getPlanLimitsMessage("codex")],
52536
52816
  readTracePage: dependencies.readTracePage,
52537
52817
  readPlanRevision: dependencies.readPlanRevision,
52538
52818
  decidePlanReview: dependencies.decidePlanReview,