Files
gods-eye-view/src/tools/surfaces.js
T
Sameh Khamis 996b95eb45 Load the God's Eye View panel through the MCP server
Hosts serve panels from their own sites and may refuse other addresses;
Codex refuses any address on the user's machine. The panel now never
requests the app's server: it loads the app's panel build and data
through panel_request, a tool only the panel may call, which requests
the path from the app's server. One path in every host; only map
providers load directly.

npm run build:panel writes the panel build to dist/panel, served at
/panel/: one app script, one stylesheet, Cesium's script, and Cesium's
workers as one script Cesium runs from memory. Tools may now be marked
app-only. The panel CORS rules are gone.
2026-10-02 18:29:42 -07:00

99 lines
3.5 KiB
JavaScript

/**
* Which surfaces offer each tool. A tool is on every surface unless this
* table turns it off; edit an entry to turn a tool on or off for MCP, voice,
* or both. Applications can pass overrides when they compose a surface.
* Tools off a surface stay reachable from tools that combine others, such as
* situation_brief using get_weather.
*/
import { ToolError } from './catalog.js';
export const SURFACES = Object.freeze(['mcp', 'voice']);
const voiceOff = Object.freeze({ voice: false });
const mcpOff = Object.freeze({ mcp: false });
export const TOOL_SURFACES = Object.freeze({
// Voice runs inside the app, so a link to it adds nothing.
show_in_gods_eye_view: voiceOff,
// Only the God's Eye View panel calls this, to load the app.
panel_request: voiceOff,
// Voice has an app action of the same name.
next_satellite_pass: voiceOff,
// Voice receives text only, so tools that answer with an image are off.
get_weather_map: voiceOff,
get_recent_imagery: voiceOff,
get_cctv_snapshot: voiceOff,
// The app shows its HUD caption itself, and assistants write their own.
get_hud_caption: Object.freeze({ voice: false, mcp: false }),
// Voice answers these from the loaded layers with analyst_query.
aircraft_in_area: voiceOff,
vessels_in_area: voiceOff,
find_aircraft: voiceOff,
find_vessel: voiceOff,
get_earthquakes: voiceOff,
get_active_fires: voiceOff,
find_infrastructure: voiceOff,
satellites_overhead: voiceOff,
// MCP leads with what the globe shows; assistants already search places,
// plan routes and report weather, and these layers add little in a chat.
search_places: mcpOff,
places_nearby: mcpOff,
plan_route: mcpOff,
get_weather: mcpOff,
get_wind: mcpOff,
get_regional_brief: mcpOff,
find_radio_stations: mcpOff,
get_bike_share: mcpOff,
get_transit_vehicles: mcpOff,
});
/**
* The tools a surface offers, in their original order. `overrides` uses the
* table's shape and wins over it, such as `{ get_weather_map: { voice: true } }`.
* An override naming an unknown tool or surface throws, so a typo cannot
* silently do nothing.
*/
export function toolsForSurface(tools, surface, overrides = {}) {
if (!SURFACES.includes(surface))
throw new TypeError(`Unknown tool surface: ${surface}`);
const names = new Set(tools.map((tool) => tool.name));
for (const [name, entry] of Object.entries(overrides)) {
if (!names.has(name)) throw new TypeError(`Unknown tool: ${name}`);
for (const key of Object.keys(entry)) {
if (!SURFACES.includes(key))
throw new TypeError(`Unknown tool surface for ${name}: ${key}`);
}
}
return tools.filter(
(tool) =>
(overrides[tool.name]?.[surface] ??
TOOL_SURFACES[tool.name]?.[surface]) !== false,
);
}
/**
* A view of a composed catalog that lists and calls only the tools a surface
* offers. Tools that combine others still reach the whole catalog, so a
* composite such as military_awareness keeps its sections on every surface.
*/
export function catalogForSurface(catalog, surface, overrides = {}) {
const offered = new Set(
toolsForSurface(catalog.list(), surface, overrides).map(
(tool) => tool.name,
),
);
return Object.freeze({
list: () => catalog.list().filter((tool) => offered.has(tool.name)),
get: (name) => (offered.has(name) ? catalog.get(name) : undefined),
async call(name, args, options) {
if (!offered.has(name))
throw new ToolError(
'unsupported',
`No tool named ${name} is available`,
);
return catalog.call(name, args, options);
},
});
}