@p4code/cli 0.1.8 → 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 (86) hide show
  1. package/dist/bin.mjs +962 -745
  2. package/dist/client/assets/{DiffPanel-D4tNpljU.js → DiffPanel-BRroAgOK.js} +2 -2
  3. package/dist/client/assets/{FilePreviewPanel-xICJSzZ6.js → FilePreviewPanel-Bdd2Ml_z.js} +2 -2
  4. package/dist/client/assets/{PreviewPanel-CK9d1fc5.js → PreviewPanel-DoJd5FXR.js} +2 -2
  5. package/dist/client/assets/{angular-html-BgzZxO9w.js → angular-html-BA0WzHZ0.js} +2 -2
  6. package/dist/client/assets/{angular-ts-YFEXVhe6.js → angular-ts-BehluKB7.js} +2 -2
  7. package/dist/client/assets/{apl-BoBwF9HI.js → apl-DPnuuIo7.js} +2 -2
  8. package/dist/client/assets/arrow-right-B_11zHBP.js +2 -0
  9. package/dist/client/assets/{astro-Ds5TXooD.js → astro-G6Zz-WSX.js} +2 -2
  10. package/dist/client/assets/{blade-CDtJf5YR.js → blade-c1NgE6B9.js} +2 -2
  11. package/dist/client/assets/{c-JH0umSmP.js → c-BbDaxzL5.js} +2 -2
  12. package/dist/client/assets/{cobol-gMss6BA3.js → cobol-DPWRf7_s.js} +2 -2
  13. package/dist/client/assets/{coffee-DESPXYwo.js → coffee-kZI3-HKr.js} +2 -2
  14. package/dist/client/assets/{cpp-CZztA1XN.js → cpp-DbB1uwNF.js} +2 -2
  15. package/dist/client/assets/{crystal-CDWlJpLE.js → crystal-C9rif4zq.js} +2 -2
  16. package/dist/client/assets/{css-CNPTTtX1.js → css-BzXrG2ii.js} +2 -2
  17. package/dist/client/assets/{edge-DSlrkPAr.js → edge-CIqg8jED.js} +2 -2
  18. package/dist/client/assets/{elixir-DrLl_wv7.js → elixir-L1xpfUlV.js} +2 -2
  19. package/dist/client/assets/{elm-CwB92FlT.js → elm-C4bqF3BU.js} +2 -2
  20. package/dist/client/assets/{erb-DXHrZ2ye.js → erb-DQxpBRS7.js} +2 -2
  21. package/dist/client/assets/{fileCommentAnnotations-CuJ4hnrK.js → fileCommentAnnotations-DXLRihuo.js} +2 -2
  22. package/dist/client/assets/{git-rebase-uRnCoWg0.js → git-rebase-DCFBvSos.js} +2 -2
  23. package/dist/client/assets/{glimmer-js-DU9fMoZx.js → glimmer-js-CMcw0KDm.js} +2 -2
  24. package/dist/client/assets/{glimmer-ts-Bftqha5Z.js → glimmer-ts-zKGsihEs.js} +2 -2
  25. package/dist/client/assets/{glsl-CVVphTeN.js → glsl-C-DKYe1o.js} +2 -2
  26. package/dist/client/assets/{graphql-CwED2DHh.js → graphql-D-Jaf-It.js} +2 -2
  27. package/dist/client/assets/{hack-CwiQSrzl.js → hack-Cy1Ek-eJ.js} +2 -2
  28. package/dist/client/assets/{haml-C2iiqAZE.js → haml-DSBhTnP_.js} +2 -2
  29. package/dist/client/assets/{handlebars-BtvKqvl0.js → handlebars-BTjhBiVA.js} +2 -2
  30. package/dist/client/assets/{html-BRvZZmrv.js → html-CuFf_rJP.js} +2 -2
  31. package/dist/client/assets/{html-derivative-rVjUDzCo.js → html-derivative-B6vKETtb.js} +2 -2
  32. package/dist/client/assets/{http-Cvb4plLT.js → http-TaTxS49l.js} +2 -2
  33. package/dist/client/assets/{hurl-TX6OEub-.js → hurl-Jwth1u4b.js} +2 -2
  34. package/dist/client/assets/index-WQyWsxuV.js +660 -0
  35. package/dist/client/assets/index-nkPMSYxq.css +1 -0
  36. package/dist/client/assets/{java-CudutWch.js → java-Ba5MdLVF.js} +2 -2
  37. package/dist/client/assets/{javascript-D7gB3c_x.js → javascript-_mHojD6_.js} +2 -2
  38. package/dist/client/assets/{jinja-Ce0WqJfV.js → jinja-BhX9YpO0.js} +2 -2
  39. package/dist/client/assets/{jison-2U6TlYgq.js → jison-S0Rrxw4o.js} +2 -2
  40. package/dist/client/assets/{json-oe5h_2FV.js → json-B-G4K1LM.js} +2 -2
  41. package/dist/client/assets/{jsx-B0f4Z6sx.js → jsx-X6TBdO7T.js} +2 -2
  42. package/dist/client/assets/{julia-By6UDEAG.js → julia-He6A0R8c.js} +2 -2
  43. package/dist/client/assets/{just-YTIO1wok.js → just-CQ533yYC.js} +2 -2
  44. package/dist/client/assets/{latex-B9HLUatB.js → latex-BBBRtbj7.js} +2 -2
  45. package/dist/client/assets/{liquid-DajcsI9e.js → liquid-CA9B-Q_J.js} +2 -2
  46. package/dist/client/assets/{lua-CINm0eWo.js → lua-DXLYC8yx.js} +2 -2
  47. package/dist/client/assets/{marko-DXvL73SK.js → marko-BXfllCK7.js} +2 -2
  48. package/dist/client/assets/{mdc-CYsP-lJY.js → mdc-D13Xs_kf.js} +2 -2
  49. package/dist/client/assets/{nginx-CPKmhI0k.js → nginx-HcisF7-i.js} +2 -2
  50. package/dist/client/assets/{nim-DodS9Uy_.js → nim-YbKRMFDo.js} +2 -2
  51. package/dist/client/assets/{perl-COac5jCW.js → perl-CV978rzN.js} +2 -2
  52. package/dist/client/assets/{php-DLugOmp_.js → php-B8Rmr1r4.js} +2 -2
  53. package/dist/client/assets/{pug-ukoxsRP-.js → pug-CI1Ka8om.js} +2 -2
  54. package/dist/client/assets/{qml-DEoQyDDD.js → qml-CyQLZsMJ.js} +2 -2
  55. package/dist/client/assets/{r-CePyxHEB.js → r-C-0JMTYO.js} +2 -2
  56. package/dist/client/assets/{razor-uhGlTCzi.js → razor-BCppTDFk.js} +2 -2
  57. package/dist/client/assets/{regexp-BYNm_E6v.js → regexp-DWt_neai.js} +2 -2
  58. package/dist/client/assets/{rst-BMMyVK7q.js → rst-CDLeyhcr.js} +2 -2
  59. package/dist/client/assets/{ruby-BBl_q7lV.js → ruby-Bq-7Grqk.js} +2 -2
  60. package/dist/client/assets/{sas-q_pdbMhY.js → sas-Cn10Nqsd.js} +2 -2
  61. package/dist/client/assets/{scss-Bf5qLSyj.js → scss-821PYndD.js} +2 -2
  62. package/dist/client/assets/{shellscript-CnkO-FF6.js → shellscript-CGnWrTWh.js} +2 -2
  63. package/dist/client/assets/{shellsession-CGTIDec0.js → shellsession-SoJX0uTA.js} +2 -2
  64. package/dist/client/assets/{soy-D_X4Ckow.js → soy-BDpemwx6.js} +2 -2
  65. package/dist/client/assets/{sql-wOawmdfM.js → sql-BAre1tMG.js} +2 -2
  66. package/dist/client/assets/{stata-CKQN4lyV.js → stata-BVoUBbRH.js} +2 -2
  67. package/dist/client/assets/{surrealql-tjpKGjMP.js → surrealql-77kldaFv.js} +2 -2
  68. package/dist/client/assets/{svelte-mCzdghlf.js → svelte-BrVa4Wrm.js} +2 -2
  69. package/dist/client/assets/{templ-COV4wNUr.js → templ-DD-yE4Ei.js} +2 -2
  70. package/dist/client/assets/{tex-CC1f6JM7.js → tex-3Cd89BRS.js} +2 -2
  71. package/dist/client/assets/{textarea-BDADSCjn.js → textarea-DJbYd4DI.js} +24 -24
  72. package/dist/client/assets/{ts-tags-CrIJ77ir.js → ts-tags-CqnWMU9s.js} +2 -2
  73. package/dist/client/assets/{tsx-IX8SGkp3.js → tsx-C56Uu7is.js} +2 -2
  74. package/dist/client/assets/{twig-DXR_a9UJ.js → twig-2EWf2LgK.js} +2 -2
  75. package/dist/client/assets/{typescript-BgLtzzgs.js → typescript-DoXLYiyl.js} +2 -2
  76. package/dist/client/assets/{vue-Dzfv___3.js → vue-DIv66ZbN.js} +2 -2
  77. package/dist/client/assets/{vue-html-BHyzHcFt.js → vue-html-DJnU3oci.js} +2 -2
  78. package/dist/client/assets/{vue-vine-DOSBq7Kw.js → vue-vine-CnFyQ4Cm.js} +2 -2
  79. package/dist/client/assets/{xml-BI5bRQfx.js → xml-DflQf7I-.js} +2 -2
  80. package/dist/client/assets/{xsl-B4FzWFML.js → xsl-DegV0fmP.js} +2 -2
  81. package/dist/client/assets/{yaml-CHgKneKM.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-CXxWzaql.js +0 -2
  85. package/dist/client/assets/index-CTDey6k4.css +0 -1
  86. package/dist/client/assets/index-DJPm8QPz.js +0 -664
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.8";
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
  });
