Compare commits
No commits in common. "9e39119555a82a381f6cae3acf29794d212820fe" and "04991295cfb96fbeeb782c7943fbc555d97a8604" have entirely different histories.
9e39119555
...
04991295cf
@ -556,289 +556,3 @@ message BadgesRefreshBadgesRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message BadgesRefreshBadgesResponse {}
|
message BadgesRefreshBadgesResponse {}
|
||||||
|
|
||||||
// --- content.* writes (content.Author, WO-WZ-019) ---
|
|
||||||
//
|
|
||||||
// Imperative page/post authoring. The idempotent seed-time counterparts live
|
|
||||||
// in the provisioner.* family below; these are for runtime authoring and for
|
|
||||||
// the WO-WZ-020 codeless seed runner.
|
|
||||||
|
|
||||||
message ContentCreatePageRequest {
|
|
||||||
string slug = 1;
|
|
||||||
string parent_slug = 2; // "" = root
|
|
||||||
string title = 3;
|
|
||||||
string template_key = 4;
|
|
||||||
string master_page_key = 5; // "" = none
|
|
||||||
}
|
|
||||||
|
|
||||||
message ContentCreatePageResponse {
|
|
||||||
string page_id = 1; // UUID
|
|
||||||
// False when a page with this slug already existed (the existing ID is
|
|
||||||
// returned and nothing is modified).
|
|
||||||
bool created = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PageBlock is one block instance placed on a page (mirrors
|
|
||||||
// plugin.PageBlockConfig / MasterPageBlock).
|
|
||||||
message PageBlock {
|
|
||||||
string block_key = 1;
|
|
||||||
string title = 2;
|
|
||||||
// JSON encoding of the block's content map.
|
|
||||||
bytes content_json = 3;
|
|
||||||
optional string html_content = 4;
|
|
||||||
string slot = 5;
|
|
||||||
int32 sort_order = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ContentSetPageBlocksRequest {
|
|
||||||
string page_id = 1; // UUID
|
|
||||||
// Full replacement set for the page's draft document blocks.
|
|
||||||
repeated PageBlock blocks = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ContentSetPageBlocksResponse {}
|
|
||||||
|
|
||||||
message ContentPublishPageRequest {
|
|
||||||
string page_id = 1; // UUID
|
|
||||||
}
|
|
||||||
|
|
||||||
message ContentPublishPageResponse {}
|
|
||||||
|
|
||||||
// ContentSetPageSeoRequest mirrors the CMS UpdatePageSEO surface. Unset
|
|
||||||
// optionals leave the existing value untouched.
|
|
||||||
message ContentSetPageSeoRequest {
|
|
||||||
string page_id = 1; // UUID
|
|
||||||
optional string meta_title = 2;
|
|
||||||
optional string meta_description = 3;
|
|
||||||
optional string og_title = 4;
|
|
||||||
optional string og_description = 5;
|
|
||||||
optional string og_image = 6;
|
|
||||||
optional string focus_keyphrase = 7;
|
|
||||||
optional string canonical_url = 8;
|
|
||||||
optional string robots_directive = 9;
|
|
||||||
optional string twitter_title = 10;
|
|
||||||
optional string twitter_description = 11;
|
|
||||||
optional string twitter_image = 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ContentSetPageSeoResponse {}
|
|
||||||
|
|
||||||
// ContentUpsertPostRequest creates or updates a blog post by slug (posts live
|
|
||||||
// in the dedicated blog_posts table).
|
|
||||||
message ContentUpsertPostRequest {
|
|
||||||
string slug = 1;
|
|
||||||
string title = 2;
|
|
||||||
// JSON encoding of the BlockNote document.
|
|
||||||
bytes document_json = 3;
|
|
||||||
optional string excerpt = 4;
|
|
||||||
optional string author_profile_id = 5; // UUID
|
|
||||||
optional string featured_image_id = 6; // UUID (e.g. from media.deposit)
|
|
||||||
// Publish immediately after the upsert.
|
|
||||||
bool publish = 7;
|
|
||||||
bool is_featured = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ContentUpsertPostResponse {
|
|
||||||
string post_id = 1; // UUID
|
|
||||||
bool created = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- provisioner.* (plugin.Provisioner, WO-WZ-019) ---
|
|
||||||
//
|
|
||||||
// Idempotent seed/ensure operations, 1:1 with the plugin.Provisioner Go
|
|
||||||
// interface. In the wasm world these are LOAD-TIME capabilities: plugins call
|
|
||||||
// deps.Provisioner from their Load hook (RegisterWithProvisioner cannot cross
|
|
||||||
// the DESCRIBE boundary — host functions are stubbed there). All methods
|
|
||||||
// check-then-create; re-running them is safe.
|
|
||||||
//
|
|
||||||
// EmbedConfig.RenderFunc deliberately does not cross: an ABI-provisioned
|
|
||||||
// embed is template-rendered host-side. A compute-rendered embed needs a
|
|
||||||
// block + hook instead.
|
|
||||||
|
|
||||||
message ProvisionerEnsureDataTableRequest {
|
|
||||||
string key = 1;
|
|
||||||
string name = 2;
|
|
||||||
string description = 3;
|
|
||||||
// JSON schema of the table (json.RawMessage in DataTableConfig).
|
|
||||||
bytes schema_json = 4;
|
|
||||||
string primary_key = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerEnsureDataTableResponse {}
|
|
||||||
|
|
||||||
message ProvisionerMergeSiteSettingsRequest {
|
|
||||||
// JSON encoding of the defaults map; existing keys win.
|
|
||||||
bytes defaults_json = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerMergeSiteSettingsResponse {}
|
|
||||||
|
|
||||||
message ProvisionerEnsureSettingRequest {
|
|
||||||
string key = 1;
|
|
||||||
// JSON encoding of the default value.
|
|
||||||
bytes default_value_json = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerEnsureSettingResponse {}
|
|
||||||
|
|
||||||
// PageSeed mirrors plugin.PageConfig.
|
|
||||||
message PageSeed {
|
|
||||||
string slug = 1;
|
|
||||||
string parent_slug = 2;
|
|
||||||
string title = 3;
|
|
||||||
string template_key = 4;
|
|
||||||
repeated PageBlock blocks = 5;
|
|
||||||
string detail_source_type = 6;
|
|
||||||
string detail_source_key = 7;
|
|
||||||
string detail_slug_field = 8;
|
|
||||||
bool reconcile_blocks = 9;
|
|
||||||
bool reconcile_template = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerEnsurePageRequest {
|
|
||||||
PageSeed page = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerEnsurePageResponse {}
|
|
||||||
|
|
||||||
message ProvisionerOverrideSiteSettingsRequest {
|
|
||||||
// JSON encoding of the overrides map; plugin values win.
|
|
||||||
bytes overrides_json = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerOverrideSiteSettingsResponse {}
|
|
||||||
|
|
||||||
// ProvisionerEnsureMenuItemRequest mirrors plugin.MenuItemConfig under a
|
|
||||||
// named menu.
|
|
||||||
message ProvisionerEnsureMenuItemRequest {
|
|
||||||
string menu_name = 1;
|
|
||||||
string label = 2;
|
|
||||||
string url = 3;
|
|
||||||
string page_slug = 4;
|
|
||||||
int32 sort_order = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerEnsureMenuItemResponse {}
|
|
||||||
|
|
||||||
// ProvisionerRegisterEmbeddingConfigRequest mirrors plugin.EmbeddingConfigDef.
|
|
||||||
message ProvisionerRegisterEmbeddingConfigRequest {
|
|
||||||
string table_key = 1;
|
|
||||||
string text_template = 2;
|
|
||||||
bool enabled = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerRegisterEmbeddingConfigResponse {}
|
|
||||||
|
|
||||||
// ProvisionerEnsureEmbedRequest mirrors plugin.EmbedConfig minus RenderFunc
|
|
||||||
// (function values cannot cross; the Template renders host-side).
|
|
||||||
message ProvisionerEnsureEmbedRequest {
|
|
||||||
string key = 1;
|
|
||||||
string title = 2;
|
|
||||||
string description = 3;
|
|
||||||
string icon = 4;
|
|
||||||
string label_field = 5;
|
|
||||||
string template = 6;
|
|
||||||
string data_source_type = 7; // EmbedDataSource.Type
|
|
||||||
string data_source_table_key = 8; // EmbedDataSource.TableKey
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerEnsureEmbedResponse {}
|
|
||||||
|
|
||||||
// ProvisionerEnsureJobScheduleRequest mirrors plugin.JobScheduleConfig.
|
|
||||||
message ProvisionerEnsureJobScheduleRequest {
|
|
||||||
string job_type = 1;
|
|
||||||
string cron_expression = 2;
|
|
||||||
// JSON job configuration.
|
|
||||||
bytes config_json = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerEnsureJobScheduleResponse {}
|
|
||||||
|
|
||||||
message ProvisionerUpdateDataTableRowFieldRequest {
|
|
||||||
string row_id = 1; // UUID
|
|
||||||
string field_key = 2;
|
|
||||||
// JSON encoding of the value.
|
|
||||||
bytes value_json = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerUpdateDataTableRowFieldResponse {}
|
|
||||||
|
|
||||||
message ProvisionerDisableOrphanedJobSchedulesRequest {
|
|
||||||
repeated string registered_types = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerDisableOrphanedJobSchedulesResponse {}
|
|
||||||
|
|
||||||
message ProvisionerEnsurePluginRequest {
|
|
||||||
string name = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerEnsurePluginResponse {}
|
|
||||||
|
|
||||||
// ProvisionerEnsureCustomColorRequest mirrors plugin.CustomColorConfig.
|
|
||||||
message ProvisionerEnsureCustomColorRequest {
|
|
||||||
string name = 1;
|
|
||||||
string light_value = 2;
|
|
||||||
string dark_value = 3;
|
|
||||||
string source = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerEnsureCustomColorResponse {}
|
|
||||||
|
|
||||||
// ProvisionerEnsureMediaRequest mirrors plugin.MediaDeposit with a REQUIRED
|
|
||||||
// deterministic id (the template-referable key). Idempotent: an existing id
|
|
||||||
// is a no-op; changed bytes warn rather than overwrite.
|
|
||||||
message ProvisionerEnsureMediaRequest {
|
|
||||||
string id = 1; // UUID, required
|
|
||||||
string filename = 2;
|
|
||||||
bytes data = 3;
|
|
||||||
string alt_text = 4;
|
|
||||||
string folder = 5;
|
|
||||||
string source = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProvisionerEnsureMediaResponse {}
|
|
||||||
|
|
||||||
// --- settings.update_plugin_settings (settings.Updater, WO-WZ-019) ---
|
|
||||||
|
|
||||||
// SettingsUpdatePluginSettingsRequest replaces the calling plugin's own
|
|
||||||
// settings map. The host binds the target plugin from the caller's identity;
|
|
||||||
// plugin_name is advisory and MUST match it (PERMISSION_DENIED otherwise).
|
|
||||||
message SettingsUpdatePluginSettingsRequest {
|
|
||||||
string plugin_name = 1;
|
|
||||||
// JSON encoding of the full settings map.
|
|
||||||
bytes settings_json = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SettingsUpdatePluginSettingsResponse {}
|
|
||||||
|
|
||||||
// --- jobs.progress (plugin.JobHandlerFunc progress callback, WO-WZ-019) ---
|
|
||||||
|
|
||||||
// JobsProgressRequest reports progress for the CURRENTLY EXECUTING job. The
|
|
||||||
// host correlates it to the job via the invoking HOOK_JOB call's context, so
|
|
||||||
// it is only meaningful while a JOB hook is on the stack; outside one it is
|
|
||||||
// accepted and discarded.
|
|
||||||
message JobsProgressRequest {
|
|
||||||
int32 current = 1;
|
|
||||||
int32 total = 2;
|
|
||||||
string message = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message JobsProgressResponse {}
|
|
||||||
|
|
||||||
// --- bridge.invoke (plugin.PluginBridge cross-plugin calls, WO-WZ-019) ---
|
|
||||||
|
|
||||||
// BridgeInvokeRequest calls a method on another plugin's registered bridge
|
|
||||||
// service. The provider answers via HOOK_BRIDGE_CALL (wasm) or an in-process
|
|
||||||
// plugin.BridgeInvokable (bundled). Payloads are opaque bytes — the two
|
|
||||||
// plugins agree on the encoding (JSON by convention).
|
|
||||||
message BridgeInvokeRequest {
|
|
||||||
string plugin_name = 1;
|
|
||||||
string service_name = 2;
|
|
||||||
string method = 3;
|
|
||||||
bytes payload = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BridgeInvokeResponse {
|
|
||||||
bytes payload = 1;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -49,18 +49,6 @@ enum Hook {
|
|||||||
// Apply a plugin-declared template filter (manifest.declared_filters):
|
// Apply a plugin-declared template filter (manifest.declared_filters):
|
||||||
// payload = ApplyFilterRequest / ApplyFilterResponse.
|
// payload = ApplyFilterRequest / ApplyFilterResponse.
|
||||||
HOOK_APPLY_FILTER = 11;
|
HOOK_APPLY_FILTER = 11;
|
||||||
// Execute a guest-registered AI tool handler (ai.tools.register):
|
|
||||||
// payload = AiToolCallRequest / AiToolCallResponse.
|
|
||||||
HOOK_AI_TOOL_CALL = 12;
|
|
||||||
// Invoke a method on a bridge service this plugin registered
|
|
||||||
// (bridge.register_service): payload = BridgeCallRequest / BridgeCallResponse.
|
|
||||||
HOOK_BRIDGE_CALL = 13;
|
|
||||||
// Render one directory panel section (DirectoryExtensions.PanelSections):
|
|
||||||
// payload = DirectoryPanelSectionRequest / DirectoryPanelSectionResponse.
|
|
||||||
HOOK_DIRECTORY_PANEL_SECTION = 14;
|
|
||||||
// Run one directory pin decorator (DirectoryExtensions.PinDecorators):
|
|
||||||
// payload = DirectoryPinDecoratorRequest / DirectoryPinDecoratorResponse.
|
|
||||||
HOOK_DIRECTORY_PIN_DECORATOR = 15;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvokeRequest is the host→guest call envelope.
|
// InvokeRequest is the host→guest call envelope.
|
||||||
@ -200,70 +188,6 @@ message MediaAnalyzedEvent {
|
|||||||
string safe_racy = 10;
|
string safe_racy = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- AI_TOOL_CALL ---
|
|
||||||
|
|
||||||
// AiToolCallRequest executes the guest-side Handler of a tool the plugin
|
|
||||||
// registered via ai.tools.register (ai.ToolHandler).
|
|
||||||
message AiToolCallRequest {
|
|
||||||
string slug = 1;
|
|
||||||
// JSON encoding of the params map.
|
|
||||||
bytes params_json = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AiToolCallResponse mirrors ai.ToolResult. A handler-level failure travels
|
|
||||||
// in error_message (a normal tool outcome the model sees); AbiError stays
|
|
||||||
// reserved for transport/decode/panic failures.
|
|
||||||
message AiToolCallResponse {
|
|
||||||
string content = 1;
|
|
||||||
string error_message = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- BRIDGE_CALL ---
|
|
||||||
|
|
||||||
// BridgeCallRequest asks THIS plugin (the provider) to run a method on one of
|
|
||||||
// its registered bridge services. The consumer side is the bridge.invoke
|
|
||||||
// capability; payload encoding is agreed between the two plugins (JSON by
|
|
||||||
// convention).
|
|
||||||
message BridgeCallRequest {
|
|
||||||
string service_name = 1;
|
|
||||||
string method = 2;
|
|
||||||
bytes payload = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BridgeCallResponse {
|
|
||||||
bytes payload = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- DIRECTORY_PANEL_SECTION / DIRECTORY_PIN_DECORATOR ---
|
|
||||||
|
|
||||||
// DirectoryPanelSectionRequest renders the index-th registered panel section
|
|
||||||
// (DirectoryExtensions.PanelSections[index], counted in
|
|
||||||
// manifest.directory_extensions.panel_section_count).
|
|
||||||
message DirectoryPanelSectionRequest {
|
|
||||||
uint32 index = 1;
|
|
||||||
// JSON encoding of the row data map.
|
|
||||||
bytes data_json = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DirectoryPanelSectionResponse {
|
|
||||||
string html = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DirectoryPinDecoratorRequest runs the index-th registered pin decorator,
|
|
||||||
// which mutates the pin map in place; the mutated pin is returned.
|
|
||||||
message DirectoryPinDecoratorRequest {
|
|
||||||
uint32 index = 1;
|
|
||||||
// JSON encoding of the pin map (mutated by the decorator).
|
|
||||||
bytes pin_json = 2;
|
|
||||||
// JSON encoding of the row data map (read-only input).
|
|
||||||
bytes data_json = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DirectoryPinDecoratorResponse {
|
|
||||||
// JSON encoding of the mutated pin map.
|
|
||||||
bytes pin_json = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModerationDecisionEvent mirrors plugin.ModerationDecisionEvent.
|
// ModerationDecisionEvent mirrors plugin.ModerationDecisionEvent.
|
||||||
message ModerationDecisionEvent {
|
message ModerationDecisionEvent {
|
||||||
string media_id = 1;
|
string media_id = 1;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -66,18 +66,6 @@ const (
|
|||||||
// Apply a plugin-declared template filter (manifest.declared_filters):
|
// Apply a plugin-declared template filter (manifest.declared_filters):
|
||||||
// payload = ApplyFilterRequest / ApplyFilterResponse.
|
// payload = ApplyFilterRequest / ApplyFilterResponse.
|
||||||
Hook_HOOK_APPLY_FILTER Hook = 11
|
Hook_HOOK_APPLY_FILTER Hook = 11
|
||||||
// Execute a guest-registered AI tool handler (ai.tools.register):
|
|
||||||
// payload = AiToolCallRequest / AiToolCallResponse.
|
|
||||||
Hook_HOOK_AI_TOOL_CALL Hook = 12
|
|
||||||
// Invoke a method on a bridge service this plugin registered
|
|
||||||
// (bridge.register_service): payload = BridgeCallRequest / BridgeCallResponse.
|
|
||||||
Hook_HOOK_BRIDGE_CALL Hook = 13
|
|
||||||
// Render one directory panel section (DirectoryExtensions.PanelSections):
|
|
||||||
// payload = DirectoryPanelSectionRequest / DirectoryPanelSectionResponse.
|
|
||||||
Hook_HOOK_DIRECTORY_PANEL_SECTION Hook = 14
|
|
||||||
// Run one directory pin decorator (DirectoryExtensions.PinDecorators):
|
|
||||||
// payload = DirectoryPinDecoratorRequest / DirectoryPinDecoratorResponse.
|
|
||||||
Hook_HOOK_DIRECTORY_PIN_DECORATOR Hook = 15
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for Hook.
|
// Enum value maps for Hook.
|
||||||
@ -95,28 +83,20 @@ var (
|
|||||||
9: "HOOK_DESCRIBE",
|
9: "HOOK_DESCRIBE",
|
||||||
10: "HOOK_RENDER_TAG",
|
10: "HOOK_RENDER_TAG",
|
||||||
11: "HOOK_APPLY_FILTER",
|
11: "HOOK_APPLY_FILTER",
|
||||||
12: "HOOK_AI_TOOL_CALL",
|
|
||||||
13: "HOOK_BRIDGE_CALL",
|
|
||||||
14: "HOOK_DIRECTORY_PANEL_SECTION",
|
|
||||||
15: "HOOK_DIRECTORY_PIN_DECORATOR",
|
|
||||||
}
|
}
|
||||||
Hook_value = map[string]int32{
|
Hook_value = map[string]int32{
|
||||||
"HOOK_UNSPECIFIED": 0,
|
"HOOK_UNSPECIFIED": 0,
|
||||||
"HOOK_RENDER_BLOCK": 1,
|
"HOOK_RENDER_BLOCK": 1,
|
||||||
"HOOK_RENDER_TEMPLATE": 2,
|
"HOOK_RENDER_TEMPLATE": 2,
|
||||||
"HOOK_HANDLE_HTTP": 3,
|
"HOOK_HANDLE_HTTP": 3,
|
||||||
"HOOK_JOB": 4,
|
"HOOK_JOB": 4,
|
||||||
"HOOK_LOAD": 5,
|
"HOOK_LOAD": 5,
|
||||||
"HOOK_UNLOAD": 6,
|
"HOOK_UNLOAD": 6,
|
||||||
"HOOK_RAG_FETCH": 7,
|
"HOOK_RAG_FETCH": 7,
|
||||||
"HOOK_MEDIA_HOOK": 8,
|
"HOOK_MEDIA_HOOK": 8,
|
||||||
"HOOK_DESCRIBE": 9,
|
"HOOK_DESCRIBE": 9,
|
||||||
"HOOK_RENDER_TAG": 10,
|
"HOOK_RENDER_TAG": 10,
|
||||||
"HOOK_APPLY_FILTER": 11,
|
"HOOK_APPLY_FILTER": 11,
|
||||||
"HOOK_AI_TOOL_CALL": 12,
|
|
||||||
"HOOK_BRIDGE_CALL": 13,
|
|
||||||
"HOOK_DIRECTORY_PANEL_SECTION": 14,
|
|
||||||
"HOOK_DIRECTORY_PIN_DECORATOR": 15,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1135,433 +1115,6 @@ func (x *MediaAnalyzedEvent) GetSafeRacy() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// AiToolCallRequest executes the guest-side Handler of a tool the plugin
|
|
||||||
// registered via ai.tools.register (ai.ToolHandler).
|
|
||||||
type AiToolCallRequest struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
Slug string `protobuf:"bytes,1,opt,name=slug,proto3" json:"slug,omitempty"`
|
|
||||||
// JSON encoding of the params map.
|
|
||||||
ParamsJson []byte `protobuf:"bytes,2,opt,name=params_json,json=paramsJson,proto3" json:"params_json,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *AiToolCallRequest) Reset() {
|
|
||||||
*x = AiToolCallRequest{}
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[17]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *AiToolCallRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*AiToolCallRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *AiToolCallRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[17]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use AiToolCallRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*AiToolCallRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_v1_invoke_proto_rawDescGZIP(), []int{17}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *AiToolCallRequest) GetSlug() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Slug
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *AiToolCallRequest) GetParamsJson() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.ParamsJson
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AiToolCallResponse mirrors ai.ToolResult. A handler-level failure travels
|
|
||||||
// in error_message (a normal tool outcome the model sees); AbiError stays
|
|
||||||
// reserved for transport/decode/panic failures.
|
|
||||||
type AiToolCallResponse struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
Content string `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"`
|
|
||||||
ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *AiToolCallResponse) Reset() {
|
|
||||||
*x = AiToolCallResponse{}
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[18]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *AiToolCallResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*AiToolCallResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *AiToolCallResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[18]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use AiToolCallResponse.ProtoReflect.Descriptor instead.
|
|
||||||
func (*AiToolCallResponse) Descriptor() ([]byte, []int) {
|
|
||||||
return file_v1_invoke_proto_rawDescGZIP(), []int{18}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *AiToolCallResponse) GetContent() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Content
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *AiToolCallResponse) GetErrorMessage() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.ErrorMessage
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// BridgeCallRequest asks THIS plugin (the provider) to run a method on one of
|
|
||||||
// its registered bridge services. The consumer side is the bridge.invoke
|
|
||||||
// capability; payload encoding is agreed between the two plugins (JSON by
|
|
||||||
// convention).
|
|
||||||
type BridgeCallRequest struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"`
|
|
||||||
Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"`
|
|
||||||
Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *BridgeCallRequest) Reset() {
|
|
||||||
*x = BridgeCallRequest{}
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[19]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *BridgeCallRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*BridgeCallRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *BridgeCallRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[19]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use BridgeCallRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*BridgeCallRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_v1_invoke_proto_rawDescGZIP(), []int{19}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *BridgeCallRequest) GetServiceName() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.ServiceName
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *BridgeCallRequest) GetMethod() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Method
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *BridgeCallRequest) GetPayload() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.Payload
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type BridgeCallResponse struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *BridgeCallResponse) Reset() {
|
|
||||||
*x = BridgeCallResponse{}
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[20]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *BridgeCallResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*BridgeCallResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *BridgeCallResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[20]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use BridgeCallResponse.ProtoReflect.Descriptor instead.
|
|
||||||
func (*BridgeCallResponse) Descriptor() ([]byte, []int) {
|
|
||||||
return file_v1_invoke_proto_rawDescGZIP(), []int{20}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *BridgeCallResponse) GetPayload() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.Payload
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DirectoryPanelSectionRequest renders the index-th registered panel section
|
|
||||||
// (DirectoryExtensions.PanelSections[index], counted in
|
|
||||||
// manifest.directory_extensions.panel_section_count).
|
|
||||||
type DirectoryPanelSectionRequest struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
Index uint32 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"`
|
|
||||||
// JSON encoding of the row data map.
|
|
||||||
DataJson []byte `protobuf:"bytes,2,opt,name=data_json,json=dataJson,proto3" json:"data_json,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPanelSectionRequest) Reset() {
|
|
||||||
*x = DirectoryPanelSectionRequest{}
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[21]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPanelSectionRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*DirectoryPanelSectionRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *DirectoryPanelSectionRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[21]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use DirectoryPanelSectionRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*DirectoryPanelSectionRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_v1_invoke_proto_rawDescGZIP(), []int{21}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPanelSectionRequest) GetIndex() uint32 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Index
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPanelSectionRequest) GetDataJson() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.DataJson
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DirectoryPanelSectionResponse struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
Html string `protobuf:"bytes,1,opt,name=html,proto3" json:"html,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPanelSectionResponse) Reset() {
|
|
||||||
*x = DirectoryPanelSectionResponse{}
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[22]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPanelSectionResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*DirectoryPanelSectionResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *DirectoryPanelSectionResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[22]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use DirectoryPanelSectionResponse.ProtoReflect.Descriptor instead.
|
|
||||||
func (*DirectoryPanelSectionResponse) Descriptor() ([]byte, []int) {
|
|
||||||
return file_v1_invoke_proto_rawDescGZIP(), []int{22}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPanelSectionResponse) GetHtml() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Html
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// DirectoryPinDecoratorRequest runs the index-th registered pin decorator,
|
|
||||||
// which mutates the pin map in place; the mutated pin is returned.
|
|
||||||
type DirectoryPinDecoratorRequest struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
Index uint32 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"`
|
|
||||||
// JSON encoding of the pin map (mutated by the decorator).
|
|
||||||
PinJson []byte `protobuf:"bytes,2,opt,name=pin_json,json=pinJson,proto3" json:"pin_json,omitempty"`
|
|
||||||
// JSON encoding of the row data map (read-only input).
|
|
||||||
DataJson []byte `protobuf:"bytes,3,opt,name=data_json,json=dataJson,proto3" json:"data_json,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPinDecoratorRequest) Reset() {
|
|
||||||
*x = DirectoryPinDecoratorRequest{}
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[23]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPinDecoratorRequest) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*DirectoryPinDecoratorRequest) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *DirectoryPinDecoratorRequest) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[23]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use DirectoryPinDecoratorRequest.ProtoReflect.Descriptor instead.
|
|
||||||
func (*DirectoryPinDecoratorRequest) Descriptor() ([]byte, []int) {
|
|
||||||
return file_v1_invoke_proto_rawDescGZIP(), []int{23}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPinDecoratorRequest) GetIndex() uint32 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Index
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPinDecoratorRequest) GetPinJson() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.PinJson
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPinDecoratorRequest) GetDataJson() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.DataJson
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DirectoryPinDecoratorResponse struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
// JSON encoding of the mutated pin map.
|
|
||||||
PinJson []byte `protobuf:"bytes,1,opt,name=pin_json,json=pinJson,proto3" json:"pin_json,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPinDecoratorResponse) Reset() {
|
|
||||||
*x = DirectoryPinDecoratorResponse{}
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[24]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPinDecoratorResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*DirectoryPinDecoratorResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *DirectoryPinDecoratorResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_v1_invoke_proto_msgTypes[24]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use DirectoryPinDecoratorResponse.ProtoReflect.Descriptor instead.
|
|
||||||
func (*DirectoryPinDecoratorResponse) Descriptor() ([]byte, []int) {
|
|
||||||
return file_v1_invoke_proto_rawDescGZIP(), []int{24}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *DirectoryPinDecoratorResponse) GetPinJson() []byte {
|
|
||||||
if x != nil {
|
|
||||||
return x.PinJson
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModerationDecisionEvent mirrors plugin.ModerationDecisionEvent.
|
// ModerationDecisionEvent mirrors plugin.ModerationDecisionEvent.
|
||||||
type ModerationDecisionEvent struct {
|
type ModerationDecisionEvent struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
@ -1580,7 +1133,7 @@ type ModerationDecisionEvent struct {
|
|||||||
|
|
||||||
func (x *ModerationDecisionEvent) Reset() {
|
func (x *ModerationDecisionEvent) Reset() {
|
||||||
*x = ModerationDecisionEvent{}
|
*x = ModerationDecisionEvent{}
|
||||||
mi := &file_v1_invoke_proto_msgTypes[25]
|
mi := &file_v1_invoke_proto_msgTypes[17]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@ -1592,7 +1145,7 @@ func (x *ModerationDecisionEvent) String() string {
|
|||||||
func (*ModerationDecisionEvent) ProtoMessage() {}
|
func (*ModerationDecisionEvent) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ModerationDecisionEvent) ProtoReflect() protoreflect.Message {
|
func (x *ModerationDecisionEvent) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_v1_invoke_proto_msgTypes[25]
|
mi := &file_v1_invoke_proto_msgTypes[17]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@ -1605,7 +1158,7 @@ func (x *ModerationDecisionEvent) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ModerationDecisionEvent.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ModerationDecisionEvent.ProtoReflect.Descriptor instead.
|
||||||
func (*ModerationDecisionEvent) Descriptor() ([]byte, []int) {
|
func (*ModerationDecisionEvent) Descriptor() ([]byte, []int) {
|
||||||
return file_v1_invoke_proto_rawDescGZIP(), []int{25}
|
return file_v1_invoke_proto_rawDescGZIP(), []int{17}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ModerationDecisionEvent) GetMediaId() string {
|
func (x *ModerationDecisionEvent) GetMediaId() string {
|
||||||
@ -1736,31 +1289,7 @@ const file_v1_invoke_proto_rawDesc = "" +
|
|||||||
"safe_adult\x18\b \x01(\tR\tsafeAdult\x12#\n" +
|
"safe_adult\x18\b \x01(\tR\tsafeAdult\x12#\n" +
|
||||||
"\rsafe_violence\x18\t \x01(\tR\fsafeViolence\x12\x1b\n" +
|
"\rsafe_violence\x18\t \x01(\tR\fsafeViolence\x12\x1b\n" +
|
||||||
"\tsafe_racy\x18\n" +
|
"\tsafe_racy\x18\n" +
|
||||||
" \x01(\tR\bsafeRacy\"H\n" +
|
" \x01(\tR\bsafeRacy\"\xb7\x02\n" +
|
||||||
"\x11AiToolCallRequest\x12\x12\n" +
|
|
||||||
"\x04slug\x18\x01 \x01(\tR\x04slug\x12\x1f\n" +
|
|
||||||
"\vparams_json\x18\x02 \x01(\fR\n" +
|
|
||||||
"paramsJson\"S\n" +
|
|
||||||
"\x12AiToolCallResponse\x12\x18\n" +
|
|
||||||
"\acontent\x18\x01 \x01(\tR\acontent\x12#\n" +
|
|
||||||
"\rerror_message\x18\x02 \x01(\tR\ferrorMessage\"h\n" +
|
|
||||||
"\x11BridgeCallRequest\x12!\n" +
|
|
||||||
"\fservice_name\x18\x01 \x01(\tR\vserviceName\x12\x16\n" +
|
|
||||||
"\x06method\x18\x02 \x01(\tR\x06method\x12\x18\n" +
|
|
||||||
"\apayload\x18\x03 \x01(\fR\apayload\".\n" +
|
|
||||||
"\x12BridgeCallResponse\x12\x18\n" +
|
|
||||||
"\apayload\x18\x01 \x01(\fR\apayload\"Q\n" +
|
|
||||||
"\x1cDirectoryPanelSectionRequest\x12\x14\n" +
|
|
||||||
"\x05index\x18\x01 \x01(\rR\x05index\x12\x1b\n" +
|
|
||||||
"\tdata_json\x18\x02 \x01(\fR\bdataJson\"3\n" +
|
|
||||||
"\x1dDirectoryPanelSectionResponse\x12\x12\n" +
|
|
||||||
"\x04html\x18\x01 \x01(\tR\x04html\"l\n" +
|
|
||||||
"\x1cDirectoryPinDecoratorRequest\x12\x14\n" +
|
|
||||||
"\x05index\x18\x01 \x01(\rR\x05index\x12\x19\n" +
|
|
||||||
"\bpin_json\x18\x02 \x01(\fR\apinJson\x12\x1b\n" +
|
|
||||||
"\tdata_json\x18\x03 \x01(\fR\bdataJson\":\n" +
|
|
||||||
"\x1dDirectoryPinDecoratorResponse\x12\x19\n" +
|
|
||||||
"\bpin_json\x18\x01 \x01(\fR\apinJson\"\xb7\x02\n" +
|
|
||||||
"\x17ModerationDecisionEvent\x12\x19\n" +
|
"\x17ModerationDecisionEvent\x12\x19\n" +
|
||||||
"\bmedia_id\x18\x01 \x01(\tR\amediaId\x12\x1f\n" +
|
"\bmedia_id\x18\x01 \x01(\tR\amediaId\x12\x1f\n" +
|
||||||
"\vanalysis_id\x18\x02 \x01(\tR\n" +
|
"\vanalysis_id\x18\x02 \x01(\tR\n" +
|
||||||
@ -1772,7 +1301,7 @@ const file_v1_invoke_proto_rawDesc = "" +
|
|||||||
"sourceType\x12\"\n" +
|
"sourceType\x12\"\n" +
|
||||||
"\rsource_ref_id\x18\a \x01(\tR\vsourceRefId\x12!\n" +
|
"\rsource_ref_id\x18\a \x01(\tR\vsourceRefId\x12!\n" +
|
||||||
"\fmoderated_by\x18\b \x01(\tR\vmoderatedBy\x12\x12\n" +
|
"\fmoderated_by\x18\b \x01(\tR\vmoderatedBy\x12\x12\n" +
|
||||||
"\x04note\x18\t \x01(\tR\x04note*\xea\x02\n" +
|
"\x04note\x18\t \x01(\tR\x04note*\xf9\x01\n" +
|
||||||
"\x04Hook\x12\x14\n" +
|
"\x04Hook\x12\x14\n" +
|
||||||
"\x10HOOK_UNSPECIFIED\x10\x00\x12\x15\n" +
|
"\x10HOOK_UNSPECIFIED\x10\x00\x12\x15\n" +
|
||||||
"\x11HOOK_RENDER_BLOCK\x10\x01\x12\x18\n" +
|
"\x11HOOK_RENDER_BLOCK\x10\x01\x12\x18\n" +
|
||||||
@ -1786,11 +1315,7 @@ const file_v1_invoke_proto_rawDesc = "" +
|
|||||||
"\rHOOK_DESCRIBE\x10\t\x12\x13\n" +
|
"\rHOOK_DESCRIBE\x10\t\x12\x13\n" +
|
||||||
"\x0fHOOK_RENDER_TAG\x10\n" +
|
"\x0fHOOK_RENDER_TAG\x10\n" +
|
||||||
"\x12\x15\n" +
|
"\x12\x15\n" +
|
||||||
"\x11HOOK_APPLY_FILTER\x10\v\x12\x15\n" +
|
"\x11HOOK_APPLY_FILTER\x10\v*\xf3\x01\n" +
|
||||||
"\x11HOOK_AI_TOOL_CALL\x10\f\x12\x14\n" +
|
|
||||||
"\x10HOOK_BRIDGE_CALL\x10\r\x12 \n" +
|
|
||||||
"\x1cHOOK_DIRECTORY_PANEL_SECTION\x10\x0e\x12 \n" +
|
|
||||||
"\x1cHOOK_DIRECTORY_PIN_DECORATOR\x10\x0f*\xf3\x01\n" +
|
|
||||||
"\fAbiErrorCode\x12\x1e\n" +
|
"\fAbiErrorCode\x12\x1e\n" +
|
||||||
"\x1aABI_ERROR_CODE_UNSPECIFIED\x10\x00\x12\x1b\n" +
|
"\x1aABI_ERROR_CODE_UNSPECIFIED\x10\x00\x12\x1b\n" +
|
||||||
"\x17ABI_ERROR_CODE_INTERNAL\x10\x01\x12\x19\n" +
|
"\x17ABI_ERROR_CODE_INTERNAL\x10\x01\x12\x19\n" +
|
||||||
@ -1813,46 +1338,38 @@ func file_v1_invoke_proto_rawDescGZIP() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var file_v1_invoke_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
var file_v1_invoke_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||||
var file_v1_invoke_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
|
var file_v1_invoke_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
|
||||||
var file_v1_invoke_proto_goTypes = []any{
|
var file_v1_invoke_proto_goTypes = []any{
|
||||||
(Hook)(0), // 0: abi.v1.Hook
|
(Hook)(0), // 0: abi.v1.Hook
|
||||||
(AbiErrorCode)(0), // 1: abi.v1.AbiErrorCode
|
(AbiErrorCode)(0), // 1: abi.v1.AbiErrorCode
|
||||||
(*InvokeRequest)(nil), // 2: abi.v1.InvokeRequest
|
(*InvokeRequest)(nil), // 2: abi.v1.InvokeRequest
|
||||||
(*InvokeResponse)(nil), // 3: abi.v1.InvokeResponse
|
(*InvokeResponse)(nil), // 3: abi.v1.InvokeResponse
|
||||||
(*AbiError)(nil), // 4: abi.v1.AbiError
|
(*AbiError)(nil), // 4: abi.v1.AbiError
|
||||||
(*DescribeRequest)(nil), // 5: abi.v1.DescribeRequest
|
(*DescribeRequest)(nil), // 5: abi.v1.DescribeRequest
|
||||||
(*DescribeResponse)(nil), // 6: abi.v1.DescribeResponse
|
(*DescribeResponse)(nil), // 6: abi.v1.DescribeResponse
|
||||||
(*LoadRequest)(nil), // 7: abi.v1.LoadRequest
|
(*LoadRequest)(nil), // 7: abi.v1.LoadRequest
|
||||||
(*HostConfig)(nil), // 8: abi.v1.HostConfig
|
(*HostConfig)(nil), // 8: abi.v1.HostConfig
|
||||||
(*LoadResponse)(nil), // 9: abi.v1.LoadResponse
|
(*LoadResponse)(nil), // 9: abi.v1.LoadResponse
|
||||||
(*UnloadRequest)(nil), // 10: abi.v1.UnloadRequest
|
(*UnloadRequest)(nil), // 10: abi.v1.UnloadRequest
|
||||||
(*UnloadResponse)(nil), // 11: abi.v1.UnloadResponse
|
(*UnloadResponse)(nil), // 11: abi.v1.UnloadResponse
|
||||||
(*JobRequest)(nil), // 12: abi.v1.JobRequest
|
(*JobRequest)(nil), // 12: abi.v1.JobRequest
|
||||||
(*JobResponse)(nil), // 13: abi.v1.JobResponse
|
(*JobResponse)(nil), // 13: abi.v1.JobResponse
|
||||||
(*RagFetchRequest)(nil), // 14: abi.v1.RagFetchRequest
|
(*RagFetchRequest)(nil), // 14: abi.v1.RagFetchRequest
|
||||||
(*RagFetchResponse)(nil), // 15: abi.v1.RagFetchResponse
|
(*RagFetchResponse)(nil), // 15: abi.v1.RagFetchResponse
|
||||||
(*MediaHookRequest)(nil), // 16: abi.v1.MediaHookRequest
|
(*MediaHookRequest)(nil), // 16: abi.v1.MediaHookRequest
|
||||||
(*MediaHookResponse)(nil), // 17: abi.v1.MediaHookResponse
|
(*MediaHookResponse)(nil), // 17: abi.v1.MediaHookResponse
|
||||||
(*MediaAnalyzedEvent)(nil), // 18: abi.v1.MediaAnalyzedEvent
|
(*MediaAnalyzedEvent)(nil), // 18: abi.v1.MediaAnalyzedEvent
|
||||||
(*AiToolCallRequest)(nil), // 19: abi.v1.AiToolCallRequest
|
(*ModerationDecisionEvent)(nil), // 19: abi.v1.ModerationDecisionEvent
|
||||||
(*AiToolCallResponse)(nil), // 20: abi.v1.AiToolCallResponse
|
(*PluginManifest)(nil), // 20: abi.v1.PluginManifest
|
||||||
(*BridgeCallRequest)(nil), // 21: abi.v1.BridgeCallRequest
|
|
||||||
(*BridgeCallResponse)(nil), // 22: abi.v1.BridgeCallResponse
|
|
||||||
(*DirectoryPanelSectionRequest)(nil), // 23: abi.v1.DirectoryPanelSectionRequest
|
|
||||||
(*DirectoryPanelSectionResponse)(nil), // 24: abi.v1.DirectoryPanelSectionResponse
|
|
||||||
(*DirectoryPinDecoratorRequest)(nil), // 25: abi.v1.DirectoryPinDecoratorRequest
|
|
||||||
(*DirectoryPinDecoratorResponse)(nil), // 26: abi.v1.DirectoryPinDecoratorResponse
|
|
||||||
(*ModerationDecisionEvent)(nil), // 27: abi.v1.ModerationDecisionEvent
|
|
||||||
(*PluginManifest)(nil), // 28: abi.v1.PluginManifest
|
|
||||||
}
|
}
|
||||||
var file_v1_invoke_proto_depIdxs = []int32{
|
var file_v1_invoke_proto_depIdxs = []int32{
|
||||||
0, // 0: abi.v1.InvokeRequest.hook:type_name -> abi.v1.Hook
|
0, // 0: abi.v1.InvokeRequest.hook:type_name -> abi.v1.Hook
|
||||||
4, // 1: abi.v1.InvokeResponse.error:type_name -> abi.v1.AbiError
|
4, // 1: abi.v1.InvokeResponse.error:type_name -> abi.v1.AbiError
|
||||||
1, // 2: abi.v1.AbiError.code:type_name -> abi.v1.AbiErrorCode
|
1, // 2: abi.v1.AbiError.code:type_name -> abi.v1.AbiErrorCode
|
||||||
28, // 3: abi.v1.DescribeResponse.manifest:type_name -> abi.v1.PluginManifest
|
20, // 3: abi.v1.DescribeResponse.manifest:type_name -> abi.v1.PluginManifest
|
||||||
8, // 4: abi.v1.LoadRequest.host_config:type_name -> abi.v1.HostConfig
|
8, // 4: abi.v1.LoadRequest.host_config:type_name -> abi.v1.HostConfig
|
||||||
18, // 5: abi.v1.MediaHookRequest.media_analyzed:type_name -> abi.v1.MediaAnalyzedEvent
|
18, // 5: abi.v1.MediaHookRequest.media_analyzed:type_name -> abi.v1.MediaAnalyzedEvent
|
||||||
27, // 6: abi.v1.MediaHookRequest.moderation_decision:type_name -> abi.v1.ModerationDecisionEvent
|
19, // 6: abi.v1.MediaHookRequest.moderation_decision:type_name -> abi.v1.ModerationDecisionEvent
|
||||||
7, // [7:7] is the sub-list for method output_type
|
7, // [7:7] is the sub-list for method output_type
|
||||||
7, // [7:7] is the sub-list for method input_type
|
7, // [7:7] is the sub-list for method input_type
|
||||||
7, // [7:7] is the sub-list for extension type_name
|
7, // [7:7] is the sub-list for extension type_name
|
||||||
@ -1876,7 +1393,7 @@ func file_v1_invoke_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_v1_invoke_proto_rawDesc), len(file_v1_invoke_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_v1_invoke_proto_rawDesc), len(file_v1_invoke_proto_rawDesc)),
|
||||||
NumEnums: 2,
|
NumEnums: 2,
|
||||||
NumMessages: 26,
|
NumMessages: 18,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -215,7 +215,7 @@ func checkGoVersion(ctx context.Context) error {
|
|||||||
minor, _ := strconv.Atoi(m[2])
|
minor, _ := strconv.Atoi(m[2])
|
||||||
if major < minGoMajor || (major == minGoMajor && minor < minGoMinor) {
|
if major < minGoMajor || (major == minGoMajor && minor < minGoMinor) {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"building reactor-mode wasip1 plugins requires Go %d.%d+; found %d.%d — upgrade your toolchain",
|
"Go %d.%d+ is required to build reactor-mode wasip1 plugins; found %d.%d — upgrade your toolchain",
|
||||||
minGoMajor, minGoMinor, major, minor)
|
minGoMajor, minGoMinor, major, minor)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
abiv1 "git.dev.alexdunmow.com/block/core/abi/v1"
|
abiv1 "git.dev.alexdunmow.com/block/core/abi/v1"
|
||||||
@ -217,8 +216,10 @@ func safeJoin(root, name string) (string, error) {
|
|||||||
if filepath.IsAbs(name) || strings.HasPrefix(norm, "/") {
|
if filepath.IsAbs(name) || strings.HasPrefix(norm, "/") {
|
||||||
return "", fmt.Errorf("bnp: absolute entry path %q rejected", name)
|
return "", fmt.Errorf("bnp: absolute entry path %q rejected", name)
|
||||||
}
|
}
|
||||||
if slices.Contains(strings.Split(norm, "/"), "..") {
|
for _, seg := range strings.Split(norm, "/") {
|
||||||
return "", fmt.Errorf("bnp: entry %q contains a traversal segment", name)
|
if seg == ".." {
|
||||||
|
return "", fmt.Errorf("bnp: entry %q contains a traversal segment", name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
target := filepath.Join(root, filepath.Clean(norm))
|
target := filepath.Join(root, filepath.Clean(norm))
|
||||||
rel, err := filepath.Rel(root, target)
|
rel, err := filepath.Rel(root, target)
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
package content
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Author provides imperative page/post authoring for plugins (WO-WZ-019).
|
|
||||||
// The CMS implements this interface and wires it into CoreServices.
|
|
||||||
//
|
|
||||||
// These are the runtime counterparts of the idempotent seed-time operations
|
|
||||||
// on plugin.Provisioner: CreatePage is create-or-return-existing, the rest
|
|
||||||
// mutate an existing page/post. Blog posts live in the CMS's dedicated
|
|
||||||
// blog_posts table (not the pages table).
|
|
||||||
type Author interface {
|
|
||||||
CreatePage(ctx context.Context, params CreatePageParams) (CreatePageResult, error)
|
|
||||||
SetPageBlocks(ctx context.Context, pageID uuid.UUID, blocks []PageBlock) error
|
|
||||||
PublishPage(ctx context.Context, pageID uuid.UUID) error
|
|
||||||
SetPageSEO(ctx context.Context, pageID uuid.UUID, seo PageSEO) error
|
|
||||||
UpsertPost(ctx context.Context, params UpsertPostParams) (UpsertPostResult, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePageParams describes a page to create.
|
|
||||||
type CreatePageParams struct {
|
|
||||||
Slug string
|
|
||||||
ParentSlug string // "" = root
|
|
||||||
Title string
|
|
||||||
TemplateKey string
|
|
||||||
MasterPageKey string // "" = none
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePageResult reports the created (or pre-existing) page.
|
|
||||||
type CreatePageResult struct {
|
|
||||||
PageID uuid.UUID
|
|
||||||
// Created is false when a page with the slug already existed; the
|
|
||||||
// existing ID is returned and nothing is modified.
|
|
||||||
Created bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// PageBlock is one block instance placed on a page.
|
|
||||||
type PageBlock struct {
|
|
||||||
BlockKey string
|
|
||||||
Title string
|
|
||||||
Content map[string]any
|
|
||||||
HTMLContent *string
|
|
||||||
Slot string
|
|
||||||
SortOrder int32
|
|
||||||
}
|
|
||||||
|
|
||||||
// PageSEO carries the SEO fields settable on a page. Nil pointers leave the
|
|
||||||
// existing value untouched.
|
|
||||||
type PageSEO struct {
|
|
||||||
MetaTitle *string
|
|
||||||
MetaDescription *string
|
|
||||||
OGTitle *string
|
|
||||||
OGDescription *string
|
|
||||||
OGImage *string
|
|
||||||
FocusKeyphrase *string
|
|
||||||
CanonicalURL *string
|
|
||||||
RobotsDirective *string
|
|
||||||
TwitterTitle *string
|
|
||||||
TwitterDescription *string
|
|
||||||
TwitterImage *string
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpsertPostParams creates or updates a blog post by slug.
|
|
||||||
type UpsertPostParams struct {
|
|
||||||
Slug string
|
|
||||||
Title string
|
|
||||||
Document map[string]any // BlockNote document
|
|
||||||
Excerpt *string
|
|
||||||
// AuthorProfileID / FeaturedImageID reference existing rows (e.g. a
|
|
||||||
// media.deposit result for the image).
|
|
||||||
AuthorProfileID *uuid.UUID
|
|
||||||
FeaturedImageID *uuid.UUID
|
|
||||||
// Publish publishes the post immediately after the upsert.
|
|
||||||
Publish bool
|
|
||||||
IsFeatured bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpsertPostResult reports the upserted post.
|
|
||||||
type UpsertPostResult struct {
|
|
||||||
PostID uuid.UUID
|
|
||||||
Created bool
|
|
||||||
}
|
|
||||||
@ -1,30 +1,10 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
// PluginBridge allows plugins to share services with each other.
|
// PluginBridge allows plugins to share services with each other.
|
||||||
// Plugins register named services during startup; other plugins look them up at runtime.
|
// Plugins register named services during startup; other plugins look them up at runtime.
|
||||||
//
|
|
||||||
// In-process (bundled) plugins can share typed Go values via
|
|
||||||
// RegisterService/GetService. Across the wasm sandbox a typed value cannot
|
|
||||||
// cross, so cross-plugin calls go through Invoke instead: the provider's
|
|
||||||
// service implements BridgeInvokable and the consumer calls Invoke with an
|
|
||||||
// opaque payload (JSON by convention). GetService still reports availability
|
|
||||||
// only (nil) for wasm consumers.
|
|
||||||
type PluginBridge interface {
|
type PluginBridge interface {
|
||||||
RegisterService(pluginName, serviceName string, service any)
|
RegisterService(pluginName, serviceName string, service any)
|
||||||
GetService(pluginName, serviceName string) any
|
GetService(pluginName, serviceName string) any
|
||||||
// Invoke calls a method on another plugin's registered bridge service.
|
|
||||||
// The provider answers via its BridgeInvokable implementation (bundled)
|
|
||||||
// or the BRIDGE_CALL hook (wasm).
|
|
||||||
Invoke(ctx context.Context, pluginName, serviceName, method string, payload []byte) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BridgeInvokable is implemented by bridge service values that support
|
|
||||||
// method-by-name invocation with opaque payloads, making them callable from
|
|
||||||
// other plugins across the wasm sandbox boundary.
|
|
||||||
type BridgeInvokable interface {
|
|
||||||
InvokeBridge(ctx context.Context, method string, payload []byte) ([]byte, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServiceAs retrieves a typed service from the bridge.
|
// GetServiceAs retrieves a typed service from the bridge.
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import (
|
|||||||
type CoreServices struct {
|
type CoreServices struct {
|
||||||
// Capability interfaces — typed access to CMS functionality
|
// Capability interfaces — typed access to CMS functionality
|
||||||
Content content.Content
|
Content content.Content
|
||||||
ContentAuthor content.Author
|
|
||||||
Settings settings.Settings
|
Settings settings.Settings
|
||||||
Gating gating.Gating
|
Gating gating.Gating
|
||||||
Crypto crypto.Crypto
|
Crypto crypto.Crypto
|
||||||
@ -62,12 +61,6 @@ type CoreServices struct {
|
|||||||
JobRunner JobRunner
|
JobRunner JobRunner
|
||||||
EmbeddingService EmbeddingService
|
EmbeddingService EmbeddingService
|
||||||
RAGService RAGService
|
RAGService RAGService
|
||||||
|
|
||||||
// Provisioner — idempotent ensure/seed operations. In the wasm world this
|
|
||||||
// is a LOAD-TIME capability: call it from the Load hook, not from
|
|
||||||
// Register/RegisterWithProvisioner (DESCRIBE stubs every host function to
|
|
||||||
// fail, so register-time provisioning cannot cross the ABI).
|
|
||||||
Provisioner Provisioner
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MediaDeposit describes an image a plugin deposits into the CMS media library.
|
// MediaDeposit describes an image a plugin deposits into the CMS media library.
|
||||||
|
|||||||
@ -179,7 +179,7 @@ func naturalValue(dv *abiv1.DbValue) (any, bool) {
|
|||||||
|
|
||||||
// uuidSliceType is the reflect.Type of []uuid.UUID, the canonical scan
|
// uuidSliceType is the reflect.Type of []uuid.UUID, the canonical scan
|
||||||
// destination sqlc emits for a uuid[] column (and COALESCE(...::uuid[]) arg).
|
// destination sqlc emits for a uuid[] column (and COALESCE(...::uuid[]) arg).
|
||||||
var uuidSliceType = reflect.TypeFor[[]uuid.UUID]()
|
var uuidSliceType = reflect.TypeOf([]uuid.UUID(nil))
|
||||||
|
|
||||||
// scanValue assigns a DbValue into a destination pointer with pgx-flavored Scan
|
// scanValue assigns a DbValue into a destination pointer with pgx-flavored Scan
|
||||||
// semantics: a nil dest skips the column; a sql.Scanner destination is fed the
|
// semantics: a nil dest skips the column; a sql.Scanner destination is fed the
|
||||||
|
|||||||
@ -280,7 +280,7 @@ func TestDatabaseSQLDriverRegistered(t *testing.T) {
|
|||||||
&abiv1.DbValue{Kind: &abiv1.DbValue_StringValue{StringValue: "alice"}},
|
&abiv1.DbValue{Kind: &abiv1.DbValue_StringValue{StringValue: "alice"}},
|
||||||
)}
|
)}
|
||||||
db := sql.OpenDB(NewConnector(host.call))
|
db := sql.OpenDB(NewConnector(host.call))
|
||||||
defer func() { _ = db.Close() }()
|
defer db.Close()
|
||||||
|
|
||||||
var id int
|
var id int
|
||||||
var name string
|
var name string
|
||||||
|
|||||||
@ -12,15 +12,9 @@ import (
|
|||||||
// CoreServices.AITextCall func field (ai.text_call).
|
// CoreServices.AITextCall func field (ai.text_call).
|
||||||
//
|
//
|
||||||
// ToolDefinition.Handler stays guest-side: registration marshals only the
|
// ToolDefinition.Handler stays guest-side: registration marshals only the
|
||||||
// static descriptor AND records the full definition locally so the host can
|
// static descriptor. Host→guest tool execution is a runtime-WO concern
|
||||||
// execute the Handler via HOOK_AI_TOOL_CALL. Register tools in Register (every
|
// flagged in core/docs/wasm-abi.md.
|
||||||
// pooled instance runs it), not Load (one instance only) — see
|
type aiStub struct{ base }
|
||||||
// core/docs/wasm-abi.md §"Per-instance state". Instances are single-threaded,
|
|
||||||
// so the plain map needs no locking.
|
|
||||||
type aiStub struct {
|
|
||||||
base
|
|
||||||
tools map[string]*ai.ToolDefinition // slug → definition (with Handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ai.ToolRegistry = (*aiStub)(nil)
|
var _ ai.ToolRegistry = (*aiStub)(nil)
|
||||||
|
|
||||||
@ -30,10 +24,6 @@ func (s *aiStub) Register(tool *ai.ToolDefinition) {
|
|||||||
if tool == nil {
|
if tool == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if s.tools == nil {
|
|
||||||
s.tools = make(map[string]*ai.ToolDefinition)
|
|
||||||
}
|
|
||||||
s.tools[tool.Slug] = tool
|
|
||||||
req := &abiv1.AiToolRegisterRequest{
|
req := &abiv1.AiToolRegisterRequest{
|
||||||
Slug: tool.Slug,
|
Slug: tool.Slug,
|
||||||
Name: tool.Name,
|
Name: tool.Name,
|
||||||
@ -47,13 +37,6 @@ func (s *aiStub) Register(tool *ai.ToolDefinition) {
|
|||||||
_ = s.invoke(context.Background(), "tools.register", req, &abiv1.AiToolRegisterResponse{})
|
_ = s.invoke(context.Background(), "tools.register", req, &abiv1.AiToolRegisterResponse{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tool returns the registered tool definition for a slug, for
|
|
||||||
// HOOK_AI_TOOL_CALL dispatch by the wasm shim.
|
|
||||||
func (s *aiStub) Tool(slug string) (*ai.ToolDefinition, bool) {
|
|
||||||
t, ok := s.tools[slug]
|
|
||||||
return t, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// textCall backs CoreServices.AITextCall.
|
// textCall backs CoreServices.AITextCall.
|
||||||
func (s *aiStub) textCall(ctx context.Context, taskKey, systemPrompt, userMessage string) (string, error) {
|
func (s *aiStub) textCall(ctx context.Context, taskKey, systemPrompt, userMessage string) (string, error) {
|
||||||
req := &abiv1.AiTextCallRequest{
|
req := &abiv1.AiTextCallRequest{
|
||||||
|
|||||||
@ -1,120 +0,0 @@
|
|||||||
package caps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
abiv1 "git.dev.alexdunmow.com/block/core/abi/v1"
|
|
||||||
"git.dev.alexdunmow.com/block/core/content"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// authorStub implements content.Author over the content.* write capability
|
|
||||||
// calls (WO-WZ-019). Same family as contentStub; split so the read surface
|
|
||||||
// stays untouched.
|
|
||||||
type authorStub struct{ base }
|
|
||||||
|
|
||||||
var _ content.Author = (*authorStub)(nil)
|
|
||||||
|
|
||||||
func (s *authorStub) CreatePage(ctx context.Context, params content.CreatePageParams) (content.CreatePageResult, error) {
|
|
||||||
req := &abiv1.ContentCreatePageRequest{
|
|
||||||
Slug: params.Slug,
|
|
||||||
ParentSlug: params.ParentSlug,
|
|
||||||
Title: params.Title,
|
|
||||||
TemplateKey: params.TemplateKey,
|
|
||||||
MasterPageKey: params.MasterPageKey,
|
|
||||||
}
|
|
||||||
resp := &abiv1.ContentCreatePageResponse{}
|
|
||||||
if err := s.invoke(ctx, "create_page", req, resp); err != nil {
|
|
||||||
return content.CreatePageResult{}, err
|
|
||||||
}
|
|
||||||
id, err := uuid.Parse(resp.GetPageId())
|
|
||||||
if err != nil {
|
|
||||||
return content.CreatePageResult{}, fmt.Errorf("content.create_page: bad page_id %q: %w", resp.GetPageId(), err)
|
|
||||||
}
|
|
||||||
return content.CreatePageResult{PageID: id, Created: resp.GetCreated()}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *authorStub) SetPageBlocks(ctx context.Context, pageID uuid.UUID, blocks []content.PageBlock) error {
|
|
||||||
req := &abiv1.ContentSetPageBlocksRequest{PageId: pageID.String()}
|
|
||||||
for _, b := range blocks {
|
|
||||||
pb, err := pageBlockToProto(b)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("content.set_page_blocks: block %q: %w", b.BlockKey, err)
|
|
||||||
}
|
|
||||||
req.Blocks = append(req.Blocks, pb)
|
|
||||||
}
|
|
||||||
return s.invoke(ctx, "set_page_blocks", req, &abiv1.ContentSetPageBlocksResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *authorStub) PublishPage(ctx context.Context, pageID uuid.UUID) error {
|
|
||||||
req := &abiv1.ContentPublishPageRequest{PageId: pageID.String()}
|
|
||||||
return s.invoke(ctx, "publish_page", req, &abiv1.ContentPublishPageResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *authorStub) SetPageSEO(ctx context.Context, pageID uuid.UUID, seo content.PageSEO) error {
|
|
||||||
req := &abiv1.ContentSetPageSeoRequest{
|
|
||||||
PageId: pageID.String(),
|
|
||||||
MetaTitle: seo.MetaTitle,
|
|
||||||
MetaDescription: seo.MetaDescription,
|
|
||||||
OgTitle: seo.OGTitle,
|
|
||||||
OgDescription: seo.OGDescription,
|
|
||||||
OgImage: seo.OGImage,
|
|
||||||
FocusKeyphrase: seo.FocusKeyphrase,
|
|
||||||
CanonicalUrl: seo.CanonicalURL,
|
|
||||||
RobotsDirective: seo.RobotsDirective,
|
|
||||||
TwitterTitle: seo.TwitterTitle,
|
|
||||||
TwitterDescription: seo.TwitterDescription,
|
|
||||||
TwitterImage: seo.TwitterImage,
|
|
||||||
}
|
|
||||||
return s.invoke(ctx, "set_page_seo", req, &abiv1.ContentSetPageSeoResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *authorStub) UpsertPost(ctx context.Context, params content.UpsertPostParams) (content.UpsertPostResult, error) {
|
|
||||||
docJSON, err := json.Marshal(params.Document)
|
|
||||||
if err != nil {
|
|
||||||
return content.UpsertPostResult{}, fmt.Errorf("content.upsert_post: marshal document: %w", err)
|
|
||||||
}
|
|
||||||
req := &abiv1.ContentUpsertPostRequest{
|
|
||||||
Slug: params.Slug,
|
|
||||||
Title: params.Title,
|
|
||||||
DocumentJson: docJSON,
|
|
||||||
Excerpt: params.Excerpt,
|
|
||||||
Publish: params.Publish,
|
|
||||||
IsFeatured: params.IsFeatured,
|
|
||||||
}
|
|
||||||
if params.AuthorProfileID != nil {
|
|
||||||
v := params.AuthorProfileID.String()
|
|
||||||
req.AuthorProfileId = &v
|
|
||||||
}
|
|
||||||
if params.FeaturedImageID != nil {
|
|
||||||
v := params.FeaturedImageID.String()
|
|
||||||
req.FeaturedImageId = &v
|
|
||||||
}
|
|
||||||
resp := &abiv1.ContentUpsertPostResponse{}
|
|
||||||
if err := s.invoke(ctx, "upsert_post", req, resp); err != nil {
|
|
||||||
return content.UpsertPostResult{}, err
|
|
||||||
}
|
|
||||||
id, err := uuid.Parse(resp.GetPostId())
|
|
||||||
if err != nil {
|
|
||||||
return content.UpsertPostResult{}, fmt.Errorf("content.upsert_post: bad post_id %q: %w", resp.GetPostId(), err)
|
|
||||||
}
|
|
||||||
return content.UpsertPostResult{PostID: id, Created: resp.GetCreated()}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pageBlockToProto converts one content.PageBlock to its wire form.
|
|
||||||
func pageBlockToProto(b content.PageBlock) (*abiv1.PageBlock, error) {
|
|
||||||
contentJSON, err := json.Marshal(b.Content)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &abiv1.PageBlock{
|
|
||||||
BlockKey: b.BlockKey,
|
|
||||||
Title: b.Title,
|
|
||||||
ContentJson: contentJSON,
|
|
||||||
HtmlContent: b.HTMLContent,
|
|
||||||
Slot: b.Slot,
|
|
||||||
SortOrder: b.SortOrder,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@ -11,64 +11,25 @@ import (
|
|||||||
//
|
//
|
||||||
// The bridge shares in-process Go values today; across sandboxes only the
|
// The bridge shares in-process Go values today; across sandboxes only the
|
||||||
// registration/lookup *surface* serializes — the service value itself cannot
|
// registration/lookup *surface* serializes — the service value itself cannot
|
||||||
// cross. RegisterService therefore forwards only plugin/service names to the
|
// cross. RegisterService therefore forwards only plugin/service names (the
|
||||||
// host AND records the value locally so this plugin's own services are
|
// value is dropped), and GetService returns nil (availability is checked but a
|
||||||
// dispatchable via HOOK_BRIDGE_CALL; GetService returns nil for remote
|
// typed value cannot be reconstructed guest-side). Typed cross-plugin
|
||||||
// services (availability is checked but a typed value cannot be reconstructed
|
// invocation is an open ABI item (core/docs/wasm-abi.md).
|
||||||
// guest-side). Cross-plugin calls use Invoke (bridge.invoke) with opaque
|
type bridgeStub struct{ base }
|
||||||
// payloads instead.
|
|
||||||
//
|
|
||||||
// Instances are single-threaded, so the plain map needs no locking; register
|
|
||||||
// bridge services in Register (every pooled instance runs it), not Load
|
|
||||||
// (which fires on one instance only) — see core/docs/wasm-abi.md
|
|
||||||
// §"Per-instance state".
|
|
||||||
type bridgeStub struct {
|
|
||||||
base
|
|
||||||
services map[string]any // serviceName → registered value (this plugin's own)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ plugin.PluginBridge = (*bridgeStub)(nil)
|
var _ plugin.PluginBridge = (*bridgeStub)(nil)
|
||||||
|
|
||||||
// RegisterService has no error channel; a transport failure is dropped.
|
// RegisterService has no error channel; a transport failure is dropped.
|
||||||
func (s *bridgeStub) RegisterService(pluginName, serviceName string, service any) {
|
func (s *bridgeStub) RegisterService(pluginName, serviceName string, _ any) {
|
||||||
if s.services == nil {
|
|
||||||
s.services = make(map[string]any)
|
|
||||||
}
|
|
||||||
s.services[serviceName] = service
|
|
||||||
req := &abiv1.BridgeRegisterServiceRequest{PluginName: pluginName, ServiceName: serviceName}
|
req := &abiv1.BridgeRegisterServiceRequest{PluginName: pluginName, ServiceName: serviceName}
|
||||||
_ = s.invoke(context.Background(), "register_service", req, &abiv1.BridgeRegisterServiceResponse{})
|
_ = s.invoke(context.Background(), "register_service", req, &abiv1.BridgeRegisterServiceResponse{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetService reports availability host-side but cannot return the concrete Go
|
// GetService reports availability host-side but cannot return the concrete Go
|
||||||
// value across the sandbox boundary, so it always returns nil for remote
|
// value across the sandbox boundary, so it always returns nil. Callers using
|
||||||
// services. Callers using plugin.GetServiceAs correctly observe (zero, false);
|
// plugin.GetServiceAs correctly observe (zero, false).
|
||||||
// cross-plugin calls go through Invoke.
|
|
||||||
func (s *bridgeStub) GetService(pluginName, serviceName string) any {
|
func (s *bridgeStub) GetService(pluginName, serviceName string) any {
|
||||||
req := &abiv1.BridgeGetServiceRequest{PluginName: pluginName, ServiceName: serviceName}
|
req := &abiv1.BridgeGetServiceRequest{PluginName: pluginName, ServiceName: serviceName}
|
||||||
_ = s.invoke(context.Background(), "get_service", req, &abiv1.BridgeGetServiceResponse{})
|
_ = s.invoke(context.Background(), "get_service", req, &abiv1.BridgeGetServiceResponse{})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invoke calls a method on another plugin's registered bridge service with an
|
|
||||||
// opaque payload (bridge.invoke; the provider answers via HOOK_BRIDGE_CALL or
|
|
||||||
// its in-process plugin.BridgeInvokable).
|
|
||||||
func (s *bridgeStub) Invoke(ctx context.Context, pluginName, serviceName, method string, payload []byte) ([]byte, error) {
|
|
||||||
req := &abiv1.BridgeInvokeRequest{
|
|
||||||
PluginName: pluginName,
|
|
||||||
ServiceName: serviceName,
|
|
||||||
Method: method,
|
|
||||||
Payload: payload,
|
|
||||||
}
|
|
||||||
resp := &abiv1.BridgeInvokeResponse{}
|
|
||||||
if err := s.invoke(ctx, "invoke", req, resp); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp.GetPayload(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service returns this plugin's own registered service value, for
|
|
||||||
// HOOK_BRIDGE_CALL dispatch by the wasm shim.
|
|
||||||
func (s *bridgeStub) Service(serviceName string) (any, bool) {
|
|
||||||
v, ok := s.services[serviceName]
|
|
||||||
return v, ok
|
|
||||||
}
|
|
||||||
|
|||||||
@ -611,137 +611,6 @@ func TestCapabilityRoundTrip(t *testing.T) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// --- content writes (WO-WZ-019) ---
|
|
||||||
{
|
|
||||||
name: "content_create_page", wantMethod: "content.create_page",
|
|
||||||
resp: &abiv1.ContentCreatePageResponse{PageId: idItem.String(), Created: true},
|
|
||||||
run: func(t *testing.T, cs plugin.CoreServices) {
|
|
||||||
got, err := cs.ContentAuthor.CreatePage(ctx, content.CreatePageParams{
|
|
||||||
Slug: "/pricing", ParentSlug: "", Title: "Pricing", TemplateKey: "landing",
|
|
||||||
})
|
|
||||||
if err != nil || got.PageID != idItem || !got.Created {
|
|
||||||
t.Errorf("create_page = %+v err = %v", got, err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "content_set_page_blocks", wantMethod: "content.set_page_blocks",
|
|
||||||
resp: &abiv1.ContentSetPageBlocksResponse{},
|
|
||||||
run: func(t *testing.T, cs plugin.CoreServices) {
|
|
||||||
html := "<h1>Hi</h1>"
|
|
||||||
err := cs.ContentAuthor.SetPageBlocks(ctx, idItem, []content.PageBlock{
|
|
||||||
{BlockKey: "html", Title: "Hero", Content: map[string]any{"align": "center"}, HTMLContent: &html, Slot: "main", SortOrder: 1},
|
|
||||||
{BlockKey: "cta", Title: "CTA", Content: map[string]any{"label": "Go"}, SortOrder: 2},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "content_publish_page", wantMethod: "content.publish_page",
|
|
||||||
resp: &abiv1.ContentPublishPageResponse{},
|
|
||||||
run: func(t *testing.T, cs plugin.CoreServices) {
|
|
||||||
if err := cs.ContentAuthor.PublishPage(ctx, idItem); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "content_set_page_seo", wantMethod: "content.set_page_seo",
|
|
||||||
resp: &abiv1.ContentSetPageSeoResponse{},
|
|
||||||
run: func(t *testing.T, cs plugin.CoreServices) {
|
|
||||||
mt, md := "Pricing — Acme", "Plans and pricing."
|
|
||||||
err := cs.ContentAuthor.SetPageSEO(ctx, idItem, content.PageSEO{MetaTitle: &mt, MetaDescription: &md})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "content_upsert_post", wantMethod: "content.upsert_post",
|
|
||||||
resp: &abiv1.ContentUpsertPostResponse{PostId: idRow.String(), Created: false},
|
|
||||||
run: func(t *testing.T, cs plugin.CoreServices) {
|
|
||||||
excerpt := "hello"
|
|
||||||
got, err := cs.ContentAuthor.UpsertPost(ctx, content.UpsertPostParams{
|
|
||||||
Slug: "hello", Title: "Hello", Document: map[string]any{"type": "doc"},
|
|
||||||
Excerpt: &excerpt, AuthorProfileID: &idAuthor, FeaturedImageID: &idMedia,
|
|
||||||
Publish: true,
|
|
||||||
})
|
|
||||||
if err != nil || got.PostID != idRow || got.Created {
|
|
||||||
t.Errorf("upsert_post = %+v err = %v", got, err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// --- settings.update_plugin_settings (WO-WZ-019) ---
|
|
||||||
{
|
|
||||||
name: "settings_update_plugin_settings", wantMethod: "settings.update_plugin_settings",
|
|
||||||
resp: &abiv1.SettingsUpdatePluginSettingsResponse{},
|
|
||||||
run: func(t *testing.T, cs plugin.CoreServices) {
|
|
||||||
err := cs.SettingsUpdater.UpdatePluginSettings(ctx, "myplugin", map[string]any{"enabled": true})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// --- provisioner (WO-WZ-019) ---
|
|
||||||
{
|
|
||||||
name: "provisioner_ensure_page", wantMethod: "provisioner.ensure_page",
|
|
||||||
resp: &abiv1.ProvisionerEnsurePageResponse{},
|
|
||||||
run: func(t *testing.T, cs plugin.CoreServices) {
|
|
||||||
err := cs.Provisioner.EnsurePage(plugin.PageConfig{
|
|
||||||
Slug: "/", Title: "Home", TemplateKey: "homepage", ReconcileBlocks: true,
|
|
||||||
Blocks: []plugin.PageBlockConfig{
|
|
||||||
{BlockKey: "html", Title: "Hero", Content: map[string]any{"x": float64(1)}, Slot: "main", SortOrder: 1},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "provisioner_merge_site_settings", wantMethod: "provisioner.merge_site_settings",
|
|
||||||
resp: &abiv1.ProvisionerMergeSiteSettingsResponse{},
|
|
||||||
run: func(t *testing.T, cs plugin.CoreServices) {
|
|
||||||
if err := cs.Provisioner.MergeSiteSettings(map[string]any{"site_name": "Acme"}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "provisioner_ensure_menu_item", wantMethod: "provisioner.ensure_menu_item",
|
|
||||||
resp: &abiv1.ProvisionerEnsureMenuItemResponse{},
|
|
||||||
run: func(t *testing.T, cs plugin.CoreServices) {
|
|
||||||
err := cs.Provisioner.EnsureMenuItem("main", plugin.MenuItemConfig{Label: "Docs", PageSlug: "/docs", SortOrder: 3})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "provisioner_ensure_media", wantMethod: "provisioner.ensure_media",
|
|
||||||
resp: &abiv1.ProvisionerEnsureMediaResponse{},
|
|
||||||
run: func(t *testing.T, cs plugin.CoreServices) {
|
|
||||||
err := cs.Provisioner.EnsureMedia(plugin.MediaDeposit{
|
|
||||||
ID: idMedia, Filename: "hero.jpg", Data: []byte{1, 2, 3}, AltText: "hero", Folder: "seed", Source: "myplugin",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// --- bridge.invoke (WO-WZ-019) ---
|
|
||||||
{
|
|
||||||
name: "bridge_invoke", wantMethod: "bridge.invoke",
|
|
||||||
resp: &abiv1.BridgeInvokeResponse{Payload: []byte(`{"ok":true}`)},
|
|
||||||
run: func(t *testing.T, cs plugin.CoreServices) {
|
|
||||||
got, err := cs.Bridge.Invoke(ctx, "symposium", "seeder", "seed_forum", []byte(`{"n":1}`))
|
|
||||||
if err != nil || string(got) != `{"ok":true}` {
|
|
||||||
t.Errorf("bridge.invoke = %q err = %v", got, err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
|||||||
@ -22,12 +22,11 @@ import (
|
|||||||
// - MediaPath / AppURL → delivered in LoadRequest.host_config at load
|
// - MediaPath / AppURL → delivered in LoadRequest.host_config at load
|
||||||
// - CoreServiceBindings → static manifest.core_service_bindings; host mounts
|
// - CoreServiceBindings → static manifest.core_service_bindings; host mounts
|
||||||
func NewCoreServices(call CallFunc) plugin.CoreServices {
|
func NewCoreServices(call CallFunc) plugin.CoreServices {
|
||||||
settings := &settingsStub{base: base{family: "settings", call: call}}
|
settings := &settingsStub{base{family: "settings", call: call}}
|
||||||
ai := &aiStub{base: base{family: "ai", call: call}}
|
ai := &aiStub{base{family: "ai", call: call}}
|
||||||
|
|
||||||
return plugin.CoreServices{
|
return plugin.CoreServices{
|
||||||
Content: &contentStub{base{family: "content", call: call}},
|
Content: &contentStub{base{family: "content", call: call}},
|
||||||
ContentAuthor: &authorStub{base{family: "content", call: call}},
|
|
||||||
Settings: settings,
|
Settings: settings,
|
||||||
SettingsUpdater: settings,
|
SettingsUpdater: settings,
|
||||||
Gating: &gatingStub{base{family: "gating", call: call}},
|
Gating: &gatingStub{base{family: "gating", call: call}},
|
||||||
@ -40,12 +39,11 @@ func NewCoreServices(call CallFunc) plugin.CoreServices {
|
|||||||
ToolRegistry: ai,
|
ToolRegistry: ai,
|
||||||
AITextCall: ai.textCall,
|
AITextCall: ai.textCall,
|
||||||
EmailSender: &emailStub{base{family: "email", call: call}},
|
EmailSender: &emailStub{base{family: "email", call: call}},
|
||||||
Bridge: &bridgeStub{base: base{family: "bridge", call: call}},
|
Bridge: &bridgeStub{base{family: "bridge", call: call}},
|
||||||
ReviewSubmitter: &reviewsStub{base{family: "reviews", call: call}},
|
ReviewSubmitter: &reviewsStub{base{family: "reviews", call: call}},
|
||||||
BadgeRefresher: &badgesStub{base{family: "badges", call: call}},
|
BadgeRefresher: &badgesStub{base{family: "badges", call: call}},
|
||||||
JobRunner: &jobsStub{base{family: "jobs", call: call}},
|
JobRunner: &jobsStub{base{family: "jobs", call: call}},
|
||||||
EmbeddingService: &embeddingsStub{base{family: "embeddings", call: call}},
|
EmbeddingService: &embeddingsStub{base{family: "embeddings", call: call}},
|
||||||
RAGService: NewRAGStub(call),
|
RAGService: NewRAGStub(call),
|
||||||
Provisioner: &provisionerStub{base{family: "provisioner", call: call}},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,189 +0,0 @@
|
|||||||
package caps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
abiv1 "git.dev.alexdunmow.com/block/core/abi/v1"
|
|
||||||
"git.dev.alexdunmow.com/block/core/plugin"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// provisionerStub implements plugin.Provisioner over provisioner.* capability
|
|
||||||
// calls (WO-WZ-019). This is a LOAD-TIME capability: call it from the Load
|
|
||||||
// hook. RegisterWithProvisioner still receives a no-op at Register/DESCRIBE
|
|
||||||
// time (host functions are stubbed to fail there), so seed logic must move to
|
|
||||||
// Load — see core/docs/wasm-abi.md.
|
|
||||||
//
|
|
||||||
// Most Provisioner methods carry no context (the Go interface predates the
|
|
||||||
// ABI); calls run under the ambient invoke deadline the host enforces.
|
|
||||||
type provisionerStub struct{ base }
|
|
||||||
|
|
||||||
var _ plugin.Provisioner = (*provisionerStub)(nil)
|
|
||||||
|
|
||||||
func (s *provisionerStub) EnsureDataTable(config plugin.DataTableConfig) error {
|
|
||||||
req := &abiv1.ProvisionerEnsureDataTableRequest{
|
|
||||||
Key: config.Key,
|
|
||||||
Name: config.Name,
|
|
||||||
Description: config.Description,
|
|
||||||
SchemaJson: config.Schema,
|
|
||||||
PrimaryKey: config.PrimaryKey,
|
|
||||||
}
|
|
||||||
return s.invoke(context.Background(), "ensure_data_table", req, &abiv1.ProvisionerEnsureDataTableResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) MergeSiteSettings(defaults map[string]any) error {
|
|
||||||
defaultsJSON, err := json.Marshal(defaults)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("provisioner.merge_site_settings: marshal defaults: %w", err)
|
|
||||||
}
|
|
||||||
req := &abiv1.ProvisionerMergeSiteSettingsRequest{DefaultsJson: defaultsJSON}
|
|
||||||
return s.invoke(context.Background(), "merge_site_settings", req, &abiv1.ProvisionerMergeSiteSettingsResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) EnsureSetting(key string, defaultValue any) error {
|
|
||||||
valueJSON, err := json.Marshal(defaultValue)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("provisioner.ensure_setting: marshal value: %w", err)
|
|
||||||
}
|
|
||||||
req := &abiv1.ProvisionerEnsureSettingRequest{Key: key, DefaultValueJson: valueJSON}
|
|
||||||
return s.invoke(context.Background(), "ensure_setting", req, &abiv1.ProvisionerEnsureSettingResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) EnsurePage(config plugin.PageConfig) error {
|
|
||||||
seed := &abiv1.PageSeed{
|
|
||||||
Slug: config.Slug,
|
|
||||||
ParentSlug: config.ParentSlug,
|
|
||||||
Title: config.Title,
|
|
||||||
TemplateKey: config.TemplateKey,
|
|
||||||
DetailSourceType: config.DetailSourceType,
|
|
||||||
DetailSourceKey: config.DetailSourceKey,
|
|
||||||
DetailSlugField: config.DetailSlugField,
|
|
||||||
ReconcileBlocks: config.ReconcileBlocks,
|
|
||||||
ReconcileTemplate: config.ReconcileTemplate,
|
|
||||||
}
|
|
||||||
for _, b := range config.Blocks {
|
|
||||||
contentJSON, err := json.Marshal(b.Content)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("provisioner.ensure_page: block %q: marshal content: %w", b.BlockKey, err)
|
|
||||||
}
|
|
||||||
seed.Blocks = append(seed.Blocks, &abiv1.PageBlock{
|
|
||||||
BlockKey: b.BlockKey,
|
|
||||||
Title: b.Title,
|
|
||||||
ContentJson: contentJSON,
|
|
||||||
HtmlContent: b.HtmlContent,
|
|
||||||
Slot: b.Slot,
|
|
||||||
SortOrder: b.SortOrder,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
req := &abiv1.ProvisionerEnsurePageRequest{Page: seed}
|
|
||||||
return s.invoke(context.Background(), "ensure_page", req, &abiv1.ProvisionerEnsurePageResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) OverrideSiteSettings(overrides map[string]any) error {
|
|
||||||
overridesJSON, err := json.Marshal(overrides)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("provisioner.override_site_settings: marshal overrides: %w", err)
|
|
||||||
}
|
|
||||||
req := &abiv1.ProvisionerOverrideSiteSettingsRequest{OverridesJson: overridesJSON}
|
|
||||||
return s.invoke(context.Background(), "override_site_settings", req, &abiv1.ProvisionerOverrideSiteSettingsResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) EnsureMenuItem(menuName string, config plugin.MenuItemConfig) error {
|
|
||||||
req := &abiv1.ProvisionerEnsureMenuItemRequest{
|
|
||||||
MenuName: menuName,
|
|
||||||
Label: config.Label,
|
|
||||||
Url: config.URL,
|
|
||||||
PageSlug: config.PageSlug,
|
|
||||||
SortOrder: config.SortOrder,
|
|
||||||
}
|
|
||||||
return s.invoke(context.Background(), "ensure_menu_item", req, &abiv1.ProvisionerEnsureMenuItemResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) RegisterEmbeddingConfig(config plugin.EmbeddingConfigDef) error {
|
|
||||||
req := &abiv1.ProvisionerRegisterEmbeddingConfigRequest{
|
|
||||||
TableKey: config.TableKey,
|
|
||||||
TextTemplate: config.TextTemplate,
|
|
||||||
Enabled: config.Enabled,
|
|
||||||
}
|
|
||||||
return s.invoke(context.Background(), "register_embedding_config", req, &abiv1.ProvisionerRegisterEmbeddingConfigResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnsureEmbed crosses the template-rendered surface only: EmbedConfig.
|
|
||||||
// RenderFunc is a function value that cannot serialize, so an ABI-provisioned
|
|
||||||
// embed must carry a Template (a RenderFunc-only embed returns an error
|
|
||||||
// rather than silently dropping the renderer).
|
|
||||||
func (s *provisionerStub) EnsureEmbed(config plugin.EmbedConfig) error {
|
|
||||||
if config.RenderFunc != nil && config.Template == "" {
|
|
||||||
return fmt.Errorf("provisioner.ensure_embed: embed %q uses RenderFunc, which cannot cross the wasm ABI — provide a Template instead", config.Key)
|
|
||||||
}
|
|
||||||
req := &abiv1.ProvisionerEnsureEmbedRequest{
|
|
||||||
Key: config.Key,
|
|
||||||
Title: config.Title,
|
|
||||||
Description: config.Description,
|
|
||||||
Icon: config.Icon,
|
|
||||||
LabelField: config.LabelField,
|
|
||||||
Template: config.Template,
|
|
||||||
DataSourceType: config.DataSource.Type,
|
|
||||||
DataSourceTableKey: config.DataSource.TableKey,
|
|
||||||
}
|
|
||||||
return s.invoke(context.Background(), "ensure_embed", req, &abiv1.ProvisionerEnsureEmbedResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) EnsureJobSchedule(config plugin.JobScheduleConfig) error {
|
|
||||||
req := &abiv1.ProvisionerEnsureJobScheduleRequest{
|
|
||||||
JobType: config.JobType,
|
|
||||||
CronExpression: config.CronExpression,
|
|
||||||
ConfigJson: config.Config,
|
|
||||||
}
|
|
||||||
return s.invoke(context.Background(), "ensure_job_schedule", req, &abiv1.ProvisionerEnsureJobScheduleResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) UpdateDataTableRowField(ctx context.Context, rowID uuid.UUID, fieldKey string, value any) error {
|
|
||||||
valueJSON, err := json.Marshal(value)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("provisioner.update_data_table_row_field: marshal value: %w", err)
|
|
||||||
}
|
|
||||||
req := &abiv1.ProvisionerUpdateDataTableRowFieldRequest{
|
|
||||||
RowId: rowID.String(),
|
|
||||||
FieldKey: fieldKey,
|
|
||||||
ValueJson: valueJSON,
|
|
||||||
}
|
|
||||||
return s.invoke(ctx, "update_data_table_row_field", req, &abiv1.ProvisionerUpdateDataTableRowFieldResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) DisableOrphanedJobSchedules(registeredTypes []string) error {
|
|
||||||
req := &abiv1.ProvisionerDisableOrphanedJobSchedulesRequest{RegisteredTypes: registeredTypes}
|
|
||||||
return s.invoke(context.Background(), "disable_orphaned_job_schedules", req, &abiv1.ProvisionerDisableOrphanedJobSchedulesResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) EnsurePlugin(name string) error {
|
|
||||||
req := &abiv1.ProvisionerEnsurePluginRequest{Name: name}
|
|
||||||
return s.invoke(context.Background(), "ensure_plugin", req, &abiv1.ProvisionerEnsurePluginResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) EnsureCustomColor(config plugin.CustomColorConfig) error {
|
|
||||||
req := &abiv1.ProvisionerEnsureCustomColorRequest{
|
|
||||||
Name: config.Name,
|
|
||||||
LightValue: config.LightValue,
|
|
||||||
DarkValue: config.DarkValue,
|
|
||||||
Source: config.Source,
|
|
||||||
}
|
|
||||||
return s.invoke(context.Background(), "ensure_custom_color", req, &abiv1.ProvisionerEnsureCustomColorResponse{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *provisionerStub) EnsureMedia(deposit plugin.MediaDeposit) error {
|
|
||||||
if deposit.ID == uuid.Nil {
|
|
||||||
return fmt.Errorf("provisioner.ensure_media: MediaDeposit.ID is required (the deterministic, template-referable key)")
|
|
||||||
}
|
|
||||||
req := &abiv1.ProvisionerEnsureMediaRequest{
|
|
||||||
Id: deposit.ID.String(),
|
|
||||||
Filename: deposit.Filename,
|
|
||||||
Data: deposit.Data,
|
|
||||||
AltText: deposit.AltText,
|
|
||||||
Folder: deposit.Folder,
|
|
||||||
Source: deposit.Source,
|
|
||||||
}
|
|
||||||
return s.invoke(context.Background(), "ensure_media", req, &abiv1.ProvisionerEnsureMediaResponse{})
|
|
||||||
}
|
|
||||||
@ -42,12 +42,3 @@ func (s *settingsStub) UpdateSiteSetting(ctx context.Context, key string, value
|
|||||||
req := &abiv1.SettingsUpdateSiteSettingRequest{Key: key, ValueJson: valueJSON}
|
req := &abiv1.SettingsUpdateSiteSettingRequest{Key: key, ValueJson: valueJSON}
|
||||||
return s.invoke(ctx, "update_site_setting", req, &abiv1.SettingsUpdateSiteSettingResponse{})
|
return s.invoke(ctx, "update_site_setting", req, &abiv1.SettingsUpdateSiteSettingResponse{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *settingsStub) UpdatePluginSettings(ctx context.Context, pluginName string, settingsMap map[string]any) error {
|
|
||||||
settingsJSON, err := json.Marshal(settingsMap)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req := &abiv1.SettingsUpdatePluginSettingsRequest{PluginName: pluginName, SettingsJson: settingsJSON}
|
|
||||||
return s.invoke(ctx, "update_plugin_settings", req, &abiv1.SettingsUpdatePluginSettingsResponse{})
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
|
|
||||||
symposiumseeder
|
|
||||||
seed_forum"{"n":1}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
{"ok":true}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
/pricingPricing"landing
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
$44444444-4444-4444-4444-444444444444
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
$44444444-4444-4444-4444-444444444444
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
$44444444-4444-4444-4444-4444444444445
|
|
||||||
htmlHero{"align":"center"}"<h1>Hi</h1>*main0
|
|
||||||
ctaCTA{"label":"Go"}0
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
$44444444-4444-4444-4444-444444444444Pricing — AcmePlans and pricing.
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
helloHello{"type":"doc"}"hello*$22222222-2222-2222-2222-2222222222222$99999999-9999-9999-9999-9999999999998
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
$bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
$99999999-9999-9999-9999-999999999999hero.jpg"hero*seed2myplugin
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
mainDocs"/docs(
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
4
|
|
||||||
/Home"homepage*
|
|
||||||
htmlHero{"x":1}*main0H
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
{"site_name":"Acme"}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
myplugin{"enabled":true}
|
|
||||||
@ -12,7 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
abiv1 "git.dev.alexdunmow.com/block/core/abi/v1"
|
abiv1 "git.dev.alexdunmow.com/block/core/abi/v1"
|
||||||
"git.dev.alexdunmow.com/block/core/ai"
|
|
||||||
"git.dev.alexdunmow.com/block/core/blocks"
|
"git.dev.alexdunmow.com/block/core/blocks"
|
||||||
"git.dev.alexdunmow.com/block/core/plugin"
|
"git.dev.alexdunmow.com/block/core/plugin"
|
||||||
"git.dev.alexdunmow.com/block/core/plugin/wasmguest/bnwasm"
|
"git.dev.alexdunmow.com/block/core/plugin/wasmguest/bnwasm"
|
||||||
@ -103,11 +102,8 @@ func newGuest(reg plugin.PluginRegistration) *guest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runRegister invokes Register (or RegisterWithProvisioner with a no-op
|
// runRegister invokes Register (or RegisterWithProvisioner with a no-op
|
||||||
// provisioner) against capture registries, converting panics to errors.
|
// provisioner — provisioning itself runs host-side at load) against capture
|
||||||
// Register-time provisioning cannot cross the ABI (DESCRIBE stubs every host
|
// registries, converting panics to errors.
|
||||||
// function to fail), so RegisterWithProvisioner's provisioner is inert here;
|
|
||||||
// wasm plugins provision from Load via deps.Provisioner (the provisioner.*
|
|
||||||
// capability family, WO-WZ-019) instead.
|
|
||||||
func runRegister(reg plugin.PluginRegistration, tr *captureTemplateRegistry, br *captureBlockRegistry) (err error) {
|
func runRegister(reg plugin.PluginRegistration, tr *captureTemplateRegistry, br *captureBlockRegistry) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -190,14 +186,6 @@ func (g *guest) invoke(hookID uint32, req []byte) (out []byte) {
|
|||||||
payload, abiErr = g.ragFetch(ctx, env.GetPayload())
|
payload, abiErr = g.ragFetch(ctx, env.GetPayload())
|
||||||
case abiv1.Hook_HOOK_MEDIA_HOOK:
|
case abiv1.Hook_HOOK_MEDIA_HOOK:
|
||||||
payload, abiErr = g.mediaHook(ctx, env.GetPayload())
|
payload, abiErr = g.mediaHook(ctx, env.GetPayload())
|
||||||
case abiv1.Hook_HOOK_AI_TOOL_CALL:
|
|
||||||
payload, abiErr = g.aiToolCall(ctx, env.GetPayload())
|
|
||||||
case abiv1.Hook_HOOK_BRIDGE_CALL:
|
|
||||||
payload, abiErr = g.bridgeCall(ctx, env.GetPayload())
|
|
||||||
case abiv1.Hook_HOOK_DIRECTORY_PANEL_SECTION:
|
|
||||||
payload, abiErr = g.directoryPanelSection(env.GetPayload())
|
|
||||||
case abiv1.Hook_HOOK_DIRECTORY_PIN_DECORATOR:
|
|
||||||
payload, abiErr = g.directoryPinDecorator(env.GetPayload())
|
|
||||||
default:
|
default:
|
||||||
abiErr = &abiv1.AbiError{
|
abiErr = &abiv1.AbiError{
|
||||||
Code: abiv1.AbiErrorCode_ABI_ERROR_CODE_UNIMPLEMENTED,
|
Code: abiv1.AbiErrorCode_ABI_ERROR_CODE_UNIMPLEMENTED,
|
||||||
@ -402,16 +390,9 @@ func (g *guest) runJob(ctx context.Context, payload []byte) (proto.Message, *abi
|
|||||||
Message: "unknown job type " + req.GetJobType(),
|
Message: "unknown job type " + req.GetJobType(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Progress crosses as best-effort jobs.progress host calls; the host
|
// Job progress reporting needs a jobs.progress host function — an ABI
|
||||||
// correlates them to the running job via the HOOK_JOB call's context. A
|
// open item (core/docs/wasm-abi.md); v1 discards progress updates.
|
||||||
// nil transport (native builds) discards updates.
|
progress := func(current, total int, message string) {}
|
||||||
progress := func(current, total int, message string) {
|
|
||||||
if capTransport == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
preq := &abiv1.JobsProgressRequest{Current: int32(current), Total: int32(total), Message: message}
|
|
||||||
_ = capTransport("jobs.progress", preq, &abiv1.JobsProgressResponse{})
|
|
||||||
}
|
|
||||||
result, err := handler(ctx, req.GetConfigJson(), progress)
|
result, err := handler(ctx, req.GetConfigJson(), progress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, internalError(err.Error())
|
return nil, internalError(err.Error())
|
||||||
@ -552,163 +533,6 @@ func (g *guest) ragFetcher(contentType string) (plugin.ContentFetcher, bool) {
|
|||||||
return g.rag.Fetcher(contentType)
|
return g.rag.Fetcher(contentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// toolLookup / serviceLookup are the recording-stub accessors dispatch needs
|
|
||||||
// for HOOK_AI_TOOL_CALL / HOOK_BRIDGE_CALL. The caps stubs implement them;
|
|
||||||
// asserting interfaces keeps the stub types unexported.
|
|
||||||
type toolLookup interface {
|
|
||||||
Tool(slug string) (*ai.ToolDefinition, bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
type serviceLookup interface {
|
|
||||||
Service(serviceName string) (any, bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// aiToolCall executes a guest-registered AI tool handler (HOOK_AI_TOOL_CALL).
|
|
||||||
// Tools must be registered in Register (every pooled instance runs it) for the
|
|
||||||
// lookup to succeed on any instance — see wasm-abi.md §"Per-instance state".
|
|
||||||
func (g *guest) aiToolCall(ctx context.Context, payload []byte) (proto.Message, *abiv1.AbiError) {
|
|
||||||
req := &abiv1.AiToolCallRequest{}
|
|
||||||
if err := proto.Unmarshal(payload, req); err != nil {
|
|
||||||
return nil, decodeError("AiToolCallRequest", err)
|
|
||||||
}
|
|
||||||
reg, ok := g.services.ToolRegistry.(toolLookup)
|
|
||||||
if !ok {
|
|
||||||
return nil, &abiv1.AbiError{
|
|
||||||
Code: abiv1.AbiErrorCode_ABI_ERROR_CODE_UNIMPLEMENTED,
|
|
||||||
Message: "tool registry does not record handlers",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tool, ok := reg.Tool(req.GetSlug())
|
|
||||||
if !ok || tool.Handler == nil {
|
|
||||||
return nil, &abiv1.AbiError{
|
|
||||||
Code: abiv1.AbiErrorCode_ABI_ERROR_CODE_UNIMPLEMENTED,
|
|
||||||
Message: "no handler for AI tool " + req.GetSlug() + " on this instance (register tools in Register, not Load)",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var params map[string]any
|
|
||||||
if raw := req.GetParamsJson(); len(raw) > 0 {
|
|
||||||
if err := json.Unmarshal(raw, ¶ms); err != nil {
|
|
||||||
return nil, decodeError("AiToolCallRequest.params_json", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result, err := tool.Handler(ctx, params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, internalError(err.Error())
|
|
||||||
}
|
|
||||||
resp := &abiv1.AiToolCallResponse{}
|
|
||||||
if result != nil {
|
|
||||||
resp.Content = result.Content
|
|
||||||
resp.ErrorMessage = result.Error
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// bridgeCall invokes a method on one of THIS plugin's registered bridge
|
|
||||||
// services (HOOK_BRIDGE_CALL; the consumer side is the bridge.invoke
|
|
||||||
// capability). The service value must implement plugin.BridgeInvokable and be
|
|
||||||
// registered in Register so every pooled instance has it.
|
|
||||||
func (g *guest) bridgeCall(ctx context.Context, payload []byte) (proto.Message, *abiv1.AbiError) {
|
|
||||||
req := &abiv1.BridgeCallRequest{}
|
|
||||||
if err := proto.Unmarshal(payload, req); err != nil {
|
|
||||||
return nil, decodeError("BridgeCallRequest", err)
|
|
||||||
}
|
|
||||||
lookup, ok := g.services.Bridge.(serviceLookup)
|
|
||||||
if !ok {
|
|
||||||
return nil, &abiv1.AbiError{
|
|
||||||
Code: abiv1.AbiErrorCode_ABI_ERROR_CODE_UNIMPLEMENTED,
|
|
||||||
Message: "bridge does not record services",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
svc, ok := lookup.Service(req.GetServiceName())
|
|
||||||
if !ok {
|
|
||||||
return nil, &abiv1.AbiError{
|
|
||||||
Code: abiv1.AbiErrorCode_ABI_ERROR_CODE_UNIMPLEMENTED,
|
|
||||||
Message: "no bridge service " + req.GetServiceName() + " on this instance (register services in Register, not Load)",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
invokable, ok := svc.(plugin.BridgeInvokable)
|
|
||||||
if !ok {
|
|
||||||
return nil, &abiv1.AbiError{
|
|
||||||
Code: abiv1.AbiErrorCode_ABI_ERROR_CODE_UNIMPLEMENTED,
|
|
||||||
Message: "bridge service " + req.GetServiceName() + " does not implement plugin.BridgeInvokable",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out, err := invokable.InvokeBridge(ctx, req.GetMethod(), req.GetPayload())
|
|
||||||
if err != nil {
|
|
||||||
return nil, internalError(err.Error())
|
|
||||||
}
|
|
||||||
return &abiv1.BridgeCallResponse{Payload: out}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// directoryExtensions resolves the registration's DirectoryExtensions value,
|
|
||||||
// nil when the plugin declares none.
|
|
||||||
func (g *guest) directoryExtensions() *plugin.DirectoryExtensions {
|
|
||||||
if g.reg.DirectoryExtensions == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return g.reg.DirectoryExtensions()
|
|
||||||
}
|
|
||||||
|
|
||||||
// directoryPanelSection renders the index-th registered panel section
|
|
||||||
// (HOOK_DIRECTORY_PANEL_SECTION).
|
|
||||||
func (g *guest) directoryPanelSection(payload []byte) (proto.Message, *abiv1.AbiError) {
|
|
||||||
req := &abiv1.DirectoryPanelSectionRequest{}
|
|
||||||
if err := proto.Unmarshal(payload, req); err != nil {
|
|
||||||
return nil, decodeError("DirectoryPanelSectionRequest", err)
|
|
||||||
}
|
|
||||||
ext := g.directoryExtensions()
|
|
||||||
idx := int(req.GetIndex())
|
|
||||||
if ext == nil || idx >= len(ext.PanelSections) || ext.PanelSections[idx] == nil {
|
|
||||||
return nil, &abiv1.AbiError{
|
|
||||||
Code: abiv1.AbiErrorCode_ABI_ERROR_CODE_UNIMPLEMENTED,
|
|
||||||
Message: fmt.Sprintf("no directory panel section at index %d", idx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var data map[string]any
|
|
||||||
if raw := req.GetDataJson(); len(raw) > 0 {
|
|
||||||
if err := json.Unmarshal(raw, &data); err != nil {
|
|
||||||
return nil, decodeError("DirectoryPanelSectionRequest.data_json", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &abiv1.DirectoryPanelSectionResponse{Html: ext.PanelSections[idx](data)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// directoryPinDecorator runs the index-th registered pin decorator, which
|
|
||||||
// mutates the pin map in place; the mutated pin is returned
|
|
||||||
// (HOOK_DIRECTORY_PIN_DECORATOR).
|
|
||||||
func (g *guest) directoryPinDecorator(payload []byte) (proto.Message, *abiv1.AbiError) {
|
|
||||||
req := &abiv1.DirectoryPinDecoratorRequest{}
|
|
||||||
if err := proto.Unmarshal(payload, req); err != nil {
|
|
||||||
return nil, decodeError("DirectoryPinDecoratorRequest", err)
|
|
||||||
}
|
|
||||||
ext := g.directoryExtensions()
|
|
||||||
idx := int(req.GetIndex())
|
|
||||||
if ext == nil || idx >= len(ext.PinDecorators) || ext.PinDecorators[idx] == nil {
|
|
||||||
return nil, &abiv1.AbiError{
|
|
||||||
Code: abiv1.AbiErrorCode_ABI_ERROR_CODE_UNIMPLEMENTED,
|
|
||||||
Message: fmt.Sprintf("no directory pin decorator at index %d", idx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pin := map[string]any{}
|
|
||||||
if raw := req.GetPinJson(); len(raw) > 0 {
|
|
||||||
if err := json.Unmarshal(raw, &pin); err != nil {
|
|
||||||
return nil, decodeError("DirectoryPinDecoratorRequest.pin_json", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var data map[string]any
|
|
||||||
if raw := req.GetDataJson(); len(raw) > 0 {
|
|
||||||
if err := json.Unmarshal(raw, &data); err != nil {
|
|
||||||
return nil, decodeError("DirectoryPinDecoratorRequest.data_json", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ext.PinDecorators[idx](pin, data)
|
|
||||||
pinJSON, err := json.Marshal(pin)
|
|
||||||
if err != nil {
|
|
||||||
return nil, internalError("marshal mutated pin: " + err.Error())
|
|
||||||
}
|
|
||||||
return &abiv1.DirectoryPinDecoratorResponse{PinJson: pinJSON}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortedKeys returns the map's keys in sorted order (deterministic manifests).
|
// sortedKeys returns the map's keys in sorted order (deterministic manifests).
|
||||||
func sortedKeys[V any](m map[string]V) []string {
|
func sortedKeys[V any](m map[string]V) []string {
|
||||||
keys := make([]string, 0, len(m))
|
keys := make([]string, 0, len(m))
|
||||||
|
|||||||
@ -8,13 +8,9 @@ type Settings interface {
|
|||||||
GetPluginSettings(ctx context.Context, pluginName string) (map[string]any, error)
|
GetPluginSettings(ctx context.Context, pluginName string) (map[string]any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updater allows plugins to modify site settings and their own plugin
|
// Updater allows plugins to modify site settings.
|
||||||
// settings.
|
|
||||||
type Updater interface {
|
type Updater interface {
|
||||||
UpdateSiteSetting(ctx context.Context, key string, value any) error
|
UpdateSiteSetting(ctx context.Context, key string, value any) error
|
||||||
// UpdatePluginSettings replaces the plugin's own settings map. Across the
|
|
||||||
// wasm ABI the host enforces that pluginName matches the calling plugin.
|
|
||||||
UpdatePluginSettings(ctx context.Context, pluginName string, settings map[string]any) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStringOr returns a string value from a map, or defaultVal if not found/wrong type.
|
// GetStringOr returns a string value from a map, or defaultVal if not found/wrong type.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user