diff --git a/.github/nightly-step-coverage.json b/.github/nightly-step-coverage.json index b5ff15e71495..999f9aba2bdb 100644 --- a/.github/nightly-step-coverage.json +++ b/.github/nightly-step-coverage.json @@ -235,8 +235,7 @@ "Wait through GitHub API rate limits in this job's gh calls": {"pr": "ci-guards.yml:workflow-guard-tests:Validate nightly publish-path gh calls wait through a rate limit"}, "Pick the Blacksmith macOS label with a moving queue": {"pr": "ci-guards.yml:workflow-guard-tests:Validate the nightly signing jobs take a Blacksmith label with a moving queue"}, - "List newer pending nightly-next notarizations": {"pr": "ci-guards.yml:workflow-guard-tests:Validate the nightly-next pending notarization continuation"}, - "Read the visual gate's verdicts": {"pr": "ci-guards.yml:workflow-guard-tests:Test the cmux NEXT nightly visual gate"}}, + "List newer pending nightly-next notarizations": {"pr": "ci-guards.yml:workflow-guard-tests:Validate the nightly-next pending notarization continuation"}}, "recover-nightly-next-notarization": { "Mint the release App token for this job's gh calls": {"nightly_only": "mints release GitHub App token"}, "Send this job's gh calls as the release App, github.token as the fallback": {"pr": "ci-guards.yml:workflow-guard-tests:Validate the nightly publish jobs send gh calls as the release App first"}, @@ -442,7 +441,7 @@ "Enforce the verdict": {"plumbing": true}}, "visual-gate-nightly-verdict": { "Wait for the visual gate, at most 10 min past the signed build": {"pr": "ci-guards.yml:workflow-guard-tests:Test the cmux NEXT nightly visual gate"}, - "Fail with the gate when it holds the build": {"plumbing": true}}, + "Record failed advisory verdict": {"plumbing": true}}, "report-visual-gate-nightly": { "Record the verdict in the visual gate issue": {"nightly_only": "writes the verdict comment to the nightly-next visual gate issue with the run's token"}} }, diff --git a/Packages/macOS/CmuxNext/Sources/CmuxNextActions/SidebarSectionActions.xcstrings b/Packages/macOS/CmuxNext/Sources/CmuxNextActions/SidebarSectionActions.xcstrings index 33b943a958eb..d5f089f68ecc 100644 --- a/Packages/macOS/CmuxNext/Sources/CmuxNextActions/SidebarSectionActions.xcstrings +++ b/Packages/macOS/CmuxNext/Sources/CmuxNextActions/SidebarSectionActions.xcstrings @@ -57,6 +57,78 @@ "state": "translated", "value": "コンパクトなワークスペース一覧" } + }, + "bs": { + "stringUnit": { + "state": "needs_review", + "value": "Kompaktna lista radnih prostora" + } + }, + "da": { + "stringUnit": { + "state": "needs_review", + "value": "Kompakt liste over arbejdsområder" + } + }, + "it": { + "stringUnit": { + "state": "needs_review", + "value": "Elenco compatto delle aree di lavoro" + } + }, + "km": { + "stringUnit": { + "state": "needs_review", + "value": "បញ្ជីកន្លែងធ្វើការបង្រួម" + } + }, + "nb": { + "stringUnit": { + "state": "needs_review", + "value": "Kompakt liste over arbeidsområder" + } + }, + "pl": { + "stringUnit": { + "state": "needs_review", + "value": "Kompaktowa lista przestrzeni roboczych" + } + }, + "pt-BR": { + "stringUnit": { + "state": "needs_review", + "value": "Lista Compacta de Áreas de Trabalho" + } + }, + "ru": { + "stringUnit": { + "state": "needs_review", + "value": "Компактный список рабочих пространств" + } + }, + "th": { + "stringUnit": { + "state": "needs_review", + "value": "รายการเวิร์กสเปซแบบกะทัดรัด" + } + }, + "tr": { + "stringUnit": { + "state": "needs_review", + "value": "Kompakt Çalışma Alanı Listesi" + } + }, + "uk": { + "stringUnit": { + "state": "needs_review", + "value": "Компактний список робочих областей" + } + }, + "vi": { + "stringUnit": { + "state": "needs_review", + "value": "Danh sách không gian làm việc thu gọn" + } } } }, diff --git a/Packages/macOS/CmuxNext/Sources/CmuxNextApps/Resources/Localizable.xcstrings b/Packages/macOS/CmuxNext/Sources/CmuxNextApps/Resources/Localizable.xcstrings index 4b79a5a369dd..9af564e4435d 100644 --- a/Packages/macOS/CmuxNext/Sources/CmuxNextApps/Resources/Localizable.xcstrings +++ b/Packages/macOS/CmuxNext/Sources/CmuxNextApps/Resources/Localizable.xcstrings @@ -15141,6 +15141,12 @@ "ja" : { "variations" : { "plural" : { + "one" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "%lld件のアップデートがあります" + } + }, "other" : { "stringUnit" : { "state" : "needs_review", @@ -15153,6 +15159,12 @@ "km" : { "variations" : { "plural" : { + "one" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "មានបច្ចុប្បន្នភាព %lld" + } + }, "other" : { "stringUnit" : { "state" : "needs_review", @@ -15165,6 +15177,12 @@ "ko" : { "variations" : { "plural" : { + "one" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "%lld개 업데이트 가능" + } + }, "other" : { "stringUnit" : { "state" : "needs_review", @@ -15279,6 +15297,12 @@ "th" : { "variations" : { "plural" : { + "one" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "มีการอัปเดต %lld รายการ" + } + }, "other" : { "stringUnit" : { "state" : "needs_review", @@ -15339,6 +15363,12 @@ "vi" : { "variations" : { "plural" : { + "one" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Có %lld bản cập nhật" + } + }, "other" : { "stringUnit" : { "state" : "needs_review", @@ -15351,6 +15381,12 @@ "zh-Hans" : { "variations" : { "plural" : { + "one" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "有 %lld 项更新" + } + }, "other" : { "stringUnit" : { "state" : "needs_review", @@ -15363,6 +15399,12 @@ "zh-Hant" : { "variations" : { "plural" : { + "one" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "有 %lld 項更新" + } + }, "other" : { "stringUnit" : { "state" : "needs_review", diff --git a/release-notes/next/highlights/chat-title-plain.md b/release-notes/next/highlights/chat-title-plain.md index 9ba969cefaef..1c64409818c1 100644 --- a/release-notes/next/highlights/chat-title-plain.md +++ b/release-notes/next/highlights/chat-title-plain.md @@ -1,4 +1,5 @@ title: A plain chat title category: improved +pr: https://github.com/manaflow-ai/cmux/pull/29920 The chat's name in the header is plain text, with no pill or dropdown. Double-click it or press ⌘R to rename; Move to machine and Delete join the chat's ⋮ menu with its other actions. diff --git a/release-notes/next/highlights/cloud-live-previews.md b/release-notes/next/highlights/cloud-live-previews.md index bf5daf9b3790..d841545472ae 100644 --- a/release-notes/next/highlights/cloud-live-previews.md +++ b/release-notes/next/highlights/cloud-live-previews.md @@ -1,4 +1,5 @@ title: Open Cloud web previews in the browser category: improved +pr: https://github.com/manaflow-ai/cmux/pull/30334 Cloud Machine Ports discovers your running servers and lets you open a private preview in the app's browser. The same preview links are available from the CLI. diff --git a/release-notes/next/highlights/working-harness-tab-icons.md b/release-notes/next/highlights/working-harness-tab-icons.md index 850445aef7b1..3ebbdc4de310 100644 --- a/release-notes/next/highlights/working-harness-tab-icons.md +++ b/release-notes/next/highlights/working-harness-tab-icons.md @@ -1,4 +1,5 @@ title: Working chats keep their custom tab icons category: fixed +pr: https://github.com/manaflow-ai/cmux/pull/27905 Pinned and narrow chat tabs keep their custom harness icon visible beside the working spinner, so you can still tell your agents apart. diff --git a/scripts/cmux-next/check-daemon-capabilities.sh b/scripts/cmux-next/check-daemon-capabilities.sh index 09736c4a8506..3ce36b2fc51f 100755 --- a/scripts/cmux-next/check-daemon-capabilities.sh +++ b/scripts/cmux-next/check-daemon-capabilities.sh @@ -88,7 +88,7 @@ runtime_only = { "agent-inventory-watch-admission-v1", "agent-inventory-watch-v1", "agent-session-attach-v1", "agent-session-children-v1", "agent-session-folders-v1", "agent-session-observe-v1", "agent-session-presence-v1", "agent-session-settings-v1", "agent-session-start-keyed-reason-v1", "agent-session-start-v1", - "agent-session-start-vm-kind-v1", "agent-session-start-model-v1", "agent-session-trust-v1", "agent-session-trust-vm-kind-v1", + "agent-session-start-vm-kind-v1", "agent-session-start-model-v1", "agent-session-start-no-cwd-v1", "agent-session-trust-v1", "agent-session-trust-vm-kind-v1", "cloud-attachments-v1", "cloud-chief-live-v1", "cloud-conversation-runs-v1", "cloud-conversation-typing-v1", "cloud-conversations-v1", "cloud-host-replica-v1", "cloud-selfhost-v1", "history-v1", "terminal-reaper-active-v1", diff --git a/tests/test-execution.toml b/tests/test-execution.toml index 656014a21c3b..789734567ed2 100644 --- a/tests/test-execution.toml +++ b/tests/test-execution.toml @@ -2265,7 +2265,8 @@ lane = "linux-guard" [[test]] path = "tests/test_ci_blacksmith_linux_sizes.py" -lane = "linux-guard" +lane = "manual" +reason = "Lawrence 2026-10-23: no new CI tests. #30360 registered it as linux-guard without a ci-guards.yml step, which reddened tooling-tests on every pull request. Checks .github/runners.json linux_sizes against every Blacksmith Linux runs-on; run python3 tests/test_ci_blacksmith_linux_sizes.py yourself after changing a runs-on label or that table (about 3 s, no build)." [[test]] path = "tests/test_cmux_tui_artifacts_linux_runner_set.py" @@ -2395,3 +2396,32 @@ lane = "linux-guard" path = "tests/test_nightly_tree_source_marker.py" lane = "manual" reason = "Portable source-contract test; run python3 tests/test_nightly_tree_source_marker.py through cmux-ci when tree observations change. No new CI gate." + +[[test]] +path = "tests/test_nightly_dedicated_mini.py" +lane = "linux-guard" + +[[test]] +path = "tests/test_cloud_pair_proof.py" +lane = "manual" +reason = "Lawrence 2026-10-23: no new CI tests. Guardrails of scripts/ci/cmux_next_cloud_pair_proof.py, the fleet Cloud chat proof (#30473); run python3 tests/test_cloud_pair_proof.py yourself after changing that script (under 1 s, no build)." + +[[test]] +path = "tests/test_cmux_next_gate_plan_suite_steps.py" +lane = "manual" +reason = "Lawrence 2026-10-23: no new CI tests. Checks gate-plan.py's CmuxNext suite steps against the cmux-ci controller's ci-step limits; run python3 tests/test_cmux_next_gate_plan_suite_steps.py yourself after changing scripts/cmux-next/gate-plan.py or gate-run.sh (about 1 s, no build)." + +[[test]] +path = "tests/test_cmux_next_guardrails_qos.py" +lane = "manual" +reason = "Lawrence 2026-10-23: no new CI gates. The file says so itself: run python3 tests/test_cmux_next_guardrails_qos.py yourself after changing guardrails.py's task-priority rule (about 2 s, no build)." + +[[test]] +path = "tests/test_cmux_next_perf_budgets.py" +lane = "manual" +reason = "Lawrence 2026-10-23: no new CI tests. Checks scripts/cmux-next/perf-budgets.py's table and gate; run python3 tests/test_cmux_next_perf_budgets.py yourself after changing it (under 1 s, no build)." + +[[test]] +path = "tests/test_cmux_tui_no_bundled_webpki_roots.py" +lane = "manual" +reason = "Lawrence 2026-10-23: no new CI tests. Checks that cmux-tui compiles in no webpki root list (#25452); run python3 tests/test_cmux_tui_no_bundled_webpki_roots.py yourself after changing a cmux-tui Cargo.toml, Cargo.lock or TLS setup (about 1 s, no build)." diff --git a/tests/test_cmux_next_checks_workflow.py b/tests/test_cmux_next_checks_workflow.py index 268d737b2213..2d5bab93fbac 100644 --- a/tests/test_cmux_next_checks_workflow.py +++ b/tests/test_cmux_next_checks_workflow.py @@ -907,10 +907,10 @@ def test_dispatch_runs_every_tier_on_any_ref(self): self.assertNotIn("next-batch", jobs["request-cmux-next-dev-publish"]["if"]) def test_branch_lookup_uses_git_https_basic_auth_and_a_timeout(self): - for filename, job_id in (("cmux-next.yml", "push-head-preflight"), - ("cmux-tui-artifacts.yml", "tree-preflight")): + for filename, job_id, step_id in (("cmux-next.yml", "push-head-preflight", "head"), + ("cmux-tui-artifacts.yml", "tree-preflight", "inspect")): document = yaml.safe_load((WORKFLOW.parent / filename).read_text()) - script = document["jobs"][job_id]["steps"][-1]["run"] + script = preflight_step(document, job_id, step_id)["run"] with self.subTest(workflow=filename): self.assertIn("Authorization: Basic", script) self.assertIn("x-access-token:%s", script) @@ -995,6 +995,13 @@ def test_a_timed_out_step_takes_its_children_with_it(self): self.fail(f"step child {child} outlived the timed-out step") +def preflight_step(document, job_id, step_id): + """The preflight step by id: steps after it (tree-preflight's owner observation, #30221) do not move it.""" + steps = [step for step in document["jobs"][job_id]["steps"] if step.get("id") == step_id] + assert len(steps) == 1, f"{job_id} has {len(steps)} steps with id {step_id!r}" + return steps[0] + + class PushPreflightBehavior(unittest.TestCase): """Execute the workflow shell with bounded fake network and git responses.""" @@ -1003,8 +1010,8 @@ def run_preflight(self, *, artifacts=False, event="push", ref="refs/heads/feat-c missing="", malformed=False, fork=False): filename = "cmux-tui-artifacts.yml" if artifacts else "cmux-next.yml" workflow = yaml.safe_load((WORKFLOW.parent / filename).read_text()) - job_id = "tree-preflight" if artifacts else "push-head-preflight" - script = workflow["jobs"][job_id]["steps"][-1]["run"] + job_id, step_id = ("tree-preflight", "inspect") if artifacts else ("push-head-preflight", "head") + script = preflight_step(workflow, job_id, step_id)["run"] with tempfile.TemporaryDirectory() as directory: root = Path(directory) git = root / "git" diff --git a/tests/test_cmux_next_route.py b/tests/test_cmux_next_route.py index 5a7f28427298..eeb4c8428faf 100644 --- a/tests/test_cmux_next_route.py +++ b/tests/test_cmux_next_route.py @@ -747,10 +747,13 @@ def setUpClass(cls): import cmux_next_route as route_module cls.route = route_module - # Page JavaScript a suite runs, and the ways a suite gives the page its own document. + # Page JavaScript a suite runs, and the ways a suite gives the page its own document. An agent pane + # pointed at a dev server URL (AgentPaneSource.devURLVariable, honored in Debug test builds) loads that + # URL, not the built bundle: AgentPageHibernationTests points it at a loopback port nobody serves and + # waits for that load to end (#30312). TALKS_TO_PAGE = re.compile(r"\b(?:evaluateJavaScript|callAsyncJavaScript|waitUntilLoaded|WKUserScript)\b") OWN_DOCUMENT = re.compile(r'loadHTMLString|registerBundledRoot|allowFixtureRoot|AgentPageTestRoot' - r'|source:\s*\.bundled\(|index\.html|\.html"') + r'|source:\s*\.bundled\(|index\.html|\.html"|AgentPaneSource\.devURLVariable') OPT_IN = re.compile(r"\.enabled\(if:\s*ProcessInfo\.processInfo\.environment\[") def test_the_bundle_targets_are_the_ones_the_bundle_build_writes_into(self): diff --git a/tests/test_nightly_next_arm64_only.py b/tests/test_nightly_next_arm64_only.py index fabf16cfc191..bb37554589f7 100644 --- a/tests/test_nightly_next_arm64_only.py +++ b/tests/test_nightly_next_arm64_only.py @@ -149,6 +149,10 @@ def compile_commands(outputs): (root / "scripts/ci/background_build.py").write_text((ROOT / "scripts/ci/background_build.py").read_text()) (root / "scripts/lib").mkdir(parents=True) (root / "scripts/lib/rust-targets.sh").write_text((ROOT / "scripts/lib/rust-targets.sh").read_text()) + # The Release step sources the shared cache-failure classifier (#30107). + (root / "scripts/cmux-next/lib").mkdir(parents=True) + (root / "scripts/cmux-next/lib/compilation-cache.sh").write_text( + (ROOT / "scripts/cmux-next/lib/compilation-cache.sh").read_text()) capture = root / "capture" capture.mkdir() steps = ( diff --git a/tests/test_nightly_next_identity.py b/tests/test_nightly_next_identity.py index d6dc3ae246b2..474121ba7ad4 100644 --- a/tests/test_nightly_next_identity.py +++ b/tests/test_nightly_next_identity.py @@ -114,7 +114,15 @@ def job_containing(text: str) -> str: def job_env(block: str, name: str) -> str: match = re.search(rf"\n {name}: ([^\n]+)\n", block) assert match, f"no job env {name}" - return match.group(1) + value = match.group(1) + # A YAML anchor (`&name value`) or alias (`*name`, #30278 shares env values to keep nightly.yml + # under GitHub's size limit) reads as the anchored value, as YAML does. + alias = re.fullmatch(r"\*([A-Za-z0-9_-]+)", value) + if alias: + anchors = re.findall(rf"[ :]&{re.escape(alias[1])} ([^\n]+)\n", WORKFLOW) + assert len(anchors) == 1, f"alias *{alias[1]} has {len(anchors)} anchors" + value = anchors[0] + return re.sub(r"^&[A-Za-z0-9_-]+ ", "", value) def test_each_app_reads_the_feed_its_publisher_writes():