@@ -7076,6 +7072,15 @@ const AssigneeField = TrimmedNonEmptyString.check(Schema$1.isMaxLength(TASK_ASSI
7076
7072
  * no remote, where there is nothing stable to key on.
7077
7073
  */
7078
7074
  const RepositoryKeyField = TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "Canonical repository key (normalized git remote, e.g. github.com/owner/repo). Portable across machines, unlike projectId." }));
7075
+ /**
7076
+ * Manual board position within a column, as a lexicographic fractional key.
7077
+ *
7078
+ * Compared as a plain string: smaller sorts higher. Keys are minted by the
7079
+ * board between the neighbours of a drop, so moving one card writes one row
7080
+ * rather than renumbering the column. `null` means the task has never been
7081
+ * placed by hand and falls back to the priority/recency order.
7082
+ */
7083
+ const OrderKeyField = TrimmedNonEmptyString.pipe(Schema$1.annotateEncoded({ description: "Manual board position key. Lexicographic: smaller sorts higher in the column. Null means no manual position." }));
7079
7084
  const LabelsField = Schema$1.Array(TaskLabel).check(Schema$1.isMaxLength(TASK_LABELS_MAX_COUNT)).pipe(Schema$1.annotateEncoded({ description: "Free-form tags used to group and filter tasks." }));
7080
7085
  const TaskIdField = TaskId.pipe(Schema$1.annotateEncoded({ description: "Id of the task to act on - the UUID or the readable id (e.g. P4-12)." }));
