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
40 changes: 40 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Release

on:
push:
tags:
- "v*"

permissions:
contents: write

jobs:
create-release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

- name: Extract changelog section
id: changelog
run: |
version="${{ steps.version.outputs.version }}"
body=$(awk "/^## ${version//./\\.} /{found=1; next} /^## [0-9]/{if(found) exit} found{print}" CHANGELOG.md)
{
echo "body<<CHANGELOG_EOF"
echo "$body"
echo "CHANGELOG_EOF"
} >> "$GITHUB_OUTPUT"

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: "Mayros ${{ github.ref_name }}"
body: ${{ steps.changelog.outputs.body }}
draft: false
prerelease: ${{ contains(github.ref_name, '-') }}
generate_release_notes: false
183 changes: 183 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,189 @@ Product: https://apilium.com/us/products/mayros
Download: https://mayros.apilium.com
Docs: https://apilium.com/us/doc/mayros

## 0.1.14 (2026-03-11)

Intelligent routing, multi-agent consensus, and execution safety.

### Eruberu — Adaptive Model Routing

- Q-Learning model selector: learns optimal provider/model per task type, budget level, and time slot
- Budget-driven fallback: auto-switches to cheaper models when budget exceeds configurable thresholds
- Task classifier: keyword-based prompt classification (code, chat, analysis, creative)
- Cortex persistence: Q-table stored as RDF triples with JSON file fallback
- Integrates via `before_model_resolve` hook — zero changes to core execution path
- New tools: `routing_status`, `routing_set_strategy`
- New CLI: `mayros routing status|strategy|reset`

### Miteru — Intelligent Task-to-Agent Routing

- Q-Learning agent selector: learns which agent handles each task type best
- Task classification by type, complexity, and language domain
- Performance tracker: EMA-based agent scoring with Cortex persistence
- Integrated into workflow orchestrator as optional routing layer
- New tool: `mesh_route_task`

### Kimeru — Multi-Agent Consensus

- Three consensus strategies: majority vote, weighted (by EMA score), LLM-arbitrated
- Automatic conflict resolution when parallel agents produce conflicting results
- Confidence scoring and detailed vote breakdown
- New tools: `mesh_agent_performance`, `mesh_consensus`

### Tomeru — Rate Limiting & Loop Breaking

- Sliding window rate limiter: per-tool call limits with configurable windows
- Global token bucket: burst protection across all tools
- Loop breaker: SHA256-based identical-call sequence detection
- Velocity circuit breaker: hard block on runaway execution
- Configurable modes: enforce, warn, off
- New tools: `rate_limit_status`, `rate_limit_adjust`
- New CLI: `mayros ratelimit status|adjust|reset`

### Token Economy Enhancements

- Response cache (Oboeru): LRU cache with TTL for observational response deduplication
- Budget bridge: Symbol-based cross-plugin bridge exposes BudgetTracker to routing subsystems
- Cache savings tracking in budget summaries

### Model Router

- `buildFromPricingCatalog()`: construct router from token-economy pricing catalog
- `routeWithBudget()`: budget-aware routing that filters by remaining spend

### Infrastructure

- 55 extensions synced at v0.1.14
- 55 new tests across 7 test files (Q-Learning, task classification, routing, performance tracking, consensus, rate limiting, loop breaking)
- Auto-release workflow: GitHub Releases created automatically on version tags

## 0.1.13 (2026-03-08)

Fix plugin loading, headless mode, and postinstall reliability.

- Fix gateway health check in headless mode
- Add postinstall retry logic for flaky network environments
- Include `src/` in npm package for extension runtime imports

## 0.1.12 (2026-03-07)

Auto-install gateway daemon on first run.

- Auto-install gateway daemon service on first run
- Fix duplicate `resolveGatewayPort` call in ensure-services

## 0.1.11 (2026-03-06)

Auto-update outdated Cortex binary.

- Auto-update outdated Cortex binary on sidecar start
- Require Cortex >= 0.4.1

## 0.1.10 (2026-03-05)

Persistent Cortex storage and sidecar hardening.

- Persistent Cortex storage via Sled backend (`~/.mayros/cortex-data/`)
- Lifecycle callback registry for flush-before-update flow
- Graceful sidecar restart with binary update
- Lock file reclaim on sidecar auto-restart
- Drain timeout and external Cortex detection fixes
- Complete sidecar lifecycle hardening (10 gaps)
- Hide internal instructions from slash command display

## 0.1.9 (2026-03-04)

Ineru rename and Cortex 0.4.0.

