@p4code/cli 0.1.9 → 0.1.10

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 (85) hide show
  1. package/dist/bin.mjs +1148 -999
  2. package/dist/client/assets/{DiffPanel-Bb72saoS.js → DiffPanel-BRroAgOK.js} +2 -2
  3. package/dist/client/assets/{FilePreviewPanel-6mJ-Vsub.js → FilePreviewPanel-Bdd2Ml_z.js} +2 -2
  4. package/dist/client/assets/{PreviewPanel-ogPpU8Ev.js → PreviewPanel-DoJd5FXR.js} +2 -2
  5. package/dist/client/assets/{angular-html-BIzUKsg7.js → angular-html-BA0WzHZ0.js} +2 -2
  6. package/dist/client/assets/{angular-ts-CSIA-4Zf.js → angular-ts-BehluKB7.js} +2 -2
  7. package/dist/client/assets/{apl-Du1uuPDy.js → apl-DPnuuIo7.js} +2 -2
  8. package/dist/client/assets/arrow-right-B_11zHBP.js +2 -0
  9. package/dist/client/assets/{astro-bVpCfC8I.js → astro-G6Zz-WSX.js} +2 -2
  10. package/dist/client/assets/{blade-KOd_hdA5.js → blade-c1NgE6B9.js} +2 -2
  11. package/dist/client/assets/{c-BUOUjYKH.js → c-BbDaxzL5.js} +2 -2
  12. package/dist/client/assets/{cobol-DVr6VPEj.js → cobol-DPWRf7_s.js} +2 -2
  13. package/dist/client/assets/{coffee-CaWTiLWu.js → coffee-kZI3-HKr.js} +2 -2
  14. package/dist/client/assets/{cpp-DOVxTtfq.js → cpp-DbB1uwNF.js} +2 -2
  15. package/dist/client/assets/{crystal-D_Q817Qu.js → crystal-C9rif4zq.js} +2 -2
  16. package/dist/client/assets/{css-Rez0Ry86.js → css-BzXrG2ii.js} +2 -2
  17. package/dist/client/assets/{edge-C5CKZJyo.js → edge-CIqg8jED.js} +2 -2
  18. package/dist/client/assets/{elixir-D_eVXxgH.js → elixir-L1xpfUlV.js} +2 -2
  19. package/dist/client/assets/{elm-BLkMkUJ0.js → elm-C4bqF3BU.js} +2 -2
  20. package/dist/client/assets/{erb-DGG0CxNR.js → erb-DQxpBRS7.js} +2 -2
  21. package/dist/client/assets/{fileCommentAnnotations-D0gz9Nvl.js → fileCommentAnnotations-DXLRihuo.js} +2 -2
  22. package/dist/client/assets/{git-rebase-D7H5ArnN.js → git-rebase-DCFBvSos.js} +2 -2
  23. package/dist/client/assets/{glimmer-js-DOuFsyYy.js → glimmer-js-CMcw0KDm.js} +2 -2
  24. package/dist/client/assets/{glimmer-ts-C_dgMrVH.js → glimmer-ts-zKGsihEs.js} +2 -2
  25. package/dist/client/assets/{glsl-BJU0wvLz.js → glsl-C-DKYe1o.js} +2 -2
  26. package/dist/client/assets/{graphql-CXUg7dVm.js → graphql-D-Jaf-It.js} +2 -2
  27. package/dist/client/assets/{hack-DEwTpBBH.js → hack-Cy1Ek-eJ.js} +2 -2
  28. package/dist/client/assets/{haml-Br-Q1jfj.js → haml-DSBhTnP_.js} +2 -2
  29. package/dist/client/assets/{handlebars-U-qCMjLv.js → handlebars-BTjhBiVA.js} +2 -2
  30. package/dist/client/assets/{html-CBhkruiu.js → html-CuFf_rJP.js} +2 -2
  31. package/dist/client/assets/{html-derivative-CtOWonuc.js → html-derivative-B6vKETtb.js} +2 -2
  32. package/dist/client/assets/{http-B-anb7K2.js → http-TaTxS49l.js} +2 -2
  33. package/dist/client/assets/{hurl-UbSvk6zv.js → hurl-Jwth1u4b.js} +2 -2
  34. package/dist/client/assets/{index-CADZUNMb.js → index-WQyWsxuV.js} +200 -204
  35. package/dist/client/assets/index-nkPMSYxq.css +1 -0
  36. package/dist/client/assets/{java-d4Ae9Kq8.js → java-Ba5MdLVF.js} +2 -2
  37. package/dist/client/assets/{javascript-DENutVTd.js → javascript-_mHojD6_.js} +2 -2
  38. package/dist/client/assets/{jinja-Bugh_6jh.js → jinja-BhX9YpO0.js} +2 -2
  39. package/dist/client/assets/{jison-5v-Z1oHG.js → jison-S0Rrxw4o.js} +2 -2
  40. package/dist/client/assets/{json-BhFmIQh2.js → json-B-G4K1LM.js} +2 -2
  41. package/dist/client/assets/{jsx-ByAWu7n0.js → jsx-X6TBdO7T.js} +2 -2
  42. package/dist/client/assets/{julia-DE4JKJ_I.js → julia-He6A0R8c.js} +2 -2
  43. package/dist/client/assets/{just-Ck-bQzBW.js → just-CQ533yYC.js} +2 -2
  44. package/dist/client/assets/{latex-0aopGn_u.js → latex-BBBRtbj7.js} +2 -2
  45. package/dist/client/assets/{liquid-BOV6YY-4.js → liquid-CA9B-Q_J.js} +2 -2
  46. package/dist/client/assets/{lua-BS79SG5v.js → lua-DXLYC8yx.js} +2 -2
  47. package/dist/client/assets/{marko-CxCHVwvS.js → marko-BXfllCK7.js} +2 -2
  48. package/dist/client/assets/{mdc-D8ArMX_3.js → mdc-D13Xs_kf.js} +2 -2
  49. package/dist/client/assets/{nginx-54lPKP4l.js → nginx-HcisF7-i.js} +2 -2
  50. package/dist/client/assets/{nim-CQW1n-W6.js → nim-YbKRMFDo.js} +2 -2
  51. package/dist/client/assets/{perl-TGEf9BSy.js → perl-CV978rzN.js} +2 -2
  52. package/dist/client/assets/{php-CRSs0ljw.js → php-B8Rmr1r4.js} +2 -2
  53. package/dist/client/assets/{pug-B94qB-T3.js → pug-CI1Ka8om.js} +2 -2
  54. package/dist/client/assets/{qml-Ce-jpaqY.js → qml-CyQLZsMJ.js} +2 -2
  55. package/dist/client/assets/{r-Di-hq60i.js → r-C-0JMTYO.js} +2 -2
  56. package/dist/client/assets/{razor-VXC_c_VY.js → razor-BCppTDFk.js} +2 -2
  57. package/dist/client/assets/{regexp-DCuWLst8.js → regexp-DWt_neai.js} +2 -2
  58. package/dist/client/assets/{rst-CaleKCxt.js → rst-CDLeyhcr.js} +2 -2
  59. package/dist/client/assets/{ruby-BNvjj0Xx.js → ruby-Bq-7Grqk.js} +2 -2
  60. package/dist/client/assets/{sas-CN3Jyhxd.js → sas-Cn10Nqsd.js} +2 -2
  61. package/dist/client/assets/{scss-iGZoIRyN.js → scss-821PYndD.js} +2 -2
  62. package/dist/client/assets/{shellscript-B3Nk58br.js → shellscript-CGnWrTWh.js} +2 -2
  63. package/dist/client/assets/{shellsession-aV0EL5Me.js → shellsession-SoJX0uTA.js} +2 -2
  64. package/dist/client/assets/{soy-CK-MnjGK.js → soy-BDpemwx6.js} +2 -2
  65. package/dist/client/assets/{sql-WiqY-pg6.js → sql-BAre1tMG.js} +2 -2
  66. package/dist/client/assets/{stata-L2KCRIsj.js → stata-BVoUBbRH.js} +2 -2
  67. package/dist/client/assets/{surrealql-y8rFKpHp.js → surrealql-77kldaFv.js} +2 -2
  68. package/dist/client/assets/{svelte-BUjzGJjC.js → svelte-BrVa4Wrm.js} +2 -2
  69. package/dist/client/assets/{templ-Lwa6_2Pi.js → templ-DD-yE4Ei.js} +2 -2
  70. package/dist/client/assets/{tex-C6_2o8N6.js → tex-3Cd89BRS.js} +2 -2
  71. package/dist/client/assets/{textarea-C26NLrE0.js → textarea-DJbYd4DI.js} +2 -2
  72. package/dist/client/assets/{ts-tags-DHGXrdlY.js → ts-tags-CqnWMU9s.js} +2 -2
  73. package/dist/client/assets/{tsx-D7a2s_rX.js → tsx-C56Uu7is.js} +2 -2
  74. package/dist/client/assets/{twig-rodlH6R_.js → twig-2EWf2LgK.js} +2 -2
  75. package/dist/client/assets/{typescript-BSVnlc3E.js → typescript-DoXLYiyl.js} +2 -2
  76. package/dist/client/assets/{vue-CPqhBG4f.js → vue-DIv66ZbN.js} +2 -2
  77. package/dist/client/assets/{vue-html-DlcYUp9j.js → vue-html-DJnU3oci.js} +2 -2
  78. package/dist/client/assets/{vue-vine-C33vB5eU.js → vue-vine-CnFyQ4Cm.js} +2 -2
  79. package/dist/client/assets/{xml-Dk7HbObH.js → xml-DflQf7I-.js} +2 -2
  80. package/dist/client/assets/{xsl-Ce_0ufBe.js → xsl-DegV0fmP.js} +2 -2
  81. package/dist/client/assets/{yaml-hWCzaJrB.js → yaml-CQI6ctBx.js} +2 -2
  82. package/dist/client/index.html +3 -3
  83. package/package.json +1 -1
  84. package/dist/client/assets/arrow-right-qNd2lFlZ.js +0 -2
  85. package/dist/client/assets/index-CPnR_Tju.css +0 -1
