@expo/build-tools 21.3.0 → 21.5.0

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.
Files changed (37) hide show
  1. package/dist/android/gradleProfile.js +14 -2
  2. package/dist/builders/custom.js +20 -12
  3. package/dist/common/git.d.ts +4 -0
  4. package/dist/common/git.js +35 -2
  5. package/dist/common/jobHooks.js +10 -0
  6. package/dist/generic.js +15 -6
  7. package/dist/index.d.ts +2 -1
  8. package/dist/index.js +3 -1
  9. package/dist/logging/HttpLogStream.d.ts +28 -0
  10. package/dist/logging/HttpLogStream.js +137 -0
  11. package/dist/steps/compositeFunctions.d.ts +11 -0
  12. package/dist/steps/compositeFunctions.js +62 -0
  13. package/dist/steps/easFunctions.js +4 -0
  14. package/dist/steps/functions/checkout.js +26 -1
  15. package/dist/steps/functions/collectServeSimMetrics.d.ts +3 -0
  16. package/dist/steps/functions/collectServeSimMetrics.js +41 -0
  17. package/dist/steps/functions/startAgentDeviceRemoteSession.js +49 -41
  18. package/dist/steps/functions/startArgentRemoteSession.d.ts +8 -1
  19. package/dist/steps/functions/startArgentRemoteSession.js +52 -4
  20. package/dist/steps/functions/startServeSimMetrics.d.ts +2 -0
  21. package/dist/steps/functions/startServeSimMetrics.js +17 -0
  22. package/dist/steps/functions/startServeSimRemoteSession.js +19 -14
  23. package/dist/steps/utils/agentDeviceArtifacts.js +1 -1
  24. package/dist/steps/utils/agentDeviceEvents.d.ts +11 -0
  25. package/dist/steps/utils/agentDeviceEvents.js +116 -0
  26. package/dist/steps/utils/argentArtifacts.js +1 -1
  27. package/dist/steps/utils/argentEvents.d.ts +12 -0
  28. package/dist/steps/utils/argentEvents.js +131 -0
  29. package/dist/steps/utils/deviceRunSessionEvents.d.ts +43 -2
  30. package/dist/steps/utils/deviceRunSessionEvents.js +95 -115
  31. package/dist/steps/utils/remoteDeviceRunSession.d.ts +43 -5
  32. package/dist/steps/utils/remoteDeviceRunSession.js +249 -37
  33. package/dist/steps/utils/serveSimMetricsArtifacts.d.ts +9 -0
  34. package/dist/steps/utils/serveSimMetricsArtifacts.js +49 -0
  35. package/dist/steps/utils/serveSimMetricsRecorder.d.ts +30 -0
  36. package/dist/steps/utils/serveSimMetricsRecorder.js +232 -0
  37. package/package.json +4 -4
@@ -13,7 +13,7 @@ const node_os_1 = __importDefault(require("node:os"));
13
13
  const node_path_1 = __importDefault(require("node:path"));
14
14
  const sentry_1 = require("../../sentry");
15
15
  const agentDeviceArtifacts_1 = require("../utils/agentDeviceArtifacts");
16
- const deviceRunSessionEvents_1 = require("../utils/deviceRunSessionEvents");
16
+ const agentDeviceEvents_1 = require("../utils/agentDeviceEvents");
17
17
  const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
18
18
  const AGENT_DEVICE_PACKAGE_NAME = 'agent-device';
19
19
  const AGENT_DEVICE_REPO_URL = 'https://github.com/callstack/agent-device.git';
@@ -59,50 +59,51 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
59
59
  daemonProcess,
60
60
  });
61
61
  logger.info(`Daemon is listening on port ${daemonPort}; loaded auth token.`);
62
- const agentDeviceRemoteSessionUrl = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
62
+ const agentDeviceTunnel = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
63
63
  port: daemonPort,
64
64
  subdomainPrefix: 'agent-device',
65
65
  baseDomain: ngrokTunnelDomain,
66
66
  authtoken: ngrokAuthtoken,
67
67
  logger,
68
68
  });
69
+ const agentDeviceRemoteSessionUrl = agentDeviceTunnel.url;
69
70
  logger.info(`Tunnel is ready at ${agentDeviceRemoteSessionUrl}.`);
