대시보드
Morit Plugin4. 기능, 데이터, 사용자 제어

Plugin Local Storage와 AI 접근

Plugin Local Storage는 Host가 관리하는 영구 JSON 저장소입니다. 데이터는 user_id + installation_id + instance_id + namespace + key로 분리되며 플러그인 코드가 파일 경로나 DB에 직접 접근하지 않습니다. UI, Tool, Skill, Background는 같은 Instance 저장소를 사용합니다.

Manifest 선언

json
{
  "schema_version": 2,
  "permissions": ["storage", "ai_storage"],
  "data_policy": "retain",
  "storage": {
    "version": 2,
    "namespaces": [
      {"id": "default", "max_bytes": 131072, "ai_access": "read"},
      {"id": "tasks", "max_bytes": 262144, "ai_access": "read_write"}
    ],
    "migrations": [
      {
        "from": 1,
        "to": 2,
        "namespace": "tasks",
        "rename": {"today": "today_tasks"},
        "delete": ["legacy_filter"]
      }
    ]
  }
}
  • storage 선언에는 schema 2와 storage 권한이 필요합니다.
  • namespace는 1~16개, ID는 안전한 plugin identifier입니다.
  • max_bytes는 1~256 KiB이고 전체 Installation은 512 KiB 이하입니다.
  • namespace마다 최대 256개 key, key는 최대 80자, 값 하나는 UTF-8 JSON 32 KiB 이하입니다.
  • migration 선언은 최대 64개이며 version은 1부터 1000까지 한 단계씩 증가합니다.
  • 값은 문자열, 유한한 숫자, boolean, null, JSON 객체·목록을 사용할 수 있습니다.
  • ai_accessnone, read, read_write입니다. 하나라도 AI에 공개하면 Manifest에 ai_storage 권한도 선언해야 합니다.

schema 1 플러그인이 기존 storage 권한만 요청한 경우 Host는 호환용 default namespace(version 1, 128 KiB)를 제공합니다. 새 플러그인은 명시적 schema 2 선언을 사용하세요.

표준 Storage Tool

Host는 저장소를 선언한 각 Instance에 다음 capability를 제공합니다.

Toolarguments동작
morit.storage.listnamespace, 선택 query, 선택 limitkey와 값 검색·목록
morit.storage.getnamespace, key단일 항목 조회
morit.storage.createnamespace, key, value없는 key 생성
morit.storage.updatenamespace, key, value, revisionrevision 일치 시 수정
morit.storage.deletenamespace, key, revisionrevision 일치 시 삭제

namespace를 생략하면 default, 없으면 첫 namespace를 사용합니다. list.limit은 1~100이고 응답 크기 때문에 일부만 반환하면 data.has_moretrue입니다. create는 같은 key를 덮어쓰지 않으며 update/delete는 직전에 읽은 양의 정수 revision을 요구합니다.

모든 결과는 공통 ExtensionResult 형태입니다.

json
{
  "summary": "task-42 항목을 수정했어요.",
  "data": {
    "namespace": "tasks",
    "item": {
      "key": "task-42",
      "value": {"title": "과학 보고서", "done": true},
      "revision": 3,
      "created_at": "2026-08-17T01:00:00+00:00",
      "updated_at": "2026-08-17T02:00:00+00:00"
    }
  },
  "evidence": [
    {"type": "plugin_storage", "instance_id": "...", "namespace": "tasks", "key": "task-42", "revision": 3}
  ]
}

Saved Entity

AI에서 저장·즐겨찾기한 Plugin 항목은 별도 DB 사본을 만들지 않고 같은 Local Storage의 saved_entities namespace를 source of truth로 사용합니다. UI, Tool, Skill과 AI 표준 Storage Tool이 같은 key·revision을 읽고 쓰므로 한 경로의 변경이 다른 경로에도 즉시 반영됩니다.

json
{
  "type": "school.assignment",
  "title": "과학 보고서",
  "subtitle": "금요일까지",
  "description": "실험 결과 정리",
  "url": "https://school.example/assignments/42",
  "image_url": "https://school.example/images/42.png",
  "favorite": true,
  "data": {"due": "2026-08-21"},
  "source_capability": "school_life.assignments"
}

typetitle은 필수입니다. 선택 필드는 subtitle, description, HTTPS url/image_url, boolean favorite, bounded JSON data, 안전한 identifier source_capability뿐입니다. CRUD는 morit.storage.list/get/create/update/delete를 그대로 사용하며 기존 namespace 권한, quota, revision, generation, credential 차단 검사가 동일하게 적용됩니다.

