@testdriverai/agent 7.9.0-canary.3 → 7.9.0-canary.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.
@@ -12,6 +12,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
12
12
  this._ably = null;
13
13
  this._sessionChannel = null;
14
14
  this._channelName = null;
15
+ this._membersChannelName = null;
15
16
  this.ps = {};
16
17
  this._execBuffers = {}; // accumulate streamed exec.output chunks per requestId
17
18
  this.heartbeat = null;
@@ -53,7 +54,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
53
54
  return this._publishCount;
54
55
  }
55
56
 
56
- async _initAbly(ablyToken, channelName) {
57
+ async _initAbly(ablyToken, channelName, membersChannelName) {
57
58
  if (this._ably) {
58
59
  try {
59
60
  this._ably.close();
@@ -62,6 +63,19 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
62
63
  }
63
64
  }
64
65
  this._channelName = channelName;
66
+
67
+ // Derive the members channel from the session channel if not supplied.
68
+ // Format: testdriver:{env}:{teamId}:{sandboxId} → testdriver:{env}:{teamId}:members
69
+ if (!membersChannelName) {
70
+ const parts = channelName.split(":");
71
+ if (parts.length >= 3) {
72
+ membersChannelName = parts.slice(0, 3).join(":") + ":members";
73
+ } else {
74
+ logger.warn("[ably] Channel name format unexpected (" + channelName + "), cannot derive members channel");
75
+ }
76
+ }
77
+ this._membersChannelName = membersChannelName;
78
+
65
79
  var self = this;
66
80
 
67
81
  this._ably = new Ably.Realtime({
@@ -103,9 +117,32 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
103
117
 
104
118
  this._sessionChannel = this._ably.channels.get(channelName);
105
119
 
106
- logger.debug(`[realtime] Channel initialized: ${channelName}`);
120
+ // Explicitly attach the session channel BEFORE entering presence on the
121
+ // members channel. Entering members-presence triggers the API's waitpoint
122
+ // completion → claim-slot task → publishes slot-approved on the session
123
+ // channel. If the session channel isn't attached yet, that message lands
124
+ // before our attachment point and historyBeforeSubscribe() won't see it.
125
+ await this._sessionChannel.attach();
126
+ logger.debug(`[realtime] Channel attached: ${channelName}`);
127
+
128
+ // Enter presence on the team members channel so the API can count
129
+ // connected SDK clients with a single direct lookup per team.
130
+ if (membersChannelName) {
131
+ try {
132
+ var membersChannel = this._ably.channels.get(membersChannelName);
133
+ await membersChannel.presence.enter({
134
+ sandboxId: this._sandboxId,
135
+ connectedAt: Date.now(),
136
+ });
137
+ logger.debug(`[realtime] Entered presence on members channel (sandbox=${this._sandboxId})`);
138
+ } catch (e) {
139
+ // Non-fatal — presence is used for concurrency counting, not critical path
140
+ logger.warn("Failed to enter presence on members channel: " + (e.message || e));
141
+ }
142
+ }
107
143
 
108
- // Enter presence on the session channel so the API can count connected SDK clients
144
+ // Enter presence on the session channel so the API's session monitor can
145
+ // detect SDK connect/disconnect events for this sandbox.
109
146
  try {
110
147
  await this._sessionChannel.presence.enter({
111
148
  sandboxId: this._sandboxId,
@@ -113,7 +150,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
113
150
  });
114
151
  logger.debug(`[realtime] Entered presence on session channel (sandbox=${this._sandboxId})`);
115
152
  } catch (e) {
116
- // Non-fatal — presence is used for concurrency counting, not critical path
153
+ // Non-fatal — presence is used for disconnect detection, not critical path
117
154
  logger.warn("Failed to enter presence on session channel: " + (e.message || e));
118
155
  }
119
156
 
@@ -510,6 +547,8 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
510
547
  timeout,
511
548
  );
512
549
 
550
+ logger.debug(`[sandbox] authenticate response: success=${reply.success} status=${reply.status || 'none'} sandboxId=${reply.sandboxId || 'none'}`);
551
+
513
552
  if (!reply.success) {
514
553
  var err = new Error(
515
554
  reply.errorMessage || "Failed to allocate sandbox",
@@ -522,7 +561,8 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
522
561
  this._teamId = reply.teamId;
523
562
 
524
563
  if (reply.ably && reply.ably.token) {
525
- await this._initAbly(reply.ably.token, reply.ably.channel);
564
+ logger.debug(`[sandbox] Initializing Ably with channel=${reply.ably.channel}, membersChannel=${reply.ably.membersChannel || '(derived)'}`);
565
+ await this._initAbly(reply.ably.token, reply.ably.channel, reply.ably.membersChannel);
526
566
  this.instanceSocketConnected = true;
527
567
 
528
568
  // Tell the runner to enable debug log forwarding if debug mode is on
@@ -547,6 +587,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
547
587
  var slotPollStart = Date.now();
548
588
  while (reply.status === 'pending') {
549
589
  logger.log('Slot claim pending — waiting for approval via Ably...');
590
+ logger.debug(`[slots] sandboxId=${this._sandboxId} channel=${this._channelName} membersChannel=${this._membersChannelName}`);
550
591
 
551
592
  var self = this;
552
593
  var slotResolved = false;
@@ -587,24 +628,32 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
587
628
  // The claim-slot task fires in response to presence enter, so the
588
629
  // decision may already be published by the time we get here.
589
630
  var slotControlSub = await self._sessionChannel.subscribe('control', onSlotControl);
631
+ logger.debug(`[slots] Subscribed to control channel for slot decision (sandboxId=${this._sandboxId})`);
590
632
 
591
633
  // Check for decisions published before this subscription was active
592
634
  if (!slotResolved && slotControlSub) {
593
635
  try {
636
+ logger.debug(`[slots] Checking history for pre-subscription slot decisions (sandboxId=${this._sandboxId})`);
637
+ logger.debug(`[slots] Checking history for pre-subscription slot decisions (sandboxId=${this._sandboxId})`);
594
638
  var histPage = await slotControlSub.historyBeforeSubscribe({ limit: 10 });
639
+ var histItemCount = 0;
595
640
  while (histPage && !slotResolved) {
596
641
  for (var hi = 0; hi < histPage.items.length; hi++) {
642
+ histItemCount++;
643
+ logger.debug(`[slots] History item: type=${histPage.items[hi].data && histPage.items[hi].data.type} (sandboxId=${this._sandboxId})`);
597
644
  onSlotControl(histPage.items[hi]);
598
645
  if (slotResolved) break;
599
646
  }
600
647
  histPage = (!slotResolved && histPage.hasNext()) ? await histPage.next() : null;
601
648
  }
649
+ logger.debug(`[slots] History check done: ${histItemCount} items checked, resolved=${slotResolved} (sandboxId=${this._sandboxId})`);
602
650
  } catch (histErr) {
603
651
  logger.warn('[slots] Failed to check history for slot decision: ' + (histErr.message || histErr));
604
652
  }
605
653
  }
606
654
 
607
655
  var slotDecision = await slotDecisionPromise;
656
+ logger.debug(`[slots] Slot decision received: approved=${slotDecision.approved} sandboxId=${this._sandboxId} elapsedMs=${Date.now() - slotPollStart}`);
608
657
 
609
658
  if (!slotDecision.approved) {
610
659
  // Slot denied — disconnect Ably and re-try the full authenticate
@@ -648,7 +697,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
648
697
  if (reply.status === 'pending' && reply.ably && reply.ably.token) {
649
698
  this._sandboxId = reply.sandboxId;
650
699
  this._teamId = reply.teamId;
651
- await this._initAbly(reply.ably.token, reply.ably.channel);
700
+ await this._initAbly(reply.ably.token, reply.ably.channel, reply.ably.membersChannel);
652
701
  this.instanceSocketConnected = true;
653
702
  }
654
703
 
package/lib/sentry.js CHANGED
@@ -14,7 +14,7 @@ const Sentry = require("@sentry/node");
14
14
  const crypto = require("crypto");
15
15
  const os = require("os");
16
16
  const { version } = require("../package.json");
17
- const { sentryEnvironment } = require("./resolve-channel");
17
+ const { active, sentryEnvironment } = require("./resolve-channel");
18
18
  const logger = require("../agent/lib/logger");
19
19
 
20
20
  // Store trace contexts per session so concurrent tests don't overwrite each other.
@@ -33,6 +33,10 @@ const isEnabled = () => {
33
33
  if (process.env.TD_TELEMETRY === "false") {
34
34
  return false;
35
35
  }
36
+ // Disable in test channel and PR previews
37
+ if (active === 'test' || process.env.IS_PULL_REQUEST === 'true') {
38
+ return false;
39
+ }
36
40
  return true;
37
41
  };
38
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testdriverai/agent",
3
- "version": "7.9.0-canary.3",
3
+ "version": "7.9.0-canary.5",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "sdk.js",
6
6
  "types": "sdk.d.ts",