70
- // serve-sim is iOS-only — only launch it (and report a webPreviewUrl)
71
- // on Darwin. Android sessions go without a preview URL.
72
- let webPreviewUrl;
73
- if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
74
- const { previewUrl } = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
75
- baseDomain: ngrokTunnelDomain,
76
- env,
71
+ let serveSim;
72
+ let eventCollection;
73
+ try {
74
+ // serve-sim is iOS-only — only launch it (and report a webPreviewUrl)
75
+ // on Darwin. Android sessions go without a preview URL.
76
+ if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
77
+ serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
78
+ baseDomain: ngrokTunnelDomain,
79
+ env,
80
+ logger,
81
+ timeoutMs: STARTUP_TIMEOUT_MS,
82
+ });
83
+ logger.info(`Web preview URL: ${serveSim.previewUrl}`);
84
+ }
85
+ await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
86
+ ctx,
87
+ deviceRunSessionId,
88
+ remoteConfig: {
89
+ agentDeviceRemoteSessionUrl,
90
+ agentDeviceRemoteSessionToken: daemonToken,
91
+ ...(serveSim ? { webPreviewUrl: serveSim.previewUrl } : {}),
92
+ },
93
+ logger,
94
+ });
95
+ void (0, agentDeviceArtifacts_1.pollAgentDeviceArtifactsForUploadAsync)(ctx, {
96
+ deviceRunSessionId,
97
+ daemonUrl: `http://127.0.0.1:${daemonPort}`,
98
+ daemonToken,
99
+ logger,
100
+ });
101
+ eventCollection = await (0, agentDeviceEvents_1.startAgentDeviceEventCollectionAsync)({
102
+ ctx,
103
+ deviceRunSessionId,
104
+ stateDir: AGENT_DEVICE_STATE_DIR,
77
105
  logger,
78
- timeoutMs: STARTUP_TIMEOUT_MS,
79
106
  });
80
- webPreviewUrl = previewUrl;
81
- logger.info(`Web preview URL: ${webPreviewUrl}`);
82
- }
83
- await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
84
- ctx,
85
- deviceRunSessionId,
86
- remoteConfig: {
87
- agentDeviceRemoteSessionUrl,
88
- agentDeviceRemoteSessionToken: daemonToken,
89
- ...(webPreviewUrl ? { webPreviewUrl } : {}),
90
- },
91
- logger,
92
- });
93
- void (0, agentDeviceArtifacts_1.pollAgentDeviceArtifactsForUploadAsync)(ctx, {
94
- deviceRunSessionId,
95
- daemonUrl: `http://127.0.0.1:${daemonPort}`,
96
- daemonToken,
97
- logger,
98
- });
99
- const eventCollection = await (0, deviceRunSessionEvents_1.startAgentDeviceEventCollectionAsync)({
100
- ctx,
101
- deviceRunSessionId,
102
- stateDir: AGENT_DEVICE_STATE_DIR,
103
- logger,
104
- });
105
- try {
106
107
  await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
107
108
  ctx,
108
109
  deviceRunSessionId,
@@ -111,11 +112,18 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
111
112
  });
112
113
  }
113
114
  finally {
114
- await stopAgentDeviceEventCollectionSafelyAsync({
115
- eventCollection,
116
- deviceRunSessionId,
117
- logger,
118
- });
115
+ if (serveSim) {
116
+ await serveSim.stopAsync();
117
+ }
118
+ await agentDeviceTunnel.stopAsync();
119
+ if (eventCollection) {
120
+ await stopAgentDeviceEventCollectionSafelyAsync({
121
+ eventCollection,
122
+ deviceRunSessionId,
123
+ logger,
124
+ });
125
+ }
126
+ await daemonProcess.stopAsync();
119
127
  }
120
128
  },
121
129
  });
@@ -2,7 +2,7 @@ import { type bunyan } from '@expo/logger';
2
2
  import { BuildFunction } from '@expo/steps';
3
3
  import { z } from 'zod';
4
4
  import { CustomBuildContext } from '../../customBuildContext';
