@norman-else/dsh-claude 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.mjs CHANGED
@@ -15,6 +15,9 @@ import { fileURLToPath } from "node:url";
15
15
  //#region src/sidecar.ts
16
16
  const SIDECAR_SCHEMA_VERSION = 1;
17
17
  const MAX_ACTIVITIES = 1e4;
18
+ /** Trailing window that coalesces per-token transcript persistence into one
19
+ * atomic disk write; live subscribers are notified synchronously regardless. */
20
+ const TEXT_FLUSH_MS = 150;
18
21
  function emptyProjection() {
19
22
  return {
20
23
  schemaVersion: SIDECAR_SCHEMA_VERSION,
@@ -117,13 +120,126 @@ var ClaudeSidecarRepository = class {
117
120
  root;
118
121
  legacyRoot;
119
122
  #pending = /* @__PURE__ */ new Map();
123
+ /** Latest durable projection per session; disk is read once and written through. */
124
+ #latest = /* @__PURE__ */ new Map();
125
+ #listeners = /* @__PURE__ */ new Map();
126
+ /** Streaming transcript segments not yet persisted, keyed by activity key. */
127
+ #live = /* @__PURE__ */ new Map();
128
+ /** Monotonic revision boost so merged reads advance while text stays in memory. */
129
+ #boost = /* @__PURE__ */ new Map();
130
+ #flushTimers = /* @__PURE__ */ new Map();
120
131
  constructor(options = {}) {
121
132
  this.root = options.root ?? dshHomePath("plugins", "dsh-claude", "sessions");
122
133
  this.legacyRoot = options.legacyRoot ?? (options.root === void 0 ? dshHomePath("plugins", "dsh-claude-code", "sessions") : void 0);
123
134
  }
124
135
  async read(sessionId) {
125
136
  await this.#pending.get(sessionId)?.catch(() => void 0);
126
- return this.#readNow(sessionId);
137
+ return this.#merged(sessionId, await this.#base(sessionId));
138
+ }
139
+ /** Observe accepted changes for one session; returns the unsubscriber. */
140
+ subscribe(sessionId, listener) {
141
+ let set = this.#listeners.get(sessionId);
142
+ if (set === void 0) {
143
+ set = /* @__PURE__ */ new Set();
144
+ this.#listeners.set(sessionId, set);
145
+ }
146
+ set.add(listener);
147
+ return () => {
148
+ set.delete(listener);
149
+ if (set.size === 0) this.#listeners.delete(sessionId);
150
+ };
151
+ }
152
+ /** Record streaming assistant prose without touching the disk on the hot
153
+ * path: subscribers are notified synchronously (as an append when the
154
+ * redacted text grows in place) and persistence is coalesced. */
155
+ appendTranscriptText(sessionId, value) {
156
+ const normalized = normalizeActivity({
157
+ kind: "text",
158
+ phase: "updated",
159
+ ...value
160
+ });
161
+ const key = activityKey(normalized);
162
+ let overlay = this.#live.get(sessionId);
163
+ if (overlay === void 0) {
164
+ overlay = /* @__PURE__ */ new Map();
165
+ this.#live.set(sessionId, overlay);
166
+ }
167
+ const previous = overlay.get(key);
168
+ overlay.set(key, normalized);
169
+ this.#boost.set(sessionId, (this.#boost.get(sessionId) ?? 0) + 1);
170
+ const text = normalized.text ?? "";
171
+ const base = {
172
+ turn: normalized.turn,
173
+ step: normalized.step,
174
+ ordinal: normalized.ordinal
175
+ };
176
+ this.#notify(sessionId, previous?.text !== void 0 && text.startsWith(previous.text) ? {
177
+ kind: "text",
178
+ ...base,
179
+ append: text.slice(previous.text.length)
180
+ } : {
181
+ kind: "text",
182
+ ...base,
183
+ text
184
+ });
185
+ this.#scheduleTextFlush(sessionId);
186
+ }
187
+ /** Persist any pending streaming transcript now (segment close, turn end). */
188
+ flushTranscriptText(sessionId) {
189
+ const timer = this.#flushTimers.get(sessionId);
190
+ if (timer !== void 0) {
191
+ clearTimeout(timer);
192
+ this.#flushTimers.delete(sessionId);
193
+ }
194
+ return this.#flushLive(sessionId);
195
+ }
196
+ #notify(sessionId, delta) {
197
+ const set = this.#listeners.get(sessionId);
198
+ if (set === void 0) return;
199
+ for (const listener of [...set]) try {
200
+ listener(delta);
201
+ } catch {}
202
+ }
203
+ #merged(sessionId, base) {
204
+ const overlay = this.#live.get(sessionId);
205
+ const boost = this.#boost.get(sessionId) ?? 0;
206
+ if ((overlay === void 0 || overlay.size === 0) && boost === 0) return base;
207
+ return {
208
+ ...base,
209
+ revision: base.revision + boost,
210
+ ...overlay === void 0 || overlay.size === 0 ? {} : { activities: mergeActivities(base.activities, [...overlay.values()]) }
211
+ };
212
+ }
213
+ async #base(sessionId) {
214
+ const cached = this.#latest.get(sessionId);
215
+ if (cached !== void 0) return cached;
216
+ const loaded = await this.#readNow(sessionId);
217
+ this.#latest.set(sessionId, loaded);
218
+ return loaded;
219
+ }
220
+ #scheduleTextFlush(sessionId) {
221
+ if (this.#flushTimers.has(sessionId)) return;
222
+ const timer = setTimeout(() => {
223
+ this.#flushTimers.delete(sessionId);
224
+ this.#flushLive(sessionId);
225
+ }, TEXT_FLUSH_MS);
226
+ timer.unref?.();
227
+ this.#flushTimers.set(sessionId, timer);
228
+ }
229
+ async #flushLive(sessionId) {
230
+ const overlay = this.#live.get(sessionId);
231
+ if (overlay === void 0 || overlay.size === 0) return;
232
+ const entries = [...overlay.entries()];
233
+ try {
234
+ await this.#update(sessionId, (current) => ({
235
+ ...current,
236
+ activities: mergeActivities(current.activities, entries.map(([, value]) => value))
237
+ }));
238
+ } catch {
239
+ return;
240
+ }
241
+ for (const [key, value] of entries) if (overlay.get(key) === value) overlay.delete(key);
242
+ if (overlay.size === 0) this.#live.delete(sessionId);
127
243
  }
