@getpaseo/server 0.1.12 → 0.1.13

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.
@@ -582,14 +582,74 @@ export class Session {
582
582
  }
583
583
  }
584
584
  }
585
- matchesAgentFilter(agent, filter) {
586
- if (filter?.agentId && agent.id !== filter.agentId) {
585
+ matchesAgentFilter(options) {
586
+ const { agent, project, filter } = options;
587
+ if (filter?.labels) {
588
+ const matchesLabels = Object.entries(filter.labels).every(([key, value]) => agent.labels[key] === value);
589
+ if (!matchesLabels) {
590
+ return false;
591
+ }
592
+ }
593
+ const includeArchived = filter?.includeArchived ?? false;
594
+ if (!includeArchived && agent.archivedAt) {
587
595
  return false;
588
596
  }
589
- if (!filter?.labels) {
590
- return true;
597
+ if (filter?.statuses && filter.statuses.length > 0) {
598
+ const statuses = new Set(filter.statuses);
599
+ if (!statuses.has(agent.status)) {
600
+ return false;
601
+ }
602
+ }
603
+ if (typeof filter?.requiresAttention === "boolean") {
604
+ const requiresAttention = agent.requiresAttention ?? false;
605
+ if (requiresAttention !== filter.requiresAttention) {
606
+ return false;
607
+ }
608
+ }
609
+ if (filter?.projectKeys && filter.projectKeys.length > 0) {
610
+ const projectKeys = new Set(filter.projectKeys.filter((item) => item.trim().length > 0));
611
+ if (projectKeys.size > 0 && !projectKeys.has(project.projectKey)) {
612
+ return false;
613
+ }
614
+ }
615
+ return true;
616
+ }
617
+ getAgentUpdateTargetId(update) {
618
+ return update.kind === "remove" ? update.agentId : update.agent.id;
619
+ }
620
+ bufferOrEmitAgentUpdate(subscription, payload) {
621
+ if (subscription.isBootstrapping) {
622
+ subscription.pendingUpdatesByAgentId.set(this.getAgentUpdateTargetId(payload), payload);
623
+ return;
624
+ }
625
+ this.emit({
626
+ type: "agent_update",
627
+ payload,
628
+ });
629
+ }
630
+ flushBootstrappedAgentUpdates(options) {
631
+ const subscription = this.agentUpdatesSubscription;
632
+ if (!subscription || !subscription.isBootstrapping) {
633
+ return;
634
+ }
635
+ subscription.isBootstrapping = false;
636
+ const pending = Array.from(subscription.pendingUpdatesByAgentId.values());
637
+ subscription.pendingUpdatesByAgentId.clear();
638
+ for (const payload of pending) {
639
+ if (payload.kind === "upsert") {
640
+ const snapshotUpdatedAt = options?.snapshotUpdatedAtByAgentId?.get(payload.agent.id);
641
+ if (typeof snapshotUpdatedAt === "number") {
642
+ const updateUpdatedAt = Date.parse(payload.agent.updatedAt);
643
+ if (!Number.isNaN(updateUpdatedAt) && updateUpdatedAt <= snapshotUpdatedAt) {
644
+ continue;
645
+ }
646
+ }
647
+ }
648
+ this.emit({
649
+ type: "agent_update",
650
+ payload,
651
+ });
591
652
  }
592
- return Object.entries(filter.labels).every(([key, value]) => agent.labels[key] === value);
593
653
  }
594
654
  buildFallbackProjectCheckout(cwd) {
595
655
  return {
@@ -645,45 +705,29 @@ export class Session {
645
705
  return;
646
706
  }
647
707
  const payload = await this.buildAgentPayload(agent);
648
- const matches = this.matchesAgentFilter(payload, subscription.filter);
708
+ const project = await this.buildProjectPlacement(payload.cwd);
709
+ const matches = this.matchesAgentFilter({
710
+ agent: payload,
711
+ project,
712
+ filter: subscription.filter,
713
+ });
649
714
  if (matches) {
650
- const project = await this.buildProjectPlacement(payload.cwd);
651
- this.emit({
652
- type: "agent_update",
653
- payload: { kind: "upsert", agent: payload, project },
715
+ this.bufferOrEmitAgentUpdate(subscription, {
716
+ kind: "upsert",
717
+ agent: payload,
718
+ project,
654
719
  });
655
720
  return;
656
721
  }
657
- this.emit({
658
- type: "agent_update",
659
- payload: { kind: "remove", agentId: payload.id },
722
+ this.bufferOrEmitAgentUpdate(subscription, {
723
+ kind: "remove",
724
+ agentId: payload.id,
660
725
  });
661
726
  }
662
727
  catch (error) {
663
728
  this.sessionLogger.error({ err: error }, "Failed to emit agent update");
664
729
  }
665
730
  }
666
- async emitCurrentAgentUpdatesForSubscription() {
667
- const subscription = this.agentUpdatesSubscription;
668
- if (!subscription) {
669
- return;
670
- }
671
- try {
672
- const agents = await this.listAgentPayloads({
673
- labels: subscription.filter?.labels,
674
- });
675
- for (const agent of agents) {
676
- const project = await this.buildProjectPlacement(agent.cwd);
677
- this.emit({
678
- type: "agent_update",
679
- payload: { kind: "upsert", agent, project },
680
- });
681
- }
682
- }
683
- catch (error) {
684
- this.sessionLogger.error({ err: error }, "Failed to emit current agent updates for subscription bootstrap");
685
- }
686
- }
687
731
  /**
688
732
  * Main entry point for processing session messages
689
733
  */
@@ -705,18 +749,6 @@ export class Session {
705
749
  case "fetch_agent_request":
706
750
  await this.handleFetchAgent(msg.agentId, msg.requestId);
707
751
  break;
708
- case "subscribe_agent_updates":
709
- this.agentUpdatesSubscription = {
710
- subscriptionId: msg.subscriptionId,
711
- filter: msg.filter,
712
- };
713
- await this.emitCurrentAgentUpdatesForSubscription();
714
- break;
715
- case "unsubscribe_agent_updates":
716
- if (this.agentUpdatesSubscription?.subscriptionId === msg.subscriptionId) {
717
- this.agentUpdatesSubscription = null;
718
- }
719
- break;
720
752
  case "delete_agent_request":
721
753
  await this.handleDeleteAgentRequest(msg.agentId, msg.requestId);
722
754
  break;
@@ -1058,9 +1090,9 @@ export class Session {
1058
1090
  },
1059
1091
  });
1060
1092
  if (this.agentUpdatesSubscription) {
1061
- this.emit({
1062
- type: "agent_update",
1063
- payload: { kind: "remove", agentId },
1093
+ this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, {
1094
+ kind: "remove",
1095
+ agentId,
1064
1096
  });
1065
1097
  }
1066
1098
  }
@@ -3798,20 +3830,9 @@ export class Session {
3798
3830
  async listFetchAgentsEntries(request) {
3799
3831
  const filter = request.filter;
3800
3832
  const sort = this.normalizeFetchAgentsSort(request.sort);
3801
- const includeArchived = filter?.includeArchived ?? false;
3802
- let agents = await this.listAgentPayloads({
3833
+ const agents = await this.listAgentPayloads({
3803
3834
  labels: filter?.labels,
3804
3835
  });
3805
- if (!includeArchived) {
3806
- agents = agents.filter((agent) => !agent.archivedAt);
3807
- }
3808
- if (filter?.statuses && filter.statuses.length > 0) {
3809
- const statuses = new Set(filter.statuses);
3810
- agents = agents.filter((agent) => statuses.has(agent.status));
3811
- }
3812
- if (typeof filter?.requiresAttention === "boolean") {
3813
- agents = agents.filter((agent) => (agent.requiresAttention ?? false) === filter.requiresAttention);
3814
- }
3815
3836
  const placementByCwd = new Map();
3816
3837
  const getPlacement = (cwd) => {
3817
3838
  const existing = placementByCwd.get(cwd);
@@ -3826,10 +3847,11 @@ export class Session {
3826
3847
  agent,
3827
3848
  project: await getPlacement(agent.cwd),
3828
3849
  })));
3829
- if (filter?.projectKeys && filter.projectKeys.length > 0) {
3830
- const projectKeys = new Set(filter.projectKeys.filter((item) => item.trim().length > 0));
3831
- entries = entries.filter((entry) => projectKeys.has(entry.project.projectKey));
3832
- }
3850
+ entries = entries.filter((entry) => this.matchesAgentFilter({
3851
+ agent: entry.agent,
3852
+ project: entry.project,
3853
+ filter,
3854
+ }));
3833
3855
  entries.sort((left, right) => this.compareFetchAgentsEntries(left, right, sort));
3834
3856
  const cursorToken = request.page?.cursor;
3835
3857
  if (cursorToken) {
@@ -3852,17 +3874,47 @@ export class Session {
3852
3874
  };
3853
3875
  }
3854
3876
  async handleFetchAgents(request) {
3877
+ const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim();
3878
+ const subscriptionId = request.subscribe
3879
+ ? requestedSubscriptionId && requestedSubscriptionId.length > 0
3880
+ ? requestedSubscriptionId
3881
+ : uuidv4()
3882
+ : null;
3855
3883
  try {
3884
+ if (subscriptionId) {
3885
+ this.agentUpdatesSubscription = {
3886
+ subscriptionId,
3887
+ filter: request.filter,
3888
+ isBootstrapping: true,
3889
+ pendingUpdatesByAgentId: new Map(),
3890
+ };
3891
+ }
3856
3892
  const payload = await this.listFetchAgentsEntries(request);
3893
+ const snapshotUpdatedAtByAgentId = new Map();
3894
+ for (const entry of payload.entries) {
3895
+ const parsedUpdatedAt = Date.parse(entry.agent.updatedAt);
3896
+ if (!Number.isNaN(parsedUpdatedAt)) {
3897
+ snapshotUpdatedAtByAgentId.set(entry.agent.id, parsedUpdatedAt);
3898
+ }
3899
+ }
3857
3900
  this.emit({
3858
3901
  type: "fetch_agents_response",
3859
3902
  payload: {
3860
3903
  requestId: request.requestId,
3904
+ ...(subscriptionId ? { subscriptionId } : {}),
3861
3905
  ...payload,
3862
3906
  },
3863
3907
  });
3908
+ if (subscriptionId &&
3909
+ this.agentUpdatesSubscription?.subscriptionId === subscriptionId) {
3910
+ this.flushBootstrappedAgentUpdates({ snapshotUpdatedAtByAgentId });
3911
+ }
3864
3912
  }
3865
3913
  catch (error) {
3914
+ if (subscriptionId &&
3915
+ this.agentUpdatesSubscription?.subscriptionId === subscriptionId) {
3916
+ this.agentUpdatesSubscription = null;
3917
+ }
3866
3918
  const code = error instanceof SessionRequestError ? error.code : "fetch_agents_failed";
3867
3919
  const message = error instanceof Error ? error.message : "Failed to fetch agents";
3868
3920
  this.sessionLogger.error({ err: error }, "Failed to handle fetch_agents_request");