Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ build/
release/
release-mock/
.t3
temp/
.idea/
apps/web/.playwright
apps/web/playwright-report
Expand Down
44 changes: 43 additions & 1 deletion apps/mobile/src/components/ProviderIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useColorScheme } from "react-native";
import { Path, Svg } from "react-native-svg";
import { Path, Rect, Svg } from "react-native-svg";

type ProviderIconProps = {
readonly provider: string | null | undefined;
Expand Down Expand Up @@ -58,6 +58,48 @@ export function ProviderIcon(props: ProviderIconProps) {
);
}

if (props.provider === "piAgent") {
return (
<Svg width={size} height={size} viewBox="0 0 800 800" fill="none">
<Rect width="800" height="800" rx="160" fill="#000" />
<Path
fill="#fff"
fillRule="evenodd"
d="M165.29 165.29H517.36V400H400V517.36H282.65V634.72H165.29ZM282.65 282.65V400H400V282.65Z"
/>
<Path fill="#fff" d="M517.36 400H634.72V634.72H517.36Z" />
</Svg>
);
}

if (props.provider === "hermes") {
return (
<Svg width={size} height={size} viewBox="0 0 800 800" fill="none">
<Rect width="800" height="800" rx="160" fill="#000" />
<Path
fill="#fff"
fillRule="evenodd"
d="M150 520 L420 250 L470 300 L200 570 Z M230 620 L500 350 L550 400 L280 670 Z M310 720 L580 450 L630 500 L360 770 Z"
/>
<Path fill="#fff" d="M585 200 L625 200 L625 640 L585 640 Z" />
<Path fill="#fff" d="M605 125 m-45 0 a45 45 0 1 0 90 0 a45 45 0 1 0 -90 0" />
</Svg>
);
}

if (props.provider === "openclaw") {
return (
<Svg width={size} height={size} viewBox="0 0 800 800" fill="none">
<Rect width="800" height="800" rx="160" fill="#000" />
<Path
fill="#fff"
fillRule="evenodd"
d="M400 130 C549 130 670 251 670 400 L520 400 C520 334 466 280 400 280 C334 280 280 334 280 400 C280 466 334 520 400 520 L400 670 C251 670 130 549 130 400 C130 251 251 130 400 130 Z M460 150 L650 340 L590 400 L460 270 Z"
/>
</Svg>
);
}

// codex (and unknown drivers)
return (
<Svg width={size} height={size} viewBox="0 0 256 260" fill="none">
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/src/lib/modelOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ function providerDisplayLabel(provider: {
if (provider.displayName) return provider.displayName;
if (provider.driver === "codex") return "Codex";
if (provider.driver === "claudeAgent") return "Claude";
if (provider.driver === "piAgent") return "Pi";
if (provider.driver === "hermes") return "Hermes";
if (provider.driver === "openclaw") return "OpenClaw";
return provider.instanceId;
}

Expand Down
68 changes: 68 additions & 0 deletions apps/server/scripts/openclaw-mock-gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node
// @effect-diagnostics nodeBuiltinImport:off
//
// openclaw-mock-gateway.ts — a scripted stand-in for the `openclaw gateway`
// binary used by the OpenClaw runtime tests. The real OpenClaw gateway is not
// installed in CI or on contributor machines, so the spawn-path tests launch
// this script through a tiny shell wrapper and drive it through the same
// WebSocket protocol the runtime speaks to a real gateway.
//
// It accepts the real CLI shape (`gateway --port <port> --allow-unconfigured`)
// and honors `OPENCLAW_GATEWAY_TOKEN`, delegating the protocol handling to
// {@link ../src/provider/testUtils/openclawMockGateway}.
//
// Behavior is selected with T3_OPENCLAW_* environment variables; unset flags
// produce the happy-path gateway.

import * as NodePath from "node:path";
import * as NodeURL from "node:url";

const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));

async function main(): Promise<void> {
const args = process.argv.slice(2);
const portIndex = args.indexOf("--port");
let port = 18_789;
if (portIndex >= 0 && args[portIndex + 1]) {
port = Number(args[portIndex + 1]);
}
const token = process.env.OPENCLAW_GATEWAY_TOKEN;

// Resolve the testUtils module relative to this script. When spawned via
// the tsx/vite transform the import graph resolves through the server src
// tree; fall back to a direct file URL when the package mapping is absent.
let startMock: typeof import("../src/provider/testUtils/openclawMockGateway.ts").startMockOpenClawGateway;
try {
({ startMockOpenClawGateway: startMock } =
await import("../src/provider/testUtils/openclawMockGateway.ts"));
} catch {
({ startMockOpenClawGateway: startMock } = await import(
NodeURL.pathToFileURL(
NodePath.join(__dirname, "..", "src", "provider", "testUtils", "openclawMockGateway.ts"),
).href
));
}

const handle = await startMock({
port,
...(token !== undefined ? { token } : {}),
serverVersion: "2026.8.1-mock",
emitThinking: process.env.T3_OPENCLAW_EMIT_THINKING === "1",
emitToolEvents: process.env.T3_OPENCLAW_EMIT_TOOL_EVENTS === "1",
emitApproval: process.env.T3_OPENCLAW_EMIT_APPROVAL === "1",
});
// The gateway listens on the requested port; the mock bound to an ephemeral
// port, so log the real one on a line the runtime can parse if needed.
console.log(`mock openclaw gateway listening on ${handle.port}`);
const onSignal = () => {
void handle.close().then(() => process.exit(0));
};
process.on("SIGTERM", onSignal);
process.on("SIGINT", onSignal);
process.on("SIGHUP", onSignal);
}

main().catch((error) => {
console.error(`mock openclaw gateway error: ${String(error)}`);
process.exit(1);
});
Loading
Loading