@frankzhang2026/opencode-android-orchestrator 0.6.0 → 0.7.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 (61) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +79 -25
  3. package/dist/cli.js +66 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/config/long-command-timeout.d.ts +20 -0
  6. package/dist/config/long-command-timeout.d.ts.map +1 -0
  7. package/dist/config/long-command-timeout.js +65 -0
  8. package/dist/config/long-command-timeout.js.map +1 -0
  9. package/dist/config/verification-policy.d.ts +20 -0
  10. package/dist/config/verification-policy.d.ts.map +1 -0
  11. package/dist/config/verification-policy.js +59 -0
  12. package/dist/config/verification-policy.js.map +1 -0
  13. package/dist/doctor/installation.d.ts +1 -1
  14. package/dist/doctor/installation.d.ts.map +1 -1
  15. package/dist/doctor/installation.js +49 -4
  16. package/dist/doctor/installation.js.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/installer/adaptive-templates.d.ts +10 -1
  22. package/dist/installer/adaptive-templates.d.ts.map +1 -1
  23. package/dist/installer/adaptive-templates.js +21 -0
  24. package/dist/installer/adaptive-templates.js.map +1 -1
  25. package/dist/installer/init.d.ts +1 -1
  26. package/dist/installer/init.d.ts.map +1 -1
  27. package/dist/installer/init.js +4 -1
  28. package/dist/installer/init.js.map +1 -1
  29. package/dist/installer/install-manifest.d.ts.map +1 -1
  30. package/dist/installer/install-manifest.js +13 -1
  31. package/dist/installer/install-manifest.js.map +1 -1
  32. package/dist/installer/opencode-config.d.ts +3 -3
  33. package/dist/installer/opencode-config.js +1 -1
  34. package/dist/installer/upgrade.d.ts +1 -0
  35. package/dist/installer/upgrade.d.ts.map +1 -1
  36. package/dist/installer/upgrade.js +41 -2
  37. package/dist/installer/upgrade.js.map +1 -1
  38. package/dist/plugin/index.d.ts.map +1 -1
  39. package/dist/plugin/index.js +5 -0
  40. package/dist/plugin/index.js.map +1 -1
  41. package/docs/MIGRATION.md +25 -13
  42. package/docs/SECURITY.md +25 -6
  43. package/docs/TROUBLESHOOTING.md +20 -2
  44. package/package.json +1 -1
  45. package/templates/.opencode/agents/scheduled-planner.md +5 -0
  46. package/templates/.opencode/commands/resume-task.md +28 -0
  47. package/templates/.opencode/skills/scheduled-quality-coder/SKILL.md +4 -3
  48. package/templates/.opencode/skills/scheduled-quality-orchestrator/SKILL.md +38 -3
  49. package/templates/.opencode/skills/scheduled-quality-reviewer/SKILL.md +3 -2
  50. package/templates/README.md +7 -4
  51. package/templates/automation/config.json +5 -1
  52. package/templates/automation/config.schema.json +13 -2
  53. package/templates/automation/tasks/TASK-TEMPLATE.json.example +1 -1
  54. package/templates/scripts/automation/claim-task.sh +23 -8
  55. package/templates/scripts/automation/lib.sh +38 -0
  56. package/templates/scripts/automation/orchestrate-task.sh +1 -1
  57. package/templates/scripts/automation/preflight.sh +1 -0
  58. package/templates/scripts/automation/resume-task.sh +135 -0
  59. package/templates/scripts/automation/tests/run-tests.sh +64 -3
  60. package/templates/scripts/automation/verify-integration.sh +2 -9
  61. package/templates/scripts/automation/verify-task.sh +2 -9
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ # shellcheck source=lib.sh
6
+ source "$SCRIPT_DIR/lib.sh"
7
+
8
+ task_id="${1:-}"
9
+ approval="${2:-}"
10
+ if [[ "$#" -ne 2 ]]; then
11
+ printf 'Usage: %s TASK-ID "RESUME-APPROVAL"\n' "$0" >&2
12
+ exit 2
13
+ fi
14
+
15
+ automation_validate_task_id "$task_id"
16
+ automation_require_orchestrated
17
+ automation_require_approval resume "$approval"
18
+
19
+ workspace_file="$(automation_workspace_path "$task_id")"
20
+ origin_file="$(automation_origin_path "$task_id")"
21
+ state_file="$(automation_state_path "$task_id")"
22
+ evidence_dir="$(automation_evidence_path "$task_id")"
23
+ transitions_file="$evidence_dir/transitions.jsonl"
24
+ resumptions_file="$evidence_dir/baseline-resumptions.jsonl"
25
+
26
+ [[ -f "$workspace_file" ]] || automation_die "workspace metadata is missing for $task_id"
27
+ [[ -f "$origin_file" ]] || automation_die "origin evidence is missing for $task_id"
28
+ [[ -f "$state_file" ]] || automation_die "state is missing for $task_id"
29
+ [[ -f "$transitions_file" ]] || automation_die "transition evidence is missing for $task_id"
30
+
31
+ automation_acquire_run_lock "$task_id"
32
+ trap 'automation_release_run_lock' EXIT
33
+
34
+ [[ "$(automation_read_state "$task_id")" == "BLOCKED" ]] || automation_die "$task_id is not BLOCKED"
35
+
36
+ source_root="$(jq -er '.sourceRoot' "$workspace_file")"
37
+ task_root="$(automation_workspace_task_root "$workspace_file")"
38
+ workspace_strategy="$(automation_workspace_strategy "$workspace_file")"
39
+ original_branch="$(jq -er '.originalBranch' "$workspace_file")"
40
+ task_branch="$(jq -er '.taskBranch' "$workspace_file")"
41
+ baseline_head="$(jq -er '.baselineHead' "$workspace_file")"
42
+
43
+ [[ "$(jq -r '.repositoryLeaseRequired // false' "$workspace_file")" == "true" ]] || \
44
+ automation_die "baseline recovery requires a leased transactional workspace"
45
+ [[ -d "$task_root" ]] || automation_die "task root is missing: $task_root"
46
+ automation_require_repository_lease "$task_id" "$source_root" "$workspace_strategy"
47
+ task_root="$(cd "$task_root" && pwd -P)"
48
+ source_root="$(cd "$source_root" && pwd -P)"
49
+ [[ "$source_root" == "$AUTOMATION_ROOT" ]] || automation_die "workspace sourceRoot does not match this repository"
50
+ [[ "$(jq -er '.sourceRoot' "$origin_file")" == "$source_root" ]] || automation_die "origin sourceRoot does not match workspace metadata"
51
+ [[ "$(jq -er '.originalBranch' "$origin_file")" == "$original_branch" ]] || automation_die "origin branch does not match workspace metadata"
52
+ [[ "$(automation_current_branch "$task_root")" == "$task_branch" ]] || automation_die "task branch changed"
53
+ [[ "$(git -C "$task_root" rev-parse HEAD)" == "$baseline_head" ]] || automation_die "task HEAD changed before baseline sealing"
54
+ git -C "$source_root" show-ref --verify --quiet "refs/heads/$original_branch" || automation_die "recorded original branch no longer exists"
55
+ [[ "$(git -C "$source_root" rev-parse "refs/heads/$original_branch")" == "$baseline_head" ]] || automation_die "original branch drifted after task preparation"
56
+
57
+ for sealed_evidence in baseline.json red.json ready.json review.json reviews.jsonl; do
58
+ [[ ! -e "$evidence_dir/$sealed_evidence" ]] || \
59
+ automation_die "$sealed_evidence already exists; baseline-only recovery is forbidden"
60
+ done
61
+
62
+ state_revision="$(jq -er '.revision' "$state_file")"
63
+ if ! jq -s -e --argjson revision "$state_revision" '
64
+ length == $revision and
65
+ length >= 2 and
66
+ .[-2].from == "PENDING" and
67
+ .[-2].to == "CODING" and
68
+ .[-2].actor == "coder-launcher" and
69
+ .[-2].note == "preflight passed; capturing baseline" and
70
+ .[-1].from == "CODING" and
71
+ .[-1].to == "BLOCKED" and
72
+ (.[-1].actor == "coder" or .[-1].actor == "preflight" or .[-1].actor == "orchestrator") and
73
+ (.[-1].note | test("baseline|claim|基线"; "i"))
74
+ ' "$transitions_file" >/dev/null; then
75
+ automation_die "BLOCKED state is not a recoverable baseline-capture interruption"
76
+ fi
77
+
78
+ automation_assert_planning_artifacts_sealed "$task_id" "$task_root"
79
+ changed_paths=()
80
+ while IFS= read -r path; do
81
+ [[ -n "$path" ]] && changed_paths+=("$path")
82
+ done < <(automation_changed_paths_at "$task_root")
83
+ [[ "${#changed_paths[@]}" -eq 2 ]] || automation_die "baseline recovery requires exactly the sealed plan and contract changes"
84
+ for path in "${changed_paths[@]}"; do
85
+ automation_is_planning_artifact "$task_id" "$path" || automation_die "product or unrelated change blocks baseline recovery: $path"
86
+ if git -C "$task_root" ls-files --error-unmatch -- "$path" >/dev/null 2>&1; then
87
+ automation_die "planning artifact became tracked before baseline recovery: $path"
88
+ fi
89
+ done
90
+
91
+ restart_count=0
92
+ if [[ -f "$resumptions_file" ]]; then
93
+ restart_count="$(jq -s --arg taskId "$task_id" '[.[] | select(.taskId == $taskId)] | length' "$resumptions_file")"
94
+ fi
95
+ max_restarts=1
96
+ [[ "$restart_count" -lt "$max_restarts" ]] || automation_die "baseline restart limit exhausted"
97
+ next_restart=$((restart_count + 1))
98
+ blocked_revision="$state_revision"
99
+ current_diff_sha="$(automation_worktree_diff_sha "$task_root")"
100
+
101
+ jq -nc \
102
+ --arg taskId "$task_id" \
103
+ --arg kind "resume-baseline" \
104
+ --arg at "$(automation_now)" \
105
+ --arg diffSha256 "$current_diff_sha" \
106
+ --argjson blockedRevision "$blocked_revision" \
107
+ '{taskId: $taskId, kind: $kind, at: $at,
108
+ blockedRevision: $blockedRevision, diffSha256: $diffSha256}' \
109
+ | automation_append_json "$(automation_approvals_path "$task_id")"
110
+
111
+ automation_transition_state \
112
+ "$task_id" \
113
+ "BLOCKED" \
114
+ "PENDING" \
115
+ "human" \
116
+ "baseline-only recovery $next_restart/$max_restarts; no product diff and baseline evidence absent"
117
+
118
+ jq -nc \
119
+ --arg taskId "$task_id" \
120
+ --arg at "$(automation_now)" \
121
+ --arg diffSha256 "$current_diff_sha" \
122
+ --argjson blockedRevision "$blocked_revision" \
123
+ --argjson restart "$next_restart" \
124
+ --argjson maxRestarts "$max_restarts" \
125
+ '{taskId: $taskId, at: $at, blockedRevision: $blockedRevision,
126
+ diffSha256: $diffSha256, restart: $restart,
127
+ maxRestarts: $maxRestarts, route: "BLOCKED->PENDING",
128
+ baselineEvidencePresent: false, productDiffPresent: false}' \
129
+ | automation_append_json "$resumptions_file"
130
+
131
+ automation_info "$task_id baseline-only recovery accepted; restarting deterministic claim flow"
132
+ automation_release_run_lock
133
+ trap - EXIT
134
+
135
+ "$SCRIPT_DIR/orchestrate-task.sh" "$task_id"
@@ -119,13 +119,17 @@ jq -n '{
119
119
  maxFixLoops: 1,