5
- export declare const MIN_ARGENT_REMOTE_SESSION_VERSION = "0.12.0";
5
+ export declare const MIN_ARGENT_REMOTE_SESSION_VERSION = "0.16.0";
6
6
  declare const ArgentToolServerStateSchema: z.ZodObject<{
7
7
  port: z.ZodNumber;
8
8
  pid: z.ZodNumber;
@@ -10,6 +10,13 @@ declare const ArgentToolServerStateSchema: z.ZodObject<{
10
10
  }, z.core.$strip>;
11
11
  type ArgentToolServerState = z.infer<typeof ArgentToolServerStateSchema>;
12
12
  export declare function createStartArgentRemoteSessionBuildFunction(ctx: CustomBuildContext): BuildFunction;
13
+ export declare function stopArgentEventCollectionSafelyAsync({ eventCollection, deviceRunSessionId, logger, }: {
14
+ eventCollection: {
15
+ stopAsync: () => Promise<void>;
16
+ };
17
+ deviceRunSessionId: string;
18
+ logger: bunyan;
19
+ }): Promise<void>;
13
20
  export declare function warnIfArgentPackageVersionCannotBeVerified({ packageVersion, logger, }: {
14
21
  packageVersion: string | undefined;
15
22
  logger: bunyan;
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.MIN_ARGENT_REMOTE_SESSION_VERSION = void 0;
7
7
  exports.createStartArgentRemoteSessionBuildFunction = createStartArgentRemoteSessionBuildFunction;
8
+ exports.stopArgentEventCollectionSafelyAsync = stopArgentEventCollectionSafelyAsync;
8
9
  exports.warnIfArgentPackageVersionCannotBeVerified = warnIfArgentPackageVersionCannotBeVerified;
9
10
  exports.waitForArgentToolServerStateAsync = waitForArgentToolServerStateAsync;
10
11
  const eas_build_job_1 = require("@expo/eas-build-job");
@@ -19,11 +20,20 @@ const sentry_1 = require("../../sentry");
19
20
  const processes_1 = require("../../utils/processes");
20
21
  const retry_1 = require("../../utils/retry");
21
22
  const argentArtifacts_1 = require("../utils/argentArtifacts");
23
+ const argentEvents_1 = require("../utils/argentEvents");
22
24
  const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
23
25
  const ARGENT_PACKAGE_NAME = '@swmansion/argent';
24
- exports.MIN_ARGENT_REMOTE_SESSION_VERSION = '0.12.0';
26
+ // 0.16.0 is the first version that exposes the tool-server event log flag; keeping the floor
27
+ // here lets us enable it (and the artifacts list endpoint) unconditionally below.
28
+ exports.MIN_ARGENT_REMOTE_SESSION_VERSION = '0.16.0';
25
29
  const ARGENT_ARTIFACTS_LIST_ENDPOINT_FLAG = 'artifacts-list-endpoint';
30
+ // Tells the tool-server to write its structured event log so we can collect session events.
31
+ const ARGENT_EVENT_LOG_FLAG = 'tool-server-event-log';
26
32
  const ARGENT_STATE_DIR = node_path_1.default.join(node_os_1.default.homedir(), '.argent');
33
+ // Pin the event log path explicitly and hand the same value to the tool-server (via
34
+ // ARGENT_EVENT_LOG) and the collector, so an ambient ARGENT_EVENT_LOG or a future change to
35
+ // Argent's default can never make the two disagree.
36
+ const ARGENT_EVENT_LOG_PATH = node_path_1.default.join(ARGENT_STATE_DIR, argentEvents_1.ARGENT_EVENT_LOG_FILENAME);
27
37
  const STARTUP_TIMEOUT_MS = 60_000;
28
38
  const ArgentToolServerStateSchema = zod_1.z.object({
29
39
  port: zod_1.z.number(),
@@ -59,8 +69,15 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
59
69
  if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
60
70
  await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
61
71
  }
72
+ // Argent shells out to ffmpeg to record the screen. Installing it pulls a
73
+ // large Homebrew dependency tree, so do not block session readiness on it:
74
+ // the session comes up at its usual speed and only a recording started in
75
+ // the first moments misses ffmpeg. Never rejects, so `void` is safe.
76
+ void (0, remoteDeviceRunSession_1.ensureFfmpegInstalledAsync)({ runtimePlatform, env, logger });
62
77
  logger.info('Enabling the Argent artifacts list endpoint flag.');
63
78
  await (0, turtle_spawn_1.default)('bunx', [`${ARGENT_PACKAGE_NAME}@${versionSpec}`, 'enable', ARGENT_ARTIFACTS_LIST_ENDPOINT_FLAG], { env, logger });
79
+ logger.info('Enabling the Argent tool-server event log flag.');
80
+ await (0, turtle_spawn_1.default)('bunx', [`${ARGENT_PACKAGE_NAME}@${versionSpec}`, 'enable', ARGENT_EVENT_LOG_FLAG], { env, logger });
64
81
  logger.info(`Launching ${ARGENT_PACKAGE_NAME}@${versionSpec} tool-server via bunx.`);
65
82
  // Keep Argent itself in foreground mode under the detached bunx process. This preserves
66
83
  // the bunx -> Argent CLI -> tool-server ancestry used to identify the matching state file.
@@ -76,7 +93,7 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
76
93
  '0',
77
94
  '--force',
78
95
  ],
79
- env,
96
+ env: { ...env, ARGENT_EVENT_LOG: ARGENT_EVENT_LOG_PATH },
80
97
  });
81
98
  if (argentServer.pid === undefined) {
82
99
  throw new eas_build_job_1.SystemError('Failed to start Argent: could not determine the PID of the launched process.');
@@ -109,8 +126,16 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
109
126
  logger,
110
127
  signal: artifactPollSignal,
111
128
  });