7081
7086
  const Task = Schema$1.Struct({
@@ -7089,6 +7094,15 @@ const Task = Schema$1.Struct({
7089
7094
  */
7090
7095
  readableId: Schema$1.NullOr(TrimmedNonEmptyString).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
7091
7096
  /**
7097
+ * Manual board position: a lexicographic fractional key, compared as a plain
7098
+ * string, smaller first. Written only when a card is dragged to a position;
7099
+ * `null` means "never placed by hand" and the board falls back to its
7100
+ * priority/recency order. Nullable-with-default for the same version-skew
7101
+ * reason as `readableId`: a row from a store without the migration decodes to
7102
+ * `null` rather than failing the whole board.
7103
+ */
7104
+ orderKey: Schema$1.NullOr(TrimmedNonEmptyString).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
7105
+ /**
7092
7106
  * The project this task belongs to, or `null` for a task that is not scoped
7093
7107
  * to one. This references the existing workspace project registry rather than
7094
7108
  * a task-board-private notion of a project.
@@ -7141,7 +7155,13 @@ const TaskCreateInput = Schema$1.Struct({
7141
7155
  */
7142
7156
  const HubTaskCreateInput = Schema$1.Struct({
7143
7157
  ...TaskCreateInput.fields,
7144
- readableIdPrefix: Schema$1.optional(TrimmedNonEmptyString)
7158
+ readableIdPrefix: Schema$1.optional(TrimmedNonEmptyString),
7159
+ /**
7160
+ * Carried on the hub's create route (but not the public one) so a server
7161
+ * mirroring an already-placed row to the hub does not lose its position.
7162
+ * An agent filing a new task has no business picking a slot.
7163
+ */
7164
+ orderKey: Schema$1.optional(OrderKeyField)
7145
7165
  });
7146
7166
  /**
7147
7167
  * Every field is optional and an omitted field is left untouched. The nullable
@@ -7158,7 +7178,8 @@ const TaskUpdateInput = Schema$1.Struct({
7158
7178
  status: Schema$1.optional(StatusField),
7159
7179
  priority: Schema$1.optional(PriorityField),
7160
7180
  assignee: Schema$1.optional(Schema$1.NullOr(AssigneeField)),
7161
- labels: Schema$1.optional(LabelsField)
7181
+ labels: Schema$1.optional(LabelsField),
7182
+ orderKey: Schema$1.optional(Schema$1.NullOr(OrderKeyField))
7162
7183
  });
7163
7184
  /**
7164
7185
  * A task an agent has drafted but has not filed.
@@ -12112,6 +12133,20 @@ var _037_TaskReadableIds_default = Effect.gen(function* () {
12112
12133
  `;
12113
12134
  });
12114
12135
  //#endregion
12136
+ //#region src/persistence/Migrations/038_TaskOrderKeys.ts
12137
+ /**
12138
+ * Manual board position (`order_key`): a lexicographic fractional key written
12139
+ * when a card is dragged to a position, so one move rewrites one row.
12140
+ *
12141
+ * Existing rows are deliberately not backfilled. A null key means "never placed
12142
+ * by hand", and the board's comparator falls back to the old priority/recency
12143
+ * order for those - so a board nobody has dragged on looks exactly as it did
12144
+ * before this migration.
12145
+ */
12146
+ var _038_TaskOrderKeys_default = Effect.gen(function* () {
12147
+ yield* (yield* SqlClient.SqlClient)`ALTER TABLE tasks ADD COLUMN order_key TEXT`;
12148
+ });
12149
+ //#endregion
12115
12150
  //#region src/persistence/Migrations.ts
12116
12151
  /**
12117
12152
  * MigrationsLive - Migration runner with inline loader
@@ -12317,6 +12352,11 @@ const migrationEntries = [
12317
12352
  37,
12318
12353
  "TaskReadableIds",
12319
12354
  _037_TaskReadableIds_default
12355
+ ],
12356
+ [
12357
+ 38,
12358
+ "TaskOrderKeys",
12359
+ _038_TaskOrderKeys_default
12320
12360
  ]
12321
12361
  ];
12322
12362
  const makeMigrationLoader = (throughId) => Migrator.fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -13950,6 +13990,7 @@ const TASK_COLUMNS = `
13950
13990
  priority,
13951
13991
  assignee,
13952
13992
  labels_json AS "labels",
13993
+ order_key AS "orderKey",
13953
13994
  created_at AS "createdAt",
13954
13995
  updated_at AS "updatedAt"
13955
13996
  `;
@@ -13972,6 +14013,7 @@ const makeTaskRepository = Effect.gen(function* () {
13972
14013
  priority,
13973
14014
  assignee,
13974
14015
  labels_json,
14016
+ order_key,
13975
14017
  created_at,
13976
14018
  updated_at
13977
14019
  )
@@ -13987,6 +14029,7 @@ const makeTaskRepository = Effect.gen(function* () {
13987
14029
  ${row.priority},
13988
14030
  ${row.assignee},
13989
14031
  ${JSON.stringify(row.labels)},
14032
+ ${row.orderKey},
13990
14033
  ${row.createdAt},
13991
14034
  ${row.updatedAt}
13992
14035
  )
@@ -14002,6 +14045,7 @@ const makeTaskRepository = Effect.gen(function* () {
14002
14045
  priority = excluded.priority,
14003
14046
  assignee = excluded.assignee,
14004
14047
  labels_json = excluded.labels_json,
14048
+ order_key = excluded.order_key,
14005
14049
  created_at = excluded.created_at,
14006
14050
  updated_at = excluded.updated_at
14007
14051
  `
@@ -14099,6 +14143,7 @@ const makeTaskRepository = Effect.gen(function* () {
14099
14143
  ...input.priority !== void 0 ? { priority: input.priority } : {},
14100
14144
  ...input.assignee !== void 0 ? { assignee: input.assignee } : {},
14101
14145
  ...input.labels !== void 0 ? { labels: input.labels } : {},
14146
+ ...input.orderKey !== void 0 ? { orderKey: input.orderKey } : {},
14102
14147
  updatedAt
14103
14148
  };
14104
14149
  yield* upsertTaskRow(updated);
@@ -14136,6 +14181,7 @@ const makeTaskRepository = Effect.gen(function* () {
14136
14181
  ...input.patch.priority !== void 0 ? { priority: input.patch.priority } : {},
14137
14182
  ...input.patch.assignee !== void 0 ? { assignee: input.patch.assignee } : {},
14138
14183
  ...input.patch.labels !== void 0 ? { labels: input.patch.labels } : {},
14184
+ ...input.patch.orderKey !== void 0 ? { orderKey: input.patch.orderKey } : {},
14139
14185
  updatedAt
14140
14186
  };
14141
14187
  yield* upsertTaskRow(updated);
@@ -14268,6 +14314,7 @@ const createTaskRoute = HttpRouter.add("POST", "/tasks", respondToHubFailures(Ef
14268
14314
  priority: input.priority ?? "none",
14269
14315
  assignee: input.assignee ?? null,
14270
14316
  labels: input.labels ?? [],
14317
+ orderKey: input.orderKey ?? null,
14271
14318
  createdAt: timestamp,
14272
14319
  updatedAt: timestamp
14273
14320
  };
@@ -14554,6 +14601,16 @@ var _004_TaskReadableIds_default = Effect.gen(function* () {
14554
14601
  `;
14555
14602
  });
14556
14603
  //#endregion
14604
+ //#region src/hub/Migrations/005_TaskOrderKeys.ts
14605
+ /**
14606
+ * Manual board position (`order_key`), mirroring the environment server's
14607
+ * migration 038: a lexicographic fractional key written when a card is dragged
14608
+ * to a position. No backfill - null falls back to priority/recency order.
14609
+ */
14610
+ var _005_TaskOrderKeys_default = Effect.gen(function* () {
14611
+ yield* (yield* SqlClient.SqlClient)`ALTER TABLE tasks ADD COLUMN order_key TEXT`;
14612
+ });
14613
+ //#endregion
14557
14614
  //#region src/hub/Migrations.ts
14558
14615
  /**
14559
14616
  * Hub migrations.
@@ -14584,6 +14641,11 @@ const hubMigrationEntries = [
14584
14641
  4,
14585
14642
  "TaskReadableIds",
14586
14643
  _004_TaskReadableIds_default
14644
+ ],
14645
+ [
14646
+ 5,
14647
+ "TaskOrderKeys",
14648
+ _005_TaskOrderKeys_default
14587
14649
  ]
14588
14650
  ];
14589
14651
  const hubMigrationLoader = Migrator.fromRecord(Object.fromEntries(hubMigrationEntries.map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -28924,222 +28986,868 @@ const staticAndDevRouteLayer = HttpRouter.add("GET", "*", Effect.gen(function* (
28924
28986
  contentType
28925
28987
  });
28926
28988
  }));
28927
- //#endregion
28928
- //#region src/checkpointing/Errors.ts
28929
- const CheckpointDiffOperation = Schema$1.Literals(["CheckpointDiffQuery.getTurnDiff", "CheckpointDiffQuery.getFullThreadDiff"]);
28930
- /** The computed result does not satisfy the checkpoint RPC contract. */
28931
- var CheckpointDiffResultInvalidError = class extends Schema$1.TaggedErrorClass()("CheckpointDiffResultInvalidError", {
28932
- operation: CheckpointDiffOperation,
28933
- threadId: ThreadId
28934
- }) {
28935
- get message() {
28936
- const result = this.operation === "CheckpointDiffQuery.getTurnDiff" ? "turn diff" : "full thread diff";
28937
- return `Checkpoint invariant violation in ${this.operation}: Computed ${result} result does not satisfy contract schema.`;
28938
- }
28939
- };
28940
- /** Projection state no longer contains the requested checkpoint thread. */
28941
- var CheckpointThreadNotFoundError = class extends Schema$1.TaggedErrorClass()("CheckpointThreadNotFoundError", {
28942
- operation: CheckpointDiffOperation,
28943
- threadId: ThreadId
28944
- }) {
28945
- get message() {
28946
- return `Checkpoint invariant violation in ${this.operation}: Thread '${this.threadId}' not found.`;
28947
- }
28948
- };
28949
- /** The checkpoint thread has no workspace path from which to compute a diff. */
28950
- var CheckpointWorkspacePathMissingError = class extends Schema$1.TaggedErrorClass()("CheckpointWorkspacePathMissingError", {
28951
- operation: CheckpointDiffOperation,
28952
- threadId: ThreadId
28953
- }) {
28954
- get message() {
28955
- const diff = this.operation === "CheckpointDiffQuery.getTurnDiff" ? "turn diff" : "full thread diff";
28956
- return `Checkpoint invariant violation in ${this.operation}: Workspace path missing for thread '${this.threadId}' when computing ${diff}.`;
28957
- }
28958
- };
28959
- /** The requested turn lies beyond the latest available checkpoint. */
28960
- var CheckpointTurnRangeUnavailableError = class extends Schema$1.TaggedErrorClass()("CheckpointTurnRangeUnavailableError", {
28961
- operation: CheckpointDiffOperation,
28962
- threadId: ThreadId,
28963
- requestedTurnCount: NonNegativeInt,
28964
- availableTurnCount: NonNegativeInt
28965
- }) {
28966
- get message() {
28967
- 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;
28968
28994
  }
28969
- };
28970
- /** Expected checkpoint metadata does not contain the requested Git ref. */
28971
- var CheckpointRefUnavailableError = class extends Schema$1.TaggedErrorClass()("CheckpointRefUnavailableError", {
28972
- operation: CheckpointDiffOperation,
28973
- threadId: ThreadId,
28974
- turnCount: NonNegativeInt,
28975
- checkpoint: Schema$1.Literals(["from", "to"])
28976
- }) {
28977
- get message() {
28978
- 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--;
28979
29013
  }
28980
- };
29014
+ return contents.slice(0, end);
29015
+ }
28981
29016
  //#endregion
28982
- //#region src/checkpointing/Utils.ts
28983
- const CHECKPOINT_REFS_PREFIX = "refs/p4/checkpoints";
28984
- function checkpointRefForThreadTurn(threadId, turnCount) {
28985
- 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);
28986
29025
  }
28987
- function resolveThreadWorkspaceCwd(input) {
28988
- const worktreeCwd = input.thread.worktreePath ?? void 0;
28989
- if (worktreeCwd) return worktreeCwd;
28990
- 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));
28991
29033
  }
28992
29034
  //#endregion
28993
- //#region src/git/remoteRefs.ts
28994
- function parseRemoteNamesInGitOrder(stdout) {
28995
- const remoteNames = [];
28996
- for (const line of stdout.split("\n")) {
28997
- const remoteName = line.trim();
28998
- 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();
28999
29041
  }
29000
- return remoteNames;
29001
- }
29002
- function parseRemoteNames(stdout) {
29003
- return parseRemoteNamesInGitOrder(stdout).toSorted((a, b) => b.length - a.length);
29004
29042
  }
29005
- function parseRemoteRefWithRemoteNames(ref, remoteNames) {
29006
- const trimmedRef = ref.trim();
29007
- if (trimmedRef.length === 0) return null;
29008
- for (const remoteName of remoteNames) {
29009
- const remotePrefix = `${remoteName}/`;
29010
- if (!trimmedRef.startsWith(remotePrefix)) continue;
29011
- const branchName = trimmedRef.slice(remotePrefix.length).trim();
29012
- if (branchName.length === 0) return null;
29013
- return {
29014
- remoteRef: trimmedRef,
29015
- remoteName,
29016
- branchName
29017
- };
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);
29018
29066
  }
