mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
8e1f8a9f8f
* perf: lazy load daemon handlers * perf: thin command metadata paths * refactor: isolate platform inventory loading * refactor: trim lazy loading cleanup * test: guard daemon routing metadata drift * ci: report startup timing with size
25 KiB
25 KiB
AGENTS.md
Minimal operating guide for AI coding agents in this repo.
Agent skills
Issue tracker
Issues and PRDs live in GitHub Issues for callstackincubator/agent-device. See docs/agents/issue-tracker.md.
Triage labels
Use the default Matt Pocock skills triage vocabulary: needs-triage, needs-info, ready-for-agent, ready-for-human, and wontfix. See docs/agents/triage-labels.md.
Domain docs
Single-context repo. Read CONTEXT.md for domain language and testing/architecture vocabulary, and docs/adr/ for accepted architecture decisions. See docs/agents/domain.md.
First 60 Seconds
- Classify task type:
- Info-only (triage/review/questions/docs guidance): no code edits and no test runs unless explicitly requested.
- Code change: make minimal scoped edits and run only required checks from Testing Matrix.
- State assumptions explicitly. If uncertain, ask.
- If the task touches tooling/builds/linting, read
package.jsonandtsconfig*.jsonbefore source files. - Prefer repo scripts over reconstructing command bundles by hand:
pnpm check:quick: lint + typecheckpnpm check:tooling: lint + typecheck + buildpnpm check:unit: unit + smokepnpm check: full non-integration validation
- Read at most 3 files first:
- owning handler/module
- one shared helper used by that handler
- one downstream platform file if needed
- Define verifiable success criteria before editing.
- Decide docs/skills impact up front.
Scope
- Solve issues with the smallest context read.
- Keep changes scoped to one command family or module group.
- Preserve daemon session semantics and platform behavior.
- Expand only when contracts cross module boundaries.
- Do not read both iOS and Android paths unless explicitly cross-platform.
- If requested fix expands beyond one command family/module group, stop and confirm before broadening scope.
Code Changes
- Minimum code that solves the problem. No speculative features.
- No abstractions for single-use code.
- Surgical edits only.
- Match existing style.
- Remove imports/variables YOUR changes made unused; do not clean unrelated dead code.
- Keep tests minimal: if TypeScript can enforce a contract or invalid shape, prefer a type-level check over duplicating that assertion in runtime tests.
- Keep modules small for agent context safety:
- target <= 300 LOC per implementation file when practical.
- if a file grows past 500 LOC, plan/extract focused submodules before adding new behavior.
- if a file grows past 1,000 LOC, treat it as architecture debt unless it is generated data, a fixture snapshot, or an integration test aggregation.
- long guidance/data tables should live behind focused modules instead of sharing a file with parser/runtime logic.
- prefer deep modules over mechanical splits: extract when it improves locality for a concept callers already need, not just to reduce line count.
Context Management
- Optimize for one-pass agent reads. A module that requires reading many siblings to understand one change is usually too shallow; a module that hides one concept behind a small interface is usually worth keeping.
- Start with the owning module, then one shared helper, then one downstream caller or adapter. Broaden only when the contract crosses that edge.
- Use targeted symbol searches before opening large files. For files over 500 LOC, search for the relevant type/function/section first, then read a bounded range.
- Do not add unrelated exports just to make tests easier. Test through the public interface when possible; if that is awkward, consider whether the module's interface is too shallow.
- When adding new guidance, examples, schemas, or command metadata, decide whether it belongs in the command surface, CLI grammar, CLI help, MCP projection, or daemon runtime before editing.
- Prefer updating existing domain vocabulary in
CONTEXT.mdwhen naming a new durable module concept. Do not coin parallel names in docs, tests, and code.
Routing
- Keep
src/daemon.tsas a thin router. - Keep command names and daemon routing groups centralized in
src/command-catalog.ts; do not re-create command string sets in handlers or request policy modules. - Keep command input/output contracts in the command modules:
- command surface and shared schemas:
src/commands/command-surface.ts,src/commands/command-contract.ts,src/commands/command-input.ts - typed client command execution:
src/commands/client-command-contracts.ts - command families:
src/commands/interaction-command-contracts.ts,src/commands/batch-command.ts, with other typed client contracts insrc/commands/client-command-contracts.ts - CLI positional/flag grammar:
src/commands/cli-grammar.tsandsrc/commands/cli-grammar/* - typed input to daemon request projection:
src/commands/command-projection.ts - CLI/client/runtime output projection:
src/commands/cli-output.ts,src/commands/client-output.ts,src/commands/runtime-output.ts
- command surface and shared schemas:
- Do not reintroduce CLI-shaped command adapters or schemas as a second source of truth. CLI, Node.js, and MCP should project from command contracts.
- Keep
src/daemon/request-router.tsas request orchestration: auth, diagnostics scope, request admission, locking, handler chain, and fallback dispatch. - New daemon handler-family commands must update the relevant
DAEMON_COMMAND_GROUPS.*Handlerentry and the handler module's exported*_COMMAND_HANDLERScoverage table;src/daemon/__tests__/request-handler-catalog.test.tsguards drift and overlap. - Put request policies in focused request modules:
- tenant/lease/selector/lock admission:
src/daemon/request-admission.ts - artifact/error finalization:
src/daemon/request-finalization.ts - request-scoped platform provider scoping:
src/daemon/request-platform-providers.ts - generic fallback dispatch + action recording:
src/daemon/request-generic-dispatch.ts - recording invalidation health:
src/daemon/request-recording-health.ts
- tenant/lease/selector/lock admission:
- Put command logic in handler modules:
- session/apps/appstate/open/close/replay/logs:
src/daemon/handlers/session.ts - click/fill/get/is:
src/daemon/handlers/interaction.ts - snapshot/wait/alert/settings:
src/daemon/handlers/snapshot.ts - find:
src/daemon/handlers/find.ts - record/trace:
src/daemon/handlers/record-trace.ts
- session/apps/appstate/open/close/replay/logs:
- Generic passthrough (press/scroll/type) is daemon fallback only after handlers return null.
Toolchain Snapshot
- Package manager:
pnpmonly. Do not add or restorepackage-lock.json. - Runtime baseline is Node >= 22. Prefer built-in Node APIs such as global
fetch, Web Streams, andAbortSignal.timeoutover compatibility wrappers unless the surrounding code needs a lower-level transport. - Lint/format stack is OXC:
- config:
.oxlintrc.json,.oxfmtrc.json
- config:
- TypeScript is strict enough to surface dead code early:
strict,isolatedModules,noUnusedLocals, andnoUnusedParametersare enabled. - The repo emits with
rslib, nottsc. If declaration generation fails, inspecttsconfig.lib.jsonfirst. tsconfig.lib.jsonneeds an explicitrootDir: "./src"for declaration layout.- Use the aggregate scripts in
package.jsonwhen possible; they encode the expected validation bundles better than ad hoc command lists.
Cheap Exploration
- Prefer these first-pass commands over broader reads:
rg -n "<symbol|command|flag>" src testrg --files src/daemon/handlers src/platforms/ios src/platforms/androidgit diff -- <path>for active-branch context- read
.oxlintrc.jsonbefore treating lint output as source-level bugs
- If build/type errors mention declaration generation, inspect
tsconfig.lib.jsonbefore reading platform code. - If lint failures appear after toolchain edits, check whether the rule is from
eslint/*,typescript/*,import/*, ornode/*in.oxlintrc.jsonbefore assuming source bugs.
Command Family Lookup
logs:src/daemon/handlers/session.ts->src/daemon/app-log.ts->src/daemon/handlers/__tests__/session.test.tsopen/close/replay/apps/appstate:src/daemon/handlers/session.ts->src/daemon/session-store.ts->src/daemon/handlers/__tests__/session.test.tsclick/fill/get/is:src/daemon/handlers/interaction.ts->src/daemon/selectors.ts->src/daemon/handlers/__tests__/interaction.test.tssnapshot/wait/settings/alert:src/daemon/handlers/snapshot.ts->src/daemon/snapshot-processing.ts->src/daemon/handlers/__tests__/snapshot-handler.test.tsrecord/trace:src/daemon/handlers/record-trace.ts->src/platforms/ios/runner-client.ts->src/daemon/handlers/__tests__/record-trace.test.ts
iOS Runner Seams
- Keep dependency direction clean:
runner-client.ts: command execution + retry behaviorrunner-transport.ts: connection/probing/HTTP transportrunner-contract.ts: sharedRunnerCommandtype and runner connect/error helpersrunner-session.ts: session lifecycle and request/response executionrunner-xctestrun.ts: xctestrun preparation/build/cache logic
runner-transport.tsmust not import back fromrunner-client.ts.- If changing runner connect errors, retry policy, or command typing, start in
src/platforms/ios/runner-contract.tsbefore touching client/transport files.
Adding a New CLI Flag
A new snapshot/command flag touches only the layers that need to understand it. Follow this checklist in order:
src/utils/cli-flags.ts: add toCliFlags,FLAG_DEFINITIONS, and the relevant exported flag group (e.g.SNAPSHOT_FLAGS). Add the flag toCLI_COMMAND_OVERRIDESinsrc/utils/cli-command-overrides.tsfor each command that supports it; command names/descriptions come from command contracts unless CLI help needs a specific override.src/commands/cli-grammar/*: read the CLI flag into command input when the CLI accepts it.src/commands/command-projection.tsand command-family projection helpers: write the input into the daemon request only if the flag affects daemon execution.src/commands/*-command-contracts.ts: add or update the command input schema only if the option should be available through Node.js or MCP as structured input.src/client-types.ts: update the public typed client option only when the Node.js interface exposes the option.src/client-normalizers.ts: update daemon flag normalization only when the request still needs a public-to-internal option translation.src/daemon/context.tsandsrc/core/dispatch-context.ts: add the field only when it flows into platform dispatch.- Handler/platform modules: thread the option only after the command surface, grammar, and projection prove it belongs there.
Command-only flags (like find --first) that do not flow to the platform layer usually stop at steps 1-3.
Hard Rules
- Use process helpers from
src/utils/exec.tsfor TypeScript process execution:runCmd,runCmdStreaming,runCmdSync,runCmdBackground, andrunCmdDetached. Do not import rawspawn/spawnSyncoutsidesrc/utils/exec.ts; add or extend an exec helper instead. Plain.mjspackaging fixtures that cannot import TypeScript helpers should keep child-process usage local and preferexecFile/execFileSyncover spawn. - Use daemon session flow for interactions (
openbefore interactions,closeafter). - Every manual
agent-device openmust have a matchingagent-device closebefore the agent finishes, using the same--session,--platform,--udid, and--state-dirflags. - Use
keyboard dismissfor iOS keyboard dismissal; it may tap safe native controls such asDonebut must not fall back to system back navigation. - Do not remove shared snapshot/session model behavior without full migration.
- Command/device support must come from
src/core/capabilities.ts. - Apple-family target changes must keep
src/utils/device.ts,src/core/capabilities.ts,src/core/dispatch-resolve.ts,src/platforms/ios/devices.ts, andsrc/platforms/ios/runner-xctestrun.tsin sync. - iOS simulator-set scoping is iOS-specific: do not let
iosSimulatorDeviceSethide the host macOS desktop target when--platform macosor--target desktopis requested. - If Swift runner code changes, run
pnpm build:xcuitest. - Use
inferFillTextanduniqueStringsfromsrc/daemon/action-utils.ts. - Use
evaluateIsPredicatefromsrc/daemon/is-predicates.tsfor assertion logic.
Logs Contract
- Logs backend/source of truth is
src/daemon/app-log.ts. session.tsshould orchestrate only (start/stop/path/doctor/mark), not duplicate backend logic.- Preserve external grep/tail workflow in docs/skills.
Diagnostics & Errors
- Diagnostics source of truth:
src/utils/diagnostics.tswithDiagnosticsScope,emitDiagnostic,withDiagnosticTimer,flushDiagnosticsToSessionFile
- Do not add ad-hoc stderr/file logging where diagnostics helpers apply.
- Normalize user-facing failures via
src/utils/errors.ts(normalizeError). - Failure payload contract:
code,message,hint,diagnosticId,logPath,details. - Preserve
hint,diagnosticId,logPathwhen wrapping/rethrowing errors. --debugis canonical;--verboseis backward-compatible alias.- Keep redaction centralized in diagnostics helpers.
Optional Optimizations
- Treat optional optimization calls such as cache/preflight/probe requests as best-effort unless the feature contract says they are required. If an optimization fails, times out, returns non-OK, or returns an unusable shape, prefer falling back to the existing required command path.
- Keep optimization timeouts shorter than the underlying operation timeout. A preflight should not consume the full budget for a later upload or command.
React Native Verification
- After changing runtime code exercised through
bin/agent-device.mjsor the daemon, runpnpm buildandpnpm clean:daemonbefore manual device verification so snapshots use currentdistoutput. - For Android RN/Expo/dev-client apps connected to any local Metro port,
adb reverse tcp:<port> tcp:<port>is harmless and should be run before opening the app or URL on the emulator/device.
Manual Device Session Hygiene
- Treat every manually opened
agent-devicesession as a resource that must be closed, including exploratory sessions and failed verification attempts. - For experiments, use a purpose-specific session name and, when practical, an isolated
--state-dirunder/private/tmpso stale metadata does not poison the default daemon. - Keep track of each opened session in the working notes. Before final response, close each one with the same flags used to open it.
- If
closeor a later command is blocked by stale daemon metadata, inspect running processes first withps -ax | rg "agent-device|xcodebuild test-without-building". Stop only exact stale PIDs that belong to the verification run, then runpnpm clean:daemon. - If cleanup cannot be completed, report the remaining session name, state dir, process IDs, and metadata paths as a blocker.
Selector System Rules
- Interaction commands (
click,fill,get,is) andwaitaccept selectors and@ref. - Pipeline: parse -> resolve -> act -> record selectorChain -> heal on replay.
- Keep selector parsing/matching in
src/daemon/selectors.ts. - Call
buildSelectorChainForNodeafter resolving target nodes. - New element-targeting interactions must support selector +
@ref, recordselectorChain, and hook replay healing (healReplayActioninsession.ts+ selector helpers insession-replay-heal.ts). - New selector keys remain centralized in
selectors.ts. - New
ispredicates belong inevaluateIsPredicate. - On macOS, snapshot rects are absolute in window space. Point-based runner interactions must translate through the interaction root frame; do not assume app-origin
(0,0)coordinates. - Prefer selector or
@refinteractions over raw x/y commands in tests and docs, especially on macOS where window position can vary across runs.
Shared Test Utilities
- Before writing a new test, check
src/__tests__/test-utils/for existing helpers:device-fixtures.ts: canonicalDeviceInfoconstants (ANDROID_EMULATOR,IOS_SIMULATOR,IOS_DEVICE,MACOS_DEVICE,LINUX_DEVICE, etc.)session-factories.ts:makeSession,makeIosSession,makeAndroidSession,makeMacOsSessionstore-factory.ts:makeSessionStore(creates tempSessionStoreinstances)snapshot-builders.ts:buildNodes,makeSnapshotStatemocked-binaries.ts:withMockedAdb,withMockedXcrun(stub CLI binaries for dispatch tests)
- Use
import { ... } from '<relative-path>/__tests__/test-utils/index.ts'for convenient barrel imports. - Prefer shared fixtures over inlining new
DeviceInfoorSessionStateobjects in tests. - Do not duplicate
makeSessionStore,makeSession, or device constants when a shared helper already exists.
Testing Matrix
- Docs/skills only: no tests required unless a more specific rule below applies.
- CLI help/guidance changes in
src/utils/cli-help.ts,src/utils/cli-command-overrides.ts, orsrc/utils/command-schema.ts: runpnpm exec vitest run src/utils/__tests__/args.test.ts. - SkillGym prompt/assertion changes: run
pnpm test:skillgym:case <case-id>; the script builds local CLI help first. For broad validation, usepnpm test:skillgym; append-- --tag fixture-smokeor-- --tag skill-guidancewhen validating one suite group. - Non-TS, no behavior impact: no tests unless requested.
- Keep tests behavioral; do not assert shapes or cases TypeScript already proves.
- Any TS change:
pnpm typecheckorpnpm check:quick. - Fallow CI failures: reproduce with
pnpm check:fallow --base origin/maininstead of manually estimating complexity/dead-code impact. - Test-only DI seam CI failures: the workflow enforces this; do not add optional
typeofDI params in production code. - Tooling/config change (
package.json,tsconfig*.json,.oxlintrc.json,.oxfmtrc.json):pnpm check:tooling. - Daemon handler/shared module change:
pnpm check:unit. - iOS runner/Swift change:
pnpm build:xcuitest. - Cross-platform behavior change: run
pnpm test:integration. - Any change in:
src/,test/,skills/:pnpm format.
Token Guardrails
- Do not read unrelated files once owning module is identified.
- Do not run integration tests by default.
- Do not inspect both iOS and Android codepaths unless task requires both.
- Prefer targeted
git diff -- <paths>over broad file reads during review. - Keep long help prose in
src/utils/cli-help.ts; keep flag definitions insrc/utils/cli-flags.ts; keep CLI-specific command usage/flag metadata insrc/utils/cli-command-overrides.ts. - Prefer
snapshot -i,find, and scoped selectors over repeated full snapshot dumps when exploring Apple desktop UIs. - Keep PR summaries short and scoped.
Common Mistakes
- Adding command logic to
src/daemon.tsinstead of handlers. - Adding capability checks outside
src/core/capabilities.ts. - Inlining
ispredicate logic in handlers. - Returning non-normalized user-facing errors.
- Duplicating logs backend logic in handlers instead of
src/daemon/app-log.ts. - Growing
src/daemon/handlers/session.tsorsrc/platforms/ios/apps.tsfurther without extracting Apple-family/macOS-specific helpers first. - Reintroducing an npm lockfile or assuming ESLint/Prettier still exist in this repo.
- Changing
tsconfig.lib.json/build tooling without runningpnpm check:tooling; declaration generation is stricter thantsc --noEmit.
Docs & Skills
- Versioned CLI help is the agent-facing source of truth. Put workflow guidance and help-topic prose in
src/utils/cli-help.ts, keep flag definitions insrc/utils/cli-flags.ts, keep CLI command overrides insrc/utils/cli-command-overrides.ts, and assert important copy insrc/utils/__tests__/args.test.ts. - Keep parser schema and help rendering separate:
src/utils/command-schema.tscomposes contract-derived command schemas with CLI overrides;src/utils/cli-help.tsowns help topics and usage rendering. - Skills are thin routers. Keep
skills/**/SKILL.mdfocused on when to use the skill, version gating, whichagent-device help <topic>page to read, and a short default loop. Do not duplicate full CLI manuals in skills. - For behavior/CLI surface changes, update the versioned help instructions in
src/utils/cli-help.tsor the CLI command metadata insrc/utils/cli-command-overrides.ts, then assert important help copy insrc/utils/__tests__/args.test.ts. Also updateREADME.mdand relevantwebsite/docs/**when user-facing docs need it. - For behavior/CLI surface changes and command-planning guidance changes, write or update a SkillGym case in
test/skillgym/suites/agent-device-smoke-suite.tsthat captures the expected agent command plan. - Do not update
skills/**/SKILL.mdfor command behavior or workflow guidance unless the user explicitly asks; skills must route to versioned CLI help instead of carrying behavior details. - Keep SkillGym cases behavioral and command-planning oriented. Prefer prompts that assert the user-visible contract and expected command family over brittle exact output, but forbid known bad patterns.
- Use
pnpm test:skillgym:case <case-id>for focused SkillGym validation; it runs the environment guard and builds local CLI help beforeskillgym run. - Run SkillGym broad validation with
pnpm test:skillgym; append v0.8 filters such as-- --tag fixture-smokefor focused suite groups. - Preserve current high-value workflow guidance:
- iOS Expo Go dogfood: prefer
agent-device open "Expo Go" <url> --platform ioswhen the shell is known, thensnapshot -ito confirm the project UI rather than the runner splash. keyboard dismissis the preferred iOS keyboard-dismissal path before manually pressing visible keyboard controls such asDone; it remains best-effort and can report unsupported layouts explicitly.- Empty replacement is not a supported clear-field command; do not document or test
fill <target> ""as clearing. Prefer visible clear/reset controls or report the tool gap. - Mutating commands against one session must run serially. Parallelize only read-only commands or commands on separate sessions/devices.
- iOS Expo Go dogfood: prefer
- In final summaries, state whether docs/skills were updated; if not, explain why.
When Blocked
- If blocked by network/device/auth/permissions, stop and report:
- blocker
- why it blocks completion
- exact next command/action needed to unblock
Key Files
- CLI parse + formatting:
src/bin.ts,src/cli.ts,src/utils/args.ts - CLI help + option metadata:
src/utils/cli-help.ts,src/utils/cli-flags.ts,src/utils/cli-command-overrides.ts,src/utils/command-schema.ts,src/utils/cli-option-schema.ts - Daemon client transport:
src/daemon-client.ts - Daemon state/store:
src/daemon/session-store.ts - Selector DSL and matching:
src/daemon/selectors.ts ispredicate evaluation:src/daemon/is-predicates.ts- Shared action helpers:
src/daemon/action-utils.ts - Snapshot shaping + labels:
src/daemon/snapshot-processing.ts - Handler context helpers:
src/daemon/context.ts,src/daemon/device-ready.ts - Request routing/policy:
src/daemon/request-router.ts,src/daemon/request-admission.ts,src/daemon/request-generic-dispatch.ts - Dispatcher + capability map:
src/core/dispatch.ts,src/core/dispatch-context.ts,src/core/dispatch-interactions.ts,src/core/capabilities.ts - Command catalog + command surface:
src/command-catalog.ts,src/commands/command-surface.ts,src/commands/command-contract.ts,src/commands/client-command-contracts.ts - CLI grammar:
src/commands/cli-grammar.ts,src/commands/cli-grammar/* - Daemon request projection:
src/commands/command-projection.ts - Platform backends:
src/platforms/ios/*,ios-runner/*,src/platforms/android/*
Pull Requests
- Before opening PR: ensure no conflict markers/unmerged paths.
- Commit messages and PR titles should use conventional prefixes such as
feat:,fix:,chore:,perf:,refactor:,docs:,test:,build:, orci:as appropriate. - Do not use bracketed automation prefixes such as
[codex]or similar bot tags in commit messages or PR titles. - Open a ready-for-review PR by default. Use a draft PR only when the user explicitly asks for one or the work is intentionally incomplete.
- Run required checks for touched scope from Testing Matrix.
- PR body must be short and include:
## Summary: describe the user-visible or reviewer-relevant change as before/after when useful, and include the issue closed by the PR usingCloses #123when applicable.## Validation: answer this prompt in concise prose: "How did you verify the change, and what passed or changed on screen?" Prefer evidence over command dumps; mention the relevant check category or scenario, and include screenshots when visual/UI behavior is relevant.
- Call out known gaps/follow-ups explicitly.
- Include touched-file count and note if scope expanded beyond initial command family.
Priority Order
- When guidance conflicts, apply in this order: Hard Rules -> Scope -> Testing Matrix -> style/preferences.