@agent-relay/factory 0.1.24 → 0.1.25

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.
@@ -17,7 +17,6 @@ type GithubIssueSource = {
17
17
  path: string;
18
18
  raw: Record<string, unknown>;
19
19
  };
20
- export declare const LIVE_GITHUB_ISSUE_GLOB = "/github/repos/**";
21
20
  export declare const DEFAULT_FACTORY_LOOP_HEARTBEAT_PATH = "/tmp/factory-run/factory-loop-heartbeat.json";
22
21
  export declare const DEFAULT_FACTORY_LOOP_REGISTRY_PATH = "/tmp/factory-run/factory-loop-registry.json";
23
22
  export declare function createFactory(config: FactoryConfig, ports: FactoryPorts): Factory;
@@ -56,6 +55,7 @@ export declare const githubIssuePathParts: (path: string) => {
56
55
  number: number;
57
56
  slug?: string;
58
57
  } | undefined;
58
+ export declare const githubRepoSubscriptionGlobs: (config: FactoryConfig) => string[];
59
59
  export declare const keyFromPath: (path: string) => string;
60
60
  export declare const changeEventPath: (event: ChangeEvent) => string | undefined;
61
61
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/orchestrator/factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/orchestrator/factory.ts"],"names":[],"mappings":"AAIA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAI1E,OAAO,KAAK,EAKV,WAAW,EAKX,WAAW,EAKZ,MAAM,UAAU,CAAA;AA6BjB,OAAO,KAAK,EACV,cAAc,EACd,OAAO,EACP,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,mBAAmB,EAEnB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EAGnB,QAAQ,EACR,eAAe,EACf,WAAW,EAGX,cAAc,EAEf,MAAM,UAAU,CAAA;AAOjB,KAAK,YAAY,GAAG,cAAc,GAAG,YAAY,GAAG,YAAY,GAAG,oBAAoB,GAAG,OAAO,CAAA;AACjG,KAAK,QAAQ,GAAG,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAA;AAiEtD,KAAK,iBAAiB,GAAG;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC7B,CAAA;AA8ED,eAAO,MAAM,mCAAmC,iDAAiD,CAAA;AACjG,eAAO,MAAM,kCAAkC,gDAAgD,CAAA;AAM/F,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAEjF;AAED,qBAAa,WAAY,YAAW,OAAO;;gBA8H7B,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY;IA8GhD,uBAAuB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAiEvD,KAAK,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiGpD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAsHrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAklBxB,OAAO,CAAC,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IAyNlE,OAAO,CAAC,IAAI,GAAE,qBAA0B,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAyDrE,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC;IAOxD,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IAoNlG,MAAM,IAAI,aAAa;IAWvB,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI;CA88LxD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,WAAW,CAiC5E;AAyBD,wBAAsB,oCAAoC,CACxD,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,EACpC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,WAAW,CAAC,CAetB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,iBAAiB,CA6BlF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,WAAW,CAEnF;AA2CD,wBAAsB,wBAAwB,CAC5C,IAAI,SAAsC,GACzC,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAM3C;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,oBAAoB,GAAG,SAAS,EAC3C,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9C,mBAAmB,CAgBrB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAO7D;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAc/D;AA0eD,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,KAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAwBpH,CAAA;AAiTD,eAAO,MAAM,2BAA2B,GAAI,QAAQ,aAAa,KAAG,MAAM,EAItE,CAAA;AA6gBJ,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,MAC4B,CAAA;AA4SvE,eAAO,MAAM,eAAe,GAAI,OAAO,WAAW,KAAG,MAAM,GAAG,SAG7D,CAAA"}
@@ -34,15 +34,6 @@ const GITHUB_ISSUE_ROOT = '/github/repos';
34
34
  const READY_EVENTS_LIMIT = 100;
35
35
  const LIVE_ISSUE_GLOB = `${ISSUE_ROOT}/**`;
36
36
  const LIVE_RELAYFLOW_GLOB = '/**';
