glimmervoid 0.32.0 → 0.32.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.
Files changed (25) hide show
  1. package/dist/bin/glimmervoid.js +10 -9
  2. package/dist/chunks/{adapters-CoC18rdt.js → adapters-BS_7o8fj.js} +3 -3
  3. package/dist/chunks/{adapters-BU74M4QZ.js → adapters-Bn-dQ02i.js} +1 -1
  4. package/dist/chunks/{agent-api-cli-V_BkYhBI.js → agent-api-cli-BR0GVDn6.js} +11 -2
  5. package/dist/chunks/{agent-api-core-B3YI8SYs.js → agent-api-core-DUiWHSHg.js} +10 -1
  6. package/dist/chunks/{agent-setup-cli-D4956BfN.js → agent-setup-cli-BqYcwfOs.js} +1 -1
  7. package/dist/chunks/{config-CZ4H51D0.js → config-B6BkTOcE.js} +1 -1
  8. package/dist/chunks/config-store-C8Ry8ZFD.js +2 -0
  9. package/dist/chunks/{config-store-BVtdHkDK.js → config-store-aZpWeX92.js} +2 -2
  10. package/dist/chunks/{contracts-DXujp-Zn.js → contracts-DzDg-Z9I.js} +42 -2
  11. package/dist/chunks/{grok-C4lzPFEg.js → grok-7Z5KcgcY.js} +1 -1
  12. package/dist/chunks/{knowledge-graph-cli-Bjy9_kv5.js → knowledge-graph-cli-D5Gf87Y7.js} +1 -1
  13. package/dist/chunks/{main-DSbpYn2Y.js → main-BG6iHwoY.js} +834 -410
  14. package/dist/chunks/{pair-cli-CKn-wUw9.js → pair-cli-DQkrt_-s.js} +2 -2
  15. package/dist/chunks/{remote-config-DP44Ybt9.js → remote-config-Da4C7tMG.js} +1 -1
  16. package/dist/chunks/{session-B8fJb_Or.js → session-Dtkp9o3r.js} +11 -2
  17. package/dist/client/assets/index-BxU3Ww0L.css +1 -0
  18. package/dist/client/assets/index-Co4S7tnh.js +254 -0
  19. package/dist/client/index.html +2 -2
  20. package/dist/server/index.js +1 -1
  21. package/dist/session/sandbox-open-launcher.js +41 -2
  22. package/package.json +1 -1
  23. package/dist/chunks/config-store-Cp62jN1O.js +0 -2
  24. package/dist/client/assets/index-C_XKsDFx.css +0 -1
  25. package/dist/client/assets/index-CoqWjYxP.js +0 -254
