Skip to content

daslang-live: --live-port flag + port-keyed lock + .das resolution chain#2726

Merged
borisbat merged 5 commits into
masterfrom
claude/recursing-cohen-9bbe12
May 19, 2026
Merged

daslang-live: --live-port flag + port-keyed lock + .das resolution chain#2726
borisbat merged 5 commits into
masterfrom
claude/recursing-cohen-9bbe12

Conversation

@borisbat
Copy link
Copy Markdown
Collaborator

@borisbat borisbat commented May 19, 2026

Summary

  • daslang-live accepts --live-port <N> (or --live-port=N), recognized BOTH before and after --. The host-wide single-instance lock is keyed on the resolved port (daslang-live-single-instance-<port> mutex on Windows / /tmp/daslang-live-<port>.lock on POSIX), so two binaries on different ports coexist on the same host; same port still serializes.
  • live_api module resolves the bind port from the same full argv via find_flag_raw_value. Both sides agree because they read identical input — no env var, no config file, no state.
  • MCP do_live_launch forwards its port arg to the spawned binary as --live-port {port} and rejects values outside [1, 65535] (digit-only + parsed range check via port_in_range) before composing the spawn argv.
  • Resolves the parallel-checkout interference: two daslang trees can both run daslang-live simultaneously on different ports.
  • Also fixes a pre-existing AOT gap surfaced by this PR: live_api.das was missing from tests/aot/CMakeLists.txt's AOT_LIVE_HOST_MODULE_FILES, so any AOT test requiring live/live_api 50101'd. Added.

Port precedence (highest first, both sides)

  1. Programmaticlive_api_set_port(p) called BEFORE the LiveApiAgent constructor. The server binds at construction time and never rebinds; later setter calls only mutate the global, not the bound socket.
  2. Script argv--live-port N or --live-port=N anywhere in get_command_line_arguments(), including the post--- slice. Last occurrence wins (clargs convention), invalid trailing values coerce to 0 so both sides fall through identically.
  3. Default9090.

The C++ daslang-live binary scans the same full argv with the same strict parse semantics and keys its lock on the resolved port.

Files

File Change
utils/daslang-live/main.cpp --live-port / --live-port=N parsing via find_live_port_in_argv (full argv, last-occurrence-wins, strict parse); port-keyed single-instance lock; help text. No env, no config-file.
modules/dasLiveHost/live/live_api.das parse_port_string / parse_argv_port / resolve_port_override_from (pure, testable) + resolve_port_override + [init]. Pulls in daslib/clargs for find_flag_raw_value and daslib/strings_convert for try_to_int.
utils/mcp/tools/live.das do_live_launch prepends --live-port {port} to the spawn argv when caller passed port; port_in_range rejects non-numeric / out-of-range up front (avoids 10s MCP timeout).
tests/live_host/test_port_override.das Unit tests for the parse / argv / precedence layers + repro tests for each correctness bug surfaced in review (port range, post--- argv, last-occurrence-invalid).
tests/aot/CMakeLists.txt Register modules/dasLiveHost/live/live_api.das as an AOT module-file so test_aot links its AOT cpp (was the darwin Release 50101 root cause).
skills/daslang_live.md Documents the 3-level precedence chain and the lock keying.
doc/source/reference/utils/daslang_live.rst CLI reference filled in (-project_root, -v1syntax, -track-allocations, -heap-report, --no-dyn-modules, --no-dump-leaks, --live-port, -h/--help); precedence chain documented; lock-key note updated.
doc/source/reference/utils/mcp.rst New Common parameters subsection documenting project + project_root (project_root was used by 18+ tools but documented 0 times). Live-reload control table rewritten with per-tool arg surface.
mouse-data/docs/daslang-live-no-port-flag-... RESOLVED marker, historical body preserved.

Test plan

  • daslang.exe dastest/dastest.das -- --test tests/live_host — interpreted: 75/75 pass (including 21 new port-override cases).
  • Lint clean on all 3 changed .das files via utils/mcp/subtools/lint_tool.das.
  • Format clean (daslib/das_source_formatter_fio).
  • AOT codegen for tests/live_host/test_port_override.das succeeds locally; full test_aot.exe runs on CI (darwin Release green after the CMake registration fix).
  • Manual smoke: daslang-live foo.das --live-port 9090abc → strict-parse rejection, exits with error; daslang-live foo.das --live-port=19090 parsed successfully; help text shows the flag.
  • Two-instance live smoke from two worktrees (--live-port 9090 + --live-port 19090) — to do post-merge.