37
- // Subscribe broadly under /github/repos and let isGithubIssueFilePath() /
38
- // githubPullPathParts() re-validate the exact shape in the callback.
39
- // globMatchesPath() treats a non-terminal `**` as a single-segment wildcard, so
40
- // a more specific glob like `.../issues/**/*.json` would miss the two-segment
41
- // <owner>/<repo> prefix and the nested <number>__<slug>/meta.json (and
42
- // directory) event shapes this factory accepts. A terminal `**` prefix-matches
43
- // every descendant, so all supported issue AND pull-request path variants reach
44
- // the handler — including the PR change events the babysitter reacts to.
45
- export const LIVE_GITHUB_ISSUE_GLOB = `${GITHUB_ISSUE_ROOT}/**`;
46
37
  const LIVE_DEDUPE_LIMIT = 5_000;
47
38
  const LIVE_EVENT_DRAIN_BATCH_SIZE = 5;
48
39
  const COMPLETION_SWEEP_INTERVAL_MS = 15_000;
@@ -453,7 +444,7 @@ export class FactoryLoop {
453
444
  if ((opts.mode ?? 'live') === 'live') {
454
445
  this.#started = true;
455
446
  try {
456
- await this.#startLiveSubscription(opts.liveSubscription);
447
+ await this.#startLiveSubscription(issueSource, opts.liveSubscription);
457
448
  await this.#rearmSlackReplyWatchers();
458
449
  await this.#drainReadyClarificationWake();
459
450
  await this.#rearmGithubIssueCommentWatchers();
@@ -467,7 +458,7 @@ export class FactoryLoop {
467
458
  }
468
459
  }
469
460
  await this.#backfillReadyIssues();
470
- this.#subscription = this.#mount.subscribe(this.#subscriptionGlobs([`${ISSUE_ROOT}/**/*.json`, LIVE_GITHUB_ISSUE_GLOB]), (event) => {
461
+ this.#subscription = this.#mount.subscribe(this.#subscriptionGlobs(issueSource, [`${ISSUE_ROOT}/**/*.json`]), (event) => {
471
462
  void this.#dispatchRelayflowEvent(event);
472
463
  // The SDK types `resource` as always-present, but the polling fallback and
473
464
  // degraded-sync paths can deliver events without it. Skip those rather
@@ -610,7 +601,7 @@ export class FactoryLoop {
610
601
  async dispose() {
611
602
  await this.stop();
612
603
  }
613
- async #startLiveSubscription(overrides = {}) {
604
+ async #startLiveSubscription(issueSource, overrides = {}) {
614
605
  const options = this.#liveOptions(overrides);
615
606
  await this.#startLiveHeartbeat();
616
607
  this.#liveConnectStartedAtMs = this.#clock.now();
@@ -624,41 +615,51 @@ export class FactoryLoop {
624
615
  replaySkewMarginMs: this.#liveReplaySkewMarginMs,
625
616
  highWatermarkRouteUnavailable: highWatermark.routeUnavailable,
626
617
  });
627
- // Register the live subscription BEFORE the startup full pull so an issue
618
+ // Register the live subscription BEFORE the startup backfill so an issue
628
619
  // that becomes Ready *during* the pull is captured, not lost in the window
629
620
  // between listTree and subscribe. Events buffer (deferred drain) until the
630
621
  // pull finishes; batch dedupe then suppresses any overlap with what the
631
622
  // pull already dispatched.
632
- if (options.transport !== 'poll') {
633
- // LIVE_GITHUB_ISSUE_GLOB is a terminal `${GITHUB_ISSUE_ROOT}/**`, so it
634
- // already covers the PR change events the babysitter consumes; pull-event
635
- // *processing* is gated on babysitter.enabled in #prepareLiveEventForDrain.
636
- this.#subscription = this.#mount.subscribe(this.#subscriptionGlobs([LIVE_ISSUE_GLOB, LIVE_GITHUB_ISSUE_GLOB]), (event) => {
637
- this.#enqueueLiveEvent(event);
638
- }, { from: 'now', coalesce: 'none' });
639
- }
640
- if (highWatermark.routeUnavailable) {
641
- this.#increment('liveHighWatermarkFullPullFallbacks');
642
- this.#logger.info?.('[factory] live subscription high-watermark route unavailable; running startup full pull before draining buffered events');
643
- this.#deferLiveEventDrain = true;
623
+ this.#deferLiveEventDrain = true;
624
+ try {
625
+ if (options.transport === 'poll') {
626
+ // Capture the cursor before the backfill. Events written while listTree
627
+ // is running are then picked up by the first poll instead of falling
628
+ // into a cursor-advance gap.
629
+ this.#liveEventCursor = await this.#currentEventCursor(options.eventLimit);
630
+ }
631
+ else {
632
+ this.#subscription = this.#mount.subscribe(this.#subscriptionGlobs(issueSource, [LIVE_ISSUE_GLOB]), (event) => {
633
+ this.#enqueueLiveEvent(event);
634
+ }, { from: 'now', coalesce: 'none' });
635
+ }
636
+ if (highWatermark.routeUnavailable) {
637
+ this.#increment('liveHighWatermarkFullPullFallbacks');
638
+ }
639
+ this.#increment('liveStartupBackfills');
640
+ this.#logger.info?.('[factory] running startup ready-issue backfill before draining buffered events', {
641
+ highWatermarkRouteUnavailable: highWatermark.routeUnavailable,
642
+ });
644
643
  try {
645
644
  await this.runOnce();
646
645
  }