129
+ const eventCollection = await (0, argentEvents_1.startArgentEventCollectionAsync)({
130
+ ctx,
131
+ deviceRunSessionId,
132
+ eventLogPath: ARGENT_EVENT_LOG_PATH,
133
+ logger,
134
+ });
135
+ let toolsTunnel;
136
+ let serveSim;
112
137
  try {
113
- const publicToolsUrl = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
138
+ toolsTunnel = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
114
139
  port: toolServerPort,
115
140
  subdomainPrefix: 'argent',
116
141
  baseDomain: ngrokTunnelDomain,
@@ -118,11 +143,12 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
118
143
  rewriteHostHeader: true,
119
144
  logger,
120
145
  });
146
+ const publicToolsUrl = toolsTunnel.url;
121
147
  logger.info(`Tunnel is ready at ${publicToolsUrl}.`);
122
148
  // serve-sim is iOS-only — Android sessions go without a preview URL.
123
149
  let webPreviewUrl;
124
150
  if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
125
- const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
151
+ serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
126
152
  baseDomain: ngrokTunnelDomain,
127
153
  env,
128
154
  logger,
@@ -149,6 +175,13 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
149
175
  });
150
176
  }
151
177
  finally {
178
+ if (serveSim) {
179
+ await serveSim.stopAsync();
180
+ }
181
+ if (toolsTunnel) {
182
+ await toolsTunnel.stopAsync();
183
+ }
184
+ await stopArgentEventCollectionSafelyAsync({ eventCollection, deviceRunSessionId, logger });
152
185
  artifactPollAbortController.abort();
153
186
  try {
154
187
  await artifactPollingPromise;
@@ -158,10 +191,25 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
158
191
  sentry_1.Sentry.capture('Could not finish Argent remote session artifact polling', error);
159
192
  logger.warn({ err: error }, 'Could not finish Argent remote session artifact polling.');
160
193
  }
194
+ await argentServer.stopAsync();
161
195
  }
162
196
  },
163
197
  });
164
198
  }
