[{"data":1,"prerenderedAt":1658},["ShallowReactive",2],{"search-de":3,"content-de-developer\u002Fplugin-authoring":4,"surround-de-\u002Fdeveloper\u002Fplugin-authoring":1651},[],{"id":5,"title":6,"body":7,"description":1644,"extension":1645,"meta":1646,"navigation":147,"path":1647,"seo":1648,"stem":1649,"__hash__":1650},"content_de\u002F3.developer\u002F41.plugin-authoring.md","Ein Plugin bauen",{"type":8,"value":9,"toc":1633},"minimark",[10,20,25,69,75,97,105,109,117,237,263,270,275,334,338,349,359,968,1017,1025,1029,1036,1248,1254,1422,1452,1464,1468,1475,1508,1523,1529,1544,1548,1574,1591,1594,1598,1629],[11,12,13,14,19],"p",{},"Dies ist der Weg des Plugin-Autors vom leeren Verzeichnis bis zur Contribution, die in einem Deployment läuft. Ein Checkout des Owlat-Workspace wird vorausgesetzt; die Betreiberseite finden Sie unter ",[15,16,18],"a",{"href":17},"\u002Fdeveloper\u002Fplugin-operations","Installation & Betrieb",".",[21,22,24],"h2",{"id":23},"_1-das-paket-scaffolden","1. Das Paket scaffolden",[26,27,32],"pre",{"className":28,"code":29,"language":30,"meta":31,"style":31},"language-sh shiki shiki-themes github-light github-dark-dimmed","bun run plugins:prepare                                  # build the plugin-kit contracts once\nbun packages\u002Fplugin-cli\u002Fsrc\u002Findex.ts create hello-owlat\n","sh","",[33,34,35,55],"code",{"__ignoreMap":31},[36,37,40,44,48,51],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"sOLd2","bun",[36,45,47],{"class":46},"s-HuK"," run",[36,49,50],{"class":46}," plugins:prepare",[36,52,54],{"class":53},"sDN9O","                                  # build the plugin-kit contracts once\n",[36,56,58,60,63,66],{"class":38,"line":57},2,[36,59,43],{"class":42},[36,61,62],{"class":46}," packages\u002Fplugin-cli\u002Fsrc\u002Findex.ts",[36,64,65],{"class":46}," create",[36,67,68],{"class":46}," hello-owlat\n",[11,70,71,74],{},[33,72,73],{},"create"," schreibt ausschließlich Dateien — es installiert, importiert oder führt niemals etwas aus, ist bei erneutem Lauf idempotent, verweigert das Überschreiben einer Datei mit abweichendem Inhalt und rollt jede angelegte Datei und jedes angelegte Verzeichnis zurück, wenn ein Schreibvorgang fehlschlägt.",[11,76,77,78,81,82,85,86,89,90,93,94,19],{},"Standardwerte: Paketname ",[33,79,80],{},"@owlat\u002Fplugin-\u003Cid>",", Verzeichnis ",[33,83,84],{},"examples\u002Fplugins\u002F\u003Cid>",". Überschreiben Sie sie mit ",[33,87,88],{},"--name"," und ",[33,91,92],{},"--dir","; eine Vorschau erhalten Sie mit ",[33,95,96],{},"--dry-run",[26,98,103],{"className":99,"code":101,"language":102,"meta":31},[100],"language-text","examples\u002Fplugins\u002Fhello-owlat\u002F\n├── package.json          # depends on @owlat\u002Fplugin-kit (workspace:*)\n├── tsconfig.json         # extends the workspace base config\n├── vitest.config.ts      # aliases @owlat\u002Fplugin-kit to its sources\n├── README.md\n└── src\u002F\n    ├── manifest.ts       # the definePlugin declaration\n    ├── index.ts          # re-exports the manifest\n    └── __tests__\u002Fmanifest.test.ts\n","text",[33,104,101],{"__ignoreMap":31},[21,106,108],{"id":107},"_2-das-manifest-deklarieren","2. Das Manifest deklarieren",[11,110,111,112,116],{},"Das Manifest ist der gesamte Vertrag. Der Host leitet Berechtigungen, Feature-Flags, das Einstellungsformular und die generierte Komposition aus diesen Daten ab, ",[113,114,115],"strong",{},"ohne Plugin-Code auszuführen"," — halten Sie es deshalb als statische, rein datenbasierte Deklaration.",[26,118,122],{"className":119,"code":120,"language":121,"meta":31,"style":31},"language-ts shiki shiki-themes github-light github-dark-dimmed","import { definePlugin } from '@owlat\u002Fplugin-kit';\n\nexport const helloPlugin = definePlugin({\n    id: 'hello-owlat',\n    version: '0.1.0',\n    capabilities: ['plugin-storage:read', 'plugin-storage:write'],\n    \u002F\u002F Storage capabilities REQUIRE a flag: access must be revocable at runtime.\n    flag: { default: false },\n});\n","ts",[33,123,124,143,149,172,184,195,213,219,231],{"__ignoreMap":31},[36,125,126,130,134,137,140],{"class":38,"line":39},[36,127,129],{"class":128},"s7YZ4","import",[36,131,133],{"class":132},"sYgZi"," { definePlugin } ",[36,135,136],{"class":128},"from",[36,138,139],{"class":46}," '@owlat\u002Fplugin-kit'",[36,141,142],{"class":132},";\n",[36,144,145],{"class":38,"line":57},[36,146,148],{"emptyLinePlaceholder":147},true,"\n",[36,150,152,155,158,162,165,169],{"class":38,"line":151},3,[36,153,154],{"class":128},"export",[36,156,157],{"class":128}," const",[36,159,161],{"class":160},"sviXB"," helloPlugin",[36,163,164],{"class":128}," =",[36,166,168],{"class":167},"sPO5f"," definePlugin",[36,170,171],{"class":132},"({\n",[36,173,175,178,181],{"class":38,"line":174},4,[36,176,177],{"class":132},"    id: ",[36,179,180],{"class":46},"'hello-owlat'",[36,182,183],{"class":132},",\n",[36,185,187,190,193],{"class":38,"line":186},5,[36,188,189],{"class":132},"    version: ",[36,191,192],{"class":46},"'0.1.0'",[36,194,183],{"class":132},[36,196,198,201,204,207,210],{"class":38,"line":197},6,[36,199,200],{"class":132},"    capabilities: [",[36,202,203],{"class":46},"'plugin-storage:read'",[36,205,206],{"class":132},", ",[36,208,209],{"class":46},"'plugin-storage:write'",[36,211,212],{"class":132},"],\n",[36,214,216],{"class":38,"line":215},7,[36,217,218],{"class":53},"    \u002F\u002F Storage capabilities REQUIRE a flag: access must be revocable at runtime.\n",[36,220,222,225,228],{"class":38,"line":221},8,[36,223,224],{"class":132},"    flag: { default: ",[36,226,227],{"class":160},"false",[36,229,230],{"class":132}," },\n",[36,232,234],{"class":38,"line":233},9,[36,235,236],{"class":132},"});\n",[11,238,239,242,243,246,247,250,251,254,255,258,259,262],{},[33,240,241],{},"definePlugin"," erhält die literale TypeScript-Inferenz und validiert zur Laufzeit; im Fehlerfall wird ein ",[33,244,245],{},"PluginManifestError"," mit einer Liste von ",[33,248,249],{},"{ code, path, message }","-Issues geworfen. Für Tooling existieren zwei Geschwisterformen: ",[33,252,253],{},"validatePluginManifest(value)"," liefert ",[33,256,257],{},"{ ok, manifest | issues }"," und wirft nie, ",[33,260,261],{},"parsePluginManifest(value)"," wirft.",[11,264,265,266,269],{},"Die Validierung weist unbekannte Felder, Accessor-Properties, fehlerhafte Bezeichner, doppelte Capabilities oder Umgebungsvariablen, ungültige Budgets, unsichere Export-Pfade und unbekannte Contribution-Buckets zurück. Referenzen werden als ",[113,267,268],{},"Daten"," inspiziert und nie aufgerufen.",[271,272,274],"h3",{"id":273},"regeln-die-der-validator-durchsetzt","Regeln, die der Validator durchsetzt",[276,277,278,291,303,320,331],"ul",{},[279,280,281,282,286,287,290],"li",{},"Ein Contribution-Bucket erfordert sowohl seine Capability ",[283,284,285],"em",{},"als auch"," ein explizites ",[33,288,289],{},"flag",". Sie können keine Contribution ausliefern, die ein Betreiber nicht abschalten kann.",[279,292,293,296,297,300,301,19],{},[33,294,295],{},"plugin-storage:read"," \u002F ",[33,298,299],{},"plugin-storage:write"," erfordern aus demselben Grund ein explizites ",[33,302,289],{},[279,304,305,308,309,311,312,315,316,319],{},[33,306,307],{},"llm:invoke"," erfordert ein explizites ",[33,310,289],{}," ",[113,313,314],{},"und"," ein gültiges ",[33,317,318],{},"llmBudget.dailyUsd"," (> 0, ≤ 1.000.000, höchstens sechs Nachkommastellen).",[279,321,322,323,326,327,330],{},"Die ",[33,324,325],{},"signature.secretEnvVar"," eines Feedback-Webhooks muss zusätzlich in ",[33,328,329],{},"flag.requiredEnvVars"," aufgeführt sein. Die Route, die sie speist, kann ohne das Secret nichts verifizieren — deshalb muss ihr Fehlen bei der Aktivierung auffallen und nicht bei jeder einzelnen Zustellung.",[279,332,333],{},"Obergrenzen: 64 Capabilities, 64 erforderliche Umgebungsvariablen, 256 Einträge pro Contribution-Bucket, 64 Einstellungsfelder.",[21,335,337],{"id":336},"_3-contributions-hinzufügen","3. Contributions hinzufügen",[11,339,340,341,344,345,348],{},"Jede Contribution ist ein Daten-Deskriptor, dessen ausführbare Hälfte an genau einem ",[113,342,343],{},"bedingungsunabhängigen Paket-Export"," liegt, zum Beispiel ",[33,346,347],{},".\u002Fgate",". Die Codegen prüft, dass der Export existiert, ohne ihn auszuführen, und erzeugt den statischen Import.",[11,350,351,352,355,356,358],{},"Das Manifest unten erweitert dieselbe ",[33,353,354],{},"manifest.ts"," und ergänzt daher nur die Capability-Konstanten — ",[33,357,241],{}," ist oben bereits importiert.",[26,360,362],{"className":119,"code":361,"language":121,"meta":31,"style":31},"import {\n    PLUGIN_AUTONOMY_GATE_CAPABILITY,\n    PLUGIN_CRON_CAPABILITY,\n    PLUGIN_NAV_ITEM_CAPABILITY,\n    PLUGIN_SETTINGS_PANEL_CAPABILITY,\n    PLUGIN_WORKER_CAPABILITY,\n} from '@owlat\u002Fplugin-kit';\n\nexport const preflightPlugin = definePlugin({\n    id: 'preflight',\n    version: '1.0.0',\n    capabilities: [\n        PLUGIN_AUTONOMY_GATE_CAPABILITY,\n        PLUGIN_CRON_CAPABILITY,\n        PLUGIN_NAV_ITEM_CAPABILITY,\n        PLUGIN_SETTINGS_PANEL_CAPABILITY,\n        PLUGIN_WORKER_CAPABILITY,\n        'llm:invoke',\n    ],\n    \u002F\u002F Contributions REQUIRE an explicit flag: an operator must be able to turn\n    \u002F\u002F the plugin off without a redeploy.\n    flag: { default: false, requiredEnvVars: ['PREFLIGHT_API_KEY'] },\n    \u002F\u002F `llm:invoke` additionally requires a hard daily budget in USD.\n    llmBudget: { dailyUsd: 1.5 },\n    contributes: {\n        sendGates: [\n            {\n                id: 'preflight',\n                label: 'Pre-send preflight',\n                module: { exportPath: '.\u002Fgate' },\n                timeoutMs: 15_000,\n            },\n        ],\n        crons: [\n            {\n                id: 'refresh-rules',\n                label: 'Refresh preflight rules',\n                module: { exportPath: '.\u002Fcron' },\n                schedule: { intervalMinutes: 360 },\n                timeoutMs: 60_000,\n            },\n        ],\n        navItems: [\n            {\n                id: 'dashboard',\n                section: 'administration',\n                name: 'Preflight',\n                href: '\u002Fdashboard\u002Fadmin\u002Finstance\u002Fplugins\u002Fpreflight',\n                icon: 'lucide:radar',\n            },\n        ],\n        settingsPanels: [\n            {\n                id: 'settings',\n                name: 'Preflight',\n                href: '\u002Fdashboard\u002Fadmin\u002Finstance\u002Fplugins\u002Fpreflight',\n                icon: 'lucide:radar',\n            },\n        ],\n    },\n    settingsSchema: [\n        {\n            kind: 'boolean',\n            key: 'holdOnFail',\n            label: 'Hold sends that fail preflight',\n            default: true,\n        },\n        {\n            kind: 'secret',\n            key: 'vendorApiKey',\n            envVar: 'PLUGIN_VENDOR_API_KEY',\n            label: 'Vendor API key',\n            required: false,\n        },\n    ],\n});\n",[33,363,364,371,376,381,386,391,396,407,411,426,436,446,452,460,468,476,484,492,500,506,512,518,534,540,551,557,563,569,579,590,601,612,618,624,630,635,645,655,665,676,686,691,696,702,707,717,728,739,750,761,766,771,777,782,792,801,810,819,824,829,835,841,847,858,869,880,891,897,902,912,922,933,943,953,958,963],{"__ignoreMap":31},[36,365,366,368],{"class":38,"line":39},[36,367,129],{"class":128},[36,369,370],{"class":132}," {\n",[36,372,373],{"class":38,"line":57},[36,374,375],{"class":132},"    PLUGIN_AUTONOMY_GATE_CAPABILITY,\n",[36,377,378],{"class":38,"line":151},[36,379,380],{"class":132},"    PLUGIN_CRON_CAPABILITY,\n",[36,382,383],{"class":38,"line":174},[36,384,385],{"class":132},"    PLUGIN_NAV_ITEM_CAPABILITY,\n",[36,387,388],{"class":38,"line":186},[36,389,390],{"class":132},"    PLUGIN_SETTINGS_PANEL_CAPABILITY,\n",[36,392,393],{"class":38,"line":197},[36,394,395],{"class":132},"    PLUGIN_WORKER_CAPABILITY,\n",[36,397,398,401,403,405],{"class":38,"line":215},[36,399,400],{"class":132},"} ",[36,402,136],{"class":128},[36,404,139],{"class":46},[36,406,142],{"class":132},[36,408,409],{"class":38,"line":221},[36,410,148],{"emptyLinePlaceholder":147},[36,412,413,415,417,420,422,424],{"class":38,"line":233},[36,414,154],{"class":128},[36,416,157],{"class":128},[36,418,419],{"class":160}," preflightPlugin",[36,421,164],{"class":128},[36,423,168],{"class":167},[36,425,171],{"class":132},[36,427,429,431,434],{"class":38,"line":428},10,[36,430,177],{"class":132},[36,432,433],{"class":46},"'preflight'",[36,435,183],{"class":132},[36,437,439,441,444],{"class":38,"line":438},11,[36,440,189],{"class":132},[36,442,443],{"class":46},"'1.0.0'",[36,445,183],{"class":132},[36,447,449],{"class":38,"line":448},12,[36,450,451],{"class":132},"    capabilities: [\n",[36,453,455,458],{"class":38,"line":454},13,[36,456,457],{"class":160},"        PLUGIN_AUTONOMY_GATE_CAPABILITY",[36,459,183],{"class":132},[36,461,463,466],{"class":38,"line":462},14,[36,464,465],{"class":160},"        PLUGIN_CRON_CAPABILITY",[36,467,183],{"class":132},[36,469,471,474],{"class":38,"line":470},15,[36,472,473],{"class":160},"        PLUGIN_NAV_ITEM_CAPABILITY",[36,475,183],{"class":132},[36,477,479,482],{"class":38,"line":478},16,[36,480,481],{"class":160},"        PLUGIN_SETTINGS_PANEL_CAPABILITY",[36,483,183],{"class":132},[36,485,487,490],{"class":38,"line":486},17,[36,488,489],{"class":160},"        PLUGIN_WORKER_CAPABILITY",[36,491,183],{"class":132},[36,493,495,498],{"class":38,"line":494},18,[36,496,497],{"class":46},"        'llm:invoke'",[36,499,183],{"class":132},[36,501,503],{"class":38,"line":502},19,[36,504,505],{"class":132},"    ],\n",[36,507,509],{"class":38,"line":508},20,[36,510,511],{"class":53},"    \u002F\u002F Contributions REQUIRE an explicit flag: an operator must be able to turn\n",[36,513,515],{"class":38,"line":514},21,[36,516,517],{"class":53},"    \u002F\u002F the plugin off without a redeploy.\n",[36,519,521,523,525,528,531],{"class":38,"line":520},22,[36,522,224],{"class":132},[36,524,227],{"class":160},[36,526,527],{"class":132},", requiredEnvVars: [",[36,529,530],{"class":46},"'PREFLIGHT_API_KEY'",[36,532,533],{"class":132},"] },\n",[36,535,537],{"class":38,"line":536},23,[36,538,539],{"class":53},"    \u002F\u002F `llm:invoke` additionally requires a hard daily budget in USD.\n",[36,541,543,546,549],{"class":38,"line":542},24,[36,544,545],{"class":132},"    llmBudget: { dailyUsd: ",[36,547,548],{"class":160},"1.5",[36,550,230],{"class":132},[36,552,554],{"class":38,"line":553},25,[36,555,556],{"class":132},"    contributes: {\n",[36,558,560],{"class":38,"line":559},26,[36,561,562],{"class":132},"        sendGates: [\n",[36,564,566],{"class":38,"line":565},27,[36,567,568],{"class":132},"            {\n",[36,570,572,575,577],{"class":38,"line":571},28,[36,573,574],{"class":132},"                id: ",[36,576,433],{"class":46},[36,578,183],{"class":132},[36,580,582,585,588],{"class":38,"line":581},29,[36,583,584],{"class":132},"                label: ",[36,586,587],{"class":46},"'Pre-send preflight'",[36,589,183],{"class":132},[36,591,593,596,599],{"class":38,"line":592},30,[36,594,595],{"class":132},"                module: { exportPath: ",[36,597,598],{"class":46},"'.\u002Fgate'",[36,600,230],{"class":132},[36,602,604,607,610],{"class":38,"line":603},31,[36,605,606],{"class":132},"                timeoutMs: ",[36,608,609],{"class":160},"15_000",[36,611,183],{"class":132},[36,613,615],{"class":38,"line":614},32,[36,616,617],{"class":132},"            },\n",[36,619,621],{"class":38,"line":620},33,[36,622,623],{"class":132},"        ],\n",[36,625,627],{"class":38,"line":626},34,[36,628,629],{"class":132},"        crons: [\n",[36,631,633],{"class":38,"line":632},35,[36,634,568],{"class":132},[36,636,638,640,643],{"class":38,"line":637},36,[36,639,574],{"class":132},[36,641,642],{"class":46},"'refresh-rules'",[36,644,183],{"class":132},[36,646,648,650,653],{"class":38,"line":647},37,[36,649,584],{"class":132},[36,651,652],{"class":46},"'Refresh preflight rules'",[36,654,183],{"class":132},[36,656,658,660,663],{"class":38,"line":657},38,[36,659,595],{"class":132},[36,661,662],{"class":46},"'.\u002Fcron'",[36,664,230],{"class":132},[36,666,668,671,674],{"class":38,"line":667},39,[36,669,670],{"class":132},"                schedule: { intervalMinutes: ",[36,672,673],{"class":160},"360",[36,675,230],{"class":132},[36,677,679,681,684],{"class":38,"line":678},40,[36,680,606],{"class":132},[36,682,683],{"class":160},"60_000",[36,685,183],{"class":132},[36,687,689],{"class":38,"line":688},41,[36,690,617],{"class":132},[36,692,694],{"class":38,"line":693},42,[36,695,623],{"class":132},[36,697,699],{"class":38,"line":698},43,[36,700,701],{"class":132},"        navItems: [\n",[36,703,705],{"class":38,"line":704},44,[36,706,568],{"class":132},[36,708,710,712,715],{"class":38,"line":709},45,[36,711,574],{"class":132},[36,713,714],{"class":46},"'dashboard'",[36,716,183],{"class":132},[36,718,720,723,726],{"class":38,"line":719},46,[36,721,722],{"class":132},"                section: ",[36,724,725],{"class":46},"'administration'",[36,727,183],{"class":132},[36,729,731,734,737],{"class":38,"line":730},47,[36,732,733],{"class":132},"                name: ",[36,735,736],{"class":46},"'Preflight'",[36,738,183],{"class":132},[36,740,742,745,748],{"class":38,"line":741},48,[36,743,744],{"class":132},"                href: ",[36,746,747],{"class":46},"'\u002Fdashboard\u002Fadmin\u002Finstance\u002Fplugins\u002Fpreflight'",[36,749,183],{"class":132},[36,751,753,756,759],{"class":38,"line":752},49,[36,754,755],{"class":132},"                icon: ",[36,757,758],{"class":46},"'lucide:radar'",[36,760,183],{"class":132},[36,762,764],{"class":38,"line":763},50,[36,765,617],{"class":132},[36,767,769],{"class":38,"line":768},51,[36,770,623],{"class":132},[36,772,774],{"class":38,"line":773},52,[36,775,776],{"class":132},"        settingsPanels: [\n",[36,778,780],{"class":38,"line":779},53,[36,781,568],{"class":132},[36,783,785,787,790],{"class":38,"line":784},54,[36,786,574],{"class":132},[36,788,789],{"class":46},"'settings'",[36,791,183],{"class":132},[36,793,795,797,799],{"class":38,"line":794},55,[36,796,733],{"class":132},[36,798,736],{"class":46},[36,800,183],{"class":132},[36,802,804,806,808],{"class":38,"line":803},56,[36,805,744],{"class":132},[36,807,747],{"class":46},[36,809,183],{"class":132},[36,811,813,815,817],{"class":38,"line":812},57,[36,814,755],{"class":132},[36,816,758],{"class":46},[36,818,183],{"class":132},[36,820,822],{"class":38,"line":821},58,[36,823,617],{"class":132},[36,825,827],{"class":38,"line":826},59,[36,828,623],{"class":132},[36,830,832],{"class":38,"line":831},60,[36,833,834],{"class":132},"    },\n",[36,836,838],{"class":38,"line":837},61,[36,839,840],{"class":132},"    settingsSchema: [\n",[36,842,844],{"class":38,"line":843},62,[36,845,846],{"class":132},"        {\n",[36,848,850,853,856],{"class":38,"line":849},63,[36,851,852],{"class":132},"            kind: ",[36,854,855],{"class":46},"'boolean'",[36,857,183],{"class":132},[36,859,861,864,867],{"class":38,"line":860},64,[36,862,863],{"class":132},"            key: ",[36,865,866],{"class":46},"'holdOnFail'",[36,868,183],{"class":132},[36,870,872,875,878],{"class":38,"line":871},65,[36,873,874],{"class":132},"            label: ",[36,876,877],{"class":46},"'Hold sends that fail preflight'",[36,879,183],{"class":132},[36,881,883,886,889],{"class":38,"line":882},66,[36,884,885],{"class":132},"            default: ",[36,887,888],{"class":160},"true",[36,890,183],{"class":132},[36,892,894],{"class":38,"line":893},67,[36,895,896],{"class":132},"        },\n",[36,898,900],{"class":38,"line":899},68,[36,901,846],{"class":132},[36,903,905,907,910],{"class":38,"line":904},69,[36,906,852],{"class":132},[36,908,909],{"class":46},"'secret'",[36,911,183],{"class":132},[36,913,915,917,920],{"class":38,"line":914},70,[36,916,863],{"class":132},[36,918,919],{"class":46},"'vendorApiKey'",[36,921,183],{"class":132},[36,923,925,928,931],{"class":38,"line":924},71,[36,926,927],{"class":132},"            envVar: ",[36,929,930],{"class":46},"'PLUGIN_VENDOR_API_KEY'",[36,932,183],{"class":132},[36,934,936,938,941],{"class":38,"line":935},72,[36,937,874],{"class":132},[36,939,940],{"class":46},"'Vendor API key'",[36,942,183],{"class":132},[36,944,946,949,951],{"class":38,"line":945},73,[36,947,948],{"class":132},"            required: ",[36,950,227],{"class":160},[36,952,183],{"class":132},[36,954,956],{"class":38,"line":955},74,[36,957,896],{"class":132},[36,959,961],{"class":38,"line":960},75,[36,962,505],{"class":132},[36,964,966],{"class":38,"line":965},76,[36,967,236],{"class":132},[11,969,322,970,973,974,977,978,981,982,206,985,206,988,206,991,206,994,206,997,206,1000,206,1003,206,1006,1009,1010,1013,1014,1016],{},[33,971,972],{},"section"," eines ",[33,975,976],{},"navItems","-Eintrags muss einer der ",[113,979,980],{},"Kern-Sidebar-Section-Keys"," sein — ",[33,983,984],{},"inbox",[33,986,987],{},"postbox",[33,989,990],{},"chat",[33,992,993],{},"assistant",[33,995,996],{},"send",[33,998,999],{},"audience",[33,1001,1002],{},"knowledge",[33,1004,1005],{},"administration",[33,1007,1008],{},"preferences",". Ein Plugin kann keine Section anlegen, und der Host verwirft einen Eintrag mit unbekannter Section fail-closed — ein falscher Key wird also stillschweigend nie gerendert. Bestehende Plugins, die noch den früheren Key ",[33,1011,1012],{},"settings"," verwenden, werden während des Kompatibilitätsfensters an ",[33,1015,1005],{}," gehängt.",[11,1018,1019,1020,1024],{},"Jeder Bucket und seine Semantik sind in der ",[15,1021,1023],{"href":1022},"\u002Fdeveloper\u002Fplugin-contributions","Contribution-Referenz"," aufgeführt.",[21,1026,1028],{"id":1027},"_4-die-module-schreiben","4. Die Module schreiben",[11,1030,1031,1032,1035],{},"Ein Modul implementiert eine kleine Schnittstelle und erhält ",[113,1033,1034],{},"ausschließlich host-vermittelte Services"," — niemals einen Convex-Context, ein Datenbank-Handle, eine Umgebungsvariable, ein Credential, eine Tenant-ID oder eine Scheduler-Referenz.",[26,1037,1039],{"className":119,"code":1038,"language":121,"meta":31,"style":31},"import type {\n    PluginAutonomyGateInput,\n    PluginAutonomyGateModule,\n    PluginAutonomyGateResult,\n    PluginAutonomyGateServices,\n} from '@owlat\u002Fplugin-kit';\n\n\u002F** A gate may object or stand aside. There is deliberately no \"approve\" result. *\u002F\nexport const gate: PluginAutonomyGateModule = {\n    async evaluate(\n        input: PluginAutonomyGateInput,\n        services: PluginAutonomyGateServices\n    ): Promise\u003CPluginAutonomyGateResult> {\n        if (services.signal.aborted) return { outcome: 'objection', reason: 'preflight cancelled' };\n        if (input.draftBody.includes('http:\u002F\u002F')) {\n            return { outcome: 'objection', reason: 'draft contains a plaintext HTTP link' };\n        }\n        return { outcome: 'no-objection' };\n    },\n};\n",[33,1040,1041,1050,1055,1060,1065,1070,1080,1084,1089,1108,1119,1132,1142,1161,1187,1206,1222,1227,1239,1243],{"__ignoreMap":31},[36,1042,1043,1045,1048],{"class":38,"line":39},[36,1044,129],{"class":128},[36,1046,1047],{"class":128}," type",[36,1049,370],{"class":132},[36,1051,1052],{"class":38,"line":57},[36,1053,1054],{"class":132},"    PluginAutonomyGateInput,\n",[36,1056,1057],{"class":38,"line":151},[36,1058,1059],{"class":132},"    PluginAutonomyGateModule,\n",[36,1061,1062],{"class":38,"line":174},[36,1063,1064],{"class":132},"    PluginAutonomyGateResult,\n",[36,1066,1067],{"class":38,"line":186},[36,1068,1069],{"class":132},"    PluginAutonomyGateServices,\n",[36,1071,1072,1074,1076,1078],{"class":38,"line":197},[36,1073,400],{"class":132},[36,1075,136],{"class":128},[36,1077,139],{"class":46},[36,1079,142],{"class":132},[36,1081,1082],{"class":38,"line":215},[36,1083,148],{"emptyLinePlaceholder":147},[36,1085,1086],{"class":38,"line":221},[36,1087,1088],{"class":53},"\u002F** A gate may object or stand aside. There is deliberately no \"approve\" result. *\u002F\n",[36,1090,1091,1093,1095,1098,1101,1104,1106],{"class":38,"line":233},[36,1092,154],{"class":128},[36,1094,157],{"class":128},[36,1096,1097],{"class":160}," gate",[36,1099,1100],{"class":128},":",[36,1102,1103],{"class":42}," PluginAutonomyGateModule",[36,1105,164],{"class":128},[36,1107,370],{"class":132},[36,1109,1110,1113,1116],{"class":38,"line":428},[36,1111,1112],{"class":128},"    async",[36,1114,1115],{"class":167}," evaluate",[36,1117,1118],{"class":132},"(\n",[36,1120,1121,1125,1127,1130],{"class":38,"line":438},[36,1122,1124],{"class":1123},"stnAF","        input",[36,1126,1100],{"class":128},[36,1128,1129],{"class":42}," PluginAutonomyGateInput",[36,1131,183],{"class":132},[36,1133,1134,1137,1139],{"class":38,"line":448},[36,1135,1136],{"class":1123},"        services",[36,1138,1100],{"class":128},[36,1140,1141],{"class":42}," PluginAutonomyGateServices\n",[36,1143,1144,1147,1149,1152,1155,1158],{"class":38,"line":454},[36,1145,1146],{"class":132},"    )",[36,1148,1100],{"class":128},[36,1150,1151],{"class":42}," Promise",[36,1153,1154],{"class":132},"\u003C",[36,1156,1157],{"class":42},"PluginAutonomyGateResult",[36,1159,1160],{"class":132},"> {\n",[36,1162,1163,1166,1169,1172,1175,1178,1181,1184],{"class":38,"line":462},[36,1164,1165],{"class":128},"        if",[36,1167,1168],{"class":132}," (services.signal.aborted) ",[36,1170,1171],{"class":128},"return",[36,1173,1174],{"class":132}," { outcome: ",[36,1176,1177],{"class":46},"'objection'",[36,1179,1180],{"class":132},", reason: ",[36,1182,1183],{"class":46},"'preflight cancelled'",[36,1185,1186],{"class":132}," };\n",[36,1188,1189,1191,1194,1197,1200,1203],{"class":38,"line":470},[36,1190,1165],{"class":128},[36,1192,1193],{"class":132}," (input.draftBody.",[36,1195,1196],{"class":167},"includes",[36,1198,1199],{"class":132},"(",[36,1201,1202],{"class":46},"'http:\u002F\u002F'",[36,1204,1205],{"class":132},")) {\n",[36,1207,1208,1211,1213,1215,1217,1220],{"class":38,"line":478},[36,1209,1210],{"class":128},"            return",[36,1212,1174],{"class":132},[36,1214,1177],{"class":46},[36,1216,1180],{"class":132},[36,1218,1219],{"class":46},"'draft contains a plaintext HTTP link'",[36,1221,1186],{"class":132},[36,1223,1224],{"class":38,"line":486},[36,1225,1226],{"class":132},"        }\n",[36,1228,1229,1232,1234,1237],{"class":38,"line":494},[36,1230,1231],{"class":128},"        return",[36,1233,1174],{"class":132},[36,1235,1236],{"class":46},"'no-objection'",[36,1238,1186],{"class":132},[36,1240,1241],{"class":38,"line":502},[36,1242,834],{"class":132},[36,1244,1245],{"class":38,"line":508},[36,1246,1247],{"class":132},"};\n",[11,1249,1250,1251,1253],{},"Hintergrundarbeit erhält ein Abort-Signal, einen Logger und — sofern ",[33,1252,307],{}," deklariert, gewährt und im Budget ist — den attribuierten LLM-Dispatch des Hosts:",[26,1255,1257],{"className":119,"code":1256,"language":121,"meta":31,"style":31},"import type { PluginCronModule, PluginCronServices } from '@owlat\u002Fplugin-kit';\n\nexport const cron: PluginCronModule = {\n    async run(services: PluginCronServices): Promise\u003Cvoid> {\n        \u002F\u002F Cancellation is cooperative: the host aborts `signal` at the declared\n        \u002F\u002F timeout and stops waiting either way.\n        if (services.signal.aborted) return;\n        const summary = await services.llm.generate({\n            tier: 'fast',\n            prompt: 'Summarise this week of deliverability tips in one sentence.',\n        });\n        services.logger.info('refreshed preflight rules', { length: summary.text.length });\n    },\n};\n",[33,1258,1259,1274,1278,1296,1326,1331,1336,1346,1367,1377,1387,1392,1414,1418],{"__ignoreMap":31},[36,1260,1261,1263,1265,1268,1270,1272],{"class":38,"line":39},[36,1262,129],{"class":128},[36,1264,1047],{"class":128},[36,1266,1267],{"class":132}," { PluginCronModule, PluginCronServices } ",[36,1269,136],{"class":128},[36,1271,139],{"class":46},[36,1273,142],{"class":132},[36,1275,1276],{"class":38,"line":57},[36,1277,148],{"emptyLinePlaceholder":147},[36,1279,1280,1282,1284,1287,1289,1292,1294],{"class":38,"line":151},[36,1281,154],{"class":128},[36,1283,157],{"class":128},[36,1285,1286],{"class":160}," cron",[36,1288,1100],{"class":128},[36,1290,1291],{"class":42}," PluginCronModule",[36,1293,164],{"class":128},[36,1295,370],{"class":132},[36,1297,1298,1300,1302,1304,1307,1309,1312,1315,1317,1319,1321,1324],{"class":38,"line":174},[36,1299,1112],{"class":128},[36,1301,47],{"class":167},[36,1303,1199],{"class":132},[36,1305,1306],{"class":1123},"services",[36,1308,1100],{"class":128},[36,1310,1311],{"class":42}," PluginCronServices",[36,1313,1314],{"class":132},")",[36,1316,1100],{"class":128},[36,1318,1151],{"class":42},[36,1320,1154],{"class":132},[36,1322,1323],{"class":160},"void",[36,1325,1160],{"class":132},[36,1327,1328],{"class":38,"line":186},[36,1329,1330],{"class":53},"        \u002F\u002F Cancellation is cooperative: the host aborts `signal` at the declared\n",[36,1332,1333],{"class":38,"line":197},[36,1334,1335],{"class":53},"        \u002F\u002F timeout and stops waiting either way.\n",[36,1337,1338,1340,1342,1344],{"class":38,"line":215},[36,1339,1165],{"class":128},[36,1341,1168],{"class":132},[36,1343,1171],{"class":128},[36,1345,142],{"class":132},[36,1347,1348,1351,1354,1356,1359,1362,1365],{"class":38,"line":221},[36,1349,1350],{"class":128},"        const",[36,1352,1353],{"class":160}," summary",[36,1355,164],{"class":128},[36,1357,1358],{"class":128}," await",[36,1360,1361],{"class":132}," services.llm.",[36,1363,1364],{"class":167},"generate",[36,1366,171],{"class":132},[36,1368,1369,1372,1375],{"class":38,"line":233},[36,1370,1371],{"class":132},"            tier: ",[36,1373,1374],{"class":46},"'fast'",[36,1376,183],{"class":132},[36,1378,1379,1382,1385],{"class":38,"line":428},[36,1380,1381],{"class":132},"            prompt: ",[36,1383,1384],{"class":46},"'Summarise this week of deliverability tips in one sentence.'",[36,1386,183],{"class":132},[36,1388,1389],{"class":38,"line":438},[36,1390,1391],{"class":132},"        });\n",[36,1393,1394,1397,1400,1402,1405,1408,1411],{"class":38,"line":448},[36,1395,1396],{"class":132},"        services.logger.",[36,1398,1399],{"class":167},"info",[36,1401,1199],{"class":132},[36,1403,1404],{"class":46},"'refreshed preflight rules'",[36,1406,1407],{"class":132},", { length: summary.text.",[36,1409,1410],{"class":160},"length",[36,1412,1413],{"class":132}," });\n",[36,1415,1416],{"class":38,"line":454},[36,1417,834],{"class":132},[36,1419,1420],{"class":38,"line":462},[36,1421,1247],{"class":132},[11,1423,1424,1425,1428,1429,206,1432,206,1435,206,1438,206,1441,89,1444,1447,1448,1451],{},"Länger laufender gehosteter Code erhält einen ",[33,1426,1427],{},"PluginContext"," mit ",[33,1430,1431],{},"pluginId",[33,1433,1434],{},"permissions",[33,1436,1437],{},"storage",[33,1439,1440],{},"llm",[33,1442,1443],{},"logger",[33,1445,1446],{},"scheduler",". Storage-Methoden nehmen ",[113,1449,1450],{},"kein"," Organisations- oder Plugin-Argument entgegen: Der Host hat den Service bereits an die authentifizierte Organisation und das validierte Plugin gebunden und prüft bei jedem Aufruf das Flag und die exakte Storage-Grant erneut.",[1453,1454,1457],"callout",{"title":1455,"type":1456},"Für die Verweigerung entwerfen","warning",[11,1458,1459,1460,1463],{},"Jeder gehostete Aufruf kann im letzten Moment verweigert werden — das Flag kann umgelegt worden sein, die Grant kann widerrufen sein, eine Umgebungsvariable kann fehlen. Schreiben Sie Module so, dass ein ",[33,1461,1462],{},"PluginHostError"," oder ein Timeout keinen halb angewendeten Zustand hinterlässt; der Host wendet in jedem Fall den deklarierten sicheren Fallback der Contribution an.",[21,1465,1467],{"id":1466},"_5-das-plugin-testen","5. Das Plugin testen",[11,1469,1470,1471,1474],{},"Contribution-Module sind gewöhnliches TypeScript und lassen sich daher als Units mit vitest testen. Prüfen Sie immer auch das Manifest selbst: ",[33,1472,1473],{},"parsePluginManifest"," ist derselbe Validator, den die Codegen ausführt — ein Manifest-Test lässt den Build also scheitern, bevor ein defektes Paket komponiert werden kann.",[26,1476,1478],{"className":28,"code":1477,"language":30,"meta":31,"style":31},"bun run --cwd examples\u002Fplugins\u002Fhello-owlat test\nbun run --cwd examples\u002Fplugins\u002Fhello-owlat typecheck\n",[33,1479,1480,1495],{"__ignoreMap":31},[36,1481,1482,1484,1486,1489,1492],{"class":38,"line":39},[36,1483,43],{"class":42},[36,1485,47],{"class":46},[36,1487,1488],{"class":160}," --cwd",[36,1490,1491],{"class":46}," examples\u002Fplugins\u002Fhello-owlat",[36,1493,1494],{"class":46}," test\n",[36,1496,1497,1499,1501,1503,1505],{"class":38,"line":57},[36,1498,43],{"class":42},[36,1500,47],{"class":46},[36,1502,1488],{"class":160},[36,1504,1491],{"class":46},[36,1506,1507],{"class":46}," typecheck\n",[11,1509,1510,1511,1514,1515,1518,1519,1522],{},"Die Referenz-Plugins sind die ausgearbeiteten Beispiele, eines je Tier: ",[33,1512,1513],{},"examples\u002Fplugins\u002Fescalation-guard"," (Tier 1 — Agent-Step, Draft-Strategie, Automation-Trigger\u002F-Step\u002F-Condition, Webhook-Event, Navigations- und Einstellungseinträge), ",[33,1516,1517],{},"examples\u002Fplugins\u002Fslack-approvals"," (Tier 2 — eine Connected App mit einem ausschließlich einschränkenden Hold-Gate) und ",[33,1520,1521],{},"examples\u002Fplugins\u002Fdeliverability-lab"," (Tier 3 — ein gesandboxter Seed-List-Job plus ein Tier-1-Gate, Cron und UI).",[11,1524,1525,1528],{},[33,1526,1527],{},"examples\u002Fconformance"," spielt anschließend alle drei durch den echten Host, die Codegen und das CLI — saubere Installation, add, remove, disable, upgrade sowie ein vollständiger Pipeline-Replay —, sodass eine Vertragsänderung die Galerie im selben Commit bricht.",[1453,1530,1533],{"title":1531,"type":1532},"Niemals vitests Globals über bun test verwenden","danger",[11,1534,1535,1536,1539,1540,1543],{},"Führen Sie Tests mit ",[33,1537,1538],{},"vitest"," aus, nicht mit ",[33,1541,1542],{},"bun test",". Erst das vitest-Setup des Workspace macht die Suites aussagekräftig.",[21,1545,1547],{"id":1546},"_6-bündeln","6. Bündeln",[26,1549,1551],{"className":28,"code":1550,"language":30,"meta":31,"style":31},"bun packages\u002Fplugin-cli\u002Fsrc\u002Findex.ts add @owlat\u002Fplugin-hello-owlat\nbun run plugins:codegen\n",[33,1552,1553,1565],{"__ignoreMap":31},[36,1554,1555,1557,1559,1562],{"class":38,"line":39},[36,1556,43],{"class":42},[36,1558,62],{"class":46},[36,1560,1561],{"class":46}," add",[36,1563,1564],{"class":46}," @owlat\u002Fplugin-hello-owlat\n",[36,1566,1567,1569,1571],{"class":38,"line":57},[36,1568,43],{"class":42},[36,1570,47],{"class":46},[36,1572,1573],{"class":46}," plugins:codegen\n",[11,1575,1576,1579,1580,1583,1584,1587,1588,1590],{},[33,1577,1578],{},"add"," bearbeitet die eingecheckte ",[33,1581,1582],{},"plugins.config.ts"," und gibt das Capability-Diff aus, das die Änderung einführt. ",[33,1585,1586],{},"codegen"," prüft Paketidentität, Lockfile-Integrität und Realpath-Containment, importiert ausschließlich den Manifest-Entry und schreibt die generierten Kompositionsdateien neu. Committen Sie die Konfiguration ",[113,1589,314],{}," die generierten Dateien gemeinsam, bauen Sie dann neu und deployen Sie.",[11,1592,1593],{},"Das Plugin existiert nun im Deployment, tut aber nichts: Sein Feature-Flag steht auf dem deklarierten Standardwert und seine Capabilities sind ungewährt, bis ein Betreiber handelt.",[21,1595,1597],{"id":1596},"checkliste-vor-der-veröffentlichung","Checkliste vor der Veröffentlichung",[276,1599,1600,1603,1610,1617,1622],{},[279,1601,1602],{},"Der Standard-Export-Pfad des Manifests ist ein bedingungsunabhängiger Root-Export, damit Bun, Convex, Nuxt SSR und der Browser-Build alle dasselbe Manifest auflösen.",[279,1604,1605,1606,1609],{},"Kein ",[33,1607,1608],{},"postinstall"," und keine Seiteneffekte zur Importzeit — die Codegen importiert den Manifest-Entry.",[279,1611,1612,1613,1616],{},"Der ",[33,1614,1615],{},"module.exportPath"," jeder Contribution ist ein echter, exakter Paket-Export.",[279,1618,1619,1621],{},[33,1620,329],{}," führt nur Variablen auf, die ein ausgeliefertes Modul tatsächlich benötigt; eine fehlende erforderliche Variable blockiert die Aktivierung.",[279,1623,1624,1625,1628],{},"Versionssprünge folgen semver; die ",[33,1626,1627],{},"@owlat\u002Fplugin-kit","-Major-Version, gegen die Sie bauen, ist Ihre Kompatibilitätslinie.",[1630,1631,1632],"style",{},"html pre.shiki code .sOLd2, html code.shiki .sOLd2{--shiki-default:#6F42C1;--shiki-dark:#F69D50}html pre.shiki code .s-HuK, html code.shiki .s-HuK{--shiki-default:#032F62;--shiki-dark:#96D0FF}html pre.shiki code .sDN9O, html code.shiki .sDN9O{--shiki-default:#6A737D;--shiki-dark:#768390}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7YZ4, html code.shiki .s7YZ4{--shiki-default:#D73A49;--shiki-dark:#F47067}html pre.shiki code .sYgZi, html code.shiki .sYgZi{--shiki-default:#24292E;--shiki-dark:#ADBAC7}html pre.shiki code .sviXB, html code.shiki .sviXB{--shiki-default:#005CC5;--shiki-dark:#6CB6FF}html pre.shiki code .sPO5f, html code.shiki .sPO5f{--shiki-default:#6F42C1;--shiki-dark:#DCBDFB}html pre.shiki code .stnAF, html code.shiki .stnAF{--shiki-default:#E36209;--shiki-dark:#F69D50}",{"title":31,"searchDepth":57,"depth":57,"links":1634},[1635,1636,1639,1640,1641,1642,1643],{"id":23,"depth":57,"text":24},{"id":107,"depth":57,"text":108,"children":1637},[1638],{"id":273,"depth":151,"text":274},{"id":336,"depth":57,"text":337},{"id":1027,"depth":57,"text":1028},{"id":1466,"depth":57,"text":1467},{"id":1546,"depth":57,"text":1547},{"id":1596,"depth":57,"text":1597},"Ein Plugin-Paket scaffolden, ein Manifest deklarieren, Contribution-Module schreiben und alles in ein Deployment bündeln.","md",{},"\u002Fdeveloper\u002Fplugin-authoring",{"title":6,"description":1644},"3.developer\u002F41.plugin-authoring","yVnLU80joBCZmgVGxfhxTUbXbceszBXQfZe5srlU4qg",[1652,1656],{"title":1653,"path":1654,"stem":1655,"children":-1},"Plugin-Plattform","\u002Fdeveloper\u002Fplugin-platform","3.developer\u002F40.plugin-platform",{"title":1023,"path":1022,"stem":1657,"children":-1},"3.developer\u002F42.plugin-contributions",1786915109158]