647
646
  catch (error) {
648
- // A startup pull failure must not abort the daemon: log it and fall back
649
- // to the live event stream (plus any buffered events) instead of leaving
650
- // the factory down.
651
- this.#increment('liveHighWatermarkFullPullErrors');
647
+ // A startup backfill failure must not abort the daemon: log it and fall
648
+ // back to the live event stream (plus any buffered events) instead of
649
+ // leaving the factory down.
650
+ this.#increment('liveStartupBackfillErrors');
651
+ if (highWatermark.routeUnavailable) {
652
+ this.#increment('liveHighWatermarkFullPullErrors');
653
+ }
652
654
  this.#error(error);
653
655
  }
654
- finally {
655
- this.#deferLiveEventDrain = false;
656
- this.#scheduleLiveEventDrain();
657
- }
658
- await this.#refreshLiveHeartbeatIfDue();
659
656
  }
657
+ finally {
658
+ this.#deferLiveEventDrain = false;
659
+ this.#scheduleLiveEventDrain();
660
+ }
661
+ await this.#refreshLiveHeartbeatIfDue();
660
662
  if (options.transport === 'poll') {
661
- this.#liveEventCursor = await this.#currentEventCursor(options.eventLimit);
662
663
  this.#scheduleLivePoll(0, options);
663
664
  }
664
665
  }
@@ -873,14 +874,25 @@ export class FactoryLoop {
873
874
  return;
874
875
  await this.#refreshLiveHeartbeat();
875
876
  }
876
- #subscriptionGlobs(factoryGlobs) {
877
- return this.#relayflows ? [LIVE_RELAYFLOW_GLOB] : factoryGlobs;
877
+ #subscriptionGlobs(issueSource, linearGlobs) {
878
+ if (this.#relayflows)
879
+ return [LIVE_RELAYFLOW_GLOB];
880
+ return [
881
+ ...(issueSource === 'linear' ? linearGlobs : []),
882
+ ...githubRepoSubscriptionGlobs(this.#config),
883
+ ];
878
884
  }
879
885
  async #prepareLiveEventForDrain(event, seenIssueKeys) {
880
886
  const path = changeEventPath(event);
881
887
  if (!path) {
882
888
  return { dispatchRelayflow: false };
883
889
  }
890
+ if (!this.#relayflows &&
891
+ path.startsWith(`${GITHUB_ISSUE_ROOT}/`) &&
892
+ !isConfiguredGithubRepoPath(path, this.#config)) {
893
+ this.#increment('liveGithubEventsOutsideConfiguredRepos');
894
+ return { dispatchRelayflow: false };
895
+ }
884
896
  const isPullPath = isGithubPullFilePath(path);
885
897
  const babysitterEvent = this.#config.babysitter.enabled
886
898
  ? githubBabysitterEventPathParts(path)
@@ -8006,24 +8018,69 @@ const reposFromConfig = (config) => {
8006
8018
  ].filter((repo) => Boolean(repo)));
8007
8019
  return [...repos];
8008
8020
  };