- Rename Titans memory client to Ineru across all modules
- Require Cortex >= 0.4.0

## 0.1.8 (2026-03-03)

P2P sync and enhanced Cortex networking.

- Native P2P sync mode with pairing, gossip, and status CLI
- Dual sync mode bridge: native P2P with polled fallback
- P2P config, CortexClient P2P methods, and sidecar flag forwarding
- Require Cortex >= 0.3.8

## 0.1.7 (2026-03-02)

Scoped package and update runner fix.

- Fix update-runner for scoped package name (`@apilium/mayros`)

## 0.1.6 (2026-03-01)

Cortex auto-start, resilience, and TUI improvements.

- Auto-start gateway and Cortex before TUI
- Cortex CLI commands, gateway methods, and TUI view
- Cortex auto-restart with resilience monitor
- Change default Cortex port from 8080 to 19090
- Enable semantic ecosystem plugins by default
- Zero-config semantic plugin startup
- `/kg` handler with tool fallback and diagnostic hints
- `/mouse` toggle for native text selection
- Dynamic VERSION in TUI welcome screen
- Pixel avatar art banner
- Adapt sidecar for Cortex 0.3.7

## 0.1.5 (2026-02-28)

Stability, resource cleanup, and IDE plugin hardening.

- Clear feedNext timer chain on PTY exit
- Replace eval with array-based command execution in mayroslog.sh
- Nullable TeamManager parameters with runtime guards
- Headless CLI timeout cleanup and prototype pollution guard
- Cap trace events at 5000 entries
- EventEmitter dispose in tree providers
- WebView message listener lifecycle management
- JetBrains plugin: daemon threads, error logging, panel disposal
- Thread-safe stream buffering (StringBuilder → StringBuffer)
- Synchronized disconnect to prevent race conditions
- Migrate gateway token to IntelliJ PasswordSafe
- Per-request cache key map for token-economy concurrency

## 0.1.4 (2026-02-27)

IDE extensions, CLI evolution, and security updates.

- VSCode extension: context menu actions, gutter markers, protocol v3
- JetBrains plugin: unified tabbed panel, Skills/Plan/KG views, protocol v3
- Welcome screen, image paste, and onboarding UX
- Heartbeat filtering, interactive selectors, and command cleanup
- Bump hono, @hono/node-server, and dompurify for security fixes
- Fix timer leak in sync timeout, stats filter, and panel disposal

## 0.1.3 (2026-02-26)

CI fixes and plugin loading.

- Fix CI: skip Android playstore without keystore
- Strip `mayros-` prefix from plugin entry hints to match manifest IDs

## 0.1.2 (2026-02-26)

Post-launch fixes and dependency updates.

- Fix 15 test files for vitest 4.x mock hoisting compatibility
- Fix 15 broken internal links in docs
- Fix README links
- Update plugin SDK exports and rename legacy plist references
- Update extensions: zod v4, observability route API, esbuild import
- Update CI workflow and build scripts
- Platform app updates: macOS, iOS, Android

## 0.1.1 (2026-02-25)

Skills Hub launch.

- Add 8 official Apilium skills with Ed25519 signatures
- Platinum skill structure and documentation
- Add markdownlint configuration

## 0.1.0 (2026-02-25)

First public release of Mayros — personal AI assistant platform.
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a>
</p>

<p align="center">
<a href="https://www.npmjs.com/package/@apilium/mayros"><img src="https://img.shields.io/npm/dm/@apilium/mayros?style=for-the-badge&label=downloads&color=brightgreen" alt="npm downloads"></a>
<img src="https://img.shields.io/badge/node-%3E%3D22.12.0-339933?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js >= 22.12.0">
<img src="https://img.shields.io/badge/TypeScript-strict-3178C6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript">
<img src="https://img.shields.io/badge/extensions-55-8B5CF6?style=for-the-badge" alt="55 extensions">
<img src="https://img.shields.io/badge/platform-macOS%20%7C%20Linux-999?style=for-the-badge" alt="macOS | Linux">
</p>

<p align="center">
<a href="https://apilium.com/en/products/mayros">Product</a> · <a href="https://mayros.apilium.com">Download</a> · <a href="https://apilium.com/en/doc/mayros">Docs</a> · <a href="https://apilium.com/en/doc/mayros/start/getting-started">Getting Started</a> · <a href="VISION.md">Vision</a> · <a href="https://discord.com/channels/1476351587105636404">Discord</a>
</p>
Expand Down
133 changes: 133 additions & 0 deletions extensions/agent-mesh/byzantine-validator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { describe, it, expect } from "vitest";
import { ByzantineValidator } from "./byzantine-validator.js";