199
+ async function stopArgentEventCollectionSafelyAsync({ eventCollection, deviceRunSessionId, logger, }) {
200
+ try {
201
+ await eventCollection.stopAsync();
202
+ }
203
+ catch (err) {
204
+ const error = err instanceof Error ? err : new Error(String(err));
205
+ sentry_1.Sentry.capture('Could not finish argent session event collection', error, {
206
+ level: 'warning',
207
+ tags: { phase: 'argent-event-collection', operation: 'stop' },
208
+ extras: { deviceRunSessionId },
209
+ });
210
+ logger.warn({ err: error }, 'Could not finish argent session event collection.');
211
+ }
212
+ }
165
213
  function warnIfArgentPackageVersionCannotBeVerified({ packageVersion, logger, }) {
166
214
  if (!packageVersion || packageVersion === 'latest') {
167
215
  return;
@@ -0,0 +1,2 @@
1
+ import { BuildFunction } from '@expo/steps';
2
+ export declare function createStartServeSimMetricsBuildFunction(): BuildFunction;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createStartServeSimMetricsBuildFunction = createStartServeSimMetricsBuildFunction;
4
+ const steps_1 = require("@expo/steps");
5
+ const serveSimMetricsRecorder_1 = require("../utils/serveSimMetricsRecorder");
6
+ function createStartServeSimMetricsBuildFunction() {
7
+ return new steps_1.BuildFunction({
8
+ namespace: 'eas',
9
+ id: 'start_serve_sim_metrics',
10
+ name: 'Start serve-sim metrics',
11
+ __metricsId: 'eas/start_serve_sim_metrics',
12
+ supportedRuntimePlatforms: [steps_1.BuildRuntimePlatform.DARWIN],
13
+ fn: async ({ logger }) => {
14
+ await serveSimMetricsRecorder_1.ServeSimMetricsRecorder.startAsync({ logger });
15
+ },
16
+ });
17
+ }
@@ -16,25 +16,30 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
16
16
  const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
17
17
  logger.info('Starting serve-sim remote session.');
18
18
  await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
19
- const { previewUrl } = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
19
+ const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
20
20
  baseDomain: ngrokTunnelDomain,
21
21
  env,
22
22
  logger,
23
23
  timeoutMs: STARTUP_TIMEOUT_MS,
24
24
  });
25
- logger.info(`Preview URL: ${previewUrl}`);
26
- await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
27
- ctx,
28
- deviceRunSessionId,
29
- remoteConfig: { previewUrl },
30
- logger,
31
- });
32
- await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
33
- ctx,
34
- deviceRunSessionId,
35
- logger,
36
- signal,
37
- });
25
+ logger.info(`Preview URL: ${serveSim.previewUrl}`);
26
+ try {
27
+ await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
28
+ ctx,
29
+ deviceRunSessionId,
30
+ remoteConfig: { previewUrl: serveSim.previewUrl },
31
+ logger,
32
+ });
33
+ await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
34
+ ctx,
35
+ deviceRunSessionId,
36
+ logger,
37
+ signal,
38
+ });
39
+ }
40
+ finally {
41
+ await serveSim.stopAsync();
42
+ }
38
43
  },
39
44
  });
40
45
  }