UI binding과 갱신

UI Runtime v2 data source에서 표준 Tool을 그대로 사용합니다.

json
{
  "id": "saved_tasks",
  "capability": "morit.storage.list",
  "trigger": "load",
  "query": "",
  "arguments": {"namespace": "tasks", "limit": 20}
}

표시는 data.saved_tasks.data.items를 source로 사용하는 list, 단일 값은 {{data.saved_task.data.item.value.title}}처럼 binding합니다. UI가 Storage create/update/delete를 완료하거나 AI 응답이 끝나면 Host가 storage revision을 갱신해 Storage data source만 다시 읽습니다. 앱 재개 시에도 갱신합니다. 일반 Tool 결과 data source는 불필요하게 재실행하지 않습니다. 서버 Background가 화면과 무관하게 값을 바꾸는 namespace는 source에 refresh_seconds: 30 이상을 선언해 화면이 열린 동안에도 주기적으로 맞춥니다.

Tool·Skill·Background capability는 storage 권한을 선언하면 동일 Instance의 plugin_storage snapshot을 읽고, 결과의 data.host_actionsplugin_storage_set 또는 plugin_storage_delete를 반환해 같은 canonical 저장소를 원자적으로 변경할 수 있습니다. namespace를 명시하면 해당 namespace를 사용합니다.

AI 권한과 보안 경계

설치 또는 설정에서 사용자가 ai_storage를 한 번 허용하면 Morit AI는 매 CRUD마다 다시 승인받지 않습니다. 하지만 다음 검사는 항상 적용됩니다.

  • 현재 사용자에게 속하고 활성화된 Instance만 선택
  • 해당 Installation·Instance·namespace와 package generation 일치
  • ai_access: read는 list/get만, read_write만 mutation 허용
  • 다른 사용자, 다른 Plugin/Instance, 선언하지 않은 namespace 접근 차단
  • key 이름에 credential, password, secret, token, access token, refresh token, API key, private key 계열을 저장하지 못하도록 차단
  • 실행 코드·파일 경로·DB 접근은 제공하지 않음

Host가 AI에 노출하는 tool handle은 Instance와 capability를 해시한 불투명 ID입니다. AI는 임의 instance_id를 arguments로 넘겨 scope를 바꿀 수 없습니다.

오류 구분

code의미처리
PLUGIN_STORAGE_PERMISSION_DENIED권한·namespace·AI access 부족설정과 manifest 확인
PLUGIN_STORAGE_NOT_FOUNDkey 없음create 여부를 사용자 의도에 따라 결정
PLUGIN_STORAGE_QUOTA_EXCEEDED값·namespace·Installation 한도 초과데이터를 줄이거나 정리
PLUGIN_STORAGE_CONFLICT중복 create, stale revision/generationlist/get 후 최신 revision으로 재시도
PLUGIN_STORAGE_INVALIDkey, JSON, query, arguments 형식 오류입력 수정

오류를 not-found나 빈 목록으로 바꾸지 마세요. 특히 conflict에서 revision 검사를 제거하면 동시에 수정한 사용자 데이터를 잃을 수 있습니다.

업데이트, migration, 삭제, 초기화

  • 업데이트: 같은 Installation/Instance의 데이터는 유지되고 설치 transaction 안에서 version을 한 단계씩 올립니다. 각 (from, namespace) migration은 한 번만 선언하며 rename 대상 충돌 시 업데이트 전체가 실패해 이전 package와 데이터가 유지됩니다.
  • data_policy: purge: uninstall 때 Plugin Local Storage를 삭제합니다.
  • data_policy: retain: uninstall 뒤 같은 사용자·Plugin Installation ID로 재설치할 때 복구합니다.
  • credential, OAuth state, pending action은 retain과 무관하게 별도 보안 수명주기로 정리됩니다.
  • 사용자가 설정의 저장 데이터 초기화를 누르면 해당 Instance 데이터만 삭제합니다. HTTP API는 DELETE /v1/plugin-instances/{instance_id}/storage이며 다른 Instance에는 영향이 없습니다.
  • disable은 데이터를 삭제하지 않지만 읽기·쓰기를 모두 막습니다.

배포 전에는 두 사용자와 둘 이상의 Instance로 격리, read-only AI mutation 차단, quota, stale revision/generation, purge/retain, migration 충돌과 재시작 복원을 검증하세요.