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
20 changes: 10 additions & 10 deletions electron/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "omniroute-desktop",
"name": "graze-desktop",
"version": "3.7.8",
"description": "OmniRoute Desktop Application",
"description": "Graze Desktop Application",
"main": "main.js",
"author": {
"name": "OmniRoute Team",
"email": "support@omniroute.online"
"name": "Open Paws",
"email": "sam@openpaws.ai"
},
"license": "MIT",
"homepage": "https://omniroute.online",
"homepage": "https://graze.openpaws.ai",
"scripts": {
"start": "electron .",
"dev": "electron . --no-sandbox",
Expand All @@ -34,17 +34,17 @@
"plist": "^4.0.0"
},
"build": {
"appId": "online.omniroute.desktop",
"productName": "OmniRoute",
"copyright": "Copyright © 2025 OmniRoute",
"appId": "ai.openpaws.graze",
"productName": "Graze",
"copyright": "Copyright © 2025 Open Paws",
"directories": {
"output": "dist-electron",
"buildResources": "assets"
},
"publish": {
"provider": "github",
"owner": "diegosouzapw",
"repo": "OmniRoute"
"owner": "OpenGaryBot",
"repo": "graze"
},
"files": [
"main.js",
Expand Down
2 changes: 1 addition & 1 deletion open-sse/utils/cors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
export const CORS_HEADERS: Record<string, string> = {
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, x-api-key, anthropic-version, x-graze-connection, x-internal-test, accept",
"Content-Type, Authorization, x-api-key, anthropic-version, x-graze-connection, x-internal-test, accept, x-gary-action-id, x-gary-stage, x-gary-playbook, x-gary-sensitivity",
};
2 changes: 2 additions & 0 deletions src/app/api/monitoring/health/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getProviderConnections, getSettings } from "@/lib/localDb";
import { buildHealthPayload } from "@/lib/monitoring/observability";
import { APP_CONFIG } from "@/shared/constants/config";
import { AI_PROVIDERS } from "@/shared/constants/providers";
import { getRecentGaryContexts } from "@/lib/gary/context";

/**
* GET /api/monitoring/health — System health overview
Expand Down Expand Up @@ -48,6 +49,7 @@ export async function GET() {
quotaMonitorMonitors,
activeSessions,
activeSessionsByKey,
recentGaryContexts: getRecentGaryContexts(),
});

return NextResponse.json(payload);
Expand Down
147 changes: 147 additions & 0 deletions src/lib/gary/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* Gary header contract — READ-ONLY parsing.
*
* Parses the four X-Gary-* request headers that GaryOS sends to identify
* the pipeline stage, playbook, sensitivity tier, and action ID for a
* given request. All headers are optional; missing or unknown values are
* treated as absent (never 4xx).
*/

import { createLogger } from "@/shared/utils/logger";
import { logAuditEvent } from "@/lib/compliance";

const log = createLogger("gary");

// ── Enum types ──────────────────────────────────────────────────────────────

export type GaryStage =
| "scout"
| "triage"
| "plan"
| "review-plan"
| "draft"
| "review-draft"
| "submit"
| "validate"
| "adversarial"
| "sam-gate"
| "commit"
| "verify";

export type GaryPlaybook =
| "code-pr"
| "correspondence"
| "decision"
| "research"
| "content"
| "task"
| "other";

export type GarySensitivity = "tier-1" | "tier-2" | "tier-3";

export type GaryContext = {
actionId: string | null;
stage: GaryStage | null;
playbook: GaryPlaybook | null;
sensitivity: GarySensitivity | null;
};

// ── Exported enum value sets (used by tests) ────────────────────────────────

export const GARY_STAGE_VALUES = new Set<string>([
"scout",
"triage",
"plan",
"review-plan",
"draft",
"review-draft",
"submit",
"validate",
"adversarial",
"sam-gate",
"commit",
"verify",
]);

export const GARY_PLAYBOOK_VALUES = new Set<string>([
"code-pr",
"correspondence",
"decision",
"research",
"content",
"task",
"other",
]);

export const GARY_SENSITIVITY_VALUES = new Set<string>(["tier-1", "tier-2", "tier-3"]);

// ── Audit ring buffer ────────────────────────────────────────────────────────

export type GaryAuditEntry = GaryContext & { timestamp: string; requestId: string | null };

const GARY_HISTORY_LIMIT = 20;
const recentGaryContexts: GaryAuditEntry[] = [];

export function getRecentGaryContexts(): GaryAuditEntry[] {
return recentGaryContexts.slice();
}

// ── Parsing ─────────────────────────────────────────────────────────────────

function parseEnum<T extends string>(
raw: string | null,
validValues: Set<string>,
headerName: string
): T | null {
if (!raw) return null;
if (validValues.has(raw)) return raw as T;
log.warn({ tag: "GARY", header: headerName, value: raw }, `Unknown ${headerName} value — treating as absent`);
return null;
}

type RequestLike = { headers: { get(name: string): string | null } };

/**
* Parse Gary pipeline headers from a request.
*
* Always returns a GaryContext (fields are null when absent/unknown).
* Never throws; never 4xx.
*/
export function parseGaryContext(request: RequestLike, requestId: string | null = null): GaryContext {
const actionId = request.headers.get("x-gary-action-id")?.trim() || null;

const stageRaw = request.headers.get("x-gary-stage")?.trim().toLowerCase() || null;
const playbookRaw = request.headers.get("x-gary-playbook")?.trim().toLowerCase() || null;
const sensitivityRaw = request.headers.get("x-gary-sensitivity")?.trim().toLowerCase() || null;

const stage = parseEnum<GaryStage>(stageRaw, GARY_STAGE_VALUES, "X-Gary-Stage");
const playbook = parseEnum<GaryPlaybook>(playbookRaw, GARY_PLAYBOOK_VALUES, "X-Gary-Playbook");
const sensitivity = parseEnum<GarySensitivity>(
sensitivityRaw,
GARY_SENSITIVITY_VALUES,
"X-Gary-Sensitivity"
);

const ctx: GaryContext = { actionId, stage, playbook, sensitivity };

// Add to in-memory ring buffer for health endpoint visibility (every request)
const entry: GaryAuditEntry = { ...ctx, timestamp: new Date().toISOString(), requestId };
recentGaryContexts.unshift(entry);
if (recentGaryContexts.length > GARY_HISTORY_LIMIT) {
recentGaryContexts.length = GARY_HISTORY_LIMIT;
}

// Persist to audit_log only when at least one Gary header was sent
if (actionId !== null || stage !== null || playbook !== null || sensitivity !== null) {
logAuditEvent({
action: "gary.context",
actor: "pipeline",
resourceType: "request",
status: "ok",
requestId: requestId ?? undefined,
details: ctx,
});
}

return ctx;
}
3 changes: 3 additions & 0 deletions src/lib/monitoring/observability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ interface BuildHealthPayloadOptions {
quotaMonitorMonitors: QuotaMonitorSnapshot[];
activeSessions: SessionSnapshot[];
activeSessionsByKey?: Record<string, number>;
recentGaryContexts?: JsonRecord[];
}

function limitMonitors(monitors: QuotaMonitorSnapshot[], maxItems = 8): QuotaMonitorSnapshot[] {
Expand Down Expand Up @@ -149,6 +150,7 @@ export function buildHealthPayload({
quotaMonitorMonitors,
activeSessions,
activeSessionsByKey = {},
recentGaryContexts = [],
}: BuildHealthPayloadOptions) {
const timestamp = new Date().toISOString();
const system = {
Expand Down Expand Up @@ -248,5 +250,6 @@ export function buildHealthPayload({
provider: "aes-256-gcm",
},
setupComplete: settings?.setupComplete || false,
garyContextHistory: recentGaryContexts,
};
}
4 changes: 4 additions & 0 deletions src/sse/handlers/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import {
resolveCooldownAwareRetrySettings,
waitForCooldownAwareRetry,
} from "../services/cooldownAwareRetry";
import { parseGaryContext } from "@/lib/gary/context";

registerCodexQuotaFetcher();

Expand Down Expand Up @@ -122,6 +123,9 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
const reqId = generateRequestId();
const telemetry = new RequestTelemetry(reqId);

// Gary header contract — parse pipeline context (READ-ONLY, never 4xx)
parseGaryContext(request, reqId);

let body;
try {
telemetry.startPhase("parse");
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/executor-codex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ test("CodexExecutor.execute adds CLI-like session identity headers without chang
assert.equal(capturedBody?.prompt_cache_key, "conversation-1");
assert.equal(
(capturedBody?.client_metadata as Record<string, unknown>)?.["x-codex-installation-id"],
"7f06a8ee-2981-4c81-a4ca-e443b5400a63"
"5594a324-c051-4a89-a595-6c1df1d3c86f"
);
} finally {
globalThis.fetch = originalFetch;
Expand Down
Loading
Loading