Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
60c8118
feat(mesh): Phase 2 — provider-agnostic IMeshTransport + runtime swap
May 9, 2026
7e908bb
fix(ci): pre-build mesh-plugin for runtime CI + format reconciler
May 10, 2026
31efddd
fix(ci): quote workflow step name containing colon
May 10, 2026
44afb7e
fix(controller): clippy fixes for Rust 1.95.0
May 10, 2026
443c930
docs(agt): full patch-by-patch audit + adapter-side fixes for #7/#12
May 10, 2026
a50dd03
docs(agt): reframe audit for upstream-AGT scenario, drop invalid Gap G5
May 10, 2026
a66e545
docs(agt): mark gaps G1 and G2 fixed on local AGT branch
May 10, 2026
69f6320
docs(agt): complete patch-by-patch audit with gaps G3, G4, G5 fixed l…
May 10, 2026
15f3d67
dev: add --mesh-provider <vendored|agt> selection with first-run prompt
May 10, 2026
ba17d3d
fix(sandbox): copy mesh-plugin into cli-builder so @azureclaw/mesh re…
May 10, 2026
495b0bf
feat(mesh-plugin): collapse agt-transport onto upstream MeshClient re…
May 10, 2026
1233463
feat(runtime): mesh-registry abstraction + migrate raw-HTTP callsites
May 10, 2026
682cbfc
fix(mesh): wire AGT provider end-to-end (6 stackup bugs)
May 10, 2026
b281a3b
fix(runtime): always route mesh registry through inference-router
May 10, 2026
2e251b1
fix(agt): break mesh_send infinite poll loop on dead sub-agent
May 10, 2026
406f5b0
fix(agt): suppress /v1/registry/* 404 leaks in AGT mode
May 11, 2026
85225cf
fix(agt): use /v1/agents/{did} for reputation lookup in AGT mode
May 11, 2026
d66df4e
fix(mesh): auto-tick AGT MeshClient sendHeartbeat every 30s
May 11, 2026
b18920b
runtime: hide Foundry tools in github-copilot mode (same as github-mo…
May 11, 2026
1fb2a87
feat(push): --mesh-provider=agt builds AGT relay/registry + swaps man…
May 11, 2026
3cbacab
feat(mesh): add 'azureclaw mesh provider <vendored|agt>' live switch
May 11, 2026
7ceb8c3
feat(up): --mesh-provider=agt picks AGT manifest + flips helm value
May 11, 2026
fae1eb7
feat(dev): plumb --mesh-provider into local-k8s helm install
May 11, 2026
1f97cd9
feat(controller): AGT wire protocol adapter for mesh_peer
May 11, 2026
02bca37
fix(ci): rustfmt + mesh-plugin fake-client establishSessionWithPeer
May 11, 2026
6139a2f
fix(deploy): AGT mesh probe path + Cilium pod-port NP allow
May 11, 2026
8034462
fix(mesh promote): AGT-compat health + WS upgrade paths
May 11, 2026
0dbd46c
feat(dev): first-run picker for local vs remote mesh source
May 11, 2026
701079e
fix(controller): propagate AZURECLAW_MESH_PROVIDER to router container
May 11, 2026
2f4d522
fix(agt): resolve 'parent' alias for spawned sub-agents on AGT mesh
May 11, 2026
aa7d28e
fix(mesh-plugin): drop bogus establishSessionWithPeer() pre-bootstrap
May 11, 2026
ce772a2
Revert 'drop establishSessionWithPeer pre-bootstrap' — was correct call
May 11, 2026
249560d
push: auto-detect mesh provider from live helm release
May 11, 2026
01dad5c
entrypoint: fail-open trust gate when running anonymous tier
May 11, 2026
b2ca41e
fix(runtime): restore foundry_* dispatcher branch in sub-agent task loop
May 11, 2026
b839d5c
fix(agt-mesh): ping registry /heartbeat every 30s to stay discoverable
May 11, 2026
1a2c487
feat(strict-tools): opt-in OpenAI strict-mode + file-first transport …
May 11, 2026
a31c9fd
fix(mesh-plugin): drop toAmid from establishSessionWithPeer error log
May 11, 2026
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
2 changes: 2 additions & 0 deletions .agt-sdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.tgz
*.tar.gz
Empty file added .agt-sdk/.keep
Empty file.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ jobs:
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "22"
- name: "Build mesh-plugin (file: dep of runtime)"
working-directory: mesh-plugin
run: |
cd ../vendor/agentmesh-sdk && npm install --ignore-scripts
cd ../../mesh-plugin && npm install && npm run build
- run: npm install
- run: npm run typecheck
- run: npm run lint
Expand Down
473 changes: 396 additions & 77 deletions cli/src/commands/dev.ts

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion cli/src/commands/dev/local-k8s.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ export interface LocalK8sOptions {
* common first-run prompt.
*/
forceRebuild?: boolean;
/**
* Mesh stack to deploy in the local kind cluster. 'vendored' (default)
* uses the Rust relay + Postgres registry; 'agt' uses the Microsoft
* AGT Python relay. The chosen value is forwarded to the helm chart
* via --set mesh.provider= so the controller spawns sandboxes with
* the matching AZURECLAW_MESH_PROVIDER env var.
*/
meshProvider?: "vendored" | "agt";
}

