diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md index 0958bed..85bdddf 100644 --- a/docs/DEVELOPER_GUIDE.md +++ b/docs/DEVELOPER_GUIDE.md @@ -262,15 +262,16 @@ const agent = new HotelSearchAgent({ --- -## Agent navigation map +## Agent connections page -OTAIP ships a first-party **agent map** for browsing the ~77 discovered agents by stage, with edges from Orchestrator workflows and agent-package workspace deps. +OTAIP ships a first-party **agent connections** view: pick an Orchestrator workflow, see the agent chain, browse the stage bento, inspect neighbors. Edges come from workflows + agent-package workspace deps. | Artifact | Role | |---|---| | `agents.manifest.json` | Authoritative agent roster | | `agents.graph.json` | Nodes + workflow/package edges for navigation | -| `docs/agent-map.html` | Standalone browsable map (no build step) | +| `docs/agent-map.html` | Standalone connections page (Apple-style bento; not a marketing landing) | +| `docs/assets/telivity/` | Brand logos/favicon from [telivity.app](https://telivity.app) (navy/teal/Montserrat) | | Platform UI → Map | Same graph via `GET /api/platform/agent-graph` | Regenerate all three (plus the HTML) with: diff --git a/docs/agent-map.html b/docs/agent-map.html index d83c33f..c528f95 100644 --- a/docs/agent-map.html +++ b/docs/agent-map.html @@ -3,998 +3,1243 @@ - Otaip Agent Map · Telivity - + Agent connections · Telivity Otaip + + + + + - + -
-
-
-

Telivity · Otaip — open source

-

Agent map for the whole travel transaction.

-

77 typed agents. Search to settlement. Solid cyan arcs are Orchestrator workflows; dashed traces are package dependencies.

-
-
-
77Agents
-
12Stages
-
5Workflows
-
27Edges
-
-
- -
- - - -
- - - - - - -
-
- Active - Stub - Workflow - Package -
-
+
+ + + Telivity + + Agent connections + 77 agents · 27 edges +
-
-
- - - -
-
-

reference 7

-
- - - - - - - +
+
+
+
+

How agents connect

+

Pick a workflow. Watch the chain. Open any agent for its neighbors.

-
-
-

search 8

-
- - - - - - - -
-
-
-

pricing 7

-
- - - - - - - +
+ +
+
Workflow path
+
+
+ +
-
-
-

booking 8

-
- - - - - - - - +
+ +
-
-
-

ticketing 5

-
- - - - - +
+ +
-
-
-

exchange 6

-
- - - - - - +
+ +
-
-
-

settlement 6

-
- - - - - - +
+ +
-
-
-

reconciliation 6

-
- - - - - - +
+ +
-
-
-

tmc 5

-
- - - - - +
+ +
-
-
-

platform 9

-
- - - - - - - - - +
+ +
-
-
-

core 1

-
- +
+ +
-
-
-

lodging 9

-
- - - - - - - - -
-
+
+
+ +
+
+
+

All agents

+

Stages light up when they sit on the active path. Select an agent to inspect edges.

+
+
+ + +
- -
+
+
+ +
+ + + + + + + +
+
+
+ +
+ + + + + + + + +
+
+
+ +
+ + + + + + + +
+
+
+ +
+ + + + + + + + +
+
+
+ +
+ + + + + +
+
+
+ +
+ + + + + + +
+
+
+ +
+ + + + + + +
+
+
+ +
+ + + + + + +
+
+
+ +
+ + + + + +
+
+
+ +
+ + + + + + + + + +
+
+
+ +
+ +
+
+
+ +
+ + + + + + + + + +
+
+
+
- Telivity — AI travel infrastructure · Otaip open core · - 77 agents · 27 edges · generated from agents.graph.json · pnpm gen:agent-map + + Telivity Otaip + · + 77 agents · 27 edges · from agents.graph.json
- + + + + diff --git a/docs/agents.md b/docs/agents.md index afeac9f..f554c09 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -3,7 +3,7 @@ > 75 agents across 12 stages. 14 have pipeline contracts (marked with checkmark). > Counts are computed from source by `pnpm run count:agents` — do not hand-edit the totals. > -> **Visual map:** open [agent-map.html](./agent-map.html) (generated by `pnpm gen:manifest`) or use **Map** on the Platform UI dashboard. That map is agent navigation only — not a travel-domain knowledge graph. +> **Agent connections:** open [agent-map.html](./agent-map.html) (generated by `pnpm gen:manifest`) or use **Map** on the Platform UI dashboard. Shows how agents wire through workflows — not a travel-domain knowledge graph. ## Stage 0 -- Reference (7 agents) diff --git a/docs/assets/telivity/README.md b/docs/assets/telivity/README.md new file mode 100644 index 0000000..f424bde --- /dev/null +++ b/docs/assets/telivity/README.md @@ -0,0 +1,28 @@ +# Telivity brand assets + +Canonical marks from the Telivity/HAIP site (`public/` on staging — e.g. [staging.haipcloud.com](https://staging.haipcloud.com/favicon.svg)), vendored here for `docs/agent-map.html`. + +| File | Source | Notes | +|------|--------|--------| +| `favicon.svg` | `/favicon.svg` | Primary favicon — same artwork as `brand/mark.svg` | +| `favicon-32.png` | `/favicon-32.png` | 32×32 PNG | +| `favicon-64.png` | `/favicon-64.png` | 64×64 PNG | +| `apple-touch-icon.png` | `/apple-touch-icon.png` | 180×180 | +| `brand/mark.svg` | `/brand/mark.svg` | Brand mark (identical to favicon.svg) | +| `logo-1color.svg` | telivity.app `/telivity-logo-1color.svg` | Wordmark (slate `#444863`) — not the favicon | +| `og-image.png` | telivity.app `/og-image.png` | 1200×630 | + +## Brand tokens (site CSS) + +| Token | Hex | +|-------|-----| +| Teal | `#06bdb4` | +| Teal dark | `#00a692` | +| Teal light | `#2cd1b9` | +| Navy | `#23273d` | +| Slate | `#444863` | +| Mid grey | `#bbbbc4` | +| Fog | `#f0f0f6` | +| Yellow | `#eec517` | +| Orange | `#f2641b` | +| Font | Montserrat 300/400/600/700 | diff --git a/docs/assets/telivity/apple-touch-icon.png b/docs/assets/telivity/apple-touch-icon.png new file mode 100644 index 0000000..cd4111e Binary files /dev/null and b/docs/assets/telivity/apple-touch-icon.png differ diff --git a/docs/assets/telivity/brand/mark.svg b/docs/assets/telivity/brand/mark.svg new file mode 100644 index 0000000..2554885 --- /dev/null +++ b/docs/assets/telivity/brand/mark.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/telivity/favicon-32.png b/docs/assets/telivity/favicon-32.png new file mode 100644 index 0000000..15b2853 Binary files /dev/null and b/docs/assets/telivity/favicon-32.png differ diff --git a/docs/assets/telivity/favicon-64.png b/docs/assets/telivity/favicon-64.png new file mode 100644 index 0000000..2b4d9f9 Binary files /dev/null and b/docs/assets/telivity/favicon-64.png differ diff --git a/docs/assets/telivity/favicon.svg b/docs/assets/telivity/favicon.svg new file mode 100644 index 0000000..2554885 --- /dev/null +++ b/docs/assets/telivity/favicon.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/telivity/logo-1color.svg b/docs/assets/telivity/logo-1color.svg new file mode 100644 index 0000000..f76e2a7 --- /dev/null +++ b/docs/assets/telivity/logo-1color.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/telivity/logo-on-dark.svg b/docs/assets/telivity/logo-on-dark.svg new file mode 100644 index 0000000..f54b449 --- /dev/null +++ b/docs/assets/telivity/logo-on-dark.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/telivity/og-image.png b/docs/assets/telivity/og-image.png new file mode 100644 index 0000000..3a8ff57 Binary files /dev/null and b/docs/assets/telivity/og-image.png differ diff --git a/examples/platform-ui/src/components/AgentMap.tsx b/examples/platform-ui/src/components/AgentMap.tsx index 95311c1..165d040 100644 --- a/examples/platform-ui/src/components/AgentMap.tsx +++ b/examples/platform-ui/src/components/AgentMap.tsx @@ -233,7 +233,7 @@ export default function AgentMap({ graph }: AgentMapProps) { key={i} d={p.d} fill="none" - stroke={p.kind === 'workflow' ? '#06b6d4' : '#64748b'} + stroke={p.kind === 'workflow' ? '#06bdb4' : '#444863'} strokeWidth={p.hl ? 2.25 : p.kind === 'workflow' ? 1.5 : 1} strokeDasharray={p.kind === 'package' ? '4 3' : undefined} opacity={p.hl ? 1 : p.kind === 'workflow' ? 0.55 : 0.35} @@ -262,8 +262,8 @@ export default function AgentMap({ graph }: AgentMapProps) { onClick={() => setSelected(a.id)} className={[ 'rounded-md border bg-white px-2 py-1.5 text-left transition', - a.contract_status === 'active' ? 'border-l-[3px] border-l-cyan-500' : 'border-l-[3px] border-l-slate-400', - isSelected || onPath ? 'border-cyan-400 shadow-[0_0_0_1px_#22d3ee]' : 'border-slate-200', + a.contract_status === 'active' ? 'border-l-[3px] border-l-[#06bdb4]' : 'border-l-[3px] border-l-[#444863]', + isSelected || onPath ? 'border-[#06bdb4] shadow-[0_0_0_1px_#06bdb4]' : 'border-slate-200', dim ? 'opacity-30' : '', ].join(' ')} > diff --git a/scripts/generate-agent-map-html.ts b/scripts/generate-agent-map-html.ts index 0cfbef9..4f2dcf7 100644 --- a/scripts/generate-agent-map-html.ts +++ b/scripts/generate-agent-map-html.ts @@ -2,15 +2,16 @@ /** * Render `docs/agent-map.html` from an AgentGraph. * - * Telivity-branded mission-control board: dark slate + cyan, Space Grotesk / - * IBM Plex, workflow edges above cards with side anchors so connections are - * actually readable. Generated by `pnpm gen:manifest` / `pnpm gen:agent-map`. + * Apple-style **connection stage** — agents and how they wire together. + * Not a marketing landing. Not a spaghetti DAG. Light Telivity craft + * (Montserrat, teal #06bdb4, navy #23273d, fog #f0f0f6). + * Generated by `pnpm gen:manifest` / `pnpm gen:agent-map`. */ import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; -import type { AgentGraph, AgentGraphNode } from './agent-graph.js'; +import type { AgentGraph, AgentGraphEdge, AgentGraphNode } from './agent-graph.js'; const GRAPH_PATH = 'agents.graph.json'; const MAP_HTML_PATH = 'docs/agent-map.html'; @@ -30,6 +31,36 @@ const STAGE_ORDER = [ 'lodging', ] as const; +const STAGE_DISPLAY: Readonly> = { + reference: 'Reference', + search: 'Search', + pricing: 'Pricing', + booking: 'Booking', + ticketing: 'Ticketing', + exchange: 'Exchange', + settlement: 'Settlement', + reconciliation: 'Reconciliation', + tmc: 'TMC', + platform: 'Platform', + core: 'Core', + lodging: 'Lodging', +}; + +const BENTO_SPAN: Readonly> = { + search: 'span-3', + pricing: 'span-3', + booking: 'span-4', + ticketing: 'span-2', + exchange: 'span-2', + settlement: 'span-2', + reconciliation: 'span-2', + reference: 'span-2', + tmc: 'span-2', + core: 'span-2', + lodging: 'span-3', + platform: 'span-3', +}; + function repoRoot(): string { let cur = dirname(fileURLToPath(import.meta.url)); for (let i = 0; i < 8; i++) { @@ -52,6 +83,10 @@ function stageSortKey(stage: string): number { return idx === -1 ? 1000 : idx; } +function stageDisplay(stage: string): string { + return STAGE_DISPLAY[stage] ?? stage.charAt(0).toUpperCase() + stage.slice(1); +} + function escapeHtml(s: string): string { return s .replace(/&/g, '&') @@ -73,668 +108,776 @@ function groupByStage(nodes: readonly AgentGraphNode[]): Array<[string, AgentGra return [...map.entries()].sort(([a], [b]) => stageSortKey(a) - stageSortKey(b) || a.localeCompare(b)); } +/** Ordered agent ids for a workflow from its directed edges. */ +export function workflowChain(edges: readonly AgentGraphEdge[], label: string): string[] { + const wf = edges.filter((e) => e.kind === 'workflow' && e.label === label); + if (wf.length === 0) return []; + const outs = new Map(); + const ins = new Map(); + const nodes = new Set(); + for (const e of wf) { + nodes.add(e.source); + nodes.add(e.target); + const list = outs.get(e.source) ?? []; + list.push(e.target); + outs.set(e.source, list); + ins.set(e.target, (ins.get(e.target) ?? 0) + 1); + if (!ins.has(e.source)) ins.set(e.source, ins.get(e.source) ?? 0); + } + const starts = [...nodes].filter((n) => (ins.get(n) ?? 0) === 0).sort(compareAgentIds); + const chain: string[] = []; + const seen = new Set(); + const queue = [...starts]; + while (queue.length) { + const id = queue.shift(); + if (!id || seen.has(id)) continue; + seen.add(id); + chain.push(id); + for (const t of outs.get(id) ?? []) { + if (!seen.has(t)) queue.push(t); + } + } + for (const n of [...nodes].sort(compareAgentIds)) { + if (!seen.has(n)) chain.push(n); + } + return chain; +} + +function workflowLabels(graph: AgentGraph): string[] { + return [ + ...new Set(graph.edges.filter((e) => e.kind === 'workflow').map((e) => e.label)), + ].sort((a, b) => { + if (a === 'full_booking') return -1; + if (b === 'full_booking') return 1; + return a.localeCompare(b); + }); +} + export function renderAgentMapHtml(graph: AgentGraph): string { const stages = groupByStage(graph.nodes); - const workflows = [ - ...new Set(graph.edges.filter((e) => e.kind === 'workflow').map((e) => e.label)), - ].sort(); + const workflows = workflowLabels(graph); + const defaultWf = workflows.includes('full_booking') ? 'full_booking' : (workflows[0] ?? ''); + const nodesById = Object.fromEntries(graph.nodes.map((n) => [n.id, n])); - const columnsHtml = stages - .map(([stage, agents], colIdx) => { - const cards = agents - .map((a, cardIdx) => { - const status = a.contract_status === 'active' ? 'active' : 'stub'; - const delay = colIdx * 45 + cardIdx * 18; - return ` `; - }) + const wfMeta = Object.fromEntries( + workflows.map((w) => { + const chain = workflowChain(graph.edges, w); + return [w, { chain, count: chain.length }]; + }), + ); + + const stageTiles = stages + .map(([stage, agents], idx) => { + const span = BENTO_SPAN[stage] ?? 'span-2'; + const num = String(idx + 1).padStart(2, '0'); + const rows = agents + .map( + (a) => ` `, + ) .join('\n'); - return `
-

${escapeHtml(stage)} ${agents.length}

-
-${cards} -
-
`; + const preview = agents + .slice(0, 2) + .map((a) => a.name) + .join(' · '); + const more = agents.length > 2 ? ` · +${agents.length - 2}` : ''; + return `
+ +
+${rows} +
+
`; }) .join('\n'); - const workflowPills = [ - ``, - ...workflows.map( - (w) => - ``, - ), - ].join('\n '); + const wfSegs = workflows + .map((w) => { + const meta = wfMeta[w]!; + const active = w === defaultWf ? ' active' : ''; + return ` `; + }) + .join('\n'); - const payload = JSON.stringify(graph); + const payload = JSON.stringify({ + nodes: graph.nodes, + edges: graph.edges, + workflows: wfMeta, + defaultWorkflow: defaultWf, + stages: Object.fromEntries(stages.map(([s, a]) => [s, a.map((x) => x.id)])), + }); + + // Seed stage HTML for default workflow (JS will re-render) + const seedChain = defaultWf ? workflowChain(graph.edges, defaultWf) : []; + const seedStage = seedChain + .map((id, i) => { + const n = nodesById[id]; + if (!n) return ''; + const conn = + i < seedChain.length - 1 + ? `` + : ''; + return `
+ + ${conn} +
`; + }) + .join('\n'); return ` - Otaip Agent Map · Telivity - + Agent connections · Telivity Otaip + + + + + - + +
+ + + Telivity + + Agent connections + ${graph.total_nodes} agents · ${graph.total_edges} edges +
+
-
-
-

Telivity · Otaip — open source

-

Agent map for the whole travel transaction.

-

${graph.total_nodes} typed agents. Search to settlement. Solid cyan arcs are Orchestrator workflows; dashed traces are package dependencies.

-
-
-
${graph.total_nodes}Agents
-
${stages.length}Stages
-
${workflows.length}Workflows
-
${graph.total_edges}Edges
+
+
+
+

How agents connect

+

Pick a workflow. Watch the chain. Open any agent for its neighbors.

+
+
+${wfSegs} +
-
-
- - - -
- ${workflowPills} -
-
- Active - Stub - Workflow - Package +
+
Workflow path
+
+${seedStage || '

No workflow edges in the graph.

'} +
-
+ -
-
- - - -
-${columnsHtml} +
+
+
+

All agents

+

Stages light up when they sit on the active path. Select an agent to inspect edges.

+
+ + + +
+
+
+${stageTiles}
- -
+
- Telivity — AI travel infrastructure · Otaip open core · - ${graph.total_nodes} agents · ${graph.total_edges} edges · generated from agents.graph.json · pnpm gen:agent-map + + Telivity Otaip + · + ${graph.total_nodes} agents · ${graph.total_edges} edges · from agents.graph.json
+ + +