8009
- const githubIssueScanRoots = (config) => {
8010
- const roots = new Set([GITHUB_ISSUE_ROOT]);
8011
- for (const repo of reposFromConfig(config)) {
8012
- const parts = githubRepoParts(repo);
8021
+ const configuredGithubRepoParts = (config) => {
8022
+ const repos = new Map();
8023
+ for (const configuredRepo of reposFromConfig(config)) {
8024
+ let parts = githubRepoParts(configuredRepo);
8025
+ if (!parts) {
8026
+ try {
8027
+ parts = githubRepoParts(normalizeGithubRepo(configuredRepo, config.repos.org));
8028
+ }
8029
+ catch {
8030
+ continue;
8031
+ }
8032
+ }
8013
8033
  if (!parts)
8014
8034
  continue;
8015
- roots.add(`/github/repos/${parts.owner}__${parts.repo}/issues/by-id`);
8035
+ repos.set(`${parts.owner.toLowerCase()}/${parts.repo.toLowerCase()}`, parts);
8036
+ }
8037
+ return [...repos.values()];
8038
+ };
8039
+ // A terminal `**` is intentional: relayfile's matcher treats a non-terminal
8040
+ // `**` as a single-segment wildcard. Scoping at the repository root still
8041
+ // covers every supported issue, PR, review, comment, and check path without
8042
+ // subscribing this factory to other repositories in the workspace.
8043
+ export const githubRepoSubscriptionGlobs = (config) => configuredGithubRepoParts(config).flatMap(({ owner, repo }) => [
8044
+ `${GITHUB_ISSUE_ROOT}/${owner}/${repo}/**`,
8045
+ `${GITHUB_ISSUE_ROOT}/${owner}__${repo}/**`,
8046
+ ]);
8047
+ const githubIssueScanRoots = (config) => {
8048
+ const roots = new Set();
8049
+ for (const { owner, repo } of configuredGithubRepoParts(config)) {
8050
+ roots.add(`${GITHUB_ISSUE_ROOT}/${owner}/${repo}/issues`);
8051
+ roots.add(`${GITHUB_ISSUE_ROOT}/${owner}__${repo}/issues`);
8016
8052
  }
8017
8053
  return [...roots];
8018
8054
  };
8055
+ const githubRepoPathParts = (path) => {
8056
+ const compactSegment = path.match(/^\/github\/repos\/([^/]+)\//u)?.[1];
8057
+ const separator = compactSegment?.indexOf('__') ?? -1;
8058
+ if (compactSegment && separator > 0 && separator < compactSegment.length - 2) {
8059
+ return {
8060
+ owner: compactSegment.slice(0, separator),
8061
+ repo: compactSegment.slice(separator + 2),
8062
+ };
8063
+ }
8064
+ const nested = path.match(/^\/github\/repos\/([^/]+)\/([^/]+)\//u);
8065
+ if (nested)
8066
+ return { owner: nested[1], repo: nested[2] };
8067
+ return undefined;
8068
+ };
8069
+ const isConfiguredGithubRepoPath = (path, config) => {
8070
+ const pathParts = githubRepoPathParts(path);
8071
+ if (!pathParts)
8072
+ return false;
8073
+ const pathRepo = `${pathParts.owner.toLowerCase()}/${pathParts.repo.toLowerCase()}`;
8074
+ return configuredGithubRepoParts(config).some(({ owner, repo }) => `${owner.toLowerCase()}/${repo.toLowerCase()}` === pathRepo);
8075
+ };
8019
8076
  const githubRepoParts = (repo) => {
8020
8077
  const split = repo.match(/^([^/]+)\/([^/]+)$/u);
8021
8078
  if (split) {
8022
8079
  return { owner: split[1], repo: split[2] };
8023
8080
  }
8024
- const compact = repo.match(/^([^/]+)__([^/]+)$/u);
8025
- if (compact) {
8026
- return { owner: compact[1], repo: compact[2] };
8081
+ const separator = repo.indexOf('__');
8082
+ if (separator > 0 && separator < repo.length - 2 && !repo.includes('/')) {
8083
+ return { owner: repo.slice(0, separator), repo: repo.slice(separator + 2) };
8027
8084
  }
8028
8085
  return undefined;
8029
8086
  };