Skip to content

refactor(agent): extract reusable core into importable agentd package#6431

Merged
gustavosbarreto merged 1 commit into
masterfrom
feature/agent-embeddable-package
Jun 5, 2026
Merged

refactor(agent): extract reusable core into importable agentd package#6431
gustavosbarreto merged 1 commit into
masterfrom
feature/agent-embeddable-package

Conversation

@gustavosbarreto
Copy link
Copy Markdown
Member

What

Extracts the agent's core (lifecycle, modes, handlers) out of package main into a new importable library, agent/pkg/agentd, so the agent can run in-process inside another Go program. The agent/ binary stays a thin package main CLI that imports the new package.

Why

The Agent type and its lifecycle lived in package main, which Go cannot import. ShellHub Desktop (Wails v3) needs to embed the agent in-process to register the host as a device, rather than shelling out to a subprocess.

Closes #6430

Changes

  • agent/pkg/agentd (new): moved agent.go, modes.go, handlers.go, and their test out of package main (via git mv, history preserved) and re-declared them as package agentd. Public API is unchanged: NewAgentWithConfig, Initialize, Listen, Close, Config, Mode, HostMode, ConnectorMode, GetInfo, LoadConfigFromEnv.
  • Version/Platform via Config: a library can't read another package's main globals, so the core no longer references AgentVersion/AgentPlatform. Added Config.Version and Config.Platform; the CLI injects them from its ldflags globals. The build invocation is unchanged — the ldflags target is still main.AgentVersion. Self-update stays entirely in the CLI loop, so an embedded agent never self-updates.
  • Injectable SFTP command: the host SFTP server starts by re-executing /proc/self/exe sftp — which breaks when embedded, since that path is the host program, not the agent. Added Config.SFTPServerCommand func() *exec.Cmd, threaded through host.NewSessioner; when nil (the CLI path) behavior is identical to before. Embedders point it at a binary that runs the SFTP server.
  • os.Exit confinement: all os.Exit calls remain in package main (main.go, sftp.go); the library has none, so embedding it cannot abort the host process.

Testing

A consumer embedding this package must replicate the agent module's replace github.com/gliderlabs/ssh => github.com/shellhub-io/ssh directive in its own go.mod — Go ignores replace directives from non-main modules. This is documented in the agentd package comment.

Verify the CLI path is unchanged: build with -ldflags "-X main.AgentVersion=<v>" and confirm agent --version reports it and a host-mode device still serves SSH and SFTP. Both the native and docker build tags compile; go vet and the agentd tests pass.

Move the agent's core (lifecycle, modes, handlers) out of package main
into a new importable library, agent/pkg/agentd, so the agent can run
in-process inside another Go program. The agent/ binary stays a thin
package main CLI that imports the new package.

The Agent type lived in package main, which Go cannot import. ShellHub
Desktop (Wails v3) needs to embed the agent in-process to register the
host as a device instead of shelling out to a subprocess.

Changes that make embedding work, not just compile:

- Version/Platform via Config: a library can't read another package's
  main globals, so the core no longer references AgentVersion or
  AgentPlatform. Added Config.Version and Config.Platform; the CLI
  injects them from its ldflags globals. The build is unchanged: the
  ldflags target is still main.AgentVersion. Self-update stays in the
  CLI loop, so an embedded agent never self-updates.

- Injectable SFTP command: the host SFTP server starts by re-executing
  /proc/self/exe, which is the host program when embedded, not the
  agent. Added Config.SFTPServerCommand, threaded through
  host.NewSessioner; when nil (the CLI path) behavior is unchanged.

- os.Exit confinement: all os.Exit calls remain in package main, so
  embedding the library cannot abort the host process.

Public API (NewAgentWithConfig, Initialize, Listen, Close, Config,
Mode, HostMode, ConnectorMode, GetInfo, LoadConfigFromEnv) is preserved;
no in-repo consumer imported the old package.

Fixes: #6430
@gustavosbarreto gustavosbarreto requested a review from a team as a code owner June 5, 2026 15:39
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 5, 2026

Claude finished @gustavosbarreto's task in 16m 31s —— View job


Code Review Complete

Reviewed 7 files across code quality, security, testing, Go patterns, and architecture — no issues found. The code looks good as-is.

The refactoring is mechanically clean: all AgentVersion/AgentPlatform global references are consistently replaced with config.Version/config.Platform, the NewSessioner signature extension is properly propagated to all call sites, the SFTPServerCommand nil-fallback logic is correct, and the test was properly updated to use config-based injection instead of global mutation. No cross-repo impact in cloud/.


To request another review round, comment /review.

@gustavosbarreto gustavosbarreto merged commit 882918b into master Jun 5, 2026
16 checks passed
@gustavosbarreto gustavosbarreto deleted the feature/agent-embeddable-package branch June 5, 2026 16:00
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.

agent: make the Agent a reusable, importable package (extract CLI from package main)

1 participant