@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.
- package/dist/android/gradleProfile.js +14 -2
- package/dist/builders/custom.js +20 -12
- package/dist/common/git.d.ts +4 -0
- package/dist/common/git.js +35 -2
- package/dist/common/jobHooks.js +10 -0
- package/dist/generic.js +15 -6
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/logging/HttpLogStream.d.ts +28 -0
- package/dist/logging/HttpLogStream.js +137 -0
- package/dist/steps/compositeFunctions.d.ts +11 -0
- package/dist/steps/compositeFunctions.js +62 -0
- package/dist/steps/easFunctions.js +4 -0
- package/dist/steps/functions/checkout.js +26 -1
- package/dist/steps/functions/collectServeSimMetrics.d.ts +3 -0
- package/dist/steps/functions/collectServeSimMetrics.js +41 -0
- package/dist/steps/functions/startAgentDeviceRemoteSession.js +49 -41
- package/dist/steps/functions/startArgentRemoteSession.d.ts +8 -1
- package/dist/steps/functions/startArgentRemoteSession.js +52 -4
- package/dist/steps/functions/startServeSimMetrics.d.ts +2 -0
- package/dist/steps/functions/startServeSimMetrics.js +17 -0
- package/dist/steps/functions/startServeSimRemoteSession.js +19 -14
- package/dist/steps/utils/agentDeviceArtifacts.js +1 -1
- package/dist/steps/utils/agentDeviceEvents.d.ts +11 -0
- package/dist/steps/utils/agentDeviceEvents.js +116 -0
- package/dist/steps/utils/argentArtifacts.js +1 -1
- package/dist/steps/utils/argentEvents.d.ts +12 -0
- package/dist/steps/utils/argentEvents.js +131 -0
- package/dist/steps/utils/deviceRunSessionEvents.d.ts +43 -2
- package/dist/steps/utils/deviceRunSessionEvents.js +95 -115
- package/dist/steps/utils/remoteDeviceRunSession.d.ts +43 -5
- package/dist/steps/utils/remoteDeviceRunSession.js +249 -37
- package/dist/steps/utils/serveSimMetricsArtifacts.d.ts +9 -0
- package/dist/steps/utils/serveSimMetricsArtifacts.js +49 -0
- package/dist/steps/utils/serveSimMetricsRecorder.d.ts +30 -0
- package/dist/steps/utils/serveSimMetricsRecorder.js +232 -0
- 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
|
|
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
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|