From 7a0a1d45d1a7d45eb7738431d15704992f52531f Mon Sep 17 00:00:00 2001 From: Jason Lernerman Date: Thu, 15 Oct 2026 19:31:07 -0400 Subject: [PATCH 1/3] refactor(agent): take the minimum @livekit/agents version as a parameter --- cmd/lk/simulate_subprocess.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/lk/simulate_subprocess.go b/cmd/lk/simulate_subprocess.go index b8d74ba8..b795c4f3 100644 --- a/cmd/lk/simulate_subprocess.go +++ b/cmd/lk/simulate_subprocess.go @@ -100,10 +100,10 @@ func checkTypeStrippingSupport(dir, nodeBin string) error { // equivalent floor from server client settings. const nodeAgentMinVersion = "1.0.0" -// checkNodeSDKVersion gates a Node agent on nodeAgentMinVersion, resolving the +// checkNodeSDKVersion gates a Node agent on minVersion, resolving the // installed @livekit/agents via the project's own runtime (see // agentfs.ResolveInstalledSDKVersion). -func checkNodeSDKVersion(cfg AgentStartConfig) error { +func checkNodeSDKVersion(cfg AgentStartConfig, minVersion string) error { // Surface a missing Node binary as its own error rather than folding it // into "package not found". if _, err := agentfs.FindNodeBinary(); err != nil { @@ -114,8 +114,8 @@ func checkNodeSDKVersion(cfg AgentStartConfig) error { return fmt.Errorf("@livekit/agents not found; install dependencies and make sure this is a LiveKit agent project") } // An unparseable version (e.g. a local "0.0.0-dev" tag) shouldn't block a run. - if ok, err := agentfs.IsVersionSatisfied(version, nodeAgentMinVersion); err == nil && !ok { - return fmt.Errorf("@livekit/agents version %s is too old, please upgrade to %s or newer", version, nodeAgentMinVersion) + if ok, err := agentfs.IsVersionSatisfied(version, minVersion); err == nil && !ok { + return fmt.Errorf("@livekit/agents version %s is too old, please upgrade to %s or newer", version, minVersion) } return nil } @@ -295,7 +295,7 @@ func startAgent(cfg AgentStartConfig) (*AgentProcess, error) { // Node resolves the installed package via the runtime so workspace/monorepo // layouts work; Python parses project files against the thin-CLI baseline. if cfg.ProjectType.IsNode() { - if err := checkNodeSDKVersion(cfg); err != nil { + if err := checkNodeSDKVersion(cfg, nodeAgentMinVersion); err != nil { return nil, err } } else if err := checkPythonSDKVersion(cfg); err != nil { From 3868f4e77e7417b27cbc01a25007964505f585a2 Mon Sep 17 00:00:00 2001 From: Jason Lernerman Date: Thu, 15 Oct 2026 19:31:25 -0400 Subject: [PATCH 2/3] fix(simulate): require @livekit/agents 1.4.11 for Node agents Earlier releases pass the 1.0.0 floor but reject --simulation, so the agent exits before registering with "unknown option '--simulation'". --- cmd/lk/simulate.go | 10 ++++++++-- cmd/lk/simulate_subprocess.go | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/cmd/lk/simulate.go b/cmd/lk/simulate.go index 37e52cbe..f42781f1 100644 --- a/cmd/lk/simulate.go +++ b/cmd/lk/simulate.go @@ -744,7 +744,7 @@ func startSimulationAgent(c *simulateConfig, forwardOutput io.Writer) (*AgentPro args = append(args, "--log-format", "colored") } - return startAgent(AgentStartConfig{ + cfg := AgentStartConfig{ Dir: c.projectDir, Entrypoint: c.entrypoint, ProjectType: c.projectType, @@ -766,7 +766,13 @@ func startSimulationAgent(c *simulateConfig, forwardOutput io.Writer) (*AgentPro }, ReadySignal: "registered worker", ForwardOutput: forwardOutput, - }) + } + if c.projectType.IsNode() { + if err := checkNodeSDKVersion(cfg, nodeSimulateMinVersion); err != nil { + return nil, err + } + } + return startAgent(cfg) } // ciFromEnv reports the pipeline that started this run, so the dashboard can diff --git a/cmd/lk/simulate_subprocess.go b/cmd/lk/simulate_subprocess.go index b795c4f3..b5ea9f25 100644 --- a/cmd/lk/simulate_subprocess.go +++ b/cmd/lk/simulate_subprocess.go @@ -100,6 +100,10 @@ func checkTypeStrippingSupport(dir, nodeBin string) error { // equivalent floor from server client settings. const nodeAgentMinVersion = "1.0.0" +// nodeSimulateMinVersion is the first @livekit/agents release with the +// --simulation flag and LIVEKIT_AGENT_NAME_OVERRIDE, which simulate relies on. +const nodeSimulateMinVersion = "1.4.11" + // checkNodeSDKVersion gates a Node agent on minVersion, resolving the // installed @livekit/agents via the project's own runtime (see // agentfs.ResolveInstalledSDKVersion). From 890ccbf30f079152c0e9e32b24cf1d454d5e58df Mon Sep 17 00:00:00 2001 From: Jason Lernerman Date: Thu, 15 Oct 2026 19:34:02 -0400 Subject: [PATCH 3/3] fix(simulate): detect a crashing Node agent from its log The fatal markers only matched Python log lines, so a Node agent whose entrypoint threw surfaced only as "No agent joined room" timeouts. --- cmd/lk/simulate_health.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/lk/simulate_health.go b/cmd/lk/simulate_health.go index 197a9f8e..a9f2e807 100644 --- a/cmd/lk/simulate_health.go +++ b/cmd/lk/simulate_health.go @@ -25,12 +25,13 @@ import ( // can cancel the run and surface the worker's error, while tolerating // transient "no agent joined" timeouts from connection pacing. -// pythonFatalMarkers are livekit-agents (Python) log lines that only appear on -// a fatal, non-transient worker error. The JS framework will need its own set. -var pythonFatalMarkers = []string{ +// fatalMarkers are livekit-agents (Python) and @livekit/agents (Node) log lines +// that only appear on a fatal, non-transient worker error. +var fatalMarkers = []string{ "unhandled exception while running the job task", "error initializing process", "closing due to unrecoverable error", + "error in entry function", } // a single fatal log line can be an isolated job crash @@ -101,7 +102,7 @@ func countFatalMarkers(logs []string) int { func isFatalMarker(line string) bool { plain := ansiEscapeRe.ReplaceAllString(line, "") - for _, marker := range pythonFatalMarkers { + for _, marker := range fatalMarkers { if strings.Contains(plain, marker) { return true }