describe("ByzantineValidator", () => {
it("requires at least 4 agents for byzantine consensus", () => {
const bv = new ByzantineValidator();
expect(bv.canRunByzantine(3)).toBe(false);
expect(bv.canRunByzantine(4)).toBe(true);
expect(bv.canRunByzantine(7)).toBe(true);
});

it("generates session keys and signs votes", () => {
const bv = new ByzantineValidator();
const key = bv.generateSessionKey("agent-a");
expect(key.agentId).toBe("agent-a");
expect(key.key.length).toBe(32);

const vote = bv.signVote("agent-a", "value-x");
expect(vote.agentId).toBe("agent-a");
expect(vote.value).toBe("value-x");
expect(vote.signature).toBeTruthy();
});

it("verifies valid votes", () => {
const bv = new ByzantineValidator();
bv.generateSessionKey("agent-a");
const vote = bv.signVote("agent-a", "value-x");
expect(bv.verifyVote(vote)).toBe(true);
});

it("rejects tampered votes", () => {
const bv = new ByzantineValidator();
bv.generateSessionKey("agent-a");
const vote = bv.signVote("agent-a", "value-x");
vote.value = "tampered";
expect(bv.verifyVote(vote)).toBe(false);
});

it("rejects votes from unknown agents", () => {
const bv = new ByzantineValidator();
const vote = {
agentId: "unknown",
value: "x",
timestamp: Date.now(),
signature: "fake",
};
expect(bv.verifyVote(vote)).toBe(false);
});

it("computes quorum correctly", () => {
const bv = new ByzantineValidator();
// n=4: f=1, need 2f+1=3
const q4 = bv.checkQuorum(4, 3);
expect(q4.reached).toBe(true);
expect(q4.faultTolerance).toBe(1);
expect(q4.requiredCount).toBe(3);

// n=4, only 2 agree
const q4low = bv.checkQuorum(4, 2);
expect(q4low.reached).toBe(false);

// n=7: f=2, need 2f+1=5
const q7 = bv.checkQuorum(7, 5);
expect(q7.reached).toBe(true);
expect(q7.faultTolerance).toBe(2);
});

it("runs PBFT successfully with 4 agents agreeing", async () => {
const bv = new ByzantineValidator();
const result = await bv.runPBFT({
agentIds: ["a", "b", "c", "d"],
values: ["yes", "yes", "yes", "no"],
agentValues: { a: "yes", b: "yes", c: "yes", d: "no" },
});

expect(result.success).toBe(true);
expect(result.resolvedValue).toBe("yes");
expect(result.phase).toBe("complete");
});

it("fails PBFT when insufficient agents", async () => {
const bv = new ByzantineValidator();
const result = await bv.runPBFT({
agentIds: ["a", "b", "c"],
values: ["yes", "no", "maybe"],
agentValues: { a: "yes", b: "no", c: "maybe" },
});

expect(result.success).toBe(false);
});

it("clears session keys", () => {
const bv = new ByzantineValidator();
bv.generateSessionKey("agent-a");
const vote = bv.signVote("agent-a", "x");
expect(bv.verifyVote(vote)).toBe(true);

bv.clearKeys();
expect(bv.verifyVote(vote)).toBe(false);
});

// --- Timing-safe HMAC comparison tests ---

it("rejects a tampered signature of the same length", () => {
const bv = new ByzantineValidator();
bv.generateSessionKey("agent-a");
const vote = bv.signVote("agent-a", "value-x");

// Flip last hex char to produce a same-length but different signature
const lastChar = vote.signature[vote.signature.length - 1];
const flipped = lastChar === "0" ? "1" : "0";
vote.signature = vote.signature.slice(0, vote.signature.length - 1) + flipped;

expect(bv.verifyVote(vote)).toBe(false);
});

it("rejects a signature of different length", () => {
const bv = new ByzantineValidator();
bv.generateSessionKey("agent-a");
const vote = bv.signVote("agent-a", "value-x");

// Truncate signature to make it shorter
vote.signature = vote.signature.slice(0, 8);
expect(bv.verifyVote(vote)).toBe(false);
});

it("still accepts valid signatures after timing-safe fix", () => {
const bv = new ByzantineValidator();
bv.generateSessionKey("agent-b");
const vote = bv.signVote("agent-b", "some-value");
expect(bv.verifyVote(vote)).toBe(true);
});
});
Loading
Loading