@yeaft/webchat-agent 1.0.627 → 1.0.628

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.627",
3
+ "version": "1.0.628",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -12,7 +12,7 @@ export const PRESENTATION_SCHEMA = obj({
12
12
  phase: { type: 'string', enum: ['progress', 'final'] },
13
13
  blocks: arr({ anyOf: [
14
14
  block('markdown', { text: str(12000) }, ['text']),
15
- block('cards', { items: arr(obj({ title: str(160), text: str(2000), label: str(80) }, ['title', 'text']), 8) }, ['items']),
15
+ block('cards', { items: arr(obj({ title: str(160), text: str(2000), label: str(80), image: obj({ url: str(2000), alt: str(240), fit: { type: 'string', enum: ['cover', 'contain'] } }, ['url', 'alt']), url: str(2000) }, ['title', 'text']), 8) }, ['items']),
16
16
  block('metrics', { items: arr(obj({ label: str(120), value: str(120), detail: str(240) }, ['label', 'value']), 8) }, ['items']),
17
17
  block('table', { columns: arr(str(120), 8), rows: arr(arr(str(1000), 8, 1), 50) }, ['columns', 'rows']),
18
18
  block('chart', { style: { type: 'string', enum: ['bar', 'line'] }, unit: str(60), series: arr(obj({ label: str(120), values: arr({ type: 'number' }, 40) }, ['label', 'values']), 4), labels: arr(str(120), 40) }, ['style', 'series', 'labels']),
@@ -20,11 +20,11 @@ export const PRESENTATION_SCHEMA = obj({
20
20
  block('diagram', { nodes: arr(obj({ id: str(40), label: str(160) }, ['id', 'label']), 12), edges: arr(obj({ from: str(40), to: str(40), label: str(120) }, ['from', 'to']), 24, 0) }, ['nodes', 'edges']),
21
21
  block('image', { url: str(2000), alt: str(240), caption: str(1000) }, ['url', 'alt']),
22
22
  block('link', { url: str(2000), text: str(1000) }, ['url', 'text']),
23
- block('map', { points: arr(obj({ label: str(160), latitude: { type: 'number', minimum: -90, maximum: 90 }, longitude: { type: 'number', minimum: -180, maximum: 180 }, detail: str(1000) }, ['label', 'latitude', 'longitude']), 20) }, ['points']),
23
+ block('map', { points: arr(obj({ label: str(160), latitude: { type: 'number', minimum: -90, maximum: 90 }, longitude: { type: 'number', minimum: -180, maximum: 180 }, detail: str(1000) }, ['label', 'latitude', 'longitude']), 20), embed: obj({ provider: { type: 'string', enum: ['openstreetmap', 'google'] }, url: str(2000) }, ['provider']) }, ['points']),
24
24
  ] }, 12),
25
25
  }, ['summary', 'blocks']);
26
26
 
27
- export const PRESENT_RESPONSE_DESCRIPTION = `Publish a rich user-visible response inside this turn: cards, metrics, comparison tables, bar/line charts, timelines, relationship diagrams, images, artifact links or coordinate maps. Optional: use normal text for simple answers. Supply a self-contained summary for copying, search, model history and older clients; do not repeat the whole rich answer afterwards. Use only verified data, mark uncertainty, and never fabricate chart values or coordinates. No HTML/JavaScript, arbitrary SVG, styling or executable content. Image/link URLs must be credential-free HTTP(S); images load automatically in the reader’s browser without a referrer. Links open in the owning in-app browser by default; the reader can explicitly choose a new tab. Maps are coordinate sketches, not navigation maps, and do not fetch tiles. phase=progress continues work; phase=final is a delivery intent, not proof of task success. Runtime still owns persistence, cancellation and turn termination. Validation failure publishes nothing; correct the payload or answer in text.`;
27
+ export const PRESENT_RESPONSE_DESCRIPTION = `Publish a rich user-visible response inside this turn: cards, metrics, comparison tables, bar/line charts, timelines, relationship diagrams, images, artifact links or maps. Cards may include an optional image {url,alt,fit:cover|contain} and source/action url; use contain for weather icons or team logos and cover for photography. Optional: use normal text for simple answers. Supply a self-contained summary for copying, search, model history and older clients; do not repeat the whole rich answer afterwards. Use only verified data, mark uncertainty, and never fabricate chart values or coordinates. No HTML/JavaScript, arbitrary SVG, styling or executable content. Image/link URLs must be credential-free HTTP(S); images load automatically in the reader’s browser without a referrer. Links open in the owning in-app browser by default; the reader can explicitly choose a new tab. Maps default to offline coordinate sketches. Set map.embed={provider:openstreetmap} for the dedicated OSM street-map embed, or {provider:google,url} with a verified official https://www.google.com/maps/embed?pb=... share URL (not a homepage or API-key URL). Embed availability depends on the reader's network; coordinates remain available. Never invent routes, ETAs, weather or scores; attach verified image sources and dated data. phase=progress continues work; phase=final is a delivery intent, not proof of task success. Runtime still owns persistence, cancellation and turn termination. Validation failure publishes nothing; correct the payload or answer in text.`;
28
28
 
29
29
  function check(value, schema, path) {
30
30
  if (schema.anyOf) {
@@ -62,6 +62,11 @@ export function normalizePresentation(input) {
62
62
  if (ids.size !== b.nodes.length || b.edges.some(e => !ids.has(e.from) || !ids.has(e.to))) throw new Error('diagram: unique nodes and existing edge endpoints required');
63
63
  }
64
64
  if (b.type === 'image' || b.type === 'link') b.url = safePresentationUrl(b.url);
65
+ if (b.type === 'cards') for (const item of b.items) {
66
+ if (item.image) item.image.url = safePresentationUrl(item.image.url);
67
+ if (item.url) item.url = safePresentationUrl(item.url);
68
+ }
69
+ if (b.type === 'map' && b.embed) b.embed = normalizeMapEmbed(b.embed);
65
70
  }
66
71
  const canonical = { version: 1, title: value.title || '', summary: value.summary, phase: value.phase || 'final', blocks: value.blocks };
67
72
  if (new TextEncoder().encode(JSON.stringify(canonical)).length > 49152) throw new Error('response: exceeds 48 KiB');
@@ -79,3 +84,17 @@ export function safePresentationUrl(value) {
79
84
  if (url.href.length > 2000) throw new Error('response: URL exceeds 2000 characters');
80
85
  return url.href;
81
86
  }
87
+
88
+ /** Only official map share embeds are allowed. API keys/configuration never enter transcripts. */
89
+ export function normalizeMapEmbed(embed) {
90
+ if (embed?.provider === 'openstreetmap' && embed.url === undefined) return { provider: 'openstreetmap' };
91
+ if (embed?.provider === 'google') {
92
+ const url = new URL(safePresentationUrl(embed.url));
93
+ const params = [...url.searchParams];
94
+ if (url.origin === 'https://www.google.com' && url.pathname === '/maps/embed' && !url.hash
95
+ && params.length === 1 && params[0][0] === 'pb' && /^![^\s\u0000-\u001f\u007f]+$/.test(params[0][1])) {
96
+ return { provider: 'google', url: url.href };
97
+ }
98
+ }
99
+ throw new Error('map: use generated openstreetmap embed or an official Google Maps share embed URL (pb only, no API key)');
100
+ }