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
28 changes: 26 additions & 2 deletions e2e/setup-dashboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,22 @@ async function mockApis(page: import("@playwright/test").Page) {
}
});

await page.route(
(url) => url.href.startsWith(`${PENGINE_API_BASE}/v1/toolengine/runtime`),
async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
available: true,
kind: "podman",
version: "5.0.0",
rootless: true,
}),
});
},
);

await page.route(
(url) => url.href.startsWith(`${PENGINE_API_BASE}/v1/mcp/servers`),
async (route) => {
Expand Down Expand Up @@ -179,14 +195,22 @@ test.describe("setup to dashboard flow", () => {
await expect(page.getByText("Ready to continue.")).toBeVisible();
await page.getByRole("button", { name: "Continue" }).click();

// Step 3: Pengine local (health check auto-passes via mock)
// Step 3: Container runtime (Podman/Docker — mocked as available)
await expect(
page.getByRole("heading", { name: "Install a container runtime", exact: true }),
).toBeVisible();
await expect(page.getByText("Container runtime detected:")).toBeVisible();
await expect(page.getByText("Ready to continue.")).toBeVisible();
await page.getByRole("button", { name: "Continue" }).click();

// Step 4: Pengine local (health check auto-passes via mock)
await expect(
page.getByRole("heading", { name: "Start Pengine locally", exact: true }),
).toBeVisible();
await expect(page.getByText("Pengine is running on localhost.")).toBeVisible();
await page.getByRole("button", { name: "Continue" }).click();

// Step 4: Connect
// Step 5: Connect
await expect(
page.getByRole("heading", { name: "Connect bot to Pengine", exact: true }),
).toBeVisible();
Expand Down
3 changes: 3 additions & 0 deletions src-tauri/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

# Local npm install for file-manager image build
src/modules/tool_engine/container/file-manager/node_modules/
11 changes: 1 addition & 10 deletions src-tauri/mcp.example.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
{
"workspace_roots": ["/absolute/path/to/your/project"],
"servers": {
"dice": {
"type": "native",
"id": "dice"
},
"filesystem": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/absolute/path/to/allowed/folder"
],
"env": {}
}
}
}
27 changes: 15 additions & 12 deletions src-tauri/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,25 @@ pub fn run() {

app.manage(shared_state.clone());

// Load MCP before any bot work so the first Telegram message never sees an empty registry.
// Connect MCP stdio servers in the background so window + HTTP API are not blocked by
// slow starters (Podman containers, `npx`, etc.). The registry stays empty until connect
// finishes; early Telegram turns simply omit tools until then.
let mcp_path = shared_state.mcp_config_path.clone();
let mcp_state = shared_state.clone();
tauri::async_runtime::block_on(async move {
tauri::async_runtime::spawn(async move {
mcp_state
.emit_log("mcp", &format!("loading {}", mcp_path.display()))
.emit_log(
"mcp",
&format!("connecting servers in background ({})", mcp_path.display()),
)
.await;
match mcp_service::load_or_init_config(&mcp_path) {
Ok(cfg) => {
mcp_service::rebuild_registry_into_state(&mcp_state, &cfg).await;
}
Err(e) => {
mcp_state
.emit_log("mcp", &format!("mcp.json error: {e}"))
.await;
}
if let Err(e) = mcp_service::rebuild_registry_into_state(&mcp_state).await {
mcp_state
.emit_log(
"mcp",
&format!("ERROR: MCP registry rebuild failed on startup: {e}"),
)
.await;
}
});

Expand Down
Loading