paperclip-github-plugin 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/worker.js CHANGED
@@ -44,7 +44,7 @@ var projectNumberProperty = {
44
44
  };
45
45
  var llmModelProperty = {
46
46
  type: "string",
47
- description: "Exact LLM name used to draft the comment. Required so the plugin can append the mandatory AI-authorship footer."
47
+ description: "Exact LLM name used to draft the GitHub content. When provided, the plugin includes it in the mandatory AI-authorship footer."
48
48
  };
49
49
  var issueTargetSchema = {
50
50
  anyOf: [
@@ -158,7 +158,7 @@ var GITHUB_AGENT_TOOLS = [
158
158
  {
159
159
  name: "update_issue",
160
160
  displayName: "Update Issue",
161
- description: "Update GitHub issue fields such as title, body, state, labels, assignees, or milestone.",
161
+ description: "Update GitHub issue fields such as title, body, state, labels, assignees, or milestone. When a non-empty body is provided, the plugin appends an AI-authorship footer and includes llmModel when supplied.",
162
162
  parametersSchema: {
163
163
  type: "object",
164
164
  additionalProperties: false,
@@ -171,8 +171,10 @@ var GITHUB_AGENT_TOOLS = [
171
171
  type: "string"
172
172
  },
173
173
  body: {
174
- type: "string"
174
+ type: "string",
175
+ description: "Optional human-facing issue description body. If provided, it must remain non-empty after trimming and removing any existing AI footer."
175
176
  },
177
+ llmModel: llmModelProperty,
176
178
  state: {
177
179
  type: "string",
178
180
  enum: ["open", "closed"]
@@ -224,11 +226,11 @@ var GITHUB_AGENT_TOOLS = [
224
226
  {
225
227
  name: "add_issue_comment",
226
228
  displayName: "Add Issue Comment",
227
- description: "Post a comment on a GitHub issue or pull request. Provide only the human-facing message body; include llmModel so the plugin can append the required AI-authorship footer.",
229
+ description: "Post a comment on a GitHub issue or pull request. Provide only the human-facing message body; it must remain non-empty after trimming and removing any existing AI footer. The plugin appends the required AI-authorship footer and includes llmModel when supplied.",
228
230
  parametersSchema: {
229
231
  type: "object",
230
232
  additionalProperties: false,
231
- required: ["body", "llmModel"],
233
+ required: ["body"],
232
234
  ...issueTargetSchema,
233
235
  properties: {
234
236
  repository: repositoryProperty,
@@ -236,7 +238,8 @@ var GITHUB_AGENT_TOOLS = [
236
238
  paperclipIssueId: paperclipIssueIdProperty,
237
239
  body: {
238
240
  type: "string",
239
- description: "Human-facing comment body without the AI footer."
241
+ minLength: 1,
242
+ description: "Human-facing comment body without the AI footer. It must remain non-empty after trimming and removing any existing AI footer."
240
243
  },
241
244
  llmModel: llmModelProperty
242
245
  }
@@ -245,7 +248,7 @@ var GITHUB_AGENT_TOOLS = [
245
248
  {
246
249
  name: "create_pull_request",
247
250
  displayName: "Create Pull Request",
248
- description: "Open a GitHub pull request once the implementation branch is pushed.",
251
+ description: "Open a GitHub pull request once the implementation branch is pushed. When a non-empty body is provided, the plugin appends an AI-authorship footer and includes llmModel when supplied.",
249
252
  parametersSchema: {
250
253
  type: "object",
251
254
  additionalProperties: false,
@@ -264,8 +267,10 @@ var GITHUB_AGENT_TOOLS = [
264
267
  type: "string"
265
268
  },
266
269
  body: {
267
- type: "string"
270
+ type: "string",
271
+ description: "Optional human-facing pull request description. If provided, it must remain non-empty after trimming and removing any existing AI footer."
268
272
  },
273
+ llmModel: llmModelProperty,
269
274
  draft: {
270
275
  type: "boolean"
271
276
  }
@@ -290,7 +295,7 @@ var GITHUB_AGENT_TOOLS = [
290
295
  {
291
296
  name: "update_pull_request",
292
297
  displayName: "Update Pull Request",
293
- description: "Edit pull request title, body, base branch, open or close it, or convert between draft and ready for review.",
298
+ description: "Edit pull request title, body, base branch, open or close it, or convert between draft and ready for review. When a non-empty body is provided, the plugin appends an AI-authorship footer and includes llmModel when supplied.",
294
299
  parametersSchema: {
295
300
  type: "object",
296
301
  additionalProperties: false,
@@ -303,8 +308,10 @@ var GITHUB_AGENT_TOOLS = [
303
308
  type: "string"
304
309
  },
305
310
  body: {
306
- type: "string"
311
+ type: "string",
312
+ description: "Optional human-facing pull request description. If provided, it must remain non-empty after trimming and removing any existing AI footer."
307
313
  },
314
+ llmModel: llmModelProperty,
308
315
  base: {
309
316
  type: "string"
310
317
  },
@@ -367,11 +374,11 @@ var GITHUB_AGENT_TOOLS = [
367
374
  {
368
375
  name: "reply_to_review_thread",
369
376
  displayName: "Reply To Review Thread",
370
- description: "Reply to an existing pull request review thread. Provide only the human-facing body; include llmModel so the plugin can append the required AI-authorship footer.",
377
+ description: "Reply to an existing pull request review thread. Provide only the human-facing body; the plugin appends the required AI-authorship footer and includes llmModel when supplied.",
371
378
  parametersSchema: {
372
379
  type: "object",
373
380
  additionalProperties: false,
374
- required: ["threadId", "body", "llmModel"],
381
+ required: ["threadId", "body"],
375
382
  properties: {
376
383
  threadId: {
377
384
  type: "string",
@@ -379,7 +386,8 @@ var GITHUB_AGENT_TOOLS = [
379
386
  },
380
387
  body: {
381
388
  type: "string",
382
- description: "Human-facing reply body without the AI footer."
389
+ minLength: 1,
390
+ description: "Human-facing reply body without the AI footer. It must remain non-empty after trimming and removing any existing AI footer."
383
391
  },
384
392
  llmModel: llmModelProperty
385
393
  }
@@ -463,7 +471,7 @@ var GITHUB_AGENT_TOOLS = [
463
471
  {
464
472
  name: "list_organization_projects",
465
473
  displayName: "List Organization Projects",
466
- description: "List GitHub organization-level Projects so an agent can choose where to associate pull requests.",
474
+ description: "Search or list GitHub organization-level Projects so an agent can choose where to associate pull requests.",
467
475
  parametersSchema: {
468
476
  type: "object",
469
477
  additionalProperties: false,
@@ -615,6 +623,8 @@ var GITHUB_TOKEN_PERMISSION_AUDIT_CACHE_TTL_MS = 5 * 6e4;
615
623
  var MANUAL_SYNC_RESPONSE_GRACE_PERIOD_MS = 500;
616
624
  var RUNNING_SYNC_MESSAGE = "GitHub sync is running in the background. This page will update when it finishes.";
617
625
  var CANCELLING_SYNC_MESSAGE = "Cancellation requested. GitHub sync will stop after the current step finishes.";
626
+ var INTERRUPTED_SYNC_MESSAGE = "GitHub sync stopped unexpectedly before it finished. The worker restarted while the sync was running.";
627
+ var INTERRUPTED_SYNC_ACTION = "Run GitHub sync again. If it stops on the same repository or issue, retry that narrower scope to isolate the failing step.";
618
628
  var SYNC_PROGRESS_PERSIST_INTERVAL_MS = 250;
619
629
  var MAX_SYNC_FAILURE_LOG_ENTRIES = 25;
620
630
  var GITHUB_SECONDARY_RATE_LIMIT_FALLBACK_MS = 6e4;
@@ -629,7 +639,9 @@ var IMPORTED_ISSUE_WAKE_REASON = "GitHub Sync imported an assigned issue that is
629
639
  var ISSUE_LINK_ENTITY_TYPE = "paperclip-github-plugin.issue-link";
630
640
  var PULL_REQUEST_LINK_ENTITY_TYPE = "paperclip-github-plugin.pull-request-link";
631
641
  var COMMENT_ANNOTATION_ENTITY_TYPE = "paperclip-github-plugin.comment-annotation";
632
- var AI_AUTHORED_COMMENT_FOOTER_PREFIX = "Created by a Paperclip AI agent using ";
642
+ var AI_AUTHORED_FOOTER_MARKDOWN_PREFIX = "###### \u2728 This ";
643
+ var AI_AUTHORED_LEGACY_FOOTER_PATTERN = /\n\n---\nCreated by a Paperclip AI agent(?: using [^\n]+)?\.\s*$/;
644
+ var AI_AUTHORED_MARKDOWN_FOOTER_PATTERN = /\n\n---\n###### ✨ This (?:comment|issue description|pull request description) was AI-generated(?: using [^\n]+)?\s*$/;
633
645
  var HIDDEN_GITHUB_IMPORT_MARKER_PREFIX = "<!-- paperclip-github-plugin-imported-from: ";
634
646
  var HIDDEN_GITHUB_IMPORT_MARKER_SUFFIX = " -->";
635
647
  var EMPTY_GITHUB_ISSUE_DESCRIPTION_PLACEHOLDER = "_No description provided on GitHub._";
@@ -2885,6 +2897,97 @@ async function getSyncCancellationRequest(ctx) {
2885
2897
  }
2886
2898
  return normalizeSyncCancellationRequest(await ctx.state.get(SYNC_CANCELLATION_SCOPE));
2887
2899
  }
2900
+ function buildInterruptedSyncMessage(progress) {
2901
+ const completedIssueCount = typeof progress?.completedIssueCount === "number" ? Math.max(0, progress.completedIssueCount) : void 0;
2902
+ const totalIssueCount = typeof progress?.totalIssueCount === "number" ? Math.max(0, progress.totalIssueCount) : void 0;
2903
+ const completionSummary = completedIssueCount !== void 0 && totalIssueCount !== void 0 ? ` Completed ${Math.min(completedIssueCount, totalIssueCount)} of ${totalIssueCount} issues before the worker restarted.` : "";
2904
+ return `${INTERRUPTED_SYNC_MESSAGE}${completionSummary}`;
2905
+ }
2906
+ function getInterruptedSyncFailurePhase(progress) {
2907
+ switch (progress?.phase) {
2908
+ case "preparing":
2909
+ return "building_import_plan";
2910
+ case "importing":
2911
+ return "importing_issue";
2912
+ case "syncing":
2913
+ return "evaluating_github_status";
2914
+ default:
2915
+ return void 0;
2916
+ }
2917
+ }
2918
+ function getActiveRunningSyncForScope(companyId) {
2919
+ const normalizedCompanyId = normalizeCompanyId(companyId);
2920
+ if (activeRunningSyncState?.syncState.status !== "running") {
2921
+ return null;
2922
+ }
2923
+ const activeSyncMatchesScope = normalizedCompanyId === void 0 || activeRunningSyncCompanyId === void 0 || activeRunningSyncCompanyId === normalizedCompanyId;
2924
+ if (!activeSyncMatchesScope) {
2925
+ return null;
2926
+ }
2927
+ return materializeScopedSettings(activeRunningSyncState, null, normalizedCompanyId);
2928
+ }
2929
+ async function reconcileOrphanedRunningSyncState(ctx, companyId, resolution = "error") {
2930
+ const normalizedCompanyId = normalizeCompanyId(companyId);
2931
+ const activeRunningSync = getActiveRunningSyncForScope(normalizedCompanyId);
2932
+ if (activeRunningSync) {
2933
+ return activeRunningSync;
2934
+ }
2935
+ const current = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
2936
+ const scopedSyncState = getScopedSyncState(current, normalizedCompanyId);
2937
+ if (scopedSyncState.status !== "running") {
2938
+ return materializeScopedSettings(current, null, normalizedCompanyId);
2939
+ }
2940
+ const trigger = scopedSyncState.lastRunTrigger ?? "manual";
2941
+ const progress = normalizeSyncProgress(scopedSyncState.progress);
2942
+ const syncCounts = {
2943
+ syncedIssuesCount: scopedSyncState.syncedIssuesCount ?? 0,
2944
+ createdIssuesCount: scopedSyncState.createdIssuesCount ?? 0,
2945
+ skippedIssuesCount: scopedSyncState.skippedIssuesCount ?? 0,
2946
+ erroredIssuesCount: scopedSyncState.erroredIssuesCount ?? 0
2947
+ };
2948
+ const nextSyncState = resolution === "cancelled" || Boolean(scopedSyncState.cancelRequestedAt?.trim()) ? createCancelledSyncState({
2949
+ message: buildCancelledSyncMessage(void 0, progress),
2950
+ trigger,
2951
+ ...syncCounts,
2952
+ ...progress ? { progress } : {}
2953
+ }) : (() => {
2954
+ const errorDetails = normalizeSyncErrorDetails({
2955
+ ...getInterruptedSyncFailurePhase(progress) ? { phase: getInterruptedSyncFailurePhase(progress) } : {},
2956
+ ...progress?.currentRepositoryUrl ? { repositoryUrl: progress.currentRepositoryUrl } : {},
2957
+ ...typeof progress?.currentIssueNumber === "number" ? { githubIssueNumber: progress.currentIssueNumber } : {},
2958
+ rawMessage: INTERRUPTED_SYNC_MESSAGE,
2959
+ suggestedAction: INTERRUPTED_SYNC_ACTION
2960
+ });
2961
+ const message = buildInterruptedSyncMessage(progress);
2962
+ return createErrorSyncState({
2963
+ message,
2964
+ trigger,
2965
+ ...syncCounts,
2966
+ ...progress ? { progress } : {},
2967
+ ...errorDetails ? { errorDetails } : {},
2968
+ recentFailures: appendRecentSyncFailureLogEntry(
2969
+ scopedSyncState.recentFailures,
2970
+ createSyncFailureLogEntry({
2971
+ message,
2972
+ ...errorDetails ? { errorDetails } : {}
2973
+ })
2974
+ )
2975
+ });
2976
+ })();
2977
+ const next = await saveSettingsSyncState(ctx, current, nextSyncState, normalizedCompanyId);
2978
+ await setSyncCancellationRequest(ctx, null);
2979
+ return next;
2980
+ }
2981
+ function resolvePersistedRunningSyncCompanyId(settings) {
2982
+ if (normalizeSyncState(settings.syncState).status === "running") {
2983
+ return void 0;
2984
+ }
2985
+ const runningCompanyIds = Object.entries(settings.syncStateByCompanyId ?? {}).flatMap(([companyId, syncState]) => {
2986
+ const normalizedCompanyId = normalizeCompanyId(companyId);
2987
+ return normalizedCompanyId && normalizeSyncState(syncState).status === "running" ? [normalizedCompanyId] : [];
2988
+ });
2989
+ return runningCompanyIds.length === 1 ? runningCompanyIds[0] : null;
2990
+ }
2888
2991
  function buildCancelledSyncMessage(target, progress) {
2889
2992
  const completedIssueCount = typeof progress?.completedIssueCount === "number" ? Math.max(0, progress.completedIssueCount) : void 0;
2890
2993
  const totalIssueCount = typeof progress?.totalIssueCount === "number" ? Math.max(0, progress.totalIssueCount) : void 0;
@@ -2939,11 +3042,11 @@ async function waitForSyncResultWithinGracePeriod(promise, timeoutMs) {
2939
3042
  }
2940
3043
  async function getActiveOrCurrentSyncState(ctx, companyId) {
2941
3044
  const normalizedCompanyId = normalizeCompanyId(companyId);
2942
- if (activeRunningSyncState?.syncState.status === "running" && activeRunningSyncCompanyId === normalizedCompanyId) {
2943
- return materializeScopedSettings(activeRunningSyncState, null, normalizedCompanyId);
3045
+ const activeRunningSync = getActiveRunningSyncForScope(normalizedCompanyId);
3046
+ if (activeRunningSync) {
3047
+ return activeRunningSync;
2944
3048
  }
2945
- const current = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
2946
- return materializeScopedSettings(current, null, normalizedCompanyId);
3049
+ return reconcileOrphanedRunningSyncState(ctx, normalizedCompanyId);
2947
3050
  }
2948
3051
  function updateSyncFailureContext(current, next) {
2949
3052
  if ("phase" in next) {
@@ -7183,22 +7286,36 @@ async function getGitHubPullRequestProjectItems(octokit, repository, pullRequest
7183
7286
  projectItems
7184
7287
  };
7185
7288
  }
7186
- function formatAiAuthorshipFooter(llmModel) {
7289
+ function stripAiAuthorshipFooter(body) {
7290
+ let strippedBody = body;
7291
+ while (true) {
7292
+ const nextBody = strippedBody.replace(AI_AUTHORED_LEGACY_FOOTER_PATTERN, "").replace(AI_AUTHORED_MARKDOWN_FOOTER_PATTERN, "");
7293
+ if (nextBody === strippedBody) {
7294
+ return strippedBody;
7295
+ }
7296
+ strippedBody = nextBody;
7297
+ }
7298
+ }
7299
+ function formatAiAuthorshipFooter(subject, llmModel) {
7300
+ const trimmedModel = llmModel?.trim();
7187
7301
  return `
7188
7302
 
7189
7303
  ---
7190
- ${AI_AUTHORED_COMMENT_FOOTER_PREFIX}${llmModel.trim()}.`;
7304
+ ${AI_AUTHORED_FOOTER_MARKDOWN_PREFIX}${subject} was AI-generated${trimmedModel ? ` using ${trimmedModel}` : ""}`;
7191
7305
  }
7192
- function appendAiAuthorshipFooter(body, llmModel) {
7193
- const trimmedBody = body.trim();
7306
+ function appendAiAuthorshipFooter(body, subject, llmModel) {
7307
+ const trimmedBody = stripAiAuthorshipFooter(body).trim();
7194
7308
  if (!trimmedBody) {
7195
7309
  throw new Error("Comment body cannot be empty.");
7196
7310
  }
7197
- const trimmedModel = llmModel.trim();
7198
- if (!trimmedModel) {
7199
- throw new Error("llmModel is required when posting a GitHub comment.");
7311
+ return `${trimmedBody}${formatAiAuthorshipFooter(subject, llmModel)}`;
7312
+ }
7313
+ function appendOptionalAiAuthorshipFooter(body, subject, llmModel) {
7314
+ const trimmedBody = stripAiAuthorshipFooter(body).trim();
7315
+ if (!trimmedBody) {
7316
+ return void 0;
7200
7317
  }
7201
- return `${trimmedBody}${formatAiAuthorshipFooter(trimmedModel)}`;
7318
+ return `${trimmedBody}${formatAiAuthorshipFooter(subject, llmModel)}`;
7202
7319
  }
7203
7320
  async function listAllGitHubIssueComments(octokit, repository, issueNumber) {
7204
7321
  const comments = [];
@@ -9443,6 +9560,9 @@ function shouldRunScheduledSync(settings, scheduledAt) {
9443
9560
  if (getActiveGitHubRateLimitPause(settings.syncState, now)) {
9444
9561
  return false;
9445
9562
  }
9563
+ if (settings.syncState.status === "running") {
9564
+ return false;
9565
+ }
9446
9566
  if (!settings.syncState.checkedAt) {
9447
9567
  return true;
9448
9568
  }
@@ -10002,6 +10122,7 @@ async function startSync(ctx, trigger, options = {}) {
10002
10122
  );
10003
10123
  return quickResult2 ?? await getActiveOrCurrentSyncState(ctx);
10004
10124
  }
10125
+ await reconcileOrphanedRunningSyncState(ctx, options.target?.companyId);
10005
10126
  const [config, persistedSettings] = await Promise.all([
10006
10127
  getResolvedConfig(ctx),
10007
10128
  ctx.state.get(SETTINGS_SCOPE).then((value) => normalizeSettings(value))
@@ -10262,7 +10383,8 @@ function registerGitHubAgentTools(ctx) {
10262
10383
  removeValues: normalizeToolStringArray(input.removeAssignees)
10263
10384
  });
10264
10385
  const title = Object.prototype.hasOwnProperty.call(input, "title") && typeof input.title === "string" ? input.title : void 0;
10265
- const body = Object.prototype.hasOwnProperty.call(input, "body") && typeof input.body === "string" ? input.body : void 0;
10386
+ const llmModel = normalizeOptionalToolString(input.llmModel);
10387
+ const body = Object.prototype.hasOwnProperty.call(input, "body") && typeof input.body === "string" ? appendOptionalAiAuthorshipFooter(input.body, "issue description", llmModel) : void 0;
10266
10388
  const state = input.state === "open" || input.state === "closed" ? input.state : void 0;
10267
10389
  const milestoneNumber = Object.prototype.hasOwnProperty.call(input, "milestoneNumber") ? input.milestoneNumber === null ? null : normalizeToolPositiveInteger(input.milestoneNumber) : void 0;
10268
10390
  const hasChanges = title !== void 0 || body !== void 0 || state !== void 0 || Object.prototype.hasOwnProperty.call(input, "milestoneNumber") || normalizeToolStringArray(input.setLabels).length > 0 || normalizeToolStringArray(input.addLabels).length > 0 || normalizeToolStringArray(input.removeLabels).length > 0 || normalizeToolStringArray(input.setAssignees).length > 0 || normalizeToolStringArray(input.addAssignees).length > 0 || normalizeToolStringArray(input.removeAssignees).length > 0;
@@ -10312,7 +10434,7 @@ function registerGitHubAgentTools(ctx) {
10312
10434
  const input = getToolInputRecord(params);
10313
10435
  const target = await resolveGitHubIssueToolTarget(ctx, runCtx, input);
10314
10436
  const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10315
- const body = appendAiAuthorshipFooter(String(input.body ?? ""), normalizeOptionalToolString(input.llmModel) ?? "");
10437
+ const body = appendAiAuthorshipFooter(String(input.body ?? ""), "comment", normalizeOptionalToolString(input.llmModel));
10316
10438
  const response = await octokit.rest.issues.createComment({
10317
10439
  owner: target.repository.owner,
10318
10440
  repo: target.repository.repo,
@@ -10350,6 +10472,11 @@ function registerGitHubAgentTools(ctx) {
10350
10472
  if (!head || !base || !title) {
10351
10473
  throw new Error("head, base, and title are required.");
10352
10474
  }
10475
+ const body = typeof input.body === "string" ? appendOptionalAiAuthorshipFooter(
10476
+ input.body,
10477
+ "pull request description",
10478
+ normalizeOptionalToolString(input.llmModel)
10479
+ ) : void 0;
10353
10480
  const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10354
10481
  const response = await octokit.rest.pulls.create({
10355
10482
  owner: repository.owner,
@@ -10357,7 +10484,7 @@ function registerGitHubAgentTools(ctx) {
10357
10484
  head,
10358
10485
  base,
10359
10486
  title,
10360
- ...typeof input.body === "string" ? { body: input.body } : {},
10487
+ ...body !== void 0 ? { body } : {},
10361
10488
  ...typeof input.draft === "boolean" ? { draft: input.draft } : {},
10362
10489
  headers: {
10363
10490
  "X-GitHub-Api-Version": GITHUB_API_VERSION
@@ -10445,7 +10572,8 @@ function registerGitHubAgentTools(ctx) {
10445
10572
  }
10446
10573
  });
10447
10574
  const title = Object.prototype.hasOwnProperty.call(input, "title") && typeof input.title === "string" ? input.title : void 0;
10448
- const body = Object.prototype.hasOwnProperty.call(input, "body") && typeof input.body === "string" ? input.body : void 0;
10575
+ const llmModel = normalizeOptionalToolString(input.llmModel);
10576
+ const body = Object.prototype.hasOwnProperty.call(input, "body") && typeof input.body === "string" ? appendOptionalAiAuthorshipFooter(input.body, "pull request description", llmModel) : void 0;
10449
10577
  const base = normalizeOptionalToolString(input.base);
10450
10578
  const state = input.state === "open" || input.state === "closed" ? input.state : void 0;
10451
10579
  const isDraft = typeof input.isDraft === "boolean" ? input.isDraft : void 0;
@@ -10637,7 +10765,7 @@ function registerGitHubAgentTools(ctx) {
10637
10765
  if (!threadId) {
10638
10766
  throw new Error("threadId is required.");
10639
10767
  }
10640
- const body = appendAiAuthorshipFooter(String(input.body ?? ""), normalizeOptionalToolString(input.llmModel) ?? "");
10768
+ const body = appendAiAuthorshipFooter(String(input.body ?? ""), "comment", normalizeOptionalToolString(input.llmModel));
10641
10769
  const octokit = await createGitHubToolOctokit(ctx, runCtx.companyId);
10642
10770
  const response = await octokit.graphql(
10643
10771
  GITHUB_ADD_PULL_REQUEST_REVIEW_THREAD_REPLY_MUTATION,
@@ -10862,6 +10990,7 @@ var plugin = definePlugin({
10862
10990
  const record = input && typeof input === "object" ? input : {};
10863
10991
  const requestedCompanyId = normalizeCompanyId(record.companyId);
10864
10992
  const includeAssignees = Boolean(requestedCompanyId && record.includeAssignees === true);
10993
+ await reconcileOrphanedRunningSyncState(ctx, requestedCompanyId);
10865
10994
  const saved = await ctx.state.get(SETTINGS_SCOPE);
10866
10995
  const importRegistry = normalizeImportRegistry(await ctx.state.get(IMPORT_REGISTRY_SCOPE));
10867
10996
  const normalizedSettings = normalizeSettings(saved);
@@ -11148,7 +11277,12 @@ var plugin = definePlugin({
11148
11277
  });
11149
11278
  });
11150
11279
  ctx.actions.register("sync.cancel", async () => {
11151
- const currentSettings = await getActiveOrCurrentSyncState(ctx, activeRunningSyncCompanyId);
11280
+ const persistedRunningSyncCompanyId = activeRunningSyncState?.syncState.status === "running" ? activeRunningSyncCompanyId : resolvePersistedRunningSyncCompanyId(normalizeSettings(await ctx.state.get(SETTINGS_SCOPE)));
11281
+ const currentSettings = await reconcileOrphanedRunningSyncState(
11282
+ ctx,
11283
+ persistedRunningSyncCompanyId === null ? void 0 : persistedRunningSyncCompanyId,
11284
+ "cancelled"
11285
+ );
11152
11286
  if (currentSettings.syncState.status !== "running") {
11153
11287
  return currentSettings;
11154
11288
  }
@@ -11169,7 +11303,7 @@ var plugin = definePlugin({
11169
11303
  message: CANCELLING_SYNC_MESSAGE,
11170
11304
  cancelRequestedAt: cancellationRequest.requestedAt
11171
11305
  }),
11172
- activeRunningSyncCompanyId
11306
+ persistedRunningSyncCompanyId === null ? void 0 : persistedRunningSyncCompanyId
11173
11307
  );
11174
11308
  activeRunningSyncState = next;
11175
11309
  return next;
@@ -11180,14 +11314,15 @@ var plugin = definePlugin({
11180
11314
  const trigger = job.trigger === "retry" ? "retry" : "schedule";
11181
11315
  const scheduledTargets = listScheduledSyncTargets(settings);
11182
11316
  if (scheduledTargets.length === 0) {
11183
- if (job.trigger === "schedule" && !shouldRunScheduledSync(settings, job.scheduledAt)) {
11317
+ const reconciledSettings = await reconcileOrphanedRunningSyncState(ctx);
11318
+ if (job.trigger === "schedule" && !shouldRunScheduledSync(reconciledSettings, job.scheduledAt)) {
11184
11319
  return;
11185
11320
  }
11186
11321
  await startSync(ctx, trigger);
11187
11322
  return;
11188
11323
  }
11189
11324
  for (const target of scheduledTargets) {
11190
- const scopedSettings = materializeScopedSettings(settings, null, target?.companyId);
11325
+ const scopedSettings = await reconcileOrphanedRunningSyncState(ctx, target?.companyId);
11191
11326
  if (job.trigger === "schedule" && !shouldRunScheduledSync(scopedSettings, job.scheduledAt)) {
11192
11327
  continue;
11193
11328
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-github-plugin",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "Paperclip plugin for synchronizing GitHub issues into Paperclip projects.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",