ActorCanvas actor.canvas@1

Generated at from openvibe-contracts v0.419.0 and openvibe-sdk v0.161.0.

Version
1.0.0
Owner
actor
Visibility
public
Status
active
Compatibility
backward
Decision
ADR-059
Schema
https://openvibe.network/contracts/actor/canvas.v1.json

actor.canvas@1: an agent's live panel beside the conversation (ADR-059) — a board of a trip, a table of prices, a checklist of what is left. It is DATA: a list of blocks drawn by one renderer (OpenVibe.Shared ov-canvas.js) wherever it is shown (Chat, Surf, the Dock, Actor). There is no HTML, no style, no script and no frame: text is plain and the renderer escapes it; an image is only a picture on openvibe.media; a link opens in a new tab with its host shown. A canvas acts only through the person: a button sends its label to the agent as the person's own message (action ask), and ticking a checklist item tells the agent which item changed; the agent's next steps go through its level and approvals like any other. VERSION only grows: a viewer ignores an older one, and the agent replaces the whole document, never part of it.

Fields

FieldTypeRequiredDescriptionConstraints
canvasconstyes
  • = "actor.canvas@1"
idstringyes
  • pattern ^cnv_[0-9A-HJKMNP-TV-Z]{26}$
titlestringyes
  • minLength 1
  • maxLength 120
versionintegeryesGrows with every update; a viewer ignores an older version.
  • minimum 1
agent_idstringThe agent that draws it.
  • pattern ^agt_[0-9A-HJKMNP-TV-Z]{26}$
threadstringThe conversation it sits beside (Actor's task or chat id).
  • maxLength 64
updated_atstringyes
  • format date-time
blocksarray of one ofyes
  • maxItems 60

Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

Valid: minimal
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Notes",
  "version": 1,
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": []
}
Valid: trip
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "heading",
      "text": "Lisbon, 14–17 November",
      "level": 1
    },
    {
      "type": "metric",
      "label": "Flights and hotel",
      "value": "€612",
      "note": "two people, booked nothing yet"
    },
    {
      "type": "checklist",
      "id": "todo",
      "items": [
        {
          "id": "flights",
          "text": "Pick the flights",
          "done": true
        },
        {
          "id": "hotel",
          "text": "Choose a hotel near Alfama",
          "done": false
        }
      ]
    },
    {
      "type": "table",
      "id": "hotels",
      "columns": [
        "Hotel",
        "Night",
        "Walk to the river"
      ],
      "rows": [
        [
          "Casa A",
          118,
          "6 min"
        ],
        [
          "Hotel B",
          96,
          "14 min"
        ]
      ]
    },
    {
      "type": "chart",
      "id": "prices",
      "kind": "line",
      "unit": "€",
      "series": [
        {
          "name": "Flights",
          "points": [
            {
              "x": "Mon",
              "y": 240
            },
            {
              "x": "Tue",
              "y": 212
            },
            {
              "x": "Wed",
              "y": 199
            }
          ]
        }
      ]
    },
    {
      "type": "image",
      "src": "https://openvibe.media/t/lisbon.jpg",
      "alt": "The river at dusk"
    },
    {
      "type": "link",
      "href": "https://openvibe.video/watch?v=AbCdEfGhIjK",
      "text": "The walking tour video"
    },
    {
      "type": "text",
      "text": "Casa A is closer to the river; Hotel B is cheaper.\nBoth have free cancellation until Friday."
    },
    {
      "type": "button",
      "label": "Book Casa A",
      "action": "ask"
    }
  ]
}
Rejected: button-runs
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "button",
      "label": "Delete everything",
      "action": "run"
    }
  ]
}
Rejected: foreign-image
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "image",
      "src": "https://tracker.example/p.png",
      "alt": "x"
    }
  ]
}
Rejected: html-block
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "html",
      "html": "<script>alert(1)</script>"
    }
  ]
}
Rejected: http-link
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "link",
      "href": "javascript:alert(1)",
      "text": "x"
    }
  ]
}
Rejected: too-many-blocks
{
  "canvas": "actor.canvas@1",
  "id": "cnv_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "title": "Lisbon, 14–17 November",
  "version": 3,
  "agent_id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "thread": "tsk_42",
  "updated_at": "2026-11-08T01:00:00.000Z",
  "blocks": [
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    },
    {
      "type": "text",
      "text": "x"
    }
  ]
}

Validate

const contracts = require('openvibe-contracts');
contracts.validate('actor.canvas@1', value);   // { valid, errors: [{ path, message }] }