128
244
  writeBinding(sessionId, value) {
129
245
  const normalized = normalizeBinding(value);
@@ -137,21 +253,30 @@ var ClaudeSidecarRepository = class {
137
253
  return this.#update(sessionId, (current) => ({
138
254
  ...current,
139
255
  activities: mergeActivities(current.activities, [normalized])
140
- }));
256
+ }), false, {
257
+ kind: "activity",
258
+ activity: normalized
259
+ });
141
260
  }
142
261
  writeContextUsage(sessionId, value) {
143
262
  const normalized = normalizeContextUsage(value);
144
263
  return this.#update(sessionId, (current) => ({
145
264
  ...current,
146
265
  contextUsage: normalized
147
- }));
266
+ }), false, {
267
+ kind: "contextUsage",
268
+ value: normalized
269
+ });
148
270
  }
149
271
  writeTasks(sessionId, value) {
150
272
  const normalized = normalizeTasksEvent(value);
151
273
  return this.#update(sessionId, (current) => ({
152
274
  ...current,
153
275
  tasks: normalized
154
- }));
276
+ }), false, {
277
+ kind: "tasks",
278
+ value: normalized
279
+ });
155
280
  }
156
281
  importLegacy(sessionId, events) {
157
282
  const importedActivities = events.filter((event) => event.type === CLAUDE_ACTIVITY_EVENT).map((event) => activity(event.data)).filter((item) => item !== void 0);
@@ -164,15 +289,15 @@ var ClaudeSidecarRepository = class {
164
289
  ...current.binding !== void 0 || importedBinding === void 0 ? {} : { binding: normalizeBinding(importedBinding) },
165
290
  ...current.contextUsage !== void 0 || importedUsage === void 0 ? {} : { contextUsage: normalizeContextUsage(importedUsage) },
166
291
  ...current.tasks !== void 0 || importedTasks === void 0 ? {} : { tasks: normalizeTasksEvent(importedTasks.tasks) }
167
- }), true);
292
+ }), true, { kind: "sync" });
168
293
  }
169
294
  #path(sessionId, root = this.root) {
170
295
  if (sessionId.length === 0 || sessionId.length > 1024) throw new Error("dsh-claude: invalid session id");