@@ -68,7 +68,7 @@ async function pollAgentDeviceArtifactsForUploadAsync(ctx, { deviceRunSessionId,
68
68
  }
69
69
  const error = err instanceof Error ? err : new Error(String(err));
70
70
  listArtifactsErrorCount += 1;
71
- if (listArtifactsErrorCount === 1 || listArtifactsErrorCount % 5 === 0) {
71
+ if (listArtifactsErrorCount % 5 === 0) {
72
72
  sentry_1.Sentry.capture('Could not list agent-device remote session artifacts', error);
73
73
  logger.warn({ err: error, failedArtifactListCount: listArtifactsErrorCount }, 'Could not list agent-device remote session artifacts.');
74
74
  }
@@ -0,0 +1,11 @@
1
+ import { type bunyan } from '@expo/logger';
2
+ import { type CustomBuildContext } from '../../customBuildContext';
3
+ export declare function startAgentDeviceEventCollectionAsync({ ctx, deviceRunSessionId, stateDir, logger, pollIntervalMs, }: {
4
+ ctx: CustomBuildContext;
5
+ deviceRunSessionId: string;
6
+ stateDir: string;
7
+ logger: bunyan;
8
+ pollIntervalMs?: number;
9
+ }): Promise<{
10
+ stopAsync: () => Promise<void>;
11
+ }>;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startAgentDeviceEventCollectionAsync = startAgentDeviceEventCollectionAsync;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const zod_1 = require("zod");
10
+ const deviceRunSessionEvents_1 = require("./deviceRunSessionEvents");
11
+ const AGENT_DEVICE_PRODUCER = 'agent-device';
12
+ const AGENT_DEVICE_EVENT_KIND_TO_TYPE = {
13
+ 'request.started': 'operation.started',
14
+ 'request.finished': 'operation.completed',
15
+ 'action.recorded': 'interaction.recorded',
16
+ };
17
+ const AgentDeviceEventSchema = zod_1.z
18
+ .object({
19
+ version: zod_1.z.number(),
20
+ ts: zod_1.z.string(),
21
+ session: zod_1.z.string(),
22
+ kind: zod_1.z.string(),
23
+ requestId: zod_1.z.string().optional().catch(undefined),
24
+ command: zod_1.z.string().optional().catch(undefined),
25
+ status: zod_1.z.string().optional().catch(undefined),
26
+ summary: zod_1.z.string().optional().catch(undefined),
27
+ details: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional().catch(undefined),
28
+ })
29
+ .passthrough();
30
+ async function startAgentDeviceEventCollectionAsync({ ctx, deviceRunSessionId, stateDir, logger, pollIntervalMs, }) {
31
+ return (0, deviceRunSessionEvents_1.startDeviceRunSessionEventCollectionAsync)({
32
+ ctx,
33
+ deviceRunSessionId,
34
+ logger,
35
+ pollIntervalMs,
36
+ source: {
37
+ producer: AGENT_DEVICE_PRODUCER,
38
+ findEventFilesAsync: () => findAgentDeviceEventFilesAsync(stateDir),
39
+ sourceKeyForFile: eventFile => node_path_1.default.basename(node_path_1.default.dirname(eventFile)),
40
+ parseLine: ({ line, sourceKey, sequenceNumber, deviceRunSessionId }) => {
41
+ const { event, failure } = parseAgentDeviceEvent(line);
42
+ if (!event) {
43
+ return { failure };
44
+ }
45
+ return {
46
+ event: normalizeAgentDeviceEvent({
47
+ event,
48
+ sequenceNumber,
49
+ sourceSessionDirectory: sourceKey,
50
+ deviceRunSessionId,
51
+ }),
52
+ };
53
+ },
54
+ },
55
+ });
56
+ }
57
+ async function findAgentDeviceEventFilesAsync(stateDir) {
58
+ const sessionsDir = node_path_1.default.join(stateDir, 'sessions');
59
+ let entries;
60
+ try {
61
+ entries = await node_fs_1.default.promises.readdir(sessionsDir, { withFileTypes: true });
62
+ }
63
+ catch (err) {
64
+ if (err.code === 'ENOENT') {
65
+ return [];
66
+ }
67
+ throw err;
68
+ }
69
+ return entries
70
+ .filter(entry => entry.isDirectory())
71
+ .map(entry => node_path_1.default.join(sessionsDir, entry.name, 'events.ndjson'));
72
+ }
73
+ function parseAgentDeviceEvent(line) {
74
+ if (!line.trim()) {
75
+ return {};
76
+ }
77
+ try {
78
+ const result = AgentDeviceEventSchema.safeParse(JSON.parse(line));
79
+ return result.success ? { event: result.data } : { failure: 'invalid-event' };
80
+ }
81
+ catch {
82
+ return { failure: 'invalid-json' };
83
+ }
84
+ }
85
+ function normalizeAgentDeviceEvent({ event, sequenceNumber, sourceSessionDirectory, deviceRunSessionId, }) {
86
+ const type = AGENT_DEVICE_EVENT_KIND_TO_TYPE[event.kind] ?? event.kind;
87
+ const durationMs = event.details?.durationMs;
88
+ const outcome = event.status === 'ok' ? 'success' : event.status === 'error' ? 'failure' : undefined;
89
+ const summary = event.summary ??
90
+ (event.kind === 'request.started'
91
+ ? `Started ${event.command ?? 'activity'}`
92
+ : event.kind === 'request.finished'
93
+ ? `Finished ${event.command ?? 'activity'}`
94
+ : event.kind === 'action.recorded'
95
+ ? `Recorded ${event.command ?? 'activity'}`
96
+ : `${event.kind}${event.command ? `: ${event.command}` : ''}`);
97
+ return {
98
+ v: 1,
99
+ // Consumers use eventId to deduplicate events across polls. Keep the per-file sequence
100
+ // monotonic across source-file truncations so an ID is never reused during collection.
101
+ eventId: `agent-device:${deviceRunSessionId}:${sourceSessionDirectory}:${sequenceNumber}`,
102
+ ts: event.ts,
103
+ producer: 'agent-device',
104
+ type,
105
+ ...(event.requestId ? { operationId: event.requestId } : {}),
106
+ ...(outcome ? { outcome } : {}),
107
+ ...(typeof durationMs === 'number' ? { durationMs } : {}),
108
+ summary,
109
+ data: {
110
+ ...event.details,
111
+ session: event.session,
112
+ sourceVersion: event.version,
113
+ ...(event.status ? { sourceStatus: event.status } : {}),
114
+ },
115
+ };
116
+ }
@@ -68,7 +68,7 @@ async function pollArgentArtifactsForUploadAsync(ctx, { deviceRunSessionId, tool
68
68
  }
69
69
  const error = err instanceof Error ? err : new Error(String(err));
70
70
  listArtifactsErrorCount += 1;
71
- if (listArtifactsErrorCount === 1 || listArtifactsErrorCount % 5 === 0) {
71
+ if (listArtifactsErrorCount % 5 === 0) {
72
72
  sentry_1.Sentry.capture('Could not list Argent remote session artifacts', error);
73
73
  logger.warn({ err: error, failedArtifactListCount: listArtifactsErrorCount }, 'Could not list Argent remote session artifacts.');
74
74
  }
@@ -0,0 +1,12 @@
1
+ import { type bunyan } from '@expo/logger';
2
+ import { type CustomBuildContext } from '../../customBuildContext';
3
+ export declare const ARGENT_EVENT_LOG_FILENAME = "tool-server-events.jsonl";
4
+ export declare function startArgentEventCollectionAsync({ ctx, deviceRunSessionId, eventLogPath, logger, pollIntervalMs, }: {
5
+ ctx: CustomBuildContext;
6
+ deviceRunSessionId: string;
7
+ eventLogPath: string;
8
+ logger: bunyan;
9
+ pollIntervalMs?: number;
10
+ }): Promise<{
11
+ stopAsync: () => Promise<void>;
12
+ }>;
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ARGENT_EVENT_LOG_FILENAME = void 0;
7
+ exports.startArgentEventCollectionAsync = startArgentEventCollectionAsync;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const zod_1 = require("zod");
11
+ const deviceRunSessionEvents_1 = require("./deviceRunSessionEvents");
12
+ const ARGENT_PRODUCER = 'argent';
13
+ // Argent's tool-server writes its event log to `~/.argent/tool-server-events.jsonl`
14
+ // (its default $ARGENT_EVENT_LOG path) once the `tool-server-event-log` flag is on.
15
+ exports.ARGENT_EVENT_LOG_FILENAME = 'tool-server-events.jsonl';
16
+ // Argent records are bunyan log lines tagged with a `type`. Map the tool-lifecycle
17
+ // types onto the shared operation vocabulary; anything else (service.*,
18
+ // tool_server.*, future types) passes through unchanged, mirroring agent-device.
19
+ const ARGENT_EVENT_TYPE_TO_TYPE = {
20
+ 'tool.invoked': 'operation.started',
21
+ 'tool.completed': 'operation.completed',
22
+ 'tool.failed': 'operation.completed',
23
+ };
24
+ // Bunyan bookkeeping plus the fields we promote to the top level of a
25
+ // DeviceRunSessionEvent — excluded from the free-form `data` bag so it carries
26
+ // only the record's own context (toolId, serviceId, failureSignal, ...).
27
+ const NON_DATA_KEYS = new Set([
28
+ 'name',
29
+ 'hostname',
30
+ 'pid',
31
+ 'v',
32
+ 'time',
33
+ 'msg',
34
+ 'level',
35
+ 'type',
36
+ 'toolInvocationId',
37
+ 'durationMs',
38
+ ]);
39
+ const ArgentEventSchema = zod_1.z
40
+ .object({
41
+ time: zod_1.z.string(),
42
+ type: zod_1.z.string(),
43
+ // Argent's EventLogRecord requires `msg` and every emit site sets it to the
44
+ // human-readable description of the event, so it is our summary verbatim.
45
+ msg: zod_1.z.string(),
46
+ level: zod_1.z.number().optional().catch(undefined),
47
+ toolId: zod_1.z.string().optional().catch(undefined),
48
+ toolInvocationId: zod_1.z.string().optional().catch(undefined),
49
+ durationMs: zod_1.z.number().optional().catch(undefined),
50
+ })
51
+ .passthrough();
52
+ async function startArgentEventCollectionAsync({ ctx, deviceRunSessionId, eventLogPath, logger, pollIntervalMs, }) {
53
+ return (0, deviceRunSessionEvents_1.startDeviceRunSessionEventCollectionAsync)({
54
+ ctx,
55
+ deviceRunSessionId,
56
+ logger,
57
+ pollIntervalMs,
58
+ source: {
59
+ producer: ARGENT_PRODUCER,
60
+ findEventFilesAsync: () => findArgentEventFilesAsync(eventLogPath),
61
+ sourceKeyForFile: eventFile => node_path_1.default.basename(eventFile, node_path_1.default.extname(eventFile)),
62
+ parseLine: ({ line, sourceKey, sequenceNumber, deviceRunSessionId }) => {
63
+ const { event, failure } = parseArgentEvent(line);
64
+ if (!event) {
65
+ return { failure };
66
+ }
67
+ return {
68
+ event: normalizeArgentEvent({ event, sequenceNumber, sourceKey, deviceRunSessionId }),
69
+ };
70
+ },
71
+ },
72
+ });
73
+ }
74
+ async function findArgentEventFilesAsync(eventLogPath) {
75
+ try {
76
+ await node_fs_1.default.promises.access(eventLogPath);
77
+ return [eventLogPath];
78
+ }
79
+ catch (err) {
80
+ // The tool-server has not created the file yet — expected until the first event is written.
81
+ // Surface any other error (permissions, I/O) to the engine so it reaches Sentry.
82
+ if (err.code === 'ENOENT') {
83
+ return [];
84
+ }
85
+ throw err;
86
+ }
87
+ }
88
+ function parseArgentEvent(line) {
89
+ if (!line.trim()) {
90
+ return {};
91
+ }
92
+ try {
93
+ const result = ArgentEventSchema.safeParse(JSON.parse(line));
94
+ return result.success ? { event: result.data } : { failure: 'invalid-event' };
95
+ }
96
+ catch {
97
+ return { failure: 'invalid-json' };
98
+ }
99
+ }
100
+ function normalizeArgentEvent({ event, sequenceNumber, sourceKey, deviceRunSessionId, }) {
101
+ const type = ARGENT_EVENT_TYPE_TO_TYPE[event.type] ?? event.type;
102
+ const outcome = event.type === 'tool.completed'
103
+ ? 'success'
104
+ : event.type === 'tool.failed'
105
+ ? 'failure'
106
+ : undefined;
107
+ const data = {};
108
+ for (const [key, value] of Object.entries(event)) {
109
+ if (!NON_DATA_KEYS.has(key) && value !== undefined) {
110
+ data[key] = value;
111
+ }
112
+ }
113
+ data.sourceType = event.type;
114
+ if (typeof event.level === 'number') {
115
+ data.sourceLevel = event.level;
116
+ }
117
+ return {
118
+ v: 1,
119
+ // Consumers use eventId to deduplicate events across polls. Keep the per-file sequence
120
+ // monotonic across source-file truncations so an ID is never reused during collection.
121
+ eventId: `argent:${deviceRunSessionId}:${sourceKey}:${sequenceNumber}`,
122
+ ts: event.time,
123
+ producer: 'argent',
124
+ type,
125
+ ...(event.toolInvocationId ? { operationId: event.toolInvocationId } : {}),
126
+ ...(outcome ? { outcome } : {}),
127
+ ...(typeof event.durationMs === 'number' ? { durationMs: event.durationMs } : {}),
128
+ summary: event.msg,
129
+ data,
130
+ };
131
+ }