29019
- return null;
29020
- }
29021
- function extractBranchNameFromRemoteRef(ref, options) {
29022
- const normalized = ref.trim();
29023
- if (normalized.length === 0) return "";
29024
- if (normalized.startsWith("refs/remotes/")) return extractBranchNameFromRemoteRef(normalized.slice(13), options);
29025
- const parsedRemoteRef = parseRemoteRefWithRemoteNames(normalized, options?.remoteName ? [options.remoteName] : options?.remoteNames ?? []);
29026
- if (parsedRemoteRef) return parsedRemoteRef.branchName;
29027
- const firstSlash = normalized.indexOf("/");
29028
- if (firstSlash === -1) return normalized;
29029
- return normalized.slice(firstSlash + 1).trim();
29030
- }
29031
- //#endregion
29032
- //#region src/vcs/GitVcsDriverCore.ts
29033
- const DEFAULT_TIMEOUT_MS$5 = 3e4;
29034
- const DEFAULT_MAX_OUTPUT_BYTES$1 = 1e6;
29035
- const OUTPUT_TRUNCATED_MARKER$1 = "\n\n[truncated]";
29036
- const PREPARED_COMMIT_PATCH_MAX_OUTPUT_BYTES = 49e3;
29037
- const RANGE_COMMIT_SUMMARY_MAX_OUTPUT_BYTES = 19e3;
29038
- const RANGE_DIFF_SUMMARY_MAX_OUTPUT_BYTES = 19e3;
29039
- const RANGE_DIFF_PATCH_MAX_OUTPUT_BYTES = 59e3;
29040
- const REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES = 12e4;
29041
- const REVIEW_UNTRACKED_DIFF_MAX_OUTPUT_BYTES = 8e4;
29042
- const WORKSPACE_FILES_MAX_OUTPUT_BYTES$1 = 12e4;
29043
- const STATUS_UPSTREAM_REFRESH_INTERVAL = Duration.seconds(15);
29044
- const STATUS_UPSTREAM_REFRESH_TIMEOUT = Duration.seconds(5);
29045
- const STATUS_UPSTREAM_REFRESH_FAILURE_BASE_COOLDOWN = Duration.seconds(30);
29046
- const STATUS_UPSTREAM_REFRESH_FAILURE_MAX_COOLDOWN = Duration.minutes(15);
29047
- const STATUS_UPSTREAM_REFRESH_CACHE_CAPACITY = 2048;
29048
- const REPOSITORY_PATHS_CACHE_CAPACITY = 2048;
29049
- const REPOSITORY_PATHS_CACHE_TTL = Duration.minutes(10);
29050
- const REPOSITORY_PATHS_REFRESH_COALESCE_TTL = Duration.seconds(5);
29051
- const NON_REPOSITORY_PATHS_CACHE_TTL = Duration.seconds(1);
29052
- const LIST_REFS_SNAPSHOT_CACHE_CAPACITY = 64;
29053
- const LIST_REFS_SNAPSHOT_CACHE_TTL = Duration.minutes(2);
29054
- const LIST_REFS_REFRESH_COALESCE_TTL = Duration.seconds(5);
29055
- const LIST_REFS_REFRESH_FAILURE_COOLDOWN = Duration.seconds(30);
29056
- const STATUS_UPSTREAM_REFRESH_ENV = Object.freeze({
29057
- GCM_INTERACTIVE: "never",
29058
- GIT_ASKPASS: "",
29059
- GIT_TERMINAL_PROMPT: "0",
29060
- SSH_ASKPASS: "",
29061
- SSH_ASKPASS_REQUIRE: "never"
29062
- });
29063
- const DEFAULT_BASE_BRANCH_CANDIDATES = ["main", "master"];
29064
- const GIT_LIST_BRANCHES_DEFAULT_LIMIT = 100;
29065
- const NON_REPOSITORY_STATUS_DETAILS = Object.freeze({
29066
- isRepo: false,
29067
- hasOriginRemote: false,
29068
- isDefaultBranch: false,
29069
- branch: null,
29070
- upstreamRef: null,
29071
- hasWorkingTreeChanges: false,
29072
- workingTree: {
29073
- files: [],
29074
- insertions: 0,
29075
- deletions: 0
29076
- },
29077
- hasUpstream: false,
29078
- aheadCount: 0,
29079
- behindCount: 0,
29080
- aheadOfDefaultCount: 0
29081
- });
29082
- const NON_REPOSITORY_REMOTE_STATUS_DETAILS = Object.freeze({
29083
- isRepo: false,
29084
- isDefaultBranch: false,
29085
- branch: null,
29086
- upstreamRef: null,
29087
- hasUpstream: false,
29088
- aheadCount: 0,
29089
- behindCount: 0,
29090
- aheadOfDefaultCount: 0
29091
- });
29092
- var StatusRemoteRefreshCacheKey = class extends Data.Class {};
29093
- function statusUpstreamRefreshFailureCooldown(consecutiveFailures) {
29094
- const exponent = Math.max(0, consecutiveFailures - 1);
29095
- const cooldownMs = Duration.toMillis(STATUS_UPSTREAM_REFRESH_FAILURE_BASE_COOLDOWN) * Math.pow(2, exponent);
29096
- return Duration.min(Duration.millis(cooldownMs), STATUS_UPSTREAM_REFRESH_FAILURE_MAX_COOLDOWN);
29097
- }
29098
- var GitRefsSnapshotCacheKey = class extends Data.Class {};
29099
- var GitRefsRefreshCacheKey = class extends Data.Class {};
29100
- function parseBranchAb(value) {
29101
- const match = value.match(/^\+(\d+)\s+-(\d+)$/);
29102
- if (!match) return {
29103
- ahead: 0,
29104
- behind: 0
29105
- };
29106
29067
  return {
29107
- ahead: Number(match[1] ?? "0"),
29108
- behind: Number(match[2] ?? "0")
29068
+ patchMetadata,
29069
+ files
29109
29070
  };
29110
29071
  }
29111
- function parseNumstatEntries(stdout) {
29112
- const entries = [];
29113
- for (const line of stdout.split(/\r?\n/g)) {
29114
- if (line.trim().length === 0) continue;
29115
- const [addedRaw, deletedRaw, ...pathParts] = line.split(" ");
29116
- const rawPath = pathParts.length > 1 ? (pathParts.at(-1) ?? "").trim() : pathParts.join(" ").trim();
29117
- if (rawPath.length === 0) continue;
29118
- const added = Number.parseInt(addedRaw ?? "0", 10);
29119
- const deleted = Number.parseInt(deletedRaw ?? "0", 10);
29120
- const renameArrowIndex = rawPath.indexOf(" => ");
29121
- const normalizedPath = renameArrowIndex >= 0 ? rawPath.slice(renameArrowIndex + 4).trim() : rawPath;
29122
- entries.push({
29123
- path: normalizedPath.length > 0 ? normalizedPath : rawPath,
29124
- insertions: Number.isFinite(added) ? added : 0,
29125
- deletions: Number.isFinite(deleted) ? deleted : 0
29126
- });
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;
29127
29274
  }
29128
- return entries;
29129
- }
29130
- function parsePorcelainPath(line) {
29131
- if (line.startsWith("? ") || line.startsWith("! ")) {
29132
- const simple = line.slice(2).trim();
29133
- 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;
29134
29284
  }
29135
- if (!(line.startsWith("1 ") || line.startsWith("2 ") || line.startsWith("u "))) return null;
29136
- const tabIndex = line.indexOf(" ");
29137
- if (tabIndex >= 0) {
29138
- const [filePath] = line.slice(tabIndex + 1).split(" ");
29139
- 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";
29140
29290
  }
29141
- const filePath = line.trim().split(/\s+/g).at(-1) ?? "";
29142
- return filePath.length > 0 ? filePath : null;
29291
+ if (currentFile.type !== "rename-pure" && currentFile.type !== "rename-changed") currentFile.prevName = void 0;
29292
+ return currentFile;
29293
+ }
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;
29143
29851
  }
