Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/agentos-actor-plugin/src/actions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ fn reply_err(host: &HostCtx, token: u64, error: anyhow::Error) {
pub(crate) async fn dispatch(
host: &HostCtx,
vm: &AgentOs,
config: &crate::config::AgentOsConfigJson,
vars: &mut Vars,
name: &str,
args: &[u8],
Expand Down Expand Up @@ -456,6 +457,23 @@ pub(crate) async fn dispatch(
}
Err(error) => reply_err(host, token, error),
},
// Config introspection: echo the actor's declarative mount / software
// config (no VM round-trip — the kernel has no runtime mount table and
// software is the requested bundle expanded TS-side in buildConfigJson).
"listMounts" => reply_ok(host, token, &config.list_mounts()),
"listSoftware" => {
// Config carries package/kind/version; the command names each
// wasm-commands package ships come from the live VM (host package
// dirs), zipped in here by package name.
let mut list = config.list_software();
let commands: HashMap<String, Vec<String>> = vm.provided_commands().into_iter().collect();
for dto in &mut list {
if let Some(cmds) = commands.get(&dto.package) {
dto.commands = cmds.clone();
}
}
reply_ok(host, token, &list);
}
other => {
host.reply_err(
token,
Expand Down
63 changes: 63 additions & 0 deletions crates/agentos-actor-plugin/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,36 @@ struct SidecarJson {
pool: Option<String>,
}

/// Reply DTO for the `listMounts` action: one configured mount, flattened so the
/// UI gets `path` / `kind` (the native plugin id, e.g. `host_dir`, `s3`,
/// `google_drive`, `sandbox_agent`) / `config` (provider-specific detail) /
/// `readOnly`. This echoes the actor's declarative mount config — the kernel
/// has no runtime mount table to enumerate.
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MountInfoDto {
pub path: String,
pub kind: String,
pub config: Option<serde_json::Value>,
pub read_only: bool,
}

/// Reply DTO for the `listSoftware` action: one configured software package.
/// `kind` is the kebab-case [`SoftwareKind`] tag (`wasm-commands` / `agent` /
/// `tool`). Reflects the requested `software` bundle (the default `common`
/// bundle is already expanded into the envelope TS-side in `buildConfigJson`).
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SoftwareInfoDto {
pub package: String,
pub kind: String,
pub version: Option<String>,
/// Command names this package ships (wasm-commands packages only; empty for
/// agent/tool). Filled from the live VM in the `listSoftware` dispatch arm,
/// not derivable from config alone. See `AgentOs::provided_commands`.
pub commands: Vec<String>,
}

impl AgentOsConfigJson {
/// Parse a `config_json` envelope. An empty/whitespace string is treated as
/// the default config (the client supplied no overrides).
Expand Down Expand Up @@ -144,4 +174,37 @@ impl AgentOsConfigJson {
..AgentOsConfig::default()
}
}

/// Configured mounts, flattened for the `listMounts` action. `kind` is the
/// native plugin id and `config` its provider-specific detail.
pub(crate) fn list_mounts(&self) -> Vec<MountInfoDto> {
self.mounts
.iter()
.map(|mount| MountInfoDto {
path: mount.path.clone(),
kind: mount.plugin.id.clone(),
config: mount.plugin.config.clone(),
read_only: mount.read_only,
})
.collect()
}

/// Configured software packages, for the `listSoftware` action. `kind` is the
/// kebab-case [`SoftwareKind`] serde tag (`wasm-commands` / `agent` / `tool`).
pub(crate) fn list_software(&self) -> Vec<SoftwareInfoDto> {
self.software
.iter()
.map(|software| SoftwareInfoDto {
package: software.package.clone(),
kind: serde_json::to_value(software.kind)
.ok()
.and_then(|value| value.as_str().map(str::to_owned))
.unwrap_or_else(|| "wasm-commands".to_owned()),
version: software.version.clone(),
// Filled from the live VM in the dispatch arm (config alone does
// not carry the packages' command binaries).
commands: Vec::new(),
})
.collect()
}
}
12 changes: 10 additions & 2 deletions crates/agentos-actor-plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,16 @@ async fn actor_worker(
match abi::decode_action_payload(&payload) {
Ok((name, action_args)) => {
tracing::debug!(action = %name, "agent-os action start");
actions::dispatch(&host, vm_ref, &mut vars, &name, &action_args, token)
.await;
actions::dispatch(
&host,
vm_ref,
&config,
&mut vars,
&name,
&action_args,
token,
)
.await;
tracing::debug!(action = %name, "agent-os action done");
}
Err(_) => {
Expand Down
42 changes: 42 additions & 0 deletions crates/client/src/agent_os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,48 @@ impl AgentOs {
pub fn sidecar(&self) -> Arc<AgentOsSidecar> {
self.inner.sidecar.clone()
}

/// The commands each configured `wasm-commands` package *ships*, keyed by
/// the package string used in the config — i.e. the wasm binaries in the
/// package's resolved command directory (`entry.descriptor.root`, which is
/// exactly what `build_command_mounts` mounts at `/__secure_exec/commands/
/// {index}/`), with `_stubs`/dotfiles filtered, sorted. Non-`wasm-commands`
/// packages contribute nothing.
///
/// WORKAROUND: agent-os owns command *provisioning* (it resolves each
/// package and mounts its dir), so it knows the host dirs and can read them
/// here. But the authoritative *resolved* command set — deduping when two
/// packages provide the same command, priority order, and which files are
/// actually executable — is owned by secure-exec's command discovery
/// (`discover_command_guest_paths`). This reads the raw package contents and
/// re-derives a slice of that. TODO: replace with a secure-exec API that
/// reports discovered commands per package instead of us re-reading dirs.
pub fn provided_commands(&self) -> Vec<(String, Vec<String>)> {
let resolved = match resolve_software(&self.inner.config) {
Ok(resolved) => resolved,
Err(_) => return Vec::new(),
};
resolved
.into_iter()
.filter(|entry| entry.kind == SoftwareKind::WasmCommands)
.map(|entry| {
// `root` IS the command directory (mounted verbatim as the
// guest command dir), so the command binaries live directly in
// it — do not descend into a `wasm/` subdir.
let mut commands: Vec<String> = std::fs::read_dir(&entry.descriptor.root)
.into_iter()
.flatten()
.flatten()
.filter_map(|dirent| dirent.file_name().into_string().ok())
// `_stubs` is a secure-exec support entry, not a command;
// dotfiles are metadata. Everything else is a command name.
.filter(|name| !name.starts_with('_') && !name.starts_with('.'))
.collect();
commands.sort();
(entry.descriptor.package_name, commands)
})
.collect()
}
}

/// Abort and clear a single tracked background-task handle (e.g. the ACP event pump) so it cannot
Expand Down
1 change: 1 addition & 0 deletions packages/agentos/.claude/scheduled_tasks.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"sessionId":"bb55ab39-b384-49a3-923e-20bf190e8d86","pid":2222620,"procStart":"42140112","acquiredAt":1782774961870}
22 changes: 22 additions & 0 deletions packages/agentos/src/actor-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,24 @@ export interface ReadFileResult {
error?: string;
}

/** One configured mount, returned by `listMounts`. Mirrors `MountInfoDto`. */
export interface MountInfo {
path: string;
/** Native mount plugin id. */
kind: "host_dir" | "s3" | "google_drive" | "sandbox_agent";
/** Provider-specific config detail (null when the plugin carries none). */
config: unknown;
readOnly: boolean;
}

/** One configured software package, returned by `listSoftware`. Mirrors `SoftwareInfoDto`. */
export interface SoftwareInfo {
package: string;
/** Kebab-case `SoftwareKind` tag. */
kind: "wasm-commands" | "agent" | "tool";
version: string | null;
}

/**
* The agentOS VM actor's action map. Keep one method per Rust `dispatch` arm.
*
Expand Down Expand Up @@ -188,4 +206,8 @@ export type AgentOsActions = {
// ── Preview URLs ──────────────────────────────────────────────────
createSignedPreviewUrl: (c: Ctx, port: number, ttlSeconds: number) => Promise<SignedPreviewUrl>;
expireSignedPreviewUrl: (c: Ctx, token: string) => Promise<void>;

// ── Config introspection ──────────────────────────────────────────
listMounts: (c: Ctx) => Promise<MountInfo[]>;
listSoftware: (c: Ctx) => Promise<SoftwareInfo[]>;
}
Loading