package/dist/bin.mjs CHANGED
@@ -233,7 +233,7 @@ const make$73 = () => {
233
233
  const layer$69 = Layer.sync(NetService, make$73);
234
234
  //#endregion
235
235
  //#region package.json
236
- var version = "0.1.9";
236
+ var version = "0.1.10";
237
237
  //#endregion
238
238
  //#region src/config.ts
239
239
  /**
@@ -5557,8 +5557,6 @@ const ClientSettingsSchema = Schema$1.Struct({
5557
5557
  sidebarProjectSortOrder: SidebarProjectSortOrder.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_PROJECT_SORT_ORDER))),
5558
5558
  sidebarThreadSortOrder: SidebarThreadSortOrder.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_SORT_ORDER))),
5559
5559
  sidebarThreadPreviewCount: SidebarThreadPreviewCount.pipe(Schema$1.withDecodingDefault(Effect.succeed(6))),
5560
- sidebarV2Enabled: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false))),
5561
- sidebarV2ConfiguredByUser: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false))),
5562
5560
  timestampFormat: TimestampFormat.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_TIMESTAMP_FORMAT))),
5563
5561
  wordWrap: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true)))
5564
5562
  });
@@ -5905,8 +5903,6 @@ Schema$1.Struct({
5905
5903
  sidebarProjectSortOrder: Schema$1.optionalKey(SidebarProjectSortOrder),
5906
5904
  sidebarThreadSortOrder: Schema$1.optionalKey(SidebarThreadSortOrder),
5907
5905
  sidebarThreadPreviewCount: Schema$1.optionalKey(SidebarThreadPreviewCount),
5908
- sidebarV2Enabled: Schema$1.optionalKey(Schema$1.Boolean),
5909
- sidebarV2ConfiguredByUser: Schema$1.optionalKey(Schema$1.Boolean),
5910
5906
  timestampFormat: Schema$1.optionalKey(TimestampFormat),
5911
5907
  wordWrap: Schema$1.optionalKey(Schema$1.Boolean)
5912
5908
  });
@@ -28990,227 +28986,873 @@ const staticAndDevRouteLayer = HttpRouter.add("GET", "*", Effect.gen(function* (
28990
28986
  contentType
28991
28987
  });
28992
28988
  }));
28993
- //#endregion
28994
- //#region src/checkpointing/Errors.ts
28995
- const CheckpointDiffOperation = Schema$1.Literals(["CheckpointDiffQuery.getTurnDiff", "CheckpointDiffQuery.getFullThreadDiff"]);
28996
- /** The computed result does not satisfy the checkpoint RPC contract. */
28997
- var CheckpointDiffResultInvalidError = class extends Schema$1.TaggedErrorClass()("CheckpointDiffResultInvalidError", {
28998
- operation: CheckpointDiffOperation,
28999
- threadId: ThreadId
29000
- }) {
29001
- get message() {
29002
- const result = this.operation === "CheckpointDiffQuery.getTurnDiff" ? "turn diff" : "full thread diff";
29003
- return `Checkpoint invariant violation in ${this.operation}: Computed ${result} result does not satisfy contract schema.`;
29004
- }
29005
- };
29006
- /** Projection state no longer contains the requested checkpoint thread. */
29007
- var CheckpointThreadNotFoundError = class extends Schema$1.TaggedErrorClass()("CheckpointThreadNotFoundError", {
29008
- operation: CheckpointDiffOperation,
29009
- threadId: ThreadId
29010
- }) {
29011
- get message() {
29012
- return `Checkpoint invariant violation in ${this.operation}: Thread '${this.threadId}' not found.`;
29013
- }
29014
- };
29015
- /** The checkpoint thread has no workspace path from which to compute a diff. */
29016
- var CheckpointWorkspacePathMissingError = class extends Schema$1.TaggedErrorClass()("CheckpointWorkspacePathMissingError", {
29017
- operation: CheckpointDiffOperation,
29018
- threadId: ThreadId
29019
- }) {
29020
- get message() {
29021
- const diff = this.operation === "CheckpointDiffQuery.getTurnDiff" ? "turn diff" : "full thread diff";
29022
- return `Checkpoint invariant violation in ${this.operation}: Workspace path missing for thread '${this.threadId}' when computing ${diff}.`;
29023
- }
29024
- };
29025
- /** The requested turn lies beyond the latest available checkpoint. */
29026
- var CheckpointTurnRangeUnavailableError = class extends Schema$1.TaggedErrorClass()("CheckpointTurnRangeUnavailableError", {
29027
- operation: CheckpointDiffOperation,
29028
- threadId: ThreadId,
29029
- requestedTurnCount: NonNegativeInt,
29030
- availableTurnCount: NonNegativeInt
29031
- }) {
29032
- get message() {
29033
- return `Checkpoint unavailable for thread ${this.threadId} turn ${this.requestedTurnCount}: Turn diff range exceeds current turn count: requested ${this.requestedTurnCount}, current ${this.availableTurnCount}.`;
28989
+ (() => {
28990
+ try {
28991
+ return process.env.NODE_ENV === "development";
28992
+ } catch {
28993
+ return false;
29034
28994
  }
29035
- };
29036
- /** Expected checkpoint metadata does not contain the requested Git ref. */
29037
- var CheckpointRefUnavailableError = class extends Schema$1.TaggedErrorClass()("CheckpointRefUnavailableError", {
29038
- operation: CheckpointDiffOperation,
29039
- threadId: ThreadId,
29040
- turnCount: NonNegativeInt,
29041
- checkpoint: Schema$1.Literals(["from", "to"])
29042
- }) {
29043
- get message() {
29044
- return `Checkpoint unavailable for thread ${this.threadId} turn ${this.turnCount}: Checkpoint ref is unavailable for turn ${this.turnCount}.`;
28995
+ })();
28996
+ const COMMIT_METADATA_SPLIT = /(?=^From [a-f0-9]+ .+$)/m;
28997
+ const GIT_DIFF_FILE_BREAK_REGEX = /(?=^diff --git)/gm;
28998
+ const FILENAME_HEADER_REGEX = /^(---|\+\+\+)\s+([^\t\r\n]+)/;
28999
+ const FILENAME_HEADER_REGEX_GIT = /^(---|\+\+\+)\s+[ab]\/([^\t\r\n]+)/;
29000
+ const ALTERNATE_FILE_NAMES_GIT = /^diff --git (?:"a\/(.+?)"|a\/(.+?)) (?:"b\/(.+?)"|b\/(.+?))$/;
29001
+ const INDEX_LINE_METADATA = /^index ([0-9a-f]+)\.\.([0-9a-f]+)(?: (\d+))?$/i;
29002
+ Object.freeze({
29003
+ fromStart: 0,
29004
+ fromEnd: 0
29005
+ });
29006
+ //#endregion
29007
+ //#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/cleanLastNewline.js
29008
+ function cleanLastNewline(contents) {
29009
+ let end = contents.length;
29010
+ if (contents.charCodeAt(end - 1) === 10) {
29011
+ end--;
29012
+ if (contents.charCodeAt(end - 1) === 13) end--;
29045
29013
  }
29046
- };
29014
+ return contents.slice(0, end);
29015
+ }
29047
29016
  //#endregion
29048
- //#region src/checkpointing/Utils.ts
29049
- const CHECKPOINT_REFS_PREFIX = "refs/p4/checkpoints";
29050
- function checkpointRefForThreadTurn(threadId, turnCount) {
29051
- return CheckpointRef.make(`${CHECKPOINT_REFS_PREFIX}/${Encoding.encodeBase64Url(threadId)}/turn/${turnCount}`);
29017
+ //#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/detachString.js
29018
+ const stringDetachEncoder = new TextEncoder();
29019
+ const stringDetachDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
29020
+ const SURROGATE_CODE_UNIT_PATTERN = /[\uD800-\uDFFF]/;
29021
+ const STRING_DETACH_INITIAL_BUFFER_SIZE = 1024;
29022
+ let stringDetachBuffer = new Uint8Array(STRING_DETACH_INITIAL_BUFFER_SIZE);
29023
+ function releaseStringDetachBuffer() {
29024
+ if (stringDetachBuffer.length !== STRING_DETACH_INITIAL_BUFFER_SIZE) stringDetachBuffer = new Uint8Array(STRING_DETACH_INITIAL_BUFFER_SIZE);
29052
29025
  }
29053
- function resolveThreadWorkspaceCwd(input) {
29054
- const worktreeCwd = input.thread.worktreePath ?? void 0;
29055
- if (worktreeCwd) return worktreeCwd;
29056
- return input.projects.find((project) => project.id === input.thread.projectId)?.workspaceRoot;
29026
+ function detachString(value) {
29027
+ if (value.length === 0) return value;
29028
+ if (SURROGATE_CODE_UNIT_PATTERN.test(value)) return JSON.parse(JSON.stringify(value));
29029
+ const requiredByteLength = value.length * 3;
29030
+ if (stringDetachBuffer.length < requiredByteLength) stringDetachBuffer = new Uint8Array(requiredByteLength);
29031
+ const { written } = stringDetachEncoder.encodeInto(value, stringDetachBuffer);
29032
+ return stringDetachDecoder.decode(stringDetachBuffer.subarray(0, written));
29057
29033
  }
29058
29034
  //#endregion
29059
- //#region src/git/remoteRefs.ts
29060
- function parseRemoteNamesInGitOrder(stdout) {
29061
- const remoteNames = [];
29062
- for (const line of stdout.split("\n")) {
29063
- const remoteName = line.trim();
29064
- if (remoteName.length > 0) remoteNames.push(remoteName);
29035
+ //#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/parsePatchFiles.js
29036
+ function processPatch(data, cacheKeyPrefix, throwOnError) {
29037
+ try {
29038
+ return _processPatch(data, cacheKeyPrefix, throwOnError);
29039
+ } finally {
29040
+ releaseStringDetachBuffer();
29065
29041
  }
29066
- return remoteNames;
29067
- }
29068
- function parseRemoteNames(stdout) {
29069
- return parseRemoteNamesInGitOrder(stdout).toSorted((a, b) => b.length - a.length);
29070
29042
  }
29071
- function parseRemoteRefWithRemoteNames(ref, remoteNames) {
29072
- const trimmedRef = ref.trim();
29073
- if (trimmedRef.length === 0) return null;
29074
- for (const remoteName of remoteNames) {
29075
- const remotePrefix = `${remoteName}/`;
29076
- if (!trimmedRef.startsWith(remotePrefix)) continue;
29077
- const branchName = trimmedRef.slice(remotePrefix.length).trim();
29078
- if (branchName.length === 0) return null;
29079
- return {
29080
- remoteRef: trimmedRef,
29081
- remoteName,
29082
- branchName
29083
- };
29043
+ function _processPatch(data, cacheKeyPrefix, throwOnError = false) {
29044
+ const isGitDiff = isGitDiffPatch(data);
29045
+ const rawFiles = isGitDiff ? splitGitDiffFiles(data) : splitUnifiedDiffFiles(data);
29046
+ let patchMetadata;
29047
+ const files = [];
29048
+ for (const fileOrPatchMetadata of rawFiles) {
29049
+ if (isGitDiff && !GIT_DIFF_FILE_BREAK_REGEX.test(fileOrPatchMetadata)) {
29050
+ if (patchMetadata == null) patchMetadata = detachString(fileOrPatchMetadata);
29051
+ else if (throwOnError) throw Error("parsePatchContent: unknown file blob");
29052
+ else console.error("parsePatchContent: unknown file blob:", fileOrPatchMetadata);
29053
+ continue;
29054
+ } else if (!isGitDiff && !startsWithUnifiedDiffFileHeader(fileOrPatchMetadata)) {
29055
+ if (patchMetadata == null) patchMetadata = detachString(fileOrPatchMetadata);
29056
+ else if (throwOnError) throw Error("parsePatchContent: unknown file blob");
29057
+ else console.error("parsePatchContent: unknown file blob:", fileOrPatchMetadata);
29058
+ continue;
29059
+ }
29060
+ const currentFile = _processFile(fileOrPatchMetadata, {
29061
+ cacheKey: cacheKeyPrefix != null ? `${cacheKeyPrefix}-${files.length}` : void 0,
29062
+ isGitDiff,
29063
+ throwOnError
29064
+ });
29065
+ if (currentFile != null) files.push(currentFile);
29084
29066
  }
29085
- return null;
29086
- }
29087
- function extractBranchNameFromRemoteRef(ref, options) {
29088
- const normalized = ref.trim();
29089
- if (normalized.length === 0) return "";
29090
- if (normalized.startsWith("refs/remotes/")) return extractBranchNameFromRemoteRef(normalized.slice(13), options);
29091
- const parsedRemoteRef = parseRemoteRefWithRemoteNames(normalized, options?.remoteName ? [options.remoteName] : options?.remoteNames ?? []);
29092
- if (parsedRemoteRef) return parsedRemoteRef.branchName;
29093
- const firstSlash = normalized.indexOf("/");
29094
- if (firstSlash === -1) return normalized;
29095
- return normalized.slice(firstSlash + 1).trim();
29096
- }
29097
- //#endregion
29098
- //#region src/vcs/GitVcsDriverCore.ts
29099
- const DEFAULT_TIMEOUT_MS$5 = 3e4;
29100
- const DEFAULT_MAX_OUTPUT_BYTES$1 = 1e6;
29101
- const OUTPUT_TRUNCATED_MARKER$1 = "\n\n[truncated]";
29102
- const PREPARED_COMMIT_PATCH_MAX_OUTPUT_BYTES = 49e3;
29103
- const RANGE_COMMIT_SUMMARY_MAX_OUTPUT_BYTES = 19e3;
29104
- const RANGE_DIFF_SUMMARY_MAX_OUTPUT_BYTES = 19e3;
29105
- const RANGE_DIFF_PATCH_MAX_OUTPUT_BYTES = 59e3;
29106
- const REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES = 12e4;
29107
- const REVIEW_UNTRACKED_DIFF_MAX_OUTPUT_BYTES = 8e4;
29108
- const WORKSPACE_FILES_MAX_OUTPUT_BYTES$1 = 12e4;
29109
- const STATUS_UPSTREAM_REFRESH_INTERVAL = Duration.seconds(15);
29110
- const STATUS_UPSTREAM_REFRESH_TIMEOUT = Duration.seconds(5);
29111
- const STATUS_UPSTREAM_REFRESH_FAILURE_BASE_COOLDOWN = Duration.seconds(30);
29112
- const STATUS_UPSTREAM_REFRESH_FAILURE_MAX_COOLDOWN = Duration.minutes(15);
29113
- const STATUS_UPSTREAM_REFRESH_CACHE_CAPACITY = 2048;
29114
- const REPOSITORY_PATHS_CACHE_CAPACITY = 2048;
29115
- const REPOSITORY_PATHS_CACHE_TTL = Duration.minutes(10);
29116
- const REPOSITORY_PATHS_REFRESH_COALESCE_TTL = Duration.seconds(5);
29117
- const NON_REPOSITORY_PATHS_CACHE_TTL = Duration.seconds(1);
29118
- const LIST_REFS_SNAPSHOT_CACHE_CAPACITY = 64;
29119
- const LIST_REFS_SNAPSHOT_CACHE_TTL = Duration.minutes(2);
29120
- const LIST_REFS_REFRESH_COALESCE_TTL = Duration.seconds(5);
29121
- const LIST_REFS_REFRESH_FAILURE_COOLDOWN = Duration.seconds(30);
29122
- const STATUS_UPSTREAM_REFRESH_ENV = Object.freeze({
29123
- GCM_INTERACTIVE: "never",
29124
- GIT_ASKPASS: "",
29125
- GIT_TERMINAL_PROMPT: "0",
29126
- SSH_ASKPASS: "",
29127
- SSH_ASKPASS_REQUIRE: "never"
29128
- });
29129
- const DEFAULT_BASE_BRANCH_CANDIDATES = ["main", "master"];
29130
- const GIT_LIST_BRANCHES_DEFAULT_LIMIT = 100;
29131
- const NON_REPOSITORY_STATUS_DETAILS = Object.freeze({
29132
- isRepo: false,
29133
- hasOriginRemote: false,
29134
- isDefaultBranch: false,
29135
- branch: null,
29136
- upstreamRef: null,
29137
- hasWorkingTreeChanges: false,
29138
- workingTree: {
29139
- files: [],
29140
- insertions: 0,
29141
- deletions: 0
29142
- },
29143
- hasUpstream: false,
29144
- aheadCount: 0,
29145
- behindCount: 0,
29146
- aheadOfDefaultCount: 0
29147
- });
29148
- const NON_REPOSITORY_REMOTE_STATUS_DETAILS = Object.freeze({
29149
- isRepo: false,
29150
- isDefaultBranch: false,
29151
- branch: null,
29152
- upstreamRef: null,
29153
- hasUpstream: false,
29154
- aheadCount: 0,
29155
- behindCount: 0,
29156
- aheadOfDefaultCount: 0
29157
- });
29158
- var StatusRemoteRefreshCacheKey = class extends Data.Class {};
29159
- function statusUpstreamRefreshFailureCooldown(consecutiveFailures) {
29160
- const exponent = Math.max(0, consecutiveFailures - 1);
29161
- const cooldownMs = Duration.toMillis(STATUS_UPSTREAM_REFRESH_FAILURE_BASE_COOLDOWN) * Math.pow(2, exponent);
29162
- return Duration.min(Duration.millis(cooldownMs), STATUS_UPSTREAM_REFRESH_FAILURE_MAX_COOLDOWN);
29163
- }
29164
- var GitRefsSnapshotCacheKey = class extends Data.Class {};
29165
- var GitRefsRefreshCacheKey = class extends Data.Class {};
29166
- function parseBranchAb(value) {
29167
- const match = value.match(/^\+(\d+)\s+-(\d+)$/);
29168
- if (!match) return {
29169
- ahead: 0,
29170
- behind: 0
29171
- };
29172
29067
  return {
29173
- ahead: Number(match[1] ?? "0"),
29174
- behind: Number(match[2] ?? "0")
29068
+ patchMetadata,
29069
+ files
29175
29070
  };
29176
29071
  }
29177
- function parseNumstatEntries(stdout) {
29178
- const entries = [];
29179
- for (const line of stdout.split(/\r?\n/g)) {
29180
- if (line.trim().length === 0) continue;
29181
- const [addedRaw, deletedRaw, ...pathParts] = line.split(" ");
29182
- const rawPath = pathParts.length > 1 ? (pathParts.at(-1) ?? "").trim() : pathParts.join(" ").trim();
29183
- if (rawPath.length === 0) continue;
29184
- const added = Number.parseInt(addedRaw ?? "0", 10);
29185
- const deleted = Number.parseInt(deletedRaw ?? "0", 10);
29186
- const renameArrowIndex = rawPath.indexOf(" => ");
29187
- const normalizedPath = renameArrowIndex >= 0 ? rawPath.slice(renameArrowIndex + 4).trim() : rawPath;
29188
- entries.push({
29189
- path: normalizedPath.length > 0 ? normalizedPath : rawPath,
29190
- insertions: Number.isFinite(added) ? added : 0,
29191
- deletions: Number.isFinite(deleted) ? deleted : 0
29192
- });
29072
+ function _processFile(fileDiffString, { cacheKey, isGitDiff = GIT_DIFF_FILE_BREAK_REGEX.test(fileDiffString), oldFile, newFile, throwOnError = false } = {}) {
29073
+ let lastHunkEnd = 0;
29074
+ const hunks = splitAtLinePrefix(fileDiffString, "@@ ");
29075
+ let currentFile;
29076
+ const isPartial = oldFile == null || newFile == null;
29077
+ let deletionLineIndex = 0;
29078
+ let additionLineIndex = 0;
29079
+ for (const hunk of hunks) {
29080
+ const lines = splitWithNewlines(hunk);
29081
+ const firstLine = lines[0];
29082
+ if (firstLine == null) {
29083
+ if (throwOnError) throw Error("parsePatchContent: invalid hunk");
29084
+ else console.error("parsePatchContent: invalid hunk", hunk);
29085
+ continue;
29086
+ }
29087
+ const fileHeader = parseHunkHeader(firstLine);
29088
+ let additionLines = 0;
29089
+ let deletionLines = 0;
29090
+ if (fileHeader == null || currentFile == null) {
29091
+ if (currentFile != null) {
29092
+ if (throwOnError) throw Error("parsePatchContent: Invalid hunk");
29093
+ else console.error("parsePatchContent: Invalid hunk", hunk);
29094
+ continue;
29095
+ }
29096
+ currentFile = {
29097
+ name: "",
29098
+ type: "change",
29099
+ hunks: [],
29100
+ splitLineCount: 0,
29101
+ unifiedLineCount: 0,
29102
+ isPartial,
29103
+ additionLines: !isPartial && oldFile != null && newFile != null ? splitFileContents(newFile.contents) : [],
29104
+ deletionLines: !isPartial && oldFile != null && newFile != null ? splitFileContents(oldFile.contents) : [],
29105
+ cacheKey: maybeDetachOptionalString(cacheKey)
29106
+ };
29107
+ if (currentFile.additionLines.length === 1 && newFile?.contents === "") currentFile.additionLines.length = 0;
29108
+ if (currentFile.deletionLines.length === 1 && oldFile?.contents === "") currentFile.deletionLines.length = 0;
29109
+ for (const line of lines) {
29110
+ if (line.startsWith("diff --git")) {
29111
+ const filenameMatch$1 = line.trim().match(ALTERNATE_FILE_NAMES_GIT);
29112
+ const prevName = filenameMatch$1?.[1] ?? filenameMatch$1?.[2];
29113
+ const name = filenameMatch$1?.[3] ?? filenameMatch$1?.[4];
29114
+ if (prevName == null || name == null) {
29115
+ if (throwOnError) throw Error("parsePatchContent: invalid git diff header");
29116
+ else console.error("parsePatchContent: invalid git diff header", line);
29117
+ continue;
29118
+ }
29119
+ currentFile.name = detachString(name.trim());
29120
+ if (prevName !== name) currentFile.prevName = detachString(prevName.trim());
29121
+ continue;
29122
+ }
29123
+ const filenameMatch = line.startsWith("---") || line.startsWith("+++") ? line.match(isGitDiff ? FILENAME_HEADER_REGEX_GIT : FILENAME_HEADER_REGEX) : null;
29124
+ if (filenameMatch != null) {
29125
+ const [, type, fileName] = filenameMatch;
29126
+ if (type === "---" && fileName !== "/dev/null") {
29127
+ const detachedFileName = detachString(fileName.trim());
29128
+ currentFile.prevName = detachedFileName;
29129
+ currentFile.name = detachedFileName;
29130
+ } else if (type === "+++" && fileName !== "/dev/null") currentFile.name = detachString(fileName.trim());
29131
+ } else if (isGitDiff) {
29132
+ if (line.startsWith("new mode ")) currentFile.mode = detachString(line.slice(8).trim());
29133
+ if (line.startsWith("old mode ")) currentFile.prevMode = detachString(line.slice(8).trim());
29134
+ if (line.startsWith("new file mode")) {
29135
+ currentFile.type = "new";
29136
+ currentFile.mode = detachString(line.slice(13).trim());
29137
+ }
29138
+ if (line.startsWith("deleted file mode")) {
29139
+ currentFile.type = "deleted";
29140
+ currentFile.mode = detachString(line.slice(17).trim());
29141
+ }
29142
+ if (line.startsWith("similarity index")) if (line.startsWith("similarity index 100%")) currentFile.type = "rename-pure";
29143
+ else currentFile.type = "rename-changed";
29144
+ if (line.startsWith("index ")) {
29145
+ const [, prevObjectId, newObjectId, mode] = line.trim().match(INDEX_LINE_METADATA) ?? [];
29146
+ if (prevObjectId != null) currentFile.prevObjectId = detachString(prevObjectId);
29147
+ if (newObjectId != null) currentFile.newObjectId = detachString(newObjectId);
29148
+ if (mode != null) currentFile.mode = detachString(mode);
29149
+ }
29150
+ if (line.startsWith("rename from ")) currentFile.prevName = detachString(line.slice(12).trim());
29151
+ if (line.startsWith("rename to ")) currentFile.name = detachString(line.slice(10).trim());
29152
+ }
29153
+ }
29154
+ continue;
29155
+ }
29156
+ let currentContent;
29157
+ let lastLineType;
29158
+ while (lines.length > 0 && (lines[lines.length - 1] === "\n" || lines[lines.length - 1] === "\r" || lines[lines.length - 1] === "\r\n" || lines[lines.length - 1] === "")) lines.pop();
29159
+ const { additionStart, deletionStart } = fileHeader;
29160
+ deletionLineIndex = isPartial ? deletionLineIndex : deletionStart - 1;
29161
+ additionLineIndex = isPartial ? additionLineIndex : additionStart - 1;
29162
+ const hunkData = {
29163
+ collapsedBefore: 0,
29164
+ splitLineCount: 0,
29165
+ splitLineStart: 0,
29166
+ unifiedLineCount: 0,
29167
+ unifiedLineStart: 0,
29168
+ additionCount: fileHeader.additionCount,
29169
+ additionStart,
29170
+ additionLines,
29171
+ deletionCount: fileHeader.deletionCount,
29172
+ deletionStart,
29173
+ deletionLines,
29174
+ deletionLineIndex,
29175
+ additionLineIndex,
29176
+ hunkContent: [],
29177
+ hunkContext: maybeDetachOptionalString(fileHeader.hunkContext),
29178
+ hunkSpecs: detachString(firstLine),
29179
+ noEOFCRAdditions: false,
29180
+ noEOFCRDeletions: false
29181
+ };
29182
+ let parsedAdditionLines = 0;
29183
+ let parsedDeletionLines = 0;
29184
+ for (let lineIndex = 1; lineIndex < lines.length; lineIndex++) {
29185
+ const rawLine = lines[lineIndex];
29186
+ if (parsedAdditionLines >= hunkData.additionCount && parsedDeletionLines >= hunkData.deletionCount && !rawLine.startsWith("\\")) {
29187
+ if (throwOnError && isHunkBodyLine(rawLine) && !isFormatPatchVersionSeparator(rawLine)) throw Error("parsePatchContent: hunk has more lines than expected");
29188
+ break;
29189
+ }
29190
+ const firstChar = rawLine[0];
29191
+ if (firstChar !== "+" && firstChar !== "-" && firstChar !== " " && firstChar !== "\\") {
29192
+ if (throwOnError) throw Error("parsePatchContent: invalid hunk line");
29193
+ console.error(`parseLineType: Invalid firstChar: "${firstChar}", full line: "${rawLine}"`);
29194
+ console.error("processFile: invalid rawLine:", rawLine);
29195
+ continue;
29196
+ }
29197
+ const type = parseRawLineType(firstChar);
29198
+ if (type === "addition") {
29199
+ if (throwOnError && parsedAdditionLines >= hunkData.additionCount) throw Error("parsePatchContent: hunk has too many addition lines");
29200
+ const line = getParsedLineContent(rawLine);
29201
+ if (currentContent == null || currentContent.type !== "change") {
29202
+ currentContent = createContentGroup("change", deletionLineIndex, additionLineIndex);
29203
+ hunkData.hunkContent.push(currentContent);
29204
+ }
29205
+ additionLineIndex++;
29206
+ parsedAdditionLines++;
29207
+ if (isPartial) currentFile.additionLines.push(line);
29208
+ currentContent.additions++;
29209
+ additionLines++;
29210
+ lastLineType = "addition";
29211
+ } else if (type === "deletion") {
29212
+ if (throwOnError && parsedDeletionLines >= hunkData.deletionCount) throw Error("parsePatchContent: hunk has too many deletion lines");
29213
+ const line = getParsedLineContent(rawLine);
29214
+ if (currentContent == null || currentContent.type !== "change") {
29215
+ currentContent = createContentGroup("change", deletionLineIndex, additionLineIndex);
29216
+ hunkData.hunkContent.push(currentContent);
29217
+ }
29218
+ deletionLineIndex++;
29219
+ parsedDeletionLines++;
29220
+ if (isPartial) currentFile.deletionLines.push(line);
29221
+ currentContent.deletions++;
29222
+ deletionLines++;
29223
+ lastLineType = "deletion";
29224
+ } else if (type === "context") {
29225
+ if (throwOnError && (parsedDeletionLines >= hunkData.deletionCount || parsedAdditionLines >= hunkData.additionCount)) throw Error("parsePatchContent: hunk has too many context lines");
29226
+ const line = getParsedLineContent(rawLine);
29227
+ if (currentContent == null || currentContent.type !== "context") {
29228
+ currentContent = createContentGroup("context", deletionLineIndex, additionLineIndex);
29229
+ hunkData.hunkContent.push(currentContent);
29230
+ }
29231
+ additionLineIndex++;
29232
+ deletionLineIndex++;
29233
+ parsedAdditionLines++;
29234
+ parsedDeletionLines++;
29235
+ if (isPartial) {
29236
+ currentFile.deletionLines.push(line);
29237
+ currentFile.additionLines.push(line);
29238
+ }
29239
+ currentContent.lines++;
29240
+ lastLineType = "context";
29241
+ } else if (type === "metadata" && currentContent != null) {
29242
+ if (currentContent.type === "context") {
29243
+ hunkData.noEOFCRAdditions = true;
29244
+ hunkData.noEOFCRDeletions = true;
29245
+ } else if (lastLineType === "deletion") hunkData.noEOFCRDeletions = true;
29246
+ else if (lastLineType === "addition") hunkData.noEOFCRAdditions = true;
29247
+ if (isPartial && (lastLineType === "addition" || lastLineType === "context")) {
29248
+ const lastIndex = currentFile.additionLines.length - 1;
29249
+ if (lastIndex >= 0) currentFile.additionLines[lastIndex] = cleanLastNewline(currentFile.additionLines[lastIndex]);
29250
+ }
29251
+ if (isPartial && (lastLineType === "deletion" || lastLineType === "context")) {
29252
+ const lastIndex = currentFile.deletionLines.length - 1;
29253
+ if (lastIndex >= 0) currentFile.deletionLines[lastIndex] = cleanLastNewline(currentFile.deletionLines[lastIndex]);
29254
+ }
29255
+ }
29256
+ }
29257
+ if (throwOnError && (parsedAdditionLines !== hunkData.additionCount || parsedDeletionLines !== hunkData.deletionCount)) throw Error("parsePatchContent: hunk line count mismatch");
29258
+ hunkData.additionLines = additionLines;
29259
+ hunkData.deletionLines = deletionLines;
29260
+ hunkData.collapsedBefore = Math.max(hunkData.additionStart - 1 - lastHunkEnd, 0);
29261
+ currentFile.hunks.push(hunkData);
29262
+ lastHunkEnd = hunkData.additionStart + hunkData.additionCount - 1;
29263
+ for (const content of hunkData.hunkContent) if (content.type === "context") {
29264
+ hunkData.splitLineCount += content.lines;
29265
+ hunkData.unifiedLineCount += content.lines;
29266
+ } else {
29267
+ hunkData.splitLineCount += Math.max(content.additions, content.deletions);
29268
+ hunkData.unifiedLineCount += content.deletions + content.additions;
29269
+ }
29270
+ hunkData.splitLineStart = currentFile.splitLineCount + hunkData.collapsedBefore;
29271
+ hunkData.unifiedLineStart = currentFile.unifiedLineCount + hunkData.collapsedBefore;
29272
+ currentFile.splitLineCount += hunkData.collapsedBefore + hunkData.splitLineCount;
29273
+ currentFile.unifiedLineCount += hunkData.collapsedBefore + hunkData.unifiedLineCount;
29193
29274
  }
29194
- return entries;
29195
- }
29196
- function parsePorcelainPath(line) {
29197
- if (line.startsWith("? ") || line.startsWith("! ")) {
29198
- const simple = line.slice(2).trim();
29199
- return simple.length > 0 ? simple : null;
29275
+ if (currentFile == null) return;
29276
+ if (throwOnError && isPartial && !isGitDiff && currentFile.hunks.length === 0) throw Error("parsePatchContent: unified file has no hunks");
29277
+ if (currentFile.hunks.length > 0 && !isPartial && currentFile.additionLines.length > 0 && currentFile.deletionLines.length > 0) {
29278
+ const lastHunk = currentFile.hunks[currentFile.hunks.length - 1];
29279
+ const lastHunkEnd$1 = lastHunk.additionStart + lastHunk.additionCount - 1;
29280
+ const totalFileLines = currentFile.additionLines.length;
29281
+ const collapsedAfter = Math.max(totalFileLines - lastHunkEnd$1, 0);
29282
+ currentFile.splitLineCount += collapsedAfter;
29283
+ currentFile.unifiedLineCount += collapsedAfter;
29200
29284
  }
29201
- if (!(line.startsWith("1 ") || line.startsWith("2 ") || line.startsWith("u "))) return null;
29202
- const tabIndex = line.indexOf(" ");
29203
- if (tabIndex >= 0) {
29204
- const [filePath] = line.slice(tabIndex + 1).split(" ");
29205
- return filePath?.trim().length ? filePath.trim() : null;
29285
+ if (!isGitDiff) {
29286
+ if (currentFile.prevName != null && currentFile.name !== currentFile.prevName) if (currentFile.hunks.length > 0) currentFile.type = "rename-changed";
29287
+ else currentFile.type = "rename-pure";
29288
+ else if ((oldFile == null || oldFile.contents === "") && newFile != null && newFile.contents !== "") currentFile.type = "new";
29289
+ else if (oldFile != null && oldFile.contents !== "" && (newFile == null || newFile.contents === "")) currentFile.type = "deleted";
29206
29290
  }
29207
- const filePath = line.trim().split(/\s+/g).at(-1) ?? "";
29208
- return filePath.length > 0 ? filePath : null;
29291
+ if (currentFile.type !== "rename-pure" && currentFile.type !== "rename-changed") currentFile.prevName = void 0;
29292
+ return currentFile;
29209
29293
  }
29210
- function filterBranchesForListQuery(refs, query) {
29211
- if (!query) return refs;
29212
- const normalizedQuery = query.toLowerCase();
29213
- return refs.filter((refName) => refName.name.toLowerCase().includes(normalizedQuery));
29294
+ /**
29295
+ * Parses a patch file string into an array of parsed patches.
29296
+ *
29297
+ * @param data - The raw patch file content (supports multi-commit patches)
29298
+ * @param cacheKeyPrefix - Optional prefix for generating cache keys. When provided,
29299
+ * each file in the patch will get a cache key in the format `prefix-patchIndex-fileIndex`.
29300
+ * This enables caching of rendered diff results in the worker pool.
29301
+ */
29302
+ function parsePatchFiles(data, cacheKeyPrefix, throwOnError = false) {
29303
+ const patches = [];
29304
+ const rawPatches = hasCommitMetadataBoundary(data) ? data.split(COMMIT_METADATA_SPLIT) : [data];
29305
+ for (const patch of rawPatches) try {
29306
+ patches.push(processPatch(patch, cacheKeyPrefix != null ? `${cacheKeyPrefix}-${patches.length}` : void 0, throwOnError));
29307
+ } catch (error) {
29308
+ if (throwOnError) throw error;
29309
+ else console.error(error);
29310
+ }
29311
+ return patches;
29312
+ }
29313
+ function hasCommitMetadataBoundary(data) {
29314
+ return data.startsWith("From ") || data.includes("\nFrom ");
29315
+ }
29316
+ function splitFileContents(contents) {
29317
+ const lines = splitWithNewlines(contents);
29318
+ for (let index = 0; index < lines.length; index++) lines[index] = detachString(lines[index]);
29319
+ return lines;
29320
+ }
29321
+ function splitWithNewlines(contents) {
29322
+ if (contents.length === 0) return [""];
29323
+ const lines = [];
29324
+ let startIndex = 0;
29325
+ for (;;) {
29326
+ const newlineIndex = contents.indexOf("\n", startIndex);
29327
+ if (newlineIndex === -1) break;
29328
+ lines.push(contents.slice(startIndex, newlineIndex + 1));
29329
+ startIndex = newlineIndex + 1;
29330
+ }
29331
+ if (startIndex < contents.length) lines.push(contents.slice(startIndex));
29332
+ return lines;
29333
+ }
29334
+ function splitGitDiffFiles(contents) {
29335
+ return splitAtLinePrefix(contents, "diff --git");
29336
+ }
29337
+ function splitUnifiedDiffFiles(contents) {
29338
+ if (contents.length === 0) return [""];
29339
+ const parts = [];
29340
+ let partStartIndex = 0;
29341
+ let lineStartIndex = 0;
29342
+ let remainingDeletionLines = 0;
29343
+ let remainingAdditionLines = 0;
29344
+ let hasOpenedUnifiedFile = false;
29345
+ while (lineStartIndex < contents.length) {
29346
+ const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);
29347
+ if (remainingDeletionLines <= 0 && remainingAdditionLines <= 0) {
29348
+ if (isUnifiedDiffFileHeaderAt(contents, lineStartIndex)) {
29349
+ if (lineStartIndex > partStartIndex) parts.push(contents.slice(partStartIndex, lineStartIndex));
29350
+ partStartIndex = lineStartIndex;
29351
+ hasOpenedUnifiedFile = true;
29352
+ lineStartIndex = getNextLineStartIndex(contents, nextLineStartIndex);
29353
+ continue;
29354
+ }
29355
+ if (hasOpenedUnifiedFile && contents.startsWith("@@ -", lineStartIndex)) {
29356
+ const fileHeader = parseHunkHeader(contents.slice(lineStartIndex, nextLineStartIndex));
29357
+ if (fileHeader != null) {
29358
+ remainingDeletionLines = fileHeader.deletionCount;
29359
+ remainingAdditionLines = fileHeader.additionCount;
29360
+ }
29361
+ }
29362
+ lineStartIndex = nextLineStartIndex;
29363
+ continue;
29364
+ }
29365
+ const firstChar = contents[lineStartIndex];
29366
+ if (firstChar === "\\") {
29367
+ lineStartIndex = nextLineStartIndex;
29368
+ continue;
29369
+ }
29370
+ if (firstChar === " ") {
29371
+ remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);
29372
+ remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);
29373
+ } else if (firstChar === "-") remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);
29374
+ else if (firstChar === "+") remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);
29375
+ lineStartIndex = nextLineStartIndex;
29376
+ }
29377
+ parts.push(contents.slice(partStartIndex));
29378
+ return parts;
29379
+ }
29380
+ function startsWithUnifiedDiffFileHeader(contents) {
29381
+ return isUnifiedDiffFileHeaderAt(contents, 0);
29382
+ }
29383
+ function isUnifiedDiffFileHeaderAt(contents, lineStartIndex) {
29384
+ const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);
29385
+ return isUnifiedDiffHeaderLineAt(contents, lineStartIndex, "---") && isUnifiedDiffHeaderLineAt(contents, nextLineStartIndex, "+++");
29386
+ }
29387
+ function isUnifiedDiffHeaderLineAt(contents, lineStartIndex, prefix) {
29388
+ if (!contents.startsWith(prefix, lineStartIndex)) return false;
29389
+ const separator = contents[lineStartIndex + prefix.length];
29390
+ if (separator !== " " && separator !== " ") return false;
29391
+ for (let index = lineStartIndex + prefix.length + 1; index < contents.length; index++) {
29392
+ const char = contents[index];
29393
+ if (char === "\n" || char === "\r") break;
29394
+ if (char !== " " && char !== " ") return true;
29395
+ }
29396
+ return false;
29397
+ }
29398
+ function getNextLineStartIndex(contents, lineStartIndex) {
29399
+ const newlineIndex = contents.indexOf("\n", lineStartIndex);
29400
+ return newlineIndex === -1 ? contents.length : newlineIndex + 1;
29401
+ }
29402
+ function isHunkBodyLine(line) {
29403
+ const firstChar = line[0];
29404
+ return firstChar === "+" || firstChar === "-" || firstChar === " ";
29405
+ }
29406
+ function isFormatPatchVersionSeparator(line) {
29407
+ if (!line.startsWith("--")) return false;
29408
+ for (let index = 2; index < line.length; index++) {
29409
+ const char = line[index];
29410
+ if (char !== " " && char !== " " && char !== "\n" && char !== "\r") return false;
29411
+ }
29412
+ return true;
29413
+ }
29414
+ function parseHunkHeader(line) {
29415
+ if (!line.startsWith("@@ -")) return;
29416
+ let index = 4;
29417
+ const deletionStartResult = readPositiveInteger(line, index);
29418
+ if (deletionStartResult == null) return;
29419
+ const deletionStart = deletionStartResult.value;
29420
+ index = deletionStartResult.endIndex;
29421
+ let deletionCount = 1;
29422
+ if (line[index] === ",") {
29423
+ const deletionCountResult = readPositiveInteger(line, index + 1);
29424
+ if (deletionCountResult == null) return;
29425
+ deletionCount = deletionCountResult.value;
29426
+ index = deletionCountResult.endIndex;
29427
+ }
29428
+ if (line[index] !== " " || line[index + 1] !== "+") return;
29429
+ index += 2;
29430
+ const additionStartResult = readPositiveInteger(line, index);
29431
+ if (additionStartResult == null) return;
29432
+ const additionStart = additionStartResult.value;
29433
+ index = additionStartResult.endIndex;
29434
+ let additionCount = 1;
29435
+ if (line[index] === ",") {
29436
+ const additionCountResult = readPositiveInteger(line, index + 1);
29437
+ if (additionCountResult == null) return;
29438
+ additionCount = additionCountResult.value;
29439
+ index = additionCountResult.endIndex;
29440
+ }
29441
+ if (line[index] !== " " || line[index + 1] !== "@" || line[index + 2] !== "@") return;
29442
+ let hunkContext;
29443
+ const contextStartIndex = index + 3;
29444
+ if (line[contextStartIndex] === " ") hunkContext = trimLineEnd(line.slice(contextStartIndex + 1));
29445
+ return {
29446
+ additionCount,
29447
+ additionStart,
29448
+ deletionCount,
29449
+ deletionStart,
29450
+ hunkContext
29451
+ };
29452
+ }
29453
+ function readPositiveInteger(value, startIndex) {
29454
+ let index = startIndex;
29455
+ let parsedValue = 0;
29456
+ for (; index < value.length; index++) {
29457
+ const digit = value.charCodeAt(index) - 48;
29458
+ if (digit < 0 || digit > 9) break;
29459
+ parsedValue = parsedValue * 10 + digit;
29460
+ }
29461
+ if (index === startIndex) return;
29462
+ return {
29463
+ value: parsedValue,
29464
+ endIndex: index
29465
+ };
29466
+ }
29467
+ function trimLineEnd(value) {
29468
+ if (value.endsWith("\r\n")) return value.slice(0, -2);
29469
+ if (value.endsWith("\n")) return value.slice(0, -1);
29470
+ return value;
29471
+ }
29472
+ function isGitDiffPatch(data) {
29473
+ return data.startsWith("diff --git") || data.includes("\ndiff --git");
29474
+ }
29475
+ function splitAtLinePrefix(contents, prefix) {
29476
+ if (contents.length === 0) return [""];
29477
+ const newlinePrefix = `\n${prefix}`;
29478
+ const firstBoundaryIndex = contents.startsWith(prefix) ? 0 : findLinePrefixIndex(contents, newlinePrefix, 0);
29479
+ if (firstBoundaryIndex === -1) return [contents];
29480
+ const parts = [];
29481
+ if (firstBoundaryIndex > 0) parts.push(contents.slice(0, firstBoundaryIndex));
29482
+ let startIndex = firstBoundaryIndex;
29483
+ for (;;) {
29484
+ const nextBoundaryIndex = findLinePrefixIndex(contents, newlinePrefix, startIndex + 1);
29485
+ if (nextBoundaryIndex === -1) break;
29486
+ parts.push(contents.slice(startIndex, nextBoundaryIndex));
29487
+ startIndex = nextBoundaryIndex;
29488
+ }
29489
+ parts.push(contents.slice(startIndex));
29490
+ return parts;
29491
+ }
29492
+ function findLinePrefixIndex(contents, newlinePrefix, fromIndex) {
29493
+ const index = contents.indexOf(newlinePrefix, fromIndex);
29494
+ return index === -1 ? -1 : index + 1;
29495
+ }
29496
+ function maybeDetachOptionalString(value) {
29497
+ return value == null ? value : detachString(value);
29498
+ }
29499
+ function parseRawLineType(firstChar) {
29500
+ return firstChar === " " ? "context" : firstChar === "\\" ? "metadata" : firstChar === "+" ? "addition" : "deletion";
29501
+ }
29502
+ function getParsedLineContent(rawLine) {
29503
+ const processedLine = rawLine.slice(1);
29504
+ return detachString(processedLine === "" ? "\n" : processedLine);
29505
+ }
29506
+ function createContentGroup(type, deletionLineIndex, additionLineIndex) {
29507
+ if (type === "change") return {
29508
+ type: "change",
29509
+ additions: 0,
29510
+ deletions: 0,
29511
+ additionLineIndex,
29512
+ deletionLineIndex
29513
+ };
29514
+ return {
29515
+ type: "context",
29516
+ lines: 0,
29517
+ additionLineIndex,
29518
+ deletionLineIndex
29519
+ };
29520
+ }
29521
+ //#endregion
29522
+ //#region src/checkpointing/ThreadChangedFiles.ts
29523
+ /**
29524
+ * Per-thread changed-file attribution for checkpoint diffs.
29525
+ *
29526
+ * A checkpoint snapshots the whole working tree (`git add -A`), so on a shared
29527
+ * worktree the diff between two of a thread's checkpoints also contains every
29528
+ * edit other threads made in the window. The thread's own tool activities are
29529
+ * per-thread by construction, and the inline changed-files summary the clients
29530
+ * derive from them is already the surface users trust. These helpers extract
29531
+ * that same path list on the server and cut a checkpoint diff down to it.
29532
+ *
29533
+ * `collectActivityChangedFilePaths` must stay in lockstep with
29534
+ * `collectChangedFiles` in `apps/web/src/session-logic.ts` (and the mobile
29535
+ * copy in `threadActivity.ts`): same field names, same nested keys, same depth
29536
+ * bound. The one deliberate difference is dropping the cap of 12 paths - that
29537
+ * cap exists for display, and a filter that stopped collecting would silently
29538
+ * drop real work from the diff.
29539
+ *
29540
+ * @module ThreadChangedFiles
29541
+ */
29542
+ const CHANGED_FILE_FIELDS = [
29543
+ "path",
29544
+ "filePath",
29545
+ "relativePath",
29546
+ "filename",
29547
+ "newPath",
29548
+ "oldPath"
29549
+ ];
29550
+ const NESTED_PAYLOAD_KEYS = [
29551
+ "item",
29552
+ "result",
29553
+ "input",
29554
+ "data",
29555
+ "changes",
29556
+ "files",
29557
+ "edits",
29558
+ "patch",
29559
+ "patches",
29560
+ "operations"
29561
+ ];
29562
+ const MAX_COLLECT_DEPTH = 4;
29563
+ function asRecord$3(value) {
29564
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
29565
+ }
29566
+ function pushChangedFilePath(target, value) {
29567
+ if (typeof value !== "string") return;
29568
+ const normalized = value.trim();
29569
+ if (normalized.length === 0) return;
29570
+ target.add(normalized);
29571
+ }
29572
+ function collectChangedFilePaths(value, target, depth) {
29573
+ if (depth > MAX_COLLECT_DEPTH) return;
29574
+ if (Array.isArray(value)) {
29575
+ for (const entry of value) collectChangedFilePaths(entry, target, depth + 1);
29576
+ return;
29577
+ }
29578
+ const record = asRecord$3(value);
29579
+ if (!record) return;
29580
+ for (const field of CHANGED_FILE_FIELDS) pushChangedFilePath(target, record[field]);
29581
+ for (const nestedKey of NESTED_PAYLOAD_KEYS) if (nestedKey in record) collectChangedFilePaths(record[nestedKey], target, depth + 1);
29582
+ }
29583
+ /**
29584
+ * Extract the file paths a single activity payload names, exactly as the
29585
+ * clients do for the inline changed-files summary.
29586
+ */
29587
+ function collectActivityChangedFilePaths(payload) {
29588
+ const target = /* @__PURE__ */ new Set();
29589
+ collectChangedFilePaths(asRecord$3(asRecord$3(payload)?.data), target, 0);
29590
+ return target;
29591
+ }
29592
+ /**
29593
+ * Normalize activity-named paths to the workspace-relative form git patches
29594
+ * use. Adapters report paths as the tool received them, which may be absolute;
29595
+ * a path outside the workspace cannot appear in the checkpoint diff and is
29596
+ * dropped rather than mismatched.
29597
+ */
29598
+ function normalizeChangedFilePaths(changedPaths, workspaceCwd) {
29599
+ const workspacePrefix = workspaceCwd.endsWith("/") ? workspaceCwd : `${workspaceCwd}/`;
29600
+ const normalized = /* @__PURE__ */ new Set();
29601
+ for (const changedPath of changedPaths) {
29602
+ let candidate = changedPath;
29603
+ if (candidate.startsWith(workspacePrefix)) candidate = candidate.slice(workspacePrefix.length);
29604
+ else if (candidate.startsWith("/")) continue;
29605
+ else if (candidate.startsWith("./")) candidate = candidate.slice(2);
29606
+ if (candidate.length > 0) normalized.add(candidate);
29607
+ }
29608
+ return normalized;
29609
+ }
29610
+ const DIFF_FILE_HEADER = /^diff --git /gm;
29611
+ function splitUnifiedDiffBlocks(diff) {
29612
+ const starts = [];
29613
+ for (const match of diff.matchAll(DIFF_FILE_HEADER)) starts.push(match.index);
29614
+ return starts.map((start, index) => diff.slice(start, starts[index + 1]));
29615
+ }
29616
+ function blockNamesPath(block, allowedPaths) {
29617
+ try {
29618
+ return parsePatchFiles(block).some((patch) => patch.files.some((file) => allowedPaths.has(file.name) || file.prevName !== void 0 && allowedPaths.has(file.prevName)));
29619
+ } catch {
29620
+ return true;
29621
+ }
29622
+ }
29623
+ /**
29624
+ * Cut a unified diff down to the files a thread's own activities name.
29625
+ *
29626
+ * `changedPaths` is the raw activity path list; normalization against
29627
+ * `workspaceCwd` happens here. The caller decides what an empty path list
29628
+ * means - this function treats it literally and returns an empty diff.
29629
+ */
29630
+ function filterUnifiedDiffToChangedFiles(diff, changedPaths, workspaceCwd) {
29631
+ if (diff.length === 0) return diff;
29632
+ const allowedPaths = normalizeChangedFilePaths(changedPaths, workspaceCwd);
29633
+ return splitUnifiedDiffBlocks(diff).filter((block) => blockNamesPath(block, allowedPaths)).join("");
29634
+ }
29635
+ //#endregion
29636
+ //#region src/checkpointing/Errors.ts
29637
+ const CheckpointDiffOperation = Schema$1.Literals(["CheckpointDiffQuery.getTurnDiff", "CheckpointDiffQuery.getFullThreadDiff"]);
29638
+ /** The computed result does not satisfy the checkpoint RPC contract. */
29639
+ var CheckpointDiffResultInvalidError = class extends Schema$1.TaggedErrorClass()("CheckpointDiffResultInvalidError", {
29640
+ operation: CheckpointDiffOperation,
29641
+ threadId: ThreadId
29642
+ }) {
29643
+ get message() {
29644
+ const result = this.operation === "CheckpointDiffQuery.getTurnDiff" ? "turn diff" : "full thread diff";
29645
+ return `Checkpoint invariant violation in ${this.operation}: Computed ${result} result does not satisfy contract schema.`;
29646
+ }
29647
+ };
29648
+ /** Projection state no longer contains the requested checkpoint thread. */
29649
+ var CheckpointThreadNotFoundError = class extends Schema$1.TaggedErrorClass()("CheckpointThreadNotFoundError", {
29650
+ operation: CheckpointDiffOperation,
29651
+ threadId: ThreadId
29652
+ }) {
29653
+ get message() {
29654
+ return `Checkpoint invariant violation in ${this.operation}: Thread '${this.threadId}' not found.`;
29655
+ }
29656
+ };
29657
+ /** The checkpoint thread has no workspace path from which to compute a diff. */
29658
+ var CheckpointWorkspacePathMissingError = class extends Schema$1.TaggedErrorClass()("CheckpointWorkspacePathMissingError", {
29659
+ operation: CheckpointDiffOperation,
29660
+ threadId: ThreadId
29661
+ }) {
29662
+ get message() {
29663
+ const diff = this.operation === "CheckpointDiffQuery.getTurnDiff" ? "turn diff" : "full thread diff";
29664
+ return `Checkpoint invariant violation in ${this.operation}: Workspace path missing for thread '${this.threadId}' when computing ${diff}.`;
29665
+ }
29666
+ };
29667
+ /** The requested turn lies beyond the latest available checkpoint. */
29668
+ var CheckpointTurnRangeUnavailableError = class extends Schema$1.TaggedErrorClass()("CheckpointTurnRangeUnavailableError", {
29669
+ operation: CheckpointDiffOperation,
29670
+ threadId: ThreadId,
29671
+ requestedTurnCount: NonNegativeInt,
29672
+ availableTurnCount: NonNegativeInt
29673
+ }) {
29674
+ get message() {
29675
+ return `Checkpoint unavailable for thread ${this.threadId} turn ${this.requestedTurnCount}: Turn diff range exceeds current turn count: requested ${this.requestedTurnCount}, current ${this.availableTurnCount}.`;
29676
+ }
29677
+ };
29678
+ /** Expected checkpoint metadata does not contain the requested Git ref. */
29679
+ var CheckpointRefUnavailableError = class extends Schema$1.TaggedErrorClass()("CheckpointRefUnavailableError", {
29680
+ operation: CheckpointDiffOperation,
29681
+ threadId: ThreadId,
29682
+ turnCount: NonNegativeInt,
29683
+ checkpoint: Schema$1.Literals(["from", "to"])
29684
+ }) {
29685
+ get message() {
29686
+ return `Checkpoint unavailable for thread ${this.threadId} turn ${this.turnCount}: Checkpoint ref is unavailable for turn ${this.turnCount}.`;
29687
+ }
29688
+ };
29689
+ //#endregion
29690
+ //#region src/checkpointing/Utils.ts
29691
+ const CHECKPOINT_REFS_PREFIX = "refs/p4/checkpoints";
29692
+ function checkpointRefForThreadTurn(threadId, turnCount) {
29693
+ return CheckpointRef.make(`${CHECKPOINT_REFS_PREFIX}/${Encoding.encodeBase64Url(threadId)}/turn/${turnCount}`);
29694
+ }
29695
+ function resolveThreadWorkspaceCwd(input) {
29696
+ const worktreeCwd = input.thread.worktreePath ?? void 0;
29697
+ if (worktreeCwd) return worktreeCwd;
29698
+ return input.projects.find((project) => project.id === input.thread.projectId)?.workspaceRoot;
29699
+ }
29700
+ //#endregion
29701
+ //#region src/git/remoteRefs.ts
29702
+ function parseRemoteNamesInGitOrder(stdout) {
29703
+ const remoteNames = [];
29704
+ for (const line of stdout.split("\n")) {
29705
+ const remoteName = line.trim();
29706
+ if (remoteName.length > 0) remoteNames.push(remoteName);
29707
+ }
29708
+ return remoteNames;
29709
+ }
29710
+ function parseRemoteNames(stdout) {
29711
+ return parseRemoteNamesInGitOrder(stdout).toSorted((a, b) => b.length - a.length);
29712
+ }
29713
+ function parseRemoteRefWithRemoteNames(ref, remoteNames) {
29714
+ const trimmedRef = ref.trim();
29715
+ if (trimmedRef.length === 0) return null;
29716
+ for (const remoteName of remoteNames) {
29717
+ const remotePrefix = `${remoteName}/`;
29718
+ if (!trimmedRef.startsWith(remotePrefix)) continue;
29719
+ const branchName = trimmedRef.slice(remotePrefix.length).trim();
29720
+ if (branchName.length === 0) return null;
29721
+ return {
29722
+ remoteRef: trimmedRef,
29723
+ remoteName,
29724
+ branchName
29725
+ };
29726
+ }
29727
+ return null;
29728
+ }
29729
+ function extractBranchNameFromRemoteRef(ref, options) {
29730
+ const normalized = ref.trim();
29731
+ if (normalized.length === 0) return "";
29732
+ if (normalized.startsWith("refs/remotes/")) return extractBranchNameFromRemoteRef(normalized.slice(13), options);
29733
+ const parsedRemoteRef = parseRemoteRefWithRemoteNames(normalized, options?.remoteName ? [options.remoteName] : options?.remoteNames ?? []);
29734
+ if (parsedRemoteRef) return parsedRemoteRef.branchName;
29735
+ const firstSlash = normalized.indexOf("/");
29736
+ if (firstSlash === -1) return normalized;
29737
+ return normalized.slice(firstSlash + 1).trim();
29738
+ }
29739
+ //#endregion
29740
+ //#region src/vcs/GitVcsDriverCore.ts
29741
+ const DEFAULT_TIMEOUT_MS$5 = 3e4;
29742
+ const DEFAULT_MAX_OUTPUT_BYTES$1 = 1e6;
29743
+ const OUTPUT_TRUNCATED_MARKER$1 = "\n\n[truncated]";
29744
+ const PREPARED_COMMIT_PATCH_MAX_OUTPUT_BYTES = 49e3;
29745
+ const RANGE_COMMIT_SUMMARY_MAX_OUTPUT_BYTES = 19e3;
29746
+ const RANGE_DIFF_SUMMARY_MAX_OUTPUT_BYTES = 19e3;
29747
+ const RANGE_DIFF_PATCH_MAX_OUTPUT_BYTES = 59e3;
29748
+ const REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES = 12e4;
29749
+ const REVIEW_UNTRACKED_DIFF_MAX_OUTPUT_BYTES = 8e4;
29750
+ const WORKSPACE_FILES_MAX_OUTPUT_BYTES$1 = 12e4;
29751
+ const STATUS_UPSTREAM_REFRESH_INTERVAL = Duration.seconds(15);
29752
+ const STATUS_UPSTREAM_REFRESH_TIMEOUT = Duration.seconds(5);
29753
+ const STATUS_UPSTREAM_REFRESH_FAILURE_BASE_COOLDOWN = Duration.seconds(30);
29754
+ const STATUS_UPSTREAM_REFRESH_FAILURE_MAX_COOLDOWN = Duration.minutes(15);
29755
+ const STATUS_UPSTREAM_REFRESH_CACHE_CAPACITY = 2048;
29756
+ const REPOSITORY_PATHS_CACHE_CAPACITY = 2048;
29757
+ const REPOSITORY_PATHS_CACHE_TTL = Duration.minutes(10);
29758
+ const REPOSITORY_PATHS_REFRESH_COALESCE_TTL = Duration.seconds(5);
29759
+ const NON_REPOSITORY_PATHS_CACHE_TTL = Duration.seconds(1);
29760
+ const LIST_REFS_SNAPSHOT_CACHE_CAPACITY = 64;
29761
+ const LIST_REFS_SNAPSHOT_CACHE_TTL = Duration.minutes(2);
29762
+ const LIST_REFS_REFRESH_COALESCE_TTL = Duration.seconds(5);
29763
+ const LIST_REFS_REFRESH_FAILURE_COOLDOWN = Duration.seconds(30);
29764
+ const STATUS_UPSTREAM_REFRESH_ENV = Object.freeze({
29765
+ GCM_INTERACTIVE: "never",
29766
+ GIT_ASKPASS: "",
29767
+ GIT_TERMINAL_PROMPT: "0",
29768
+ SSH_ASKPASS: "",
29769
+ SSH_ASKPASS_REQUIRE: "never"
29770
+ });
29771
+ const DEFAULT_BASE_BRANCH_CANDIDATES = ["main", "master"];
29772
+ const GIT_LIST_BRANCHES_DEFAULT_LIMIT = 100;
29773
+ const NON_REPOSITORY_STATUS_DETAILS = Object.freeze({
29774
+ isRepo: false,
29775
+ hasOriginRemote: false,
29776
+ isDefaultBranch: false,
29777
+ branch: null,
29778
+ upstreamRef: null,
29779
+ hasWorkingTreeChanges: false,
29780
+ workingTree: {
29781
+ files: [],
29782
+ insertions: 0,
29783
+ deletions: 0
29784
+ },
29785
+ hasUpstream: false,
29786
+ aheadCount: 0,
29787
+ behindCount: 0,
29788
+ aheadOfDefaultCount: 0
29789
+ });
29790
+ const NON_REPOSITORY_REMOTE_STATUS_DETAILS = Object.freeze({
29791
+ isRepo: false,
29792
+ isDefaultBranch: false,
29793
+ branch: null,
29794
+ upstreamRef: null,
29795
+ hasUpstream: false,
29796
+ aheadCount: 0,
29797
+ behindCount: 0,
29798
+ aheadOfDefaultCount: 0
29799
+ });
29800
+ var StatusRemoteRefreshCacheKey = class extends Data.Class {};
29801
+ function statusUpstreamRefreshFailureCooldown(consecutiveFailures) {
29802
+ const exponent = Math.max(0, consecutiveFailures - 1);
29803
+ const cooldownMs = Duration.toMillis(STATUS_UPSTREAM_REFRESH_FAILURE_BASE_COOLDOWN) * Math.pow(2, exponent);
29804
+ return Duration.min(Duration.millis(cooldownMs), STATUS_UPSTREAM_REFRESH_FAILURE_MAX_COOLDOWN);
29805
+ }
29806
+ var GitRefsSnapshotCacheKey = class extends Data.Class {};
29807
+ var GitRefsRefreshCacheKey = class extends Data.Class {};
29808
+ function parseBranchAb(value) {
29809
+ const match = value.match(/^\+(\d+)\s+-(\d+)$/);
29810
+ if (!match) return {
29811
+ ahead: 0,
29812
+ behind: 0
29813
+ };
29814
+ return {
29815
+ ahead: Number(match[1] ?? "0"),
29816
+ behind: Number(match[2] ?? "0")
29817
+ };
29818
+ }
29819
+ function parseNumstatEntries(stdout) {
29820
+ const entries = [];
29821
+ for (const line of stdout.split(/\r?\n/g)) {
29822
+ if (line.trim().length === 0) continue;
29823
+ const [addedRaw, deletedRaw, ...pathParts] = line.split(" ");
29824
+ const rawPath = pathParts.length > 1 ? (pathParts.at(-1) ?? "").trim() : pathParts.join(" ").trim();
29825
+ if (rawPath.length === 0) continue;
29826
+ const added = Number.parseInt(addedRaw ?? "0", 10);
29827
+ const deleted = Number.parseInt(deletedRaw ?? "0", 10);
29828
+ const renameArrowIndex = rawPath.indexOf(" => ");
29829
+ const normalizedPath = renameArrowIndex >= 0 ? rawPath.slice(renameArrowIndex + 4).trim() : rawPath;
29830
+ entries.push({
29831
+ path: normalizedPath.length > 0 ? normalizedPath : rawPath,
29832
+ insertions: Number.isFinite(added) ? added : 0,
29833
+ deletions: Number.isFinite(deleted) ? deleted : 0
29834
+ });
29835
+ }
29836
+ return entries;
29837
+ }
29838
+ function parsePorcelainPath(line) {
29839
+ if (line.startsWith("? ") || line.startsWith("! ")) {
29840
+ const simple = line.slice(2).trim();
29841
+ return simple.length > 0 ? simple : null;
29842
+ }
29843
+ if (!(line.startsWith("1 ") || line.startsWith("2 ") || line.startsWith("u "))) return null;
29844
+ const tabIndex = line.indexOf(" ");
29845
+ if (tabIndex >= 0) {
29846
+ const [filePath] = line.slice(tabIndex + 1).split(" ");
29847
+ return filePath?.trim().length ? filePath.trim() : null;
29848
+ }
29849
+ const filePath = line.trim().split(/\s+/g).at(-1) ?? "";
29850
+ return filePath.length > 0 ? filePath : null;
29851
+ }
29852
+ function filterBranchesForListQuery(refs, query) {
29853
+ if (!query) return refs;
29854
+ const normalizedQuery = query.toLowerCase();
29855
+ return refs.filter((refName) => refName.name.toLowerCase().includes(normalizedQuery));
29214
29856
  }
29215
29857
  function paginateBranches(input) {
29216
29858
  const cursor = input.cursor ?? 0;
@@ -31596,6 +32238,32 @@ function buildTurnDiffResult(input, diff) {
31596
32238
  const make$39 = Effect.gen(function* () {
31597
32239
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
31598
32240
  const checkpointStore = yield* CheckpointStore;
32241
+ const threadActivities = yield* ProjectionThreadActivityRepository;
32242
+ /**
32243
+ * The file paths this thread's own tool activities name, optionally limited
32244
+ * to a set of turns. This is the attribution a checkpoint cannot provide: a
32245
+ * checkpoint snapshots the whole tree, so on a shared worktree its diff also
32246
+ * contains other threads' edits, while activities are per-thread by
32247
+ * construction.
32248
+ */
32249
+ const collectThreadChangedFilePaths = Effect.fn("CheckpointDiffQuery.collectThreadChangedFilePaths")(function* (threadId, turnIds) {
32250
+ const activities = yield* threadActivities.listByThreadId({ threadId });
32251
+ const changedPaths = /* @__PURE__ */ new Set();
32252
+ for (const activity of activities) {
32253
+ if (activity.tone !== "tool") continue;
32254
+ if (turnIds !== null && (activity.turnId === null || !turnIds.has(activity.turnId))) continue;
32255
+ for (const changedPath of collectActivityChangedFilePaths(activity.payload)) changedPaths.add(changedPath);
32256
+ }
32257
+ return changedPaths;
32258
+ });
32259
+ /**
32260
+ * A thread whose activities name no paths at all gets the unfiltered diff.
32261
+ * That is the deliberate escape valve: a thread working purely through shell
32262
+ * commands never names the files it touched, and erasing its whole diff
32263
+ * would be worse than the shared-worktree contamination this filter exists
32264
+ * to remove.
32265
+ */
32266
+ const attributeDiffToThread = (diff, changedPaths, workspaceCwd) => changedPaths.size === 0 ? diff : filterUnifiedDiffToChangedFiles(diff, changedPaths, workspaceCwd);
31599
32267
  const getTurnDiff = Effect.fn("getTurnDiff")(function* (input) {
31600
32268
  const operation = "CheckpointDiffQuery.getTurnDiff";
31601
32269
  const ignoreWhitespace = input.ignoreWhitespace ?? true;
@@ -31649,13 +32317,16 @@ const make$39 = Effect.gen(function* () {
31649
32317
  turnCount: input.toTurnCount,
31650
32318
  checkpoint: "to"
31651
32319
  });
31652
- const turnDiff = buildTurnDiffResult(input, yield* checkpointStore.diffCheckpoints({
32320
+ const diff = yield* checkpointStore.diffCheckpoints({
31653
32321
  cwd: workspaceCwd,
31654
32322
  fromCheckpointRef,
31655
32323
  toCheckpointRef,
31656
32324
  fallbackFromToHead: false,
31657
32325
  ignoreWhitespace
31658
- }).pipe(Effect.withSpan("checkpoint.turnDiff.diffCheckpoints")));
32326
+ }).pipe(Effect.withSpan("checkpoint.turnDiff.diffCheckpoints"));
32327
+ const turnIdsInRange = new Set(threadContext.value.checkpoints.filter((checkpoint) => checkpoint.checkpointTurnCount > input.fromTurnCount && checkpoint.checkpointTurnCount <= input.toTurnCount).map((checkpoint) => checkpoint.turnId));
32328
+ const changedPaths = yield* collectThreadChangedFilePaths(input.threadId, turnIdsInRange);
32329
+ const turnDiff = buildTurnDiffResult(input, attributeDiffToThread(diff, changedPaths, workspaceCwd));
31659
32330
  if (!isTurnDiffResult(turnDiff)) return yield* new CheckpointDiffResultInvalidError({
31660
32331
  operation,
31661
32332
  threadId: input.threadId
@@ -31713,11 +32384,12 @@ const make$39 = Effect.gen(function* () {
31713
32384
  fallbackFromToHead: false,
31714
32385
  ignoreWhitespace
31715
32386
  }).pipe(Effect.withSpan("checkpoint.fullThread.diffCheckpoints"));
32387
+ const changedPaths = yield* collectThreadChangedFilePaths(input.threadId, null);
31716
32388
  const turnDiff = buildTurnDiffResult({
31717
32389
  threadId: input.threadId,
31718
32390
  fromTurnCount: 0,
31719
32391
  toTurnCount: input.toTurnCount
31720
- }, diff);
32392
+ }, attributeDiffToThread(diff, changedPaths, workspaceCwd));
31721
32393
  if (!isTurnDiffResult(turnDiff)) return yield* new CheckpointDiffResultInvalidError({
31722
32394
  operation,
31723
32395
  threadId: input.threadId
@@ -32297,7 +32969,7 @@ const normalizeDispatchCommand = (command) => Effect.gen(function* () {
32297
32969
  const buildTaskRow = (input, taskId, timestamp, repositoryKey = null) => ({
32298
32970
  taskId,
32299
32971
  projectId: input.projectId ?? null,
32300
- repositoryKey,
32972
+ repositoryKey: repositoryKey ?? input.repositoryKey ?? null,
32301
32973
  threadId: input.threadId ?? null,
32302
32974
  title: input.title,
32303
32975
  body: input.body ?? "",
@@ -83142,796 +83814,264 @@ const make$2 = Effect.gen(function* () {
83142
83814
  if (targetBranch === oldBranch) return;
83143
83815
  const renamed = yield* gitWorkflow.renameBranch({
83144
83816
  cwd,
83145
- oldBranch,
83146
- newBranch: targetBranch
83147
- });
83148
- yield* orchestrationEngine.dispatch({
83149
- type: "thread.meta.update",
83150
- commandId: yield* serverCommandId("worktree-branch-rename"),
83151
- threadId: input.threadId,
83152
- branch: renamed.branch,
83153
- worktreePath: cwd
83154
- });
83155
- yield* vcsStatusBroadcaster.refreshStatus(cwd).pipe(Effect.ignoreCause({ log: true }));
83156
- }).pipe(Effect.catchCause((cause) => Effect.logWarning("provider command reactor failed to generate or rename worktree branch", {
83157
- threadId: input.threadId,
83158
- cwd,
83159
- oldBranch,
83160
- cause: Cause.pretty(cause)
83161
- })));
83162
- });
83163
- const maybeGenerateThreadTitleForFirstTurn = Effect.fn("maybeGenerateThreadTitleForFirstTurn")(function* (input) {
83164
- const attachments = input.attachments ?? [];
83165
- yield* Effect.gen(function* () {
83166
- const { textGenerationModelSelection: modelSelection } = yield* serverSettingsService.getSettings;
83167
- const generated = yield* textGeneration.generateThreadTitle({
83168
- cwd: input.cwd,
83169
- message: input.messageText,
83170
- ...attachments.length > 0 ? { attachments } : {},
83171
- modelSelection
83172
- });
83173
- if (!generated) return;
83174
- const thread = yield* resolveThread(input.threadId);
83175
- if (!thread) return;
83176
- if (!canReplaceThreadTitle(thread.title, input.titleSeed)) return;
83177
- yield* orchestrationEngine.dispatch({
83178
- type: "thread.meta.update",
83179
- commandId: yield* serverCommandId("thread-title-rename"),
83180
- threadId: input.threadId,
83181
- title: generated.title
83182
- });
83183
- }).pipe(Effect.catchCause((cause) => Effect.logWarning("provider command reactor failed to generate or rename thread title", {
83184
- threadId: input.threadId,
83185
- cwd: input.cwd,
83186
- cause: Cause.pretty(cause)
83187
- })));
83188
- });
83189
- const processTurnStartRequested = Effect.fn("processTurnStartRequested")(function* (event) {
83190
- const key = turnStartKeyForEvent(event);
83191
- if (yield* hasHandledTurnStartRecently(key)) return;
83192
- const thread = yield* resolveThread(event.payload.threadId);
83193
- if (!thread) return;
83194
- const message = thread.messages.find((entry) => entry.id === event.payload.messageId);
83195
- if (!message || message.role !== "user") {
83196
- yield* appendProviderFailureActivity({
83197
- threadId: event.payload.threadId,
83198
- kind: "provider.turn.start.failed",
83199
- summary: "Provider turn start failed",
83200
- detail: `User message '${event.payload.messageId}' was not found for turn start request.`,
83201
- turnId: null,
83202
- createdAt: event.payload.createdAt
83203
- });
83204
- return;
83205
- }
83206
- if (thread.messages.filter((entry) => entry.role === "user").length === 1) {
83207
- const project = yield* resolveProject(thread.projectId);
83208
- const generationCwd = resolveThreadWorkspaceCwd({
83209
- thread,
83210
- projects: project ? [project] : []
83211
- }) ?? process.cwd();
83212
- const generationInput = {
83213
- messageText: message.text,
83214
- ...message.attachments !== void 0 ? { attachments: message.attachments } : {},
83215
- ...event.payload.titleSeed !== void 0 ? { titleSeed: event.payload.titleSeed } : {}
83216
- };
83217
- yield* maybeGenerateAndRenameWorktreeBranchForFirstTurn({
83218
- threadId: event.payload.threadId,
83219
- branch: thread.branch,
83220
- worktreePath: thread.worktreePath,
83221
- ...generationInput
83222
- }).pipe(Effect.forkScoped);
83223
- if (canReplaceThreadTitle(thread.title, event.payload.titleSeed)) yield* maybeGenerateThreadTitleForFirstTurn({
83224
- threadId: event.payload.threadId,
83225
- cwd: generationCwd,
83226
- ...generationInput
83227
- }).pipe(Effect.forkScoped);
83228
- }
83229
- const handleTurnStartFailure = (cause) => {
83230
- if (Cause.hasInterruptsOnly(cause)) return Effect.void;
83231
- const detail = formatFailureDetail(cause);
83232
- return setThreadSessionErrorOnTurnStartFailure({
83233
- threadId: event.payload.threadId,
83234
- detail,
83235
- createdAt: event.payload.createdAt
83236
- }).pipe(Effect.flatMap(() => appendProviderFailureActivity({
83237
- threadId: event.payload.threadId,
83238
- kind: "provider.turn.start.failed",
83239
- summary: "Provider turn start failed",
83240
- detail,
83241
- turnId: null,
83242
- createdAt: event.payload.createdAt
83243
- })), Effect.asVoid);
83244
- };
83245
- const recoverTurnStartFailure = (cause) => handleTurnStartFailure(cause).pipe(Effect.catchCause((recoveryCause) => Effect.logWarning("provider command reactor failed to recover turn start failure", {
83246
- eventType: event.type,
83247
- threadId: event.payload.threadId,
83248
- cause: Cause.pretty(recoveryCause),
83249
- originalCause: Cause.pretty(cause)
83250
- })));
83251
- const sendTurnRequest = yield* buildSendTurnRequestForThread({
83252
- threadId: event.payload.threadId,
83253
- messageText: message.text,
83254
- ...message.attachments !== void 0 ? { attachments: message.attachments } : {},
83255
- ...event.payload.modelSelection !== void 0 ? { modelSelection: event.payload.modelSelection } : {},
83256
- interactionMode: event.payload.interactionMode,
83257
- createdAt: event.payload.createdAt
83258
- }).pipe(Effect.map(Option.some), Effect.catchCause((cause) => handleTurnStartFailure(cause).pipe(Effect.as(Option.none()))));
83259
- if (Option.isNone(sendTurnRequest)) return;
83260
- yield* providerService.sendTurn(sendTurnRequest.value).pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped);
83261
- });
83262
- const processTurnInterruptRequested = Effect.fn("processTurnInterruptRequested")(function* (event) {
83263
- const thread = yield* resolveThread(event.payload.threadId);
83264
- if (!thread) return;
83265
- if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
83266
- threadId: event.payload.threadId,
83267
- kind: "provider.turn.interrupt.failed",
83268
- summary: "Provider turn interrupt failed",
83269
- detail: "No active provider session is bound to this thread.",
83270
- turnId: event.payload.turnId ?? null,
83271
- createdAt: event.payload.createdAt
83272
- });
83273
- yield* providerService.interruptTurn({ threadId: event.payload.threadId });
83274
- yield* appendTurnInterruptedActivity({
83275
- threadId: event.payload.threadId,
83276
- turnId: event.payload.turnId ?? null,
83277
- createdAt: event.payload.createdAt
83278
- });
83279
- });
83280
- const processApprovalResponseRequested = Effect.fn("processApprovalResponseRequested")(function* (event) {
83281
- const thread = yield* resolveThread(event.payload.threadId);
83282
- if (!thread) return;
83283
- if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
83284
- threadId: event.payload.threadId,
83285
- kind: "provider.approval.respond.failed",
83286
- summary: "Provider approval response failed",
83287
- detail: "No active provider session is bound to this thread.",
83288
- turnId: null,
83289
- createdAt: event.payload.createdAt,
83290
- requestId: event.payload.requestId
83291
- });
83292
- yield* providerService.respondToRequest({
83293
- threadId: event.payload.threadId,
83294
- requestId: event.payload.requestId,
83295
- decision: event.payload.decision
83296
- }).pipe(Effect.catchCause((cause) => appendProviderFailureActivity({
83297
- threadId: event.payload.threadId,
83298
- kind: "provider.approval.respond.failed",
83299
- summary: "Provider approval response failed",
83300
- detail: isUnknownPendingApprovalRequestError(cause) ? stalePendingRequestDetail("approval", event.payload.requestId) : Cause.pretty(cause),
83301
- turnId: null,
83302
- createdAt: event.payload.createdAt,
83303
- requestId: event.payload.requestId
83304
- })));
83305
- });
83306
- const processUserInputResponseRequested = Effect.fn("processUserInputResponseRequested")(function* (event) {
83307
- const thread = yield* resolveThread(event.payload.threadId);
83308
- if (!thread) return;
83309
- if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
83310
- threadId: event.payload.threadId,
83311
- kind: "provider.user-input.respond.failed",
83312
- summary: "Provider user input response failed",
83313
- detail: "No active provider session is bound to this thread.",
83314
- turnId: null,
83315
- createdAt: event.payload.createdAt,
83316
- requestId: event.payload.requestId
83317
- });
83318
- yield* providerService.respondToUserInput({
83319
- threadId: event.payload.threadId,
83320
- requestId: event.payload.requestId,
83321
- answers: event.payload.answers
83322
- }).pipe(Effect.catchCause((cause) => appendProviderFailureActivity({
83323
- threadId: event.payload.threadId,
83324
- kind: "provider.user-input.respond.failed",
83325
- summary: "Provider user input response failed",
83326
- detail: isUnknownPendingUserInputRequestError(cause) ? stalePendingRequestDetail("user-input", event.payload.requestId) : Cause.pretty(cause),
83327
- turnId: null,
83328
- createdAt: event.payload.createdAt,
83329
- requestId: event.payload.requestId
83330
- })));
83331
- });
83332
- const processSessionStopRequested = Effect.fn("processSessionStopRequested")(function* (event) {
83333
- const thread = yield* resolveThread(event.payload.threadId);
83334
- if (!thread) return;
83335
- const now = event.payload.createdAt;
83336
- if (thread.session && thread.session.status !== "stopped") yield* providerService.stopSession({ threadId: thread.id });
83337
- yield* setThreadSession({
83338
- threadId: thread.id,
83339
- session: {
83340
- threadId: thread.id,
83341
- status: "stopped",
83342
- providerName: thread.session?.providerName ?? null,
83343
- ...thread.session?.providerInstanceId !== void 0 ? { providerInstanceId: thread.session.providerInstanceId } : {},
83344
- runtimeMode: thread.session?.runtimeMode ?? DEFAULT_RUNTIME_MODE,
83345
- activeTurnId: null,
83346
- lastError: thread.session?.lastError ?? null,
83347
- updatedAt: now
83348
- },
83349
- createdAt: now
83350
- });
83351
- });
83352
- const processDomainEvent = Effect.fn("processDomainEvent")(function* (event) {
83353
- yield* Effect.annotateCurrentSpan({
83354
- "orchestration.event_type": event.type,
83355
- "orchestration.thread_id": event.payload.threadId,
83356
- ...event.commandId ? { "orchestration.command_id": event.commandId } : {}
83357
- });
83358
- yield* increment(orchestrationEventsProcessedTotal, { eventType: event.type });
83359
- switch (event.type) {
83360
- case "thread.runtime-mode-set": {
83361
- const thread = yield* resolveThread(event.payload.threadId);
83362
- if (!thread?.session || thread.session.status === "stopped") return;
83363
- const cachedModelSelection = threadModelSelections.get(event.payload.threadId);
83364
- yield* ensureSessionForThread(event.payload.threadId, event.occurredAt, cachedModelSelection !== void 0 ? { modelSelection: cachedModelSelection } : {});
83365
- return;
83366
- }
83367
- case "thread.turn-start-requested":
83368
- yield* processTurnStartRequested(event);
83369
- return;
83370
- case "thread.turn-interrupt-requested":
83371
- yield* processTurnInterruptRequested(event);
83372
- return;
83373
- case "thread.approval-response-requested":
83374
- yield* processApprovalResponseRequested(event);
83375
- return;
83376
- case "thread.user-input-response-requested":
83377
- yield* processUserInputResponseRequested(event);
83378
- return;
83379
- case "thread.session-stop-requested":
83380
- yield* processSessionStopRequested(event);
83381
- return;
83382
- }
83383
- });
83384
- const processDomainEventSafely = (event) => processDomainEvent(event).pipe(Effect.catchCause((cause) => {
83385
- if (Cause.hasInterruptsOnly(cause)) return Effect.failCause(cause);
83386
- return Effect.logWarning("provider command reactor failed to process event", {
83387
- eventType: event.type,
83388
- cause: Cause.pretty(cause)
83389
- });
83390
- }));
83391
- const worker = yield* makeDrainableWorker(processDomainEventSafely);
83392
- return {
83393
- start: Effect.fn("start")(function* () {
83394
- const processEvent = Effect.fn("processEvent")(function* (event) {
83395
- if (event.type === "thread.runtime-mode-set" || event.type === "thread.turn-start-requested" || event.type === "thread.turn-interrupt-requested" || event.type === "thread.approval-response-requested" || event.type === "thread.user-input-response-requested" || event.type === "thread.session-stop-requested") return yield* worker.enqueue(event);
83396
- });
83397
- yield* Effect.forkScoped(Stream.runForEach(orchestrationEngine.streamDomainEvents, processEvent));
83398
- }),
83399
- drain: worker.drain
83400
- };
83401
- });
83402
- const ProviderCommandReactorLive = Layer.effect(ProviderCommandReactor, make$2);
83403
- (() => {
83404
- try {
83405
- return process.env.NODE_ENV === "development";
83406
- } catch {
83407
- return false;
83408
- }
83409
- })();
83410
- const COMMIT_METADATA_SPLIT = /(?=^From [a-f0-9]+ .+$)/m;
83411
- const GIT_DIFF_FILE_BREAK_REGEX = /(?=^diff --git)/gm;
83412
- const FILENAME_HEADER_REGEX = /^(---|\+\+\+)\s+([^\t\r\n]+)/;
83413
- const FILENAME_HEADER_REGEX_GIT = /^(---|\+\+\+)\s+[ab]\/([^\t\r\n]+)/;
83414
- const ALTERNATE_FILE_NAMES_GIT = /^diff --git (?:"a\/(.+?)"|a\/(.+?)) (?:"b\/(.+?)"|b\/(.+?))$/;
83415
- const INDEX_LINE_METADATA = /^index ([0-9a-f]+)\.\.([0-9a-f]+)(?: (\d+))?$/i;
83416
- Object.freeze({
83417
- fromStart: 0,
83418
- fromEnd: 0
83419
- });
83420
- //#endregion
83421
- //#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/cleanLastNewline.js
83422
- function cleanLastNewline(contents) {
83423
- let end = contents.length;
83424
- if (contents.charCodeAt(end - 1) === 10) {
83425
- end--;
83426
- if (contents.charCodeAt(end - 1) === 13) end--;
83427
- }
83428
- return contents.slice(0, end);
83429
- }
83430
- //#endregion
83431
- //#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/detachString.js
83432
- const stringDetachEncoder = new TextEncoder();
83433
- const stringDetachDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
83434
- const SURROGATE_CODE_UNIT_PATTERN = /[\uD800-\uDFFF]/;
83435
- const STRING_DETACH_INITIAL_BUFFER_SIZE = 1024;
83436
- let stringDetachBuffer = new Uint8Array(STRING_DETACH_INITIAL_BUFFER_SIZE);
83437
- function releaseStringDetachBuffer() {
83438
- if (stringDetachBuffer.length !== STRING_DETACH_INITIAL_BUFFER_SIZE) stringDetachBuffer = new Uint8Array(STRING_DETACH_INITIAL_BUFFER_SIZE);
83439
- }
83440
- function detachString(value) {
83441
- if (value.length === 0) return value;
83442
- if (SURROGATE_CODE_UNIT_PATTERN.test(value)) return JSON.parse(JSON.stringify(value));
83443
- const requiredByteLength = value.length * 3;
83444
- if (stringDetachBuffer.length < requiredByteLength) stringDetachBuffer = new Uint8Array(requiredByteLength);
83445
- const { written } = stringDetachEncoder.encodeInto(value, stringDetachBuffer);
83446
- return stringDetachDecoder.decode(stringDetachBuffer.subarray(0, written));
83447
- }
83448
- //#endregion
83449
- //#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/parsePatchFiles.js
83450
- function processPatch(data, cacheKeyPrefix, throwOnError) {
83451
- try {
83452
- return _processPatch(data, cacheKeyPrefix, throwOnError);
83453
- } finally {
83454
- releaseStringDetachBuffer();
83455
- }
83456
- }
83457
- function _processPatch(data, cacheKeyPrefix, throwOnError = false) {
83458
- const isGitDiff = isGitDiffPatch(data);
83459
- const rawFiles = isGitDiff ? splitGitDiffFiles(data) : splitUnifiedDiffFiles(data);
83460
- let patchMetadata;
83461
- const files = [];
83462
- for (const fileOrPatchMetadata of rawFiles) {
83463
- if (isGitDiff && !GIT_DIFF_FILE_BREAK_REGEX.test(fileOrPatchMetadata)) {
83464
- if (patchMetadata == null) patchMetadata = detachString(fileOrPatchMetadata);
83465
- else if (throwOnError) throw Error("parsePatchContent: unknown file blob");
83466
- else console.error("parsePatchContent: unknown file blob:", fileOrPatchMetadata);
83467
- continue;
83468
- } else if (!isGitDiff && !startsWithUnifiedDiffFileHeader(fileOrPatchMetadata)) {
83469
- if (patchMetadata == null) patchMetadata = detachString(fileOrPatchMetadata);
83470
- else if (throwOnError) throw Error("parsePatchContent: unknown file blob");
83471
- else console.error("parsePatchContent: unknown file blob:", fileOrPatchMetadata);
83472
- continue;
83473
- }
83474
- const currentFile = _processFile(fileOrPatchMetadata, {
83475
- cacheKey: cacheKeyPrefix != null ? `${cacheKeyPrefix}-${files.length}` : void 0,
83476
- isGitDiff,
83477
- throwOnError
83478
- });
83479
- if (currentFile != null) files.push(currentFile);
83480
- }
83481
- return {
83482
- patchMetadata,
83483
- files
83484
- };
83485
- }
83486
- function _processFile(fileDiffString, { cacheKey, isGitDiff = GIT_DIFF_FILE_BREAK_REGEX.test(fileDiffString), oldFile, newFile, throwOnError = false } = {}) {
83487
- let lastHunkEnd = 0;
83488
- const hunks = splitAtLinePrefix(fileDiffString, "@@ ");
83489
- let currentFile;
83490
- const isPartial = oldFile == null || newFile == null;
83491
- let deletionLineIndex = 0;
83492
- let additionLineIndex = 0;
83493
- for (const hunk of hunks) {
83494
- const lines = splitWithNewlines(hunk);
83495
- const firstLine = lines[0];
83496
- if (firstLine == null) {
83497
- if (throwOnError) throw Error("parsePatchContent: invalid hunk");
83498
- else console.error("parsePatchContent: invalid hunk", hunk);
83499
- continue;
83500
- }
83501
- const fileHeader = parseHunkHeader(firstLine);
83502
- let additionLines = 0;
83503
- let deletionLines = 0;
83504
- if (fileHeader == null || currentFile == null) {
83505
- if (currentFile != null) {
83506
- if (throwOnError) throw Error("parsePatchContent: Invalid hunk");
83507
- else console.error("parsePatchContent: Invalid hunk", hunk);
83508
- continue;
83509
- }
83510
- currentFile = {
83511
- name: "",
83512
- type: "change",
83513
- hunks: [],
83514
- splitLineCount: 0,
83515
- unifiedLineCount: 0,
83516
- isPartial,
83517
- additionLines: !isPartial && oldFile != null && newFile != null ? splitFileContents(newFile.contents) : [],
83518
- deletionLines: !isPartial && oldFile != null && newFile != null ? splitFileContents(oldFile.contents) : [],
83519
- cacheKey: maybeDetachOptionalString(cacheKey)
83520
- };
83521
- if (currentFile.additionLines.length === 1 && newFile?.contents === "") currentFile.additionLines.length = 0;
83522
- if (currentFile.deletionLines.length === 1 && oldFile?.contents === "") currentFile.deletionLines.length = 0;
83523
- for (const line of lines) {
83524
- if (line.startsWith("diff --git")) {
83525
- const filenameMatch$1 = line.trim().match(ALTERNATE_FILE_NAMES_GIT);
83526
- const prevName = filenameMatch$1?.[1] ?? filenameMatch$1?.[2];
83527
- const name = filenameMatch$1?.[3] ?? filenameMatch$1?.[4];
83528
- if (prevName == null || name == null) {
83529
- if (throwOnError) throw Error("parsePatchContent: invalid git diff header");
83530
- else console.error("parsePatchContent: invalid git diff header", line);
83531
- continue;
83532
- }
83533
- currentFile.name = detachString(name.trim());
83534
- if (prevName !== name) currentFile.prevName = detachString(prevName.trim());
83535
- continue;
83536
- }
83537
- const filenameMatch = line.startsWith("---") || line.startsWith("+++") ? line.match(isGitDiff ? FILENAME_HEADER_REGEX_GIT : FILENAME_HEADER_REGEX) : null;
83538
- if (filenameMatch != null) {
83539
- const [, type, fileName] = filenameMatch;
83540
- if (type === "---" && fileName !== "/dev/null") {
83541
- const detachedFileName = detachString(fileName.trim());
83542
- currentFile.prevName = detachedFileName;
83543
- currentFile.name = detachedFileName;
83544
- } else if (type === "+++" && fileName !== "/dev/null") currentFile.name = detachString(fileName.trim());
83545
- } else if (isGitDiff) {
83546
- if (line.startsWith("new mode ")) currentFile.mode = detachString(line.slice(8).trim());
83547
- if (line.startsWith("old mode ")) currentFile.prevMode = detachString(line.slice(8).trim());
83548
- if (line.startsWith("new file mode")) {
83549
- currentFile.type = "new";
83550
- currentFile.mode = detachString(line.slice(13).trim());
83551
- }
83552
- if (line.startsWith("deleted file mode")) {
83553
- currentFile.type = "deleted";
83554
- currentFile.mode = detachString(line.slice(17).trim());
83555
- }
83556
- if (line.startsWith("similarity index")) if (line.startsWith("similarity index 100%")) currentFile.type = "rename-pure";
83557
- else currentFile.type = "rename-changed";
83558
- if (line.startsWith("index ")) {
83559
- const [, prevObjectId, newObjectId, mode] = line.trim().match(INDEX_LINE_METADATA) ?? [];
83560
- if (prevObjectId != null) currentFile.prevObjectId = detachString(prevObjectId);
83561
- if (newObjectId != null) currentFile.newObjectId = detachString(newObjectId);
83562
- if (mode != null) currentFile.mode = detachString(mode);
83563
- }
83564
- if (line.startsWith("rename from ")) currentFile.prevName = detachString(line.slice(12).trim());
83565
- if (line.startsWith("rename to ")) currentFile.name = detachString(line.slice(10).trim());
83566
- }
83567
- }
83568
- continue;
83569
- }
83570
- let currentContent;
83571
- let lastLineType;
83572
- while (lines.length > 0 && (lines[lines.length - 1] === "\n" || lines[lines.length - 1] === "\r" || lines[lines.length - 1] === "\r\n" || lines[lines.length - 1] === "")) lines.pop();
83573
- const { additionStart, deletionStart } = fileHeader;
83574
- deletionLineIndex = isPartial ? deletionLineIndex : deletionStart - 1;
83575
- additionLineIndex = isPartial ? additionLineIndex : additionStart - 1;
83576
- const hunkData = {
83577
- collapsedBefore: 0,
83578
- splitLineCount: 0,
83579
- splitLineStart: 0,
83580
- unifiedLineCount: 0,
83581
- unifiedLineStart: 0,
83582
- additionCount: fileHeader.additionCount,
83583
- additionStart,
83584
- additionLines,
83585
- deletionCount: fileHeader.deletionCount,
83586
- deletionStart,
83587
- deletionLines,
83588
- deletionLineIndex,
83589
- additionLineIndex,
83590
- hunkContent: [],
83591
- hunkContext: maybeDetachOptionalString(fileHeader.hunkContext),
83592
- hunkSpecs: detachString(firstLine),
83593
- noEOFCRAdditions: false,
83594
- noEOFCRDeletions: false
83595
- };
83596
- let parsedAdditionLines = 0;
83597
- let parsedDeletionLines = 0;
83598
- for (let lineIndex = 1; lineIndex < lines.length; lineIndex++) {
83599
- const rawLine = lines[lineIndex];
83600
- if (parsedAdditionLines >= hunkData.additionCount && parsedDeletionLines >= hunkData.deletionCount && !rawLine.startsWith("\\")) {
83601
- if (throwOnError && isHunkBodyLine(rawLine) && !isFormatPatchVersionSeparator(rawLine)) throw Error("parsePatchContent: hunk has more lines than expected");
83602
- break;
83603
- }
83604
- const firstChar = rawLine[0];
83605
- if (firstChar !== "+" && firstChar !== "-" && firstChar !== " " && firstChar !== "\\") {
83606
- if (throwOnError) throw Error("parsePatchContent: invalid hunk line");
83607
- console.error(`parseLineType: Invalid firstChar: "${firstChar}", full line: "${rawLine}"`);
83608
- console.error("processFile: invalid rawLine:", rawLine);
83609
- continue;
83610
- }
83611
- const type = parseRawLineType(firstChar);
83612
- if (type === "addition") {
83613
- if (throwOnError && parsedAdditionLines >= hunkData.additionCount) throw Error("parsePatchContent: hunk has too many addition lines");
83614
- const line = getParsedLineContent(rawLine);
83615
- if (currentContent == null || currentContent.type !== "change") {
83616
- currentContent = createContentGroup("change", deletionLineIndex, additionLineIndex);
83617
- hunkData.hunkContent.push(currentContent);
83618
- }
83619
- additionLineIndex++;
83620
- parsedAdditionLines++;
83621
- if (isPartial) currentFile.additionLines.push(line);
83622
- currentContent.additions++;
83623
- additionLines++;
83624
- lastLineType = "addition";
83625
- } else if (type === "deletion") {
83626
- if (throwOnError && parsedDeletionLines >= hunkData.deletionCount) throw Error("parsePatchContent: hunk has too many deletion lines");
83627
- const line = getParsedLineContent(rawLine);
83628
- if (currentContent == null || currentContent.type !== "change") {
83629
- currentContent = createContentGroup("change", deletionLineIndex, additionLineIndex);
83630
- hunkData.hunkContent.push(currentContent);
83631
- }
83632
- deletionLineIndex++;
83633
- parsedDeletionLines++;
83634
- if (isPartial) currentFile.deletionLines.push(line);
83635
- currentContent.deletions++;
83636
- deletionLines++;
83637
- lastLineType = "deletion";
83638
- } else if (type === "context") {
83639
- if (throwOnError && (parsedDeletionLines >= hunkData.deletionCount || parsedAdditionLines >= hunkData.additionCount)) throw Error("parsePatchContent: hunk has too many context lines");
83640
- const line = getParsedLineContent(rawLine);
83641
- if (currentContent == null || currentContent.type !== "context") {
83642
- currentContent = createContentGroup("context", deletionLineIndex, additionLineIndex);
83643
- hunkData.hunkContent.push(currentContent);
83644
- }
83645
- additionLineIndex++;
83646
- deletionLineIndex++;
83647
- parsedAdditionLines++;
83648
- parsedDeletionLines++;
83649
- if (isPartial) {
83650
- currentFile.deletionLines.push(line);
83651
- currentFile.additionLines.push(line);
83652
- }
83653
- currentContent.lines++;
83654
- lastLineType = "context";
83655
- } else if (type === "metadata" && currentContent != null) {
83656
- if (currentContent.type === "context") {
83657
- hunkData.noEOFCRAdditions = true;
83658
- hunkData.noEOFCRDeletions = true;
83659
- } else if (lastLineType === "deletion") hunkData.noEOFCRDeletions = true;
83660
- else if (lastLineType === "addition") hunkData.noEOFCRAdditions = true;
83661
- if (isPartial && (lastLineType === "addition" || lastLineType === "context")) {
83662
- const lastIndex = currentFile.additionLines.length - 1;
83663
- if (lastIndex >= 0) currentFile.additionLines[lastIndex] = cleanLastNewline(currentFile.additionLines[lastIndex]);
83664
- }
83665
- if (isPartial && (lastLineType === "deletion" || lastLineType === "context")) {
83666
- const lastIndex = currentFile.deletionLines.length - 1;
83667
- if (lastIndex >= 0) currentFile.deletionLines[lastIndex] = cleanLastNewline(currentFile.deletionLines[lastIndex]);
83668
- }
83669
- }
83817
+ oldBranch,
83818
+ newBranch: targetBranch
83819
+ });
83820
+ yield* orchestrationEngine.dispatch({
83821
+ type: "thread.meta.update",
83822
+ commandId: yield* serverCommandId("worktree-branch-rename"),
83823
+ threadId: input.threadId,
83824
+ branch: renamed.branch,
83825
+ worktreePath: cwd
83826
+ });
83827
+ yield* vcsStatusBroadcaster.refreshStatus(cwd).pipe(Effect.ignoreCause({ log: true }));
83828
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("provider command reactor failed to generate or rename worktree branch", {
83829
+ threadId: input.threadId,
83830
+ cwd,
83831
+ oldBranch,
83832
+ cause: Cause.pretty(cause)
83833
+ })));
83834
+ });
83835
+ const maybeGenerateThreadTitleForFirstTurn = Effect.fn("maybeGenerateThreadTitleForFirstTurn")(function* (input) {
83836
+ const attachments = input.attachments ?? [];
83837
+ yield* Effect.gen(function* () {
83838
+ const { textGenerationModelSelection: modelSelection } = yield* serverSettingsService.getSettings;
83839
+ const generated = yield* textGeneration.generateThreadTitle({
83840
+ cwd: input.cwd,
83841
+ message: input.messageText,
83842
+ ...attachments.length > 0 ? { attachments } : {},
83843
+ modelSelection
83844
+ });
83845
+ if (!generated) return;
83846
+ const thread = yield* resolveThread(input.threadId);
83847
+ if (!thread) return;
83848
+ if (!canReplaceThreadTitle(thread.title, input.titleSeed)) return;
83849
+ yield* orchestrationEngine.dispatch({
83850
+ type: "thread.meta.update",
83851
+ commandId: yield* serverCommandId("thread-title-rename"),
83852
+ threadId: input.threadId,
83853
+ title: generated.title
83854
+ });
83855
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("provider command reactor failed to generate or rename thread title", {
83856
+ threadId: input.threadId,
83857
+ cwd: input.cwd,
83858
+ cause: Cause.pretty(cause)
83859
+ })));
83860
+ });
83861
+ const processTurnStartRequested = Effect.fn("processTurnStartRequested")(function* (event) {
83862
+ const key = turnStartKeyForEvent(event);
83863
+ if (yield* hasHandledTurnStartRecently(key)) return;
83864
+ const thread = yield* resolveThread(event.payload.threadId);
83865
+ if (!thread) return;
83866
+ const message = thread.messages.find((entry) => entry.id === event.payload.messageId);
83867
+ if (!message || message.role !== "user") {
83868
+ yield* appendProviderFailureActivity({
83869
+ threadId: event.payload.threadId,
83870
+ kind: "provider.turn.start.failed",
83871
+ summary: "Provider turn start failed",
83872
+ detail: `User message '${event.payload.messageId}' was not found for turn start request.`,
83873
+ turnId: null,
83874
+ createdAt: event.payload.createdAt
83875
+ });
83876
+ return;
83670
83877
  }
83671
- if (throwOnError && (parsedAdditionLines !== hunkData.additionCount || parsedDeletionLines !== hunkData.deletionCount)) throw Error("parsePatchContent: hunk line count mismatch");
83672
- hunkData.additionLines = additionLines;
83673
- hunkData.deletionLines = deletionLines;
83674
- hunkData.collapsedBefore = Math.max(hunkData.additionStart - 1 - lastHunkEnd, 0);
83675
- currentFile.hunks.push(hunkData);
83676
- lastHunkEnd = hunkData.additionStart + hunkData.additionCount - 1;
83677
- for (const content of hunkData.hunkContent) if (content.type === "context") {
83678
- hunkData.splitLineCount += content.lines;
83679
- hunkData.unifiedLineCount += content.lines;
83680
- } else {
83681
- hunkData.splitLineCount += Math.max(content.additions, content.deletions);
83682
- hunkData.unifiedLineCount += content.deletions + content.additions;
83878
+ if (thread.messages.filter((entry) => entry.role === "user").length === 1) {
83879
+ const project = yield* resolveProject(thread.projectId);
83880
+ const generationCwd = resolveThreadWorkspaceCwd({
83881
+ thread,
83882
+ projects: project ? [project] : []
83883
+ }) ?? process.cwd();
83884
+ const generationInput = {
83885
+ messageText: message.text,
83886
+ ...message.attachments !== void 0 ? { attachments: message.attachments } : {},
83887
+ ...event.payload.titleSeed !== void 0 ? { titleSeed: event.payload.titleSeed } : {}
83888
+ };
83889
+ yield* maybeGenerateAndRenameWorktreeBranchForFirstTurn({
83890
+ threadId: event.payload.threadId,
83891
+ branch: thread.branch,
83892
+ worktreePath: thread.worktreePath,
83893
+ ...generationInput
83894
+ }).pipe(Effect.forkScoped);
83895
+ if (canReplaceThreadTitle(thread.title, event.payload.titleSeed)) yield* maybeGenerateThreadTitleForFirstTurn({
83896
+ threadId: event.payload.threadId,
83897
+ cwd: generationCwd,
83898
+ ...generationInput
83899
+ }).pipe(Effect.forkScoped);
83683
83900
  }
83684
- hunkData.splitLineStart = currentFile.splitLineCount + hunkData.collapsedBefore;
83685
- hunkData.unifiedLineStart = currentFile.unifiedLineCount + hunkData.collapsedBefore;
83686
- currentFile.splitLineCount += hunkData.collapsedBefore + hunkData.splitLineCount;
83687
- currentFile.unifiedLineCount += hunkData.collapsedBefore + hunkData.unifiedLineCount;
83688
- }
83689
- if (currentFile == null) return;
83690
- if (throwOnError && isPartial && !isGitDiff && currentFile.hunks.length === 0) throw Error("parsePatchContent: unified file has no hunks");
83691
- if (currentFile.hunks.length > 0 && !isPartial && currentFile.additionLines.length > 0 && currentFile.deletionLines.length > 0) {
83692
- const lastHunk = currentFile.hunks[currentFile.hunks.length - 1];
83693
- const lastHunkEnd$1 = lastHunk.additionStart + lastHunk.additionCount - 1;
83694
- const totalFileLines = currentFile.additionLines.length;
83695
- const collapsedAfter = Math.max(totalFileLines - lastHunkEnd$1, 0);
83696
- currentFile.splitLineCount += collapsedAfter;
83697
- currentFile.unifiedLineCount += collapsedAfter;
83698
- }
83699
- if (!isGitDiff) {
83700
- if (currentFile.prevName != null && currentFile.name !== currentFile.prevName) if (currentFile.hunks.length > 0) currentFile.type = "rename-changed";
83701
- else currentFile.type = "rename-pure";
83702
- else if ((oldFile == null || oldFile.contents === "") && newFile != null && newFile.contents !== "") currentFile.type = "new";
83703
- else if (oldFile != null && oldFile.contents !== "" && (newFile == null || newFile.contents === "")) currentFile.type = "deleted";
83704
- }
83705
- if (currentFile.type !== "rename-pure" && currentFile.type !== "rename-changed") currentFile.prevName = void 0;
83706
- return currentFile;
83707
- }
83708
- /**
83709
- * Parses a patch file string into an array of parsed patches.
83710
- *
83711
- * @param data - The raw patch file content (supports multi-commit patches)
83712
- * @param cacheKeyPrefix - Optional prefix for generating cache keys. When provided,
83713
- * each file in the patch will get a cache key in the format `prefix-patchIndex-fileIndex`.
83714
- * This enables caching of rendered diff results in the worker pool.
83715
- */
83716
- function parsePatchFiles(data, cacheKeyPrefix, throwOnError = false) {
83717
- const patches = [];
83718
- const rawPatches = hasCommitMetadataBoundary(data) ? data.split(COMMIT_METADATA_SPLIT) : [data];
83719
- for (const patch of rawPatches) try {
83720
- patches.push(processPatch(patch, cacheKeyPrefix != null ? `${cacheKeyPrefix}-${patches.length}` : void 0, throwOnError));
83721
- } catch (error) {
83722
- if (throwOnError) throw error;
83723
- else console.error(error);
83724
- }
83725
- return patches;
83726
- }
83727
- function hasCommitMetadataBoundary(data) {
83728
- return data.startsWith("From ") || data.includes("\nFrom ");
83729
- }
83730
- function splitFileContents(contents) {
83731
- const lines = splitWithNewlines(contents);
83732
- for (let index = 0; index < lines.length; index++) lines[index] = detachString(lines[index]);
83733
- return lines;
83734
- }
83735
- function splitWithNewlines(contents) {
83736
- if (contents.length === 0) return [""];
83737
- const lines = [];
83738
- let startIndex = 0;
83739
- for (;;) {
83740
- const newlineIndex = contents.indexOf("\n", startIndex);
83741
- if (newlineIndex === -1) break;
83742
- lines.push(contents.slice(startIndex, newlineIndex + 1));
83743
- startIndex = newlineIndex + 1;
83744
- }
83745
- if (startIndex < contents.length) lines.push(contents.slice(startIndex));
83746
- return lines;
83747
- }
83748
- function splitGitDiffFiles(contents) {
83749
- return splitAtLinePrefix(contents, "diff --git");
83750
- }
83751
- function splitUnifiedDiffFiles(contents) {
83752
- if (contents.length === 0) return [""];
83753
- const parts = [];
83754
- let partStartIndex = 0;
83755
- let lineStartIndex = 0;
83756
- let remainingDeletionLines = 0;
83757
- let remainingAdditionLines = 0;
83758
- let hasOpenedUnifiedFile = false;
83759
- while (lineStartIndex < contents.length) {
83760
- const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);
83761
- if (remainingDeletionLines <= 0 && remainingAdditionLines <= 0) {
83762
- if (isUnifiedDiffFileHeaderAt(contents, lineStartIndex)) {
83763
- if (lineStartIndex > partStartIndex) parts.push(contents.slice(partStartIndex, lineStartIndex));
83764
- partStartIndex = lineStartIndex;
83765
- hasOpenedUnifiedFile = true;
83766
- lineStartIndex = getNextLineStartIndex(contents, nextLineStartIndex);
83767
- continue;
83768
- }
83769
- if (hasOpenedUnifiedFile && contents.startsWith("@@ -", lineStartIndex)) {
83770
- const fileHeader = parseHunkHeader(contents.slice(lineStartIndex, nextLineStartIndex));
83771
- if (fileHeader != null) {
83772
- remainingDeletionLines = fileHeader.deletionCount;
83773
- remainingAdditionLines = fileHeader.additionCount;
83774
- }
83901
+ const handleTurnStartFailure = (cause) => {
83902
+ if (Cause.hasInterruptsOnly(cause)) return Effect.void;
83903
+ const detail = formatFailureDetail(cause);
83904
+ return setThreadSessionErrorOnTurnStartFailure({
83905
+ threadId: event.payload.threadId,
83906
+ detail,
83907
+ createdAt: event.payload.createdAt
83908
+ }).pipe(Effect.flatMap(() => appendProviderFailureActivity({
83909
+ threadId: event.payload.threadId,
83910
+ kind: "provider.turn.start.failed",
83911
+ summary: "Provider turn start failed",
83912
+ detail,
83913
+ turnId: null,
83914
+ createdAt: event.payload.createdAt
83915
+ })), Effect.asVoid);
83916
+ };
83917
+ const recoverTurnStartFailure = (cause) => handleTurnStartFailure(cause).pipe(Effect.catchCause((recoveryCause) => Effect.logWarning("provider command reactor failed to recover turn start failure", {
83918
+ eventType: event.type,
83919
+ threadId: event.payload.threadId,
83920
+ cause: Cause.pretty(recoveryCause),
83921
+ originalCause: Cause.pretty(cause)
83922
+ })));
83923
+ const sendTurnRequest = yield* buildSendTurnRequestForThread({
83924
+ threadId: event.payload.threadId,
83925
+ messageText: message.text,
83926
+ ...message.attachments !== void 0 ? { attachments: message.attachments } : {},
83927
+ ...event.payload.modelSelection !== void 0 ? { modelSelection: event.payload.modelSelection } : {},
83928
+ interactionMode: event.payload.interactionMode,
83929
+ createdAt: event.payload.createdAt
83930
+ }).pipe(Effect.map(Option.some), Effect.catchCause((cause) => handleTurnStartFailure(cause).pipe(Effect.as(Option.none()))));
83931
+ if (Option.isNone(sendTurnRequest)) return;
83932
+ yield* providerService.sendTurn(sendTurnRequest.value).pipe(Effect.catchCause(recoverTurnStartFailure), Effect.forkScoped);
83933
+ });
83934
+ const processTurnInterruptRequested = Effect.fn("processTurnInterruptRequested")(function* (event) {
83935
+ const thread = yield* resolveThread(event.payload.threadId);
83936
+ if (!thread) return;
83937
+ if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
83938
+ threadId: event.payload.threadId,
83939
+ kind: "provider.turn.interrupt.failed",
83940
+ summary: "Provider turn interrupt failed",
83941
+ detail: "No active provider session is bound to this thread.",
83942
+ turnId: event.payload.turnId ?? null,
83943
+ createdAt: event.payload.createdAt
83944
+ });
83945
+ yield* providerService.interruptTurn({ threadId: event.payload.threadId });
83946
+ yield* appendTurnInterruptedActivity({
83947
+ threadId: event.payload.threadId,
83948
+ turnId: event.payload.turnId ?? null,
83949
+ createdAt: event.payload.createdAt
83950
+ });
83951
+ });
83952
+ const processApprovalResponseRequested = Effect.fn("processApprovalResponseRequested")(function* (event) {
83953
+ const thread = yield* resolveThread(event.payload.threadId);
83954
+ if (!thread) return;
83955
+ if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
83956
+ threadId: event.payload.threadId,
83957
+ kind: "provider.approval.respond.failed",
83958
+ summary: "Provider approval response failed",
83959
+ detail: "No active provider session is bound to this thread.",
83960
+ turnId: null,
83961
+ createdAt: event.payload.createdAt,
83962
+ requestId: event.payload.requestId
83963
+ });
83964
+ yield* providerService.respondToRequest({
83965
+ threadId: event.payload.threadId,
83966
+ requestId: event.payload.requestId,
83967
+ decision: event.payload.decision
83968
+ }).pipe(Effect.catchCause((cause) => appendProviderFailureActivity({
83969
+ threadId: event.payload.threadId,
83970
+ kind: "provider.approval.respond.failed",
83971
+ summary: "Provider approval response failed",
83972
+ detail: isUnknownPendingApprovalRequestError(cause) ? stalePendingRequestDetail("approval", event.payload.requestId) : Cause.pretty(cause),
83973
+ turnId: null,
83974
+ createdAt: event.payload.createdAt,
83975
+ requestId: event.payload.requestId
83976
+ })));
83977
+ });
83978
+ const processUserInputResponseRequested = Effect.fn("processUserInputResponseRequested")(function* (event) {
83979
+ const thread = yield* resolveThread(event.payload.threadId);
83980
+ if (!thread) return;
83981
+ if (!(thread.session && thread.session.status !== "stopped")) return yield* appendProviderFailureActivity({
83982
+ threadId: event.payload.threadId,
83983
+ kind: "provider.user-input.respond.failed",
83984
+ summary: "Provider user input response failed",
83985
+ detail: "No active provider session is bound to this thread.",
83986
+ turnId: null,
83987
+ createdAt: event.payload.createdAt,
83988
+ requestId: event.payload.requestId
83989
+ });
83990
+ yield* providerService.respondToUserInput({
83991
+ threadId: event.payload.threadId,
83992
+ requestId: event.payload.requestId,
83993
+ answers: event.payload.answers
83994
+ }).pipe(Effect.catchCause((cause) => appendProviderFailureActivity({
83995
+ threadId: event.payload.threadId,
83996
+ kind: "provider.user-input.respond.failed",
83997
+ summary: "Provider user input response failed",
83998
+ detail: isUnknownPendingUserInputRequestError(cause) ? stalePendingRequestDetail("user-input", event.payload.requestId) : Cause.pretty(cause),
83999
+ turnId: null,
84000
+ createdAt: event.payload.createdAt,
84001
+ requestId: event.payload.requestId
84002
+ })));
84003
+ });
84004
+ const processSessionStopRequested = Effect.fn("processSessionStopRequested")(function* (event) {
84005
+ const thread = yield* resolveThread(event.payload.threadId);
84006
+ if (!thread) return;
84007
+ const now = event.payload.createdAt;
84008
+ if (thread.session && thread.session.status !== "stopped") yield* providerService.stopSession({ threadId: thread.id });
84009
+ yield* setThreadSession({
84010
+ threadId: thread.id,
84011
+ session: {
84012
+ threadId: thread.id,
84013
+ status: "stopped",
84014
+ providerName: thread.session?.providerName ?? null,
84015
+ ...thread.session?.providerInstanceId !== void 0 ? { providerInstanceId: thread.session.providerInstanceId } : {},
84016
+ runtimeMode: thread.session?.runtimeMode ?? DEFAULT_RUNTIME_MODE,
84017
+ activeTurnId: null,
84018
+ lastError: thread.session?.lastError ?? null,
84019
+ updatedAt: now
84020
+ },
84021
+ createdAt: now
84022
+ });
84023
+ });
84024
+ const processDomainEvent = Effect.fn("processDomainEvent")(function* (event) {
84025
+ yield* Effect.annotateCurrentSpan({
84026
+ "orchestration.event_type": event.type,
84027
+ "orchestration.thread_id": event.payload.threadId,
84028
+ ...event.commandId ? { "orchestration.command_id": event.commandId } : {}
84029
+ });
84030
+ yield* increment(orchestrationEventsProcessedTotal, { eventType: event.type });
84031
+ switch (event.type) {
84032
+ case "thread.runtime-mode-set": {
84033
+ const thread = yield* resolveThread(event.payload.threadId);
84034
+ if (!thread?.session || thread.session.status === "stopped") return;
84035
+ const cachedModelSelection = threadModelSelections.get(event.payload.threadId);
84036
+ yield* ensureSessionForThread(event.payload.threadId, event.occurredAt, cachedModelSelection !== void 0 ? { modelSelection: cachedModelSelection } : {});
84037
+ return;
83775
84038
  }
83776
- lineStartIndex = nextLineStartIndex;
83777
- continue;
83778
- }
83779
- const firstChar = contents[lineStartIndex];
83780
- if (firstChar === "\\") {
83781
- lineStartIndex = nextLineStartIndex;
83782
- continue;
84039
+ case "thread.turn-start-requested":
84040
+ yield* processTurnStartRequested(event);
84041
+ return;
84042
+ case "thread.turn-interrupt-requested":
84043
+ yield* processTurnInterruptRequested(event);
84044
+ return;
84045
+ case "thread.approval-response-requested":
84046
+ yield* processApprovalResponseRequested(event);
84047
+ return;
84048
+ case "thread.user-input-response-requested":
84049
+ yield* processUserInputResponseRequested(event);
84050
+ return;
84051
+ case "thread.session-stop-requested":
84052
+ yield* processSessionStopRequested(event);
84053
+ return;
83783
84054
  }
83784
- if (firstChar === " ") {
83785
- remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);
83786
- remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);
83787
- } else if (firstChar === "-") remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);
83788
- else if (firstChar === "+") remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);
83789
- lineStartIndex = nextLineStartIndex;
83790
- }
83791
- parts.push(contents.slice(partStartIndex));
83792
- return parts;
83793
- }
83794
- function startsWithUnifiedDiffFileHeader(contents) {
83795
- return isUnifiedDiffFileHeaderAt(contents, 0);
83796
- }
83797
- function isUnifiedDiffFileHeaderAt(contents, lineStartIndex) {
83798
- const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);
83799
- return isUnifiedDiffHeaderLineAt(contents, lineStartIndex, "---") && isUnifiedDiffHeaderLineAt(contents, nextLineStartIndex, "+++");
83800
- }
83801
- function isUnifiedDiffHeaderLineAt(contents, lineStartIndex, prefix) {
83802
- if (!contents.startsWith(prefix, lineStartIndex)) return false;
83803
- const separator = contents[lineStartIndex + prefix.length];
83804
- if (separator !== " " && separator !== " ") return false;
83805
- for (let index = lineStartIndex + prefix.length + 1; index < contents.length; index++) {
83806
- const char = contents[index];
83807
- if (char === "\n" || char === "\r") break;
83808
- if (char !== " " && char !== " ") return true;
83809
- }
83810
- return false;
83811
- }
83812
- function getNextLineStartIndex(contents, lineStartIndex) {
83813
- const newlineIndex = contents.indexOf("\n", lineStartIndex);
83814
- return newlineIndex === -1 ? contents.length : newlineIndex + 1;
83815
- }
83816
- function isHunkBodyLine(line) {
83817
- const firstChar = line[0];
83818
- return firstChar === "+" || firstChar === "-" || firstChar === " ";
83819
- }
83820
- function isFormatPatchVersionSeparator(line) {
83821
- if (!line.startsWith("--")) return false;
83822
- for (let index = 2; index < line.length; index++) {
83823
- const char = line[index];
83824
- if (char !== " " && char !== " " && char !== "\n" && char !== "\r") return false;
83825
- }
83826
- return true;
83827
- }
83828
- function parseHunkHeader(line) {
83829
- if (!line.startsWith("@@ -")) return;
83830
- let index = 4;
83831
- const deletionStartResult = readPositiveInteger(line, index);
83832
- if (deletionStartResult == null) return;
83833
- const deletionStart = deletionStartResult.value;
83834
- index = deletionStartResult.endIndex;
83835
- let deletionCount = 1;
83836
- if (line[index] === ",") {
83837
- const deletionCountResult = readPositiveInteger(line, index + 1);
83838
- if (deletionCountResult == null) return;
83839
- deletionCount = deletionCountResult.value;
83840
- index = deletionCountResult.endIndex;
83841
- }
83842
- if (line[index] !== " " || line[index + 1] !== "+") return;
83843
- index += 2;
83844
- const additionStartResult = readPositiveInteger(line, index);
83845
- if (additionStartResult == null) return;
83846
- const additionStart = additionStartResult.value;
83847
- index = additionStartResult.endIndex;
83848
- let additionCount = 1;
83849
- if (line[index] === ",") {
83850
- const additionCountResult = readPositiveInteger(line, index + 1);
83851
- if (additionCountResult == null) return;
83852
- additionCount = additionCountResult.value;
83853
- index = additionCountResult.endIndex;
83854
- }
83855
- if (line[index] !== " " || line[index + 1] !== "@" || line[index + 2] !== "@") return;
83856
- let hunkContext;
83857
- const contextStartIndex = index + 3;
83858
- if (line[contextStartIndex] === " ") hunkContext = trimLineEnd(line.slice(contextStartIndex + 1));
83859
- return {
83860
- additionCount,
83861
- additionStart,
83862
- deletionCount,
83863
- deletionStart,
83864
- hunkContext
83865
- };
83866
- }
83867
- function readPositiveInteger(value, startIndex) {
83868
- let index = startIndex;
83869
- let parsedValue = 0;
83870
- for (; index < value.length; index++) {
83871
- const digit = value.charCodeAt(index) - 48;
83872
- if (digit < 0 || digit > 9) break;
83873
- parsedValue = parsedValue * 10 + digit;
83874
- }
83875
- if (index === startIndex) return;
83876
- return {
83877
- value: parsedValue,
83878
- endIndex: index
83879
- };
83880
- }
83881
- function trimLineEnd(value) {
83882
- if (value.endsWith("\r\n")) return value.slice(0, -2);
83883
- if (value.endsWith("\n")) return value.slice(0, -1);
83884
- return value;
83885
- }
83886
- function isGitDiffPatch(data) {
83887
- return data.startsWith("diff --git") || data.includes("\ndiff --git");
83888
- }
83889
- function splitAtLinePrefix(contents, prefix) {
83890
- if (contents.length === 0) return [""];
83891
- const newlinePrefix = `\n${prefix}`;
83892
- const firstBoundaryIndex = contents.startsWith(prefix) ? 0 : findLinePrefixIndex(contents, newlinePrefix, 0);
83893
- if (firstBoundaryIndex === -1) return [contents];
83894
- const parts = [];
83895
- if (firstBoundaryIndex > 0) parts.push(contents.slice(0, firstBoundaryIndex));
83896
- let startIndex = firstBoundaryIndex;
83897
- for (;;) {
83898
- const nextBoundaryIndex = findLinePrefixIndex(contents, newlinePrefix, startIndex + 1);
83899
- if (nextBoundaryIndex === -1) break;
83900
- parts.push(contents.slice(startIndex, nextBoundaryIndex));
83901
- startIndex = nextBoundaryIndex;
83902
- }
83903
- parts.push(contents.slice(startIndex));
83904
- return parts;
83905
- }
83906
- function findLinePrefixIndex(contents, newlinePrefix, fromIndex) {
83907
- const index = contents.indexOf(newlinePrefix, fromIndex);
83908
- return index === -1 ? -1 : index + 1;
83909
- }
83910
- function maybeDetachOptionalString(value) {
83911
- return value == null ? value : detachString(value);
83912
- }
83913
- function parseRawLineType(firstChar) {
83914
- return firstChar === " " ? "context" : firstChar === "\\" ? "metadata" : firstChar === "+" ? "addition" : "deletion";
83915
- }
83916
- function getParsedLineContent(rawLine) {
83917
- const processedLine = rawLine.slice(1);
83918
- return detachString(processedLine === "" ? "\n" : processedLine);
83919
- }
83920
- function createContentGroup(type, deletionLineIndex, additionLineIndex) {
83921
- if (type === "change") return {
83922
- type: "change",
83923
- additions: 0,
83924
- deletions: 0,
83925
- additionLineIndex,
83926
- deletionLineIndex
83927
- };
84055
+ });
84056
+ const processDomainEventSafely = (event) => processDomainEvent(event).pipe(Effect.catchCause((cause) => {
84057
+ if (Cause.hasInterruptsOnly(cause)) return Effect.failCause(cause);
84058
+ return Effect.logWarning("provider command reactor failed to process event", {
84059
+ eventType: event.type,
84060
+ cause: Cause.pretty(cause)
84061
+ });
84062
+ }));
84063
+ const worker = yield* makeDrainableWorker(processDomainEventSafely);
83928
84064
  return {
83929
- type: "context",
83930
- lines: 0,
83931
- additionLineIndex,
83932
- deletionLineIndex
84065
+ start: Effect.fn("start")(function* () {
84066
+ const processEvent = Effect.fn("processEvent")(function* (event) {
84067
+ if (event.type === "thread.runtime-mode-set" || event.type === "thread.turn-start-requested" || event.type === "thread.turn-interrupt-requested" || event.type === "thread.approval-response-requested" || event.type === "thread.user-input-response-requested" || event.type === "thread.session-stop-requested") return yield* worker.enqueue(event);
84068
+ });
84069
+ yield* Effect.forkScoped(Stream.runForEach(orchestrationEngine.streamDomainEvents, processEvent));
84070
+ }),
84071
+ drain: worker.drain
83933
84072
  };