171
296
  return join(root, `${Buffer.from(sessionId).toString("base64url")}.json`);
172
297
  }
173
- #update(sessionId, change, skipUnchanged = false) {
298
+ #update(sessionId, change, skipUnchanged = false, delta) {
174
299
  const operation = (this.#pending.get(sessionId) ?? Promise.resolve()).catch(() => void 0).then(async () => {
175
- const current = await this.#readNow(sessionId);
300
+ const current = await this.#base(sessionId);
176
301
  const changed = parseClaudeSidecar({
177
302
  ...change(current),
178
303
  schemaVersion: SIDECAR_SCHEMA_VERSION,
@@ -184,6 +309,8 @@ var ClaudeSidecarRepository = class {
184
309
  revision: current.revision + 1
185
310
  };
186
311
  await this.#writeNow(sessionId, next);
312
+ this.#latest.set(sessionId, next);
313
+ if (delta !== void 0) this.#notify(sessionId, delta);
187
314
  return next;
188
315
  });
189
316
  this.#pending.set(sessionId, operation);
@@ -594,7 +721,9 @@ function normalizeAssistant(message) {
594
721
  return normalized;
595
722
  }
596
723
  function normalizeUser(message) {
724
+ if (message.isReplay === true) return [];
597
725
  const content = record$4(message.message)?.content;
726
+ if (typeof content === "string") return [];
598
727
  if (!Array.isArray(content)) return [{
599
728
  kind: "protocol-error",
600
729
  title: "Malformed Claude user message",
@@ -1647,6 +1776,7 @@ var ClaudeSupervisor = class {
1647
1776
  if (entry.active !== active) return;
1648
1777
  if (active.aborted) {
1649
1778
  await this.#upsertTranscriptText(active);
1779
+ await this.#flushTranscript(active);
1650
1780
  await this.#appendSafely(active, {
1651
1781
  kind: "status",
1652
1782
  phase: "failed",
@@ -1689,6 +1819,7 @@ var ClaudeSupervisor = class {
1689
1819
  });
1690
1820
  }
1691
1821
  await this.#upsertTranscriptText(active);
1822
+ await this.#flushTranscript(active);
1692
1823
  const message = result.errors?.join("\n") ?? (result.terminalReason !== void 0 ? `Claude Code failed the turn (${result.terminalReason})` : "Claude Code failed the turn");
1693
1824
  await this.#appendSafely(active, {
1694
1825
  kind: "error",
@@ -1724,6 +1855,7 @@ var ClaudeSupervisor = class {
1724
1855
  phase: "completed",
1725
1856
  title: "Claude Code turn completed"
1726
1857
  });
1858
+ await this.#flushTranscript(active);
1727
1859
  active.output.push({
1728
1860
  type: "complete",
1729
1861
  text: active.text
@@ -1741,9 +1873,7 @@ var ClaudeSupervisor = class {
1741
1873
  const ordinal = active.transcriptTextOrdinal ?? active.cursor.nextOrdinal++;
1742
1874
  active.transcriptTextOrdinal = ordinal;
1743
1875
  try {
1744
- await this.#sidecar.appendActivity(active.agent.id, {
1745
- kind: "text",
1746
- phase: "updated",
1876
+ this.#sidecar.appendTranscriptText(active.agent.id, {
1747
1877
  text: active.transcriptText,
1748
1878
  turn: active.cursor.turn,
1749
1879
  step: active.cursor.step,
@@ -1751,7 +1881,11 @@ var ClaudeSupervisor = class {
1751
1881
  });
1752
1882
  } catch {}
1753
1883
  }
1884
+ async #flushTranscript(active) {
1885
+ await this.#sidecar.flushTranscriptText(active.agent.id).catch(() => void 0);
1886
+ }
1754
1887
  #closeTranscriptTextSegment(active) {
1888
+ this.#flushTranscript(active);
1755
1889
  active.transcriptText = "";
1756
1890
  active.transcriptTextOrdinal = void 0;
1757
1891
  }
@@ -1807,6 +1941,7 @@ var ClaudeSupervisor = class {
1807
1941
  const stderr = entry.process?.stderrTail();
1808
1942
  if (active !== void 0) {
1809
1943
  await this.#upsertTranscriptText(active);
1944
+ await this.#flushTranscript(active);
1810
1945
  if (active.signal !== void 0 && active.abortListener !== void 0) active.signal.removeEventListener("abort", active.abortListener);
1811
1946
  const unknown = active.sawActivity;
1812
1947
  entry.state = unknown ? "outcome-unknown" : "disconnected";
@@ -2376,47 +2511,173 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
2376
2511
  //#endregion
2377
2512
  //#region src/projection-routes.ts
2378
2513
  const MAX_SESSION_ID_CHARS$2 = 1024;
2379
- function sessionIdFromUrl$1(rawUrl) {
2514
+ /** Slow-moving metadata refresh and stream heartbeat cadence; deliberately off
2515
+ * the transcript hot path so git/gh latency never delays visible text. */
2516
+ const META_REFRESH_MS = 5e3;
2517
+ function targetFromUrl(rawUrl) {
2380
2518
  try {
2381
2519
  const pathname = new URL(rawUrl ?? "/", "http://localhost").pathname;
2382
2520
  const prefix = `${CLAUDE_PROJECTION_PATH}/`;
2383
2521
  if (!pathname.startsWith(prefix)) return void 0;
2384
- const encoded = pathname.slice(prefix.length);
2522
+ let encoded = pathname.slice(prefix.length);
2523
+ const stream = encoded.endsWith("/stream");
2524
+ if (stream) encoded = encoded.slice(0, -7);
2385
2525
  if (encoded.length === 0 || encoded.includes("/")) return void 0;
2386
2526
  const sessionId = decodeURIComponent(encoded);
2387
2527
  if (sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$2) return void 0;
2388
- return sessionId;
2528
+ return {
2529
+ sessionId,
2530
+ stream
2531
+ };
2389
2532
  } catch {
2390
2533
  return;
2391
2534
  }
2392
2535
  }
2393
- /** Register the browser-readable, credential-free sidecar projection endpoint. */
2536
+ function envelope(projection, meta) {
2537
+ return {
2538
+ schemaVersion: projection.schemaVersion,
2539
+ revision: projection.revision,
2540
+ owned: meta.owned,
2541
+ commands: meta.commands,
2542
+ activities: projection.activities,
2543
+ ...projection.contextUsage === void 0 ? {} : { contextUsage: projection.contextUsage },
2544
+ ...projection.tasks === void 0 ? {} : { tasks: projection.tasks },
2545
+ ...meta.repository === void 0 ? {} : { repository: meta.repository },
2546
+ reviewComments: meta.reviewComments
2547
+ };
2548
+ }
2549
+ /** Register the browser-readable, credential-free sidecar projection endpoint.
2550
+ * `GET <path>/:sessionId` returns one snapshot; `GET <path>/:sessionId/stream`
2551
+ * returns an NDJSON stream: a full snapshot line followed by incremental
2552
+ * transcript/activity deltas and periodic metadata/heartbeat lines. */
2394
2553
  function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSession = () => [], repositoryForSession = async () => void 0, reviewCommentsForSession = () => []) {
2554
+ const info = (message) => {
2555
+ ctx.logger?.info?.(message);
2556
+ };
2557
+ const assembleMeta = async (sessionId) => {
2558
+ const owned = ownsSession(sessionId);
2559
+ const repository = owned ? await repositoryForSession(sessionId) : void 0;
2560
+ return {
2561
+ owned,
2562
+ commands: commandsForSession(sessionId),
2563
+ ...repository === void 0 ? {} : { repository },
2564
+ reviewComments: owned ? reviewCommentsForSession(sessionId) : []
2565
+ };
2566
+ };
2567
+ const streamProjection = async (res, sessionId) => {
2568
+ info(`dsh-claude: projection stream opened for ${sessionId.slice(0, 64)}`);
2569
+ let textDeltas = 0;
2570
+ let textBytes = 0;
2571
+ let textSince = Date.now();
2572
+ let meta = await assembleMeta(sessionId);
2573
+ res.writeHead(200, {
2574
+ "content-type": "application/x-ndjson; charset=utf-8",
2575
+ "cache-control": "no-store",
2576
+ "x-content-type-options": "nosniff"
2577
+ });
2578
+ res.flushHeaders?.();
2579
+ let closed = false;
2580
+ const writeLine = (value) => {
2581
+ if (closed) return;
2582
+ try {
2583
+ res.write(`${JSON.stringify(value)}\n`);
2584
+ } catch {
2585
+ closed = true;
2586
+ }
2587
+ };
2588
+ const writeSnapshot = async () => {
2589
+ const projection = await sidecar.read(sessionId);
2590
+ writeLine({
2591
+ type: "snapshot",
2592
+ ...envelope(projection, meta)
2593
+ });
2594
+ };
2595
+ await writeSnapshot();
2596
+ const unsubscribe = sidecar.subscribe(sessionId, (delta) => {
2597
+ switch (delta.kind) {
2598
+ case "text":
2599
+ textDeltas += 1;
2600
+ textBytes += (delta.append ?? delta.text ?? "").length;
2601
+ if (textDeltas % 25 === 0) {
2602
+ const elapsed = Date.now() - textSince;
2603
+ info(`dsh-claude: stream ${sessionId.slice(0, 24)} 25 text deltas ${textBytes}B in ${elapsed}ms`);
2604
+ textBytes = 0;
2605
+ textSince = Date.now();
2606
+ }
2607
+ writeLine({
2608
+ type: "text",
2609
+ turn: delta.turn,
2610
+ step: delta.step,
2611
+ ordinal: delta.ordinal,
2612
+ ...delta.append === void 0 ? {} : { append: delta.append },
2613
+ ...delta.text === void 0 ? {} : { text: delta.text }
2614
+ });
2615
+ return;
2616
+ case "activity":
2617
+ writeLine({
2618
+ type: "activity",
2619
+ activity: delta.activity
2620
+ });
2621
+ return;
2622
+ case "contextUsage":
2623
+ writeLine({
2624
+ type: "contextUsage",
2625
+ value: delta.value
2626
+ });
2627
+ return;
2628
+ case "tasks":
2629
+ writeLine({
2630
+ type: "tasks",
2631
+ value: delta.value
2632
+ });
2633
+ return;
2634
+ case "sync": writeSnapshot().catch(() => void 0);
2635
+ }
2636
+ });
2637
+ const timer = setInterval(() => {
2638
+ (async () => {
2639
+ const next = await assembleMeta(sessionId);
2640
+ if (closed) return;
2641
+ if (JSON.stringify(next) === JSON.stringify(meta)) {
2642
+ writeLine({ type: "ping" });
2643
+ return;
2644
+ }
2645
+ meta = next;
2646
+ writeLine({
2647
+ type: "meta",
2648
+ owned: meta.owned,
2649
+ commands: meta.commands,
2650
+ ...meta.repository === void 0 ? {} : { repository: meta.repository },
2651
+ reviewComments: meta.reviewComments
2652
+ });
2653
+ })().catch(() => void 0);
2654
+ }, META_REFRESH_MS);
2655
+ timer.unref?.();
2656
+ await new Promise((resolve) => {
2657
+ res.on("close", () => {
2658
+ closed = true;
2659
+ clearInterval(timer);
2660
+ unsubscribe();
2661
+ info(`dsh-claude: projection stream closed for ${sessionId.slice(0, 64)} after ${textDeltas} text deltas`);
2662
+ resolve();
2663
+ });
2664
+ });
2665
+ };
2395
2666
  ctx.effect(() => ctx.webServer.register({
2396
2667
  kind: "prefix",
2397
2668
  path: CLAUDE_PROJECTION_PATH,
2398
2669
  handler: async (req, res) => {
2399
2670
  if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
2400
2671
  if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
2401
- const sessionId = sessionIdFromUrl$1(req.url);
2402
- if (sessionId === void 0) return json(res, 400, { error: "invalid session id" });
2672
+ const target = targetFromUrl(req.url);
2673
+ if (target === void 0) return json(res, 400, { error: "invalid session id" });
2403
2674
  try {
2404
- const projection = await sidecar.read(sessionId);
2405
- const owned = ownsSession(sessionId);
2406
- const repository = owned ? await repositoryForSession(sessionId) : void 0;
2407
- return json(res, 200, {
2408
- schemaVersion: projection.schemaVersion,
2409
- revision: projection.revision,
2410
- owned,
2411
- commands: commandsForSession(sessionId),
2412
- activities: projection.activities,
2413
- ...projection.contextUsage === void 0 ? {} : { contextUsage: projection.contextUsage },
2414
- ...projection.tasks === void 0 ? {} : { tasks: projection.tasks },
2415
- ...repository === void 0 ? {} : { repository },
2416
- reviewComments: owned ? reviewCommentsForSession(sessionId) : []
2417
- });
2675
+ if (target.stream) return await streamProjection(res, target.sessionId);
2676
+ info(`dsh-claude: projection poll for ${target.sessionId.slice(0, 64)}`);
2677
+ return json(res, 200, envelope(await sidecar.read(target.sessionId), await assembleMeta(target.sessionId)));
2418
2678
  } catch {
2419
- return json(res, 500, { error: "projection unavailable" });
2679
+ if (!res.headersSent) return json(res, 500, { error: "projection unavailable" });
2680
+ res.end();
2420
2681
  }
2421
2682
  }
2422
2683
  }), "dsh-claude: sidecar projection route");
@@ -3318,6 +3579,30 @@ var RepositoryActionService = class {
3318
3579
  pushed: true
3319
3580
  };
3320
3581
  }
3582
+ if (request.action === "merge-pr") {
3583
+ const method = request.mergeMethod;
3584
+ if (method !== "merge" && method !== "squash" && method !== "rebase") throw new RepositoryActionError("invalid-request", "The merge method is invalid.");
3585
+ let gh;
3586
+ try {
3587
+ gh = await this.#gh();
3588
+ } catch (error) {
3589
+ throw new RepositoryActionError("gh-unavailable", error instanceof Error ? error.message : "GitHub CLI is unavailable.");
3590
+ }
3591
+ const merged = await this.#run(gh, [
3592
+ "pr",
3593
+ "merge",
3594
+ `--${method}`
3595
+ ], before.root, REMOTE_TIMEOUT_MS);
3596
+ if (merged.exitCode !== 0 || merged.lossy) {
3597
+ const reason = merged.stderr.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0).at(-1);
3598
+ throw new RepositoryActionError("merge-failed", reason === void 0 || reason.length === 0 ? "The pull request could not be merged." : reason);
3599
+ }
3600
+ this.#invalidate(before.root);
3601
+ return {
3602
+ commit: before.head,
3603
+ pushed: true
3604
+ };
3605
+ }
3321
3606
  const message = safeText(request.message, MAX_MESSAGE_CHARS, "Commit message");
3322
3607
  if (before.files.length === 0 && request.action !== "create-pr") throw new RepositoryActionError("nothing-to-commit", "There are no changes to commit.");
3323
3608
  const git = await this.#git();
@@ -3655,7 +3940,8 @@ const ACTIONS = /* @__PURE__ */ new Set([
3655
3940
  "commit",
3656
3941
  "commit-push",
3657
3942
  "push",
3658
- "create-pr"
3943
+ "create-pr",
3944
+ "merge-pr"
3659
3945
  ]);
3660
3946
  function record$1(value) {
3661
3947
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
@@ -3695,13 +3981,16 @@ function actionRequest(input) {
3695
3981
  return {
3696
3982
  action,
3697
3983
  fingerprint: string(input, "fingerprint"),
3698
- message: action === "push" ? optionalString(input, "message") ?? "" : string(input, "message"),
3984
+ message: action === "push" || action === "merge-pr" ? optionalString(input, "message") ?? "" : string(input, "message"),
3699
3985
  includeUnstaged: input.includeUnstaged,
3700
3986
  ...optionalString(input, "prTitle") === void 0 ? {} : { prTitle: optionalString(input, "prTitle") },
3701
3987
  ...optionalString(input, "prBody") === void 0 ? {} : { prBody: optionalString(input, "prBody") },
3702
3988
  ...optionalString(input, "baseBranch") === void 0 ? {} : { baseBranch: optionalString(input, "baseBranch") },
3703
3989
  ...input.draft === void 0 ? {} : typeof input.draft === "boolean" ? { draft: input.draft } : (() => {
3704
3990
  throw new RepositoryActionError("invalid-request", "The draft field must be a boolean.");
3991
+ })(),
3992
+ ...input.mergeMethod === void 0 ? {} : input.mergeMethod === "merge" || input.mergeMethod === "squash" || input.mergeMethod === "rebase" ? { mergeMethod: input.mergeMethod } : (() => {
3993
+ throw new RepositoryActionError("invalid-request", "The mergeMethod field is invalid.");
3705
3994
  })()
3706
3995
  };
3707
3996
  }