🤖 Generated with Claude Code

Resolves the "two daslang trees can't both run daslang-live" interference
that the parallel-checkout plan surfaced. After this:

  daslang-live my.das --live-port 19090     # binary form
  daslang script.das -- --live-port 19090   # scripts using live_api

coexist with another instance on the default 9090.

Port precedence (highest first), unified across C++ binary and .das module
via an env-var roundtrip so the single-instance lock and the HTTP bind
can never disagree:

  1. live_api_set_port(p)             (programmatic, always wins)
  2. script argv --live-port N        (find_flag_raw_value, full argv)
  3. env DASLANG_LIVE_PORT            (daslang-live re-exports its
                                       resolved port into this env)
  4. get_das_root() /daslang-live.cfg.json  "port" key
  5. default 9090

C++ daslang-live binary parses --live-port pre-doubledash, writes the
resolved port back into DASLANG_LIVE_PORT, and keys acquire_single_instance()
on that port (Windows: daslang-live-single-instance-<port> mutex; POSIX:
/tmp/daslang-live-<port>.lock).

MCP do_live_launch now forwards its port arg to the spawned binary --
fixes the deferred-test root cause documented in the mouse-card
daslang-live-no-port-flag-single-instance-lock-e2e-spawn-tests-infeasible.md
(now marked RESOLVED).

Tests cover parse_port_string boundaries, --live-port and --live-port=N
forms, JSON config parsing, precedence chains, and the
live_api_set_port-after-init smoke. 78/78 pass interpreted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 19, 2026 05:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances daslang-live port configurability and enables multiple concurrent instances by keying the single-instance lock to the resolved REST API port, while keeping the C++ launcher and daScript live_api module in sync via DASLANG_LIVE_PORT.

Changes:

  • Added --live-port <N> handling in daslang-live, plus port-keyed single-instance lock and env handoff (DASLANG_LIVE_PORT).
  • Implemented a testable 5-level port resolution chain in live/live_api (argv/env/config/default + programmatic override).
  • Updated MCP live launch tooling to forward the requested port to the spawned daslang-live, and added dedicated tests + docs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
utils/mcp/tools/live.das Forwards --live-port to spawned daslang-live to align bind port with MCP polling port.
utils/daslang-live/main.cpp Adds --live-port parsing, resolves/export port via env, and keys single-instance lock by port.
modules/dasLiveHost/live/live_api.das Adds pure parsing/precedence helpers + init-time override resolution from argv/env/config.
tests/live_host/test_port_override.das New test suite covering parsing and precedence + setter-after-init behavior.
skills/daslang_live.md Documents precedence chain and port-keyed locking behavior.
mouse-data/docs/daslang-live-no-port-flag-single-instance-lock-e2e-spawn-tests-infeasible.md Marks prior limitation as resolved and preserves historical details.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread utils/daslang-live/main.cpp Outdated
Comment thread utils/daslang-live/main.cpp Outdated
Comment thread utils/mcp/tools/live.das
…nly guard

Per Copilot review on #2726:

- parse_port_strict() in daslang-live binary replaces atoi for both --live-port
  arg and DASLANG_LIVE_PORT env. atoi silently accepted '9090abc' -> 9090; the
  new parser rejects trailing garbage to match the .das side's try_to_int.
- Accept --live-port=N alongside --live-port N for parity with
  find_flag_raw_value on the .das side.
- do_live_launch now rejects non-digit / over-5-char port at entry so a
  caller-supplied port cannot smuggle quotes / spaces / metacharacters into
  the shell-ready argv string passed to system().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI darwin Release failure (run 26078968013): test_aot reported
error[50101] AOT link failed on every live_api function referenced
by tests/live_host/test_port_override.das (the first AOT test to
require live/live_api).

Root cause: live_api.das was missing from AOT_LIVE_HOST_MODULE_FILES
in tests/aot/CMakeLists.txt. Without an AOT-cpp build of live_api
itself, the test_port_override.das.cpp recorded hashes that disagreed
with what test_aot.exe computed at simulate time -> link miss on
live_api_set_port / parse_port_string / parse_argv_port / etc.

Pre-existing live_host tests only require live_host (the C++ DLL
bridge), so the gap was invisible until now.

