glimmervoid 0.37.0 → 0.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/glimmervoid.js +16 -14
- package/dist/chunks/{adapters-BmfVPxlH.js → adapters-B0EJp_oE.js} +19 -4
- package/dist/chunks/{adapters-CqFFmM2R.js → adapters-BIbF8RJB.js} +1 -1
- package/dist/chunks/{agent-api-cli-CBgOKXoO.js → agent-api-cli-B5Tm0wXO.js} +27 -8
- package/dist/chunks/{agent-api-cli-BTHEyJKS.js → agent-api-cli-BDu1CxZf.js} +1 -1
- package/dist/chunks/{agent-api-core-DzKhakCv.js → agent-api-core-B_mUSenR.js} +30 -7
- package/dist/chunks/{agent-setup-cli-C8DGxlA-.js → agent-setup-cli-C9MMiask.js} +2 -2
- package/dist/chunks/{config-BYc2z1Oc.js → browser-config-BrBT8269.js} +9 -223
- package/dist/chunks/config-CjawSOBk.js +219 -0
- package/dist/chunks/config-store-B6mX2phK.js +1 -0
- package/dist/chunks/{config-store-B7MpiYxG.js → config-store-CKcDJebD.js} +42 -64
- package/dist/chunks/{contracts-C6ipN_zw.js → contracts-HVCEJfxs.js} +23 -250
- package/dist/chunks/factory-D7ryES9n.js +240 -0
- package/dist/chunks/{git-version-6QFnKbWa.js → git-version-rWZjF2Px.js} +1 -1
- package/dist/chunks/{grok-CZ6kP_vD.js → grok-y65HOvlU.js} +1 -1
- package/dist/chunks/{json-file-BaxGcB2R.js → json-file-CIJf0Cel.js} +190 -9
- package/dist/chunks/{knowledge-graph-cli-DApeMzA8.js → knowledge-graph-cli-ByUJoKw_.js} +1 -1
- package/dist/chunks/{loopback-post-DJ6iYWuL.js → loopback-post-DZK7YibD.js} +1 -1
- package/dist/chunks/{main-BUfjroXE.js → main-DeiiNNI7.js} +1186 -475
- package/dist/chunks/{pair-cli-GZ9t1mIO.js → pair-cli-B9hHUHLl.js} +2 -2
- package/dist/chunks/{remote-config-D2Is89Kj.js → remote-config-DlquYoVe.js} +2 -2
- package/dist/chunks/{session-BZbariv5.js → session-BlT68ppa.js} +6 -3
- package/dist/chunks/{session-routes-C4t8O8yg.js → session-routes-CT1JrjoT.js} +1 -1
- package/dist/chunks/{settings-cli-DQOjmjev.js → settings-cli-C5i9v59a.js} +3 -3
- package/dist/chunks/{ssh-sign-cli-LIBP8ppO.js → ssh-sign-cli-C74BGTQS.js} +2 -2
- package/dist/client/assets/index-ChXRDbjt.js +275 -0
- package/dist/client/assets/index-DA251uL1.css +1 -0
- package/dist/client/index.html +11 -3
- package/dist/server/index.js +1 -1
- package/dist/session/command-hook-relay.js +1 -1
- package/dist/session/hook-relay.js +1 -1
- package/dist/session/statusline-relay.js +1 -1
- package/dist/session/visions-relay.js +7 -7
- package/package.json +3 -2
- package/scripts/recover-handoff.mjs +1 -1
- package/dist/chunks/config-store-CsUoy88Q.js +0 -1
- package/dist/client/assets/index-B_Qt7fbZ.css +0 -1
- package/dist/client/assets/index-D-upBor0.js +0 -265
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { i as sanitizeOneLine, n as requireExecutableSpawnHelper, r as firstLine$1 } from "./node-pty-preflight-C8QIlrtU.js";
|
|
2
2
|
import { a as isMissingFileError, i as errorText$1, r as errorMessage, t as errorCode } from "./text-DoirglWV.js";
|
|
3
|
-
import { a as sanitizeWorktreeName, i as safePathSegment, r as isPathInside, t as caseFoldedPathKey } from "./paths-Bu4eH1Yd.js";
|
|
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 { At as
|
|
5
|
+
import { At as FULL_SHA_RE, Bt as KILLABLE_STATES, Ct as TeamReviewThreadCommentsResponse, D as SSH_SIGN_BODY_CAP_BYTES, Dt as decisionAsIfApprovalRequired, E as SIGN_URL_ENV, Et as canApproveAfterComment, F as LinkedPr, Ft as REPO_SLUG_RE, G as MyPrMergeWhenReadyRequest, H as MyPrMergeRequest, Ht as RESTARTABLE_STATES, I as MyPrAutoRebase, It as repoParts, J as MyPrSearchResponse, L as MyPrCheckContext, M as SshSignRequest, Mt as shortSha$1, N as TASK_TITLE_CONTROL_CHARACTERS, Nt as GH_SEGMENT, P as TaskTitle, Pt as NODE_ID_RE, Q as MyPrsStatus, R as MyPrCheckContextsResponse, S as RefinedTaskTitle, St as TeamReviewStatus, Tt as TeamReviewThreadsRepository, Ut as STATES, V as MyPrMergeMethod, Vt as MERGEABLE_LIVE_STATES, W as MyPrMergeStateResponse, X as MyPrThreadsResponse, Y as MyPrThreadNode, Z as MyPrsState, _t as TeamReviewActionRequest, a as AgentDispatchRequest, at as GithubReviewState, b as ORCHESTRATOR_AGENT_VERBS, bt as TeamReviewResolveResponse, ct as QueuedReview, dt as ReviewDraft, f as AgentTicketRequest, ft as ReviewFinding, gt as SearchedPr, h as CONVERSATION_TOOL_RESULT_HOOK_EVENT, ht as ReviewVerdict, it as GithubReviewDecision, jt as HEX_SHA_RE, kt as hasStandingViewerApproval, lt as ReviewChecksState, m as CONVERSATION_PERMISSION_HOOK_EVENT, mt as ReviewThreadId, n as AGENT_URL_ENV, nt as DECIDING_REVIEW_STATES, ot as PostingPlan, p as CONVERSATION_APPROVAL_SUMMARY_MAX_CHARS, pt as ReviewResult, q as MyPrSearchNode, r as ASK_USER_QUESTION_TOOL_NAME, rt as FindingSeverity, st as PrDetail, tt as CommitSha, ut as ReviewComment, w as SESSION_DOC_BODY_CAP_BYTES, wt as TeamReviewThreadResult, xt as TeamReviewStateEntry, y as ConversationQuestion, yt as TeamReviewCompareFiles, z as MyPrKeepMergeableRequest } from "./session-BlT68ppa.js";
|
|
6
6
|
import { n as execFileAsync, o as spawn, r as execFileSync, t as execFile } from "./child-process-safe-CbQnyh7D.js";
|
|
7
7
|
import { n as glimmervoidHomeDir, t as decideConfigPath } from "./config-path-core-DFt-TemS.js";
|
|
8
|
-
import { i as parseCommandLine, n as warnWhenGitIsTooOld } from "./git-version-
|
|
8
|
+
import { i as parseCommandLine, n as warnWhenGitIsTooOld } from "./git-version-rWZjF2Px.js";
|
|
9
9
|
import { n as checkGuardrailsDependencies, t as allowGuardedSpawn } from "./guardrails-deps-Cb-ki4EU.js";
|
|
10
10
|
import { t as HOOK_URL_ENV } from "./hook-relay-core-DA_bz2KA.js";
|
|
11
11
|
import { t as HOOK_VIA_GUARDRAILS_PROXY_ENV } from "./guardrails-proxy-core-BI3rdY-m.js";
|
|
@@ -16,18 +16,20 @@ import { a as SANE_YOLO_FILES } from "./rtk-hook-core-VrUafGAp.js";
|
|
|
16
16
|
import { _ as lineStartOffsets, a as projectForUri, b as replacedSpanOfWholeTextChange, c as visionsLaneReloadSignature, d as applyDidOpen, f as createDocStore, g as lineOfOffset, h as getDoc, i as isUriInProjects, l as applyDidChange, m as formatRange, n as applyEditorNotification, o as resolveVisionsScopeProjects, p as detectBlankLineBoundary, r as createEditorState, s as scopePathsOf, t as ACTIVITY_METHOD, u as applyDidClose, v as listDocs, x as uriOfParams, y as offsetOfPosition } from "./ingest-editor-core-B2tycW_m.js";
|
|
17
17
|
import { n as DEFAULT_MAX_MS, r as nextBackoffMs, t as DEFAULT_BASE_MS } from "./backoff-DjVpfHk7.js";
|
|
18
18
|
import { n as unrefTimer, t as DEFAULT_TIMER_FNS } from "./timer-deps-u2DAQLok.js";
|
|
19
|
-
import { o as permitsHelpCommand, r as describeSessionLimits } from "./session-routes-
|
|
20
|
-
import {
|
|
21
|
-
import { s as
|
|
19
|
+
import { o as permitsHelpCommand, r as describeSessionLimits } from "./session-routes-CT1JrjoT.js";
|
|
20
|
+
import { $ as CoherenceTicketCreated, C as WorkflowsState, G as USAGE_INTEGER_RANGES, N as KEEP_MERGEABLE_TIMEOUT_MINUTES_RANGE, T as USAGE_VENDOR_KEYS, X as CoherenceOrient, a as DEFAULT_FACTORY_CHECKS, b as WorkflowSearchNode, et as CoherenceWorkCreated, o as DEFAULT_FACTORY_PROTECTED_PATHS, tt as CoherenceWorkInspect, v as WorkflowCommentBody, w as USAGE_COST_MODES, x as WorkflowsSettings, y as WorkflowLabelName } from "./browser-config-BrBT8269.js";
|
|
21
|
+
import { _ as isSingleFactoryPathSegment, a as FactoryControlRequest, c as FactoryHarnessVerdict, d as FactoryQueueIntentRequest, g as FactoryWorkerEvent, i as FactoryConsequences, l as FactoryLaneState, m as FactoryReviewerOutput, n as FACTORY_ERROR_MAX_CHARS, p as FactoryReviewVerdict, r as FACTORY_PROTECTED_PATH_FLOOR, s as FactoryHarnessStateLine, u as FactoryProjectState } from "./factory-D7ryES9n.js";
|
|
22
|
+
import { _ as parseAgentVerb, a as decideFactoryControlAllowance, c as deriveChildSessionName, d as describePushRouteRefusal, f as describeUnavailableFactoryState, g as isOrchestratorAgentVerb, i as decideEditAllowance, l as describeFactorySetupProfile, m as formatFactoryStatus, n as decideAgentRequest, o as decideSpawnAllowance, p as findSessionProjectId, r as decideAttentionAllowance, s as decideTicketAction, t as REFUSAL_REASON, u as describePushLanding } from "./agent-api-core-B_mUSenR.js";
|
|
23
|
+
import { s as GLIMMERVOID_SECRET_KEYS } from "./grok-y65HOvlU.js";
|
|
22
24
|
import { n as shellQuote } from "./hook-command-core-qTNK-xMF.js";
|
|
23
|
-
import { _ as
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { A as
|
|
25
|
+
import { C as headroomFromReadings, D as parseMemAvailableBytes, E as parseCgroupV2Path, S as cgroupLimitBytes, T as parseCgroupBytes, _ as createSpawnGate, a as jsonStateLoadError, 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 isQueueAdmissionAbandoned, w as memoryReserveBytes, x as cgroupAncestorPaths, y as isQueueAdmissionTimeout } from "./json-file-CIJf0Cel.js";
|
|
26
|
+
import { B as buildExceptionProperties, F as TELEMETRY_BATCH_URL, G as decideTelemetryConsent, H as classifySessionExit, I as TELEMETRY_FLAGS_URL, J as parsePosthogReleaseId, K as exceptionFingerprint, L as adapterBucket, P as FIRST_RUN_NOTICE, Q as stableConfigKey, R as buildAiGenerationEvents, U as decideIsDevInstall, V as buildFactoryDecisionEvent, W as decideRemoteTelemetryState, Y as resolveProjectToken, a as generateProjectId, at as usablePrefixes, b as validateSettingsBlock, ct as comparableDirectoryPath, d as assetForPlatform, et as DEFAULT_BRANCH_GC_PREFIXES, f as decideRtkInstall, g as verifyDigest, h as isRtkBinaryName, i as ensureProjectIds, it as sessionIdFromBranch, lt as isSameDirectoryPath, m as installTargetPath, n as DEFAULT_CONFIG, nt as planBranchGc, o as glimmervoidHomeDir$1, ot as worktreeIntegrationTips, p as findEscapingArchiveMember, q as nodeMajorVersion, r as createConfigStore, rt as planWorktreeGc, st as canonicalizePath, t as ABORT_CONFIG_SAVE, tt as matchedPrefix, v as mergeSettingsBlock, y as settingsBlockSpecFor, z as buildBrowserExceptionProperties } from "./config-store-CKcDJebD.js";
|
|
27
|
+
import { a as ConfigUpdate, c as RUNTIME_CONFIG_SCALAR_KEYS, l as TASK_TITLE_SOURCE_PRIORITY, o as PersistedTaskTitle, r as BrowserConfig, s as ProjectConfig, u as configIssueMessage } from "./config-CjawSOBk.js";
|
|
28
|
+
import { A as ChangeMap, B as MergedPrListing, C as GithubIssuePullRequestsRepository, D as IssuesSearchResponse, E as IssueUpdate, F as BenchmarkJudgement, G as TELEMETRY_EVENT_SCHEMAS, H as PrCheckoutCaseInput, I as BenchmarkRun, J as FACTORY_DECISION_OUTPUT_MAX_BYTES, L as BenchmarkStatus, M as BenchmarkActionRequest, N as BenchmarkCase, O as IssuesState, P as BenchmarkId, R as BenchmarkSuite, S as planTitle, T as IssueKey, V as MinedPrReviewData, W as PendingCrashReport, Y as FactoryDecisionLogLine, _ as PLAN_RESULT_HOOK_EVENT, a as parseDataClientMessage, b as PlanRevision, c as DIFF_ANNOTATION_NOTE_MAX_CHARS, d as MAX_RAW_LINE_CHARS, f as TraceCheckpoint, g as PLAN_HOOK_OUTPUT_MAX_CHARS, h as PLAN_HOOK_EVENT, i as parseDataAttachHistory, j as changeMapFactId, k as IssuesStatus, l as InstallFlavor, m as PLAN_BODY_CAP_BYTES, n as HookEnvelope, o as CONTROL_FRAME_MAX_BYTES, p as TraceRecord, q as TelemetryState, r as DEEP_HISTORY_SCROLLBACK_LINES, s as ClientMessage, t as HTTP_URL_RE, u as UpdateJournal, v as PLAN_TOOL_NAME, w as GithubIssueResponse, x as parseExitPlanModeHookPayload, y as PlanDecision, z as CommitComparison } from "./contracts-HVCEJfxs.js";
|
|
29
|
+
import { A as shouldPersistWarehouse, C as isUsageFile, D as resolveProjectsDirs, E as projectDirCandidates, M as claudeCode, O as scopedClaudeRoots, S as grokRootCandidates, T as passOutcome, _ as continuationDelayMs, a as describeAgentResolvability, b as expandTilde, c as isKnownAgentId, d as resolveAdapter, f as setCustomAgents, g as codexSessionIdFromPath, h as codexRootCandidates, i as customAgentFingerprint, j as splitLines$1, k as shouldEvaluateDespiteIoFailures, l as listAgentIds, m as codexHomes, n as cachedAgentResolvability, o as getAdapter, p as codexFallbackRoots, r as commandFor, s as hookProfileOf, t as DEFAULT_AGENT_ID, u as resetCommandCache, v as decideFileRead, w as laneSpendStatus, x as grokHomes, y as dedupeCodexFiles } from "./adapters-B0EJp_oE.js";
|
|
28
30
|
import { t as HOOK_TOOLS } from "./hook-tools-BkxllUHE.js";
|
|
29
31
|
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";
|
|
30
|
-
import { a as configSiblingPath, c as defaultPairingsPath, d as decideDeviceAuth, f as deviceNameFromUserAgent, l as defaultSeenPath, n as normalizeRemoteConfig, o as createPairingsStore, p as hashSecret, r as validateRemoteConfig, s as createSeenStore, t as decideBindHost, u as DEFAULT_DEVICE_MAX_AGE_MS } from "./remote-config-
|
|
32
|
+
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-DlquYoVe.js";
|
|
31
33
|
import { _ as escapeMarkup, g as createLaneLog, n as createVisionsSetup } from "./visions-setup-B-ZeRdsi.js";
|
|
32
34
|
import { t as getRtkPath } from "./rtk-resolver-C_micGrL.js";
|
|
33
35
|
import { createRequire } from "node:module";
|
|
@@ -36,11 +38,11 @@ import path, { join, resolve } from "node:path";
|
|
|
36
38
|
import os from "node:os";
|
|
37
39
|
import { z } from "zod";
|
|
38
40
|
import { stripVTControlCharacters } from "node:util";
|
|
41
|
+
import WebSocket$1, { WebSocket, WebSocketServer } from "ws";
|
|
39
42
|
import http from "node:http";
|
|
40
|
-
import WebSocket, { WebSocketServer } from "ws";
|
|
41
43
|
import crypto, { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
42
44
|
import { isIPv6 } from "node:net";
|
|
43
|
-
import fsp, { access, chmod, copyFile, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, symlink, writeFile } from "node:fs/promises";
|
|
45
|
+
import fsp, { access, chmod, copyFile, lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, realpath, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
|
|
44
46
|
import { pipeline } from "node:stream";
|
|
45
47
|
import express from "express";
|
|
46
48
|
import pty from "node-pty";
|
|
@@ -198,6 +200,7 @@ function createLifecycle({ shutdown, httpServer, extraServers = [], onRestart =
|
|
|
198
200
|
warn: log
|
|
199
201
|
});
|
|
200
202
|
closeExtraServers(extraServers);
|
|
203
|
+
await prepareHandOffOnce();
|
|
201
204
|
let exited = false;
|
|
202
205
|
const doExit = () => {
|
|
203
206
|
if (exited) return;
|
|
@@ -1136,7 +1139,7 @@ function isPairPath(pathname) {
|
|
|
1136
1139
|
if (typeof pathname !== "string") return false;
|
|
1137
1140
|
const normalized = normalizePathname(pathname);
|
|
1138
1141
|
if (normalized.suspicious) return false;
|
|
1139
|
-
return normalized.pathname
|
|
1142
|
+
return normalized.pathname.startsWith(PAIR_PATH_PREFIX);
|
|
1140
1143
|
}
|
|
1141
1144
|
var ENCODED_WORD_SHAPE = /=\?[^?]*\?[^?]*\?[^?]*\?=/;
|
|
1142
1145
|
var ENCODED_WORD_PARTS = /^=\?([^?]*)\?([^?]*)\?([^?]*)\?=$/;
|
|
@@ -2052,7 +2055,6 @@ function projectSessionSnapshots(source) {
|
|
|
2052
2055
|
agent: source.agent,
|
|
2053
2056
|
state: source.state,
|
|
2054
2057
|
stateSince: source.stateSince,
|
|
2055
|
-
sleeping: source.sleeping,
|
|
2056
2058
|
dangerouslySkipPermissions: source.dangerouslySkipPermissions,
|
|
2057
2059
|
saneYolo: !!source.saneYolo,
|
|
2058
2060
|
ephemeral: source.ephemeral,
|
|
@@ -2063,6 +2065,7 @@ function projectSessionSnapshots(source) {
|
|
|
2063
2065
|
activeAgents: source.activeAgents,
|
|
2064
2066
|
awaitingBackgroundTasks: source.awaitingBackgroundTasks,
|
|
2065
2067
|
isCompacting: source.isCompacting ?? false,
|
|
2068
|
+
isWaitingForMemory: source.isWaitingForMemory === true,
|
|
2066
2069
|
pendingWakeup: source.pendingWakeup,
|
|
2067
2070
|
pendingPromptKind: source.pendingPromptKind,
|
|
2068
2071
|
conversationApproval: source.conversationApproval ?? null,
|
|
@@ -3265,6 +3268,87 @@ function resolveFactoryProfile(globalSettings, project) {
|
|
|
3265
3268
|
};
|
|
3266
3269
|
}
|
|
3267
3270
|
//#endregion
|
|
3271
|
+
//#region server/factory/core/factory-setup-core.ts
|
|
3272
|
+
var PACKAGE_MANAGERS = [
|
|
3273
|
+
{
|
|
3274
|
+
lockfiles: ["npm-shrinkwrap.json", "package-lock.json"],
|
|
3275
|
+
prefix: "npm run",
|
|
3276
|
+
test: "npm test"
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
lockfiles: ["pnpm-lock.yaml"],
|
|
3280
|
+
prefix: "pnpm run",
|
|
3281
|
+
test: "pnpm run test"
|
|
3282
|
+
},
|
|
3283
|
+
{
|
|
3284
|
+
lockfiles: ["yarn.lock"],
|
|
3285
|
+
prefix: "yarn",
|
|
3286
|
+
test: "yarn test"
|
|
3287
|
+
},
|
|
3288
|
+
{
|
|
3289
|
+
lockfiles: ["bun.lockb", "bun.lock"],
|
|
3290
|
+
prefix: "bun run",
|
|
3291
|
+
test: "bun run test"
|
|
3292
|
+
}
|
|
3293
|
+
];
|
|
3294
|
+
var FACTORY_TEST_COMMANDS = PACKAGE_MANAGERS.map((manager) => manager.test);
|
|
3295
|
+
var FOCUSED_RUNNERS = [
|
|
3296
|
+
{
|
|
3297
|
+
dependency: "vitest",
|
|
3298
|
+
command: "npx vitest run"
|
|
3299
|
+
},
|
|
3300
|
+
{
|
|
3301
|
+
dependency: "jest",
|
|
3302
|
+
command: "npx jest"
|
|
3303
|
+
},
|
|
3304
|
+
{
|
|
3305
|
+
dependency: "mocha",
|
|
3306
|
+
command: "npx mocha"
|
|
3307
|
+
}
|
|
3308
|
+
];
|
|
3309
|
+
function detectFactorySetup({ scripts, lockfiles, dependencies, globalChecks }) {
|
|
3310
|
+
const packageManager = PACKAGE_MANAGERS.find((manager) => manager.lockfiles.some((lockfile) => lockfiles.includes(lockfile))) ?? PACKAGE_MANAGERS[0];
|
|
3311
|
+
const hasScript = (name) => typeof scripts?.[name] === "string" && String(scripts[name]).trim().length > 0;
|
|
3312
|
+
return {
|
|
3313
|
+
checks: globalChecks === void 0 ? [
|
|
3314
|
+
"typecheck",
|
|
3315
|
+
"lint",
|
|
3316
|
+
"test"
|
|
3317
|
+
].filter(hasScript).map((name) => name === "test" ? packageManager.test : `${packageManager.prefix} ${name}`) : [...globalChecks],
|
|
3318
|
+
focusedTestCommandCandidates: [...hasScript("test:files") ? [`${packageManager.prefix} test:files`] : [], ...FOCUSED_RUNNERS.filter((runner) => dependencies.includes(runner.dependency)).map((runner) => runner.command)],
|
|
3319
|
+
widenableScopes: [...DEFAULT_WIDENABLE_SCOPES]
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
3322
|
+
var TEST_RESULT_LINE = /^[ \t]*(?:not )?ok \d+\b|^# tests \d+\b/m;
|
|
3323
|
+
var SHELL_NOT_FOUND_LINE = /^(?:\/[^\s:]*\/)?(?:sh|bash|dash|ash|zsh)(?:: (?:line )?\d+)?: [^\n]*?: (?:command )?not found/m;
|
|
3324
|
+
var LAUNCH_FAILURE_LINE = /(?:^|\n)(?:Error: )?(?:spawn[^\n]*\b(?:ENOENT|EACCES|EPERM|failed|error)\b|(?:npm (?:error|ERR!) )?missing script:|(?:error )?(?:script|command)[^\n]*(?:not found|does not exist|not recognized)|Check command contains shell syntax or is empty)/im;
|
|
3325
|
+
function classifyFactorySetupCheck(check) {
|
|
3326
|
+
const hasLaunchFailure = !check.pass && !TEST_RESULT_LINE.test(check.output) && (SHELL_NOT_FOUND_LINE.test(check.output) || LAUNCH_FAILURE_LINE.test(check.output));
|
|
3327
|
+
return {
|
|
3328
|
+
...check,
|
|
3329
|
+
status: hasLaunchFailure ? "could-not-run" : "ran"
|
|
3330
|
+
};
|
|
3331
|
+
}
|
|
3332
|
+
function decideFactorySetupProof(proposal, fullChecks, focusedChecks) {
|
|
3333
|
+
const checks = [...new Set(proposal.checks)].filter((command) => fullChecks.some((check) => check.command === command && check.status === "ran"));
|
|
3334
|
+
if (checks.length === 0) {
|
|
3335
|
+
const failures = fullChecks.map((check) => `${check.command}: ${check.output}`).join("\n");
|
|
3336
|
+
return {
|
|
3337
|
+
ok: false,
|
|
3338
|
+
reason: `Factory setup could not run any full checks.${failures ? `\n${failures}` : " No check commands were detected."}`
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
const focusedTestCommand = proposal.focusedTestCommandCandidates.find((command) => focusedChecks.some((check) => check.command === command && check.pass));
|
|
3342
|
+
return {
|
|
3343
|
+
ok: true,
|
|
3344
|
+
profile: {
|
|
3345
|
+
checks,
|
|
3346
|
+
widenableScopes: [...proposal.widenableScopes],
|
|
3347
|
+
...focusedTestCommand ? { focusedTestCommand } : {}
|
|
3348
|
+
}
|
|
3349
|
+
};
|
|
3350
|
+
}
|
|
3351
|
+
//#endregion
|
|
3268
3352
|
//#region server/lanes/core/lane-permissions-core.ts
|
|
3269
3353
|
var ACCEPT_EDITS_MODE = "acceptEdits";
|
|
3270
3354
|
var LANE_CONFIG_EDIT_DENY_RULES = Object.freeze(["Edit(**/.git/**)", "Edit(**/.claude/**)"]);
|
|
@@ -3872,6 +3956,17 @@ var FACTORY_INTEGRATION_MOVED_OUTSIDE = "integration branch moved outside the fa
|
|
|
3872
3956
|
function appendFactoryLandedSha(landedShas, landedSha) {
|
|
3873
3957
|
return [...landedShas.filter((recordedSha) => recordedSha !== landedSha), landedSha].slice(-50);
|
|
3874
3958
|
}
|
|
3959
|
+
function workerChecksOf(checks) {
|
|
3960
|
+
return checks.filter((command) => !FACTORY_TEST_COMMANDS.some((testCommand) => command === testCommand || command.startsWith(`${testCommand} `)));
|
|
3961
|
+
}
|
|
3962
|
+
var CLOSE_OUT_RUNS_EVERY_CHECK = "Do not run the full test suite; Glimmervoid runs every check in a clean checkout when you stop.";
|
|
3963
|
+
function workerCheckInstruction(checks, focusedTestCommand) {
|
|
3964
|
+
const workerChecks = workerChecksOf(checks);
|
|
3965
|
+
const workerChecksText = workerChecks.length > 0 ? ` and these checks: ${JSON.stringify(workerChecks)}` : "";
|
|
3966
|
+
if (focusedTestCommand) return `While iterating, run the test files you touched with ${focusedTestCommand} <file> ... . Before finishing, run those test files once more${workerChecksText}. ${CLOSE_OUT_RUNS_EVERY_CHECK}`;
|
|
3967
|
+
if (workerChecks.length === 0) return CLOSE_OUT_RUNS_EVERY_CHECK;
|
|
3968
|
+
return `Run these checks before finishing: ${JSON.stringify(workerChecks)}. ${CLOSE_OUT_RUNS_EVERY_CHECK}`;
|
|
3969
|
+
}
|
|
3875
3970
|
function buildWorkerPrompt({ projectName, intent, order, claudeSessionId, checks = DEFAULT_FACTORY_CHECKS, focusedTestCommand }) {
|
|
3876
3971
|
const orderSections = [
|
|
3877
3972
|
["Objective", JSON.stringify(order.opened.objective)],
|
|
@@ -3890,7 +3985,7 @@ function buildWorkerPrompt({ projectName, intent, order, claudeSessionId, checks
|
|
|
3890
3985
|
"Edit only inside these write scopes. Do not expand the ticket or the intent.",
|
|
3891
3986
|
`Start by running coherence context ${scopes} --max-bytes 12000`,
|
|
3892
3987
|
"Leave your finished changes in the working tree and never run git add or git commit. Glimmervoid commits every change in this worktree when you stop, so remove any file you do not want landed.",
|
|
3893
|
-
|
|
3988
|
+
workerCheckInstruction(checks, focusedTestCommand),
|
|
3894
3989
|
"Never push, never open PRs, never change coherence work state. Glimmervoid closes the ticket.",
|
|
3895
3990
|
"Run each shell command on its own with no pipes, &&, ; or subshells, because chained commands are denied.",
|
|
3896
3991
|
"A denied command means \"not allowed here\". Do not retry it another way.",
|
|
@@ -4730,7 +4825,15 @@ function registerEphemeralSession({ map, id, sess, closeSessionDataClients, logP
|
|
|
4730
4825
|
closeSessionDataClients(id);
|
|
4731
4826
|
}
|
|
4732
4827
|
};
|
|
4733
|
-
|
|
4828
|
+
const releaseAfterListeners = () => {
|
|
4829
|
+
removeFromMap();
|
|
4830
|
+
setImmediate(() => sess.destroy());
|
|
4831
|
+
};
|
|
4832
|
+
sess.on("exit", releaseAfterListeners);
|
|
4833
|
+
sess.on("error", () => {
|
|
4834
|
+
if (sess.hasLivePty !== false) return;
|
|
4835
|
+
releaseAfterListeners();
|
|
4836
|
+
});
|
|
4734
4837
|
const origDestroy = sess.destroy.bind(sess);
|
|
4735
4838
|
sess.destroy = () => {
|
|
4736
4839
|
origDestroy();
|
|
@@ -4741,19 +4844,32 @@ function registerEphemeralSession({ map, id, sess, closeSessionDataClients, logP
|
|
|
4741
4844
|
//#region server/agent-api-wiring.ts
|
|
4742
4845
|
var AGENT_LANE_TAG = "agent-spawn";
|
|
4743
4846
|
var SPAWN_FAILED_MESSAGE = "could not spawn the session";
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4847
|
+
var BYTES_PER_MEGABYTE = 1048576;
|
|
4848
|
+
function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoardSessions, makeSession, wireSessionEvents, closeSessionDataClients, broadcastControl, spawnGate, sessionDocs, factory, recordLane = null, memoryAdmission = null, logger = console }) {
|
|
4849
|
+
async function describeMemoryShortfall() {
|
|
4850
|
+
const shortfall = await memoryAdmission?.describeShortfallNow("session") ?? null;
|
|
4851
|
+
if (shortfall === null) return null;
|
|
4852
|
+
const toMegabytes = (bytes) => Math.round(bytes / BYTES_PER_MEGABYTE);
|
|
4853
|
+
const heldDetails = [...shortfall.heldBytes > 0 ? [`${toMegabytes(shortfall.heldBytes)} MB held for recent starts`] : [], ...shortfall.waitingStartCount > 0 ? [`${shortfall.waitingStartCount} ${shortfall.waitingStartCount === 1 ? "start" : "starts"} already waiting for memory`] : []];
|
|
4854
|
+
const heldClause = heldDetails.length > 0 ? ` (${heldDetails.join(", ")})` : "";
|
|
4855
|
+
return `memory is low: ${toMegabytes(shortfall.headroomBytes)} MB free${heldClause}, and a new session needs ${toMegabytes(shortfall.reserveBytes)} MB. Try again once other work finishes`;
|
|
4856
|
+
}
|
|
4857
|
+
function refuseByBudget(parent) {
|
|
4747
4858
|
const allowance = decideSpawnAllowance({
|
|
4748
|
-
...
|
|
4859
|
+
...parent.agentSpawnBudget(),
|
|
4749
4860
|
isFactoryWorker: parent.factoryRole === "worker",
|
|
4750
4861
|
isEphemeral: parent.ephemeral
|
|
4751
4862
|
});
|
|
4752
|
-
if (
|
|
4863
|
+
if (allowance.ok) return null;
|
|
4864
|
+
return {
|
|
4753
4865
|
ok: false,
|
|
4754
4866
|
status: allowance.status,
|
|
4755
4867
|
error: allowance.reason
|
|
4756
4868
|
};
|
|
4869
|
+
}
|
|
4870
|
+
async function spawn(parent, request) {
|
|
4871
|
+
const budgetRefusal = refuseByBudget(parent);
|
|
4872
|
+
if (budgetRefusal) return budgetRefusal;
|
|
4757
4873
|
if (request.agent && !isKnownAgentId(request.agent)) return {
|
|
4758
4874
|
ok: false,
|
|
4759
4875
|
status: 400,
|
|
@@ -4765,6 +4881,15 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
|
|
|
4765
4881
|
status: 403,
|
|
4766
4882
|
error: `a guarded session can only spawn a guarded agent, and ${agentId} does not run inside the guardrails`
|
|
4767
4883
|
};
|
|
4884
|
+
const memoryShortfall = await describeMemoryShortfall();
|
|
4885
|
+
if (memoryShortfall) return {
|
|
4886
|
+
ok: false,
|
|
4887
|
+
status: 503,
|
|
4888
|
+
error: memoryShortfall
|
|
4889
|
+
};
|
|
4890
|
+
const budgetRefusalAfterMemoryRead = refuseByBudget(parent);
|
|
4891
|
+
if (budgetRefusalAfterMemoryRead) return budgetRefusalAfterMemoryRead;
|
|
4892
|
+
const budget = parent.agentSpawnBudget();
|
|
4768
4893
|
const name = deriveChildSessionName(request.name || parent.name, listAllSessions().map((session) => session.name));
|
|
4769
4894
|
const id = `${AGENT_LANE_TAG}-${crypto.randomUUID()}`;
|
|
4770
4895
|
const project = {
|
|
@@ -4886,6 +5011,70 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
|
|
|
4886
5011
|
if (routeRefusal) return routeRefusal;
|
|
4887
5012
|
return describePushLanding(await session.mergeAndContinue({ force: true }));
|
|
4888
5013
|
}
|
|
5014
|
+
async function setUpFactoryProject(session, factoryPort, projects) {
|
|
5015
|
+
const projectId = findSessionProjectId(config.projects, session.path);
|
|
5016
|
+
if (!projectId) return {
|
|
5017
|
+
status: 404,
|
|
5018
|
+
body: {
|
|
5019
|
+
ok: false,
|
|
5020
|
+
error: `no configured project has the path ${session.path}, so the operator has to add this repository as a project first`
|
|
5021
|
+
}
|
|
5022
|
+
};
|
|
5023
|
+
const outcome = await factoryPort.rerunSetup(projectId);
|
|
5024
|
+
if (!outcome.ok) return {
|
|
5025
|
+
status: 409,
|
|
5026
|
+
body: {
|
|
5027
|
+
ok: false,
|
|
5028
|
+
error: outcome.reason
|
|
5029
|
+
}
|
|
5030
|
+
};
|
|
5031
|
+
return {
|
|
5032
|
+
status: 200,
|
|
5033
|
+
body: {
|
|
5034
|
+
ok: true,
|
|
5035
|
+
projectId,
|
|
5036
|
+
profile: outcome.profile,
|
|
5037
|
+
text: describeFactorySetupProfile(outcome.profile, projects.some((project) => project.projectId === projectId))
|
|
5038
|
+
}
|
|
5039
|
+
};
|
|
5040
|
+
}
|
|
5041
|
+
async function queueFactoryIntent(session, factoryPort, projects, request) {
|
|
5042
|
+
const projectId = findSessionProjectId(config.projects, session.path);
|
|
5043
|
+
if (!projectId) return {
|
|
5044
|
+
status: 404,
|
|
5045
|
+
body: {
|
|
5046
|
+
ok: false,
|
|
5047
|
+
error: `no configured project has the path ${session.path}, so the operator has to add this repository as a project first`
|
|
5048
|
+
}
|
|
5049
|
+
};
|
|
5050
|
+
if (!projects.some((project) => project.projectId === projectId)) return {
|
|
5051
|
+
status: 409,
|
|
5052
|
+
body: {
|
|
5053
|
+
ok: false,
|
|
5054
|
+
error: `${projectId} is not a factory project yet: commit a coherence.config.json holding {} at the repository root, land it on the integration branch with glimmervoid push, then queue again`
|
|
5055
|
+
}
|
|
5056
|
+
};
|
|
5057
|
+
const outcome = await factoryPort.queueIntent({
|
|
5058
|
+
...request,
|
|
5059
|
+
projectId
|
|
5060
|
+
});
|
|
5061
|
+
if (!outcome.ok) return {
|
|
5062
|
+
status: 409,
|
|
5063
|
+
body: {
|
|
5064
|
+
ok: false,
|
|
5065
|
+
error: outcome.error ?? "the factory refused the intent"
|
|
5066
|
+
}
|
|
5067
|
+
};
|
|
5068
|
+
return {
|
|
5069
|
+
status: 200,
|
|
5070
|
+
body: {
|
|
5071
|
+
ok: true,
|
|
5072
|
+
projectId,
|
|
5073
|
+
workId: outcome.workId,
|
|
5074
|
+
text: `Queued ${outcome.workId ?? "the intent"} on ${projectId}. Follow it with glimmervoid factory status --project ${projectId}`
|
|
5075
|
+
}
|
|
5076
|
+
};
|
|
5077
|
+
}
|
|
4889
5078
|
async function handle(session, verb, payload) {
|
|
4890
5079
|
const parsed = parseAgentVerb(verb, payload);
|
|
4891
5080
|
if (!parsed.ok) return {
|
|
@@ -4895,7 +5084,7 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
|
|
|
4895
5084
|
error: parsed.error
|
|
4896
5085
|
}
|
|
4897
5086
|
};
|
|
4898
|
-
if (parsed.verb === "factory-status" || parsed.verb === "factory-retry") {
|
|
5087
|
+
if (parsed.verb === "factory-status" || parsed.verb === "factory-retry" || parsed.verb === "factory-setup" || parsed.verb === "factory-queue") {
|
|
4899
5088
|
const allowance = decideFactoryControlAllowance(roleFactsOf(session));
|
|
4900
5089
|
if (!allowance.ok) return {
|
|
4901
5090
|
status: allowance.status,
|
|
@@ -4912,6 +5101,8 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
|
|
|
4912
5101
|
error: "Factory is not running"
|
|
4913
5102
|
}
|
|
4914
5103
|
};
|
|
5104
|
+
if (parsed.verb === "factory-setup") return setUpFactoryProject(session, factory, projects);
|
|
5105
|
+
if (parsed.verb === "factory-queue") return queueFactoryIntent(session, factory, projects, parsed.request);
|
|
4915
5106
|
if (parsed.verb === "factory-status") {
|
|
4916
5107
|
const selectedProjects = parsed.request.projectId === void 0 ? projects : projects.filter((project) => project.projectId === parsed.request.projectId);
|
|
4917
5108
|
if (parsed.request.projectId !== void 0 && selectedProjects.length === 0) return {
|
|
@@ -4961,13 +5152,6 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
|
|
|
4961
5152
|
}
|
|
4962
5153
|
};
|
|
4963
5154
|
}
|
|
4964
|
-
if (parsed.verb === "dispatch") return {
|
|
4965
|
-
status: 403,
|
|
4966
|
-
body: {
|
|
4967
|
-
ok: false,
|
|
4968
|
-
reason: "dispatch requires a live factory orchestrator"
|
|
4969
|
-
}
|
|
4970
|
-
};
|
|
4971
5155
|
if (parsed.verb === "board") return {
|
|
4972
5156
|
status: 200,
|
|
4973
5157
|
body: {
|
|
@@ -5200,6 +5384,14 @@ function resolveSessionSize({ viewers, departingKey, current }) {
|
|
|
5200
5384
|
//#endregion
|
|
5201
5385
|
//#region session/core/screen-keeper-core.ts
|
|
5202
5386
|
var SCREEN_RESET = "\x1Bc\x1B[2J\x1B[3J\x1B[H";
|
|
5387
|
+
var SCREEN_KEEPER_SCROLLBACK = DEEP_HISTORY_SCROLLBACK_LINES;
|
|
5388
|
+
var SCREEN_KEEPER_SLICE_ROWS = 1e3;
|
|
5389
|
+
var SCREEN_KEEPER_SLICE_JOINER = "\x1B[0m\r\n";
|
|
5390
|
+
var SCREEN_KEEPER_PENDING_CAP = 4194304;
|
|
5391
|
+
var SCREEN_KEEPER_SEED_LIMIT = 1048576;
|
|
5392
|
+
function isKeeperOverrun(pushedOffset, parsedOffset, pendingCap = SCREEN_KEEPER_PENDING_CAP) {
|
|
5393
|
+
return pushedOffset - parsedOffset > pendingCap;
|
|
5394
|
+
}
|
|
5203
5395
|
function pendingResizes(queue, parsedOffset) {
|
|
5204
5396
|
let cut = 0;
|
|
5205
5397
|
while (cut < queue.length) {
|
|
@@ -5212,6 +5404,36 @@ function pendingResizes(queue, parsedOffset) {
|
|
|
5212
5404
|
rest: queue.slice(cut)
|
|
5213
5405
|
};
|
|
5214
5406
|
}
|
|
5407
|
+
function unwrappedLineAtOrBefore(line, isWrapped) {
|
|
5408
|
+
let candidate = line;
|
|
5409
|
+
while (candidate > 0 && isWrapped(candidate)) candidate -= 1;
|
|
5410
|
+
return candidate;
|
|
5411
|
+
}
|
|
5412
|
+
function sliceEndLine(start, limit, sliceRows, isWrapped) {
|
|
5413
|
+
const target = Math.min(start + sliceRows, limit);
|
|
5414
|
+
const unwrappedBefore = unwrappedLineAtOrBefore(target, isWrapped);
|
|
5415
|
+
if (unwrappedBefore > start) return unwrappedBefore;
|
|
5416
|
+
let unwrappedAfter = target;
|
|
5417
|
+
while (unwrappedAfter < limit && isWrapped(unwrappedAfter)) unwrappedAfter += 1;
|
|
5418
|
+
return unwrappedAfter;
|
|
5419
|
+
}
|
|
5420
|
+
function planDeepSlices({ lineCount, rows, sliceRows, isWrapped }) {
|
|
5421
|
+
const finalStart = unwrappedLineAtOrBefore(Math.max(0, lineCount - rows - sliceRows), isWrapped);
|
|
5422
|
+
const ranges = [];
|
|
5423
|
+
let start = 0;
|
|
5424
|
+
while (start < finalStart) {
|
|
5425
|
+
const end = sliceEndLine(start, finalStart, sliceRows, isWrapped);
|
|
5426
|
+
ranges.push({
|
|
5427
|
+
start,
|
|
5428
|
+
end: end - 1
|
|
5429
|
+
});
|
|
5430
|
+
start = end;
|
|
5431
|
+
}
|
|
5432
|
+
return {
|
|
5433
|
+
ranges,
|
|
5434
|
+
finalScrollback: Math.max(0, lineCount - rows - finalStart)
|
|
5435
|
+
};
|
|
5436
|
+
}
|
|
5215
5437
|
//#endregion
|
|
5216
5438
|
//#region server/ws-sender.ts
|
|
5217
5439
|
var OPEN = 1;
|
|
@@ -5451,93 +5673,117 @@ function createBackendWebSockets(dependencies) {
|
|
|
5451
5673
|
socket.close(1008, "Session not found");
|
|
5452
5674
|
return;
|
|
5453
5675
|
}
|
|
5454
|
-
|
|
5455
|
-
if (
|
|
5456
|
-
|
|
5457
|
-
|
|
5676
|
+
socket.on("error", (error) => reportDataSocketError(session, error));
|
|
5677
|
+
if (parseDataAttachHistory(request.url) === "screen") {
|
|
5678
|
+
wireDataSocket(socket, session, sessionId, session.getScreenSnapshot());
|
|
5679
|
+
return;
|
|
5458
5680
|
}
|
|
5459
|
-
const
|
|
5460
|
-
const
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
session.resize(resolved.cols, resolved.rows);
|
|
5470
|
-
};
|
|
5471
|
-
const releaseViewerSize = () => {
|
|
5472
|
-
if (!viewerSizeMap.get(socket)) return;
|
|
5473
|
-
viewerSizeMap.set(socket, null);
|
|
5474
|
-
if (sessionDataClients.get(activeSessionId) !== viewerSizeMap) return;
|
|
5475
|
-
applyResolvedSize(true);
|
|
5476
|
-
};
|
|
5477
|
-
const snapshot = session.getScreenSnapshot();
|
|
5478
|
-
const sender = createWsSender(socket, {
|
|
5479
|
-
source: { getBufferSince: (offset) => session.getBufferSince(offset) },
|
|
5480
|
-
startOffset: snapshot.offset
|
|
5481
|
-
});
|
|
5482
|
-
sendPtySizeFrame(sender, session.ptySize());
|
|
5483
|
-
if (snapshot.data) sender.sendImmediate(snapshot.data, 0);
|
|
5484
|
-
const dataListener = (data) => sender.onData(data);
|
|
5485
|
-
session.on("data", dataListener);
|
|
5486
|
-
const resizeListener = (size) => sendPtySizeFrame(sender, size);
|
|
5487
|
-
session.on("resize", resizeListener);
|
|
5488
|
-
socket.on("message", (raw) => {
|
|
5489
|
-
const message = parseDataClientMessage(String(raw));
|
|
5490
|
-
if (!message) return;
|
|
5491
|
-
if (message.type === "input") {
|
|
5492
|
-
if (message.data.length > 16384) {
|
|
5493
|
-
logger.warn(`[data-ws] Rejected oversized input (${message.data.length} chars) for ${session.name}`);
|
|
5494
|
-
broadcastControl({
|
|
5495
|
-
type: "session-error",
|
|
5496
|
-
id: session.id,
|
|
5497
|
-
session: session.name,
|
|
5498
|
-
message: "Paste too large - try pasting smaller chunks",
|
|
5499
|
-
timestamp: Date.now()
|
|
5500
|
-
});
|
|
5501
|
-
return;
|
|
5502
|
-
}
|
|
5503
|
-
session.write(message.data);
|
|
5504
|
-
sender.markInputFlush();
|
|
5505
|
-
session.noteOperatorInput(message.data);
|
|
5681
|
+
const messagesBeforeWiring = [];
|
|
5682
|
+
const holdMessage = (raw) => {
|
|
5683
|
+
messagesBeforeWiring.push(raw);
|
|
5684
|
+
};
|
|
5685
|
+
socket.on("message", holdMessage);
|
|
5686
|
+
session.getDeepScreenSnapshot().then((snapshot) => {
|
|
5687
|
+
socket.off("message", holdMessage);
|
|
5688
|
+
if (socket.readyState !== WebSocket.OPEN) return;
|
|
5689
|
+
if (getSession(sessionId) !== session) {
|
|
5690
|
+
socket.close(1008, "Session not found");
|
|
5506
5691
|
return;
|
|
5507
5692
|
}
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5693
|
+
wireDataSocket(socket, session, sessionId, snapshot);
|
|
5694
|
+
for (const raw of messagesBeforeWiring) socket.emit("message", raw, false);
|
|
5695
|
+
}, (error) => {
|
|
5696
|
+
logger.warn(`[data-ws] Deep history failed for ${session.name}: ${String(error)}`);
|
|
5697
|
+
socket.close(1011, "History unavailable");
|
|
5698
|
+
});
|
|
5699
|
+
});
|
|
5700
|
+
}
|
|
5701
|
+
function reportDataSocketError(session, error) {
|
|
5702
|
+
const reason = error.code === "WS_ERR_UNSUPPORTED_MESSAGE_LENGTH" ? "Message too large - try pasting smaller chunks" : error.message;
|
|
5703
|
+
logger.warn(`[data-ws] Error for ${session.name}: ${error.message}`);
|
|
5704
|
+
broadcastControl({
|
|
5705
|
+
type: "session-error",
|
|
5706
|
+
id: session.id,
|
|
5707
|
+
session: session.name,
|
|
5708
|
+
message: reason,
|
|
5709
|
+
timestamp: Date.now()
|
|
5710
|
+
});
|
|
5711
|
+
}
|
|
5712
|
+
function wireDataSocket(socket, session, sessionId, snapshot) {
|
|
5713
|
+
let viewerSizes = sessionDataClients.get(sessionId);
|
|
5714
|
+
if (!viewerSizes) {
|
|
5715
|
+
viewerSizes = /* @__PURE__ */ new Map();
|
|
5716
|
+
sessionDataClients.set(sessionId, viewerSizes);
|
|
5717
|
+
}
|
|
5718
|
+
const activeSessionId = sessionId;
|
|
5719
|
+
const viewerSizeMap = viewerSizes;
|
|
5720
|
+
viewerSizeMap.set(socket, null);
|
|
5721
|
+
const applyResolvedSize = (departing) => {
|
|
5722
|
+
const resolved = resolveSessionSize({
|
|
5723
|
+
viewers: viewerSizeMap,
|
|
5724
|
+
...departing ? { departingKey: socket } : {},
|
|
5725
|
+
current: session.ptySize()
|
|
5726
|
+
});
|
|
5727
|
+
if (!resolved.changed) return;
|
|
5728
|
+
session.resize(resolved.cols, resolved.rows);
|
|
5729
|
+
};
|
|
5730
|
+
const releaseViewerSize = () => {
|
|
5731
|
+
if (!viewerSizeMap.get(socket)) return;
|
|
5732
|
+
viewerSizeMap.set(socket, null);
|
|
5733
|
+
if (sessionDataClients.get(activeSessionId) !== viewerSizeMap) return;
|
|
5734
|
+
applyResolvedSize(true);
|
|
5735
|
+
};
|
|
5736
|
+
const sender = createWsSender(socket, {
|
|
5737
|
+
source: { getBufferSince: (offset) => session.getBufferSince(offset) },
|
|
5738
|
+
startOffset: snapshot.offset
|
|
5739
|
+
});
|
|
5740
|
+
sendPtySizeFrame(sender, session.ptySize());
|
|
5741
|
+
if (snapshot.data) sender.sendImmediate(snapshot.data, 0);
|
|
5742
|
+
const dataListener = (data) => sender.onData(data);
|
|
5743
|
+
session.on("data", dataListener);
|
|
5744
|
+
const resizeListener = (size) => sendPtySizeFrame(sender, size);
|
|
5745
|
+
session.on("resize", resizeListener);
|
|
5746
|
+
socket.on("message", (raw) => {
|
|
5747
|
+
const message = parseDataClientMessage(String(raw));
|
|
5748
|
+
if (!message) return;
|
|
5749
|
+
if (message.type === "input") {
|
|
5750
|
+
if (message.data.length > 16384) {
|
|
5751
|
+
logger.warn(`[data-ws] Rejected oversized input (${message.data.length} chars) for ${session.name}`);
|
|
5752
|
+
broadcastControl({
|
|
5753
|
+
type: "session-error",
|
|
5754
|
+
id: session.id,
|
|
5755
|
+
session: session.name,
|
|
5756
|
+
message: "Paste too large - try pasting smaller chunks",
|
|
5757
|
+
timestamp: Date.now()
|
|
5514
5758
|
});
|
|
5515
|
-
applyResolvedSize(false);
|
|
5516
5759
|
return;
|
|
5517
5760
|
}
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5761
|
+
session.write(message.data);
|
|
5762
|
+
sender.markInputFlush();
|
|
5763
|
+
session.noteOperatorInput(message.data);
|
|
5764
|
+
return;
|
|
5765
|
+
}
|
|
5766
|
+
if (message.type === "claim") {
|
|
5767
|
+
nextViewerResizeSeq += 1;
|
|
5768
|
+
viewerSizeMap.set(socket, {
|
|
5769
|
+
cols: message.cols,
|
|
5770
|
+
rows: message.rows,
|
|
5771
|
+
resizeSeq: nextViewerResizeSeq
|
|
5529
5772
|
});
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5773
|
+
applyResolvedSize(false);
|
|
5774
|
+
return;
|
|
5775
|
+
}
|
|
5776
|
+
if (message.type === "unview") releaseViewerSize();
|
|
5777
|
+
});
|
|
5778
|
+
socket.on("close", () => {
|
|
5779
|
+
session.removeListener("data", dataListener);
|
|
5780
|
+
session.removeListener("resize", resizeListener);
|
|
5781
|
+
sender.destroy();
|
|
5782
|
+
releaseViewerSize();
|
|
5783
|
+
const clients = sessionDataClients.get(activeSessionId);
|
|
5784
|
+
if (!clients) return;
|
|
5785
|
+
clients.delete(socket);
|
|
5786
|
+
if (clients.size === 0) sessionDataClients.delete(activeSessionId);
|
|
5541
5787
|
});
|
|
5542
5788
|
}
|
|
5543
5789
|
function handleUpgrade(request, socket, head) {
|
|
@@ -5622,6 +5868,7 @@ function createBackendShutdown(dependencies) {
|
|
|
5622
5868
|
isShuttingDown = true;
|
|
5623
5869
|
const stoppers = createStopperCollector();
|
|
5624
5870
|
dependencies.cancelAutoResume();
|
|
5871
|
+
dependencies.cancelPostTurnLeftoverCleanup?.();
|
|
5625
5872
|
clearInterval(dependencies.healthInterval);
|
|
5626
5873
|
const stopConfigWatch = dependencies.getStopConfigWatch();
|
|
5627
5874
|
if (stopConfigWatch) stopConfigWatch();
|
|
@@ -6561,7 +6808,7 @@ function runGh(args, options = {}) {
|
|
|
6561
6808
|
}
|
|
6562
6809
|
//#endregion
|
|
6563
6810
|
//#region server/post-turn-checker.ts
|
|
6564
|
-
var GIT_TIMEOUT_MS$
|
|
6811
|
+
var GIT_TIMEOUT_MS$2 = 5e3;
|
|
6565
6812
|
var DEFAULTS = Object.freeze({
|
|
6566
6813
|
enabled: true,
|
|
6567
6814
|
mode: "report",
|
|
@@ -6597,9 +6844,7 @@ var DEFAULTS = Object.freeze({
|
|
|
6597
6844
|
],
|
|
6598
6845
|
maxFiles: 200,
|
|
6599
6846
|
maxFileBytes: 524288,
|
|
6600
|
-
debounceMs: 1500
|
|
6601
|
-
runOnExit: false,
|
|
6602
|
-
reportDir: ".glimmervoid/post-turn"
|
|
6847
|
+
debounceMs: 1500
|
|
6603
6848
|
});
|
|
6604
6849
|
function explicitMode(mode) {
|
|
6605
6850
|
return mode === "fix" ? "fix" : "report";
|
|
@@ -6630,9 +6875,7 @@ var SCALAR_KEYS = [
|
|
|
6630
6875
|
"mode",
|
|
6631
6876
|
"maxFiles",
|
|
6632
6877
|
"maxFileBytes",
|
|
6633
|
-
"debounceMs"
|
|
6634
|
-
"runOnExit",
|
|
6635
|
-
"reportDir"
|
|
6878
|
+
"debounceMs"
|
|
6636
6879
|
];
|
|
6637
6880
|
function resolveCheckConfig(globalCfg, projectCfg) {
|
|
6638
6881
|
const base = JSON.parse(JSON.stringify(DEFAULTS));
|
|
@@ -6654,16 +6897,14 @@ function resolveCheckConfig(globalCfg, projectCfg) {
|
|
|
6654
6897
|
exclude: base.exclude,
|
|
6655
6898
|
maxFiles: base.maxFiles,
|
|
6656
6899
|
maxFileBytes: base.maxFileBytes,
|
|
6657
|
-
debounceMs: base.debounceMs
|
|
6658
|
-
runOnExit: !!base.runOnExit,
|
|
6659
|
-
reportDir: base.reportDir
|
|
6900
|
+
debounceMs: base.debounceMs
|
|
6660
6901
|
};
|
|
6661
6902
|
}
|
|
6662
6903
|
var GIT_MAX_BUFFER_BYTES$1 = 8388608;
|
|
6663
6904
|
async function execGit(args, cwd) {
|
|
6664
6905
|
const result = await runGit(args, {
|
|
6665
6906
|
cwd,
|
|
6666
|
-
timeoutMs: GIT_TIMEOUT_MS$
|
|
6907
|
+
timeoutMs: GIT_TIMEOUT_MS$2,
|
|
6667
6908
|
maxBuffer: GIT_MAX_BUFFER_BYTES$1,
|
|
6668
6909
|
trim: false
|
|
6669
6910
|
});
|
|
@@ -6694,7 +6935,7 @@ async function listChangedFiles(root) {
|
|
|
6694
6935
|
}
|
|
6695
6936
|
return files;
|
|
6696
6937
|
}
|
|
6697
|
-
async function runPostTurnChecks({ cwd, config,
|
|
6938
|
+
async function runPostTurnChecks({ cwd, config, deps = {} } = {}) {
|
|
6698
6939
|
const started = Date.now();
|
|
6699
6940
|
const cfg = config || resolveCheckConfig();
|
|
6700
6941
|
const report = {
|
|
@@ -6794,14 +7035,7 @@ async function runPostTurnChecks({ cwd, config, sessionId, deps = {} } = {}) {
|
|
|
6794
7035
|
});
|
|
6795
7036
|
}
|
|
6796
7037
|
}
|
|
6797
|
-
|
|
6798
|
-
if (sessionId) try {
|
|
6799
|
-
const dir = path.join(root, cfg.reportDir);
|
|
6800
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
6801
|
-
const safe = String(sessionId).replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
6802
|
-
writeJsonAtomicSync(path.join(dir, `${safe}.json`), finalReport);
|
|
6803
|
-
} catch {}
|
|
6804
|
-
return finalReport;
|
|
7038
|
+
return done();
|
|
6805
7039
|
}
|
|
6806
7040
|
//#endregion
|
|
6807
7041
|
//#region server/session/session-event-wiring.ts
|
|
@@ -6879,7 +7113,9 @@ function createSessionEventWiring(dependencies) {
|
|
|
6879
7113
|
duration_seconds: durationSeconds
|
|
6880
7114
|
});
|
|
6881
7115
|
};
|
|
6882
|
-
|
|
7116
|
+
let liveTranscriptPath = null;
|
|
7117
|
+
session.on("claude-session-id", ({ id, vendor, isResumeTarget, transcriptPath }) => {
|
|
7118
|
+
liveTranscriptPath = transcriptPath;
|
|
6883
7119
|
if (isResumeTarget) persistProjectField("resumeSessionId", id);
|
|
6884
7120
|
dependencies.recordLane(id, INTERACTIVE_LANE, vendor);
|
|
6885
7121
|
dependencies.usage.refreshSessions();
|
|
@@ -6912,6 +7148,7 @@ function createSessionEventWiring(dependencies) {
|
|
|
6912
7148
|
session.on("teardown", () => {
|
|
6913
7149
|
clearCompactionConfirm();
|
|
6914
7150
|
isAcknowledgeDeferredForCompaction = false;
|
|
7151
|
+
dependencies.getIngestLane()?.detachSessionTap(session);
|
|
6915
7152
|
});
|
|
6916
7153
|
const resolvePostTurn = () => {
|
|
6917
7154
|
const project = dependencies.config.projects.find((candidate) => candidate.id === session.id);
|
|
@@ -6927,8 +7164,7 @@ function createSessionEventWiring(dependencies) {
|
|
|
6927
7164
|
if (!runConfig?.enabled) return;
|
|
6928
7165
|
runPostTurnChecks({
|
|
6929
7166
|
cwd: session.effectiveCwd(),
|
|
6930
|
-
config: runConfig
|
|
6931
|
-
sessionId: session.id
|
|
7167
|
+
config: runConfig
|
|
6932
7168
|
}).then((report) => {
|
|
6933
7169
|
dependencies.broadcastControl({
|
|
6934
7170
|
type: "post-turn-result",
|
|
@@ -6940,7 +7176,7 @@ function createSessionEventWiring(dependencies) {
|
|
|
6940
7176
|
}).catch((error) => dependencies.logger.warn(`[${session.name}] post-turn checks failed: ${errorMessage(error)}`));
|
|
6941
7177
|
}, config.debounceMs);
|
|
6942
7178
|
});
|
|
6943
|
-
session.on("post-turn-check", () => dependencies.usage.nudgeSession());
|
|
7179
|
+
session.on("post-turn-check", () => dependencies.usage.nudgeSession(liveTranscriptPath));
|
|
6944
7180
|
session.on("state-change", ({ from, to, event, detail }) => {
|
|
6945
7181
|
if (to === STATES.IDLE || to === STATES.COMPLETE) dependencies.taskTitleRefiner?.onTurnEnd(session);
|
|
6946
7182
|
if (event === "spawn_success") {
|
|
@@ -7038,8 +7274,6 @@ function createSessionEventWiring(dependencies) {
|
|
|
7038
7274
|
relay("wakeup-change", "session-wakeup");
|
|
7039
7275
|
relay("prompt-kind-change", "session-prompt");
|
|
7040
7276
|
relay("conversation-change", "session-conversation");
|
|
7041
|
-
relay("sleep", "session-sleep");
|
|
7042
|
-
relay("wake", "session-wake");
|
|
7043
7277
|
session.on("merge-status", ({ mergeStatus, reason, parked }) => {
|
|
7044
7278
|
dependencies.broadcastControl({
|
|
7045
7279
|
type: "session-merge-status",
|
|
@@ -7392,6 +7626,9 @@ function runAutoResume(sessions, config, spawnGate, logger = console) {
|
|
|
7392
7626
|
runs.push(spawnGate.run(() => {
|
|
7393
7627
|
if (session.state !== STATES.DORMANT) return null;
|
|
7394
7628
|
return session.start({ resumePrompt });
|
|
7629
|
+
}, {
|
|
7630
|
+
isStillWanted: () => session.state === STATES.DORMANT,
|
|
7631
|
+
onMemoryWait: (isWaiting) => session.setWaitingForMemory(isWaiting)
|
|
7395
7632
|
}).catch((error) => {
|
|
7396
7633
|
logger.warn(`[boot] auto-resume failed for ${session.name}: ${errorMessage(error)}`);
|
|
7397
7634
|
}));
|
|
@@ -12732,22 +12969,33 @@ var DEFAULT_STABILIZATION_MS = 1500;
|
|
|
12732
12969
|
var OSC_START = "\x1B]0;";
|
|
12733
12970
|
var BEL = "\x07";
|
|
12734
12971
|
var ST = "\x1B\\";
|
|
12735
|
-
|
|
12972
|
+
var MAX_PENDING_TITLE = 4096;
|
|
12973
|
+
function nextTerminatorIndex(buffer, terminator, previousIndex, fromIndex) {
|
|
12974
|
+
if (previousIndex >= fromIndex) return previousIndex;
|
|
12975
|
+
const index = buffer.indexOf(terminator, fromIndex);
|
|
12976
|
+
return index === -1 ? Number.POSITIVE_INFINITY : index;
|
|
12977
|
+
}
|
|
12978
|
+
function findOscTitle(buffer, fromIndex, terminators) {
|
|
12736
12979
|
const start = buffer.indexOf(OSC_START, fromIndex);
|
|
12737
12980
|
if (start === -1) return null;
|
|
12738
12981
|
const contentStart = start + 4;
|
|
12739
|
-
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
|
|
12743
|
-
const end = useBel ? belEnd : stEnd;
|
|
12982
|
+
terminators.bel = nextTerminatorIndex(buffer, BEL, terminators.bel, contentStart);
|
|
12983
|
+
terminators.st = nextTerminatorIndex(buffer, ST, terminators.st, contentStart);
|
|
12984
|
+
const end = Math.min(terminators.bel, terminators.st);
|
|
12985
|
+
if (end === Number.POSITIVE_INFINITY) return null;
|
|
12744
12986
|
return {
|
|
12745
12987
|
start,
|
|
12746
12988
|
end,
|
|
12747
|
-
next: end + (
|
|
12989
|
+
next: end + (end === terminators.bel ? 1 : 2),
|
|
12748
12990
|
title: buffer.slice(contentStart, end)
|
|
12749
12991
|
};
|
|
12750
12992
|
}
|
|
12993
|
+
function unterminatedTail(buffer, fromIndex) {
|
|
12994
|
+
const openStart = buffer.indexOf(OSC_START, fromIndex);
|
|
12995
|
+
if (openStart !== -1) return buffer.length - openStart > MAX_PENDING_TITLE ? "" : buffer.slice(openStart);
|
|
12996
|
+
for (let prefixLength = 3; prefixLength > 0; prefixLength -= 1) if (buffer.endsWith(OSC_START.slice(0, prefixLength))) return buffer.slice(buffer.length - prefixLength);
|
|
12997
|
+
return "";
|
|
12998
|
+
}
|
|
12751
12999
|
var OscTitleSource = class extends EventEmitter {
|
|
12752
13000
|
_stabilizationMs;
|
|
12753
13001
|
_profile;
|
|
@@ -12774,16 +13022,19 @@ var OscTitleSource = class extends EventEmitter {
|
|
|
12774
13022
|
}
|
|
12775
13023
|
feed(chunk) {
|
|
12776
13024
|
if (this._destroyed || !chunk) return;
|
|
12777
|
-
this._pending
|
|
12778
|
-
|
|
13025
|
+
const buffer = this._pending ? this._pending + chunk : chunk;
|
|
13026
|
+
const terminators = {
|
|
13027
|
+
bel: -1,
|
|
13028
|
+
st: -1
|
|
13029
|
+
};
|
|
12779
13030
|
let cursor = 0;
|
|
12780
13031
|
while (true) {
|
|
12781
|
-
const osc = findOscTitle(
|
|
13032
|
+
const osc = findOscTitle(buffer, cursor, terminators);
|
|
12782
13033
|
if (!osc) break;
|
|
12783
13034
|
this._processTitle(osc.title);
|
|
12784
13035
|
cursor = osc.next;
|
|
12785
13036
|
}
|
|
12786
|
-
|
|
13037
|
+
this._pending = unterminatedTail(buffer, cursor);
|
|
12787
13038
|
}
|
|
12788
13039
|
_classifyTitle(trimmed, char) {
|
|
12789
13040
|
const profile = this._profile;
|
|
@@ -13532,7 +13783,7 @@ async function startCodexAppServerBridge(options) {
|
|
|
13532
13783
|
clearTimeout(killTimer);
|
|
13533
13784
|
}
|
|
13534
13785
|
} finally {
|
|
13535
|
-
if (socket && socket.readyState !== WebSocket.CLOSED) guard(() => socket?.terminate());
|
|
13786
|
+
if (socket && socket.readyState !== WebSocket$1.CLOSED) guard(() => socket?.terminate());
|
|
13536
13787
|
await rm(privateDirectory, {
|
|
13537
13788
|
recursive: true,
|
|
13538
13789
|
force: true
|
|
@@ -13542,7 +13793,7 @@ async function startCodexAppServerBridge(options) {
|
|
|
13542
13793
|
return stopPromise;
|
|
13543
13794
|
}
|
|
13544
13795
|
function send(message) {
|
|
13545
|
-
if (isStopped || socket?.readyState !== WebSocket.OPEN) throw new Error("Codex app-server socket is not open");
|
|
13796
|
+
if (isStopped || socket?.readyState !== WebSocket$1.OPEN) throw new Error("Codex app-server socket is not open");
|
|
13546
13797
|
socket.send(JSON.stringify(message));
|
|
13547
13798
|
}
|
|
13548
13799
|
function request(method, params, timeoutMs = 15e3) {
|
|
@@ -13572,7 +13823,7 @@ async function startCodexAppServerBridge(options) {
|
|
|
13572
13823
|
});
|
|
13573
13824
|
}
|
|
13574
13825
|
function answerApproval(requestId, decision) {
|
|
13575
|
-
if (!activeApproval || activeApproval.requestId !== requestId || isStopped || socket?.readyState !== WebSocket.OPEN) return false;
|
|
13826
|
+
if (!activeApproval || activeApproval.requestId !== requestId || isStopped || socket?.readyState !== WebSocket$1.OPEN) return false;
|
|
13576
13827
|
try {
|
|
13577
13828
|
if (activeApproval.kind === "permissions") {
|
|
13578
13829
|
if (!approvalPermissions) return false;
|
|
@@ -13762,7 +14013,7 @@ async function startCodexAppServerBridge(options) {
|
|
|
13762
14013
|
function connect(url) {
|
|
13763
14014
|
if (socket || isStopped) return;
|
|
13764
14015
|
bridge.url = url;
|
|
13765
|
-
socket = options.createSocket?.(url, { Authorization: `Bearer ${token}` }) ?? new WebSocket(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
14016
|
+
socket = options.createSocket?.(url, { Authorization: `Bearer ${token}` }) ?? new WebSocket$1(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
13766
14017
|
socket.on("error", (failure) => guard(() => reportError(failure)));
|
|
13767
14018
|
socket.on("message", (frame, isBinary) => guard(() => handleMessage(frame, isBinary)));
|
|
13768
14019
|
socket.on("close", () => guard(() => {
|
|
@@ -14694,11 +14945,16 @@ function serializeMouseEncoding(encoding) {
|
|
|
14694
14945
|
//#region session/screen-keeper.ts
|
|
14695
14946
|
var { Terminal } = headless;
|
|
14696
14947
|
var { SerializeAddon } = createRequire(import.meta.url)("@xterm/addon-serialize");
|
|
14697
|
-
function
|
|
14948
|
+
function yieldToEventLoop$2() {
|
|
14949
|
+
return new Promise((resolve) => {
|
|
14950
|
+
setImmediate(resolve);
|
|
14951
|
+
});
|
|
14952
|
+
}
|
|
14953
|
+
function createScreenKeeper({ cols, rows }, { pendingCap = SCREEN_KEEPER_PENDING_CAP, sliceRows = SCREEN_KEEPER_SLICE_ROWS } = {}) {
|
|
14698
14954
|
const terminal = new Terminal({
|
|
14699
14955
|
cols,
|
|
14700
14956
|
rows,
|
|
14701
|
-
scrollback:
|
|
14957
|
+
scrollback: SCREEN_KEEPER_SCROLLBACK,
|
|
14702
14958
|
allowProposedApi: true
|
|
14703
14959
|
});
|
|
14704
14960
|
const serializer = new SerializeAddon();
|
|
@@ -14716,25 +14972,106 @@ function createScreenKeeper({ cols, rows }) {
|
|
|
14716
14972
|
return false;
|
|
14717
14973
|
}));
|
|
14718
14974
|
let pushedOffset = 0;
|
|
14975
|
+
let writtenOffset = 0;
|
|
14719
14976
|
let parsedOffset = 0;
|
|
14720
14977
|
let queue = [];
|
|
14721
14978
|
let disposed = false;
|
|
14979
|
+
let overrun = false;
|
|
14980
|
+
let heldChunks = [];
|
|
14981
|
+
let isHoldingWrites = false;
|
|
14982
|
+
let isBufferFrozen = false;
|
|
14983
|
+
let drainWaiters = [];
|
|
14984
|
+
let deepSerializeInFlight = null;
|
|
14722
14985
|
function drainResizes() {
|
|
14723
|
-
if (disposed) return;
|
|
14986
|
+
if (disposed || isBufferFrozen) return;
|
|
14724
14987
|
const split = pendingResizes(queue, parsedOffset);
|
|
14725
14988
|
queue = split.rest;
|
|
14726
14989
|
for (const marker of split.due) terminal.resize(marker.cols, marker.rows);
|
|
14727
14990
|
}
|
|
14991
|
+
function settleDrainWaiters() {
|
|
14992
|
+
const waiters = drainWaiters;
|
|
14993
|
+
drainWaiters = [];
|
|
14994
|
+
for (const resolve of waiters) resolve();
|
|
14995
|
+
}
|
|
14996
|
+
function writeToTerminal(chunk) {
|
|
14997
|
+
writtenOffset += chunk.length;
|
|
14998
|
+
const reachedOffset = writtenOffset;
|
|
14999
|
+
terminal.write(chunk, () => {
|
|
15000
|
+
if (disposed) return;
|
|
15001
|
+
parsedOffset = reachedOffset;
|
|
15002
|
+
drainResizes();
|
|
15003
|
+
if (parsedOffset === writtenOffset) settleDrainWaiters();
|
|
15004
|
+
});
|
|
15005
|
+
}
|
|
15006
|
+
function waitForWrittenBytesToParse() {
|
|
15007
|
+
if (disposed || parsedOffset === writtenOffset) return Promise.resolve();
|
|
15008
|
+
return new Promise((resolve) => {
|
|
15009
|
+
drainWaiters.push(resolve);
|
|
15010
|
+
});
|
|
15011
|
+
}
|
|
15012
|
+
function releaseHeldWrites() {
|
|
15013
|
+
isBufferFrozen = false;
|
|
15014
|
+
isHoldingWrites = false;
|
|
15015
|
+
if (disposed) return;
|
|
15016
|
+
drainResizes();
|
|
15017
|
+
const chunks = heldChunks;
|
|
15018
|
+
heldChunks = [];
|
|
15019
|
+
for (const chunk of chunks) writeToTerminal(chunk);
|
|
15020
|
+
}
|
|
15021
|
+
function serializeAttach() {
|
|
15022
|
+
return serializer.serialize({ scrollback: 500 }) + serializeMouseEncoding(mouseEncoding);
|
|
15023
|
+
}
|
|
15024
|
+
async function serializeFrozenSlices() {
|
|
15025
|
+
const buffer = terminal.buffer.normal;
|
|
15026
|
+
const plan = planDeepSlices({
|
|
15027
|
+
lineCount: buffer.length,
|
|
15028
|
+
rows: terminal.rows,
|
|
15029
|
+
sliceRows,
|
|
15030
|
+
isWrapped: (line) => buffer.getLine(line)?.isWrapped ?? false
|
|
15031
|
+
});
|
|
15032
|
+
const slices = [];
|
|
15033
|
+
for (const range of plan.ranges) {
|
|
15034
|
+
slices.push(serializer.serialize({
|
|
15035
|
+
range,
|
|
15036
|
+
excludeAltBuffer: true,
|
|
15037
|
+
excludeModes: true
|
|
15038
|
+
}));
|
|
15039
|
+
await yieldToEventLoop$2();
|
|
15040
|
+
if (disposed) return null;
|
|
15041
|
+
}
|
|
15042
|
+
slices.push(serializer.serialize({ scrollback: plan.finalScrollback }) + serializeMouseEncoding(mouseEncoding));
|
|
15043
|
+
return {
|
|
15044
|
+
data: slices.join(SCREEN_KEEPER_SLICE_JOINER),
|
|
15045
|
+
parsedOffset
|
|
15046
|
+
};
|
|
15047
|
+
}
|
|
15048
|
+
async function serializeDeepHoldingWrites() {
|
|
15049
|
+
isHoldingWrites = true;
|
|
15050
|
+
try {
|
|
15051
|
+
await waitForWrittenBytesToParse();
|
|
15052
|
+
if (disposed) return null;
|
|
15053
|
+
isBufferFrozen = true;
|
|
15054
|
+
return await serializeFrozenSlices();
|
|
15055
|
+
} finally {
|
|
15056
|
+
releaseHeldWrites();
|
|
15057
|
+
deepSerializeInFlight = null;
|
|
15058
|
+
}
|
|
15059
|
+
}
|
|
14728
15060
|
return {
|
|
14729
15061
|
push(chunk) {
|
|
14730
|
-
if (disposed ||
|
|
15062
|
+
if (disposed || overrun) return false;
|
|
15063
|
+
if (chunk.length === 0) return true;
|
|
15064
|
+
if (isKeeperOverrun(pushedOffset, parsedOffset, pendingCap)) {
|
|
15065
|
+
overrun = true;
|
|
15066
|
+
return false;
|
|
15067
|
+
}
|
|
14731
15068
|
pushedOffset += chunk.length;
|
|
14732
|
-
|
|
14733
|
-
|
|
14734
|
-
|
|
14735
|
-
|
|
14736
|
-
|
|
14737
|
-
|
|
15069
|
+
if (isHoldingWrites) {
|
|
15070
|
+
heldChunks.push(chunk);
|
|
15071
|
+
return true;
|
|
15072
|
+
}
|
|
15073
|
+
writeToTerminal(chunk);
|
|
15074
|
+
return true;
|
|
14738
15075
|
},
|
|
14739
15076
|
resize(nextCols, nextRows) {
|
|
14740
15077
|
if (disposed) return;
|
|
@@ -14746,11 +15083,21 @@ function createScreenKeeper({ cols, rows }) {
|
|
|
14746
15083
|
drainResizes();
|
|
14747
15084
|
},
|
|
14748
15085
|
parsedOffset: () => parsedOffset,
|
|
14749
|
-
|
|
15086
|
+
isOverrun: () => overrun,
|
|
15087
|
+
isDrained: () => parsedOffset === pushedOffset,
|
|
15088
|
+
serialize: serializeAttach,
|
|
15089
|
+
serializeDeep() {
|
|
15090
|
+
if (disposed) return Promise.resolve(null);
|
|
15091
|
+
if (deepSerializeInFlight) return deepSerializeInFlight;
|
|
15092
|
+
deepSerializeInFlight = serializeDeepHoldingWrites();
|
|
15093
|
+
return deepSerializeInFlight;
|
|
15094
|
+
},
|
|
14750
15095
|
dispose() {
|
|
14751
15096
|
if (disposed) return;
|
|
14752
15097
|
disposed = true;
|
|
14753
15098
|
queue = [];
|
|
15099
|
+
heldChunks = [];
|
|
15100
|
+
settleDrainWaiters();
|
|
14754
15101
|
for (const handler of mouseEncodingHandlers) handler.dispose();
|
|
14755
15102
|
serializer.dispose();
|
|
14756
15103
|
terminal.dispose();
|
|
@@ -14838,31 +15185,44 @@ function createSessionOutput(options) {
|
|
|
14838
15185
|
return changed;
|
|
14839
15186
|
}
|
|
14840
15187
|
function push(chunk) {
|
|
14841
|
-
if (!keeper) {
|
|
14842
|
-
keeperBaseOffset = ring.stats().total;
|
|
14843
|
-
keeper = keeperFactory(ptySize());
|
|
14844
|
-
}
|
|
14845
|
-
keeper.push(chunk);
|
|
14846
15188
|
ring.push(chunk);
|
|
15189
|
+
if (keeper?.push(chunk)) return;
|
|
15190
|
+
if (keeper && !keeper.isDrained()) return;
|
|
15191
|
+
disposeScreenKeeper();
|
|
15192
|
+
const { total, bytes } = ring.stats();
|
|
15193
|
+
keeperBaseOffset = Math.max(total - bytes, total - SCREEN_KEEPER_SEED_LIMIT);
|
|
15194
|
+
keeper = keeperFactory(ptySize());
|
|
15195
|
+
keeper.push(ring.since(keeperBaseOffset).data);
|
|
14847
15196
|
}
|
|
14848
15197
|
function reset() {
|
|
14849
15198
|
disposeScreenKeeper();
|
|
14850
15199
|
ring.reset();
|
|
14851
15200
|
}
|
|
14852
|
-
function
|
|
14853
|
-
|
|
14854
|
-
if (!keeper) return {
|
|
15201
|
+
function ringSnapshot() {
|
|
15202
|
+
return {
|
|
14855
15203
|
data: SCREEN_RESET + ring.replay(),
|
|
14856
|
-
offset
|
|
15204
|
+
offset: ring.stats().total
|
|
14857
15205
|
};
|
|
15206
|
+
}
|
|
15207
|
+
function snapshot() {
|
|
15208
|
+
if (!keeper || keeper.isOverrun()) return ringSnapshot();
|
|
14858
15209
|
const tail = ring.since(keeperBaseOffset + keeper.parsedOffset());
|
|
14859
|
-
if (tail.evicted) return
|
|
14860
|
-
data: SCREEN_RESET + ring.replay(),
|
|
14861
|
-
offset
|
|
14862
|
-
};
|
|
15210
|
+
if (tail.evicted) return ringSnapshot();
|
|
14863
15211
|
return {
|
|
14864
15212
|
data: SCREEN_RESET + keeper.serialize() + tail.data,
|
|
14865
|
-
offset
|
|
15213
|
+
offset: ring.stats().total
|
|
15214
|
+
};
|
|
15215
|
+
}
|
|
15216
|
+
async function deepSnapshot() {
|
|
15217
|
+
const deepKeeper = keeper;
|
|
15218
|
+
if (!deepKeeper || deepKeeper.isOverrun()) return ringSnapshot();
|
|
15219
|
+
const deepScreen = await deepKeeper.serializeDeep();
|
|
15220
|
+
if (!deepScreen || keeper !== deepKeeper) return ringSnapshot();
|
|
15221
|
+
const tail = ring.since(keeperBaseOffset + deepScreen.parsedOffset);
|
|
15222
|
+
if (tail.evicted) return ringSnapshot();
|
|
15223
|
+
return {
|
|
15224
|
+
data: SCREEN_RESET + deepScreen.data + tail.data,
|
|
15225
|
+
offset: ring.stats().total
|
|
14866
15226
|
};
|
|
14867
15227
|
}
|
|
14868
15228
|
options.on("exit", disposeScreenKeeper);
|
|
@@ -14870,6 +15230,7 @@ function createSessionOutput(options) {
|
|
|
14870
15230
|
push,
|
|
14871
15231
|
since: (offset) => ring.since(offset),
|
|
14872
15232
|
snapshot,
|
|
15233
|
+
deepSnapshot,
|
|
14873
15234
|
reset,
|
|
14874
15235
|
setMax: (bytes) => ring.setMax(bytes),
|
|
14875
15236
|
stats: () => ring.stats(),
|
|
@@ -21148,7 +21509,6 @@ function createSessionBackgroundTracking({ detectBackgroundAgents, agentTtlMs, s
|
|
|
21148
21509
|
var KILL_POLL_INTERVAL_MS = 200;
|
|
21149
21510
|
var KILL_MAX_WAIT_MS = 3e3;
|
|
21150
21511
|
var KILL_REAP_MAX_WAIT_MS = 2400;
|
|
21151
|
-
var SLEEP_KILL_TIMEOUT_MS = 9e5;
|
|
21152
21512
|
var MAX_PENDING_ATTENTION_NOTES = 5;
|
|
21153
21513
|
var GUARDRAILS_UNAPPLIED_ERROR = "guardrail settings could not be applied, session not started";
|
|
21154
21514
|
var GUARDRAILS_REFUSED_PREFIX = "session not started, the guardrails refused: ";
|
|
@@ -21203,13 +21563,10 @@ var Session = class extends EventEmitter {
|
|
|
21203
21563
|
_receivedFirstOutput;
|
|
21204
21564
|
_killPollTimer;
|
|
21205
21565
|
_killReapTimer;
|
|
21206
|
-
_sleepKillTimer;
|
|
21207
21566
|
_titleQuietFallbackTimer;
|
|
21208
21567
|
_resolveKillReap;
|
|
21209
21568
|
_killReap;
|
|
21210
21569
|
_exitReap;
|
|
21211
|
-
_sleeping;
|
|
21212
|
-
_autoKilled;
|
|
21213
21570
|
_destroyed;
|
|
21214
21571
|
_pendingRestart;
|
|
21215
21572
|
_finishing;
|
|
@@ -21223,6 +21580,7 @@ var Session = class extends EventEmitter {
|
|
|
21223
21580
|
_lastSignal;
|
|
21224
21581
|
_compaction;
|
|
21225
21582
|
_pendingPromptKind;
|
|
21583
|
+
_isWaitingForMemory;
|
|
21226
21584
|
_pendingPromptToolName;
|
|
21227
21585
|
_pendingAttentionNotes;
|
|
21228
21586
|
_titleQuiet;
|
|
@@ -21292,9 +21650,6 @@ var Session = class extends EventEmitter {
|
|
|
21292
21650
|
this._resolveKillReap = null;
|
|
21293
21651
|
this._killReap = null;
|
|
21294
21652
|
this._exitReap = null;
|
|
21295
|
-
this._sleeping = false;
|
|
21296
|
-
this._sleepKillTimer = null;
|
|
21297
|
-
this._autoKilled = false;
|
|
21298
21653
|
this._destroyed = false;
|
|
21299
21654
|
this._pendingRestart = false;
|
|
21300
21655
|
this._finishing = false;
|
|
@@ -21346,6 +21701,7 @@ var Session = class extends EventEmitter {
|
|
|
21346
21701
|
this._lastSignal = null;
|
|
21347
21702
|
this._compaction = createCompactionTracking();
|
|
21348
21703
|
this._pendingPromptKind = null;
|
|
21704
|
+
this._isWaitingForMemory = false;
|
|
21349
21705
|
this._pendingPromptToolName = null;
|
|
21350
21706
|
this._pendingAttentionNotes = [];
|
|
21351
21707
|
this._titleQuiet = false;
|
|
@@ -21489,11 +21845,8 @@ var Session = class extends EventEmitter {
|
|
|
21489
21845
|
this._taskTitleSources.customTitle = this.customTitle;
|
|
21490
21846
|
this._updateTaskTitle();
|
|
21491
21847
|
}
|
|
21492
|
-
get
|
|
21493
|
-
return
|
|
21494
|
-
...this._taskTitleSources,
|
|
21495
|
-
pendingPromptTitle: null
|
|
21496
|
-
}).taskTitle;
|
|
21848
|
+
get refinedTaskTitle() {
|
|
21849
|
+
return this._taskTitleSources.refinedTitle ?? null;
|
|
21497
21850
|
}
|
|
21498
21851
|
get persistedTaskTitle() {
|
|
21499
21852
|
return buildPersistedTaskTitle(this._taskTitleSources);
|
|
@@ -21717,10 +22070,16 @@ var Session = class extends EventEmitter {
|
|
|
21717
22070
|
});
|
|
21718
22071
|
if (this._compaction.isCompacting !== wasCompacting) this._emitSessionDetail();
|
|
21719
22072
|
}
|
|
22073
|
+
setWaitingForMemory(isWaiting) {
|
|
22074
|
+
if (isWaiting === this._isWaitingForMemory) return;
|
|
22075
|
+
this._isWaitingForMemory = isWaiting;
|
|
22076
|
+
this._emitSessionDetail();
|
|
22077
|
+
}
|
|
21720
22078
|
_emitSessionDetail() {
|
|
21721
22079
|
this.emit("prompt-kind-change", {
|
|
21722
22080
|
pendingPromptKind: this._pendingPromptKind,
|
|
21723
|
-
isCompacting: this._compaction.isCompacting
|
|
22081
|
+
isCompacting: this._compaction.isCompacting,
|
|
22082
|
+
isWaitingForMemory: this._isWaitingForMemory
|
|
21724
22083
|
});
|
|
21725
22084
|
}
|
|
21726
22085
|
_finishCompaction() {
|
|
@@ -21907,9 +22266,6 @@ var Session = class extends EventEmitter {
|
|
|
21907
22266
|
this._pendingAttentionNotes = [];
|
|
21908
22267
|
this._raiseAttention(releasing.join(" | "));
|
|
21909
22268
|
}
|
|
21910
|
-
get sleeping() {
|
|
21911
|
-
return this._sleeping;
|
|
21912
|
-
}
|
|
21913
22269
|
get hasLivePty() {
|
|
21914
22270
|
return !this._destroyed && !!this.ptyProcess && !!this._ptyAlive;
|
|
21915
22271
|
}
|
|
@@ -22051,8 +22407,6 @@ var Session = class extends EventEmitter {
|
|
|
22051
22407
|
id: this.id,
|
|
22052
22408
|
name: this.name,
|
|
22053
22409
|
state: this.state,
|
|
22054
|
-
sleeping: this._sleeping,
|
|
22055
|
-
autoKilled: this._autoKilled,
|
|
22056
22410
|
destroyed: this._destroyed,
|
|
22057
22411
|
pendingRestart: this._pendingRestart,
|
|
22058
22412
|
hasPty: this.ptyProcess !== null,
|
|
@@ -22063,10 +22417,7 @@ var Session = class extends EventEmitter {
|
|
|
22063
22417
|
auditLogLength: this.auditLog.length,
|
|
22064
22418
|
dataListenerCount: this.listenerCount("data"),
|
|
22065
22419
|
hookSeen: this._hookSeen,
|
|
22066
|
-
timers: {
|
|
22067
|
-
sleepKill: this._sleepKillTimer !== null,
|
|
22068
|
-
killPoll: this._killPollTimer !== null
|
|
22069
|
-
}
|
|
22420
|
+
timers: { killPoll: this._killPollTimer !== null }
|
|
22070
22421
|
};
|
|
22071
22422
|
}
|
|
22072
22423
|
getDebugState() {
|
|
@@ -22084,7 +22435,6 @@ var Session = class extends EventEmitter {
|
|
|
22084
22435
|
agent: this.agentId,
|
|
22085
22436
|
state: this.state,
|
|
22086
22437
|
stateSince: this.stateSince,
|
|
22087
|
-
sleeping: this._sleeping,
|
|
22088
22438
|
dangerouslySkipPermissions: this.dangerouslySkipPermissions,
|
|
22089
22439
|
saneYolo: this.saneYolo,
|
|
22090
22440
|
ephemeral: this.ephemeral,
|
|
@@ -22100,6 +22450,7 @@ var Session = class extends EventEmitter {
|
|
|
22100
22450
|
hasConversationBridge: this._codexBridge !== null,
|
|
22101
22451
|
isConversationViewOn: this._isConversationViewEnabled(),
|
|
22102
22452
|
isCompacting: this._compaction.isCompacting,
|
|
22453
|
+
isWaitingForMemory: this._isWaitingForMemory,
|
|
22103
22454
|
hasPlan: this._planReviewPort?.hasPlan(this.id) === true,
|
|
22104
22455
|
mergeStatus: this.mergeStatus,
|
|
22105
22456
|
mergeReason: this.mergeReason,
|
|
@@ -22180,6 +22531,7 @@ var Session = class extends EventEmitter {
|
|
|
22180
22531
|
}
|
|
22181
22532
|
async _startBody({ fresh = false, resumePrompt = null } = {}) {
|
|
22182
22533
|
if (this._destroyed) return;
|
|
22534
|
+
this.setWaitingForMemory(false);
|
|
22183
22535
|
if (this.ptyProcess) {
|
|
22184
22536
|
console.warn(`[session:${this.name}] start() called while PTY exists - killing previous PTY first`);
|
|
22185
22537
|
const oldPid = this.ptyProcess.pid;
|
|
@@ -22201,9 +22553,7 @@ var Session = class extends EventEmitter {
|
|
|
22201
22553
|
}
|
|
22202
22554
|
if (this.state === STATES.DORMANT) this.transition("user_start");
|
|
22203
22555
|
this._receivedFirstOutput = false;
|
|
22204
|
-
this._sleeping = false;
|
|
22205
22556
|
this._clearDetectionTracking();
|
|
22206
|
-
this._autoKilled = false;
|
|
22207
22557
|
this._output.reset();
|
|
22208
22558
|
this.emit("rebaseline");
|
|
22209
22559
|
this._resetDetectionSources({ quiet: false });
|
|
@@ -22555,7 +22905,7 @@ var Session = class extends EventEmitter {
|
|
|
22555
22905
|
this._receivedFirstOutput = true;
|
|
22556
22906
|
this.transition("first_output");
|
|
22557
22907
|
}
|
|
22558
|
-
|
|
22908
|
+
try {
|
|
22559
22909
|
this._titleSource.feed(data);
|
|
22560
22910
|
} catch (err) {
|
|
22561
22911
|
console.error(`[session:${this.name}] title source error: ${errorMessage(err)}`);
|
|
@@ -22599,6 +22949,9 @@ var Session = class extends EventEmitter {
|
|
|
22599
22949
|
getScreenSnapshot() {
|
|
22600
22950
|
return this._output.snapshot();
|
|
22601
22951
|
}
|
|
22952
|
+
getDeepScreenSnapshot() {
|
|
22953
|
+
return this._output.deepSnapshot();
|
|
22954
|
+
}
|
|
22602
22955
|
ptySize() {
|
|
22603
22956
|
const size = this._output.ptySize();
|
|
22604
22957
|
return {
|
|
@@ -22759,36 +23112,9 @@ var Session = class extends EventEmitter {
|
|
|
22759
23112
|
if (!DISMISSIBLE_STATES.has(this.state)) return false;
|
|
22760
23113
|
return this.transition("user_dismiss");
|
|
22761
23114
|
}
|
|
22762
|
-
sleep() {
|
|
22763
|
-
if (this._sleeping) return;
|
|
22764
|
-
if (!RESTARTABLE_STATES.includes(this.state)) return;
|
|
22765
|
-
this._sleeping = true;
|
|
22766
|
-
this._resetDetectionSources();
|
|
22767
|
-
this._scheduleSleepKill();
|
|
22768
|
-
this.emit("sleep");
|
|
22769
|
-
}
|
|
22770
|
-
wake() {
|
|
22771
|
-
if (this._destroyed) return;
|
|
22772
|
-
if (!this._sleeping) return;
|
|
22773
|
-
this._sleeping = false;
|
|
22774
|
-
this._clearSleepKill();
|
|
22775
|
-
this.emit("wake");
|
|
22776
|
-
if (this._autoKilled && !this._teardownPending() && RESTARTABLE_STATES.includes(this.state)) this.restart();
|
|
22777
|
-
}
|
|
22778
23115
|
_teardownPending() {
|
|
22779
23116
|
return this._pendingRestart || this._finishing;
|
|
22780
23117
|
}
|
|
22781
|
-
_scheduleSleepKill() {
|
|
22782
|
-
this._armTimer("_sleepKillTimer", SLEEP_KILL_TIMEOUT_MS, () => {
|
|
22783
|
-
if (!this._sleeping) return;
|
|
22784
|
-
const wasActive = KILLABLE_STATES.includes(this.state);
|
|
22785
|
-
this.killSession();
|
|
22786
|
-
if (wasActive && RESTARTABLE_STATES.includes(this.state)) this._autoKilled = true;
|
|
22787
|
-
});
|
|
22788
|
-
}
|
|
22789
|
-
_clearSleepKill() {
|
|
22790
|
-
this._clearTimer("_sleepKillTimer");
|
|
22791
|
-
}
|
|
22792
23118
|
killSession() {
|
|
22793
23119
|
if (!KILLABLE_STATES.includes(this.state)) return false;
|
|
22794
23120
|
this.kill();
|
|
@@ -22876,7 +23202,6 @@ var Session = class extends EventEmitter {
|
|
|
22876
23202
|
this._agentToken = null;
|
|
22877
23203
|
this._commitSigningToken = null;
|
|
22878
23204
|
this._pendingAttentionNotes = [];
|
|
22879
|
-
this._clearSleepKill();
|
|
22880
23205
|
this._clearPendingPaste();
|
|
22881
23206
|
this._hooks.cleanup();
|
|
22882
23207
|
this._disposeGuardrailsLaunch(this._guardrailsLaunch);
|
|
@@ -23007,6 +23332,20 @@ function appendTrailStep(trail, step, maxSteps = TRAIL_MAX_STEPS) {
|
|
|
23007
23332
|
};
|
|
23008
23333
|
}
|
|
23009
23334
|
//#endregion
|
|
23335
|
+
//#region shared/contracts/guardrails-launch.ts
|
|
23336
|
+
var GuardrailsLaunchLogin = z.object({
|
|
23337
|
+
linkPath: z.string().min(1),
|
|
23338
|
+
targetPath: z.string().min(1)
|
|
23339
|
+
}).strict();
|
|
23340
|
+
var GuardrailsLaunchOwner = z.object({
|
|
23341
|
+
ownerPid: z.number().int().positive(),
|
|
23342
|
+
ownerHost: z.string(),
|
|
23343
|
+
ownerPidNamespace: z.string().nullable(),
|
|
23344
|
+
ownsTempDir: z.boolean(),
|
|
23345
|
+
logins: z.array(GuardrailsLaunchLogin),
|
|
23346
|
+
placeholderPaths: z.array(z.string().min(1))
|
|
23347
|
+
}).strict();
|
|
23348
|
+
//#endregion
|
|
23010
23349
|
//#region server/core/claude-trust-core.ts
|
|
23011
23350
|
var CLAUDE_GLOBAL_CONFIG_FILE_NAME = ".claude.json";
|
|
23012
23351
|
var CLAUDE_LEGACY_CONFIG_FILE_NAME = ".config.json";
|
|
@@ -23099,6 +23438,21 @@ function decideFolderTrust(configText, repositoryRoot, platform) {
|
|
|
23099
23438
|
};
|
|
23100
23439
|
}
|
|
23101
23440
|
//#endregion
|
|
23441
|
+
//#region server/guardrails/core/guardrails-bridge-socket-core.ts
|
|
23442
|
+
var LONGEST_BRIDGE_SOCKET_NAME = "claude-socks-0123456789abcdef.sock";
|
|
23443
|
+
var UNIX_SOCKET_PATH_BYTE_LIMIT = 107;
|
|
23444
|
+
var SHORT_BRIDGE_SOCKET_FOLDER = "/tmp";
|
|
23445
|
+
function bridgeSocketPathBytes(tempDirectory) {
|
|
23446
|
+
return Buffer.byteLength(path.join(tempDirectory, LONGEST_BRIDGE_SOCKET_NAME));
|
|
23447
|
+
}
|
|
23448
|
+
function hasRoomForBridgeSocket(tempDirectory, platform) {
|
|
23449
|
+
return platform !== "linux" || bridgeSocketPathBytes(tempDirectory) <= UNIX_SOCKET_PATH_BYTE_LIMIT;
|
|
23450
|
+
}
|
|
23451
|
+
function bridgeSocketLauncherEnv(inheritedTempDirectory, platform) {
|
|
23452
|
+
if (hasRoomForBridgeSocket(inheritedTempDirectory, platform)) return {};
|
|
23453
|
+
return { TMPDIR: SHORT_BRIDGE_SOCKET_FOLDER };
|
|
23454
|
+
}
|
|
23455
|
+
//#endregion
|
|
23102
23456
|
//#region server/guardrails/guardrails-launch.ts
|
|
23103
23457
|
var LAUNCH_ID_SHAPE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
23104
23458
|
var GUARDED_ENV_COMMAND = "/usr/bin/env";
|
|
@@ -23106,9 +23460,11 @@ var POLICY_FOLDER = "guardrails-policies";
|
|
|
23106
23460
|
var TEMP_FOLDER = "guardrails-temp";
|
|
23107
23461
|
var LAUNCH_HOME_FOLDER = "guardrails-homes";
|
|
23108
23462
|
var START_FOLDER = "start";
|
|
23463
|
+
var OWNER_FILE_SUFFIX = ".owner.json";
|
|
23109
23464
|
var ENTER_FOLDER_SCRIPT = "cd \"$1\" && shift && exec \"$@\"";
|
|
23110
23465
|
var LOGIN_FILE_MODE = 384;
|
|
23111
23466
|
var ABSENT_PATH_DENY_PLATFORM = "darwin";
|
|
23467
|
+
var OWN_PID_NAMESPACE_LINK = "/proc/self/ns/pid";
|
|
23112
23468
|
async function isPresent(candidate) {
|
|
23113
23469
|
try {
|
|
23114
23470
|
await access(candidate);
|
|
@@ -23216,7 +23572,7 @@ async function lstatWhenPresent(candidate) {
|
|
|
23216
23572
|
async function isRegularFile(candidate) {
|
|
23217
23573
|
return (await lstatWhenPresent(candidate))?.isFile() ?? false;
|
|
23218
23574
|
}
|
|
23219
|
-
async function isRealDirectory(candidate) {
|
|
23575
|
+
async function isRealDirectory$1(candidate) {
|
|
23220
23576
|
return (await lstatWhenPresent(candidate))?.isDirectory() ?? false;
|
|
23221
23577
|
}
|
|
23222
23578
|
async function absentPaths(candidates) {
|
|
@@ -23231,21 +23587,94 @@ async function removeLeftoverPlaceholders(placeholderPaths) {
|
|
|
23231
23587
|
}
|
|
23232
23588
|
async function createMissingMountPoints(mountPoints) {
|
|
23233
23589
|
for (const mountPoint of mountPoints) {
|
|
23234
|
-
if (!await isRealDirectory(path.dirname(mountPoint))) continue;
|
|
23590
|
+
if (!await isRealDirectory$1(path.dirname(mountPoint))) continue;
|
|
23235
23591
|
await mkdir(mountPoint).catch((error) => {
|
|
23236
23592
|
if (errorCode(error) !== "EEXIST") throw error;
|
|
23237
23593
|
});
|
|
23238
23594
|
}
|
|
23239
23595
|
}
|
|
23240
|
-
async function
|
|
23241
|
-
|
|
23596
|
+
async function isNewerThanTarget(linkPath, targetPath) {
|
|
23597
|
+
const targetStats = await stat(targetPath).catch(() => null);
|
|
23598
|
+
if (targetStats === null) return true;
|
|
23599
|
+
return (await lstat(linkPath)).mtimeMs > targetStats.mtimeMs;
|
|
23600
|
+
}
|
|
23601
|
+
async function keepRefreshedLogins(logins, launchId, { onlyWhenNewer }) {
|
|
23602
|
+
for (const { linkPath, targetPath } of logins) {
|
|
23242
23603
|
if (!await isRegularFile(linkPath)) continue;
|
|
23604
|
+
if (onlyWhenNewer && !await isNewerThanTarget(linkPath, targetPath)) continue;
|
|
23243
23605
|
const stagedPath = `${targetPath}.${launchId}`;
|
|
23244
23606
|
await copyFile(linkPath, stagedPath);
|
|
23245
23607
|
await chmod(stagedPath, LOGIN_FILE_MODE);
|
|
23246
23608
|
await rename(stagedPath, targetPath);
|
|
23247
23609
|
}
|
|
23248
23610
|
}
|
|
23611
|
+
async function removeLaunch(folders, owner, launchId, { onlyNewerLogins }) {
|
|
23612
|
+
await rm(folders.policyPath, { force: true });
|
|
23613
|
+
await removeLeftoverPlaceholders(owner.placeholderPaths);
|
|
23614
|
+
await keepRefreshedLogins(owner.logins.filter((login) => isPathInside(folders.launchHomeDir, login.linkPath, { allowEqual: false })), launchId, { onlyWhenNewer: onlyNewerLogins });
|
|
23615
|
+
await rm(folders.launchHomeDir, {
|
|
23616
|
+
recursive: true,
|
|
23617
|
+
force: true
|
|
23618
|
+
});
|
|
23619
|
+
if (folders.ownedTempDir !== null) await rm(folders.ownedTempDir, {
|
|
23620
|
+
recursive: true,
|
|
23621
|
+
force: true
|
|
23622
|
+
});
|
|
23623
|
+
await rm(folders.ownerPath, { force: true });
|
|
23624
|
+
}
|
|
23625
|
+
function isProcessAlive$1(pid) {
|
|
23626
|
+
try {
|
|
23627
|
+
process.kill(pid, 0);
|
|
23628
|
+
return true;
|
|
23629
|
+
} catch (error) {
|
|
23630
|
+
return errorCode(error) === "EPERM";
|
|
23631
|
+
}
|
|
23632
|
+
}
|
|
23633
|
+
function readOwnPidNamespace() {
|
|
23634
|
+
return readlink(OWN_PID_NAMESPACE_LINK).catch(() => null);
|
|
23635
|
+
}
|
|
23636
|
+
async function readOwnerFile(ownerPath) {
|
|
23637
|
+
const outcome = await loadJsonStateFile({
|
|
23638
|
+
filePath: ownerPath,
|
|
23639
|
+
quarantine: false,
|
|
23640
|
+
parse: (raw) => GuardrailsLaunchOwner.safeParse(raw).data ?? null
|
|
23641
|
+
});
|
|
23642
|
+
return outcome.status === "loaded" ? outcome.value : null;
|
|
23643
|
+
}
|
|
23644
|
+
async function sweepOrphanedGuardrailsLaunches(log = console) {
|
|
23645
|
+
try {
|
|
23646
|
+
const policyDir = path.join(glimmervoidHomeDir$1(), POLICY_FOLDER);
|
|
23647
|
+
const entryNames = await readdir(policyDir).catch((error) => {
|
|
23648
|
+
if (isMissingFileError(error)) return [];
|
|
23649
|
+
throw error;
|
|
23650
|
+
});
|
|
23651
|
+
const glimmervoidHome = await realpath(glimmervoidHomeDir$1());
|
|
23652
|
+
const sweeperPidNamespace = await readOwnPidNamespace();
|
|
23653
|
+
let removedLaunchCount = 0;
|
|
23654
|
+
for (const entryName of entryNames) {
|
|
23655
|
+
if (!entryName.endsWith(OWNER_FILE_SUFFIX)) continue;
|
|
23656
|
+
const launchId = entryName.slice(0, -11);
|
|
23657
|
+
if (!LAUNCH_ID_SHAPE.test(launchId)) continue;
|
|
23658
|
+
const ownerPath = path.join(policyDir, entryName);
|
|
23659
|
+
const owner = await readOwnerFile(ownerPath);
|
|
23660
|
+
if (owner === null || owner.ownerHost !== os.hostname() || owner.ownerPidNamespace !== sweeperPidNamespace || isProcessAlive$1(owner.ownerPid)) continue;
|
|
23661
|
+
try {
|
|
23662
|
+
await removeLaunch({
|
|
23663
|
+
policyPath: path.join(policyDir, `${launchId}.json`),
|
|
23664
|
+
ownerPath,
|
|
23665
|
+
launchHomeDir: path.join(glimmervoidHome, LAUNCH_HOME_FOLDER, launchId),
|
|
23666
|
+
ownedTempDir: owner.ownsTempDir ? path.join(glimmervoidHome, TEMP_FOLDER, launchId) : null
|
|
23667
|
+
}, owner, launchId, { onlyNewerLogins: true });
|
|
23668
|
+
removedLaunchCount += 1;
|
|
23669
|
+
} catch (error) {
|
|
23670
|
+
log.warn(`[guardrails] could not remove the leftover launch ${launchId}: ${errorMessage(error)}`);
|
|
23671
|
+
}
|
|
23672
|
+
}
|
|
23673
|
+
if (removedLaunchCount > 0) log.log(`[guardrails] removed ${removedLaunchCount} launch folders left by sessions that ended without a clean shutdown`);
|
|
23674
|
+
} catch (error) {
|
|
23675
|
+
log.warn(`[guardrails] could not sweep leftover launch folders: ${errorMessage(error)}`);
|
|
23676
|
+
}
|
|
23677
|
+
}
|
|
23249
23678
|
function launcherCommand(plan, policyPath) {
|
|
23250
23679
|
if (plan.launcher === "open-network") return {
|
|
23251
23680
|
file: process.execPath,
|
|
@@ -23290,10 +23719,11 @@ async function prepareGuardrailsLaunch({ role, launchId, tempDir, enterDir }) {
|
|
|
23290
23719
|
recursive: true,
|
|
23291
23720
|
mode: 448
|
|
23292
23721
|
});
|
|
23293
|
-
const [homeDir, glimmervoidHome, realRole] = await Promise.all([
|
|
23722
|
+
const [homeDir, glimmervoidHome, realRole, ownerPidNamespace] = await Promise.all([
|
|
23294
23723
|
realpath(os.homedir()),
|
|
23295
23724
|
realpath(glimmervoidHomeDir$1()),
|
|
23296
|
-
withRealRoots(role)
|
|
23725
|
+
withRealRoots(role),
|
|
23726
|
+
readOwnPidNamespace()
|
|
23297
23727
|
]);
|
|
23298
23728
|
const ownsTempDir = tempDir === void 0;
|
|
23299
23729
|
const launchTempDir = tempDir === void 0 ? path.join(glimmervoidHome, TEMP_FOLDER, launchId) : path.resolve(tempDir);
|
|
@@ -23342,8 +23772,25 @@ async function prepareGuardrailsLaunch({ role, launchId, tempDir, enterDir }) {
|
|
|
23342
23772
|
if (startDir !== void 0) await mkdir(startDir, { recursive: true });
|
|
23343
23773
|
await Promise.all(plan.launchHomeLinks.map((link) => symlink(link.targetPath, link.linkPath)));
|
|
23344
23774
|
await writeJsonAtomic(policyPath, plan.policy);
|
|
23775
|
+
const folders = {
|
|
23776
|
+
policyPath,
|
|
23777
|
+
ownerPath: path.join(policyDir, `${launchId}${OWNER_FILE_SUFFIX}`),
|
|
23778
|
+
launchHomeDir,
|
|
23779
|
+
ownedTempDir: ownsTempDir ? launchTempDir : null
|
|
23780
|
+
};
|
|
23781
|
+
const owner = {
|
|
23782
|
+
ownerPid: process.pid,
|
|
23783
|
+
ownerHost: os.hostname(),
|
|
23784
|
+
ownerPidNamespace,
|
|
23785
|
+
ownsTempDir,
|
|
23786
|
+
logins: plan.launchHomeLinks.filter((link) => link.isLogin).map(({ linkPath, targetPath }) => ({
|
|
23787
|
+
linkPath,
|
|
23788
|
+
targetPath
|
|
23789
|
+
})),
|
|
23790
|
+
placeholderPaths: []
|
|
23791
|
+
};
|
|
23792
|
+
await writeJsonAtomic(folders.ownerPath, owner);
|
|
23345
23793
|
const envPrefix = guardedEnvPrefix(plan);
|
|
23346
|
-
let placeholderPathsAbsentAtWrap = [];
|
|
23347
23794
|
return {
|
|
23348
23795
|
ok: true,
|
|
23349
23796
|
launch: {
|
|
@@ -23354,7 +23801,8 @@ async function prepareGuardrailsLaunch({ role, launchId, tempDir, enterDir }) {
|
|
|
23354
23801
|
...rebuilt.plan.policy,
|
|
23355
23802
|
allowPty: hasTerminal && rebuilt.plan.launcher === "open-network"
|
|
23356
23803
|
});
|
|
23357
|
-
|
|
23804
|
+
owner.placeholderPaths = await absentPaths(guardrailsPlaceholderPaths(realRole));
|
|
23805
|
+
await writeJsonAtomic(folders.ownerPath, owner);
|
|
23358
23806
|
await createMissingMountPoints(lockedClaudeMountPoints(realRole));
|
|
23359
23807
|
return {
|
|
23360
23808
|
file: launcher.file,
|
|
@@ -23365,21 +23813,12 @@ async function prepareGuardrailsLaunch({ role, launchId, tempDir, enterDir }) {
|
|
|
23365
23813
|
]
|
|
23366
23814
|
};
|
|
23367
23815
|
},
|
|
23368
|
-
env: {
|
|
23816
|
+
env: {
|
|
23817
|
+
...plan.launchEnv,
|
|
23818
|
+
...bridgeSocketLauncherEnv(os.tmpdir(), process.platform)
|
|
23819
|
+
},
|
|
23369
23820
|
...startDir === void 0 ? {} : { cwd: startDir },
|
|
23370
|
-
dispose:
|
|
23371
|
-
await rm(policyPath, { force: true });
|
|
23372
|
-
await removeLeftoverPlaceholders(placeholderPathsAbsentAtWrap);
|
|
23373
|
-
await keepRefreshedLogins(plan.launchHomeLinks, launchId);
|
|
23374
|
-
await rm(launchHomeDir, {
|
|
23375
|
-
recursive: true,
|
|
23376
|
-
force: true
|
|
23377
|
-
});
|
|
23378
|
-
if (ownsTempDir) await rm(launchTempDir, {
|
|
23379
|
-
recursive: true,
|
|
23380
|
-
force: true
|
|
23381
|
-
});
|
|
23382
|
-
}
|
|
23821
|
+
dispose: () => removeLaunch(folders, owner, launchId, { onlyNewerLogins: false })
|
|
23383
23822
|
}
|
|
23384
23823
|
};
|
|
23385
23824
|
} catch (error) {
|
|
@@ -26974,6 +27413,7 @@ function withForcedTapReporter(runCheck) {
|
|
|
26974
27413
|
}
|
|
26975
27414
|
};
|
|
26976
27415
|
}
|
|
27416
|
+
var QUEUED_RUN_LIVENESS_POLL_MS = 1e3;
|
|
26977
27417
|
async function readCheckoutSecretValues(checkoutPath, sharedEntries) {
|
|
26978
27418
|
const sharedEnvFiles = sharedEntries.filter((sharedEntry) => !sharedEntry.includes("..") && path.basename(sharedEntry).startsWith(".env"));
|
|
26979
27419
|
return (await Promise.all(sharedEnvFiles.map(async (sharedEnvFile) => {
|
|
@@ -26985,7 +27425,33 @@ async function readCheckoutSecretValues(checkoutPath, sharedEntries) {
|
|
|
26985
27425
|
}
|
|
26986
27426
|
}))).flatMap(envFileSecretValues);
|
|
26987
27427
|
}
|
|
26988
|
-
async function runChecksInCleanCheckout(
|
|
27428
|
+
async function runChecksInCleanCheckout(options) {
|
|
27429
|
+
const { signal, isLive = () => true, checkRunGate = null, onMemoryWait } = options;
|
|
27430
|
+
if (!checkRunGate) return runChecksInStagedCheckout(options);
|
|
27431
|
+
const isStillWanted = () => isLive() && !signal.aborted;
|
|
27432
|
+
const livenessLost = new AbortController();
|
|
27433
|
+
const livenessPoll = setInterval(() => {
|
|
27434
|
+
if (!isLive()) livenessLost.abort();
|
|
27435
|
+
}, QUEUED_RUN_LIVENESS_POLL_MS);
|
|
27436
|
+
const stopLivenessPoll = () => clearInterval(livenessPoll);
|
|
27437
|
+
try {
|
|
27438
|
+
return await checkRunGate.run(async () => {
|
|
27439
|
+
stopLivenessPoll();
|
|
27440
|
+
if (!isStillWanted()) return null;
|
|
27441
|
+
return runChecksInStagedCheckout(options);
|
|
27442
|
+
}, {
|
|
27443
|
+
isStillWanted,
|
|
27444
|
+
onMemoryWait,
|
|
27445
|
+
signal: AbortSignal.any([signal, livenessLost.signal])
|
|
27446
|
+
});
|
|
27447
|
+
} catch (error) {
|
|
27448
|
+
if (isQueueAdmissionAbandoned(error)) return null;
|
|
27449
|
+
throw error;
|
|
27450
|
+
} finally {
|
|
27451
|
+
stopLivenessPoll();
|
|
27452
|
+
}
|
|
27453
|
+
}
|
|
27454
|
+
async function runChecksInStagedCheckout({ projectPath, sha, commands, writeScopes, sharedEntries, gitWorkspace, baseEnv, secretValues, signal, isLive = () => true, prepareCheckRunner = prepareGuardedCheckRunner, rerunFailedChecks = false, writeFailedCheckOutputFile = async () => null }) {
|
|
26989
27455
|
const checkoutParent = await makeHostScratchDirectory("check-");
|
|
26990
27456
|
const checkoutPath = path.join(checkoutParent, "checkout");
|
|
26991
27457
|
const env = buildFactoryCheckEnv(baseEnv, process.platform);
|
|
@@ -27309,7 +27775,7 @@ async function runFactoryReview(options) {
|
|
|
27309
27775
|
};
|
|
27310
27776
|
return review.value;
|
|
27311
27777
|
}
|
|
27312
|
-
function createFactoryCloseOut({ config, spawnReviewer, gitWorkspace, serializeProject, ensureLedger, runCoherence, commitAndLand, readIntegrationSha, recordFactoryLandedSha, readPaused, readIntentFloorAllowances = async () => [], countTicketBlock = createMemoryCapCounter(), trustVerification, notifyOrchestrator, setException, clearException = () => {}, recordDecision = async () => {}, recordConfirmedFlakes = () => {}, recordScopeNeeds = createMemoryScopeNeedRecorder(), onReviewingChanged = () => {}, baseEnv = process.env, prepareCheckRunner = prepareGuardedCheckRunner }) {
|
|
27778
|
+
function createFactoryCloseOut({ config, spawnReviewer, gitWorkspace, serializeProject, ensureLedger, runCoherence, commitAndLand, readIntegrationSha, recordFactoryLandedSha, readPaused, readIntentFloorAllowances = async () => [], countTicketBlock = createMemoryCapCounter(), trustVerification, notifyOrchestrator, setException, clearException = () => {}, recordDecision = async () => {}, recordConfirmedFlakes = () => {}, recordScopeNeeds = createMemoryScopeNeedRecorder(), onReviewingChanged = () => {}, baseEnv = process.env, prepareCheckRunner = prepareGuardedCheckRunner, checkRunGate = null, onCheckMemoryWait = () => {} }) {
|
|
27313
27779
|
const reviewing = /* @__PURE__ */ new Set();
|
|
27314
27780
|
const attempts = /* @__PURE__ */ new Map();
|
|
27315
27781
|
const failingFilesOutsideScopesByWork = /* @__PURE__ */ new Map();
|
|
@@ -27498,7 +27964,9 @@ function createFactoryCloseOut({ config, spawnReviewer, gitWorkspace, serializeP
|
|
|
27498
27964
|
isLive: () => isLive(worker),
|
|
27499
27965
|
prepareCheckRunner,
|
|
27500
27966
|
rerunFailedChecks,
|
|
27501
|
-
writeFailedCheckOutputFile: (outputText) => writeFailedCheckOutputFile(worker, outputText)
|
|
27967
|
+
writeFailedCheckOutputFile: (outputText) => writeFailedCheckOutputFile(worker, outputText),
|
|
27968
|
+
checkRunGate,
|
|
27969
|
+
onMemoryWait: (isWaiting) => onCheckMemoryWait(worker.projectId, isWaiting)
|
|
27502
27970
|
});
|
|
27503
27971
|
}
|
|
27504
27972
|
async function listInheritedCheckCommands(worker, baseSha, headRun) {
|
|
@@ -28762,7 +29230,7 @@ function createFactoryDispatch({ config, sessions, makeSession, wireSessionEvent
|
|
|
28762
29230
|
allowCommands: [
|
|
28763
29231
|
"coherence context",
|
|
28764
29232
|
"rm",
|
|
28765
|
-
...checks,
|
|
29233
|
+
...workerChecksOf(checks),
|
|
28766
29234
|
...profile.focusedTestCommand ? [profile.focusedTestCommand] : []
|
|
28767
29235
|
],
|
|
28768
29236
|
extraDeny: FACTORY_WORKER_DENY
|
|
@@ -29520,10 +29988,11 @@ function createFactoryPoller({ shouldHoldCheckout = () => false, beforeTick = ()
|
|
|
29520
29988
|
};
|
|
29521
29989
|
try {
|
|
29522
29990
|
paused = await readPaused(project.id);
|
|
29523
|
-
if (!await hasCoherenceConfigAt(project.path, headSha)) return null;
|
|
29524
|
-
const confirmedFlakesByIntent = await readIntentConfirmedFlakes(project.id);
|
|
29525
29991
|
const previous = processedProjects.get(project.id);
|
|
29526
|
-
|
|
29992
|
+
const isUnchangedTip = previous?.path === project.path && previous.state.headSha === headSha;
|
|
29993
|
+
if (!isUnchangedTip && !await hasCoherenceConfigAt(project.path, headSha)) return null;
|
|
29994
|
+
const confirmedFlakesByIntent = await readIntentConfirmedFlakes(project.id);
|
|
29995
|
+
if (previous?.path === project.path && (isUnchangedTip || shouldHoldCheckout(project.id))) return {
|
|
29527
29996
|
...previous.state,
|
|
29528
29997
|
paused,
|
|
29529
29998
|
projectName: project.name,
|
|
@@ -29957,92 +30426,12 @@ function createFactoryVerifier({ config, spawnVerifier, serializeProject, ensure
|
|
|
29957
30426
|
};
|
|
29958
30427
|
}
|
|
29959
30428
|
//#endregion
|
|
29960
|
-
//#region server/factory/core/factory-setup-core.ts
|
|
29961
|
-
var PACKAGE_MANAGERS = [
|
|
29962
|
-
{
|
|
29963
|
-
lockfiles: ["npm-shrinkwrap.json", "package-lock.json"],
|
|
29964
|
-
prefix: "npm run",
|
|
29965
|
-
test: "npm test"
|
|
29966
|
-
},
|
|
29967
|
-
{
|
|
29968
|
-
lockfiles: ["pnpm-lock.yaml"],
|
|
29969
|
-
prefix: "pnpm run",
|
|
29970
|
-
test: "pnpm run test"
|
|
29971
|
-
},
|
|
29972
|
-
{
|
|
29973
|
-
lockfiles: ["yarn.lock"],
|
|
29974
|
-
prefix: "yarn",
|
|
29975
|
-
test: "yarn test"
|
|
29976
|
-
},
|
|
29977
|
-
{
|
|
29978
|
-
lockfiles: ["bun.lockb", "bun.lock"],
|
|
29979
|
-
prefix: "bun run",
|
|
29980
|
-
test: "bun run test"
|
|
29981
|
-
}
|
|
29982
|
-
];
|
|
29983
|
-
var FOCUSED_RUNNERS = [
|
|
29984
|
-
{
|
|
29985
|
-
dependency: "vitest",
|
|
29986
|
-
command: "npx vitest run"
|
|
29987
|
-
},
|
|
29988
|
-
{
|
|
29989
|
-
dependency: "jest",
|
|
29990
|
-
command: "npx jest"
|
|
29991
|
-
},
|
|
29992
|
-
{
|
|
29993
|
-
dependency: "mocha",
|
|
29994
|
-
command: "npx mocha"
|
|
29995
|
-
}
|
|
29996
|
-
];
|
|
29997
|
-
function detectFactorySetup({ scripts, lockfiles, dependencies, globalChecks }) {
|
|
29998
|
-
const packageManager = PACKAGE_MANAGERS.find((manager) => manager.lockfiles.some((lockfile) => lockfiles.includes(lockfile))) ?? PACKAGE_MANAGERS[0];
|
|
29999
|
-
const hasScript = (name) => typeof scripts?.[name] === "string" && String(scripts[name]).trim().length > 0;
|
|
30000
|
-
return {
|
|
30001
|
-
checks: globalChecks === void 0 ? [
|
|
30002
|
-
"typecheck",
|
|
30003
|
-
"lint",
|
|
30004
|
-
"test"
|
|
30005
|
-
].filter(hasScript).map((name) => name === "test" ? packageManager.test : `${packageManager.prefix} ${name}`) : [...globalChecks],
|
|
30006
|
-
focusedTestCommandCandidates: [...hasScript("test:files") ? [`${packageManager.prefix} test:files`] : [], ...FOCUSED_RUNNERS.filter((runner) => dependencies.includes(runner.dependency)).map((runner) => runner.command)],
|
|
30007
|
-
widenableScopes: [...DEFAULT_WIDENABLE_SCOPES]
|
|
30008
|
-
};
|
|
30009
|
-
}
|
|
30010
|
-
var TEST_RESULT_LINE = /^[ \t]*(?:not )?ok \d+\b|^# tests \d+\b/m;
|
|
30011
|
-
var SHELL_NOT_FOUND_LINE = /^(?:\/[^\s:]*\/)?(?:sh|bash|dash|ash|zsh)(?:: (?:line )?\d+)?: [^\n]*?: (?:command )?not found/m;
|
|
30012
|
-
var LAUNCH_FAILURE_LINE = /(?:^|\n)(?:Error: )?(?:spawn[^\n]*\b(?:ENOENT|EACCES|EPERM|failed|error)\b|(?:npm (?:error|ERR!) )?missing script:|(?:error )?(?:script|command)[^\n]*(?:not found|does not exist|not recognized)|Check command contains shell syntax or is empty)/im;
|
|
30013
|
-
function classifyFactorySetupCheck(check) {
|
|
30014
|
-
const hasLaunchFailure = !check.pass && !TEST_RESULT_LINE.test(check.output) && (SHELL_NOT_FOUND_LINE.test(check.output) || LAUNCH_FAILURE_LINE.test(check.output));
|
|
30015
|
-
return {
|
|
30016
|
-
...check,
|
|
30017
|
-
status: hasLaunchFailure ? "could-not-run" : "ran"
|
|
30018
|
-
};
|
|
30019
|
-
}
|
|
30020
|
-
function decideFactorySetupProof(proposal, fullChecks, focusedChecks) {
|
|
30021
|
-
const checks = [...new Set(proposal.checks)].filter((command) => fullChecks.some((check) => check.command === command && check.status === "ran"));
|
|
30022
|
-
if (checks.length === 0) {
|
|
30023
|
-
const failures = fullChecks.map((check) => `${check.command}: ${check.output}`).join("\n");
|
|
30024
|
-
return {
|
|
30025
|
-
ok: false,
|
|
30026
|
-
reason: `Factory setup could not run any full checks.${failures ? `\n${failures}` : " No check commands were detected."}`
|
|
30027
|
-
};
|
|
30028
|
-
}
|
|
30029
|
-
const focusedTestCommand = proposal.focusedTestCommandCandidates.find((command) => focusedChecks.some((check) => check.command === command && check.pass));
|
|
30030
|
-
return {
|
|
30031
|
-
ok: true,
|
|
30032
|
-
profile: {
|
|
30033
|
-
checks,
|
|
30034
|
-
widenableScopes: [...proposal.widenableScopes],
|
|
30035
|
-
...focusedTestCommand ? { focusedTestCommand } : {}
|
|
30036
|
-
}
|
|
30037
|
-
};
|
|
30038
|
-
}
|
|
30039
|
-
//#endregion
|
|
30040
30429
|
//#region server/factory/factory-setup.ts
|
|
30041
30430
|
var setupAgentProfileSchema = ProjectConfig.shape.factoryProfile.unwrap().pick({
|
|
30042
30431
|
checks: true,
|
|
30043
30432
|
focusedTestCommand: true
|
|
30044
30433
|
});
|
|
30045
|
-
function createFactorySetup({ readIntegrationSha, globalChecks, sharedEntries, gitWorkspace, saveProjectFactoryProfile, recordBaseline = async () => {}, spawnSetupAgent, model = () => null, prepareCheckRunner, baseEnv = process.env, secretValues = [] }) {
|
|
30434
|
+
function createFactorySetup({ readIntegrationSha, globalChecks, sharedEntries, gitWorkspace, saveProjectFactoryProfile, recordBaseline = async () => {}, spawnSetupAgent, model = () => null, prepareCheckRunner, checkRunGate = null, baseEnv = process.env, secretValues = [] }) {
|
|
30046
30435
|
const pending = /* @__PURE__ */ new Map();
|
|
30047
30436
|
const controller = new AbortController();
|
|
30048
30437
|
async function setupProject(projectId, projectPath) {
|
|
@@ -30087,7 +30476,8 @@ function createFactorySetup({ readIntegrationSha, globalChecks, sharedEntries, g
|
|
|
30087
30476
|
baseEnv,
|
|
30088
30477
|
secretValues,
|
|
30089
30478
|
signal: controller.signal,
|
|
30090
|
-
prepareCheckRunner
|
|
30479
|
+
prepareCheckRunner,
|
|
30480
|
+
checkRunGate
|
|
30091
30481
|
});
|
|
30092
30482
|
if (!run) throw new Error("Factory setup was interrupted");
|
|
30093
30483
|
for (const check of run.checks) checkProofs.set(check.command, classifyFactorySetupCheck({
|
|
@@ -30205,10 +30595,11 @@ function createFactorySetup({ readIntegrationSha, globalChecks, sharedEntries, g
|
|
|
30205
30595
|
//#region server/factory/factory-wiring.ts
|
|
30206
30596
|
var COHERENCE_CONFIG_PROBE_TIMEOUT_MS = 5e3;
|
|
30207
30597
|
var ORCHESTRATOR_EXCEPTION_PREFIX = "factory-exception";
|
|
30598
|
+
var CHECK_RUN_HELD_FOR_MEMORY_NOTE = "Waiting for memory to run checks";
|
|
30208
30599
|
function createFactoryWiring({ config, broadcast, gitWorkspace = createGitWorkspace(), homeDir = glimmervoidHomeDir$1(), createPoller = createFactoryPoller, log = console, telemetry, orchestratorOptions, trustClaudeFolder, spawnReviewer, prepareCheckRunner, saveProjectFactoryProfile, spawnVerifier = spawnReviewer, readSpentTodayUsd = () => ({
|
|
30209
30600
|
status: "known",
|
|
30210
30601
|
amountUsd: 0
|
|
30211
|
-
}), notify = () => {}, runCoherence: customRunCoherence, ...pollerDeps }) {
|
|
30602
|
+
}), notify = () => {}, runCoherence: customRunCoherence, checkRunGate = null, ...pollerDeps }) {
|
|
30212
30603
|
gitWorkspace = isolateGitWorkspace(gitWorkspace, { disableRepoCommands: true });
|
|
30213
30604
|
const coherenceCliPath = resolvePackageBin("@danilocampos/coherence", "coherence");
|
|
30214
30605
|
const coherenceHookCliPath = resolvePackageBin("@danilocampos/coherence", "coherence-hook");
|
|
@@ -30297,6 +30688,7 @@ function createFactoryWiring({ config, broadcast, gitWorkspace = createGitWorksp
|
|
|
30297
30688
|
sharedEntries: () => config.worktreeShare ?? DEFAULT_CONFIG.worktreeShare,
|
|
30298
30689
|
gitWorkspace,
|
|
30299
30690
|
prepareCheckRunner,
|
|
30691
|
+
checkRunGate,
|
|
30300
30692
|
spawnSetupAgent: spawnReviewer,
|
|
30301
30693
|
model: () => config.factory?.reviewerModel ?? null,
|
|
30302
30694
|
secretValues: factorySecretValues(process.env),
|
|
@@ -31024,6 +31416,17 @@ function createFactoryWiring({ config, broadcast, gitWorkspace = createGitWorksp
|
|
|
31024
31416
|
};
|
|
31025
31417
|
const refresh = refreshAfter("state");
|
|
31026
31418
|
const refusalsByWork = /* @__PURE__ */ new Map();
|
|
31419
|
+
const projectsWithCheckRunHeldForMemory = /* @__PURE__ */ new Set();
|
|
31420
|
+
const noteCheckRunHeldForMemory = (projectId, isWaiting) => {
|
|
31421
|
+
if (isWaiting === projectsWithCheckRunHeldForMemory.has(projectId)) return;
|
|
31422
|
+
if (isWaiting) projectsWithCheckRunHeldForMemory.add(projectId);
|
|
31423
|
+
if (!isWaiting) projectsWithCheckRunHeldForMemory.delete(projectId);
|
|
31424
|
+
refresh();
|
|
31425
|
+
};
|
|
31426
|
+
const withCheckMemoryNote = (projectId, note) => {
|
|
31427
|
+
if (!projectsWithCheckRunHeldForMemory.has(projectId)) return note;
|
|
31428
|
+
return note ? `${note}\n${CHECK_RUN_HELD_FOR_MEMORY_NOTE}` : CHECK_RUN_HELD_FOR_MEMORY_NOTE;
|
|
31429
|
+
};
|
|
31027
31430
|
const notifyOrchestrator = (projectId, event) => {
|
|
31028
31431
|
const refusalKey = JSON.stringify([projectId, event.workId]);
|
|
31029
31432
|
const shouldRecord = event.event !== "refused" || refusalsByWork.get(refusalKey)?.reason !== (event.detail ?? null);
|
|
@@ -31071,6 +31474,8 @@ function createFactoryWiring({ config, broadcast, gitWorkspace = createGitWorksp
|
|
|
31071
31474
|
readIntentFloorAllowances,
|
|
31072
31475
|
countTicketBlock,
|
|
31073
31476
|
prepareCheckRunner,
|
|
31477
|
+
checkRunGate,
|
|
31478
|
+
onCheckMemoryWait: noteCheckRunHeldForMemory,
|
|
31074
31479
|
recordDecision: decisionLog.record,
|
|
31075
31480
|
recordScopeNeeds: async (worker, files) => {
|
|
31076
31481
|
let rememberedNeeds = [];
|
|
@@ -31231,6 +31636,7 @@ function createFactoryWiring({ config, broadcast, gitWorkspace = createGitWorksp
|
|
|
31231
31636
|
const factoryProfile = resolveFactoryProfile(config.factory, config.projects?.find((entry) => entry.id === project.projectId));
|
|
31232
31637
|
return {
|
|
31233
31638
|
...updated,
|
|
31639
|
+
note: withCheckMemoryNote(project.projectId, updated.note),
|
|
31234
31640
|
reviewing,
|
|
31235
31641
|
liveWorkers,
|
|
31236
31642
|
factoryProfile: {
|
|
@@ -32235,13 +32641,12 @@ function branchGcShouldStart(config) {
|
|
|
32235
32641
|
function branchGcCfgKey(config) {
|
|
32236
32642
|
return JSON.stringify(config.branchGc ?? null);
|
|
32237
32643
|
}
|
|
32238
|
-
function createBranchGcWiring({ config, gitWorkspace, liveSessionIds, liveWorktreePaths,
|
|
32644
|
+
function createBranchGcWiring({ config, gitWorkspace, liveSessionIds, liveWorktreePaths, log = console, decisionTrace = (entry) => log.info(`[branch-gc] decision ${JSON.stringify(entry)}`), createPoller = createBranchGcPoller }) {
|
|
32239
32645
|
const runner = createLaneRunner({
|
|
32240
32646
|
tag: "branch-gc",
|
|
32241
32647
|
gate: () => branchGcShouldStart(config),
|
|
32242
32648
|
cfgKey: () => branchGcCfgKey(config),
|
|
32243
32649
|
emptyStatus: () => emptyLaneStatus("branch-gc-status", branchGcShouldStart(config)),
|
|
32244
|
-
broadcast,
|
|
32245
32650
|
createPoller: ({ onTickComplete }) => createPoller({
|
|
32246
32651
|
gitWorkspace,
|
|
32247
32652
|
getConfig: () => config,
|
|
@@ -32262,8 +32667,7 @@ function createBranchGcWiring({ config, gitWorkspace, liveSessionIds, liveWorktr
|
|
|
32262
32667
|
return {
|
|
32263
32668
|
start: runner.startPoller,
|
|
32264
32669
|
stop: runner.stopPoller,
|
|
32265
|
-
restartIfConfigChanged: runner.restartIfConfigChanged
|
|
32266
|
-
getStatus: runner.getStatus
|
|
32670
|
+
restartIfConfigChanged: runner.restartIfConfigChanged
|
|
32267
32671
|
};
|
|
32268
32672
|
}
|
|
32269
32673
|
//#endregion
|
|
@@ -32724,6 +33128,9 @@ function buildContextDigest(store, { scopes = null, budgetChars = DEFAULT_DIGEST
|
|
|
32724
33128
|
if (lines.length === 1) return "";
|
|
32725
33129
|
return lines.join("\n");
|
|
32726
33130
|
}
|
|
33131
|
+
//#endregion
|
|
33132
|
+
//#region server/ingest/core/ingest-fs-core.ts
|
|
33133
|
+
var SOURCE$3 = "fs";
|
|
32727
33134
|
var KIND = "file-change";
|
|
32728
33135
|
var DEFAULT_BATCH_MS = SOURCE_DEFAULTS.fs.batchMs;
|
|
32729
33136
|
var MAX_FILES_PER_BATCH = SOURCE_DEFAULTS.fs.digestQuota;
|
|
@@ -32773,15 +33180,13 @@ var DAEMON_CONFIG_SIBLINGS = Object.freeze([
|
|
|
32773
33180
|
"usage-lanes.json",
|
|
32774
33181
|
"usage-warehouse.json",
|
|
32775
33182
|
"usage-budget-state.json",
|
|
32776
|
-
"pr-review-state.json",
|
|
32777
33183
|
"team-review-state.json",
|
|
32778
33184
|
"pairings.json",
|
|
32779
33185
|
"pairings-seen.json",
|
|
32780
33186
|
"litellm-pricing.json",
|
|
32781
33187
|
"update-check.json",
|
|
32782
33188
|
"recordings",
|
|
32783
|
-
"uploads"
|
|
32784
|
-
"memory"
|
|
33189
|
+
"uploads"
|
|
32785
33190
|
]);
|
|
32786
33191
|
var ACTIVE_SESSION_STATES = Object.freeze([
|
|
32787
33192
|
"INITIALIZING",
|
|
@@ -32964,7 +33369,7 @@ function batchSize(batch) {
|
|
|
32964
33369
|
}
|
|
32965
33370
|
function fileChangeEvent({ root, relPath, change, now }) {
|
|
32966
33371
|
return {
|
|
32967
|
-
source:
|
|
33372
|
+
source: SOURCE$3,
|
|
32968
33373
|
kind: KIND,
|
|
32969
33374
|
ts: now,
|
|
32970
33375
|
scope: {
|
|
@@ -33019,7 +33424,7 @@ function decideFsEvents(batch, { root = null, now = 0 } = {}) {
|
|
|
33019
33424
|
}
|
|
33020
33425
|
const { summary, counts } = burstSummary(batch);
|
|
33021
33426
|
return [{
|
|
33022
|
-
source:
|
|
33427
|
+
source: SOURCE$3,
|
|
33023
33428
|
kind: KIND,
|
|
33024
33429
|
ts: now,
|
|
33025
33430
|
scope: {
|
|
@@ -33072,6 +33477,7 @@ function drainBatchLog(state) {
|
|
|
33072
33477
|
//#endregion
|
|
33073
33478
|
//#region server/ingest/core/ingest-tail-core.ts
|
|
33074
33479
|
var MAX_CATCH_UP_BYTES = 262144;
|
|
33480
|
+
var DEFAULT_MAX_TRACKED$1 = 256;
|
|
33075
33481
|
var LISTING_SETTLE_MS = 2e3;
|
|
33076
33482
|
function fileIdentity(stat) {
|
|
33077
33483
|
return `${Math.floor(coercedNumberOr(stat?.ino, 0))}:${Math.floor(coercedNumberOr(stat?.birthtimeMs, 0))}`;
|
|
@@ -33181,13 +33587,18 @@ function applyRead(state, { text = "", end = 0, stat = null, head = null, reset
|
|
|
33181
33587
|
if (stat) state.identity = fileIdentity(stat);
|
|
33182
33588
|
return split.lines;
|
|
33183
33589
|
}
|
|
33590
|
+
function isTailCurrent(state, stat) {
|
|
33591
|
+
if (!stat || fileIdentity(stat) !== state.identity) return false;
|
|
33592
|
+
if (coercedNumberOr(stat.size, -1) !== state.size || state.offset < state.size) return false;
|
|
33593
|
+
return coercedNumberOr(stat.mtimeMs, -1) === state.mtimeMs;
|
|
33594
|
+
}
|
|
33184
33595
|
function isActiveMtime(mtimeMs, { now, withinMs }) {
|
|
33185
33596
|
return coercedNumberOr(mtimeMs, 0) >= coercedNumberOr(now, 0) - Math.max(0, coercedNumberOr(withinMs, 0));
|
|
33186
33597
|
}
|
|
33187
33598
|
function canTrustCachedListing({ mtimeMs, listedAtMs } = {}) {
|
|
33188
33599
|
return coercedNumberOr(listedAtMs, 0) - coercedNumberOr(mtimeMs, 0) >= LISTING_SETTLE_MS;
|
|
33189
33600
|
}
|
|
33190
|
-
function pickStaleByMtime(entriesByKey, { maxTracked =
|
|
33601
|
+
function pickStaleByMtime(entriesByKey, { maxTracked = DEFAULT_MAX_TRACKED$1 } = {}) {
|
|
33191
33602
|
const entries = [...entriesByKey.entries()];
|
|
33192
33603
|
const bound = Math.max(1, Math.floor(maxTracked));
|
|
33193
33604
|
if (entries.length <= bound) return [];
|
|
@@ -34397,6 +34808,9 @@ function createFsIngest({ publish, sourceConfig = {}, configPath = null, logger
|
|
|
34397
34808
|
}
|
|
34398
34809
|
};
|
|
34399
34810
|
}
|
|
34811
|
+
//#endregion
|
|
34812
|
+
//#region server/ingest/core/ingest-git-core.ts
|
|
34813
|
+
var SOURCE$1 = "git";
|
|
34400
34814
|
var DEFAULT_DEBOUNCE_MS = SOURCE_DEFAULTS.git.debounceMs;
|
|
34401
34815
|
var DEFAULT_POLL_MS$1 = SOURCE_DEFAULTS.git.pollMs;
|
|
34402
34816
|
var CLEAN_SIGNATURE = "clean";
|
|
@@ -34581,7 +34995,7 @@ function subjectSuffix(commit) {
|
|
|
34581
34995
|
function commitEvent({ status, commit, root = null, now = 0 }) {
|
|
34582
34996
|
const sha = shortSha$1(commit?.sha || status.oid) || null;
|
|
34583
34997
|
return {
|
|
34584
|
-
source:
|
|
34998
|
+
source: SOURCE$1,
|
|
34585
34999
|
kind: "commit",
|
|
34586
35000
|
ts: now,
|
|
34587
35001
|
scope: {
|
|
@@ -34601,7 +35015,7 @@ function branchChangeEvent({ status, commit, root = null, now = 0 }) {
|
|
|
34601
35015
|
const sha = shortSha$1(commit?.sha || status.oid) || null;
|
|
34602
35016
|
const at = sha ? ` at ${sha}` : "";
|
|
34603
35017
|
return {
|
|
34604
|
-
source:
|
|
35018
|
+
source: SOURCE$1,
|
|
34605
35019
|
kind: "branch-change",
|
|
34606
35020
|
ts: now,
|
|
34607
35021
|
scope: {
|
|
@@ -34624,7 +35038,7 @@ function statusChangeEvent({ status, root = null, now = 0 }) {
|
|
|
34624
35038
|
if (counts.untracked) parts.push(`${counts.untracked} untracked`);
|
|
34625
35039
|
if (counts.conflicted) parts.push(`${counts.conflicted} conflicted`);
|
|
34626
35040
|
return {
|
|
34627
|
-
source:
|
|
35041
|
+
source: SOURCE$1,
|
|
34628
35042
|
kind: "status-change",
|
|
34629
35043
|
ts: now,
|
|
34630
35044
|
scope: {
|
|
@@ -35081,10 +35495,11 @@ function createGitIngest({ publish, sourceConfig = {}, reposProvider = null, log
|
|
|
35081
35495
|
//#region server/ingest/ingest-shell-history.ts
|
|
35082
35496
|
var DEFAULT_POLL_MS = 2e3;
|
|
35083
35497
|
var DEFAULT_DISCOVER_MS = 3e4;
|
|
35498
|
+
var DEFAULT_MAX_TRACKED = 8;
|
|
35084
35499
|
var DEFAULT_MAX_COMMANDS_PER_DRAIN = 50;
|
|
35085
35500
|
var WATCH_POLL_DEBOUNCE_MS = 100;
|
|
35086
35501
|
var WATCH_SWEEP_DEBOUNCE_MS = 500;
|
|
35087
|
-
function createShellHistoryIngest({ publish, sourceConfig = {}, logger = console, env = process.env, homeDir = os.homedir(), platform = process.platform, fsPromises = fs.promises, watchFn = fs.watch, nowFn = Date.now, setIntervalFn = DEFAULT_TIMER_FNS.setIntervalFn, clearIntervalFn = DEFAULT_TIMER_FNS.clearIntervalFn, setTimeoutFn = DEFAULT_TIMER_FNS.setTimeoutFn, clearTimeoutFn = DEFAULT_TIMER_FNS.clearTimeoutFn, discoverIntervalMs = DEFAULT_DISCOVER_MS, maxTrackedFiles =
|
|
35502
|
+
function createShellHistoryIngest({ publish, sourceConfig = {}, logger = console, env = process.env, homeDir = os.homedir(), platform = process.platform, fsPromises = fs.promises, watchFn = fs.watch, nowFn = Date.now, setIntervalFn = DEFAULT_TIMER_FNS.setIntervalFn, clearIntervalFn = DEFAULT_TIMER_FNS.clearIntervalFn, setTimeoutFn = DEFAULT_TIMER_FNS.setTimeoutFn, clearTimeoutFn = DEFAULT_TIMER_FNS.clearTimeoutFn, discoverIntervalMs = DEFAULT_DISCOVER_MS, maxTrackedFiles = DEFAULT_MAX_TRACKED, maxCommandsPerDrain = DEFAULT_MAX_COMMANDS_PER_DRAIN, maxCatchUpBytes = MAX_CATCH_UP_BYTES } = {}) {
|
|
35088
35503
|
if (typeof publish !== "function") throw new Error("createShellHistoryIngest requires publish");
|
|
35089
35504
|
const publishEvent = publish;
|
|
35090
35505
|
const pollMs = positiveIntOr(sourceConfig.pollMs, DEFAULT_POLL_MS);
|
|
@@ -36576,7 +36991,7 @@ function createTaskTitleRefiner({ getConfig, spawnLane, nowFn = () => Date.now()
|
|
|
36576
36991
|
const request = {
|
|
36577
36992
|
session,
|
|
36578
36993
|
revision: session.taskTitlePromptRevision,
|
|
36579
|
-
currentTitle: session.
|
|
36994
|
+
currentTitle: session.refinedTaskTitle,
|
|
36580
36995
|
recentPrompts: session.recentTaskPrompts
|
|
36581
36996
|
};
|
|
36582
36997
|
queuedBySession.set(session, request);
|
|
@@ -38850,24 +39265,15 @@ function resolvePosthogReportPath(issueId, reportDir = DEFAULT_POSTHOG_REPORT_DI
|
|
|
38850
39265
|
ok: false,
|
|
38851
39266
|
reason: "Invalid issue id"
|
|
38852
39267
|
};
|
|
38853
|
-
const
|
|
38854
|
-
|
|
38855
|
-
const markdownReportPath = confinePath(resolvedReportDir, `${rawIssueId}.md`);
|
|
38856
|
-
if (!htmlReportPath || !markdownReportPath) return {
|
|
39268
|
+
const htmlReportPath = confinePath(path.resolve(reportDir), `${rawIssueId}.html`);
|
|
39269
|
+
if (!htmlReportPath) return {
|
|
38857
39270
|
ok: false,
|
|
38858
39271
|
reason: "Invalid report path"
|
|
38859
39272
|
};
|
|
38860
39273
|
return {
|
|
38861
39274
|
ok: true,
|
|
38862
39275
|
issueId: rawIssueId,
|
|
38863
|
-
|
|
38864
|
-
candidates: [{
|
|
38865
|
-
format: "html",
|
|
38866
|
-
reportPath: htmlReportPath
|
|
38867
|
-
}, {
|
|
38868
|
-
format: "markdown",
|
|
38869
|
-
reportPath: markdownReportPath
|
|
38870
|
-
}]
|
|
39276
|
+
reportPath: htmlReportPath
|
|
38871
39277
|
};
|
|
38872
39278
|
}
|
|
38873
39279
|
async function readPosthogReport(issueId, { reportDir = DEFAULT_POSTHOG_REPORT_DIR } = {}) {
|
|
@@ -38878,17 +39284,22 @@ async function readPosthogReport(issueId, { reportDir = DEFAULT_POSTHOG_REPORT_D
|
|
|
38878
39284
|
issueId: typeof issueId === "string" ? issueId : null,
|
|
38879
39285
|
error: resolved.reason
|
|
38880
39286
|
};
|
|
38881
|
-
|
|
38882
|
-
const content = await fs.promises.readFile(
|
|
39287
|
+
try {
|
|
39288
|
+
const content = await fs.promises.readFile(resolved.reportPath, "utf8");
|
|
38883
39289
|
return {
|
|
38884
39290
|
ok: true,
|
|
38885
39291
|
found: true,
|
|
38886
39292
|
issueId: resolved.issueId,
|
|
38887
|
-
format:
|
|
39293
|
+
format: "html",
|
|
38888
39294
|
content
|
|
38889
39295
|
};
|
|
38890
39296
|
} catch (err) {
|
|
38891
|
-
if (isMissingFileError(err, { includeNotDir: false }))
|
|
39297
|
+
if (isMissingFileError(err, { includeNotDir: false })) return {
|
|
39298
|
+
ok: true,
|
|
39299
|
+
found: false,
|
|
39300
|
+
issueId: resolved.issueId,
|
|
39301
|
+
message: "Report not found"
|
|
39302
|
+
};
|
|
38892
39303
|
return {
|
|
38893
39304
|
ok: false,
|
|
38894
39305
|
found: false,
|
|
@@ -38896,12 +39307,6 @@ async function readPosthogReport(issueId, { reportDir = DEFAULT_POSTHOG_REPORT_D
|
|
|
38896
39307
|
error: "Could not read report"
|
|
38897
39308
|
};
|
|
38898
39309
|
}
|
|
38899
|
-
return {
|
|
38900
|
-
ok: true,
|
|
38901
|
-
found: false,
|
|
38902
|
-
issueId: resolved.issueId,
|
|
38903
|
-
message: "Report not found"
|
|
38904
|
-
};
|
|
38905
39310
|
}
|
|
38906
39311
|
//#endregion
|
|
38907
39312
|
//#region server/telegram-transport.ts
|
|
@@ -39747,6 +40152,69 @@ function createRadarWiring({ config, investigationSessions, closeSessionDataClie
|
|
|
39747
40152
|
};
|
|
39748
40153
|
}
|
|
39749
40154
|
//#endregion
|
|
40155
|
+
//#region server/memory-probe.ts
|
|
40156
|
+
var CGROUP_ROOT = "/sys/fs/cgroup";
|
|
40157
|
+
var MACOS_FREE_MEMORY_REASON = "macOS reports only never-used pages as free, so headroom is not measured there";
|
|
40158
|
+
function isMissingFile(error) {
|
|
40159
|
+
return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
|
|
40160
|
+
}
|
|
40161
|
+
async function readTextIfPresent(filePath) {
|
|
40162
|
+
try {
|
|
40163
|
+
return await readFile(filePath, "utf8");
|
|
40164
|
+
} catch (error) {
|
|
40165
|
+
if (isMissingFile(error)) return null;
|
|
40166
|
+
throw error;
|
|
40167
|
+
}
|
|
40168
|
+
}
|
|
40169
|
+
async function readCgroupLevel(cgroupPath) {
|
|
40170
|
+
const cgroupDirectory = path.posix.join(CGROUP_ROOT, cgroupPath);
|
|
40171
|
+
const [memoryHighText, memoryMaxText, memoryCurrentText] = await Promise.all([
|
|
40172
|
+
"memory.high",
|
|
40173
|
+
"memory.max",
|
|
40174
|
+
"memory.current"
|
|
40175
|
+
].map((fileName) => readTextIfPresent(path.posix.join(cgroupDirectory, fileName))));
|
|
40176
|
+
return {
|
|
40177
|
+
limitBytes: cgroupLimitBytes(memoryHighText ?? null, memoryMaxText ?? null),
|
|
40178
|
+
currentBytes: parseCgroupBytes(memoryCurrentText ?? null)
|
|
40179
|
+
};
|
|
40180
|
+
}
|
|
40181
|
+
async function readLinuxHeadroom() {
|
|
40182
|
+
const [procSelfCgroupText, meminfoText] = await Promise.all([readTextIfPresent("/proc/self/cgroup"), readTextIfPresent("/proc/meminfo")]);
|
|
40183
|
+
const cgroupPath = procSelfCgroupText === null ? null : parseCgroupV2Path(procSelfCgroupText);
|
|
40184
|
+
const cgroupLevels = cgroupPath === null ? [] : await Promise.all(cgroupAncestorPaths(cgroupPath).map(readCgroupLevel));
|
|
40185
|
+
const hostAvailableBytes = meminfoText === null ? null : parseMemAvailableBytes(meminfoText);
|
|
40186
|
+
const headroomBytes = headroomFromReadings({
|
|
40187
|
+
cgroupLevels,
|
|
40188
|
+
hostAvailableBytes
|
|
40189
|
+
});
|
|
40190
|
+
if (headroomBytes === null) return {
|
|
40191
|
+
ok: false,
|
|
40192
|
+
reason: "neither cgroup v2 nor /proc/meminfo reported memory"
|
|
40193
|
+
};
|
|
40194
|
+
return {
|
|
40195
|
+
ok: true,
|
|
40196
|
+
headroomBytes
|
|
40197
|
+
};
|
|
40198
|
+
}
|
|
40199
|
+
async function readMemoryHeadroom() {
|
|
40200
|
+
if (process.platform === "darwin") return {
|
|
40201
|
+
ok: false,
|
|
40202
|
+
reason: MACOS_FREE_MEMORY_REASON
|
|
40203
|
+
};
|
|
40204
|
+
if (process.platform !== "linux") return {
|
|
40205
|
+
ok: true,
|
|
40206
|
+
headroomBytes: os.freemem()
|
|
40207
|
+
};
|
|
40208
|
+
try {
|
|
40209
|
+
return await readLinuxHeadroom();
|
|
40210
|
+
} catch (error) {
|
|
40211
|
+
return {
|
|
40212
|
+
ok: false,
|
|
40213
|
+
reason: errorMessage(error)
|
|
40214
|
+
};
|
|
40215
|
+
}
|
|
40216
|
+
}
|
|
40217
|
+
//#endregion
|
|
39750
40218
|
//#region shared/my-pr-merge.ts
|
|
39751
40219
|
var FAILING_CHECK_STATES$1 = /* @__PURE__ */ new Set(["FAILURE", "ERROR"]);
|
|
39752
40220
|
function myPrMergeBlocker(pr) {
|
|
@@ -44151,6 +44619,7 @@ function createUsageScanner(deps = {}) {
|
|
|
44151
44619
|
let lastIoFailures = 0;
|
|
44152
44620
|
let activePass = null;
|
|
44153
44621
|
let pendingForce = false;
|
|
44622
|
+
let pendingScopeDirs = null;
|
|
44154
44623
|
let isReportDirty = true;
|
|
44155
44624
|
let resolutionError = null;
|
|
44156
44625
|
const cachedRollupsByDays = /* @__PURE__ */ new Map();
|
|
@@ -44702,7 +45171,7 @@ function createUsageScanner(deps = {}) {
|
|
|
44702
45171
|
resolutionError
|
|
44703
45172
|
};
|
|
44704
45173
|
}
|
|
44705
|
-
async function runPassInternal({ force }) {
|
|
45174
|
+
async function runPassInternal({ force, scopeDirs = null }) {
|
|
44706
45175
|
const startedAt = nowFn();
|
|
44707
45176
|
let parsedLineCount = 0;
|
|
44708
45177
|
let newEntryCount = 0;
|
|
@@ -44727,7 +45196,11 @@ function createUsageScanner(deps = {}) {
|
|
|
44727
45196
|
claudeDirs = resolved.dirs;
|
|
44728
45197
|
resolutionError = resolved.error;
|
|
44729
45198
|
ioFailures += resolved.ioFailures;
|
|
44730
|
-
const
|
|
45199
|
+
const scopedRoots = force || !isHistoryCaughtUp ? null : scopedClaudeRoots(scopeDirs, claudeDirs);
|
|
45200
|
+
const vendorRoots = scopedRoots ? {
|
|
45201
|
+
roots: [],
|
|
45202
|
+
ioFailures: 0
|
|
45203
|
+
} : await resolveVendorRootsAsync({
|
|
44731
45204
|
fsPromises,
|
|
44732
45205
|
env,
|
|
44733
45206
|
homeDir,
|
|
@@ -44735,16 +45208,16 @@ function createUsageScanner(deps = {}) {
|
|
|
44735
45208
|
laneLog
|
|
44736
45209
|
});
|
|
44737
45210
|
ioFailures += vendorRoots.ioFailures;
|
|
44738
|
-
const roots = [...claudeDirs.map((dir) => ({
|
|
45211
|
+
const roots = scopedRoots ?? [...claudeDirs.map((dir) => ({
|
|
44739
45212
|
vendor: "claude",
|
|
44740
45213
|
dir,
|
|
44741
45214
|
kind: "active"
|
|
44742
45215
|
})), ...vendorRoots.roots];
|
|
44743
|
-
dirs = roots.map((root) => root.dir);
|
|
45216
|
+
if (!scopedRoots) dirs = roots.map((root) => root.dir);
|
|
44744
45217
|
const walked = await walkSourceFiles(roots, fsPromises, laneLog);
|
|
44745
45218
|
const files = walked.files;
|
|
44746
45219
|
ioFailures += walked.ioFailures;
|
|
44747
|
-
lastFileCount = files.length;
|
|
45220
|
+
if (!scopedRoots) lastFileCount = files.length;
|
|
44748
45221
|
for (const file of files) {
|
|
44749
45222
|
const remainingBudget = byteBudget - bytesReadThisPass;
|
|
44750
45223
|
if (remainingBudget <= 0) {
|
|
@@ -44816,25 +45289,36 @@ function createUsageScanner(deps = {}) {
|
|
|
44816
45289
|
generationRollup: Array.from(passGenerationRollup.values())
|
|
44817
45290
|
};
|
|
44818
45291
|
}
|
|
44819
|
-
async function runPassChain({ force }) {
|
|
45292
|
+
async function runPassChain({ force, scopeDirs }) {
|
|
44820
45293
|
try {
|
|
44821
|
-
let result = await runPassInternal({
|
|
44822
|
-
|
|
45294
|
+
let result = await runPassInternal({
|
|
45295
|
+
force,
|
|
45296
|
+
scopeDirs
|
|
45297
|
+
});
|
|
45298
|
+
while (pendingForce || pendingScopeDirs) {
|
|
45299
|
+
const queued = {
|
|
45300
|
+
force: pendingForce,
|
|
45301
|
+
scopeDirs: pendingForce ? null : pendingScopeDirs
|
|
45302
|
+
};
|
|
44823
45303
|
pendingForce = false;
|
|
44824
|
-
|
|
45304
|
+
pendingScopeDirs = null;
|
|
45305
|
+
result = await runPassInternal(queued);
|
|
44825
45306
|
}
|
|
44826
45307
|
return result;
|
|
44827
45308
|
} finally {
|
|
44828
45309
|
activePass = null;
|
|
44829
45310
|
}
|
|
44830
45311
|
}
|
|
44831
|
-
function runPass({ force = false } = {}) {
|
|
45312
|
+
function runPass({ force = false, scopeDirs = null } = {}) {
|
|
44832
45313
|
if (activePass) {
|
|
44833
|
-
if (
|
|
44834
|
-
|
|
45314
|
+
if (force) pendingForce = true;
|
|
45315
|
+
if (!force && scopeDirs) pendingScopeDirs = [...pendingScopeDirs ?? [], ...scopeDirs];
|
|
44835
45316
|
return activePass;
|
|
44836
45317
|
}
|
|
44837
|
-
activePass = runPassChain({
|
|
45318
|
+
activePass = runPassChain({
|
|
45319
|
+
force,
|
|
45320
|
+
scopeDirs
|
|
45321
|
+
});
|
|
44838
45322
|
return activePass;
|
|
44839
45323
|
}
|
|
44840
45324
|
const api = {
|
|
@@ -45045,12 +45529,15 @@ function createUsageWiring({ config, sessions = /* @__PURE__ */ new Map(), broad
|
|
|
45045
45529
|
})().catch((error) => laneLog.warn("start failed", { error: errorMessage(error) }));
|
|
45046
45530
|
return startPromise;
|
|
45047
45531
|
}
|
|
45048
|
-
async function runPassAndPush({ force }) {
|
|
45532
|
+
async function runPassAndPush({ force, scopeDirs = null }) {
|
|
45049
45533
|
if (stopped || !scanner) return null;
|
|
45050
45534
|
passInFlight = true;
|
|
45051
45535
|
let result = null;
|
|
45052
45536
|
try {
|
|
45053
|
-
result = await scanner.runPass({
|
|
45537
|
+
result = await scanner.runPass({
|
|
45538
|
+
force,
|
|
45539
|
+
scopeDirs
|
|
45540
|
+
});
|
|
45054
45541
|
} catch (error) {
|
|
45055
45542
|
laneLog.warn("scan pass failed", { error: errorMessage(error) });
|
|
45056
45543
|
} finally {
|
|
@@ -45107,22 +45594,40 @@ function createUsageWiring({ config, sessions = /* @__PURE__ */ new Map(), broad
|
|
|
45107
45594
|
}, delayMs);
|
|
45108
45595
|
unrefTimer(continueTimer);
|
|
45109
45596
|
}
|
|
45597
|
+
let nudgedTranscriptDirs = /* @__PURE__ */ new Set();
|
|
45598
|
+
function takeNudgedTranscriptDirs() {
|
|
45599
|
+
const scopeDirs = nudgedTranscriptDirs === null ? null : [...nudgedTranscriptDirs];
|
|
45600
|
+
nudgedTranscriptDirs = /* @__PURE__ */ new Set();
|
|
45601
|
+
return scopeDirs;
|
|
45602
|
+
}
|
|
45603
|
+
function noteNudgedTranscript(transcriptPath) {
|
|
45604
|
+
if (nudgedTranscriptDirs === null) return;
|
|
45605
|
+
if (!transcriptPath) {
|
|
45606
|
+
nudgedTranscriptDirs = null;
|
|
45607
|
+
return;
|
|
45608
|
+
}
|
|
45609
|
+
nudgedTranscriptDirs.add(path.dirname(transcriptPath));
|
|
45610
|
+
}
|
|
45110
45611
|
const nudge = createCoalescedTimer({
|
|
45111
45612
|
mode: "trailing",
|
|
45112
45613
|
delayMs: NUDGE_DEBOUNCE_MS,
|
|
45113
45614
|
run: () => {
|
|
45114
45615
|
if (stopped || !scanner) return;
|
|
45115
45616
|
if (passInFlight) {
|
|
45116
|
-
|
|
45617
|
+
nudge.schedule();
|
|
45117
45618
|
return;
|
|
45118
45619
|
}
|
|
45119
|
-
runPassAndPush({
|
|
45620
|
+
runPassAndPush({
|
|
45621
|
+
force: false,
|
|
45622
|
+
scopeDirs: takeNudgedTranscriptDirs()
|
|
45623
|
+
});
|
|
45120
45624
|
},
|
|
45121
45625
|
setTimeoutFn,
|
|
45122
45626
|
clearTimeoutFn
|
|
45123
45627
|
});
|
|
45124
|
-
function nudgeSession() {
|
|
45628
|
+
function nudgeSession(transcriptPath = null) {
|
|
45125
45629
|
if (stopped || !scanner) return;
|
|
45630
|
+
noteNudgedTranscript(transcriptPath);
|
|
45126
45631
|
nudge.schedule();
|
|
45127
45632
|
}
|
|
45128
45633
|
function getSessionsMessage() {
|
|
@@ -46264,7 +46769,17 @@ function createTraceWiring({ configPath = null, logger = console, debug = false,
|
|
|
46264
46769
|
appendedRecordCount: recordCount
|
|
46265
46770
|
};
|
|
46266
46771
|
}
|
|
46772
|
+
async function isTranscriptUnchanged(binding) {
|
|
46773
|
+
if (!binding.hasOpenedTranscript || binding.bindingBeforeFirstOpen) return false;
|
|
46774
|
+
if (pendingRecordsBySessionId.has(binding.glimmervoidSessionId)) return false;
|
|
46775
|
+
try {
|
|
46776
|
+
return isTailCurrent(binding.tailState, await fs.promises.stat(binding.transcriptPath));
|
|
46777
|
+
} catch {
|
|
46778
|
+
return false;
|
|
46779
|
+
}
|
|
46780
|
+
}
|
|
46267
46781
|
async function drainBinding(binding) {
|
|
46782
|
+
if (await isTranscriptUnchanged(binding)) return;
|
|
46268
46783
|
const opened = await openContainedFile(binding.transcriptPath, projectsRoot());
|
|
46269
46784
|
if (!opened.ok) {
|
|
46270
46785
|
if (binding.bindingBeforeFirstOpen) await drainBinding(binding.bindingBeforeFirstOpen);
|
|
@@ -48155,7 +48670,16 @@ function createVisionsWiring({ debounceMs = 300, setTimeoutFn = DEFAULT_TIMER_FN
|
|
|
48155
48670
|
//#region server/backend/backend-lanes.ts
|
|
48156
48671
|
function createBackendLanes(dependencies) {
|
|
48157
48672
|
const { config, makeSession, wireSessionEvents, configStore, sessions, agentSessions, guardrailsRefusal, reviewSessions, investigationSessions, closeSessionDataClients, hookRouter, getHookPort, broadcastControl, broadcastLocalControl, controlWss, options, notificationManager, telemetry, logger } = dependencies;
|
|
48158
|
-
const
|
|
48673
|
+
const memoryAdmission = createMemoryAdmission({
|
|
48674
|
+
readHeadroom: () => readMemoryHeadroom(),
|
|
48675
|
+
reserveBytesFor: (kind) => memoryReserveBytes(configStore.config, kind),
|
|
48676
|
+
warn: (message) => logger.warn(message)
|
|
48677
|
+
});
|
|
48678
|
+
const spawnGate = createSpawnGate({ memoryAdmission });
|
|
48679
|
+
const checkRunGate = createSpawnGate({
|
|
48680
|
+
memoryAdmission,
|
|
48681
|
+
kind: "checkRun"
|
|
48682
|
+
});
|
|
48159
48683
|
const gitWorkspace = createGitWorkspace({ rerere: config.worktreeRerere !== false });
|
|
48160
48684
|
const gitWorkspaceSync = createGitWorkspaceSync();
|
|
48161
48685
|
const laneLedger = createLaneLedger({
|
|
@@ -48186,7 +48710,6 @@ function createBackendLanes(dependencies) {
|
|
|
48186
48710
|
const branchGc = createBranchGcWiring({
|
|
48187
48711
|
config,
|
|
48188
48712
|
gitWorkspace,
|
|
48189
|
-
broadcast: broadcastControl,
|
|
48190
48713
|
liveSessionIds: () => new Set(allLiveSessions().map((session) => session.id)),
|
|
48191
48714
|
liveWorktreePaths: async () => new Set(await Promise.all(allLiveSessions().flatMap((session) => [session.worktreeDir, session.path]).filter((sessionDirectory) => Boolean(sessionDirectory)).map((sessionDirectory) => comparableDirectoryPath(sessionDirectory)))),
|
|
48192
48715
|
...options.branchGcWiringOptions || {}
|
|
@@ -48203,6 +48726,7 @@ function createBackendLanes(dependencies) {
|
|
|
48203
48726
|
broadcast: broadcastControl,
|
|
48204
48727
|
log: logger,
|
|
48205
48728
|
telemetry,
|
|
48729
|
+
checkRunGate,
|
|
48206
48730
|
saveProjectFactoryProfile: async (projectPath, profile) => {
|
|
48207
48731
|
if (!await configStore.saveAsync((freshConfig) => {
|
|
48208
48732
|
const project = freshConfig.projects?.find((entry) => entry.path === projectPath);
|
|
@@ -48654,6 +49178,7 @@ function createBackendLanes(dependencies) {
|
|
|
48654
49178
|
restartServiceLanes,
|
|
48655
49179
|
reviewSessions,
|
|
48656
49180
|
spawnGate,
|
|
49181
|
+
memoryAdmission,
|
|
48657
49182
|
teamReview,
|
|
48658
49183
|
myPrs,
|
|
48659
49184
|
issues,
|
|
@@ -48676,7 +49201,6 @@ function createBackendHealth(dependencies) {
|
|
|
48676
49201
|
const memory = process.memoryUsage();
|
|
48677
49202
|
const sessionStats = [];
|
|
48678
49203
|
let alivePtyCount = 0;
|
|
48679
|
-
let sleepingCount = 0;
|
|
48680
49204
|
let totalDataListeners = 0;
|
|
48681
49205
|
let totalOutputBufferBytes = 0;
|
|
48682
49206
|
let listenerMismatch = false;
|
|
@@ -48690,7 +49214,6 @@ function createBackendHealth(dependencies) {
|
|
|
48690
49214
|
};
|
|
48691
49215
|
sessionStats.push(stats);
|
|
48692
49216
|
if (stats.hasPty) alivePtyCount += 1;
|
|
48693
|
-
if (stats.sleeping) sleepingCount += 1;
|
|
48694
49217
|
totalDataListeners += stats.dataListenerCount;
|
|
48695
49218
|
totalOutputBufferBytes += stats.outputBufferBytes;
|
|
48696
49219
|
if (!session.ephemeral) {
|
|
@@ -48721,11 +49244,11 @@ function createBackendHealth(dependencies) {
|
|
|
48721
49244
|
sessions: {
|
|
48722
49245
|
total: dependencies.sessions.size,
|
|
48723
49246
|
alivePty: alivePtyCount,
|
|
48724
|
-
sleeping: sleepingCount,
|
|
48725
49247
|
totalDataListeners,
|
|
48726
49248
|
totalOutputBufferBytes,
|
|
48727
49249
|
list: sessionStats
|
|
48728
49250
|
},
|
|
49251
|
+
memoryGate: dependencies.getMemoryGateStatus(),
|
|
48729
49252
|
websockets: {
|
|
48730
49253
|
control: dependencies.controlWss.clients.size,
|
|
48731
49254
|
data: dependencies.dataWss.clients.size,
|
|
@@ -50893,7 +51416,7 @@ function computeCrossRepoLinks(repos) {
|
|
|
50893
51416
|
}
|
|
50894
51417
|
//#endregion
|
|
50895
51418
|
//#region server/change-map/change-map-wiring.ts
|
|
50896
|
-
var GIT_TIMEOUT_MS = 15e3;
|
|
51419
|
+
var GIT_TIMEOUT_MS$1 = 15e3;
|
|
50897
51420
|
var GIT_MAX_BUFFER_BYTES = 67108864;
|
|
50898
51421
|
var FILE_READ_BATCH = 64;
|
|
50899
51422
|
var MAX_CACHED_FILES = 5e4;
|
|
@@ -50905,14 +51428,14 @@ var MAX_MEMOIZED_REPO_FACTS = 64;
|
|
|
50905
51428
|
async function runGitAsync(args, cwd) {
|
|
50906
51429
|
const result = await runGit(args, {
|
|
50907
51430
|
cwd,
|
|
50908
|
-
timeoutMs: GIT_TIMEOUT_MS,
|
|
51431
|
+
timeoutMs: GIT_TIMEOUT_MS$1,
|
|
50909
51432
|
maxBuffer: GIT_MAX_BUFFER_BYTES,
|
|
50910
51433
|
trim: false
|
|
50911
51434
|
});
|
|
50912
|
-
if (!result.ok) throw gitCommandFailure(args, result, GIT_TIMEOUT_MS);
|
|
51435
|
+
if (!result.ok) throw gitCommandFailure(args, result, GIT_TIMEOUT_MS$1);
|
|
50913
51436
|
return result.out;
|
|
50914
51437
|
}
|
|
50915
|
-
function yieldToEventLoop() {
|
|
51438
|
+
function yieldToEventLoop$1() {
|
|
50916
51439
|
return new Promise((resolve) => {
|
|
50917
51440
|
setImmediate(resolve);
|
|
50918
51441
|
});
|
|
@@ -50959,7 +51482,7 @@ function createFileFactCache(derive, oversizedValue) {
|
|
|
50959
51482
|
const value = values[index];
|
|
50960
51483
|
if (value !== null) valueByPath.set(repoPath, value);
|
|
50961
51484
|
});
|
|
50962
|
-
await yieldToEventLoop();
|
|
51485
|
+
await yieldToEventLoop$1();
|
|
50963
51486
|
}
|
|
50964
51487
|
return valueByPath;
|
|
50965
51488
|
}
|
|
@@ -51575,7 +52098,7 @@ function parseSinceParam(url) {
|
|
|
51575
52098
|
return Number.isFinite(n) ? n : null;
|
|
51576
52099
|
}
|
|
51577
52100
|
function registerControlHandlers(controlWss, deps) {
|
|
51578
|
-
const { sessions, agentSessions = /* @__PURE__ */ new Map(), config, configStore, broadcastControl, generateProjectId, applyConfigReload, applySettingsReload, requestShutdown, requestRestart, handleClientFocus, buildHealthSnapshot, getUpdateStatus, getUpdateJournal = null,
|
|
52101
|
+
const { sessions, agentSessions = /* @__PURE__ */ new Map(), config, configStore, broadcastControl, generateProjectId, applyConfigReload, applySettingsReload, requestShutdown, requestRestart, handleClientFocus, buildHealthSnapshot, getUpdateStatus, getUpdateJournal = null, getPostTurnLeftoversNotice = null, recheckPostTurnLeftovers = null, checkNow = null, applyUpdate = null, noteRestartRequested = null, getRadarStatus, posthogReportsDir = null, posthogSetIssueStatus = null, radarArchiveInvestigation = null, getTeamReviewStatus = null, getMyPrsStatus = null, getIssuesStatus = null, issues = null, teamReview = null, myPrs = null, getBenchmarkStatus = null, getFactoryState = null, benchmarks = null, factory = null, createGithubClient = createPrGh, issueProjectRepos = null, serverBuild = () => null, getUsageSessions = null, getUsageReport = null, requestUsageReport = null, getPlanLimits = null, controlReplayLog = null, getRtkInstallStatus = () => null, resolveRtkPath = () => getRtkPath(), conversationFs = fs, conversationGit = runGitForConversationHistory, conversationProjectsDir = claudeProjectsDir(process.env, os.homedir()), readTracePage = null, readPlanRevision = null, decidePlanReview = null, sessionDocs = null, changeMapNarrator = null } = deps;
|
|
51579
52102
|
const changeMaps = createChangeMapService({
|
|
51580
52103
|
sessions,
|
|
51581
52104
|
narrator: changeMapNarrator,
|
|
@@ -51848,35 +52371,6 @@ function registerControlHandlers(controlWss, deps) {
|
|
|
51848
52371
|
}
|
|
51849
52372
|
session.unlinkPr(msg.key);
|
|
51850
52373
|
}
|
|
51851
|
-
function handleReorderSessions(msg, ws) {
|
|
51852
|
-
const order = msg.order;
|
|
51853
|
-
if (!Array.isArray(order) || order.length === 0) {
|
|
51854
|
-
sendError(ws, "order must be a non-empty array");
|
|
51855
|
-
return;
|
|
51856
|
-
}
|
|
51857
|
-
if (!order.every((id) => sessions.has(id))) {
|
|
51858
|
-
sendError(ws, "Session list changed during reorder");
|
|
51859
|
-
broadcastControl(buildSnapshot());
|
|
51860
|
-
return;
|
|
51861
|
-
}
|
|
51862
|
-
const entries = new Map(sessions);
|
|
51863
|
-
sessions.clear();
|
|
51864
|
-
for (const id of order) {
|
|
51865
|
-
const existing = entries.get(id);
|
|
51866
|
-
if (existing) sessions.set(id, existing);
|
|
51867
|
-
}
|
|
51868
|
-
for (const [id, sess] of entries) if (!sessions.has(id)) sessions.set(id, sess);
|
|
51869
|
-
configStore.save((cfg) => {
|
|
51870
|
-
const projectMap = new Map(cfg.projects.map((p) => [p.id, p]));
|
|
51871
|
-
cfg.projects = order.map((id) => projectMap.get(id)).filter((project) => project !== void 0);
|
|
51872
|
-
for (const p of projectMap.values()) if (!cfg.projects.some((x) => x.id === p.id)) cfg.projects.push(p);
|
|
51873
|
-
});
|
|
51874
|
-
broadcastControl({
|
|
51875
|
-
type: "sessions-reordered",
|
|
51876
|
-
order
|
|
51877
|
-
});
|
|
51878
|
-
console.log(`[control] Sessions reordered`);
|
|
51879
|
-
}
|
|
51880
52374
|
async function handleListConversations(msg, ws) {
|
|
51881
52375
|
const sess = findSession(msg);
|
|
51882
52376
|
if (!sess) {
|
|
@@ -52828,29 +53322,16 @@ function registerControlHandlers(controlWss, deps) {
|
|
|
52828
53322
|
}
|
|
52829
53323
|
await checkNow();
|
|
52830
53324
|
}
|
|
52831
|
-
async function handleUpdateApply(
|
|
53325
|
+
async function handleUpdateApply(_msg, ws) {
|
|
52832
53326
|
if (!applyUpdate) {
|
|
52833
53327
|
sendError(ws, "[update-apply-unavailable] Update application is unavailable.");
|
|
52834
53328
|
return;
|
|
52835
53329
|
}
|
|
52836
53330
|
const outcome = await applyUpdate();
|
|
52837
|
-
if (
|
|
52838
|
-
|
|
52839
|
-
return;
|
|
52840
|
-
}
|
|
52841
|
-
if (msg.restartWhenStaged !== true) return;
|
|
52842
|
-
const confirmedSessionIds = new Set(msg.confirmedSessionIds ?? []);
|
|
52843
|
-
if ([...sessions.keys(), ...agentSessions.keys()].some((liveSessionId) => !confirmedSessionIds.has(liveSessionId))) {
|
|
52844
|
-
sendError(ws, "[update-restart-skipped] The update is staged, but sessions started since you confirmed. Restart when ready.");
|
|
52845
|
-
return;
|
|
52846
|
-
}
|
|
52847
|
-
handleRestart(msg, ws);
|
|
53331
|
+
if (outcome.ok) return;
|
|
53332
|
+
sendError(ws, `[${outcome.reason || "update-refused"}] ${outcome.message}`);
|
|
52848
53333
|
}
|
|
52849
|
-
function handleRestart(_msg
|
|
52850
|
-
if (isStaging?.()) {
|
|
52851
|
-
sendError(ws, "[update-staging] Wait for the update staging run to finish before restarting.");
|
|
52852
|
-
return;
|
|
52853
|
-
}
|
|
53334
|
+
function handleRestart(_msg) {
|
|
52854
53335
|
noteRestartRequested?.();
|
|
52855
53336
|
console.log("[control] Restart requested via UI");
|
|
52856
53337
|
broadcastControl({ type: "restarting" });
|
|
@@ -52869,7 +53350,6 @@ function registerControlHandlers(controlWss, deps) {
|
|
|
52869
53350
|
"set-session-title": handleSetSessionTitle,
|
|
52870
53351
|
"link-session-pr": handleLinkSessionPr,
|
|
52871
53352
|
"unlink-session-pr": handleUnlinkSessionPr,
|
|
52872
|
-
"reorder-sessions": handleReorderSessions,
|
|
52873
53353
|
"ping": handlePing,
|
|
52874
53354
|
"get-settings": handleGetSettings,
|
|
52875
53355
|
"update-settings": handleUpdateSettings,
|
|
@@ -52914,14 +53394,6 @@ function registerControlHandlers(controlWss, deps) {
|
|
|
52914
53394
|
if (msg.expectedState !== void 0 && session.state !== msg.expectedState) return;
|
|
52915
53395
|
session.dismiss();
|
|
52916
53396
|
},
|
|
52917
|
-
"sleep": (msg) => {
|
|
52918
|
-
const s = findSession(msg);
|
|
52919
|
-
if (s) s.sleep();
|
|
52920
|
-
},
|
|
52921
|
-
"wake": (msg) => {
|
|
52922
|
-
const s = findSession(msg);
|
|
52923
|
-
if (s) s.wake();
|
|
52924
|
-
},
|
|
52925
53397
|
"merge-session": async (msg, ws) => {
|
|
52926
53398
|
const s = findSession(msg);
|
|
52927
53399
|
if (s) reportMergeRefusal(ws, s, await s.mergeWorktree());
|
|
@@ -53239,6 +53711,9 @@ function registerControlHandlers(controlWss, deps) {
|
|
|
53239
53711
|
type: "update-progress",
|
|
53240
53712
|
journal: updateJournal
|
|
53241
53713
|
}));
|
|
53714
|
+
const postTurnLeftoversNotice = getPostTurnLeftoversNotice?.() ?? null;
|
|
53715
|
+
if (postTurnLeftoversNotice) ws.send(JSON.stringify(postTurnLeftoversNotice));
|
|
53716
|
+
recheckPostTurnLeftovers?.();
|
|
53242
53717
|
const radarStatus = typeof getRadarStatus === "function" ? getRadarStatus() : null;
|
|
53243
53718
|
if (radarStatus) ws.send(JSON.stringify(radarStatus));
|
|
53244
53719
|
const teamReviewStatus = typeof getTeamReviewStatus === "function" ? getTeamReviewStatus() : null;
|
|
@@ -53313,9 +53788,10 @@ function createBackendControl(dependencies) {
|
|
|
53313
53788
|
buildHealthSnapshot: dependencies.buildHealthSnapshot,
|
|
53314
53789
|
getUpdateStatus: dependencies.getUpdateStatus,
|
|
53315
53790
|
getUpdateJournal: dependencies.getUpdateJournal,
|
|
53791
|
+
getPostTurnLeftoversNotice: dependencies.getPostTurnLeftoversNotice,
|
|
53792
|
+
recheckPostTurnLeftovers: dependencies.recheckPostTurnLeftovers,
|
|
53316
53793
|
checkNow: dependencies.checkNow,
|
|
53317
53794
|
applyUpdate: dependencies.applyUpdate,
|
|
53318
|
-
isStaging: dependencies.isStaging,
|
|
53319
53795
|
noteRestartRequested: dependencies.noteRestartRequested,
|
|
53320
53796
|
getRadarStatus: () => posthog.getStatus(),
|
|
53321
53797
|
posthogSetIssueStatus: (args) => posthog.setIssueStatus(args),
|
|
@@ -53367,6 +53843,7 @@ var PREVIOUS_DEPENDENCIES_BACKUP_NAME = "prev-node_modules";
|
|
|
53367
53843
|
var QUARANTINED_DIST_NAME = "broken-dist";
|
|
53368
53844
|
var QUARANTINED_DEPENDENCIES_NAME = "broken-node_modules";
|
|
53369
53845
|
var SUPPORTED_UPDATE_REMOTE = "origin";
|
|
53846
|
+
var LOWEST_SCHEDULING_PRIORITY = "19";
|
|
53370
53847
|
var TRANSITIONS_ALLOWED_BY_STATE = Object.freeze({
|
|
53371
53848
|
idle: Object.freeze(["begin-run"]),
|
|
53372
53849
|
running: Object.freeze([
|
|
@@ -53528,6 +54005,21 @@ function planSteps({ lockfileChanged }) {
|
|
|
53528
54005
|
"build"
|
|
53529
54006
|
];
|
|
53530
54007
|
}
|
|
54008
|
+
function lowPriorityCommand({ file, args, platform }) {
|
|
54009
|
+
if (platform === "win32") return {
|
|
54010
|
+
file,
|
|
54011
|
+
args
|
|
54012
|
+
};
|
|
54013
|
+
return {
|
|
54014
|
+
file: "nice",
|
|
54015
|
+
args: [
|
|
54016
|
+
"-n",
|
|
54017
|
+
LOWEST_SCHEDULING_PRIORITY,
|
|
54018
|
+
file,
|
|
54019
|
+
...args
|
|
54020
|
+
]
|
|
54021
|
+
};
|
|
54022
|
+
}
|
|
53531
54023
|
function planHandOffRenames({ root, stagingPath, lockfileChanged }) {
|
|
53532
54024
|
const updatePath = `${root}/.glimmervoid/update`;
|
|
53533
54025
|
const renames = [{
|
|
@@ -54054,6 +54546,7 @@ function createBackendUpdateCheck(dependencies) {
|
|
|
54054
54546
|
let inFlight = null;
|
|
54055
54547
|
let lastChannel = normalizeUpdateChannel(dependencies.config.updateChannel);
|
|
54056
54548
|
let lastSurfacedIdentity = null;
|
|
54549
|
+
let lastDownloadedIdentity = null;
|
|
54057
54550
|
let pendingChannelRefresh = false;
|
|
54058
54551
|
let stopped = false;
|
|
54059
54552
|
const now = dependencies.now || (() => Date.now());
|
|
@@ -54157,6 +54650,26 @@ function createBackendUpdateCheck(dependencies) {
|
|
|
54157
54650
|
const latestRelease = status.latest || shortSha(status.latestSha) || "unknown";
|
|
54158
54651
|
dependencies.logger.log(`[update] A newer glimmervoid is available: ${currentRelease} -> ${latestRelease}. Update: ${status.command}`);
|
|
54159
54652
|
}
|
|
54653
|
+
function downloadInBackground(status) {
|
|
54654
|
+
const downloadUpdate = dependencies.downloadUpdate;
|
|
54655
|
+
if (!downloadUpdate || dependencies.config.checkForUpdates === false) return;
|
|
54656
|
+
if (!status.updateAvailable || status.applyRefusal) return;
|
|
54657
|
+
const identity = status.latestSha || status.latest;
|
|
54658
|
+
if (!identity || identity === lastDownloadedIdentity) return;
|
|
54659
|
+
lastDownloadedIdentity = identity;
|
|
54660
|
+
dependencies.logger.log(`[update] downloading ${status.latest || shortSha(status.latestSha)} in the background`);
|
|
54661
|
+
const allowRetryAtNextCheck = () => {
|
|
54662
|
+
if (lastDownloadedIdentity === identity) lastDownloadedIdentity = null;
|
|
54663
|
+
};
|
|
54664
|
+
downloadUpdate().then((outcome) => {
|
|
54665
|
+
if (outcome.ok) return;
|
|
54666
|
+
allowRetryAtNextCheck();
|
|
54667
|
+
dependencies.logger.log(`[update] background download stopped: ${outcome.message}`);
|
|
54668
|
+
}, (error) => {
|
|
54669
|
+
allowRetryAtNextCheck();
|
|
54670
|
+
dependencies.logger.log(`[update] background download failed: ${String(error)}`);
|
|
54671
|
+
});
|
|
54672
|
+
}
|
|
54160
54673
|
function runCheck(ttlMs) {
|
|
54161
54674
|
if (inFlight) return inFlight;
|
|
54162
54675
|
const channel = normalizeUpdateChannel(dependencies.config.updateChannel);
|
|
@@ -54175,6 +54688,7 @@ function createBackendUpdateCheck(dependencies) {
|
|
|
54175
54688
|
});
|
|
54176
54689
|
const status = recordStatus(updateResult, checkedAt, channel);
|
|
54177
54690
|
surfaceAvailableUpdate(status);
|
|
54691
|
+
downloadInBackground(status);
|
|
54178
54692
|
return status;
|
|
54179
54693
|
}).finally(() => {
|
|
54180
54694
|
inFlight = null;
|
|
@@ -54197,6 +54711,7 @@ function createBackendUpdateCheck(dependencies) {
|
|
|
54197
54711
|
recordedStatus = null;
|
|
54198
54712
|
lastLaneSignature = null;
|
|
54199
54713
|
lastSurfacedIdentity = null;
|
|
54714
|
+
lastDownloadedIdentity = null;
|
|
54200
54715
|
updateAbort?.abort();
|
|
54201
54716
|
return true;
|
|
54202
54717
|
}
|
|
@@ -54950,7 +55465,12 @@ function createUpdateApplyLane(dependencies) {
|
|
|
54950
55465
|
stepId,
|
|
54951
55466
|
now: clock()
|
|
54952
55467
|
}));
|
|
54953
|
-
const
|
|
55468
|
+
const throttled = lowPriorityCommand({
|
|
55469
|
+
file,
|
|
55470
|
+
args,
|
|
55471
|
+
platform: dependencies.platform || process.platform
|
|
55472
|
+
});
|
|
55473
|
+
const outcome = await command(throttled.file, throttled.args, cwd, timeout);
|
|
54954
55474
|
journal = appendStepOutput(journal, stepId, outcome.output);
|
|
54955
55475
|
if (!outcome.ok) throw new Error(outcome.reason || `${stepId} failed`);
|
|
54956
55476
|
await publish(finishStep(journal, {
|
|
@@ -55245,9 +55765,6 @@ function createUpdateApplyLane(dependencies) {
|
|
|
55245
55765
|
activeRun = run;
|
|
55246
55766
|
await run;
|
|
55247
55767
|
}
|
|
55248
|
-
function isStaging() {
|
|
55249
|
-
return inFlight;
|
|
55250
|
-
}
|
|
55251
55768
|
function isRestartRequested() {
|
|
55252
55769
|
return restartRequested;
|
|
55253
55770
|
}
|
|
@@ -55354,13 +55871,158 @@ function createUpdateApplyLane(dependencies) {
|
|
|
55354
55871
|
getJournal,
|
|
55355
55872
|
handOffStagedUpdate,
|
|
55356
55873
|
isRestartRequested,
|
|
55357
|
-
isStaging,
|
|
55358
55874
|
noteRestartRequested,
|
|
55359
55875
|
startAfterListening,
|
|
55360
55876
|
stop,
|
|
55361
55877
|
whenIdle
|
|
55362
55878
|
};
|
|
55363
55879
|
}
|
|
55880
|
+
//#endregion
|
|
55881
|
+
//#region shared/post-turn-leftovers.ts
|
|
55882
|
+
var REPORT_FOLDER_GIT_PATH = ".glimmervoid/post-turn";
|
|
55883
|
+
var REPORT_FILE_NAME = /^[a-zA-Z0-9_.-]+\.json$/;
|
|
55884
|
+
var POWERSHELL_SINGLE_QUOTES = /['\u2018\u2019\u201A\u201B]/g;
|
|
55885
|
+
function isReportGitPath(gitPath) {
|
|
55886
|
+
const folderPrefix = `${REPORT_FOLDER_GIT_PATH}/`;
|
|
55887
|
+
return gitPath.startsWith(folderPrefix) && REPORT_FILE_NAME.test(gitPath.slice(folderPrefix.length));
|
|
55888
|
+
}
|
|
55889
|
+
function untrackedReportFileNames(fileNames, trackedGitPaths) {
|
|
55890
|
+
const trackedPaths = new Set(trackedGitPaths);
|
|
55891
|
+
return fileNames.filter((fileName) => REPORT_FILE_NAME.test(fileName) && !trackedPaths.has(`.glimmervoid/post-turn/${fileName}`));
|
|
55892
|
+
}
|
|
55893
|
+
function quoteShellArgument(argument, platform) {
|
|
55894
|
+
if (platform === "win32") return `'${argument.replace(POWERSHELL_SINGLE_QUOTES, "$&$&")}'`;
|
|
55895
|
+
return `'${argument.replaceAll("'", "'\\''")}'`;
|
|
55896
|
+
}
|
|
55897
|
+
function trackedReportCleanupCommands(repositories, platform) {
|
|
55898
|
+
return repositories.map(({ repositoryPath, trackedReportGitPaths }) => {
|
|
55899
|
+
const quotedReportPaths = trackedReportGitPaths.map((gitPath) => quoteShellArgument(gitPath, platform)).join(" ");
|
|
55900
|
+
return `git -C ${quoteShellArgument(repositoryPath, platform)} rm --cached -q -- ${quotedReportPaths}`;
|
|
55901
|
+
}).join("\n");
|
|
55902
|
+
}
|
|
55903
|
+
function trackedReportNoticeText(repositoryCount) {
|
|
55904
|
+
return `Git tracks an old Glimmervoid post-turn report in ${repositoryCount === 1 ? "1 repository" : `${repositoryCount} repositories`}. Nothing reads it, so run this to stop git tracking it and commit the deletion git then shows. Glimmervoid deletes the file itself on its next start:`;
|
|
55905
|
+
}
|
|
55906
|
+
//#endregion
|
|
55907
|
+
//#region server/post-turn-leftovers.ts
|
|
55908
|
+
var GIT_TIMEOUT_MS = 1e4;
|
|
55909
|
+
var CONTROL_CHARACTER = /[\x00-\x1f\x7f]/;
|
|
55910
|
+
var NOTHING_CLEANED = {
|
|
55911
|
+
removedFileCount: 0,
|
|
55912
|
+
trackedReportGitPaths: []
|
|
55913
|
+
};
|
|
55914
|
+
async function gitOutput(args, cwd) {
|
|
55915
|
+
const result = await runGit(args, {
|
|
55916
|
+
cwd,
|
|
55917
|
+
timeoutMs: GIT_TIMEOUT_MS,
|
|
55918
|
+
trim: false
|
|
55919
|
+
});
|
|
55920
|
+
if (!result.ok) throw result.error ?? new Error(result.err);
|
|
55921
|
+
return result.out;
|
|
55922
|
+
}
|
|
55923
|
+
function yieldToEventLoop() {
|
|
55924
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
55925
|
+
}
|
|
55926
|
+
async function collectWorktreePaths(projects) {
|
|
55927
|
+
const repositoryPaths = new Set(projects.flatMap((project) => [project.path, ...project.repos ?? []]).filter(Boolean));
|
|
55928
|
+
const worktreePaths = /* @__PURE__ */ new Set();
|
|
55929
|
+
for (const repositoryPath of repositoryPaths) {
|
|
55930
|
+
if (worktreePaths.has(repositoryPath)) continue;
|
|
55931
|
+
await yieldToEventLoop();
|
|
55932
|
+
for (const worktreePath of await listRepoWorktreePaths(repositoryPath, gitOutput)) worktreePaths.add(worktreePath);
|
|
55933
|
+
}
|
|
55934
|
+
return [...worktreePaths];
|
|
55935
|
+
}
|
|
55936
|
+
async function readFileNames(folder) {
|
|
55937
|
+
try {
|
|
55938
|
+
return (await fsp.readdir(folder, { withFileTypes: true })).filter((entry) => entry.isFile()).map((entry) => entry.name);
|
|
55939
|
+
} catch (error) {
|
|
55940
|
+
if (isMissingFileError(error)) return null;
|
|
55941
|
+
throw error;
|
|
55942
|
+
}
|
|
55943
|
+
}
|
|
55944
|
+
async function removeFolderIfEmpty(folder) {
|
|
55945
|
+
if ((await fsp.readdir(folder)).length > 0) return;
|
|
55946
|
+
await fsp.rmdir(folder);
|
|
55947
|
+
}
|
|
55948
|
+
async function isRealDirectory(folder) {
|
|
55949
|
+
try {
|
|
55950
|
+
return (await fsp.lstat(folder)).isDirectory();
|
|
55951
|
+
} catch (error) {
|
|
55952
|
+
if (isMissingFileError(error)) return false;
|
|
55953
|
+
throw error;
|
|
55954
|
+
}
|
|
55955
|
+
}
|
|
55956
|
+
async function isReportFolderOwnedByWorktree(worktreePath, reportFolder) {
|
|
55957
|
+
if (!await isRealDirectory(path.dirname(reportFolder)) || !await isRealDirectory(reportFolder)) return false;
|
|
55958
|
+
const realWorktreePath = await fsp.realpath(worktreePath);
|
|
55959
|
+
if (!isPathInside(realWorktreePath, await fsp.realpath(reportFolder), { allowEqual: false })) return false;
|
|
55960
|
+
const reportFolderTopLevel = (await gitOutput(["rev-parse", "--show-toplevel"], reportFolder)).trim();
|
|
55961
|
+
return equalsIgnoringCaseOnWindows(await fsp.realpath(reportFolderTopLevel), realWorktreePath);
|
|
55962
|
+
}
|
|
55963
|
+
async function listTrackedReportGitPaths(worktreePath) {
|
|
55964
|
+
return (await gitOutput([
|
|
55965
|
+
"ls-files",
|
|
55966
|
+
"-z",
|
|
55967
|
+
"--",
|
|
55968
|
+
REPORT_FOLDER_GIT_PATH
|
|
55969
|
+
], worktreePath)).split("\0").filter(isReportGitPath);
|
|
55970
|
+
}
|
|
55971
|
+
async function cleanWorktree(worktreePath) {
|
|
55972
|
+
const reportFolder = path.join(worktreePath, ...REPORT_FOLDER_GIT_PATH.split("/"));
|
|
55973
|
+
if (!await isReportFolderOwnedByWorktree(worktreePath, reportFolder)) return NOTHING_CLEANED;
|
|
55974
|
+
const fileNames = await readFileNames(reportFolder);
|
|
55975
|
+
if (fileNames === null) return NOTHING_CLEANED;
|
|
55976
|
+
const trackedReportGitPaths = await listTrackedReportGitPaths(worktreePath);
|
|
55977
|
+
const deletableFileNames = untrackedReportFileNames(fileNames, trackedReportGitPaths);
|
|
55978
|
+
for (const fileName of deletableFileNames) await fsp.rm(path.join(reportFolder, fileName), { force: true });
|
|
55979
|
+
await removeFolderIfEmpty(reportFolder);
|
|
55980
|
+
await removeFolderIfEmpty(path.dirname(reportFolder));
|
|
55981
|
+
return {
|
|
55982
|
+
removedFileCount: deletableFileNames.length,
|
|
55983
|
+
trackedReportGitPaths
|
|
55984
|
+
};
|
|
55985
|
+
}
|
|
55986
|
+
async function cleanPostTurnLeftovers({ projects, logger = console, platform = process.platform }) {
|
|
55987
|
+
const repositoriesTrackingReports = [];
|
|
55988
|
+
let removedFileCount = 0;
|
|
55989
|
+
for (const worktreePath of await collectWorktreePaths(projects)) {
|
|
55990
|
+
await yieldToEventLoop();
|
|
55991
|
+
if (CONTROL_CHARACTER.test(worktreePath)) {
|
|
55992
|
+
logger.warn(`[post-turn] skipped the old report folder in ${JSON.stringify(worktreePath)} because its path contains a control character`);
|
|
55993
|
+
continue;
|
|
55994
|
+
}
|
|
55995
|
+
try {
|
|
55996
|
+
const cleanup = await cleanWorktree(worktreePath);
|
|
55997
|
+
removedFileCount += cleanup.removedFileCount;
|
|
55998
|
+
if (cleanup.trackedReportGitPaths.length > 0) repositoriesTrackingReports.push({
|
|
55999
|
+
repositoryPath: worktreePath,
|
|
56000
|
+
trackedReportGitPaths: cleanup.trackedReportGitPaths
|
|
56001
|
+
});
|
|
56002
|
+
} catch (error) {
|
|
56003
|
+
logger.warn(`[post-turn] could not clean the old report folder in ${worktreePath}: ${errorMessage(error)}`);
|
|
56004
|
+
}
|
|
56005
|
+
}
|
|
56006
|
+
if (removedFileCount > 0) logger.log(`[post-turn] removed ${removedFileCount} old post-turn report files that earlier versions left in your repositories`);
|
|
56007
|
+
if (repositoriesTrackingReports.length > 0) logger.warn(`[post-turn] ${trackedReportNoticeText(repositoriesTrackingReports.length)}\n${trackedReportCleanupCommands(repositoriesTrackingReports, platform)}`);
|
|
56008
|
+
return repositoriesTrackingReports;
|
|
56009
|
+
}
|
|
56010
|
+
async function recheckRepositoriesTrackingReports(repositories, logger = console) {
|
|
56011
|
+
const repositoriesStillTrackingReports = [];
|
|
56012
|
+
for (const { repositoryPath } of repositories) {
|
|
56013
|
+
await yieldToEventLoop();
|
|
56014
|
+
try {
|
|
56015
|
+
const trackedReportGitPaths = await listTrackedReportGitPaths(repositoryPath);
|
|
56016
|
+
if (trackedReportGitPaths.length > 0) repositoriesStillTrackingReports.push({
|
|
56017
|
+
repositoryPath,
|
|
56018
|
+
trackedReportGitPaths
|
|
56019
|
+
});
|
|
56020
|
+
} catch (error) {
|
|
56021
|
+
logger.warn(`[post-turn] could not re-check the old report folder in ${repositoryPath}: ${errorMessage(error)}`);
|
|
56022
|
+
}
|
|
56023
|
+
}
|
|
56024
|
+
return repositoriesStillTrackingReports;
|
|
56025
|
+
}
|
|
55364
56026
|
var FLUSH_AT_EVENT_COUNT = 20;
|
|
55365
56027
|
var FLUSH_INTERVAL_MS = 6e4;
|
|
55366
56028
|
var SEND_TIMEOUT_MS = 5e3;
|
|
@@ -56086,6 +56748,7 @@ function createBackend(httpServer, options = {}) {
|
|
|
56086
56748
|
getAllSessions: () => allLiveSessions(),
|
|
56087
56749
|
sessionDataClients,
|
|
56088
56750
|
getIngestLane: getCurrentIngestLane,
|
|
56751
|
+
getMemoryGateStatus: () => laneAssembly.memoryAdmission.gateStatus(),
|
|
56089
56752
|
controlWss,
|
|
56090
56753
|
dataWss,
|
|
56091
56754
|
broadcastControl
|
|
@@ -56138,6 +56801,7 @@ function createBackend(httpServer, options = {}) {
|
|
|
56138
56801
|
closeSessionDataClients,
|
|
56139
56802
|
broadcastControl,
|
|
56140
56803
|
spawnGate,
|
|
56804
|
+
memoryAdmission: laneAssembly.memoryAdmission,
|
|
56141
56805
|
recordLane,
|
|
56142
56806
|
sessionDocs,
|
|
56143
56807
|
logger: console
|
|
@@ -56187,6 +56851,7 @@ function createBackend(httpServer, options = {}) {
|
|
|
56187
56851
|
sessionRegistry.initialize();
|
|
56188
56852
|
const reconcileProjectsOnReload = sessionRegistry.applyConfigReload;
|
|
56189
56853
|
rtkInstall.maybeInstall();
|
|
56854
|
+
sweepOrphanedGuardrailsLaunches();
|
|
56190
56855
|
laneAssembly.startRuntimeLanes();
|
|
56191
56856
|
function applyCustomAgents(sourceConfig) {
|
|
56192
56857
|
setCustomAgents(sourceConfig.customAgents ?? []);
|
|
@@ -56207,6 +56872,12 @@ function createBackend(httpServer, options = {}) {
|
|
|
56207
56872
|
visionsSetup.maybeApply();
|
|
56208
56873
|
laneAssembly.restartDynamicLanes();
|
|
56209
56874
|
}
|
|
56875
|
+
function downloadUpdateOnceListening() {
|
|
56876
|
+
if (httpServer.listening) return updateApply.applyUpdate();
|
|
56877
|
+
return new Promise((resolve) => {
|
|
56878
|
+
httpServer.once("listening", resolve);
|
|
56879
|
+
}).then(() => updateApply.applyUpdate());
|
|
56880
|
+
}
|
|
56210
56881
|
const updateCheck = createBackendUpdateCheck({
|
|
56211
56882
|
config,
|
|
56212
56883
|
currentVersion: packageJson.version,
|
|
@@ -56217,7 +56888,8 @@ function createBackend(httpServer, options = {}) {
|
|
|
56217
56888
|
isRestartRequested: () => updateApply.isRestartRequested(),
|
|
56218
56889
|
getControlClientCount: () => controlWss.clients.size,
|
|
56219
56890
|
broadcastControl,
|
|
56220
|
-
logger: console
|
|
56891
|
+
logger: console,
|
|
56892
|
+
...options.downloadUpdatesInBackground ? { downloadUpdate: downloadUpdateOnceListening } : {}
|
|
56221
56893
|
});
|
|
56222
56894
|
const updateApply = createUpdateApplyLane({
|
|
56223
56895
|
gitWorkspace: assembledGitWorkspace,
|
|
@@ -56233,10 +56905,42 @@ function createBackend(httpServer, options = {}) {
|
|
|
56233
56905
|
},
|
|
56234
56906
|
logger: console
|
|
56235
56907
|
});
|
|
56908
|
+
let repositoriesTrackingPostTurnReports = [];
|
|
56909
|
+
let isRecheckingPostTurnReports = false;
|
|
56910
|
+
function postTurnLeftoversMessage() {
|
|
56911
|
+
return {
|
|
56912
|
+
type: "post-turn-leftovers",
|
|
56913
|
+
repositories: repositoriesTrackingPostTurnReports.map(({ repositoryPath }) => repositoryPath),
|
|
56914
|
+
command: trackedReportCleanupCommands(repositoriesTrackingPostTurnReports, process.platform)
|
|
56915
|
+
};
|
|
56916
|
+
}
|
|
56917
|
+
function startPostTurnLeftoverCleanup() {
|
|
56918
|
+
cleanPostTurnLeftovers({ projects: config.projects }).then((repositories) => {
|
|
56919
|
+
repositoriesTrackingPostTurnReports = repositories;
|
|
56920
|
+
if (repositories.length > 0) broadcastControlFromWebSockets(postTurnLeftoversMessage());
|
|
56921
|
+
}).catch((error) => {
|
|
56922
|
+
console.warn(`[post-turn] old report cleanup failed: ${errorMessage(error)}`);
|
|
56923
|
+
});
|
|
56924
|
+
}
|
|
56925
|
+
function recheckPostTurnLeftovers() {
|
|
56926
|
+
if (isRecheckingPostTurnReports || repositoriesTrackingPostTurnReports.length === 0) return;
|
|
56927
|
+
isRecheckingPostTurnReports = true;
|
|
56928
|
+
const previousRepositories = repositoriesTrackingPostTurnReports;
|
|
56929
|
+
recheckRepositoriesTrackingReports(previousRepositories).then((repositories) => {
|
|
56930
|
+
if (JSON.stringify(repositories) === JSON.stringify(previousRepositories)) return;
|
|
56931
|
+
repositoriesTrackingPostTurnReports = repositories;
|
|
56932
|
+
broadcastControlFromWebSockets(postTurnLeftoversMessage());
|
|
56933
|
+
}).catch((error) => {
|
|
56934
|
+
console.warn(`[post-turn] old report re-check failed: ${errorMessage(error)}`);
|
|
56935
|
+
}).finally(() => {
|
|
56936
|
+
isRecheckingPostTurnReports = false;
|
|
56937
|
+
});
|
|
56938
|
+
}
|
|
56236
56939
|
let stopConfigWatch = null;
|
|
56237
56940
|
const shutdown = createBackendShutdown({
|
|
56238
56941
|
configStore,
|
|
56239
56942
|
cancelAutoResume: sessionRegistry.cancelAutoResume,
|
|
56943
|
+
cancelPostTurnLeftoverCleanup: () => httpServer.off("listening", startPostTurnLeftoverCleanup),
|
|
56240
56944
|
healthInterval,
|
|
56241
56945
|
getStopConfigWatch: () => stopConfigWatch,
|
|
56242
56946
|
remoteAuth,
|
|
@@ -56304,9 +57008,10 @@ function createBackend(httpServer, options = {}) {
|
|
|
56304
57008
|
buildHealthSnapshot,
|
|
56305
57009
|
getUpdateStatus: updateCheck.getStatus,
|
|
56306
57010
|
getUpdateJournal: updateApply.getJournal,
|
|
57011
|
+
getPostTurnLeftoversNotice: () => repositoriesTrackingPostTurnReports.length > 0 ? postTurnLeftoversMessage() : null,
|
|
57012
|
+
recheckPostTurnLeftovers,
|
|
56307
57013
|
checkNow: updateCheck.checkNow,
|
|
56308
57014
|
applyUpdate: updateApply.applyUpdate,
|
|
56309
|
-
isStaging: updateApply.isStaging,
|
|
56310
57015
|
noteRestartRequested: () => {
|
|
56311
57016
|
updateApply.noteRestartRequested();
|
|
56312
57017
|
updateCheck.refreshApplyAvailability();
|
|
@@ -56332,6 +57037,7 @@ function createBackend(httpServer, options = {}) {
|
|
|
56332
57037
|
const { handleUpgrade } = webSockets;
|
|
56333
57038
|
httpServer.on("upgrade", handleUpgrade);
|
|
56334
57039
|
updateApply.startAfterListening(httpServer);
|
|
57040
|
+
httpServer.once("listening", startPostTurnLeftoverCleanup);
|
|
56335
57041
|
stopConfigWatch = configStore.watchForChanges((newConfig) => {
|
|
56336
57042
|
applyConfigReload(newConfig);
|
|
56337
57043
|
broadcastControl({
|
|
@@ -56345,6 +57051,7 @@ function createBackend(httpServer, options = {}) {
|
|
|
56345
57051
|
updateCheck.start();
|
|
56346
57052
|
return {
|
|
56347
57053
|
shutdown,
|
|
57054
|
+
handOffStagedUpdate: updateApply.handOffStagedUpdate,
|
|
56348
57055
|
port,
|
|
56349
57056
|
app,
|
|
56350
57057
|
telemetry,
|
|
@@ -56394,7 +57101,10 @@ function isBootRefusal(error) {
|
|
|
56394
57101
|
}
|
|
56395
57102
|
function createBackendOrExit() {
|
|
56396
57103
|
try {
|
|
56397
|
-
return createBackend(server, {
|
|
57104
|
+
return createBackend(server, {
|
|
57105
|
+
staticDir: "auto",
|
|
57106
|
+
downloadUpdatesInBackground: true
|
|
57107
|
+
});
|
|
56398
57108
|
} catch (err) {
|
|
56399
57109
|
if (!isBootRefusal(err)) throw err;
|
|
56400
57110
|
console.error(err.message);
|
|
@@ -56402,7 +57112,7 @@ function createBackendOrExit() {
|
|
|
56402
57112
|
}
|
|
56403
57113
|
}
|
|
56404
57114
|
var backend = createBackendOrExit();
|
|
56405
|
-
var { shutdown, port, app, telemetry, version } = backend;
|
|
57115
|
+
var { shutdown, handOffStagedUpdate, port, app, telemetry, version } = backend;
|
|
56406
57116
|
server.on("request", app);
|
|
56407
57117
|
function writeTerminalTitle(sequence) {
|
|
56408
57118
|
if (!process.stdout.isTTY) return;
|
|
@@ -56483,7 +57193,8 @@ var { requestShutdown } = createLifecycle({
|
|
|
56483
57193
|
httpServer: server,
|
|
56484
57194
|
extraServers: remoteServers,
|
|
56485
57195
|
spawn,
|
|
56486
|
-
exit: exitWithClearedTerminalTitle
|
|
57196
|
+
exit: exitWithClearedTerminalTitle,
|
|
57197
|
+
beforeHandOff: handOffStagedUpdate
|
|
56487
57198
|
});
|
|
56488
57199
|
function handleShutdownSignal(signal) {
|
|
56489
57200
|
console.log(`\n${signal} received - shutting down...`);
|