83934
- }
84073
+ });
84074
+ const ProviderCommandReactorLive = Layer.effect(ProviderCommandReactor, make$2);
83935
84075
  //#endregion
83936
84076
  //#region src/checkpointing/Diffs.ts
83937
84077
  function parseTurnDiffFilesFromUnifiedDiff(diff) {
@@ -83972,6 +84112,7 @@ const make$1 = Effect.gen(function* () {
83972
84112
  const receiptBus = yield* RuntimeReceiptBus;
83973
84113
  const workspaceEntries = yield* WorkspaceEntries;
83974
84114
  const vcsStatusBroadcaster = yield* VcsStatusBroadcaster;
84115
+ const threadActivities = yield* ProjectionThreadActivityRepository;
83975
84116
  const appendRevertFailureActivity = (input) => Effect.all({
83976
84117
  commandId: serverCommandId("checkpoint-revert-failure"),
83977
84118
  activityId: serverEventId
@@ -84060,13 +84201,21 @@ const make$1 = Effect.gen(function* () {
84060
84201
  checkpointRef: targetCheckpointRef
84061
84202
  });
84062
84203
  yield* workspaceEntries.refresh(input.cwd);
84204
+ const turnChangedPaths = yield* threadActivities.listByThreadId({ threadId: input.threadId }).pipe(Effect.map((activities) => {
84205
+ const changedPaths = /* @__PURE__ */ new Set();
84206
+ for (const activity of activities) {
84207
+ if (activity.tone !== "tool" || activity.turnId !== input.turnId) continue;
84208
+ for (const changedPath of collectActivityChangedFilePaths(activity.payload)) changedPaths.add(changedPath);
84209
+ }
84210
+ return normalizeChangedFilePaths(changedPaths, input.cwd);
84211
+ }), Effect.orElseSucceed(() => /* @__PURE__ */ new Set()));
84063
84212
  const files = yield* checkpointStore.diffCheckpoints({
84064
84213
  cwd: input.cwd,
84065
84214
  fromCheckpointRef,
84066
84215
  toCheckpointRef: targetCheckpointRef,
84067
84216
  fallbackFromToHead: false,
84068
84217
  ignoreWhitespace: false
84069
- }).pipe(Effect.map((diff) => parseTurnDiffFilesFromUnifiedDiff(diff).map((file) => ({
84218
+ }).pipe(Effect.map((diff) => parseTurnDiffFilesFromUnifiedDiff(diff).filter((file) => turnChangedPaths.size === 0 || turnChangedPaths.has(file.path)).map((file) => ({
84070
84219
  path: file.path,
84071
84220
  kind: "modified",
84072
84221
  additions: file.additions,