Fix: add modules/dasLiveHost/live/live_api.das to the list, same
spot as live_api_builtins / live_api_stdio. Local reconfigure
generates test_aot_live_host_modules_live_api.das.cpp under
modules/dasLiveHost/live/_aot_generated/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

utils/mcp/tools/live.das:156

  • Now that live_api supports env/config-based port overrides, the MCP tool’s defaulting logic (empty(port) ? "9090" : port) can become incorrect: if a user relies on DASLANG_LIVE_PORT or daslang-live.cfg.json without passing an explicit port to MCP, do_live_launch/live_is_running will still poll 9090 and likely time out even though the server is up on the overridden port. Consider resolving the default port in the MCP tool from the same sources (env and/or config) when port is empty, so polling/launch align with the actual bind port.
    let p = empty(port) ? "9090" : port
    // Check if already running
    if (live_is_running(p)) {
        // Already running — get status to show useful info

Comment thread utils/mcp/tools/live.das Outdated
Comment thread utils/daslang-live/main.cpp Outdated
Comment thread skills/daslang_live.md Outdated
Comment thread modules/dasLiveHost/live/live_api.das
Comment thread utils/daslang-live/main.cpp Outdated
Per Boris's direction on PR #2726 R2: keep daslang-live stateless --
drop the env-var roundtrip and the config-file fallback. Both sides
(C++ daslang-live + .das live_api) now scan the same source (full argv,
including post-doubledash) for --live-port, so the lock key and the
HTTP bind cannot drift.

Correctness bugs fixed (each with a repro test per the
"correctness-bug -> repro test" rule):

- Thread 4: MCP do_live_launch's port_is_safe accepted "0", "65536",
  "99999" -- daslang-live would error after 10s of polling. Renamed to
  port_in_range and now actually parses + range-checks [1, 65535].
- Thread 5: C++ arg loop stopped at doubledash while .das scanned full
  argv, so `daslang-live foo.das -- --live-port 19090` would bind 19090
  on the .das side but key the C++ lock on 9090. New
  find_live_port_in_argv() scans the full argv. parse_argv_port test
  pins the post-doubledash case on the .das side.
- Thread 8: env-roundtrip masked the config-file fallthrough. Resolved
  by removing both -- no more env, no more config file.

Doc-only (Threads 6, 7): live_api_set_port "always wins after init" was
misleading. Server binds at LiveApiAgent constructor time and does not
rebind. Reworded the docstring on live_api_set_port and the [init]
function comment to say "before agent constructs".

RST audit (Boris's "MCP server and live documentation might be out of
sync" request):
- mcp.rst: project_root was referenced 18+ times in protocol.das but
  documented 0 times. Added a Common parameters subsection listing
  project + project_root once for all tools that accept them.
- mcp.rst: Live-reload control table rewritten with per-tool arg
  surface (file/project/project_root/port on live_launch; full on
  live_reload; paused on live_pause; name/args on live_command;
  commands on live_commands).
- daslang_live.rst: CLI reference filled in to match main.cpp
  (-project_root, -v1syntax, -track-allocations, -heap-report,
  --no-dyn-modules, --no-dump-leaks, --live-port, -h/--help).
- daslang_live.rst: single-instance lock note now mentions port-keying
  (daslang-live-single-instance-<port> / /tmp/daslang-live-<port>.lock).
- daslang_live.rst: REST API section documents the 3-level precedence
  chain (programmatic > argv > default) and the "before constructor"
  caveat on live_api_set_port.

skills/daslang_live.md updated to match the simplified chain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Comment thread utils/daslang-live/main.cpp Outdated
Comment thread utils/daslang-live/main.cpp Outdated
Comment thread skills/daslang_live.md
Per Copilot review on PR #2726 R3:

- Thread 9 (real correctness bug): C++ find_live_port_in_argv kept
  'last VALID', while .das clargs.find_flag_raw_value keeps 'last RAW
  (validated to 0 on bad input)'. A tail like '--live-port 19090
  --live-port abc' would have C++ key its lock on 19090 while .das
  defaulted to 9090 -- lock-vs-bind drift. Dropped the if(p>0) guard
  so the C++ scan is also last-occurrence-wins-unconditional. Repro
  test added per the correctness-bug rule: parse_argv_port treating
  invalid trailing values as 0.

- Thread 10 (doc fix): g_resolvedLivePort comment still mentioned a
  'DASLANG_LIVE_PORT env handoff' that was removed in cf58023.
  Reworded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@borisbat borisbat merged commit f0c826f into master May 19, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants