@r_masseater/ops-harbor 0.1.3 → 0.1.5

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.
@@ -567,133 +567,8 @@ function deriveAlerts(item, recentEvents = []) {
567
567
  function hasBlockingAlerts(item) {
568
568
  return item.alerts.some((alert) => alert.severity !== "info");
569
569
  }
570
- // ../../packages/ops-harbor-core/src/db-helpers.ts
571
- function mapWorkItemsToAlertSummaries(workItems) {
572
- return workItems.map((item) => ({
573
- workItemId: item.id,
574
- repository: item.repository,
575
- number: item.number,
576
- title: item.title,
577
- url: item.url,
578
- alerts: item.alerts,
579
- updatedAt: item.updatedAt
580
- }));
581
- }
582
- function buildActivityWhereClause(options, columnMap = {
583
- workItemId: "work_item_id",
584
- repository: "repository"
585
- }) {
586
- const clauses = [];
587
- const params = [];
588
- if (options.workItemId) {
589
- clauses.push(`${columnMap.workItemId} = ?`);
590
- params.push(options.workItemId);
591
- }
592
- if (options.repository) {
593
- clauses.push(`${columnMap.repository} = ?`);
594
- params.push(options.repository);
595
- }
596
- const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
597
- return { where, params };
598
- }
599
- // ../../packages/ops-harbor-core/src/filters.ts
600
- function filterWorkItems(items, filter) {
601
- return items.filter((item) => {
602
- if (filter.state && item.state !== filter.state)
603
- return false;
604
- if (filter.repository && item.repository !== filter.repository)
605
- return false;
606
- if (filter.hasBlocker !== undefined && hasBlockingAlerts(item) !== filter.hasBlocker) {
607
- return false;
608
- }
609
- if (filter.updatedSince && item.updatedAt < filter.updatedSince)
610
- return false;
611
- return true;
612
- });
613
- }
614
- function parseWorkItemFilterFromQuery(getParam) {
615
- const filter = {};
616
- const state = getParam("state");
617
- if (state === "open" || state === "closed" || state === "merged") {
618
- filter.state = state;
619
- }
620
- const repository = getParam("repository");
621
- if (repository)
622
- filter.repository = repository;
623
- if (getParam("has_blocker") === "true") {
624
- filter.hasBlocker = true;
625
- } else if (getParam("has_blocker") === "false") {
626
- filter.hasBlocker = false;
627
- }
628
- const updatedSince = getParam("updated_since");
629
- if (updatedSince)
630
- filter.updatedSince = updatedSince;
631
- return filter;
632
- }
633
- // ../../packages/ops-harbor-core/src/prompt.ts
634
- var TRIGGER_INSTRUCTIONS = {
635
- ci_failed: "Investigate the failing checks, implement the minimum safe fix, and rerun local validation.",
636
- conflicted: "Resolve merge conflicts with the base branch while preserving branch intent.",
637
- base_behind: "Update the branch with the latest base branch changes and resolve follow-up issues.",
638
- review_commented: "Review the latest reviewer feedback, apply the requested follow-up if it is actionable, and summarize the changes.",
639
- review_changes_requested: "Address the requested review changes, validate locally, and prepare the branch for another review."
640
- };
641
- function buildAutomationPrompt(workItem, trigger) {
642
- return [
643
- `Provider: ${workItem.provider}`,
644
- `Repository: ${workItem.repository}`,
645
- `Work item: #${workItem.number} ${workItem.title}`,
646
- `URL: ${workItem.url}`,
647
- `Base branch: ${workItem.baseBranch}`,
648
- `Head branch: ${workItem.headBranch}`,
649
- `Trigger: ${trigger}`,
650
- "",
651
- TRIGGER_INSTRUCTIONS[trigger]
652
- ].join(`
653
- `);
654
- }
655
- // ../../packages/ops-harbor-core/src/port-finder.ts
656
- import { createServer } from "net";
657
- var MAX_PORT = 65535;
658
- async function findAvailablePort(startPort) {
659
- for (let port = startPort;port <= MAX_PORT; port += 1) {
660
- if (await isPortAvailable(port)) {
661
- return port;
662
- }
663
- }
664
- throw new Error(`No available port found between ${startPort} and ${MAX_PORT}`);
665
- }
666
- function isPortAvailable(port) {
667
- return new Promise((resolve) => {
668
- const server = createServer();
669
- server.once("error", () => resolve(false));
670
- server.once("listening", () => {
671
- server.close(() => resolve(true));
672
- });
673
- server.listen(port, "127.0.0.1");
674
- });
675
- }
676
- // ../../packages/ops-harbor-core/src/sqlite.ts
677
- import { createRequire } from "module";
678
- import { resolve } from "path";
679
- var requireFromModule = createRequire(import.meta.url);
680
- var requireFromWorkingDirectory = createRequire(resolve(process.cwd(), "package.json"));
681
- function openSqliteDatabase(filename) {
682
- const db = typeof Bun !== "undefined" ? new (requireFromModule("bun:sqlite")).Database(filename) : new (requireFromWorkingDirectory("better-sqlite3"))(filename);
683
- db.exec("PRAGMA journal_mode = WAL");
684
- return db;
685
- }
686
- function parseJson(value, fallback) {
687
- if (!value)
688
- return fallback;
689
- try {
690
- return JSON.parse(value);
691
- } catch {
692
- return fallback;
693
- }
694
- }
695
- // ../ops-harbor-control-plane/src/lib/github.ts
696
- import { createHmac, createSign } from "crypto";
570
+ // ../../packages/ops-harbor-core/src/github.ts
571
+ import { createSign } from "crypto";
697
572
  var SEARCH_QUERY = `
698
573
  query SearchPullRequests($query: String!, $first: Int!, $after: String) {
699
574
  search(query: $query, type: ISSUE, first: $first, after: $after) {
@@ -836,6 +711,202 @@ function createAppJwt(appId, privateKeyPem) {
836
711
  const signature = signer.sign(privateKeyPem);
837
712
  return `${encoded}.${toBase64Url(signature)}`;
838
713
  }
714
+ function mapChecks(node) {
715
+ const contexts = node.commits.nodes[0]?.commit.statusCheckRollup?.contexts.nodes ?? [];
716
+ return contexts.map((context) => {
717
+ if (context.__typename === "CheckRun") {
718
+ return {
719
+ name: context.name,
720
+ status: context.status,
721
+ conclusion: context.conclusion,
722
+ url: context.detailsUrl ?? null,
723
+ startedAt: context.startedAt ?? null,
724
+ completedAt: context.completedAt ?? null
725
+ };
726
+ }
727
+ return {
728
+ name: context.context,
729
+ status: context.state === "PENDING" ? "pending" : "completed",
730
+ conclusion: context.state.toLowerCase(),
731
+ url: context.targetUrl ?? null,
732
+ startedAt: context.createdAt ?? null,
733
+ completedAt: context.createdAt ?? null
734
+ };
735
+ });
736
+ }
737
+ function mapReviews(node) {
738
+ return node.latestReviews.nodes.map((review) => ({
739
+ id: review.id,
740
+ state: review.state.toLowerCase(),
741
+ author: review.author?.login ?? "unknown",
742
+ submittedAt: review.submittedAt,
743
+ ...review.body ? { bodySnippet: review.body.slice(0, 240) } : {},
744
+ ...review.url ? { url: review.url } : {}
745
+ }));
746
+ }
747
+ function mapPullRequestNode(node, installationId) {
748
+ const checks = mapChecks(node);
749
+ const reviews = mapReviews(node);
750
+ const statusSummary = summarizeStatus(checks, reviews, {
751
+ mergeable: node.mergeable === "CONFLICTING" ? "conflicting" : node.mergeable === "MERGEABLE" ? "mergeable" : "unknown",
752
+ mergeStateStatus: node.mergeStateStatus,
753
+ reviewDecision: node.reviewDecision
754
+ });
755
+ return {
756
+ id: node.id,
757
+ provider: "github",
758
+ kind: "pull_request",
759
+ repository: node.repository.nameWithOwner,
760
+ number: node.number,
761
+ title: node.title,
762
+ url: node.url,
763
+ state: node.state.toLowerCase(),
764
+ author: node.author?.login ?? "unknown",
765
+ isDraft: node.isDraft,
766
+ headBranch: node.headRefName,
767
+ headSha: node.headRefOid,
768
+ baseBranch: node.baseRefName,
769
+ mergeable: node.mergeable === "CONFLICTING" ? "conflicting" : node.mergeable === "MERGEABLE" ? "mergeable" : "unknown",
770
+ mergeStateStatus: node.mergeStateStatus,
771
+ reviewDecision: node.reviewDecision,
772
+ statusSummary,
773
+ checks,
774
+ reviews,
775
+ alerts: [],
776
+ lastActivityAt: node.updatedAt,
777
+ updatedAt: node.updatedAt,
778
+ providerPayload: {
779
+ installationId,
780
+ repositoryUrl: node.repository.url
781
+ }
782
+ };
783
+ }
784
+ // ../../packages/ops-harbor-core/src/db-helpers.ts
785
+ function mapWorkItemsToAlertSummaries(workItems) {
786
+ return workItems.map((item) => ({
787
+ workItemId: item.id,
788
+ repository: item.repository,
789
+ number: item.number,
790
+ title: item.title,
791
+ url: item.url,
792
+ alerts: item.alerts,
793
+ updatedAt: item.updatedAt
794
+ }));
795
+ }
796
+ function buildActivityWhereClause(options, columnMap = {
797
+ workItemId: "work_item_id",
798
+ repository: "repository"
799
+ }) {
800
+ const clauses = [];
801
+ const params = [];
802
+ if (options.workItemId) {
803
+ clauses.push(`${columnMap.workItemId} = ?`);
804
+ params.push(options.workItemId);
805
+ }
806
+ if (options.repository) {
807
+ clauses.push(`${columnMap.repository} = ?`);
808
+ params.push(options.repository);
809
+ }
810
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
811
+ return { where, params };
812
+ }
813
+ // ../../packages/ops-harbor-core/src/filters.ts
814
+ function filterWorkItems(items, filter) {
815
+ return items.filter((item) => {
816
+ if (filter.state && item.state !== filter.state)
817
+ return false;
818
+ if (filter.repository && item.repository !== filter.repository)
819
+ return false;
820
+ if (filter.hasBlocker !== undefined && hasBlockingAlerts(item) !== filter.hasBlocker) {
821
+ return false;
822
+ }
823
+ if (filter.updatedSince && item.updatedAt < filter.updatedSince)
824
+ return false;
825
+ return true;
826
+ });
827
+ }
828
+ function parseWorkItemFilterFromQuery(getParam) {
829
+ const filter = {};
830
+ const state = getParam("state");
831
+ if (state === "open" || state === "closed" || state === "merged") {
832
+ filter.state = state;
833
+ }
834
+ const repository = getParam("repository");
835
+ if (repository)
836
+ filter.repository = repository;
837
+ if (getParam("has_blocker") === "true") {
838
+ filter.hasBlocker = true;
839
+ } else if (getParam("has_blocker") === "false") {
840
+ filter.hasBlocker = false;
841
+ }
842
+ const updatedSince = getParam("updated_since");
843
+ if (updatedSince)
844
+ filter.updatedSince = updatedSince;
845
+ return filter;
846
+ }
847
+ // ../../packages/ops-harbor-core/src/prompt.ts
848
+ var TRIGGER_INSTRUCTIONS = {
849
+ ci_failed: "Investigate the failing checks, implement the minimum safe fix, and rerun local validation.",
850
+ conflicted: "Resolve merge conflicts with the base branch while preserving branch intent.",
851
+ base_behind: "Update the branch with the latest base branch changes and resolve follow-up issues.",
852
+ review_commented: "Review the latest reviewer feedback, apply the requested follow-up if it is actionable, and summarize the changes.",
853
+ review_changes_requested: "Address the requested review changes, validate locally, and prepare the branch for another review."
854
+ };
855
+ function buildAutomationPrompt(workItem, trigger) {
856
+ return [
857
+ `Provider: ${workItem.provider}`,
858
+ `Repository: ${workItem.repository}`,
859
+ `Work item: #${workItem.number} ${workItem.title}`,
860
+ `URL: ${workItem.url}`,
861
+ `Base branch: ${workItem.baseBranch}`,
862
+ `Head branch: ${workItem.headBranch}`,
863
+ `Trigger: ${trigger}`,
864
+ "",
865
+ TRIGGER_INSTRUCTIONS[trigger]
866
+ ].join(`
867
+ `);
868
+ }
869
+ // ../../packages/ops-harbor-core/src/port-finder.ts
870
+ import { createServer } from "net";
871
+ var MAX_PORT = 65535;
872
+ async function findAvailablePort(startPort) {
873
+ for (let port = startPort;port <= MAX_PORT; port += 1) {
874
+ if (await isPortAvailable(port)) {
875
+ return port;
876
+ }
877
+ }
878
+ throw new Error(`No available port found between ${startPort} and ${MAX_PORT}`);
879
+ }
880
+ function isPortAvailable(port) {
881
+ return new Promise((resolve) => {
882
+ const server = createServer();
883
+ server.once("error", () => resolve(false));
884
+ server.once("listening", () => {
885
+ server.close(() => resolve(true));
886
+ });
887
+ server.listen(port, "127.0.0.1");
888
+ });
889
+ }
890
+ // ../../packages/ops-harbor-core/src/sqlite.ts
891
+ import { createRequire } from "module";
892
+ var overriddenCtor;
893
+ function openSqliteDatabase(filename) {
894
+ const Ctor = overriddenCtor ?? createRequire(import.meta.url)("bun:sqlite").Database;
895
+ const db = new Ctor(filename);
896
+ db.exec("PRAGMA journal_mode = WAL");
897
+ return db;
898
+ }
899
+ function parseJson(value, fallback) {
900
+ if (!value)
901
+ return fallback;
902
+ try {
903
+ return JSON.parse(value);
904
+ } catch {
905
+ return fallback;
906
+ }
907
+ }
908
+ // ../ops-harbor-control-plane/src/lib/github.ts
909
+ import { createHmac } from "crypto";
839
910
  async function githubRequest(config, path, init, scope) {
840
911
  const response = await fetch(`${config.githubApiUrl}${path}`, {
841
912
  ...init,
@@ -957,7 +1028,7 @@ async function ensureGitHubAppWebhookUrl(config, webhookUrl) {
957
1028
  rateLimit: mergeRateLimitSnapshots(current.rateLimit, next.rateLimit)
958
1029
  };
959
1030
  }
960
- async function createInstallationToken(config, installationId) {
1031
+ async function createInstallationToken2(config, installationId) {
961
1032
  const { data, rateLimit } = await githubRequest(config, `/app/installations/${installationId}/access_tokens`, {
962
1033
  method: "POST",
963
1034
  headers: {
@@ -967,7 +1038,7 @@ async function createInstallationToken(config, installationId) {
967
1038
  return { token: data.token, rateLimit };
968
1039
  }
969
1040
  async function graphql(config, installationId, query, variables) {
970
- const { token } = await createInstallationToken(config, installationId);
1041
+ const { token } = await createInstallationToken2(config, installationId);
971
1042
  const response = await fetch(`${config.githubApiUrl}/graphql`, {
972
1043
  method: "POST",
973
1044
  headers: {
@@ -996,77 +1067,7 @@ async function graphql(config, installationId, query, variables) {
996
1067
  }
997
1068
  };
998
1069
  }
999
- function mapChecks(node) {
1000
- const contexts = node.commits.nodes[0]?.commit.statusCheckRollup?.contexts.nodes ?? [];
1001
- return contexts.map((context) => {
1002
- if (context.__typename === "CheckRun") {
1003
- return {
1004
- name: context.name,
1005
- status: context.status,
1006
- conclusion: context.conclusion,
1007
- url: context.detailsUrl ?? null,
1008
- startedAt: context.startedAt ?? null,
1009
- completedAt: context.completedAt ?? null
1010
- };
1011
- }
1012
- return {
1013
- name: context.context,
1014
- status: context.state === "PENDING" ? "pending" : "completed",
1015
- conclusion: context.state.toLowerCase(),
1016
- url: context.targetUrl ?? null,
1017
- startedAt: context.createdAt ?? null,
1018
- completedAt: context.createdAt ?? null
1019
- };
1020
- });
1021
- }
1022
- function mapReviews(node) {
1023
- return node.latestReviews.nodes.map((review) => ({
1024
- id: review.id,
1025
- state: review.state.toLowerCase(),
1026
- author: review.author?.login ?? "unknown",
1027
- submittedAt: review.submittedAt,
1028
- ...review.body ? { bodySnippet: review.body.slice(0, 240) } : {},
1029
- ...review.url ? { url: review.url } : {}
1030
- }));
1031
- }
1032
- function mapPullRequest(node, installationId) {
1033
- const checks = mapChecks(node);
1034
- const reviews = mapReviews(node);
1035
- const statusSummary = summarizeStatus(checks, reviews, {
1036
- mergeable: node.mergeable === "CONFLICTING" ? "conflicting" : node.mergeable === "MERGEABLE" ? "mergeable" : "unknown",
1037
- mergeStateStatus: node.mergeStateStatus,
1038
- reviewDecision: node.reviewDecision
1039
- });
1040
- return {
1041
- id: node.id,
1042
- provider: "github",
1043
- kind: "pull_request",
1044
- repository: node.repository.nameWithOwner,
1045
- number: node.number,
1046
- title: node.title,
1047
- url: node.url,
1048
- state: node.state.toLowerCase(),
1049
- author: node.author?.login ?? "unknown",
1050
- isDraft: node.isDraft,
1051
- headBranch: node.headRefName,
1052
- headSha: node.headRefOid,
1053
- baseBranch: node.baseRefName,
1054
- mergeable: node.mergeable === "CONFLICTING" ? "conflicting" : node.mergeable === "MERGEABLE" ? "mergeable" : "unknown",
1055
- mergeStateStatus: node.mergeStateStatus,
1056
- reviewDecision: node.reviewDecision,
1057
- statusSummary,
1058
- checks,
1059
- reviews,
1060
- alerts: [],
1061
- lastActivityAt: node.updatedAt,
1062
- updatedAt: node.updatedAt,
1063
- providerPayload: {
1064
- installationId,
1065
- repositoryUrl: node.repository.url
1066
- }
1067
- };
1068
- }
1069
- async function fetchOpenPullRequestsForAuthor(config, installationId, author, limit = 100) {
1070
+ async function fetchOpenPullRequestsForAuthor2(config, installationId, author, limit = 100) {
1070
1071
  const items = [];
1071
1072
  let cursor = null;
1072
1073
  let lastRateLimit = {};
@@ -1078,7 +1079,7 @@ async function fetchOpenPullRequestsForAuthor(config, installationId, author, li
1078
1079
  });
1079
1080
  const data = response.data;
1080
1081
  lastRateLimit = response.rateLimit;
1081
- items.push(...data.search.nodes.map((node) => mapPullRequest(node, installationId)));
1082
+ items.push(...data.search.nodes.map((node) => mapPullRequestNode(node, installationId)));
1082
1083
  if (!data.search.pageInfo.hasNextPage)
1083
1084
  break;
1084
1085
  cursor = data.search.pageInfo.endCursor;
@@ -1092,7 +1093,7 @@ async function hydratePullRequest(config, installationId, repository, number2) {
1092
1093
  }
1093
1094
  const { data, rateLimit } = await graphql(config, installationId, PULL_REQUEST_QUERY, { owner, repo, number: number2 });
1094
1095
  return {
1095
- item: data.repository.pullRequest ? mapPullRequest(data.repository.pullRequest, installationId) : null,
1096
+ item: data.repository.pullRequest ? mapPullRequestNode(data.repository.pullRequest, installationId) : null,
1096
1097
  rateLimit
1097
1098
  };
1098
1099
  }
@@ -2749,6 +2750,148 @@ var cors = (options) => {
2749
2750
  };
2750
2751
  };
2751
2752
 
2753
+ // ../../node_modules/.bun/hono@4.12.10/node_modules/hono/dist/utils/stream.js
2754
+ var StreamingApi = class {
2755
+ writer;
2756
+ encoder;
2757
+ writable;
2758
+ abortSubscribers = [];
2759
+ responseReadable;
2760
+ aborted = false;
2761
+ closed = false;
2762
+ constructor(writable, _readable) {
2763
+ this.writable = writable;
2764
+ this.writer = writable.getWriter();
2765
+ this.encoder = new TextEncoder;
2766
+ const reader = _readable.getReader();
2767
+ this.abortSubscribers.push(async () => {
2768
+ await reader.cancel();
2769
+ });
2770
+ this.responseReadable = new ReadableStream({
2771
+ async pull(controller) {
2772
+ const { done, value } = await reader.read();
2773
+ done ? controller.close() : controller.enqueue(value);
2774
+ },
2775
+ cancel: () => {
2776
+ this.abort();
2777
+ }
2778
+ });
2779
+ }
2780
+ async write(input) {
2781
+ try {
2782
+ if (typeof input === "string") {
2783
+ input = this.encoder.encode(input);
2784
+ }
2785
+ await this.writer.write(input);
2786
+ } catch {}
2787
+ return this;
2788
+ }
2789
+ async writeln(input) {
2790
+ await this.write(input + `
2791
+ `);
2792
+ return this;
2793
+ }
2794
+ sleep(ms) {
2795
+ return new Promise((res) => setTimeout(res, ms));
2796
+ }
2797
+ async close() {
2798
+ try {
2799
+ await this.writer.close();
2800
+ } catch {}
2801
+ this.closed = true;
2802
+ }
2803
+ async pipe(body) {
2804
+ this.writer.releaseLock();
2805
+ await body.pipeTo(this.writable, { preventClose: true });
2806
+ this.writer = this.writable.getWriter();
2807
+ }
2808
+ onAbort(listener) {
2809
+ this.abortSubscribers.push(listener);
2810
+ }
2811
+ abort() {
2812
+ if (!this.aborted) {
2813
+ this.aborted = true;
2814
+ this.abortSubscribers.forEach((subscriber) => subscriber());
2815
+ }
2816
+ }
2817
+ };
2818
+
2819
+ // ../../node_modules/.bun/hono@4.12.10/node_modules/hono/dist/helper/streaming/utils.js
2820
+ var isOldBunVersion = () => {
2821
+ const version = typeof Bun !== "undefined" ? Bun.version : undefined;
2822
+ if (version === undefined) {
2823
+ return false;
2824
+ }
2825
+ const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
2826
+ isOldBunVersion = () => result;
2827
+ return result;
2828
+ };
2829
+
2830
+ // ../../node_modules/.bun/hono@4.12.10/node_modules/hono/dist/helper/streaming/sse.js
2831
+ var SSEStreamingApi = class extends StreamingApi {
2832
+ constructor(writable, readable) {
2833
+ super(writable, readable);
2834
+ }
2835
+ async writeSSE(message) {
2836
+ const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
2837
+ const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
2838
+ return `data: ${line}`;
2839
+ }).join(`
2840
+ `);
2841
+ for (const key of ["event", "id", "retry"]) {
2842
+ if (message[key] && /[\r\n]/.test(message[key])) {
2843
+ throw new Error(`${key} must not contain "\\r" or "\\n"`);
2844
+ }
2845
+ }
2846
+ const sseData = [
2847
+ message.event && `event: ${message.event}`,
2848
+ dataLines,
2849
+ message.id && `id: ${message.id}`,
2850
+ message.retry && `retry: ${message.retry}`
2851
+ ].filter(Boolean).join(`
2852
+ `) + `
2853
+
2854
+ `;
2855
+ await this.write(sseData);
2856
+ }
2857
+ };
2858
+ var run = async (stream, cb, onError) => {
2859
+ try {
2860
+ await cb(stream);
2861
+ } catch (e) {
2862
+ if (e instanceof Error && onError) {
2863
+ await onError(e, stream);
2864
+ await stream.writeSSE({
2865
+ event: "error",
2866
+ data: e.message
2867
+ });
2868
+ } else {
2869
+ console.error(e);
2870
+ }
2871
+ } finally {
2872
+ stream.close();
2873
+ }
2874
+ };
2875
+ var contextStash = /* @__PURE__ */ new WeakMap;
2876
+ var streamSSE = (c, cb, onError) => {
2877
+ const { readable, writable } = new TransformStream;
2878
+ const stream = new SSEStreamingApi(writable, readable);
2879
+ if (isOldBunVersion()) {
2880
+ c.req.raw.signal.addEventListener("abort", () => {
2881
+ if (!stream.closed) {
2882
+ stream.abort();
2883
+ }
2884
+ });
2885
+ }
2886
+ contextStash.set(stream.responseReadable, c);
2887
+ c.header("Transfer-Encoding", "chunked");
2888
+ c.header("Content-Type", "text/event-stream");
2889
+ c.header("Cache-Control", "no-cache");
2890
+ c.header("Connection", "keep-alive");
2891
+ run(stream, cb, onError);
2892
+ return c.newResponse(stream.responseReadable);
2893
+ };
2894
+
2752
2895
  // ../ops-harbor-control-plane/src/lib/db.ts
2753
2896
  import { mkdirSync } from "fs";
2754
2897
  import { dirname as dirname2 } from "path";
@@ -3277,7 +3420,7 @@ async function syncAuthorWorkItems(db, config, author) {
3277
3420
  let changed = 0;
3278
3421
  for (const installation of installations) {
3279
3422
  upsertInstallation(db, installation.id, installation.account?.login, installation.account?.type);
3280
- const { items, rateLimit: searchRateLimit } = await fetchOpenPullRequestsForAuthor(config, installation.id, author);
3423
+ const { items, rateLimit: searchRateLimit } = await fetchOpenPullRequestsForAuthor2(config, installation.id, author);
3281
3424
  recordRateLimit(db, `installation.${installation.id}.search`, searchRateLimit);
3282
3425
  for (const item of items) {
3283
3426
  const existing = getWorkItem(db, item.id);
@@ -3309,6 +3452,8 @@ async function hydrateAndPersist(db, config, installationId, repository, number2
3309
3452
  recordRateLimit(db, `installation.${installationId}.hydrate`, rateLimit);
3310
3453
  if (!item)
3311
3454
  return;
3455
+ if (config.defaultAuthor && item.author !== config.defaultAuthor)
3456
+ return;
3312
3457
  await saveHydratedWorkItem(db, item, event);
3313
3458
  }
3314
3459
  async function handleWebhookEvent(db, config, eventName, payload) {
@@ -3370,6 +3515,19 @@ async function handleWebhookEvent(db, config, eventName, payload) {
3370
3515
  }
3371
3516
 
3372
3517
  // ../ops-harbor-control-plane/src/server.ts
3518
+ function createEventBus() {
3519
+ const listeners = new Set;
3520
+ return {
3521
+ subscribe(listener) {
3522
+ listeners.add(listener);
3523
+ return () => listeners.delete(listener);
3524
+ },
3525
+ broadcast(event, data) {
3526
+ for (const listener of listeners)
3527
+ listener(event, data);
3528
+ }
3529
+ };
3530
+ }
3373
3531
  function fallbackRuntimeStatus() {
3374
3532
  return {
3375
3533
  tunnelEnabled: false,
@@ -3382,9 +3540,9 @@ function redactStoredConfig(config) {
3382
3540
  const { githubWebhookSecret: _githubWebhookSecret, ...rest } = config;
3383
3541
  return rest;
3384
3542
  }
3385
- function createControlPlaneApp({ config, db, getRuntimeStatus }) {
3543
+ function createControlPlaneApp({ config, db, bus, getRuntimeStatus }) {
3386
3544
  const app = new Hono2;
3387
- const webhookHandler = createGitHubWebhookHandler({ config, db });
3545
+ const webhookHandler = createGitHubWebhookHandler({ config, db, bus });
3388
3546
  app.use("/api/*", cors());
3389
3547
  app.use("/api/admin/*", async (c, next) => {
3390
3548
  if (config.internalApiToken && c.req.header("x-ops-harbor-token") !== config.internalApiToken) {
@@ -3398,6 +3556,28 @@ function createControlPlaneApp({ config, db, getRuntimeStatus }) {
3398
3556
  }
3399
3557
  await next();
3400
3558
  });
3559
+ app.get("/api/events", (c) => {
3560
+ const token = c.req.header("x-ops-harbor-token") ?? c.req.query("token");
3561
+ if (config.internalApiToken && token !== config.internalApiToken) {
3562
+ return c.json({ error: "unauthorized" }, 401);
3563
+ }
3564
+ return streamSSE(c, async (stream2) => {
3565
+ await stream2.writeSSE({ data: "", retry: 3000 });
3566
+ const unsubscribe = bus.subscribe((event, data) => {
3567
+ stream2.writeSSE({ event, data: JSON.stringify(data) });
3568
+ });
3569
+ const heartbeat = setInterval(() => {
3570
+ stream2.writeSSE({ event: "heartbeat", data: "{}" });
3571
+ }, 15000);
3572
+ stream2.onAbort(() => {
3573
+ clearInterval(heartbeat);
3574
+ unsubscribe();
3575
+ });
3576
+ await new Promise(() => {});
3577
+ }, async (error) => {
3578
+ console.error("SSE stream error:", error);
3579
+ });
3580
+ });
3401
3581
  app.get("/api/health", (c) => c.json({
3402
3582
  ok: true,
3403
3583
  provider: "github",
@@ -3441,6 +3621,7 @@ function createControlPlaneApp({ config, db, getRuntimeStatus }) {
3441
3621
  return c.json({ error: "author is required" }, 400);
3442
3622
  try {
3443
3623
  const result = await syncAuthorWorkItems(db, config, author);
3624
+ bus.broadcast("sync_completed", { synchronized: result.synchronized });
3444
3625
  return c.json(result);
3445
3626
  } catch (error) {
3446
3627
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
@@ -3461,7 +3642,7 @@ function createControlPlaneApp({ config, db, getRuntimeStatus }) {
3461
3642
  });
3462
3643
  return app;
3463
3644
  }
3464
- function createGitHubWebhookHandler({ config, db }) {
3645
+ function createGitHubWebhookHandler({ config, db, bus }) {
3465
3646
  return async (c) => {
3466
3647
  const rawBody = await c.req.text();
3467
3648
  if (config.githubWebhookSecret) {
@@ -3484,15 +3665,18 @@ function createGitHubWebhookHandler({ config, db }) {
3484
3665
  try {
3485
3666
  await handleWebhookEvent(db, config, eventName, payload);
3486
3667
  markWebhookDeliveryProcessed(db, deliveryId);
3668
+ const repoObj = payload.repository;
3669
+ const repository = typeof repoObj === "object" && repoObj !== null ? String(repoObj.full_name ?? "") : "";
3670
+ bus.broadcast("webhook_processed", { repository, event: eventName });
3487
3671
  return c.json({ ok: true });
3488
3672
  } catch (error) {
3489
3673
  return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
3490
3674
  }
3491
3675
  };
3492
3676
  }
3493
- function createWebhookIngressApp({ config, db, getRuntimeStatus }) {
3677
+ function createWebhookIngressApp({ config, db, bus, getRuntimeStatus }) {
3494
3678
  const app = new Hono2;
3495
- const webhookHandler = createGitHubWebhookHandler({ config, db });
3679
+ const webhookHandler = createGitHubWebhookHandler({ config, db, bus });
3496
3680
  app.get("/api/health", (c) => c.json({
3497
3681
  ok: true,
3498
3682
  ingress: "github-webhooks",
@@ -3503,10 +3687,11 @@ function createWebhookIngressApp({ config, db, getRuntimeStatus }) {
3503
3687
  }
3504
3688
  function openControlPlaneApps(config, getRuntimeStatus) {
3505
3689
  const db = openControlPlaneDb(config.dbPath);
3690
+ const bus = createEventBus();
3506
3691
  return {
3507
3692
  db,
3508
- controlPlaneApp: createControlPlaneApp({ config, db, getRuntimeStatus }),
3509
- webhookIngressApp: createWebhookIngressApp({ config, db, getRuntimeStatus })
3693
+ controlPlaneApp: createControlPlaneApp({ config, db, bus, getRuntimeStatus }),
3694
+ webhookIngressApp: createWebhookIngressApp({ config, db, bus, getRuntimeStatus })
3510
3695
  };
3511
3696
  }
3512
3697
 
@@ -3527,6 +3712,7 @@ async function main() {
3527
3712
  Bun.serve({
3528
3713
  port: config.port,
3529
3714
  hostname: "127.0.0.1",
3715
+ idleTimeout: 0,
3530
3716
  fetch: controlPlaneApp.fetch
3531
3717
  });
3532
3718
  let tunnelSession = null;
@@ -3536,6 +3722,7 @@ async function main() {
3536
3722
  Bun.serve({
3537
3723
  port: webhookIngressPort,
3538
3724
  hostname: "127.0.0.1",
3725
+ idleTimeout: 0,
3539
3726
  fetch: webhookIngressApp.fetch
3540
3727
  });
3541
3728
  tunnelSession = await openTunnel({