29144
29852
  function filterBranchesForListQuery(refs, query) {
29145
29853
  if (!query) return refs;
@@ -31530,6 +32238,32 @@ function buildTurnDiffResult(input, diff) {
31530
32238
  const make$39 = Effect.gen(function* () {
31531
32239
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
31532
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);
31533
32267
  const getTurnDiff = Effect.fn("getTurnDiff")(function* (input) {
31534
32268
  const operation = "CheckpointDiffQuery.getTurnDiff";
31535
32269
  const ignoreWhitespace = input.ignoreWhitespace ?? true;
@@ -31583,13 +32317,16 @@ const make$39 = Effect.gen(function* () {
31583
32317
  turnCount: input.toTurnCount,
31584
32318
  checkpoint: "to"
31585
32319
  });
31586
- const turnDiff = buildTurnDiffResult(input, yield* checkpointStore.diffCheckpoints({
32320
+ const diff = yield* checkpointStore.diffCheckpoints({
31587
32321
  cwd: workspaceCwd,
31588
32322
  fromCheckpointRef,
31589
32323
  toCheckpointRef,
31590
32324
  fallbackFromToHead: false,
31591
32325
  ignoreWhitespace
31592
- }).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));
31593
32330
  if (!isTurnDiffResult(turnDiff)) return yield* new CheckpointDiffResultInvalidError({
31594
32331
  operation,
31595
32332
  threadId: input.threadId
@@ -31647,11 +32384,12 @@ const make$39 = Effect.gen(function* () {
31647
32384
  fallbackFromToHead: false,
31648
32385
  ignoreWhitespace
31649
32386
  }).pipe(Effect.withSpan("checkpoint.fullThread.diffCheckpoints"));
32387
+ const changedPaths = yield* collectThreadChangedFilePaths(input.threadId, null);
31650
32388
  const turnDiff = buildTurnDiffResult({
31651
32389
  threadId: input.threadId,
31652
32390
  fromTurnCount: 0,
31653
32391
  toTurnCount: input.toTurnCount
31654
- }, diff);
32392
+ }, attributeDiffToThread(diff, changedPaths, workspaceCwd));
31655
32393
  if (!isTurnDiffResult(turnDiff)) return yield* new CheckpointDiffResultInvalidError({
31656
32394
  operation,
31657
32395
  threadId: input.threadId
@@ -32231,7 +32969,7 @@ const normalizeDispatchCommand = (command) => Effect.gen(function* () {
32231
32969
  const buildTaskRow = (input, taskId, timestamp, repositoryKey = null) => ({
32232
32970
  taskId,
32233
32971
  projectId: input.projectId ?? null,
32234
- repositoryKey,
32972
+ repositoryKey: repositoryKey ?? input.repositoryKey ?? null,
32235
32973
  threadId: input.threadId ?? null,
32236
32974
  title: input.title,
32237
32975
  body: input.body ?? "",
@@ -32239,6 +32977,7 @@ const buildTaskRow = (input, taskId, timestamp, repositoryKey = null) => ({
32239
32977
  priority: input.priority ?? "none",
32240
32978
  assignee: input.assignee ?? null,
32241
32979
  labels: input.labels ?? [],
32980
+ orderKey: null,
32242
32981
  createdAt: timestamp,
32243
32982
  updatedAt: timestamp
32244
32983
  });
@@ -43349,6 +44088,7 @@ const makeRemoteTaskRepository = Effect.fn("makeRemoteTaskRepository")(function*
43349
44088
  priority: row.priority,
43350
44089
  ...row.assignee === null ? {} : { assignee: row.assignee },
43351
44090
  labels: row.labels,
44091
+ ...row.orderKey === null ? {} : { orderKey: row.orderKey },
43352
44092
  ...readableIdPrefix === void 0 ? {} : { readableIdPrefix }
43353
44093
  }));
43354
44094
  if (response.status !== 201 && response.status !== 200) return yield* hubUnavailable(operation, `status ${response.status}`);
@@ -83332,538 +84072,6 @@ const make$2 = Effect.gen(function* () {
83332
84072
  };
83333
84073
  });
83334
84074
  const ProviderCommandReactorLive = Layer.effect(ProviderCommandReactor, make$2);
83335
- (() => {
83336
- try {
83337
- return process.env.NODE_ENV === "development";
83338
- } catch {
83339
- return false;
83340
- }
83341
- })();
83342
- const COMMIT_METADATA_SPLIT = /(?=^From [a-f0-9]+ .+$)/m;
83343
- const GIT_DIFF_FILE_BREAK_REGEX = /(?=^diff --git)/gm;
83344
- const FILENAME_HEADER_REGEX = /^(---|\+\+\+)\s+([^\t\r\n]+)/;
83345
- const FILENAME_HEADER_REGEX_GIT = /^(---|\+\+\+)\s+[ab]\/([^\t\r\n]+)/;
83346
- const ALTERNATE_FILE_NAMES_GIT = /^diff --git (?:"a\/(.+?)"|a\/(.+?)) (?:"b\/(.+?)"|b\/(.+?))$/;
83347
- const INDEX_LINE_METADATA = /^index ([0-9a-f]+)\.\.([0-9a-f]+)(?: (\d+))?$/i;
83348
- Object.freeze({
83349
- fromStart: 0,
83350
- fromEnd: 0
83351
- });
83352
- //#endregion
83353
- //#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/cleanLastNewline.js
83354
- function cleanLastNewline(contents) {
83355
- let end = contents.length;
83356
- if (contents.charCodeAt(end - 1) === 10) {
83357
- end--;
83358
- if (contents.charCodeAt(end - 1) === 13) end--;
83359
- }
83360
- return contents.slice(0, end);
83361
- }
83362
- //#endregion
83363
- //#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/detachString.js
83364
- const stringDetachEncoder = new TextEncoder();
83365
- const stringDetachDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
83366
- const SURROGATE_CODE_UNIT_PATTERN = /[\uD800-\uDFFF]/;
83367
- const STRING_DETACH_INITIAL_BUFFER_SIZE = 1024;
83368
- let stringDetachBuffer = new Uint8Array(STRING_DETACH_INITIAL_BUFFER_SIZE);
83369
- function releaseStringDetachBuffer() {
83370
- if (stringDetachBuffer.length !== STRING_DETACH_INITIAL_BUFFER_SIZE) stringDetachBuffer = new Uint8Array(STRING_DETACH_INITIAL_BUFFER_SIZE);
83371
- }
83372
- function detachString(value) {
83373
- if (value.length === 0) return value;
83374
- if (SURROGATE_CODE_UNIT_PATTERN.test(value)) return JSON.parse(JSON.stringify(value));
83375
- const requiredByteLength = value.length * 3;
83376
- if (stringDetachBuffer.length < requiredByteLength) stringDetachBuffer = new Uint8Array(requiredByteLength);
83377
- const { written } = stringDetachEncoder.encodeInto(value, stringDetachBuffer);
83378
- return stringDetachDecoder.decode(stringDetachBuffer.subarray(0, written));
83379
- }
83380
- //#endregion
83381
- //#region ../../node_modules/.pnpm/@pierre+diffs@1.3.0-beta.5_patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b3_3b51331382fbad9d080c5033ee1e2a6a/node_modules/@pierre/diffs/dist/utils/parsePatchFiles.js
83382
- function processPatch(data, cacheKeyPrefix, throwOnError) {
83383
- try {
83384
- return _processPatch(data, cacheKeyPrefix, throwOnError);
83385
- } finally {
83386
- releaseStringDetachBuffer();
83387
- }
83388
- }
83389
- function _processPatch(data, cacheKeyPrefix, throwOnError = false) {
83390
- const isGitDiff = isGitDiffPatch(data);
83391
- const rawFiles = isGitDiff ? splitGitDiffFiles(data) : splitUnifiedDiffFiles(data);
83392
- let patchMetadata;
83393
- const files = [];
83394
- for (const fileOrPatchMetadata of rawFiles) {
83395
- if (isGitDiff && !GIT_DIFF_FILE_BREAK_REGEX.test(fileOrPatchMetadata)) {
83396
- if (patchMetadata == null) patchMetadata = detachString(fileOrPatchMetadata);
83397
- else if (throwOnError) throw Error("parsePatchContent: unknown file blob");
83398
- else console.error("parsePatchContent: unknown file blob:", fileOrPatchMetadata);
83399
- continue;
83400
- } else if (!isGitDiff && !startsWithUnifiedDiffFileHeader(fileOrPatchMetadata)) {
83401
- if (patchMetadata == null) patchMetadata = detachString(fileOrPatchMetadata);
83402
- else if (throwOnError) throw Error("parsePatchContent: unknown file blob");
83403
- else console.error("parsePatchContent: unknown file blob:", fileOrPatchMetadata);
83404
- continue;
83405
- }
83406
- const currentFile = _processFile(fileOrPatchMetadata, {
83407
- cacheKey: cacheKeyPrefix != null ? `${cacheKeyPrefix}-${files.length}` : void 0,
83408
- isGitDiff,
83409
- throwOnError
83410
- });
83411
- if (currentFile != null) files.push(currentFile);
83412
- }
83413
- return {
83414
- patchMetadata,
83415
- files
83416
- };
83417
- }
83418
- function _processFile(fileDiffString, { cacheKey, isGitDiff = GIT_DIFF_FILE_BREAK_REGEX.test(fileDiffString), oldFile, newFile, throwOnError = false } = {}) {
83419
- let lastHunkEnd = 0;
83420
- const hunks = splitAtLinePrefix(fileDiffString, "@@ ");
83421
- let currentFile;
83422
- const isPartial = oldFile == null || newFile == null;
83423
- let deletionLineIndex = 0;
83424
- let additionLineIndex = 0;
83425
- for (const hunk of hunks) {
83426
- const lines = splitWithNewlines(hunk);
83427
- const firstLine = lines[0];
83428
- if (firstLine == null) {
83429
- if (throwOnError) throw Error("parsePatchContent: invalid hunk");
83430
- else console.error("parsePatchContent: invalid hunk", hunk);
83431
- continue;
83432
- }
83433
- const fileHeader = parseHunkHeader(firstLine);
83434
- let additionLines = 0;
83435
- let deletionLines = 0;
83436
- if (fileHeader == null || currentFile == null) {
83437
- if (currentFile != null) {
83438
- if (throwOnError) throw Error("parsePatchContent: Invalid hunk");
83439
- else console.error("parsePatchContent: Invalid hunk", hunk);
83440
- continue;
83441
- }
83442
- currentFile = {
83443
- name: "",
83444
- type: "change",
83445
- hunks: [],
83446
- splitLineCount: 0,
83447
- unifiedLineCount: 0,
83448
- isPartial,
83449
- additionLines: !isPartial && oldFile != null && newFile != null ? splitFileContents(newFile.contents) : [],
83450
- deletionLines: !isPartial && oldFile != null && newFile != null ? splitFileContents(oldFile.contents) : [],
83451
- cacheKey: maybeDetachOptionalString(cacheKey)
83452
- };
83453
- if (currentFile.additionLines.length === 1 && newFile?.contents === "") currentFile.additionLines.length = 0;
83454
- if (currentFile.deletionLines.length === 1 && oldFile?.contents === "") currentFile.deletionLines.length = 0;
83455
- for (const line of lines) {
83456
- if (line.startsWith("diff --git")) {
83457
- const filenameMatch$1 = line.trim().match(ALTERNATE_FILE_NAMES_GIT);
83458
- const prevName = filenameMatch$1?.[1] ?? filenameMatch$1?.[2];
83459
- const name = filenameMatch$1?.[3] ?? filenameMatch$1?.[4];
83460
- if (prevName == null || name == null) {
83461
- if (throwOnError) throw Error("parsePatchContent: invalid git diff header");
83462
- else console.error("parsePatchContent: invalid git diff header", line);
83463
- continue;
83464
- }
83465
- currentFile.name = detachString(name.trim());
83466
- if (prevName !== name) currentFile.prevName = detachString(prevName.trim());
83467
- continue;
83468
- }
83469
- const filenameMatch = line.startsWith("---") || line.startsWith("+++") ? line.match(isGitDiff ? FILENAME_HEADER_REGEX_GIT : FILENAME_HEADER_REGEX) : null;
83470
- if (filenameMatch != null) {
83471
- const [, type, fileName] = filenameMatch;
83472
- if (type === "---" && fileName !== "/dev/null") {
83473
- const detachedFileName = detachString(fileName.trim());
83474
- currentFile.prevName = detachedFileName;
83475
- currentFile.name = detachedFileName;
83476
- } else if (type === "+++" && fileName !== "/dev/null") currentFile.name = detachString(fileName.trim());
83477
- } else if (isGitDiff) {
83478
- if (line.startsWith("new mode ")) currentFile.mode = detachString(line.slice(8).trim());
83479
- if (line.startsWith("old mode ")) currentFile.prevMode = detachString(line.slice(8).trim());
83480
- if (line.startsWith("new file mode")) {
83481
- currentFile.type = "new";
83482
- currentFile.mode = detachString(line.slice(13).trim());
83483
- }
83484
- if (line.startsWith("deleted file mode")) {
83485
- currentFile.type = "deleted";
83486
- currentFile.mode = detachString(line.slice(17).trim());
83487
- }
83488
- if (line.startsWith("similarity index")) if (line.startsWith("similarity index 100%")) currentFile.type = "rename-pure";
83489
- else currentFile.type = "rename-changed";
83490
- if (line.startsWith("index ")) {
83491
- const [, prevObjectId, newObjectId, mode] = line.trim().match(INDEX_LINE_METADATA) ?? [];
83492
- if (prevObjectId != null) currentFile.prevObjectId = detachString(prevObjectId);
83493
- if (newObjectId != null) currentFile.newObjectId = detachString(newObjectId);
83494
- if (mode != null) currentFile.mode = detachString(mode);
83495
- }
83496
- if (line.startsWith("rename from ")) currentFile.prevName = detachString(line.slice(12).trim());
83497
- if (line.startsWith("rename to ")) currentFile.name = detachString(line.slice(10).trim());
83498
- }
83499
- }
83500
- continue;
83501
- }
83502
- let currentContent;
83503
- let lastLineType;
83504
- 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();
83505
- const { additionStart, deletionStart } = fileHeader;
83506
- deletionLineIndex = isPartial ? deletionLineIndex : deletionStart - 1;
83507
- additionLineIndex = isPartial ? additionLineIndex : additionStart - 1;
83508
- const hunkData = {
83509
- collapsedBefore: 0,
83510
- splitLineCount: 0,
83511
- splitLineStart: 0,
83512
- unifiedLineCount: 0,
83513
- unifiedLineStart: 0,
83514
- additionCount: fileHeader.additionCount,
83515
- additionStart,
83516
- additionLines,
83517
- deletionCount: fileHeader.deletionCount,
83518
- deletionStart,
83519
- deletionLines,
83520
- deletionLineIndex,
83521
- additionLineIndex,
83522
- hunkContent: [],
83523
- hunkContext: maybeDetachOptionalString(fileHeader.hunkContext),
83524
- hunkSpecs: detachString(firstLine),
83525
- noEOFCRAdditions: false,
83526
- noEOFCRDeletions: false
83527
- };
83528
- let parsedAdditionLines = 0;
83529
- let parsedDeletionLines = 0;
83530
- for (let lineIndex = 1; lineIndex < lines.length; lineIndex++) {
83531
- const rawLine = lines[lineIndex];
83532
- if (parsedAdditionLines >= hunkData.additionCount && parsedDeletionLines >= hunkData.deletionCount && !rawLine.startsWith("\\")) {
83533
- if (throwOnError && isHunkBodyLine(rawLine) && !isFormatPatchVersionSeparator(rawLine)) throw Error("parsePatchContent: hunk has more lines than expected");
83534
- break;
83535
- }
83536
- const firstChar = rawLine[0];
83537
- if (firstChar !== "+" && firstChar !== "-" && firstChar !== " " && firstChar !== "\\") {
83538
- if (throwOnError) throw Error("parsePatchContent: invalid hunk line");
83539
- console.error(`parseLineType: Invalid firstChar: "${firstChar}", full line: "${rawLine}"`);
83540
- console.error("processFile: invalid rawLine:", rawLine);
83541
- continue;
83542
- }
83543
- const type = parseRawLineType(firstChar);
83544
- if (type === "addition") {
83545
- if (throwOnError && parsedAdditionLines >= hunkData.additionCount) throw Error("parsePatchContent: hunk has too many addition lines");
83546
- const line = getParsedLineContent(rawLine);
83547
- if (currentContent == null || currentContent.type !== "change") {
83548
- currentContent = createContentGroup("change", deletionLineIndex, additionLineIndex);
83549
- hunkData.hunkContent.push(currentContent);
83550
- }
83551
- additionLineIndex++;
83552
- parsedAdditionLines++;
83553
- if (isPartial) currentFile.additionLines.push(line);
83554
- currentContent.additions++;
83555
- additionLines++;
83556
- lastLineType = "addition";
83557
- } else if (type === "deletion") {
83558
- if (throwOnError && parsedDeletionLines >= hunkData.deletionCount) throw Error("parsePatchContent: hunk has too many deletion lines");
83559
- const line = getParsedLineContent(rawLine);
83560
- if (currentContent == null || currentContent.type !== "change") {
83561
- currentContent = createContentGroup("change", deletionLineIndex, additionLineIndex);
83562
- hunkData.hunkContent.push(currentContent);
83563
- }
83564
- deletionLineIndex++;
83565
- parsedDeletionLines++;
83566
- if (isPartial) currentFile.deletionLines.push(line);
83567
- currentContent.deletions++;
83568
- deletionLines++;
83569
- lastLineType = "deletion";
83570
- } else if (type === "context") {
83571
- if (throwOnError && (parsedDeletionLines >= hunkData.deletionCount || parsedAdditionLines >= hunkData.additionCount)) throw Error("parsePatchContent: hunk has too many context lines");
83572
- const line = getParsedLineContent(rawLine);
83573
- if (currentContent == null || currentContent.type !== "context") {
83574
- currentContent = createContentGroup("context", deletionLineIndex, additionLineIndex);
83575
- hunkData.hunkContent.push(currentContent);
83576
- }
83577
- additionLineIndex++;
83578
- deletionLineIndex++;
83579
- parsedAdditionLines++;
83580
- parsedDeletionLines++;
83581
- if (isPartial) {
83582
- currentFile.deletionLines.push(line);
83583
- currentFile.additionLines.push(line);
83584
- }
83585
- currentContent.lines++;
83586
- lastLineType = "context";
83587
- } else if (type === "metadata" && currentContent != null) {
83588
- if (currentContent.type === "context") {
83589
- hunkData.noEOFCRAdditions = true;
83590
- hunkData.noEOFCRDeletions = true;
83591
- } else if (lastLineType === "deletion") hunkData.noEOFCRDeletions = true;
83592
- else if (lastLineType === "addition") hunkData.noEOFCRAdditions = true;
83593
- if (isPartial && (lastLineType === "addition" || lastLineType === "context")) {
83594
- const lastIndex = currentFile.additionLines.length - 1;
83595
- if (lastIndex >= 0) currentFile.additionLines[lastIndex] = cleanLastNewline(currentFile.additionLines[lastIndex]);
83596
- }
83597
- if (isPartial && (lastLineType === "deletion" || lastLineType === "context")) {
83598
- const lastIndex = currentFile.deletionLines.length - 1;
83599
- if (lastIndex >= 0) currentFile.deletionLines[lastIndex] = cleanLastNewline(currentFile.deletionLines[lastIndex]);
83600
- }
83601
- }
83602
- }
83603
- if (throwOnError && (parsedAdditionLines !== hunkData.additionCount || parsedDeletionLines !== hunkData.deletionCount)) throw Error("parsePatchContent: hunk line count mismatch");
83604
- hunkData.additionLines = additionLines;
83605
- hunkData.deletionLines = deletionLines;
83606
- hunkData.collapsedBefore = Math.max(hunkData.additionStart - 1 - lastHunkEnd, 0);
83607
- currentFile.hunks.push(hunkData);
83608
- lastHunkEnd = hunkData.additionStart + hunkData.additionCount - 1;
83609
- for (const content of hunkData.hunkContent) if (content.type === "context") {
83610
- hunkData.splitLineCount += content.lines;
83611
- hunkData.unifiedLineCount += content.lines;
83612
- } else {
83613
- hunkData.splitLineCount += Math.max(content.additions, content.deletions);
83614
- hunkData.unifiedLineCount += content.deletions + content.additions;
83615
- }
83616
- hunkData.splitLineStart = currentFile.splitLineCount + hunkData.collapsedBefore;
83617
- hunkData.unifiedLineStart = currentFile.unifiedLineCount + hunkData.collapsedBefore;
83618
- currentFile.splitLineCount += hunkData.collapsedBefore + hunkData.splitLineCount;
83619
- currentFile.unifiedLineCount += hunkData.collapsedBefore + hunkData.unifiedLineCount;
83620
- }
83621
- if (currentFile == null) return;
83622
- if (throwOnError && isPartial && !isGitDiff && currentFile.hunks.length === 0) throw Error("parsePatchContent: unified file has no hunks");
83623
- if (currentFile.hunks.length > 0 && !isPartial && currentFile.additionLines.length > 0 && currentFile.deletionLines.length > 0) {
83624
- const lastHunk = currentFile.hunks[currentFile.hunks.length - 1];
83625
- const lastHunkEnd$1 = lastHunk.additionStart + lastHunk.additionCount - 1;
83626
- const totalFileLines = currentFile.additionLines.length;
83627
- const collapsedAfter = Math.max(totalFileLines - lastHunkEnd$1, 0);
83628
- currentFile.splitLineCount += collapsedAfter;
83629
- currentFile.unifiedLineCount += collapsedAfter;
83630
- }
83631
- if (!isGitDiff) {
83632
- if (currentFile.prevName != null && currentFile.name !== currentFile.prevName) if (currentFile.hunks.length > 0) currentFile.type = "rename-changed";
83633
- else currentFile.type = "rename-pure";
83634
- else if ((oldFile == null || oldFile.contents === "") && newFile != null && newFile.contents !== "") currentFile.type = "new";
83635
- else if (oldFile != null && oldFile.contents !== "" && (newFile == null || newFile.contents === "")) currentFile.type = "deleted";
83636
- }
83637
- if (currentFile.type !== "rename-pure" && currentFile.type !== "rename-changed") currentFile.prevName = void 0;
83638
- return currentFile;
83639
- }
83640
- /**
83641
- * Parses a patch file string into an array of parsed patches.
83642
- *
83643
- * @param data - The raw patch file content (supports multi-commit patches)
83644
- * @param cacheKeyPrefix - Optional prefix for generating cache keys. When provided,
83645
- * each file in the patch will get a cache key in the format `prefix-patchIndex-fileIndex`.
83646
- * This enables caching of rendered diff results in the worker pool.
83647
- */
83648
- function parsePatchFiles(data, cacheKeyPrefix, throwOnError = false) {
83649
- const patches = [];
83650
- const rawPatches = hasCommitMetadataBoundary(data) ? data.split(COMMIT_METADATA_SPLIT) : [data];
83651
- for (const patch of rawPatches) try {
83652
- patches.push(processPatch(patch, cacheKeyPrefix != null ? `${cacheKeyPrefix}-${patches.length}` : void 0, throwOnError));
83653
- } catch (error) {
83654
- if (throwOnError) throw error;
83655
- else console.error(error);
83656
- }
83657
- return patches;
83658
- }
83659
- function hasCommitMetadataBoundary(data) {
83660
- return data.startsWith("From ") || data.includes("\nFrom ");
83661
- }
83662
- function splitFileContents(contents) {
83663
- const lines = splitWithNewlines(contents);
83664
- for (let index = 0; index < lines.length; index++) lines[index] = detachString(lines[index]);
83665
- return lines;
83666
- }
83667
- function splitWithNewlines(contents) {
83668
- if (contents.length === 0) return [""];
83669
- const lines = [];
83670
- let startIndex = 0;
83671
- for (;;) {
83672
- const newlineIndex = contents.indexOf("\n", startIndex);
83673
- if (newlineIndex === -1) break;
83674
- lines.push(contents.slice(startIndex, newlineIndex + 1));
83675
- startIndex = newlineIndex + 1;
83676
- }
83677
- if (startIndex < contents.length) lines.push(contents.slice(startIndex));
83678
- return lines;
83679
- }
83680
- function splitGitDiffFiles(contents) {
83681
- return splitAtLinePrefix(contents, "diff --git");
83682
- }
83683
- function splitUnifiedDiffFiles(contents) {
83684
- if (contents.length === 0) return [""];
83685
- const parts = [];
83686
- let partStartIndex = 0;
83687
- let lineStartIndex = 0;
83688
- let remainingDeletionLines = 0;
83689
- let remainingAdditionLines = 0;
83690
- let hasOpenedUnifiedFile = false;
83691
- while (lineStartIndex < contents.length) {
83692
- const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);
83693
- if (remainingDeletionLines <= 0 && remainingAdditionLines <= 0) {
83694
- if (isUnifiedDiffFileHeaderAt(contents, lineStartIndex)) {
83695
- if (lineStartIndex > partStartIndex) parts.push(contents.slice(partStartIndex, lineStartIndex));
83696
- partStartIndex = lineStartIndex;
83697
- hasOpenedUnifiedFile = true;
83698
- lineStartIndex = getNextLineStartIndex(contents, nextLineStartIndex);
83699
- continue;
83700
- }
83701
- if (hasOpenedUnifiedFile && contents.startsWith("@@ -", lineStartIndex)) {
83702
- const fileHeader = parseHunkHeader(contents.slice(lineStartIndex, nextLineStartIndex));
83703
- if (fileHeader != null) {
83704
- remainingDeletionLines = fileHeader.deletionCount;
83705
- remainingAdditionLines = fileHeader.additionCount;
83706
- }
83707
- }
83708
- lineStartIndex = nextLineStartIndex;
83709
- continue;
83710
- }
83711
- const firstChar = contents[lineStartIndex];
83712
- if (firstChar === "\\") {
83713
- lineStartIndex = nextLineStartIndex;
83714
- continue;
83715
- }
83716
- if (firstChar === " ") {
83717
- remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);
83718
- remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);
83719
- } else if (firstChar === "-") remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);
83720
- else if (firstChar === "+") remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);
83721
- lineStartIndex = nextLineStartIndex;
83722
- }
83723
- parts.push(contents.slice(partStartIndex));
83724
- return parts;
83725
- }
83726
- function startsWithUnifiedDiffFileHeader(contents) {
83727
- return isUnifiedDiffFileHeaderAt(contents, 0);
83728
- }
83729
- function isUnifiedDiffFileHeaderAt(contents, lineStartIndex) {
83730
- const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);
83731
- return isUnifiedDiffHeaderLineAt(contents, lineStartIndex, "---") && isUnifiedDiffHeaderLineAt(contents, nextLineStartIndex, "+++");
83732
- }
83733
- function isUnifiedDiffHeaderLineAt(contents, lineStartIndex, prefix) {
83734
- if (!contents.startsWith(prefix, lineStartIndex)) return false;
83735
- const separator = contents[lineStartIndex + prefix.length];
83736
- if (separator !== " " && separator !== " ") return false;
83737
- for (let index = lineStartIndex + prefix.length + 1; index < contents.length; index++) {
83738
- const char = contents[index];
83739
- if (char === "\n" || char === "\r") break;
83740
- if (char !== " " && char !== " ") return true;
83741
- }
83742
- return false;
83743
- }
83744
- function getNextLineStartIndex(contents, lineStartIndex) {
83745
- const newlineIndex = contents.indexOf("\n", lineStartIndex);
83746
- return newlineIndex === -1 ? contents.length : newlineIndex + 1;
83747
- }
83748
- function isHunkBodyLine(line) {
83749
- const firstChar = line[0];
83750
- return firstChar === "+" || firstChar === "-" || firstChar === " ";
83751
- }
83752
- function isFormatPatchVersionSeparator(line) {
83753
- if (!line.startsWith("--")) return false;
83754
- for (let index = 2; index < line.length; index++) {
83755
- const char = line[index];
83756
- if (char !== " " && char !== " " && char !== "\n" && char !== "\r") return false;
83757
- }
83758
- return true;
83759
- }
83760
- function parseHunkHeader(line) {
83761
- if (!line.startsWith("@@ -")) return;
83762
- let index = 4;
83763
- const deletionStartResult = readPositiveInteger(line, index);
83764
- if (deletionStartResult == null) return;
83765
- const deletionStart = deletionStartResult.value;
83766
- index = deletionStartResult.endIndex;
83767
- let deletionCount = 1;
83768
- if (line[index] === ",") {
83769
- const deletionCountResult = readPositiveInteger(line, index + 1);
83770
- if (deletionCountResult == null) return;
83771
- deletionCount = deletionCountResult.value;
83772
- index = deletionCountResult.endIndex;
83773
- }
83774
- if (line[index] !== " " || line[index + 1] !== "+") return;
83775
- index += 2;
83776
- const additionStartResult = readPositiveInteger(line, index);
83777
- if (additionStartResult == null) return;
83778
- const additionStart = additionStartResult.value;
83779
- index = additionStartResult.endIndex;
83780
- let additionCount = 1;
83781
- if (line[index] === ",") {
83782
- const additionCountResult = readPositiveInteger(line, index + 1);
83783
- if (additionCountResult == null) return;
83784
- additionCount = additionCountResult.value;
83785
- index = additionCountResult.endIndex;
83786
- }
83787
- if (line[index] !== " " || line[index + 1] !== "@" || line[index + 2] !== "@") return;
83788
- let hunkContext;
83789
- const contextStartIndex = index + 3;
83790
- if (line[contextStartIndex] === " ") hunkContext = trimLineEnd(line.slice(contextStartIndex + 1));
83791
- return {
83792
- additionCount,
83793
- additionStart,
83794
- deletionCount,
83795
- deletionStart,
83796
- hunkContext
83797
- };
83798
- }
83799
- function readPositiveInteger(value, startIndex) {
83800
- let index = startIndex;
83801
- let parsedValue = 0;
83802
- for (; index < value.length; index++) {
83803
- const digit = value.charCodeAt(index) - 48;
83804
- if (digit < 0 || digit > 9) break;
83805
- parsedValue = parsedValue * 10 + digit;
83806
- }
83807
- if (index === startIndex) return;
83808
- return {
83809
- value: parsedValue,
83810
- endIndex: index
83811
- };
83812
- }
83813
- function trimLineEnd(value) {
83814
- if (value.endsWith("\r\n")) return value.slice(0, -2);
83815
- if (value.endsWith("\n")) return value.slice(0, -1);
83816
- return value;
83817
- }
83818
- function isGitDiffPatch(data) {
83819
- return data.startsWith("diff --git") || data.includes("\ndiff --git");
83820
- }
83821
- function splitAtLinePrefix(contents, prefix) {
83822
- if (contents.length === 0) return [""];
83823
- const newlinePrefix = `\n${prefix}`;
83824
- const firstBoundaryIndex = contents.startsWith(prefix) ? 0 : findLinePrefixIndex(contents, newlinePrefix, 0);
83825
- if (firstBoundaryIndex === -1) return [contents];
83826
- const parts = [];
83827
- if (firstBoundaryIndex > 0) parts.push(contents.slice(0, firstBoundaryIndex));
83828
- let startIndex = firstBoundaryIndex;
83829
- for (;;) {
83830
- const nextBoundaryIndex = findLinePrefixIndex(contents, newlinePrefix, startIndex + 1);
83831
- if (nextBoundaryIndex === -1) break;
83832
- parts.push(contents.slice(startIndex, nextBoundaryIndex));
83833
- startIndex = nextBoundaryIndex;
83834
- }
83835
- parts.push(contents.slice(startIndex));
83836
- return parts;
83837
- }
83838
- function findLinePrefixIndex(contents, newlinePrefix, fromIndex) {
83839
- const index = contents.indexOf(newlinePrefix, fromIndex);
83840
- return index === -1 ? -1 : index + 1;
83841
- }
83842
- function maybeDetachOptionalString(value) {
83843
- return value == null ? value : detachString(value);
83844
- }
83845
- function parseRawLineType(firstChar) {
83846
- return firstChar === " " ? "context" : firstChar === "\\" ? "metadata" : firstChar === "+" ? "addition" : "deletion";
83847
- }
83848
- function getParsedLineContent(rawLine) {
83849
- const processedLine = rawLine.slice(1);
83850
- return detachString(processedLine === "" ? "\n" : processedLine);
83851
- }
83852
- function createContentGroup(type, deletionLineIndex, additionLineIndex) {
83853
- if (type === "change") return {
83854
- type: "change",
83855
- additions: 0,
83856
- deletions: 0,
83857
- additionLineIndex,
83858
- deletionLineIndex
83859
- };
83860
- return {
83861
- type: "context",
83862
- lines: 0,
83863
- additionLineIndex,
83864
- deletionLineIndex
83865
- };
83866
- }
83867
84075
  //#endregion
83868
84076
  //#region src/checkpointing/Diffs.ts
83869
84077
  function parseTurnDiffFilesFromUnifiedDiff(diff) {
@@ -83904,6 +84112,7 @@ const make$1 = Effect.gen(function* () {
83904
84112
  const receiptBus = yield* RuntimeReceiptBus;
83905
84113
  const workspaceEntries = yield* WorkspaceEntries;
83906
84114
  const vcsStatusBroadcaster = yield* VcsStatusBroadcaster;
84115
+ const threadActivities = yield* ProjectionThreadActivityRepository;
83907
84116
  const appendRevertFailureActivity = (input) => Effect.all({
83908
84117
  commandId: serverCommandId("checkpoint-revert-failure"),
83909
84118
  activityId: serverEventId
@@ -83992,13 +84201,21 @@ const make$1 = Effect.gen(function* () {
83992
84201
  checkpointRef: targetCheckpointRef
83993
84202
  });
83994
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()));
83995
84212
  const files = yield* checkpointStore.diffCheckpoints({
83996
84213
  cwd: input.cwd,
83997
84214
  fromCheckpointRef,
83998
84215
  toCheckpointRef: targetCheckpointRef,
83999
84216
  fallbackFromToHead: false,
84000
84217
  ignoreWhitespace: false
84001
- }).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) => ({
84002
84219
  path: file.path,
84003
84220
  kind: "modified",
84004
84221
  additions: file.additions,