@@ -2,7 +2,7 @@ import { i as sanitizeOneLine, n as requireExecutableSpawnHelper, r as firstLine
2
2
  import { a as isMissingFileError, i as errorText$1, r as errorMessage, t as errorCode } from "./text-DoirglWV.js";
3
3
  import { a as isPathInside, c as sanitizeWorktreeName, n as caseFoldedPathKey, o as isSameDirectoryPath, r as comparableDirectoryPath, s as safePathSegment, t as canonicalizePath } from "./paths-CeJl2Vcg.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-BPoxENmK.js";
5
- import { C as MERGEABLE_LIVE_STATES, S as KILLABLE_STATES, T as STATES, _ as TaskTitle, a as AgentDispatchRequest, c as ORCHESTRATOR_AGENT_VERBS, g as TASK_TITLE_CONTROL_CHARACTERS, n as AGENT_URL_ENV, p as RefinedTaskTitle, r as ASK_USER_QUESTION_TOOL_NAME, s as AgentTicketRequest, v as isSamePromptQuestion, w as RESTARTABLE_STATES, x as toolDetailLine } from "./session-B8fJb_Or.js";
5
+ import { D as MERGEABLE_LIVE_STATES, E as KILLABLE_STATES, O as RESTARTABLE_STATES, S as isSamePromptQuestion, T as toolDetailLine, a as AgentDispatchRequest, b as TASK_TITLE_CONTROL_CHARACTERS, c as AgentTicketRequest, h as SESSION_DOC_BODY_CAP_BYTES, k as STATES, l as ORCHESTRATOR_AGENT_VERBS, m as RefinedTaskTitle, n as AGENT_URL_ENV, r as ASK_USER_QUESTION_TOOL_NAME, x as TaskTitle } from "./session-Dtkp9o3r.js";
6
6
  import { n as execFileAsync, o as spawn, r as execFileSync, t as execFile } from "./child-process-safe-DDELk9XC.js";
7
7
  import { n as glimmervoidHomeDir } from "./config-path-core-DFt-TemS.js";
8
8
  import { n as checkSandboxDependencies, t as allowSandboxedSpawn } from "./sandbox-deps-DuaNFq7n.js";
@@ -16,15 +16,15 @@ import { i as SANE_YOLO_FILES } from "./rtk-hook-core-BmEz2aq9.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-vR6Wv6bP.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 { a as decideTicketAction, i as decideSpawnAllowance, l as isOrchestratorAgentVerb, n as REFUSAL_REASON, o as deriveChildSessionName, r as decideAgentRequest, s as describePushLanding, t as PUSH_UNAVAILABLE_REASON, u as parseAgentVerb } from "./agent-api-core-B3YI8SYs.js";
20
- import { s as GLIMMERVOID_SECRET_KEYS } from "./grok-C4lzPFEg.js";
19
+ import { a as decideTicketAction, i as decideSpawnAllowance, l as isOrchestratorAgentVerb, n as REFUSAL_REASON, o as deriveChildSessionName, r as decideAgentRequest, s as describePushLanding, t as PUSH_UNAVAILABLE_REASON, u as parseAgentVerb } from "./agent-api-core-DUiWHSHg.js";
20
+ import { s as GLIMMERVOID_SECRET_KEYS } from "./grok-7Z5KcgcY.js";
21
21
  import { _ as isQueueAdmissionTimeout, a as jsonStateLoadError, c as loadedJsonValue, d as writeJsonAtomicSync, f as writeTextAtomic, g as createSpawnGate, h as createSerialQueue, 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 } from "./json-file-oEH9vt-0.js";
22
- import { $ as TeamReviewCompareFiles, A as MyPrSearchResponse, At as CoherenceWorkInspect, B as PostingPlan, C as MyPrMergeMethod, Ct as KEEP_MERGEABLE_TIMEOUT_MINUTES_RANGE, D as MyPrMergeWhenReadyRequest, E as MyPrMergeStateResponse, F as CommitSha, G as ReviewDraft, H as QueuedReview, I as DECIDING_REVIEW_STATES, J as ReviewThreadId, K as ReviewFinding, L as FindingSeverity, M as MyPrThreadsResponse, N as MyPrsState, Ot as CoherenceTicketCreated, P as MyPrsStatus, R as GithubReviewDecision, Tt as CoherenceOrient, U as ReviewChecksState, V as PrDetail, W as ReviewComment, X as SearchedPr, Y as ReviewVerdict, Z as TeamReviewActionRequest, _ as WorkflowSearchNode, a as ConfigUpdate, at as TeamReviewThreadsRepository, b as MyPrAutoRebase, bt as USAGE_COST_MODES, c as TASK_TITLE_SOURCE_PRIORITY, ct as hasStandingViewerApproval, d as BRANCH_GC_CONTROL_NUMERIC_KEYS, dt as shortSha$1, et as TeamReviewResolveResponse, ft as GH_SEGMENT, g as WorkflowLabelName, h as WorkflowCommentBody, ht as repoParts, it as TeamReviewThreadResult, j as MyPrThreadNode, k as MyPrSearchNode, kt as CoherenceWorkCreated, l as configIssueMessage, lt as FULL_SHA_RE, m as DEFAULT_FACTORY_PROTECTED_PATHS, mt as REPO_SLUG_RE, nt as TeamReviewStatus, o as PersistedTaskTitle, ot as canApproveAfterComment, p as DEFAULT_FACTORY_CHECKS, pt as NODE_ID_RE, q as ReviewResult, r as BrowserConfig, rt as TeamReviewThreadCommentsResponse, s as RUNTIME_CONFIG_SCALAR_KEYS, st as decisionAsIfApprovalRequired, tt as TeamReviewStateEntry, u as BRANCH_GC_CONTROL_BOOLEAN_KEYS, ut as HEX_SHA_RE, v as WorkflowsSettings, w as MyPrMergeRequest, wt as USAGE_INTEGER_RANGES, x as MyPrKeepMergeableRequest, xt as USAGE_VENDOR_KEYS, y as WorkflowsState, yt as USAGE_BUDGET_KEYS, z as GithubReviewState } from "./config-CZ4H51D0.js";
23
- import { $ as sessionIdFromBranch, A as snapshotEvents, B as classifySessionExit, C as createIngestStore, D as resolveIngestConfig, E as publishEvent, F as adapterBucket, G as nodeMajorVersion, H as decideRemoteTelemetryState, I as buildAiGenerationEvents, J as stableConfigKey, K as parsePosthogReleaseId, L as buildBrowserExceptionProperties, M as FIRST_RUN_NOTICE, N as TELEMETRY_BATCH_URL, O as ringStats, P as TELEMETRY_FLAGS_URL, Q as planWorktreeGc, R as buildExceptionProperties, S as buildContextDigest, T as latestSeq, U as decideTelemetryConsent, V as decideIsDevInstall, W as exceptionFingerprint, X as matchedPrefix, Y as DEFAULT_BRANCH_GC_PREFIXES, Z as planBranchGc, _ as INGEST_SPEC, a as ensureProjectIds, b as DEFAULT_DIGEST_BUDGET_CHARS, d as assetForPlatform, et as usablePrefixes, f as decideRtkInstall, g as verifyDigest, h as isRtkBinaryName, i as createConfigStore, j as localDayKey, k as scrubText, m as installTargetPath, n as DEFAULT_CONFIG, o as generateProjectId, p as findEscapingArchiveMember, q as resolveProjectToken, s as glimmervoidHomeDir$1, t as ABORT_CONFIG_SAVE, tt as worktreeIntegrationTips, v as mergeSettingsBlock, w as enabledSourceNames, x as SOURCE_DEFAULTS, y as validateSettingsBlock, z as buildFactoryDecisionEvent } from "./config-store-BVtdHkDK.js";
24
- import { $ as FactoryHarnessVerdict, A as BenchmarkActionRequest, B as PrCheckoutCaseInput, C as IssueKey, D as IssuesStatus, E as IssuesState, F as BenchmarkStatus, G as TelemetryState, H as PendingCrashReport, I as BenchmarkSuite, J as FACTORY_ERROR_MAX_CHARS, K as FACTORY_DECISION_OUTPUT_MAX_BYTES, L as CommitComparison, M as BenchmarkId, N as BenchmarkJudgement, O as ChangeMap, P as BenchmarkRun, Q as FactoryHarnessStateLine, R as MergedPrListing, S as GithubIssueResponse, T as IssuesSearchResponse, U as TELEMETRY_EVENT_SCHEMAS, X as FactoryConsequences, Y as FACTORY_PROTECTED_PATH_FLOOR, Z as FactoryControlRequest, _ as PlanDecision, a as ClientMessage, at as FactoryQueueIntentRequest, b as planTitle, c as UpdateJournal, ct as FactoryWorkerEvent, d as TraceRecord, et as FactoryHogQLResponse, f as PLAN_BODY_CAP_BYTES, g as PLAN_TOOL_NAME, h as PLAN_RESULT_HOOK_EVENT, i as CONTROL_FRAME_MAX_BYTES, it as FactoryProjectState, j as BenchmarkCase, k as changeMapFactId, l as MAX_RAW_LINE_CHARS, lt as isSingleFactoryPathSegment, m as PLAN_HOOK_OUTPUT_MAX_CHARS, n as HookEnvelope, nt as FactoryIssueQueryRow, o as DIFF_ANNOTATION_NOTE_MAX_CHARS, ot as FactoryReviewVerdict, p as PLAN_HOOK_EVENT, q as FactoryDecisionLogLine, r as parseDataClientMessage, rt as FactoryLaneState, s as InstallFlavor, st as FactoryReviewerOutput, t as HTTP_URL_RE, tt as FactoryIssue, u as TraceCheckpoint, v as PlanRevision, w as IssueUpdate, x as GithubIssuePullRequestsRepository, y as parseExitPlanModeHookPayload, z as MinedPrReviewData } from "./contracts-DXujp-Zn.js";
25
- import { a as describeAgentResolvability, c as isKnownAgentId, d as setCustomAgents, f as claudeCode, i as customAgentFingerprint, l as listAgentIds, n as cachedAgentResolvability, o as getAdapter, r as commandFor, s as hookProfileOf, t as DEFAULT_AGENT_ID, u as resolveAdapter } from "./adapters-CoC18rdt.js";
22
+ import { $ as TeamReviewCompareFiles, A as MyPrSearchResponse, At as CoherenceWorkInspect, B as PostingPlan, C as MyPrMergeMethod, Ct as KEEP_MERGEABLE_TIMEOUT_MINUTES_RANGE, D as MyPrMergeWhenReadyRequest, E as MyPrMergeStateResponse, F as CommitSha, G as ReviewDraft, H as QueuedReview, I as DECIDING_REVIEW_STATES, J as ReviewThreadId, K as ReviewFinding, L as FindingSeverity, M as MyPrThreadsResponse, N as MyPrsState, Ot as CoherenceTicketCreated, P as MyPrsStatus, R as GithubReviewDecision, Tt as CoherenceOrient, U as ReviewChecksState, V as PrDetail, W as ReviewComment, X as SearchedPr, Y as ReviewVerdict, Z as TeamReviewActionRequest, _ as WorkflowSearchNode, a as ConfigUpdate, at as TeamReviewThreadsRepository, b as MyPrAutoRebase, bt as USAGE_COST_MODES, c as TASK_TITLE_SOURCE_PRIORITY, ct as hasStandingViewerApproval, d as BRANCH_GC_CONTROL_NUMERIC_KEYS, dt as shortSha$1, et as TeamReviewResolveResponse, ft as GH_SEGMENT, g as WorkflowLabelName, h as WorkflowCommentBody, ht as repoParts, it as TeamReviewThreadResult, j as MyPrThreadNode, k as MyPrSearchNode, kt as CoherenceWorkCreated, l as configIssueMessage, lt as FULL_SHA_RE, m as DEFAULT_FACTORY_PROTECTED_PATHS, mt as REPO_SLUG_RE, nt as TeamReviewStatus, o as PersistedTaskTitle, ot as canApproveAfterComment, p as DEFAULT_FACTORY_CHECKS, pt as NODE_ID_RE, q as ReviewResult, r as BrowserConfig, rt as TeamReviewThreadCommentsResponse, s as RUNTIME_CONFIG_SCALAR_KEYS, st as decisionAsIfApprovalRequired, tt as TeamReviewStateEntry, u as BRANCH_GC_CONTROL_BOOLEAN_KEYS, ut as HEX_SHA_RE, v as WorkflowsSettings, w as MyPrMergeRequest, wt as USAGE_INTEGER_RANGES, x as MyPrKeepMergeableRequest, xt as USAGE_VENDOR_KEYS, y as WorkflowsState, yt as USAGE_BUDGET_KEYS, z as GithubReviewState } from "./config-B6BkTOcE.js";
23
+ import { $ as sessionIdFromBranch, A as snapshotEvents, B as classifySessionExit, C as createIngestStore, D as resolveIngestConfig, E as publishEvent, F as adapterBucket, G as nodeMajorVersion, H as decideRemoteTelemetryState, I as buildAiGenerationEvents, J as stableConfigKey, K as parsePosthogReleaseId, L as buildBrowserExceptionProperties, M as FIRST_RUN_NOTICE, N as TELEMETRY_BATCH_URL, O as ringStats, P as TELEMETRY_FLAGS_URL, Q as planWorktreeGc, R as buildExceptionProperties, S as buildContextDigest, T as latestSeq, U as decideTelemetryConsent, V as decideIsDevInstall, W as exceptionFingerprint, X as matchedPrefix, Y as DEFAULT_BRANCH_GC_PREFIXES, Z as planBranchGc, _ as INGEST_SPEC, a as ensureProjectIds, b as DEFAULT_DIGEST_BUDGET_CHARS, d as assetForPlatform, et as usablePrefixes, f as decideRtkInstall, g as verifyDigest, h as isRtkBinaryName, i as createConfigStore, j as localDayKey, k as scrubText, m as installTargetPath, n as DEFAULT_CONFIG, o as generateProjectId, p as findEscapingArchiveMember, q as resolveProjectToken, s as glimmervoidHomeDir$1, t as ABORT_CONFIG_SAVE, tt as worktreeIntegrationTips, v as mergeSettingsBlock, w as enabledSourceNames, x as SOURCE_DEFAULTS, y as validateSettingsBlock, z as buildFactoryDecisionEvent } from "./config-store-aZpWeX92.js";
24
+ import { $ as FactoryHarnessVerdict, A as BenchmarkActionRequest, B as PrCheckoutCaseInput, C as IssueKey, D as IssuesStatus, E as IssuesState, F as BenchmarkStatus, G as TelemetryState, H as PendingCrashReport, I as BenchmarkSuite, J as FACTORY_ERROR_MAX_CHARS, K as FACTORY_DECISION_OUTPUT_MAX_BYTES, L as CommitComparison, M as BenchmarkId, N as BenchmarkJudgement, O as ChangeMap, P as BenchmarkRun, Q as FactoryHarnessStateLine, R as MergedPrListing, S as GithubIssueResponse, T as IssuesSearchResponse, U as TELEMETRY_EVENT_SCHEMAS, X as FactoryConsequences, Y as FACTORY_PROTECTED_PATH_FLOOR, Z as FactoryControlRequest, _ as PlanDecision, a as ClientMessage, at as FactoryQueueIntentRequest, b as planTitle, c as UpdateJournal, ct as FactoryWorkerEvent, d as TraceRecord, et as FactoryHogQLResponse, f as PLAN_BODY_CAP_BYTES, g as PLAN_TOOL_NAME, h as PLAN_RESULT_HOOK_EVENT, i as CONTROL_FRAME_MAX_BYTES, it as FactoryProjectState, j as BenchmarkCase, k as changeMapFactId, l as MAX_RAW_LINE_CHARS, lt as isSingleFactoryPathSegment, m as PLAN_HOOK_OUTPUT_MAX_CHARS, n as HookEnvelope, nt as FactoryIssueQueryRow, o as DIFF_ANNOTATION_NOTE_MAX_CHARS, ot as FactoryReviewVerdict, p as PLAN_HOOK_EVENT, q as FactoryDecisionLogLine, r as parseDataClientMessage, rt as FactoryLaneState, s as InstallFlavor, st as FactoryReviewerOutput, t as HTTP_URL_RE, tt as FactoryIssue, u as TraceCheckpoint, v as PlanRevision, w as IssueUpdate, x as GithubIssuePullRequestsRepository, y as parseExitPlanModeHookPayload, z as MinedPrReviewData } from "./contracts-DzDg-Z9I.js";
25
+ import { a as describeAgentResolvability, c as isKnownAgentId, d as setCustomAgents, f as claudeCode, i as customAgentFingerprint, l as listAgentIds, n as cachedAgentResolvability, o as getAdapter, r as commandFor, s as hookProfileOf, t as DEFAULT_AGENT_ID, u as resolveAdapter } from "./adapters-BS_7o8fj.js";
26
26
  import { t as HOOK_TOOLS } from "./hook-tools-UX2fmt81.js";
27
- 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-DP44Ybt9.js";
27
+ 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-Da4C7tMG.js";
28
28
  import { _ as escapeMarkup, g as createLaneLog, n as createVisionsSetup } from "./visions-setup-CfgNHiG3.js";
29
29
  import { t as getRtkPath } from "./rtk-resolver-jJxy_Mrt.js";
30
30
  import { createRequire } from "node:module";
@@ -2019,7 +2019,7 @@ function judgeRole({ vendor, cwd, hookPort, readablePaths, agentSessionId }) {
2019
2019
  readableWithinDenied: [...readablePaths]
2020
2020
  };
2021
2021
  }