/**
Expand Down Expand Up @@ -490,6 +498,7 @@ async function helmInstall(
release: string,
chartDir: string,
valuesOverlays: string[],
setArgs: string[] = [],
): Promise<void> {
// We render-then-apply (rather than `helm install`) to keep failures
// visible: `kubectl apply -f -` shows precisely which resources didn't
Expand All @@ -506,6 +515,9 @@ async function helmInstall(
for (const overlay of valuesOverlays) {
args.push("-f", overlay);
}
for (const kv of setArgs) {
args.push("--set", kv);
}
const { stdout } = await execa(helm, args);
await execa(
kubectl,
Expand Down Expand Up @@ -796,10 +808,11 @@ export async function runLocalK8s(opts: LocalK8sOptions): Promise<void> {
// rollout (no second restart needed).
const credsOverlay = await provisionDevCreds(tools.kubectl, creds);
try {
const meshProvider = opts.meshProvider ?? "vendored";
await helmInstall(tools.helm, tools.kubectl, opts.name, chartDir, [
valuesOverlay,
credsOverlay,
]);
], [`mesh.provider=${meshProvider}`]);
} finally {
// The overlay only references the API key by name (secretKeyRef);
// the file itself contains no secret material, but we still clean up
Expand Down
6 changes: 6 additions & 0 deletions cli/src/commands/mesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
import { attachAuthSubcommand } from "./mesh/auth.js";
import { attachPromoteSubcommand } from "./mesh/promote.js";
import { attachSetupTrustSubcommand } from "./mesh/setup-trust.js";
import { attachProviderSubcommand } from "./mesh/provider.js";

export function meshCommand(): Command {
const cmd = new Command("mesh");
Expand All @@ -52,6 +53,11 @@ export function meshCommand(): Command {
// -----------------------------------------------------------------------
attachSetupTrustSubcommand(cmd);

// -----------------------------------------------------------------------
// mesh provider — switch a live cluster between vendored ↔ AGT
// -----------------------------------------------------------------------
attachProviderSubcommand(cmd);

// -----------------------------------------------------------------------
// mesh status
// -----------------------------------------------------------------------
Expand Down
79 changes: 44 additions & 35 deletions cli/src/commands/mesh/health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,32 +61,58 @@ export async function findDuplicateListeners(ports: number[]): Promise<Array<{ p
return results;
}

/** Check registry health via HTTP /v1/health endpoint. */
/** Check registry health via HTTP /health endpoint.
*
* Path compat: AGT registry only exposes `/health`; the vendored Rust
* registry exposes both `/health` and `/v1/health`. Hitting `/health`
* works against both, so probe that first. We still display the vendored
* `agents_registered`/`agents_online` counters when present (AGT returns
* just `{status, service}`).
*/
export async function checkRegistryHealth(port: number): Promise<boolean> {
try {
const resp = await fetch(`http://localhost:${port}/v1/health`, {
signal: AbortSignal.timeout(5000),
});
if (resp.ok) {
for (const probePath of ["/health", "/v1/health"]) {
try {
const resp = await fetch(`http://localhost:${port}${probePath}`, {
signal: AbortSignal.timeout(5000),
});
if (!resp.ok) continue;
const body = await resp.json() as Record<string, unknown>;
checkLine(true, `Registry healthy (${body.agents_registered ?? 0} agents, ${body.agents_online ?? 0} online)`);
if (typeof body.agents_registered === "number") {
checkLine(true, `Registry healthy (${body.agents_registered} agents, ${body.agents_online ?? 0} online)`);
} else {
checkLine(true, `Registry healthy (${(body.service as string | undefined) ?? "agentmesh-registry"})`);
}
return true;
}
checkLine(false, `Registry returned HTTP ${resp.status}`);
return false;
} catch (e: unknown) {
checkLine(false, `Registry not reachable: ${e instanceof Error ? e.message : String(e)}`);
return false;
} catch { /* try next path */ }
}
checkLine(false, `Registry not reachable on localhost:${port}`);
return false;
}

/** Check relay health via WebSocket upgrade (not just TCP connect). */
/** Check relay health via WebSocket upgrade (not just TCP connect).
*
* Path compat: AGT relay only accepts WS upgrades on `/ws`; the
* vendored Rust relay accepts WS on `/`. Try `/ws` first; if the relay
* 404s (vendored), fall back to `/`.
*/
export async function checkRelayHealth(port: number): Promise<boolean> {
for (const probePath of ["/ws", "/"]) {
const ok = await tryWsUpgrade(port, probePath);
if (ok) {
checkLine(true, `Relay healthy (WebSocket upgrade on localhost:${port}${probePath})`);
return true;
}
}
checkLine(false, `Relay not reachable on localhost:${port} (tried /ws and /)`);
return false;
}

function tryWsUpgrade(port: number, probePath: string): Promise<boolean> {
return new Promise((resolve) => {
const req = http.request({
hostname: "127.0.0.1",
port,
path: "/",
path: probePath,
method: "GET",
headers: {
"Upgrade": "websocket",
Expand All @@ -98,33 +124,16 @@ export async function checkRelayHealth(port: number): Promise<boolean> {
});

req.on("upgrade", (_res, socket) => {
checkLine(true, `Relay healthy (WebSocket upgrade on localhost:${port})`);
socket.destroy();
resolve(true);
});

req.on("response", (res) => {
// Got an HTTP response instead of upgrade — relay is serving but not WS
if (res.statusCode === 101) {
checkLine(true, `Relay healthy (localhost:${port})`);
resolve(true);
} else {
checkLine(false, `Relay returned HTTP ${res.statusCode} (expected WebSocket upgrade)`);
resolve(false);
}
});

req.on("error", (e) => {
checkLine(false, `Relay not reachable: ${e.message}`);
resolve(false);
});

req.on("timeout", () => {
checkLine(false, `Relay timeout on localhost:${port}`);
req.destroy();
resolve(false);
resolve(res.statusCode === 101);
});

req.on("error", () => resolve(false));
req.on("timeout", () => { req.destroy(); resolve(false); });
req.end();
});
}
4 changes: 2 additions & 2 deletions cli/src/commands/mesh/promote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ export function attachPromoteSubcommand(cmd: Command): void {
console.log(chalk.green(" ✓ ") + chalk.bold("Registry promoted to global (port-forward)."));
console.log(chalk.dim(" Tunnels are running in the background."));
console.log(chalk.dim(` PIDs saved to ${pidFile}`));
console.log(chalk.dim(`\n Test: curl ${globalRegistryUrl}/v1/health`));
console.log(chalk.dim(`\n Test: curl ${globalRegistryUrl}/health`));
console.log(chalk.dim(` Then: azureclaw dev --global-registry ${globalRegistryUrl}`));
console.log(chalk.dim(` Stop: azureclaw mesh demote`));
console.log();
Expand Down Expand Up @@ -405,7 +405,7 @@ export function attachPromoteSubcommand(cmd: Command): void {
console.log(chalk.green(" ✓ ") + chalk.bold("Registry promoted to global."));
console.log(chalk.dim(" Using sslip.io for DNS (auto-resolved, no setup needed)."));
console.log(chalk.dim(" HTTP only — secured by LoadBalancer IP allowlist."));
console.log(chalk.dim(`\n Test: curl ${globalRegistryUrl}/v1/health`));
console.log(chalk.dim(`\n Test: curl ${globalRegistryUrl}/health`));
console.log(chalk.dim(` Then: azureclaw dev --global-registry ${globalRegistryUrl}`));
console.log();
});
Expand Down
Loading
Loading