Skip to content

Commit f71bb4b

Browse files
committed
Merge remote-tracking branch 'origin/main' into jakubstec/domains/security-group-details-page-group-permissions4
2 parents 7f1144a + b4d3bd7 commit f71bb4b

289 files changed

Lines changed: 2702 additions & 2350 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
name: measure-telemetry-span
3+
description: Use when measuring a Sentry performance span locally with an agent-device replay flow on iOS simulator or Android emulator.
4+
argument-hint: "<span-name> [runs] [platform] [--boot]"
5+
allowed-tools: Bash(.claude/skills/measure-telemetry-span/measure.sh) Read Grep Glob
6+
---
7+
8+
# Measure Telemetry Span
9+
10+
**Pattern:** from repo root, run one command with a span name and platform → stdout is a small summary table (avg / min / max + sample ms list). The script measures whatever Git checkout is currently active: it never runs `git checkout` or otherwise switches branches. To compare this branch with `main` (or any other revision), check out each commit/branch in turn—or use two worktrees/clones—and run `measure.sh` separately, then compare the two printed summaries.
11+
12+
## Command
13+
14+
```bash
15+
.claude/skills/measure-telemetry-span/measure.sh <span-name> [runs] [platform] [--boot]
16+
```
17+
18+
| Argument | Default | Description |
19+
| -------------- | ------- | --------------------------------------------------------------------------- |
20+
| `<span-name>` || Must match `# @tag sentry-<span-name>` on a flow under `.claude/skills/agent-device/flows/tests/` (sentry-tagged QA scenarios live there; the script searches `flows/` recursively, so `flows/macros/` is also scanned for completeness). |
21+
| `[runs]` | `10` | Measured replays after **one** warmup inside the script. |
22+
| `[platform]` | `ios` | `ios` or `android` — must match the simulator/emulator you use. |
23+
| `--boot` | off | Before `open`, runs `agent-device boot --platform <platform>` so a simulator/emulator is started when nothing was connected (`adb devices` empty, etc.). |
24+
25+
To pick a **specific** Android AVD or iOS simulator, use the same global flags `agent-device` already supports (for example `--device "Pixel_7_API_34"`) on **`boot`** and on later commands — either run `agent-device boot --platform android --device "…"` yourself before `measure.sh`, or rely on `agent-device` config (`~/.agent-device/config.json`). `--boot` inside this script only passes `--platform` through to `boot`.
26+
27+
**Environment:** `APP_ID` overrides app bundle (default `com.expensify.chat.dev`). If the flow declares `# @param KEY …`, set **`AD_KEY`** to pass `-e KEY=VALUE` to replay.
28+
29+
**Output:** table + `Samples: …ms` line; stderr has progress (`Using flow:`, runs, optional reset).
30+
31+
## Before you run
32+
33+
| Must have | Notes |
34+
| --------- | ----- |
35+
| `agent-device` (global install, version per repo agent-device skill) | |
36+
| Metro on **8081** (`npm run start`) | |
37+
| Dev build on device | |
38+
| **iOS** | `agent-device react-devtools` attached so Hermes `console.debug` reaches logs. |
39+
| **Android** | Span line visible in `adb logcat` at debug once you verify manually. |
40+
41+
If you see **no Android device** (`adb devices` empty): append **`--boot`** to the measure command, or run manually first:
42+
43+
`agent-device boot --platform android` (optional `--device "<AVD name>"`). For iOS, `agent-device boot --platform ios` or `agent-device ensure-simulator --boot` when you need a created simulator instance.
44+
45+
## Contract
46+
47+
- App logs: `[Sentry][<SpanName>] Ending span (<N>ms)` via `console.debug`.
48+
- Flow file includes `# @tag sentry-<SpanName>` (same name, case-sensitive).
49+
- Optional flow headers: `@reset <path.ad>` (run by the script after warmup and each measured replay; if absent, the script relaunches the app instead so each run starts from `@pre`); `@param` keys overridable via `AD_*` (passed as `-e KEY=VALUE` to replay).
50+
- **Parsing:** stats take the **last** `RUNS` matching log lines from the capture. That matches one sample per measured replay only if each replay emits **one** such line for this span name. Extra matches (duplicate logs, nested/sub-spans with the same message pattern, noisy startup logging) can shift which samples are included—fix the app logging or tighten the grep if that happens.
51+
52+
## `@reset` and loop stability
53+
54+
`measure.sh` replays the **same** tagged flow every iteration. Treat `@reset` as “return to a known anchor,” not a second copy of the whole scenario:
55+
56+
- Prefer a **short** reset flow (tabs to Inbox, dismiss sheet, etc.). When agent-device splits **macros** vs **tests**, point `@reset` at a **macro** path so one file stays the source of truth.
57+
- If runs are flaky locally but fine for others, walk the bring-up checklist in `.claude/skills/agent-device/SKILL.md` (Metro, dev build, device boot, iOS + DevTools for `console.debug`) before blaming selectors.
58+
59+
Optional: keep a tiny markdown table in your team notes mapping `SpanName` → one-line intent + `@pre` anchor; the span name still drives which `.ad` is picked — no need to repeat long repro prose in every chat.
60+
61+
## If something fails
62+
63+
| Symptom | Action |
64+
| ------- | ------ |
65+
| `No captured runs` | iOS: DevTools. Android: log level / package. Retry after clearing log pipeline. |
66+
| `SESSION_NOT_FOUND` / empty `adb devices` | Use **`--boot`** on the measure script, or `agent-device boot --platform android|ios` (see `--device` if multiple AVDs/simulators). Then `open` again if needed. |
67+
| Fewer samples than `runs` | Span not emitted or flow flaky — `agent-device replay <flow> --debug`; fix selectors (`ad-flow-author`). |
68+
69+
For another app bundle, export `APP_ID` before the command.
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
BOOT="false"
5+
POSITIONAL=()
6+
for arg in "$@"; do
7+
if [[ "$arg" == "--boot" ]]; then
8+
BOOT="true"
9+
else
10+
POSITIONAL+=("$arg")
11+
fi
12+
done
13+
set -- "${POSITIONAL[@]}"
14+
15+
SPAN="${1:?span name required}"
16+
RUNS="${2:-10}"
17+
PLATFORM="${3:-ios}"
18+
APP_ID="${APP_ID:-com.expensify.chat.dev}"
19+
LOG_PID=""
20+
FLOW_ENV_ARGS=()
21+
RESET_FLOW=""
22+
23+
if [[ "$PLATFORM" != "ios" && "$PLATFORM" != "android" ]]; then
24+
echo "Platform must be 'ios' or 'android'." >&2
25+
exit 1
26+
fi
27+
28+
if ! [[ "$RUNS" =~ ^[0-9]+$ ]] || [[ "$RUNS" -lt 1 ]]; then
29+
echo "Runs must be a positive integer." >&2
30+
exit 1
31+
fi
32+
33+
REPO="$(git rev-parse --show-toplevel)"
34+
FLOWS_DIR="$REPO/.claude/skills/agent-device/flows"
35+
FLOW=""
36+
while IFS= read -r -d '' candidate; do
37+
if grep -q "^# @tag[[:space:]]\+sentry-${SPAN}\$" "$candidate" 2>/dev/null; then
38+
FLOW="$candidate"
39+
break
40+
fi
41+
done < <(find "$FLOWS_DIR" -name '*.ad' -type f -print0 2>/dev/null)
42+
43+
if [[ -z "$FLOW" ]]; then
44+
echo "No flow declares '@tag sentry-$SPAN'. Available:" >&2
45+
find "$FLOWS_DIR" -name '*.ad' -type f -exec grep -h '^# @tag[[:space:]]\+sentry-' {} + 2>/dev/null | sed 's/.*sentry-//' | sort -u >&2
46+
exit 1
47+
fi
48+
49+
RESET_DECL=$(grep -E '^# @reset[[:space:]]+' "$FLOW" | sed -E 's/^# @reset[[:space:]]+//' | head -1 || true)
50+
if [[ -n "$RESET_DECL" ]]; then
51+
if [[ "$RESET_DECL" = /* ]]; then
52+
RESET_FLOW="$RESET_DECL"
53+
else
54+
RESET_FLOW="$REPO/$RESET_DECL"
55+
fi
56+
57+
if [[ ! -f "$RESET_FLOW" ]]; then
58+
echo "Reset flow does not exist: $RESET_FLOW" >&2
59+
exit 1
60+
fi
61+
fi
62+
63+
TMP_DIR="$(mktemp -d)"
64+
DURATIONS_FILE="$TMP_DIR/durations.txt"
65+
66+
cleanup() {
67+
if [[ -n "$LOG_PID" ]]; then
68+
kill "$LOG_PID" 2>/dev/null || true
69+
fi
70+
rm -rf "$TMP_DIR"
71+
}
72+
trap cleanup EXIT
73+
74+
# Flow `# @param KEY description` headers: only AD_<KEY> overrides are passed via `-e KEY=VALUE`.
75+
append_flow_env_from_ad_vars() {
76+
local param_lines line key env_key
77+
param_lines=$(grep -E '^# @param[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$FLOW" || true)
78+
if [[ -z "$param_lines" ]]; then
79+
return
80+
fi
81+
82+
while IFS= read -r line; do
83+
key=$(echo "$line" | sed -E 's/^# @param[[:space:]]+([A-Za-z_][A-Za-z0-9_]*).*/\1/')
84+
env_key="AD_${key}"
85+
if [[ -n "${!env_key:-}" ]]; then
86+
FLOW_ENV_ARGS+=("-e" "$key=${!env_key}")
87+
echo "Replay param: $key from $env_key" >&2
88+
fi
89+
done <<< "$param_lines"
90+
}
91+
92+
start_log() {
93+
local out="$1"
94+
if [[ "$PLATFORM" == "ios" ]]; then
95+
xcrun simctl spawn booted log stream --level debug \
96+
--predicate "eventMessage CONTAINS \"[Sentry][$SPAN] Ending span\"" > "$out" &
97+
else
98+
adb logcat -c
99+
adb logcat '*:D' > "$out" &
100+
fi
101+
echo $!
102+
}
103+
104+
reset_if_needed() {
105+
if [[ -n "$RESET_FLOW" ]]; then
106+
echo "Resetting with: $RESET_FLOW" >&2
107+
agent-device replay "$RESET_FLOW" >&2
108+
return
109+
fi
110+
111+
# No @reset declared: relaunch the app so the next replay starts from the flow's @pre state
112+
# instead of the previous run's @post state (Codex review r3191676565).
113+
agent-device open "$APP_ID" --platform "$PLATFORM" --relaunch >&2
114+
sleep 5
115+
if [[ "$PLATFORM" == "android" ]]; then
116+
wait_until_android_ui_ready
117+
fi
118+
}
119+
120+
# After --relaunch on Android, UIAutomator often returns Snapshot: 0 nodes briefly; warmup replay then fails @pre.
121+
wait_until_android_ui_ready() {
122+
local snapshot
123+
for _ in $(seq 1 60); do
124+
snapshot="$(agent-device snapshot 2>/dev/null || true)"
125+
if [[ "$snapshot" == *'"Home"'* && "$snapshot" == *'"Inbox'* ]]; then
126+
return
127+
fi
128+
sleep 1
129+
done
130+
131+
echo "Warning: Android UI not ready after 60s (no Home/Inbox tabs); proceeding anyway." >&2
132+
}
133+
134+
measure_current_branch() {
135+
local raw="$TMP_DIR/capture.log"
136+
137+
if [[ "$BOOT" == "true" ]]; then
138+
echo "Booting $PLATFORM target (agent-device boot)..." >&2
139+
agent-device boot --platform "$PLATFORM" >&2
140+
fi
141+
142+
agent-device open "$APP_ID" --platform "$PLATFORM" --relaunch >&2
143+
sleep 5
144+
if [[ "$PLATFORM" == "android" ]]; then
145+
wait_until_android_ui_ready
146+
fi
147+
148+
LOG_PID=$(start_log "$raw")
149+
agent-device replay "$FLOW" ${FLOW_ENV_ARGS[@]+"${FLOW_ENV_ARGS[@]}"} >&2 # warmup
150+
reset_if_needed
151+
sleep 1
152+
153+
for i in $(seq 1 "$RUNS"); do
154+
echo "Run $i/$RUNS" >&2
155+
agent-device replay "$FLOW" ${FLOW_ENV_ARGS[@]+"${FLOW_ENV_ARGS[@]}"} >&2
156+
reset_if_needed
157+
sleep 1
158+
done
159+
160+
kill "$LOG_PID" 2>/dev/null || true
161+
LOG_PID=""
162+
163+
# Last N numeric durations: assumes one "[Sentry][<span>] Ending span (Nms)" line per measured replay (see SKILL.md Contract).
164+
grep "\\[Sentry\\]\\[$SPAN\\] Ending span" "$raw" | grep -oE "Ending span \(([0-9]+)ms\)" | grep -oE '[0-9]+' | tail -n "$RUNS" || true
165+
}
166+
167+
echo "Using flow: $FLOW" >&2
168+
if [[ -n "$RESET_FLOW" ]]; then
169+
echo "Using reset flow: $RESET_FLOW" >&2
170+
fi
171+
append_flow_env_from_ad_vars
172+
measure_current_branch > "$DURATIONS_FILE"
173+
174+
awk '
175+
{ b[++bn]=$1; bs+=$1; if(!bmin||$1<bmin)bmin=$1; if($1>bmax)bmax=$1 }
176+
END {
177+
if (!bn) { print "No captured runs."; exit 1 }
178+
ba=bs/bn
179+
printf "| Metric | value |\n"
180+
printf "|--------|-------|\n"
181+
printf "| Runs | %5d |\n", bn
182+
printf "| Avg | %4.0fms |\n", ba
183+
printf "| Min | %4dms |\n", bmin
184+
printf "| Max | %4dms |\n", bmax
185+
printf "\nSamples: "
186+
for (i=1; i<=bn; i++) printf "%sms%s", b[i], (i<bn ? ", " : "\n")
187+
}' "$DURATIONS_FILE"

.github/actions/javascript/proposalPoliceComment/index.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11696,7 +11696,6 @@ async function run() {
1169611696
const duplicateCheckPrompt = proposalPolice_1.default.getPromptForNewProposalDuplicateCheck(previousProposal.body, newProposalBody);
1169711697
const duplicateCheckResponse = await openAI.promptAssistant(assistantID, duplicateCheckPrompt);
1169811698
let similarityPercentage = 0;
11699-
// eslint-disable-next-line @typescript-eslint/no-deprecated -- TODO: refactor `parseAssistantResponse` to use `promptResponses` instead
1170011699
const parsedDuplicateCheckResponse = openAI.parseAssistantResponse(duplicateCheckResponse);
1170111700
core.startGroup('Parsed Duplicate Check Response');
1170211701
console.log('parsedDuplicateCheckResponse: ', parsedDuplicateCheckResponse);
@@ -11734,7 +11733,6 @@ async function run() {
1173411733
? proposalPolice_1.default.getPromptForNewProposalTemplateCheck(payload.comment?.body)
1173511734
: proposalPolice_1.default.getPromptForEditedProposal(payload.changes.body?.from, payload.comment?.body);
1173611735
const assistantResponse = await openAI.promptAssistant(assistantID, prompt);
11737-
// eslint-disable-next-line @typescript-eslint/no-deprecated -- TODO: refactor `parseAssistantResponse` to use `promptResponses` instead
1173811736
const parsedAssistantResponse = openAI.parseAssistantResponse(assistantResponse);
1173911737
core.startGroup('Parsed Assistant Response');
1174011738
console.log('parsedAssistantResponse: ', parsedAssistantResponse);
@@ -12554,23 +12552,19 @@ class OpenAIUtils {
1255412552
*/
1255512553
async promptAssistant(assistantID, userMessage) {
1255612554
// 1. Create a thread
12557-
const thread = await (0, retryWithBackoff_1.default)(() =>
12558-
// eslint-disable-next-line @typescript-eslint/no-deprecated
12559-
this.client.beta.threads.create({
12555+
const thread = await (0, retryWithBackoff_1.default)(() => this.client.beta.threads.create({
1256012556
messages: [{ role: OpenAIUtils.USER, content: userMessage }],
1256112557
}), { isRetryable: (err) => OpenAIUtils.isRetryableError(err) });
1256212558
// 2. Create a run on the thread
12563-
let run = await (0, retryWithBackoff_1.default)(() =>
12564-
// eslint-disable-next-line @typescript-eslint/no-deprecated
12565-
this.client.beta.threads.runs.create(thread.id, {
12559+
let run = await (0, retryWithBackoff_1.default)(() => this.client.beta.threads.runs.create(thread.id, {
1256612560
// eslint-disable-next-line @typescript-eslint/naming-convention
1256712561
assistant_id: assistantID,
1256812562
}), { isRetryable: (err) => OpenAIUtils.isRetryableError(err) });
1256912563
// 3. Poll for completion
1257012564
let response = '';
1257112565
let count = 0;
1257212566
while (!response && count < OpenAIUtils.MAX_POLL_COUNT) {
12573-
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-deprecated
12567+
// eslint-disable-next-line @typescript-eslint/naming-convention
1257412568
run = await this.client.beta.threads.runs.retrieve(run.id, { thread_id: thread.id });
1257512569
if (run.status !== OpenAIUtils.OPENAI_RUN_COMPLETED) {
1257612570
count++;
@@ -12579,7 +12573,6 @@ class OpenAIUtils {
1257912573
});
1258012574
continue;
1258112575
}
12582-
// eslint-disable-next-line @typescript-eslint/no-deprecated
1258312576
for await (const message of this.client.beta.threads.messages.list(thread.id)) {
1258412577
if (message.role !== OpenAIUtils.ASSISTANT) {
1258512578
continue;

.github/actions/javascript/proposalPoliceComment/proposalPoliceComment.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ async function run() {
147147
const duplicateCheckPrompt = PROPOSAL_POLICE_TEMPLATES.getPromptForNewProposalDuplicateCheck(previousProposal.body, newProposalBody);
148148
const duplicateCheckResponse = await openAI.promptAssistant(assistantID, duplicateCheckPrompt);
149149
let similarityPercentage = 0;
150-
// eslint-disable-next-line @typescript-eslint/no-deprecated -- TODO: refactor `parseAssistantResponse` to use `promptResponses` instead
151150
const parsedDuplicateCheckResponse = openAI.parseAssistantResponse<DuplicateProposalResponse>(duplicateCheckResponse);
152151
core.startGroup('Parsed Duplicate Check Response');
153152
console.log('parsedDuplicateCheckResponse: ', parsedDuplicateCheckResponse);
@@ -188,7 +187,6 @@ async function run() {
188187
: PROPOSAL_POLICE_TEMPLATES.getPromptForEditedProposal(payload.changes.body?.from, payload.comment?.body);
189188

190189
const assistantResponse = await openAI.promptAssistant(assistantID, prompt);
191-
// eslint-disable-next-line @typescript-eslint/no-deprecated -- TODO: refactor `parseAssistantResponse` to use `promptResponses` instead
192190
const parsedAssistantResponse = openAI.parseAssistantResponse<AssistantResponse>(assistantResponse);
193191
core.startGroup('Parsed Assistant Response');
194192
console.log('parsedAssistantResponse: ', parsedAssistantResponse);

Mobile-Expensify

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ android {
111111
minSdkVersion rootProject.ext.minSdkVersion
112112
targetSdkVersion rootProject.ext.targetSdkVersion
113113
multiDexEnabled rootProject.ext.multiDexEnabled
114-
versionCode 1009036703
115-
versionName "9.3.67-3"
114+
versionCode 1009036711
115+
versionName "9.3.67-11"
116116
// Supported language variants must be declared here to avoid from being removed during the compilation.
117117
// This also helps us to not include unnecessary language variants in the APK.
118118
resConfigs "en", "es"

0 commit comments

Comments
 (0)