2022
- function workRole({ vendor, cwd, checkout, readablePaths, agentSessionId }) {
2022
+ function workRole({ vendor, cwd, checkout, readablePaths, writablePaths = [], agentSessionId }) {
2023
2023
  return {
2024
2024
  vendors: [vendor],
2025
2025
  vendorHome: {
@@ -2028,7 +2028,7 @@ function workRole({ vendor, cwd, checkout, readablePaths, agentSessionId }) {
2028
2028
  agentSessionId
2029
2029
  },
2030
2030
  network: { access: "open" },
2031
- writableRoots: [],
2031
+ writableRoots: [...writablePaths],
2032
2032
  lockedCheckouts: [checkout],
2033
2033
  commitCheckouts: [],
2034
2034
  readDenied: [],
@@ -2218,7 +2218,7 @@ function registerEphemeralSession({ map, id, sess, closeSessionDataClients, logP
2218
2218
  //#region server/agent-api-wiring.ts
2219
2219
  var AGENT_LANE_TAG = "agent-spawn";
2220
2220
  var SPAWN_FAILED_MESSAGE = "could not spawn the session";
2221
- function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoardSessions, makeSession, wireSessionEvents, closeSessionDataClients, broadcastControl, spawnGate, recordLane = null, logger = console }) {
2221
+ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoardSessions, makeSession, wireSessionEvents, closeSessionDataClients, broadcastControl, spawnGate, sessionDocs, recordLane = null, logger = console }) {
2222
2222
  async function spawn(parent, request) {
2223
2223
  const budget = parent.agentSpawnBudget();
2224
2224
  const allowance = decideSpawnAllowance(budget);
@@ -2364,6 +2364,21 @@ function createAgentApiWiring({ config, agentSessions, listAllSessions, listBoar
2364
2364
  body: reply
2365
2365
  };
2366
2366
  }
2367
+ if (parsed.verb === "edit") {
2368
+ const reply = await sessionDocs.surface(session, parsed.request);
2369
+ if (!reply.ok) return {
2370
+ status: 400,
2371
+ body: reply
2372
+ };
2373
+ return {
2374
+ status: 200,
2375
+ body: {
2376
+ ok: true,
2377
+ path: reply.doc.path,
2378
+ name: reply.doc.name
2379
+ }
2380
+ };
2381
+ }
2367
2382
  const outcome = await spawn(session, parsed.request);
2368
2383
  if (!outcome.ok) return {
2369
2384
  status: outcome.status,
@@ -4965,12 +4980,6 @@ var FINDING_HEADER_SOURCE = String.raw`\*\*(?:\[([^\]\n]+)\]\s+)?(${SEVERITY_ALT
4965
4980
  function findingHeader$1(reviewer, severity) {
4966
4981
  return `**[${reviewer}] ${severity}**`;
4967
4982
  }
4968
- function findingSeveritiesIn(body) {
4969
- return [...body.matchAll(new RegExp(FINDING_HEADER_SOURCE, "g"))].flatMap((match) => {
4970
- const severity = FindingSeverity.safeParse(match[2]);
4971
- return severity.success ? [severity.data] : [];
4972
- });
4973
- }
4974
4983
  function parseLeadingFindingHeader(body) {
4975
4984
  const match = new RegExp(String.raw`^${FINDING_HEADER_SOURCE}\s*(?:\r?\n|$)`).exec(body);
4976
4985
  if (!match) return null;
@@ -7085,10 +7094,49 @@ function isCommentable(comment, commentable) {
7085
7094
  if (commentable === null) return true;
7086
7095
  return isLineCommentable(commentable, comment.path, comment.side, comment.line);
7087
7096
  }
7088
- function renderPostingPlan(plan, commentable) {
7097
+ function locationKey$1(location) {
7098
+ return `${location.path}:${location.line}:${location.side}`;
7099
+ }
7100
+ function lineKey(location) {
7101
+ return `${location.path}:${location.line}`;
7102
+ }
7103
+ function keepStrongestFinding(strongestByLocation, location, finding) {
7104
+ const strongest = strongestByLocation.get(location);
7105
+ if (strongest && FindingSeverity.options.indexOf(strongest.severity) <= FindingSeverity.options.indexOf(finding.severity)) return;
7106
+ strongestByLocation.set(location, finding);
7107
+ }
7108
+ function strongestFindingByLocation(findings) {
7109
+ const strongestByLocation = /* @__PURE__ */ new Map();
7110
+ for (const finding of findings) {
7111
+ if (finding.line === null) continue;
7112
+ keepStrongestFinding(strongestByLocation, locationKey$1({
7113
+ path: finding.path,
7114
+ line: finding.line,
7115
+ side: finding.side
7116
+ }), finding);
7117
+ keepStrongestFinding(strongestByLocation, lineKey({
7118
+ path: finding.path,
7119
+ line: finding.line
7120
+ }), finding);
7121
+ }
7122
+ return strongestByLocation;
7123
+ }
7124
+ function labeledComment(comment, finding) {
7125
+ if (!finding) return comment;
7126
+ const content = withoutAutomatedNote(comment.body);
7127
+ const agentHeader = parseLeadingFindingHeader(content);
7128
+ const commentText = agentHeader ? content.slice(agentHeader.length).trim() : content;
7129
+ return {
7130
+ ...comment,
7131
+ body: `${findingHeader(finding)}\n\n${commentText}`
7132
+ };
7133
+ }
7134
+ function renderPostingPlan(plan, findings, commentable) {
7089
7135
  const comments = [];
7090
7136
  const foldedSections = [];
7091
- for (const comment of plan.comments) {
7137
+ const findingAtLocation = strongestFindingByLocation(findings);
7138
+ for (const plannedComment of plan.comments) {
7139
+ const comment = labeledComment(plannedComment, findingAtLocation.get(locationKey$1(plannedComment)) ?? findingAtLocation.get(lineKey(plannedComment)));
7092
7140
  if (isCommentable(comment, commentable)) {
7093
7141
  comments.push({
7094
7142
  ...comment,
@@ -7104,23 +7152,7 @@ function renderPostingPlan(plan, commentable) {
7104
7152
  };
7105
7153
  }
7106
7154
  function readyDraft({ candidate, tier, reasons, result, reviewedBase, commentable = null, posting = null }) {
7107
- const rendered = posting ? renderPostingPlan(posting, commentable) : renderReview(result, commentable);
7108
- const severityByLocation = /* @__PURE__ */ new Map();
7109
- for (const finding of result.findings) {
7110
- if (finding.line === null) continue;
7111
- const location = `${finding.path}:${finding.line}:${finding.side}`;
7112
- const previousSeverity = severityByLocation.get(location);
7113
- if (previousSeverity && FindingSeverity.options.indexOf(previousSeverity) <= FindingSeverity.options.indexOf(finding.severity)) continue;
7114
- severityByLocation.set(location, finding.severity);
7115
- }
7116
- const comments = rendered.comments.map((comment) => {
7117
- if (findingSeveritiesIn(comment.body).length > 0) return comment;
7118
- const severity = severityByLocation.get(`${comment.path}:${comment.line}:${comment.side}`);
7119
- return severity ? {
7120
- ...comment,
7121
- severity
7122
- } : comment;
7123
- });
7155
+ const rendered = posting ? renderPostingPlan(posting, result.findings, commentable) : renderReview(result, commentable);
7124
7156
  return {
7125
7157
  ...draftBase(candidate, tier, reasons, result.head),
7126
7158
  ...reviewedBase ? { reviewedBase } : {},
@@ -7128,7 +7160,7 @@ function readyDraft({ candidate, tier, reasons, result, reviewedBase, commentabl
7128
7160
  summary: result.summary,
7129
7161
  ...result.assessment ? { assessment: result.assessment } : {},
7130
7162
  body: rendered.body,
7131
- comments,
7163
+ comments: rendered.comments,
7132
7164
  status: "ready"
7133
7165
  };
7134
7166
  }
@@ -7295,10 +7327,13 @@ function buildReviewPrompt({ candidate, detail, tier, reasons, checkoutPath, rep
7295
7327
  `- commit_id must be exactly ${head}.`,
7296
7328
  "- comments holds one entry per finding anchored to a line of the diff; each path is non-empty, each line is a",
7297
7329
  ` positive integer on that side of the diff, side is one of ${sides}, and each body is non-empty.`,
7298
- "- body holds only the findings that cannot anchor to a diff line, one short bullet each: `path:line`, the problem,",
7299
- " the fix. Nothing else: no summary, no recap of the change, no praise, no verdict or its reasoning, no follow-up",
7300
- " preamble, no list of what was checked or could not be run. With every finding inline, body is the empty string.",
7301
- "- Keep each comment to the problem and the fix, in a few sentences. Glimmervoid marks the review as automated itself.",
7330
+ "- body holds only the findings that cannot anchor to a diff line, one short bullet each, in the form",
7331
+ " - **[<reviewer>] <severity>** `path:line`: the problem and the fix",
7332
+ " with the reviewer and severity that finding has in the report.",
7333
+ " Nothing else: no summary, no recap of the change, no praise, no verdict or its reasoning, no follow-up preamble,",
7334
+ " no list of what was checked or could not be run. With every finding inline, body is the empty string.",
7335
+ "- Keep each comment to the problem and the fix, in a few sentences, with no label or heading. Glimmervoid marks the",
7336
+ " review as automated and labels each comment with its finding's reviewer and severity itself.",
7302
7337
  "If this file is missing or invalid, Glimmervoid renders the draft from the report findings instead.",
7303
7338
  "",
7304
7339
  "Untrusted data:",
@@ -11847,9 +11882,358 @@ function createSessionOutput(options) {
11847
11882
  };
11848
11883
  }
11849
11884
  //#endregion
11885
+ //#region server/core/ingest-shell-core.ts
11886
+ var SOURCE$4 = "shellHistory";
11887
+ var SHELLS = Object.freeze([
11888
+ "powershell",
11889
+ "fish",
11890
+ "bash",
11891
+ "zsh"
11892
+ ]);
11893
+ var ZERO_SETUP_SHELLS = Object.freeze(["powershell", "fish"]);
11894
+ var ZERO_SETUP_SHELLS_ON_MACOS = Object.freeze(["zsh", "fish"]);
11895
+ var PSREADLINE_SUFFIX = "_history.txt";
11896
+ var MAX_COMMAND_CHARS = 4e3;
11897
+ var TRIVIAL_COMMANDS = /* @__PURE__ */ new Set([
11898
+ "",
11899
+ "l",
11900
+ "ls",
11901
+ "ll",
11902
+ "la",
11903
+ "dir",
11904
+ "cd",
11905
+ "cd ..",
11906
+ "cd -",
11907
+ "cd ~",
11908
+ "cd /",
11909
+ "pwd",
11910
+ "cls",
11911
+ "clear",
11912
+ "exit",
11913
+ "q",
11914
+ "history"
11915
+ ]);
11916
+ function defaultShellsFor(platform) {
11917
+ return platform === "darwin" ? ZERO_SETUP_SHELLS_ON_MACOS : ZERO_SETUP_SHELLS;
11918
+ }
11919
+ function normalizeShells(raw, platform) {
11920
+ if (!Array.isArray(raw) || raw.length === 0) return {
11921
+ shells: [...defaultShellsFor(platform)],
11922
+ rejected: []
11923
+ };
11924
+ const shells = [];
11925
+ const rejected = [];
11926
+ for (const entry of raw) {
11927
+ const name = typeof entry === "string" ? entry.trim().toLowerCase() : "";
11928
+ const shell = name === "pwsh" || name === "powershell.exe" ? "powershell" : name;
11929
+ if (SHELLS.includes(shell)) {
11930
+ if (!shells.includes(shell)) shells.push(shell);
11931
+ continue;
11932
+ }
11933
+ const label = typeof entry === "string" ? entry.trim() : String(entry);
11934
+ if (rejected.includes(label)) continue;
11935
+ rejected.push(label);
11936
+ }
11937
+ return {
11938
+ shells,
11939
+ rejected
11940
+ };
11941
+ }
11942
+ function resolveHomeDir(env, homeDir) {
11943
+ return rawTextOr(env?.HOME, null) || rawTextOr(env?.USERPROFILE, null) || homeDir;
11944
+ }
11945
+ function dataHome(env, homeDir) {
11946
+ return rawTextOr(env?.XDG_DATA_HOME, null) || path.join(resolveHomeDir(env, homeDir), ".local", "share");
11947
+ }
11948
+ function configHome(env, homeDir) {
11949
+ return rawTextOr(env?.XDG_CONFIG_HOME, null) || path.join(resolveHomeDir(env, homeDir), ".config");
11950
+ }
11951
+ function powershellLocations(env, platform, homeDir) {
11952
+ if (platform === "win32") {
11953
+ const appData = rawTextOr(env?.APPDATA, null) || path.join(resolveHomeDir(env, homeDir), "AppData", "Roaming");
11954
+ return [{
11955
+ shell: "powershell",
11956
+ dir: path.join(appData, "Microsoft", "Windows", "PowerShell", "PSReadLine"),
11957
+ suffix: PSREADLINE_SUFFIX,
11958
+ name: null
11959
+ }];
11960
+ }
11961
+ return [{
11962
+ shell: "powershell",
11963
+ dir: path.join(dataHome(env, homeDir), "powershell", "PSReadLine"),
11964
+ suffix: PSREADLINE_SUFFIX,
11965
+ name: null
11966
+ }];
11967
+ }
11968
+ function fishLocations(env, homeDir) {
11969
+ return [{
11970
+ shell: "fish",
11971
+ dir: path.join(dataHome(env, homeDir), "fish"),
11972
+ suffix: null,
11973
+ name: "fish_history"
11974
+ }, {
11975
+ shell: "fish",
11976
+ dir: path.join(configHome(env, homeDir), "fish"),
11977
+ suffix: null,
11978
+ name: "fish_history"
11979
+ }];
11980
+ }
11981
+ function fileLocation(shell, filePath) {
11982
+ return {
11983
+ shell,
11984
+ dir: path.dirname(filePath),
11985
+ suffix: null,
11986
+ name: path.basename(filePath)
11987
+ };
11988
+ }
11989
+ function histFileLocation(wanted, env) {
11990
+ const posixShells = wanted.filter((shell) => shell === "bash" || shell === "zsh");
11991
+ if (posixShells.length !== 1) return [];
11992
+ const histFile = rawTextOr(env?.HISTFILE, null);
11993
+ if (!histFile) return [];
11994
+ return [fileLocation(posixShells[0], histFile)];
11995
+ }
11996
+ function defaultHistFiles(shell, env, homeDir) {
11997
+ const home = resolveHomeDir(env, homeDir);
11998
+ if (shell === "bash") return [fileLocation("bash", path.join(home, ".bash_history"))];
11999
+ return [fileLocation("zsh", path.join(home, ".zsh_history")), fileLocation("zsh", path.join(home, ".histfile"))];
12000
+ }
12001
+ function locationKey(location) {
12002
+ return `${path.resolve(location.dir)}|${location.name || location.suffix}`;
12003
+ }
12004
+ function historyLocations({ shells = null, env = {}, platform = process.platform, homeDir = null } = {}) {
12005
+ const wanted = normalizeShells(shells, platform).shells;
12006
+ const locations = [...histFileLocation(wanted, env)];
12007
+ for (const shell of wanted) {
12008
+ if (shell === "powershell") locations.push(...powershellLocations(env, platform, homeDir));
12009
+ if (shell === "fish") locations.push(...fishLocations(env, homeDir));
12010
+ if (shell === "bash" || shell === "zsh") locations.push(...defaultHistFiles(shell, env, homeDir));
12011
+ }
12012
+ const seen = /* @__PURE__ */ new Set();
12013
+ const deduped = [];
12014
+ for (const location of locations) {
12015
+ const key = locationKey(location);
12016
+ if (seen.has(key)) continue;
12017
+ seen.add(key);
12018
+ deduped.push(location);
12019
+ }
12020
+ return deduped;
12021
+ }
12022
+ function matchesLocation(location, filename) {
12023
+ if (typeof filename !== "string" || !filename) return false;
12024
+ if (location?.name) return filename === location.name;
12025
+ const suffix = location?.suffix;
12026
+ if (!suffix) return false;
12027
+ if (filename.length <= suffix.length) return false;
12028
+ return filename.toLowerCase().endsWith(suffix.toLowerCase());
12029
+ }
12030
+ function createParseState() {
12031
+ return {
12032
+ pending: [],
12033
+ pendingTs: null,
12034
+ entry: null
12035
+ };
12036
+ }
12037
+ function withinContinuationBounds(pending) {
12038
+ if (pending.length >= 64) return false;
12039
+ let chars = 0;
12040
+ for (const line of pending) chars += line.length + 1;
12041
+ return chars < MAX_COMMAND_CHARS;
12042
+ }
12043
+ function parsePowershell(lines, state) {
12044
+ const commands = [];
12045
+ let pending = state.pending;
12046
+ for (const line of lines) {
12047
+ const continued = line.endsWith("`");
12048
+ pending = [...pending, continued ? line.slice(0, -1) : line];
12049
+ if (continued && withinContinuationBounds(pending)) continue;
12050
+ commands.push({
12051
+ text: pending.join("\n"),
12052
+ ts: null
12053
+ });
12054
+ pending = [];
12055
+ }
12056
+ return {
12057
+ commands,
12058
+ state: {
12059
+ ...state,
12060
+ pending
12061
+ }
12062
+ };
12063
+ }
12064
+ var FISH_ENTRY = /^- cmd:\s?([\s\S]*)$/;
12065
+ var FISH_WHEN = /^\s+when:\s*(\d+)/;
12066
+ function unescapeFish(text) {
12067
+ let out = "";
12068
+ for (let index = 0; index < text.length; index += 1) {
12069
+ const char = text[index];
12070
+ if (char !== "\\") {
12071
+ out += char;
12072
+ continue;
12073
+ }
12074
+ const next = text[index + 1];
12075
+ if (next === "n") {
12076
+ out += "\n";
12077
+ index += 1;
12078
+ continue;
12079
+ }
12080
+ if (next === "\\") {
12081
+ out += "\\";
12082
+ index += 1;
12083
+ continue;
12084
+ }
12085
+ out += char;
12086
+ }
12087
+ return out;
12088
+ }
12089
+ function parseFish(lines, state) {
12090
+ const commands = [];
12091
+ let entry = state.entry;
12092
+ for (const line of lines) {
12093
+ const started = FISH_ENTRY.exec(line);
12094
+ if (started) {
12095
+ if (entry) commands.push(entry);
12096
+ entry = {
12097
+ text: unescapeFish(started[1]),
12098
+ ts: null
12099
+ };
12100
+ continue;
12101
+ }
12102
+ if (!entry) continue;
12103
+ const when = FISH_WHEN.exec(line);
12104
+ if (!when) continue;
12105
+ commands.push({
12106
+ text: entry.text,
12107
+ ts: Number(when[1]) * 1e3
12108
+ });
12109
+ entry = null;
12110
+ }
12111
+ return {
12112
+ commands,
12113
+ state: {
12114
+ ...state,
12115
+ entry
12116
+ }
12117
+ };
12118
+ }
12119
+ var BASH_TIMESTAMP = /^#(\d{9,})\s*$/;
12120
+ function parseBash(lines, state) {
12121
+ const commands = [];
12122
+ let pendingTs = state.pendingTs;
12123
+ for (const line of lines) {
12124
+ const stamped = BASH_TIMESTAMP.exec(line);
12125
+ if (stamped) {
12126
+ pendingTs = Number(stamped[1]) * 1e3;
12127
+ continue;
12128
+ }
12129
+ if (!line.trim()) continue;
12130
+ commands.push({
12131
+ text: line,
12132
+ ts: pendingTs
12133
+ });
12134
+ pendingTs = null;
12135
+ }
12136
+ return {
12137
+ commands,
12138
+ state: {
12139
+ ...state,
12140
+ pendingTs
12141
+ }
12142
+ };
12143
+ }
12144
+ var ZSH_EXTENDED = /^:\s(\d+):(\d+);([\s\S]*)$/;
12145
+ function parseZsh(lines, state) {
12146
+ const commands = [];
12147
+ let pending = state.pending;
12148
+ let pendingTs = state.pendingTs;
12149
+ for (const line of lines) {
12150
+ const starting = pending.length === 0;
12151
+ const extended = starting ? ZSH_EXTENDED.exec(line) : null;
12152
+ if (starting) pendingTs = extended ? Number(extended[1]) * 1e3 : null;
12153
+ const text = extended ? extended[3] : line;
12154
+ const continued = text.endsWith("\\");
12155
+ pending = [...pending, continued ? text.slice(0, -1) : text];
12156
+ if (continued && withinContinuationBounds(pending)) continue;
12157
+ const joined = pending.join("\n");
12158
+ pending = [];
12159
+ if (joined.trim()) commands.push({
12160
+ text: joined,
12161
+ ts: pendingTs
12162
+ });
12163
+ pendingTs = null;
12164
+ }
12165
+ return {
12166
+ commands,
12167
+ state: {
12168
+ ...state,
12169
+ pending,
12170
+ pendingTs
12171
+ }
12172
+ };
12173
+ }
12174
+ var PARSERS = Object.freeze({
12175
+ powershell: parsePowershell,
12176
+ fish: parseFish,
12177
+ bash: parseBash,
12178
+ zsh: parseZsh
12179
+ });
12180
+ function parseHistoryLines({ shell, lines = [], state = null } = {}) {
12181
+ const current = state || createParseState();
12182
+ const parser = shell ? PARSERS[shell] : void 0;
12183
+ if (!parser || !Array.isArray(lines) || lines.length === 0) return {
12184
+ commands: [],
12185
+ state: current
12186
+ };
12187
+ return parser(lines, current);
12188
+ }
12189
+ function foldForCompare(text) {
12190
+ return String(text == null ? "" : text).replace(/\s+/g, " ").trim();
12191
+ }
12192
+ function isTrivialCommand(text) {
12193
+ if (typeof text !== "string") return true;
12194
+ return TRIVIAL_COMMANDS.has(text.trim().toLowerCase());
12195
+ }
12196
+ function decideCommandEvent({ shell = "", command = null, previous = null, now = 0 } = {}) {
12197
+ const text = typeof command?.text === "string" ? command.text : "";
12198
+ const compared = foldForCompare(text);
12199
+ if (!compared) return {
12200
+ event: null,
12201
+ previous
12202
+ };
12203
+ if (isTrivialCommand(compared)) return {
12204
+ event: null,
12205
+ previous: compared
12206
+ };
12207
+ if (compared === previous) return {
12208
+ event: null,
12209
+ previous: compared
12210
+ };
12211
+ const stamped = Number(command?.ts);
12212
+ const lineCount = text.split("\n").length;
12213
+ return {
12214
+ event: {
12215
+ source: SOURCE$4,
12216
+ kind: "command",
12217
+ ts: Number.isFinite(stamped) && stamped > 0 ? Math.floor(stamped) : now,
12218
+ scope: {
12219
+ root: null,
12220
+ sessionId: null
12221
+ },
12222
+ summary: `${shell}: ${text}`,
12223
+ detail: lineCount > 1 ? {
12224
+ shell,
12225
+ lines: lineCount
12226
+ } : { shell }
12227
+ },
12228
+ previous: compared
12229
+ };
12230
+ }
12231
+ //#endregion
11850
12232
  //#region server/hook-tools.ts
11851
12233
  var writtenHomes = /* @__PURE__ */ new Set();
11852
12234
  var warnedTools = /* @__PURE__ */ new Set();
12235
+ var ensuredDataDirs = /* @__PURE__ */ new Set();
12236
+ var RTK_DATA_FOLDER = "rtk";
11853
12237
  function saneYoloHomeDir() {
11854
12238
  return path.join(glimmervoidHomeDir(os.homedir(), process.env), "sane-yolo");
11855
12239
  }
@@ -11872,6 +12256,37 @@ function writeSaneYoloPolicy(homeDir) {
11872
12256
  }
11873
12257
  writtenHomes.add(homeDir);
11874
12258
  }
12259
+ function rtkDataDir(platform = process.platform, env = process.env, homeDir = os.homedir()) {
12260
+ if (platform === "darwin") return path.join(homeDir, "Library", "Application Support", RTK_DATA_FOLDER);
12261
+ if (platform !== "linux") return null;
12262
+ const dataHomeDir = dataHome(env, homeDir);
12263
+ return path.isAbsolute(dataHomeDir) ? path.join(dataHomeDir, RTK_DATA_FOLDER) : null;
12264
+ }
12265
+ function ensureDataDir(dataDir) {
12266
+ if (dataDir === null || ensuredDataDirs.has(dataDir)) return dataDir;
12267
+ fs.mkdirSync(dataDir, { recursive: true });
12268
+ ensuredDataDirs.add(dataDir);
12269
+ return dataDir;
12270
+ }
12271
+ function rtkHookTool(binPath, warn) {
12272
+ try {
12273
+ const dataDir = ensureDataDir(rtkDataDir());
12274
+ return dataDir === null ? {
12275
+ id: "rtk",
12276
+ binPath
12277
+ } : {
12278
+ id: "rtk",
12279
+ binPath,
12280
+ dataDir
12281
+ };
12282
+ } catch (error) {
12283
+ warnOnce("rtkDataDir", `[rtk] The rtk data folder could not be created: ${String(error)}. Sandboxed sessions will run rtk without savings tracking.`, warn);
12284
+ return {
12285
+ id: "rtk",
12286
+ binPath
12287
+ };
12288
+ }
12289
+ }
11875
12290
  function warnOnce(toolId, message, warn) {
11876
12291
  if (warnedTools.has(toolId)) return;
11877
12292
  warnedTools.add(toolId);
@@ -11881,10 +12296,7 @@ function resolveHookTools(config, { skipPermissions, warn = console.warn }) {
11881
12296
  const hookTools = [];
11882
12297
  if (config.rtk) {
11883
12298
  const rtkPath = getRtkPath();
11884
- if (rtkPath) hookTools.push({
11885
- id: "rtk",
11886
- binPath: rtkPath
11887
- });
12299
+ if (rtkPath) hookTools.push(rtkHookTool(rtkPath, warn));
11888
12300
  if (!rtkPath) warnOnce("rtk", "[rtk] config.rtk is true, but no rtk binary was found. Sessions will spawn without rtk hooks.", warn);
11889
12301
  }
11890
12302
  if (config.saneYolo === false || !skipPermissions) return hookTools;
@@ -11940,6 +12352,10 @@ function createSessionHookLifecycle(options) {
11940
12352
  ...isSaneYoloActive ? [saneYoloHomeDir()] : []
11941
12353
  ];
11942
12354
  }
12355
+ function injectedWritablePaths(isSaneYoloActive) {
12356
+ if (token === null) return [];
12357
+ return injectedHookTools(isSaneYoloActive).flatMap((tool) => tool.dataDir === void 0 ? [] : [tool.dataDir]);
12358
+ }
11943
12359
  function cleanup() {
11944
12360
  if (!token && !settingsHandle) return;
11945
12361
  if (options.hookRouter) try {
@@ -12140,7 +12556,8 @@ function createSessionHookLifecycle(options) {
12140
12556
  hasInjection: () => token !== null,
12141
12557
  hasSettings: () => settingsHandle !== null,
12142
12558
  isRequiredSandboxMissing: () => options.settingsSandbox !== null && settingsHandle === null,
12143
- injectedReadablePaths
12559
+ injectedReadablePaths,
12560
+ injectedWritablePaths
12144
12561
  };
12145
12562
  }
12146
12563
  //#endregion
@@ -19618,13 +20035,14 @@ var Session = class extends EventEmitter {
19618
20035
  cwd: this.effectiveCwd(),
19619
20036
  hookPort: this._hooks.hasInjection() ? this._hooks.listenerPort() : null,
19620
20037
  readablePaths: this._hooks.injectedReadablePaths(this._isSaneYoloActive),
20038
+ writablePaths: this._hooks.injectedWritablePaths(this._isSaneYoloActive),
19621
20039
  agentSessionId
19622
20040
  });
19623
20041
  if (!prepared.ok) throw new Error(prepared.reason);
19624
20042
  this._disposeSandboxLaunch(this._sandboxLaunch);
19625
20043
  this._sandboxLaunch = prepared.launch;
19626
20044
  return {
19627
- ...await prepared.launch.wrap(command.file, command.args),
20045
+ ...await prepared.launch.wrap(command.file, command.args, { hasTerminal: true }),
19628
20046
  env: prepared.launch.env,
19629
20047
  cwd: prepared.launch.cwd
19630
20048
  };
@@ -20395,10 +20813,13 @@ async function prepareSandboxLaunch({ role, launchId, tempDir, enterDir }) {
20395
20813
  return {
20396
20814
  ok: true,
20397
20815
  launch: {
20398
- wrap: async (file, args) => {
20816
+ wrap: async (file, args, { hasTerminal }) => {
20399
20817
  const rebuilt = await buildPlanFromCurrentDisk();
20400
20818
  if (!rebuilt.ok) throw new Error(rebuilt.reason);
20401
- await writeJsonAtomic(policyPath, rebuilt.plan.policy);
20819
+ await writeJsonAtomic(policyPath, {
20820
+ ...rebuilt.plan.policy,
20821
+ allowPty: hasTerminal && rebuilt.plan.launcher === "open-network"
20822
+ });
20402
20823
  placeholderPathsAbsentAtWrap = await absentPaths(sandboxPlaceholderPaths(realRole));
20403
20824
  await createMissingMountPoints(lockedClaudeMountPoints(realRole));
20404
20825
  return {
@@ -23821,7 +24242,7 @@ var prepareSandboxedCheckRunner = async ({ scratchParent, writablePaths, readabl
23821
24242
  const runCheck = async (argv, { timeout, signal, ...execOptions }) => {
23822
24243
  const [checkCommand, ...checkArgs] = argv;
23823
24244
  if (checkCommand === void 0) throw new Error("Factory check command is empty");
23824
- const wrapped = await launch.wrap(checkCommand, checkArgs);
24245
+ const wrapped = await launch.wrap(checkCommand, checkArgs, { hasTerminal: false });
23825
24246
  const timeoutSignal = AbortSignal.timeout(timeout);
23826
24247
  try {
23827
24248
  return await execFileAsync(wrapped.file, wrapped.args, {
@@ -25250,8 +25671,11 @@ async function existingRealPath(candidate) {
25250
25671
  return null;
25251
25672
  }
25252
25673
  }
25253
- async function readableToolFolders(homeDir) {
25254
- return (await Promise.all(LANE_READABLE_HOME_FOLDERS.map((folder) => existingRealPath(path.join(homeDir, folder))))).filter((folder) => folder !== null);
25674
+ async function existingRealPaths(candidates) {
25675
+ return (await Promise.all(candidates.filter((candidate) => path.isAbsolute(candidate)).map(existingRealPath))).filter((realPath) => realPath !== null);
25676
+ }
25677
+ function readableToolFolders(homeDir) {
25678
+ return existingRealPaths(LANE_READABLE_HOME_FOLDERS.map((folder) => path.join(homeDir, folder)));
25255
25679
  }
25256
25680
  async function readLaneCommonGitDir(cwd) {
25257
25681
  const { stdout } = await runHardenedGit([
@@ -25329,7 +25753,8 @@ async function resolveWorkLane(input, { hookToolConfig = {}, resolveSaneYoloHook
25329
25753
  cwd: facts.cwd,
25330
25754
  checkout,
25331
25755
  agentSessionId: facts.agentSessionId,
25332
- readablePaths: [...facts.readablePaths, ...toolFolders]
25756
+ readablePaths: [...facts.readablePaths, ...toolFolders],
25757
+ writablePaths: await existingRealPaths(facts.writablePaths)
25333
25758
  }),
25334
25759
  launchId: facts.launchId
25335
25760
  });
@@ -29948,7 +30373,7 @@ function pickStaleByMtime(entriesByKey, { maxTracked = 256 } = {}) {
29948
30373
  }
29949
30374
  //#endregion
29950
30375
  //#region server/core/ingest-agent-core.ts
29951
- var SOURCE$3 = "agentLogs";
30376
+ var SOURCE$2 = "agentLogs";
29952
30377
  var MAX_RAW_CHARS = 4e3;
29953
30378
  var TOOL_TARGET_KEYS = Object.freeze([
29954
30379
  "file_path",
@@ -30015,7 +30440,7 @@ function turnEvent({ ts, root, sessionId, vendor, text }) {
30015
30440
  const summary = boundRaw(text).trim();
30016
30441
  if (!summary) return null;
30017
30442
  return {
30018
- source: SOURCE$3,
30443
+ source: SOURCE$2,
30019
30444
  kind: "agent-turn",
30020
30445
  ts,
30021
30446
  scope: {
@@ -30030,7 +30455,7 @@ function toolEvent({ ts, root, sessionId, vendor, name, target }) {
30030
30455
  const tool = boundRaw(textOr(name, null) || "tool");
30031
30456
  const suffix = target ? ` ${target}` : "";
30032
30457
  return {
30033
- source: SOURCE$3,
30458
+ source: SOURCE$2,
30034
30459
  kind: "agent-tool",
30035
30460
  ts,
30036
30461
  scope: {
@@ -31824,353 +32249,6 @@ function createGitIngest({ publish, sourceConfig = {}, reposProvider = null, log
31824
32249
  };
31825
32250
  }
31826
32251
  //#endregion
31827
- //#region server/core/ingest-shell-core.ts
31828
- var SOURCE$1 = "shellHistory";
31829
- var SHELLS = Object.freeze([
31830
- "powershell",
31831
- "fish",
31832
- "bash",
31833
- "zsh"
31834
- ]);
31835
- var ZERO_SETUP_SHELLS = Object.freeze(["powershell", "fish"]);
31836
- var ZERO_SETUP_SHELLS_ON_MACOS = Object.freeze(["zsh", "fish"]);
31837
- var PSREADLINE_SUFFIX = "_history.txt";
31838
- var MAX_COMMAND_CHARS = 4e3;
31839
- var TRIVIAL_COMMANDS = /* @__PURE__ */ new Set([
31840
- "",
31841
- "l",
31842
- "ls",
31843
- "ll",
31844
- "la",
31845
- "dir",
31846
- "cd",
31847
- "cd ..",
31848
- "cd -",
31849
- "cd ~",
31850
- "cd /",
31851
- "pwd",
31852
- "cls",
31853
- "clear",
31854
- "exit",
31855
- "q",
31856
- "history"
31857
- ]);
31858
- function defaultShellsFor(platform) {
31859
- return platform === "darwin" ? ZERO_SETUP_SHELLS_ON_MACOS : ZERO_SETUP_SHELLS;
31860
- }
31861
- function normalizeShells(raw, platform) {
31862
- if (!Array.isArray(raw) || raw.length === 0) return {
31863
- shells: [...defaultShellsFor(platform)],
31864
- rejected: []
31865
- };
31866
- const shells = [];
31867
- const rejected = [];
31868
- for (const entry of raw) {
31869
- const name = typeof entry === "string" ? entry.trim().toLowerCase() : "";
31870
- const shell = name === "pwsh" || name === "powershell.exe" ? "powershell" : name;
31871
- if (SHELLS.includes(shell)) {
31872
- if (!shells.includes(shell)) shells.push(shell);
31873
- continue;
31874
- }
31875
- const label = typeof entry === "string" ? entry.trim() : String(entry);
31876
- if (rejected.includes(label)) continue;
31877
- rejected.push(label);
31878
- }
31879
- return {
31880
- shells,
31881
- rejected
31882
- };
31883
- }
31884
- function resolveHomeDir(env, homeDir) {
31885
- return rawTextOr(env?.HOME, null) || rawTextOr(env?.USERPROFILE, null) || homeDir;
31886
- }
31887
- function dataHome(env, homeDir) {
31888
- return rawTextOr(env?.XDG_DATA_HOME, null) || path.join(resolveHomeDir(env, homeDir), ".local", "share");
31889
- }
31890
- function configHome(env, homeDir) {
31891
- return rawTextOr(env?.XDG_CONFIG_HOME, null) || path.join(resolveHomeDir(env, homeDir), ".config");
31892
- }
31893
- function powershellLocations(env, platform, homeDir) {
31894
- if (platform === "win32") {
31895
- const appData = rawTextOr(env?.APPDATA, null) || path.join(resolveHomeDir(env, homeDir), "AppData", "Roaming");
31896
- return [{
31897
- shell: "powershell",
31898
- dir: path.join(appData, "Microsoft", "Windows", "PowerShell", "PSReadLine"),
31899
- suffix: PSREADLINE_SUFFIX,
31900
- name: null
31901
- }];
31902
- }
31903
- return [{
31904
- shell: "powershell",
31905
- dir: path.join(dataHome(env, homeDir), "powershell", "PSReadLine"),
31906
- suffix: PSREADLINE_SUFFIX,
31907
- name: null
31908
- }];
31909
- }
31910
- function fishLocations(env, homeDir) {
31911
- return [{
31912
- shell: "fish",
31913
- dir: path.join(dataHome(env, homeDir), "fish"),
31914
- suffix: null,
31915
- name: "fish_history"
31916
- }, {
31917
- shell: "fish",
31918
- dir: path.join(configHome(env, homeDir), "fish"),
31919
- suffix: null,
31920
- name: "fish_history"
31921
- }];
31922
- }
31923
- function fileLocation(shell, filePath) {
31924
- return {
31925
- shell,
31926
- dir: path.dirname(filePath),
31927
- suffix: null,
31928
- name: path.basename(filePath)
31929
- };
31930
- }
31931
- function histFileLocation(wanted, env) {
31932
- const posixShells = wanted.filter((shell) => shell === "bash" || shell === "zsh");
31933
- if (posixShells.length !== 1) return [];
31934
- const histFile = rawTextOr(env?.HISTFILE, null);
31935
- if (!histFile) return [];
31936
- return [fileLocation(posixShells[0], histFile)];
31937
- }
31938
- function defaultHistFiles(shell, env, homeDir) {
31939
- const home = resolveHomeDir(env, homeDir);
31940
- if (shell === "bash") return [fileLocation("bash", path.join(home, ".bash_history"))];
31941
- return [fileLocation("zsh", path.join(home, ".zsh_history")), fileLocation("zsh", path.join(home, ".histfile"))];
31942
- }
31943
- function locationKey(location) {
31944
- return `${path.resolve(location.dir)}|${location.name || location.suffix}`;
31945
- }
31946
- function historyLocations({ shells = null, env = {}, platform = process.platform, homeDir = null } = {}) {
31947
- const wanted = normalizeShells(shells, platform).shells;
31948
- const locations = [...histFileLocation(wanted, env)];
31949
- for (const shell of wanted) {
31950
- if (shell === "powershell") locations.push(...powershellLocations(env, platform, homeDir));
31951
- if (shell === "fish") locations.push(...fishLocations(env, homeDir));
31952
- if (shell === "bash" || shell === "zsh") locations.push(...defaultHistFiles(shell, env, homeDir));
31953
- }
31954
- const seen = /* @__PURE__ */ new Set();
31955
- const deduped = [];
31956
- for (const location of locations) {
31957
- const key = locationKey(location);
31958
- if (seen.has(key)) continue;
31959
- seen.add(key);
31960
- deduped.push(location);
31961
- }
31962
- return deduped;
31963
- }
31964
- function matchesLocation(location, filename) {
31965
- if (typeof filename !== "string" || !filename) return false;
31966
- if (location?.name) return filename === location.name;
31967
- const suffix = location?.suffix;
31968
- if (!suffix) return false;
31969
- if (filename.length <= suffix.length) return false;
31970
- return filename.toLowerCase().endsWith(suffix.toLowerCase());
31971
- }
31972
- function createParseState() {
31973
- return {
31974
- pending: [],
31975
- pendingTs: null,
31976
- entry: null
31977
- };
31978
- }
31979
- function withinContinuationBounds(pending) {
31980
- if (pending.length >= 64) return false;
31981
- let chars = 0;
31982
- for (const line of pending) chars += line.length + 1;
31983
- return chars < MAX_COMMAND_CHARS;
31984
- }
31985
- function parsePowershell(lines, state) {
31986
- const commands = [];
31987
- let pending = state.pending;
31988
- for (const line of lines) {
31989
- const continued = line.endsWith("`");
31990
- pending = [...pending, continued ? line.slice(0, -1) : line];
31991
- if (continued && withinContinuationBounds(pending)) continue;
31992
- commands.push({
31993
- text: pending.join("\n"),
31994
- ts: null
31995
- });
31996
- pending = [];
31997
- }
31998
- return {
31999
- commands,
32000
- state: {
32001
- ...state,
32002
- pending
32003
- }
32004
- };
32005
- }
32006
- var FISH_ENTRY = /^- cmd:\s?([\s\S]*)$/;
32007
- var FISH_WHEN = /^\s+when:\s*(\d+)/;
32008
- function unescapeFish(text) {
32009
- let out = "";
32010
- for (let index = 0; index < text.length; index += 1) {
32011
- const char = text[index];
32012
- if (char !== "\\") {
32013
- out += char;
32014
- continue;
32015
- }
32016
- const next = text[index + 1];
32017
- if (next === "n") {
32018
- out += "\n";
32019
- index += 1;
32020
- continue;
32021
- }
32022
- if (next === "\\") {
32023
- out += "\\";
32024
- index += 1;
32025
- continue;
32026
- }
32027
- out += char;
32028
- }
32029
- return out;
32030
- }
32031
- function parseFish(lines, state) {
32032
- const commands = [];
32033
- let entry = state.entry;
32034
- for (const line of lines) {
32035
- const started = FISH_ENTRY.exec(line);
32036
- if (started) {
32037
- if (entry) commands.push(entry);
32038
- entry = {
32039
- text: unescapeFish(started[1]),
32040
- ts: null
32041
- };
32042
- continue;
32043
- }
32044
- if (!entry) continue;
32045
- const when = FISH_WHEN.exec(line);
32046
- if (!when) continue;
32047
- commands.push({
32048
- text: entry.text,
32049
- ts: Number(when[1]) * 1e3
32050
- });
32051
- entry = null;
32052
- }
32053
- return {
32054
- commands,
32055
- state: {
32056
- ...state,
32057
- entry
32058
- }
32059
- };
32060
- }
32061
- var BASH_TIMESTAMP = /^#(\d{9,})\s*$/;
32062
- function parseBash(lines, state) {
32063
- const commands = [];
32064
- let pendingTs = state.pendingTs;
32065
- for (const line of lines) {
32066
- const stamped = BASH_TIMESTAMP.exec(line);
32067
- if (stamped) {
32068
- pendingTs = Number(stamped[1]) * 1e3;
32069
- continue;
32070
- }
32071
- if (!line.trim()) continue;
32072
- commands.push({
32073
- text: line,
32074
- ts: pendingTs
32075
- });
32076
- pendingTs = null;
32077
- }
32078
- return {
32079
- commands,
32080
- state: {
32081
- ...state,
32082
- pendingTs
32083
- }
32084
- };
32085
- }
32086
- var ZSH_EXTENDED = /^:\s(\d+):(\d+);([\s\S]*)$/;
32087
- function parseZsh(lines, state) {
32088
- const commands = [];
32089
- let pending = state.pending;
32090
- let pendingTs = state.pendingTs;
32091
- for (const line of lines) {
32092
- const starting = pending.length === 0;
32093
- const extended = starting ? ZSH_EXTENDED.exec(line) : null;
32094
- if (starting) pendingTs = extended ? Number(extended[1]) * 1e3 : null;
32095
- const text = extended ? extended[3] : line;
32096
- const continued = text.endsWith("\\");
32097
- pending = [...pending, continued ? text.slice(0, -1) : text];
32098
- if (continued && withinContinuationBounds(pending)) continue;
32099
- const joined = pending.join("\n");
32100
- pending = [];
32101
- if (joined.trim()) commands.push({
32102
- text: joined,
32103
- ts: pendingTs
32104
- });
32105
- pendingTs = null;
32106
- }
32107
- return {
32108
- commands,
32109
- state: {
32110
- ...state,
32111
- pending,
32112
- pendingTs
32113
- }
32114
- };
32115
- }
32116
- var PARSERS = Object.freeze({
32117
- powershell: parsePowershell,
32118
- fish: parseFish,
32119
- bash: parseBash,
32120
- zsh: parseZsh
32121
- });
32122
- function parseHistoryLines({ shell, lines = [], state = null } = {}) {
32123
- const current = state || createParseState();
32124
- const parser = shell ? PARSERS[shell] : void 0;
32125
- if (!parser || !Array.isArray(lines) || lines.length === 0) return {
32126
- commands: [],
32127
- state: current
32128
- };
32129
- return parser(lines, current);
32130
- }
32131
- function foldForCompare(text) {
32132
- return String(text == null ? "" : text).replace(/\s+/g, " ").trim();
32133
- }
32134
- function isTrivialCommand(text) {
32135
- if (typeof text !== "string") return true;
32136
- return TRIVIAL_COMMANDS.has(text.trim().toLowerCase());
32137
- }
32138
- function decideCommandEvent({ shell = "", command = null, previous = null, now = 0 } = {}) {
32139
- const text = typeof command?.text === "string" ? command.text : "";
32140
- const compared = foldForCompare(text);
32141
- if (!compared) return {
32142
- event: null,
32143
- previous
32144
- };
32145
- if (isTrivialCommand(compared)) return {
32146
- event: null,
32147
- previous: compared
32148
- };
32149
- if (compared === previous) return {
32150
- event: null,
32151
- previous: compared
32152
- };
32153
- const stamped = Number(command?.ts);
32154
- const lineCount = text.split("\n").length;
32155
- return {
32156
- event: {
32157
- source: SOURCE$1,
32158
- kind: "command",
32159
- ts: Number.isFinite(stamped) && stamped > 0 ? Math.floor(stamped) : now,
32160
- scope: {
32161
- root: null,
32162
- sessionId: null
32163
- },
32164
- summary: `${shell}: ${text}`,
32165
- detail: lineCount > 1 ? {
32166
- shell,
32167
- lines: lineCount
32168
- } : { shell }
32169
- },
32170
- previous: compared
32171
- };
32172
- }
32173
- //#endregion
32174
32252
  //#region server/ingest-shell-history.ts
32175
32253
  var DEFAULT_POLL_MS = 2e3;
32176
32254
  var DEFAULT_DISCOVER_MS = 3e4;
@@ -34117,7 +34195,7 @@ async function containedPathForMissingFile(candidate, realRoot) {
34117
34195
  };
34118
34196
  }
34119
34197
  }
34120
- async function openContainedFile(candidate, root, allowsMissingFile = false) {
34198
+ async function openContainedFile(candidate, root, allowsMissingFile = false, opensForWriting = false) {
34121
34199
  let realRoot;
34122
34200
  try {
34123
34201
  realRoot = await fs.promises.realpath(root);
@@ -34144,7 +34222,8 @@ async function openContainedFile(candidate, root, allowsMissingFile = false) {
34144
34222
  };
34145
34223
  let handle = null;
34146
34224
  try {
34147
- handle = await fs.promises.open(realCandidate, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK);
34225
+ const accessFlag = opensForWriting ? fs.constants.O_RDWR : fs.constants.O_RDONLY;
34226
+ handle = await fs.promises.open(realCandidate, accessFlag | fs.constants.O_NONBLOCK);
34148
34227
  const stat = await handle.stat();
34149
34228
  if (stat.isFile()) return {
34150
34229
  ok: true,
@@ -47445,6 +47524,22 @@ function requestValidationErrorReply(msg, message) {
47445
47524
  requestId,
47446
47525
  ok: false,
47447
47526
  error: message
47527
+ }),
47528
+ "session-doc-read": () => ({
47529
+ type: "session-doc-response",
47530
+ requestId,
47531
+ id: typeof msg?.id === "string" ? msg.id : "",
47532
+ path: typeof msg?.path === "string" ? msg.path : "",
47533
+ ok: false,
47534
+ error: message
47535
+ }),
47536
+ "session-doc-save": () => ({
47537
+ type: "session-doc-save-result",
47538
+ requestId,
47539
+ id: typeof msg?.id === "string" ? msg.id : "",
47540
+ path: typeof msg?.path === "string" ? msg.path : "",
47541
+ ok: false,
47542
+ error: message
47448
47543
  })
47449
47544
  };
47450
47545
  const requestType = typeof msg?.type === "string" ? msg.type : "";
@@ -47475,7 +47570,7 @@ function parseSinceParam(url) {
47475
47570
  return Number.isFinite(n) ? n : null;
47476
47571
  }
47477
47572
  function registerControlHandlers(controlWss, deps) {
47478
- const { sessions, agentSessions = /* @__PURE__ */ new Map(), config, configStore, broadcastControl, generateProjectId, applyConfigReload, applySettingsReload, requestShutdown, requestRestart, handleClientFocus, buildHealthSnapshot, getUpdateStatus, getUpdateJournal = null, checkNow = null, applyUpdate = null, isStaging = null, noteRestartRequested = null, getPosthogStatus, posthogReportsDir = null, posthogSetIssueStatus = null, posthogArchiveInvestigation = 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, changeMapNarrator = null } = deps;
47573
+ const { sessions, agentSessions = /* @__PURE__ */ new Map(), config, configStore, broadcastControl, generateProjectId, applyConfigReload, applySettingsReload, requestShutdown, requestRestart, handleClientFocus, buildHealthSnapshot, getUpdateStatus, getUpdateJournal = null, checkNow = null, applyUpdate = null, isStaging = null, noteRestartRequested = null, getPosthogStatus, posthogReportsDir = null, posthogSetIssueStatus = null, posthogArchiveInvestigation = 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;
47479
47574
  const changeMaps = createChangeMapService({
47480
47575
  sessions,
47481
47576
  narrator: changeMapNarrator,
@@ -48801,6 +48896,58 @@ function registerControlHandlers(controlWss, deps) {
48801
48896
  message: refusal
48802
48897
  }));
48803
48898
  },
48899
+ "session-doc-read": async (message, ws) => {
48900
+ const reply = {
48901
+ type: "session-doc-response",
48902
+ requestId: message.requestId ?? null,
48903
+ id: message.id,
48904
+ path: message.path
48905
+ };
48906
+ const session = findSession(message);
48907
+ if (!sessionDocs || !session) return ws.send(JSON.stringify({
48908
+ ...reply,
48909
+ ok: false,
48910
+ error: "Session not found"
48911
+ }));
48912
+ try {
48913
+ ws.send(JSON.stringify({
48914
+ ...reply,
48915
+ ...await sessionDocs.read(session, message.path)
48916
+ }));
48917
+ } catch (error) {
48918
+ ws.send(JSON.stringify({
48919
+ ...reply,
48920
+ ok: false,
48921
+ error: `Read failed: ${errorMessage(error)}`
48922
+ }));
48923
+ }
48924
+ },
48925
+ "session-doc-save": async (message, ws) => {
48926
+ const reply = {
48927
+ type: "session-doc-save-result",
48928
+ requestId: message.requestId ?? null,
48929
+ id: message.id,
48930
+ path: message.path
48931
+ };
48932
+ const session = findSession(message);
48933
+ if (!sessionDocs || !session) return ws.send(JSON.stringify({
48934
+ ...reply,
48935
+ ok: false,
48936
+ error: "Session not found"
48937
+ }));
48938
+ try {
48939
+ ws.send(JSON.stringify({
48940
+ ...reply,
48941
+ ...await sessionDocs.save(session, message.path, message.text, message.baseHash)
48942
+ }));
48943
+ } catch (error) {
48944
+ ws.send(JSON.stringify({
48945
+ ...reply,
48946
+ ok: false,
48947
+ error: `Save failed: ${errorMessage(error)}`
48948
+ }));
48949
+ }
48950
+ },
48804
48951
  "shutdown": handleShutdown,
48805
48952
  "restart-server": handleRestart,
48806
48953
  "update-check": handleUpdateCheck,
@@ -48950,6 +49097,7 @@ function createBackendControl(dependencies) {
48950
49097
  readTracePage: dependencies.readTracePage,
48951
49098
  readPlanRevision: dependencies.readPlanRevision,
48952
49099
  decidePlanReview: dependencies.decidePlanReview,
49100
+ sessionDocs: dependencies.sessionDocs,
48953
49101
  changeMapNarrator: dependencies.changeMapNarrator
48954
49102
  });
48955
49103
  const sendLaneSnapshotOnConnect = (laneName, readLane, refuseRemote) => {
@@ -48967,6 +49115,7 @@ function createBackendControl(dependencies) {
48967
49115
  };
48968
49116
  sendLaneSnapshotOnConnect("visions", laneAssembly.currentVisions, false);
48969
49117
  sendLaneSnapshotOnConnect("ingest", laneAssembly.currentIngest, true);
49118
+ sendLaneSnapshotOnConnect("session-docs", () => dependencies.sessionDocs, false);
48970
49119
  }
48971
49120
  //#endregion
48972
49121
  //#region server/core/update-apply-core.ts
@@ -50706,9 +50855,6 @@ async function sharedFoldersOf({ checkout, gitDir }, worktreeShare) {
50706
50855
  const sharedEntries = worktreeShare.filter((entry) => !UNSHARED_WORKTREE_ENTRIES.includes(entry));
50707
50856
  return (await Promise.all(sharedEntries.map((entry) => sharedFolderBehind(path.join(checkout, entry), path.resolve(path.join(repositoryRoot, entry)))))).filter((sharedFolder) => sharedFolder !== null);
50708
50857
  }
50709
- async function existingRealPaths(candidates) {
50710
- return (await Promise.all(candidates.filter((candidate) => path.isAbsolute(candidate)).map(existingRealPath))).filter((realPath) => realPath !== null);
50711
- }
50712
50858
  function createSessionSandbox({ getSettings, uploadsRoot, sandboxRefusal = allowSandboxedSpawn, prepareSandbox = prepareSandboxLaunch, trustClaudeFolder, platform = process.platform, homeDir = os.homedir(), env = process.env, readGithubToken = () => readGithubCliToken(env), log = console }) {
50713
50859
  const expandHome = (candidates) => candidates.map((candidate) => expandTilde(candidate.trim(), env, homeDir)).filter(Boolean);
50714
50860
  const githubCliConfigDir = () => rawTextOr(env.GH_CONFIG_DIR, null) ?? path.join(rawTextOr(env.XDG_CONFIG_HOME, null) ?? path.join(homeDir, ".config"), "gh");
@@ -50728,7 +50874,7 @@ function createSessionSandbox({ getSettings, uploadsRoot, sandboxRefusal = allow
50728
50874
  writableRoots: [
50729
50875
  ...startFolderRoots,
50730
50876
  ...sharedFolders,
50731
- ...await existingRealPaths(expandHome(settings.writablePaths))
50877
+ ...await existingRealPaths([...expandHome(settings.writablePaths), ...facts.writablePaths])
50732
50878
  ],
50733
50879
  readDenied: expandHome(settings.unreadablePaths).filter((unreadablePath) => path.isAbsolute(unreadablePath)),
50734
50880
  readablePaths: [...facts.readablePaths, ...await existingRealPaths([sessionUploads, githubCliConfigDir()])]
@@ -51811,6 +51957,281 @@ function createTelemetry(options) {
51811
51957
  };
51812
51958
  }
51813
51959
  //#endregion
51960
+ //#region server/core/session-docs-core.ts
51961
+ var NUL_CHARACTER = String.fromCharCode(0);
51962
+ var DOC_REFUSAL_COPY = Object.freeze({
51963
+ missing: "no file exists at that path, so write it first and surface it again",
51964
+ unreadable: "the file cannot be read",
51965
+ "outside-root": "the path is outside this session's checkout",
51966
+ "not-a-regular-file": "the path is not a regular file",
51967
+ "root-unresolvable": "this session's checkout cannot be resolved",
51968
+ "too-large": `the file is over ${SESSION_DOC_BODY_CAP_BYTES / 1024} KiB`,
51969
+ binary: "the file is not UTF-8 text",
51970
+ "not-surfaced": "that file was not surfaced by this session",
51971
+ "changed-on-disk": "the file changed on disk since it was opened, so reload it before saving",
51972
+ "hard-linked": "the file has other hard links, so it may live outside this session's checkout"
51973
+ });
51974
+ function describeDocRefusal(reason) {
51975
+ return DOC_REFUSAL_COPY[reason];
51976
+ }
51977
+ function hashDocBytes(bytes) {
51978
+ return crypto.createHash("sha256").update(bytes).digest("hex");
51979
+ }
51980
+ function countOccurrences(text, needle) {
51981
+ return text.split(needle).length - 1;
51982
+ }
51983
+ function decodeDocBytes(bytes) {
51984
+ if (bytes.byteLength > 262144) return {
51985
+ ok: false,
51986
+ reason: "too-large"
51987
+ };
51988
+ let text;
51989
+ try {
51990
+ text = new TextDecoder("utf-8", {
51991
+ fatal: true,
51992
+ ignoreBOM: true
51993
+ }).decode(bytes);
51994
+ } catch {
51995
+ return {
51996
+ ok: false,
51997
+ reason: "binary"
51998
+ };
51999
+ }
52000
+ if (text.includes(NUL_CHARACTER)) return {
52001
+ ok: false,
52002
+ reason: "binary"
52003
+ };
52004
+ const crlfCount = countOccurrences(text, "\r\n");
52005
+ const usesCrlf = crlfCount > 0 && crlfCount === countOccurrences(text, "\n");
52006
+ if (!usesCrlf) return {
52007
+ ok: true,
52008
+ text,
52009
+ usesCrlf
52010
+ };
52011
+ return {
52012
+ ok: true,
52013
+ text: text.replaceAll("\r\n", "\n"),
52014
+ usesCrlf
52015
+ };
52016
+ }
52017
+ function encodeDocText(text, usesCrlf) {
52018
+ if (text.includes(NUL_CHARACTER)) return {
52019
+ ok: false,
52020
+ reason: "binary"
52021
+ };
52022
+ const lineFeedText = text.replaceAll("\r\n", "\n");
52023
+ const content = usesCrlf ? lineFeedText.replaceAll("\n", "\r\n") : lineFeedText;
52024
+ if (Buffer.byteLength(content, "utf8") > 262144) return {
52025
+ ok: false,
52026
+ reason: "too-large"
52027
+ };
52028
+ return {
52029
+ ok: true,
52030
+ content
52031
+ };
52032
+ }
52033
+ function withSurfacedDoc(docs, surfaced, maxDocs) {
52034
+ return [surfaced, ...docs.filter((doc) => doc.path !== surfaced.path)].slice(0, maxDocs);
52035
+ }
52036
+ //#endregion
52037
+ //#region server/session-docs-wiring.ts
52038
+ async function readAtMostCapPlusOneByte(handle) {
52039
+ const buffer = Buffer.alloc(SESSION_DOC_BODY_CAP_BYTES + 1);
52040
+ let filledBytes = 0;
52041
+ while (filledBytes < buffer.byteLength) {
52042
+ const { bytesRead } = await handle.read(buffer, filledBytes, buffer.byteLength - filledBytes, filledBytes);
52043
+ if (bytesRead === 0) break;
52044
+ filledBytes += bytesRead;
52045
+ }
52046
+ return buffer.subarray(0, filledBytes);
52047
+ }
52048
+ async function readOpenedDoc(file) {
52049
+ if (file.stat.size > 262144) return {
52050
+ ok: false,
52051
+ reason: "too-large"
52052
+ };
52053
+ try {
52054
+ return {
52055
+ ok: true,
52056
+ loaded: {
52057
+ file,
52058
+ bytes: await readAtMostCapPlusOneByte(file.handle)
52059
+ }
52060
+ };
52061
+ } catch {
52062
+ return {
52063
+ ok: false,
52064
+ reason: "unreadable"
52065
+ };
52066
+ }
52067
+ }
52068
+ async function refusalForOpenedInode(file, root) {
52069
+ if (file.stat.nlink > 1) return "hard-linked";
52070
+ const kernelPath = await fs.promises.readlink(`/proc/self/fd/${file.handle.fd}`).catch(() => null);
52071
+ if (kernelPath === null) return null;
52072
+ const realRoot = await fs.promises.realpath(root).catch(() => null);
52073
+ if (realRoot === null) return "root-unresolvable";
52074
+ if (!isPathInside(realRoot, kernelPath, { allowEqual: false })) return "outside-root";
52075
+ return null;
52076
+ }
52077
+ async function openVerifiedDoc(candidate, root, opensForWriting) {
52078
+ const opened = opensForWriting ? await openContainedFile(candidate, root, false, true) : await openContainedFile(candidate, root);
52079
+ if (!opened.ok) return opened;
52080
+ const refusal = await refusalForOpenedInode(opened.file, root);
52081
+ if (refusal === null) return opened;
52082
+ await opened.file.handle.close().catch(() => {});
52083
+ return {
52084
+ ok: false,
52085
+ reason: refusal
52086
+ };
52087
+ }
52088
+ async function loadContainedDoc(candidate, root) {
52089
+ const opened = await openVerifiedDoc(candidate, root, false);
52090
+ if (!opened.ok) return opened;
52091
+ try {
52092
+ return await readOpenedDoc(opened.file);
52093
+ } finally {
52094
+ await opened.file.handle.close().catch(() => {});
52095
+ }
52096
+ }
52097
+ async function overwriteThroughHandle(handle, bytes) {
52098
+ let writtenBytes = 0;
52099
+ while (writtenBytes < bytes.byteLength) {
52100
+ const { bytesWritten } = await handle.write(bytes, writtenBytes, bytes.byteLength - writtenBytes, writtenBytes);
52101
+ writtenBytes += bytesWritten;
52102
+ }
52103
+ await handle.truncate(bytes.byteLength);
52104
+ await handle.sync();
52105
+ }
52106
+ async function overwriteIfUnchanged(file, text, baseHash) {
52107
+ const outcome = await readOpenedDoc(file);
52108
+ if (!outcome.ok) return {
52109
+ ok: false,
52110
+ error: describeDocRefusal(outcome.reason),
52111
+ conflict: false
52112
+ };
52113
+ if (hashDocBytes(outcome.loaded.bytes) !== baseHash) return {
52114
+ ok: false,
52115
+ error: describeDocRefusal("changed-on-disk"),
52116
+ conflict: true
52117
+ };
52118
+ const current = decodeDocBytes(outcome.loaded.bytes);
52119
+ const encoded = encodeDocText(text, current.ok && current.usesCrlf);
52120
+ if (!encoded.ok) return {
52121
+ ok: false,
52122
+ error: describeDocRefusal(encoded.reason),
52123
+ conflict: false
52124
+ };
52125
+ const encodedBytes = Buffer.from(encoded.content, "utf8");
52126
+ await overwriteThroughHandle(file.handle, encodedBytes);
52127
+ return {
52128
+ ok: true,
52129
+ hash: hashDocBytes(encodedBytes)
52130
+ };
52131
+ }
52132
+ async function nameWithinRoot(root, realPath) {
52133
+ const realRoot = await fs.promises.realpath(root).catch(() => root);
52134
+ return path.relative(realRoot, realPath);
52135
+ }
52136
+ function createSessionDocsWiring({ broadcastControl, now = Date.now }) {
52137
+ const docsBySessionId = /* @__PURE__ */ new Map();
52138
+ const saveQueue = createKeyedSerialQueue();
52139
+ function broadcastDocs(sessionId) {
52140
+ broadcastControl({
52141
+ type: "session-docs-changed",
52142
+ id: sessionId,
52143
+ docs: docsBySessionId.get(sessionId) ?? []
52144
+ });
52145
+ }
52146
+ function forgetSession(sessionId) {
52147
+ if (!docsBySessionId.delete(sessionId)) return;
52148
+ broadcastDocs(sessionId);
52149
+ }
52150
+ function isSurfaced(session, docPath) {
52151
+ return (docsBySessionId.get(session.id) ?? []).some((doc) => doc.path === docPath);
52152
+ }
52153
+ async function surface(session, request) {
52154
+ const root = session.effectiveCwd();
52155
+ const outcome = await loadContainedDoc(path.resolve(root, request.path), root);
52156
+ if (!outcome.ok) return {
52157
+ ok: false,
52158
+ error: describeDocRefusal(outcome.reason)
52159
+ };
52160
+ const decoded = decodeDocBytes(outcome.loaded.bytes);
52161
+ if (!decoded.ok) return {
52162
+ ok: false,
52163
+ error: describeDocRefusal(decoded.reason)
52164
+ };
52165
+ const realPath = outcome.loaded.file.realPath;
52166
+ const doc = {
52167
+ path: realPath,
52168
+ name: await nameWithinRoot(root, realPath),
52169
+ surfacedAt: now()
52170
+ };
52171
+ if (!docsBySessionId.has(session.id)) session.once("teardown", () => forgetSession(session.id));
52172
+ docsBySessionId.set(session.id, withSurfacedDoc(docsBySessionId.get(session.id) ?? [], doc, 20));
52173
+ broadcastDocs(session.id);
52174
+ return {
52175
+ ok: true,
52176
+ doc
52177
+ };
52178
+ }
52179
+ async function read(session, docPath) {
52180
+ if (!isSurfaced(session, docPath)) return {
52181
+ ok: false,
52182
+ error: describeDocRefusal("not-surfaced")
52183
+ };
52184
+ const outcome = await loadContainedDoc(docPath, session.effectiveCwd());
52185
+ if (!outcome.ok) return {
52186
+ ok: false,
52187
+ error: describeDocRefusal(outcome.reason)
52188
+ };
52189
+ const decoded = decodeDocBytes(outcome.loaded.bytes);
52190
+ if (!decoded.ok) return {
52191
+ ok: false,
52192
+ error: describeDocRefusal(decoded.reason)
52193
+ };
52194
+ return {
52195
+ ok: true,
52196
+ text: decoded.text,
52197
+ hash: hashDocBytes(outcome.loaded.bytes)
52198
+ };
52199
+ }
52200
+ async function writeIfUnchanged(session, docPath, text, baseHash) {
52201
+ const opened = await openVerifiedDoc(docPath, session.effectiveCwd(), true);
52202
+ if (!opened.ok) return {
52203
+ ok: false,
52204
+ error: describeDocRefusal(opened.reason),
52205
+ conflict: false
52206
+ };
52207
+ try {
52208
+ return await overwriteIfUnchanged(opened.file, text, baseHash);
52209
+ } finally {
52210
+ await opened.file.handle.close().catch(() => {});
52211
+ }
52212
+ }
52213
+ async function save(session, docPath, text, baseHash) {
52214
+ if (!isSurfaced(session, docPath)) return {
52215
+ ok: false,
52216
+ error: describeDocRefusal("not-surfaced"),
52217
+ conflict: false
52218
+ };
52219
+ return saveQueue.run(docPath, () => writeIfUnchanged(session, docPath, text, baseHash));
52220
+ }
52221
+ function snapshotMessage() {
52222
+ return {
52223
+ type: "session-docs-snapshot",
52224
+ sessions: Object.fromEntries(docsBySessionId)
52225
+ };
52226
+ }
52227
+ return {
52228
+ surface,
52229
+ read,
52230
+ save,
52231
+ snapshotMessage
52232
+ };
52233
+ }
52234
+ //#endregion
51814
52235
  //#region server/backend.ts
51815
52236
  var AGENT_INGRESS_HOST = "127.0.0.1";
51816
52237
  function createBackend(httpServer, options = {}) {
@@ -51916,6 +52337,7 @@ function createBackend(httpServer, options = {}) {
51916
52337
  });
51917
52338
  const { controlWss, dataWss, controlReplayLog, sessionDataClients, broadcastControl: broadcastControlFromWebSockets, broadcastLocalControl, closeSessionDataClients } = webSockets;
51918
52339
  broadcastControl = broadcastControlFromWebSockets;
52340
+ const sessionDocs = createSessionDocsWiring({ broadcastControl: broadcastControlFromWebSockets });
51919
52341
  const sessions = /* @__PURE__ */ new Map();
51920
52342
  const agentSessions = /* @__PURE__ */ new Map();
51921
52343
  const { buildHealthSnapshot, healthInterval } = createBackendHealth({
@@ -51975,6 +52397,7 @@ function createBackend(httpServer, options = {}) {
51975
52397
  broadcastControl,
51976
52398
  spawnGate,
51977
52399
  recordLane,
52400
+ sessionDocs,
51978
52401
  logger: console
51979
52402
  });
51980
52403
  const wireSessionEvents = createSessionEventWiring({
@@ -52157,6 +52580,7 @@ function createBackend(httpServer, options = {}) {
52157
52580
  readTracePage: traceWiring?.readTracePage ?? null,
52158
52581
  readPlanRevision: laneAssembly.planReview?.readPlanRevision ?? null,
52159
52582
  decidePlanReview: laneAssembly.planReview?.decide ?? null,
52583
+ sessionDocs,
52160
52584
  changeMapNarrator: laneAssembly.changeMapNarrator,
52161
52585
  serverBuild: () => serverBuild,
52162
52586
  telemetry,