120
120
  maxReviewCycles: 1,
121
121
  maxReviewerRestarts: 2,
122
+ unitTestsEnabled: true,
123
+ lintEnabled: false,
124
+ longCommandTimeoutMs: 1800000,
122
125
  autoCleanupWorktrees: true,
123
126
  pushAfterAcceptance: false,
124
127
  approvalPhrases: {
125
128
  proposal: "批准方案,生成计划和任务合同。",
126
129
  contract: "合同已复核,批准自动执行到人工验收阶段。",
127
130
  acceptance: "验收通过,提交到原分支。",
128
- abort: "中止任务,封存修改并恢复原分支。"
131
+ abort: "中止任务,封存修改并恢复原分支。",
132
+ resume: "恢复任务,重新捕获基线并继续自动执行。"
129
133
  },
130
134
  plugins: {
131
135
  superpowers: "superpowers@git+https://github.com/obra/superpowers.git#v6.2.0"
@@ -317,7 +321,29 @@ printf '%s\n' \
317
321
  pass 'invalid, duplicate, symbolic-link, patterned, and protected allowlist input fails closed'
318
322
 
319
323
  run_fixture ./scripts/automation/validate-contract.sh TASK-TEST-001 >/dev/null
320
- pass 'valid version-3 configuration and task contract are accepted'
324
+ verification_config_backup="$(mktemp "$runtime_root/config.json.XXXXXX")"
325
+ cp "$fixture/automation/config.json" "$verification_config_backup"
326
+
327
+ unit_enabled_output="$(run_fixture env AUTOMATION_FAKE_GREEN=1 bash -c 'source ./scripts/automation/lib.sh; automation_run_configured_unit_tests ./automation/tasks/TASK-TEST-001.json')"
328
+ [[ "$unit_enabled_output" == *"running focused test"* ]] || fail 'default unit-test policy did not run the focused test'
329
+ [[ "$unit_enabled_output" == *"running full unit tests"* ]] || fail 'default unit-test policy did not run the full unit-test group'
330
+ [[ "$unit_enabled_output" == *"BUILD SUCCESSFUL"* ]] || fail 'enabled unit-test policy did not execute configured tests'
331
+ jq '.unitTestsEnabled = false' "$verification_config_backup" > "$fixture/automation/config.json"
332
+ unit_disabled_output="$(run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_run_configured_unit_tests ./automation/tasks/TASK-TEST-001.json')"
333
+ [[ "$unit_disabled_output" == *"skipping unit tests (unitTestsEnabled=false)"* ]] || fail 'disabled unit-test policy did not skip verification'
334
+ [[ "$unit_disabled_output" != *"BUILD SUCCESSFUL"* ]] || fail 'disabled unit-test policy executed a configured test'
335
+ cp "$verification_config_backup" "$fixture/automation/config.json"
336
+
337
+ lint_disabled_output="$(run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_run_configured_lint')"
338
+ [[ "$lint_disabled_output" == *"skipping Android lint (lintEnabled=false)"* ]] || fail 'default lint policy did not skip Android lint'
339
+ jq '.lintEnabled = true' "$verification_config_backup" > "$fixture/automation/config.json"
340
+ lint_enabled_output="$(run_fixture bash -c 'source ./scripts/automation/lib.sh; automation_run_configured_lint')"
341
+ [[ "$lint_enabled_output" == *"running configured Android lint tasks"* ]] || fail 'enabled lint policy did not run Android lint'
342
+ [[ "$lint_enabled_output" == *"BUILD SUCCESSFUL"* ]] || fail 'enabled lint policy did not execute the configured lint task'
343
+ cp "$verification_config_backup" "$fixture/automation/config.json"
344
+ rm "$verification_config_backup"
345
+ run_fixture ./scripts/automation/validate-contract.sh TASK-TEST-001 >/dev/null
346
+ pass 'repository configuration defaults unit tests on and lint off and honors both switches'
321
347
 
322
348
  if run_fixture env AUTOMATION_HUMAN_APPROVED=0 ./scripts/automation/queue-task.sh TASK-TEST-001 >/dev/null 2>&1; then
323
349
  fail 'legacy queue accepted without explicit human approval'
@@ -483,9 +509,12 @@ fi
483
509
  if run_fixture env AUTOMATION_SKIP_AGENT_RUN=1 ./scripts/automation/resume-review.sh TASK-TEST-003 >/dev/null 2>&1; then
484
510
  fail 'reviewer-only recovery accepted a non-review blocker'
485
511
  fi
512
+ if run_fixture env AUTOMATION_SKIP_AGENT_RUN=1 ./scripts/automation/resume-task.sh TASK-TEST-003 '恢复任务,重新捕获基线并继续自动执行。' >/dev/null 2>&1; then
513
+ fail 'baseline-only recovery accepted a preflight blocker'
514
+ fi
486
515
  rm "$fixture/unapproved-change.txt"
487
516
  pass 'preflight blocks dirty task roots without retrying implicitly'
488
- pass 'reviewer-only recovery rejects unrelated BLOCKED states'
517
+ pass 'reviewer and baseline recovery reject unrelated BLOCKED states'
489
518
 
490
519
  printf '%s\n' '# Approved reviewer-fix plan' > "$fixture/docs/plans/TASK-TEST-005.md"
491
520
  jq \
@@ -745,6 +774,38 @@ run_fixture ./scripts/automation/abort-task.sh TASK-TEST-009 '中止任务,封
745
774
  [[ ! -e "$fixture/docs/plans/TASK-TEST-009.md" && ! -e "$fixture/automation/tasks/TASK-TEST-009.json" ]] || fail 'planning-only abort left generated artifacts in the original worktree'
746
775
  pass 'aborting before product edits archives planning artifacts without creating a planning-only commit'
747
776
 
777
+ printf '%s\n' '# Baseline interruption recovery plan' > "$fixture/docs/plans/TASK-TEST-010.md"
778
+ jq \
779
+ '.id = "TASK-TEST-010" |
780
+ .title = "Recover one interrupted baseline capture" |
781
+ .planPath = "docs/plans/TASK-TEST-010.md" |
782
+ .targetTests = [{gradleTask: "testDebugUnitTest", filter: "dev.example.orchestratorfixture.BaselineRecoveryTest"}]' \
783
+ "$fixture/automation/tasks/TASK-TEST-001.json" \
784
+ > "$fixture/automation/tasks/TASK-TEST-010.json"
785
+ run_fixture ./scripts/automation/prepare-contract-review.sh TASK-TEST-010 '批准方案,生成计划和任务合同。' >/dev/null
786
+ run_fixture env AUTOMATION_SKIP_AGENT_RUN=1 ./scripts/automation/approve-and-run.sh TASK-TEST-010 '合同已复核,批准自动执行到人工验收阶段。' >/dev/null
787
+ run_fixture ./scripts/automation/transition-state.sh TASK-TEST-010 PENDING CODING coder-launcher 'preflight passed; capturing baseline' >/dev/null
788
+ run_fixture ./scripts/automation/block-task.sh TASK-TEST-010 'claim baseline capture interrupted before metadata sealing' >/dev/null
789
+ if run_fixture env AUTOMATION_SKIP_AGENT_RUN=1 ./scripts/automation/resume-task.sh TASK-TEST-010 '拒绝' >/dev/null 2>&1; then
790
+ fail 'baseline recovery accepted an invalid confirmation'
791
+ fi
792
+ [[ "$(jq -r '.state' "$runtime_root/state/TASK-TEST-010.json")" == "BLOCKED" ]] || fail 'rejected baseline recovery changed task state'
793
+ run_fixture env AUTOMATION_SKIP_AGENT_RUN=1 ./scripts/automation/resume-task.sh TASK-TEST-010 '恢复任务,重新捕获基线并继续自动执行。' >/dev/null
794
+ [[ "$(jq -r '.state' "$runtime_root/state/TASK-TEST-010.json")" == "PENDING" ]] || fail 'baseline recovery did not return to PENDING'
795
+ [[ "$(jq -s 'length' "$runtime_root/evidence/TASK-TEST-010/baseline-resumptions.jsonl")" -eq 1 ]] || fail 'baseline recovery did not record one bounded resumption'
796
+ [[ "$(jq -s -r '.[-1].kind' "$runtime_root/evidence/TASK-TEST-010/approvals.jsonl")" == "resume-baseline" ]] || fail 'baseline recovery approval was not audited'
797
+ [[ ! -f "$runtime_root/evidence/TASK-TEST-010/baseline.json" ]] || fail 'baseline recovery manufactured baseline evidence'
798
+ pass 'one explicitly approved baseline interruption resumes through PENDING without product changes'
799
+
800
+ run_fixture ./scripts/automation/transition-state.sh TASK-TEST-010 PENDING CODING coder-launcher 'preflight passed; capturing baseline' >/dev/null
801
+ run_fixture ./scripts/automation/block-task.sh TASK-TEST-010 'claim baseline capture interrupted a second time before metadata sealing' >/dev/null
802
+ if run_fixture env AUTOMATION_SKIP_AGENT_RUN=1 ./scripts/automation/resume-task.sh TASK-TEST-010 '恢复任务,重新捕获基线并继续自动执行。' >/dev/null 2>&1; then
803
+ fail 'baseline recovery exceeded its one-restart limit'
804
+ fi
805
+ [[ "$(jq -r '.state' "$runtime_root/state/TASK-TEST-010.json")" == "BLOCKED" ]] || fail 'exhausted baseline recovery changed task state'
806
+ pass 'baseline recovery stops after one auditable restart'
807
+ run_fixture ./scripts/automation/abort-task.sh TASK-TEST-010 '中止任务,封存修改并恢复原分支。' >/dev/null
808
+
748
809
  isolated_config_tmp="$(mktemp "$fixture/automation/.config.XXXXXX")"
749
810
  jq '.workspaceStrategy = "isolatedWorktree"' "$fixture/automation/config.json" > "$isolated_config_tmp"
750
811
  mv "$isolated_config_tmp" "$fixture/automation/config.json"
@@ -16,19 +16,12 @@ contract="$(automation_contract_path "$task_id")"
16
16
 
17
17
  "$SCRIPT_DIR/integration-scope-gate.sh" "$task_id" "$base" "$head"
18
18
 
19
- while IFS=$'\t' read -r gradle_task filter; do
20
- automation_info "running integration focused test ($gradle_task): $filter"
21
- automation_run_focused_test "$gradle_task" "$filter" "$AUTOMATION_ROOT"
22
- done < <(jq -r '.targetTests[] | [.gradleTask, .filter] | @tsv' "$contract")
23
-
24
- automation_info "running integration full unit tests"
25
- automation_run_gradle_group "fullUnitTestTasks" "$AUTOMATION_ROOT"
19
+ automation_run_configured_unit_tests "$contract" "$AUTOMATION_ROOT" "integration "
26
20
 
27
21
  automation_info "running configured integration assemble tasks"
28
22
  automation_run_gradle_group "assembleTasks" "$AUTOMATION_ROOT"
29
23
 
30
- automation_info "running configured integration Android lint tasks"
31
- automation_run_gradle_group "lintTasks" "$AUTOMATION_ROOT"
24
+ automation_run_configured_lint "$AUTOMATION_ROOT"
32
25
 
33
26
  if [[ "$(jq -r '.deviceTestsRequired' "$contract")" == "true" ]]; then
34
27
  automation_info "running configured integration device tests"
@@ -17,19 +17,12 @@ evidence_dir="$(automation_evidence_path "$task_id")"
17
17
 
18
18
  "$SCRIPT_DIR/scope-gate.sh" "$task_id"
19
19
 
20
- while IFS=$'\t' read -r gradle_task filter; do
21
- automation_info "running focused test ($gradle_task): $filter"
22
- automation_run_focused_test "$gradle_task" "$filter" "$AUTOMATION_ROOT"
23
- done < <(jq -r '.targetTests[] | [.gradleTask, .filter] | @tsv' "$contract")
24
-
25
- automation_info "running full unit tests"
26
- automation_run_gradle_group "fullUnitTestTasks" "$AUTOMATION_ROOT"
20
+ automation_run_configured_unit_tests "$contract" "$AUTOMATION_ROOT"
27
21
 
28
22
  automation_info "running configured assemble tasks"
29
23
  automation_run_gradle_group "assembleTasks" "$AUTOMATION_ROOT"
30
24
 
31
- automation_info "running configured Android lint tasks"
32
- automation_run_gradle_group "lintTasks" "$AUTOMATION_ROOT"
25
+ automation_run_configured_lint "$AUTOMATION_ROOT"
33
26
 
34
27
  if [[ "$(jq -r '.deviceTestsRequired' "$contract")" == "true" ]]; then
35
28
  automation_info "running configured device tests"