Skip to content

feat(console): console CLI runner + AgentsConsole session wiring (text mode)#1706

Open
toubatbrian wants to merge 1 commit into
brian/node-console-audiofrom
brian/node-console-cli
Open

feat(console): console CLI runner + AgentsConsole session wiring (text mode)#1706
toubatbrian wants to merge 1 commit into
brian/node-console-audiofrom
brian/node-console-cli

Conversation

@toubatbrian
Copy link
Copy Markdown
Contributor

Summary

Third and final PR in the series that ports the TCP console/session machinery from python livekit-agents so a local broker (e.g. the LiveKit CLI lk session daemon) can drive a Node agent over TCP. Stacked on #1694 (console audio IO).

This PR adds the glue that actually runs an agent in console mode:

  • agents/src/console.ts (runConsole) — an in-process runner that bypasses the websocket worker and ProcPool entirely. It loads the agent, opens a TcpSessionTransport to --connect-addr, sets up the AgentsConsole singleton, fabricates a fakeJob RunningJobInfo/JobContext, and runs the agent entrypoint on the current event loop. This mirrors python's _run_tcp_console, which relies on JobExecutorType.THREAD to keep the job in-process so the AgentsConsole singleton is shared with the agent's AgentSession. (JS has no in-process/THREAD executor and AgentServer requires WS credentials, so a self-contained runner is the clean equivalent.)
  • AgentsConsole singleton (voice/console_io.ts) — carries the console transport + audio bridges from the runner into the AgentSession. AgentSession._startImpl now acquires console IO and builds its SessionHost from the singleton when console mode is active, instead of the RoomIO path.
  • JobContext fake-job supportisFakeJob, plus no-op connect/deleteRoom/initRecording and a guarded _onSessionEnd (no cloud upload / URL parse) so a console job without a backing LiveKit room behaves correctly.
  • console CLI subcommandconsole --connect-addr <host:port> [--record].

Ships text mode (the agent is driven over the session transport: text in via runInput, events out). Audio-mode session wiring is intentionally a follow-up — the PR #1694 audio bridges are constructed and routed at the transport level, but not attached to the session pipeline yet.

Note: this cannot be verified end-to-end in this repo yet (the Go lk session spawner for Node is still a // TODO(node) placeholder), so verification is build/lint + targeted unit tests on the new pieces.

Test plan

  • pnpm build:agents green
  • ESLint + Prettier clean on changed files
  • New unit tests: AgentsConsole.acquireIo (text-mode IO unset, double-acquire guard, singleton); JobContext fake-job guards (isFakeJob, no-op connect/deleteRoom/initRecording)
  • No regression in agent_session / agent_activity / remote_session / console_io / job suites (23 + 10 passing)

Made with Cursor

Add the `console` CLI subcommand and an in-process console runner that
lets a local broker (e.g. the LiveKit CLI `lk session` daemon) drive a
Node agent over TCP. `runConsole` loads the agent, opens a
`TcpSessionTransport` to `--connect-addr`, sets up the `AgentsConsole`
singleton, and runs the agent entrypoint in-process (mirroring python's
`_run_tcp_console` / `JobExecutorType.THREAD`).

`AgentSession._startImpl` now wires its `SessionHost` from the
`AgentsConsole` singleton when console mode is active, and `JobContext`
gained fake-job support (`isFakeJob`, no-op connect/deleteRoom/recording)
so a console job without a backing LiveKit room behaves correctly.

Ships text-mode console; audio-mode session wiring is a follow-up.

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 3, 2026

🦋 Changeset detected

Latest commit: 80f07b4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 34 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80f07b4e63

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread agents/src/console.ts

const consoleInst = AgentsConsole.getInstance();
consoleInst.enabled = true;
consoleInst.record = record;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Wire the console record flag into session reporting

When running agents console --record, this assignment is the only place the flag is propagated, but AgentsConsole.record is never read elsewhere and the fake Job still leaves enableRecording at its default false. Since AgentSession.start() defaults record from ctx.job.enableRecording, the option advertised as saving a local session report has no effect for users who rely on the CLI flag.

Useful? React with 👍 / 👎.

Comment thread agents/src/console.ts
Comment on lines +141 to +142
await transport.close();
await room.disconnect();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Run job shutdown callbacks in console mode

The console runner mirrors the job lifecycle but exits the finally block without awaiting ctx.shutdownCallbacks. Normal jobs execute these callbacks after _onSessionEnd() in ipc/job_proc_lazy_main.ts; console agents or SDK helpers that call JobContext.addShutdownCallback() will therefore leak resources or leave background work running on SIGINT/SIGTERM instead of being closed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment thread agents/src/console.ts
}

async function loadAgent(agentPath: string): Promise<Agent> {
const module = await import(pathToFileURL(agentPath).pathname);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Dynamic import uses URL-encoded pathname instead of full file URL, breaking paths with spaces

loadAgent at agents/src/console.ts:31 uses pathToFileURL(agentPath).pathname which yields a URL-encoded path like /home/user/my%20agent/file.ts. Node.js import() treats bare absolute paths as literal file paths (no percent-decoding), so it would look for a file with %20 in the name rather than a space. The correct approach is pathToFileURL(agentPath).href which gives file:///home/user/my%20agent/file.ts — Node.js recognizes the file:// protocol and correctly decodes percent-encoded characters. Note that agents/src/download.ts:148 correctly uses .href, while agents/src/ipc/job_proc_lazy_main.ts:207 has the same pre-existing bug.

Suggested change
const module = await import(pathToFileURL(agentPath).pathname);
const module = await import(pathToFileURL(agentPath).href);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

1 participant