feat(arcrun): mira wiki page with tag filter + accumulated WIP
- landing/app/mira/wiki: tag=mira-wiki list now shows all wiki paragraphs (depends on KBDB tag filter exposed in matrix/kbdb commit, separate repo) - landing: app/mira hub + feed split + various WIP from prior sessions - registry/components: claude_api / kbdb_create_block / kbdb_get / km_writer / platform_crypto / auth_oauth2 contracts + main.go (accumulated) - .component-builds: pkg-lock updates + index.ts adjustments (WIP) - .agents/specs/arcrun/frontend-redesign: design notes - docs/test_credentials, docs/user_requirements/arcrun-landing-page: WIP docs - cypher-executor: auth-dispatcher / wasi-shim adjustments (WIP) Includes accumulated work from prior sessions plus the wiki UI tag-filter update that surfaces the AI-generated wiki paragraphs at /mira/wiki. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
canonical_id: "kbdb_patch_block"
|
||||
display_name: "KBDB Block 部分更新"
|
||||
category: "data"
|
||||
version: "v1"
|
||||
wasi_target: "preview1"
|
||||
stability: "floating"
|
||||
runtime_compat:
|
||||
- "cf-workers"
|
||||
- "workerd"
|
||||
- "wazero"
|
||||
constraints:
|
||||
max_size_kb: 2048
|
||||
max_cold_start_ms: 50
|
||||
no_network_syscall: false
|
||||
no_filesystem_syscall: true
|
||||
io_model: "stdin_stdout_json"
|
||||
input_schema:
|
||||
type: object
|
||||
required: [api_key, block_id]
|
||||
properties:
|
||||
api_key:
|
||||
type: string
|
||||
description: KBDB partner key(pk_live_xxx 或 ak_xxx)
|
||||
block_id:
|
||||
type: string
|
||||
description: 要更新的 block UUID
|
||||
content:
|
||||
type: string
|
||||
description: 新內容(傳入則覆寫;不傳則不動)
|
||||
tags:
|
||||
type: array
|
||||
items: { type: string }
|
||||
description: tags 陣列(完整覆寫;不傳則不動)
|
||||
refs:
|
||||
type: array
|
||||
items: { type: string }
|
||||
description: refs 陣列(完整覆寫;不傳則不動)
|
||||
source:
|
||||
type: string
|
||||
description: 來源標記(傳入則覆寫)
|
||||
metadata_json:
|
||||
type: object
|
||||
description: 任意附加資料(完整覆寫)
|
||||
kbdb_url:
|
||||
type: string
|
||||
description: KBDB API base(預設 https://kbdb.finally.click)
|
||||
default: "https://kbdb.finally.click"
|
||||
output_schema:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
data:
|
||||
type: object
|
||||
description: KBDB 回傳的更新後 block
|
||||
error:
|
||||
type: string
|
||||
gherkin_tests:
|
||||
- scenario: "缺 block_id"
|
||||
given: '{"api_key":"pk_live_x"}'
|
||||
then_contains: '{"success":false'
|
||||
- scenario: "至少要一個欄位"
|
||||
given: '{"api_key":"pk_live_x","block_id":"b_x"}'
|
||||
then_contains: '{"success":false'
|
||||
- scenario: "改 content"
|
||||
given: '{"api_key":"pk_live_x","block_id":"b_x","content":"new"}'
|
||||
then_contains: 'success'
|
||||
tags: [data, storage, kbdb, patch, edit, primitive]
|
||||
description: "PATCH 一個既有 KBDB block 的欄位(content / tags / refs / source / metadata_json)。透過 host function 呼叫 KBDB PATCH /blocks/:id。Mira 前端 inline edit 與 AI 自我修正使用,本零件為 P0 必備。"
|
||||
config_example: |
|
||||
patch_block:
|
||||
api_key: "{{secret.kbdb_key}}"
|
||||
block_id: "{{previous_node.output.block_id}}"
|
||||
content: "新內容"
|
||||
tags: ["news", "ai"]
|
||||
@@ -0,0 +1,3 @@
|
||||
module kbdb_patch_block
|
||||
|
||||
go 1.21
|
||||
@@ -0,0 +1,155 @@
|
||||
// kbdb_patch_block — PATCH 一個既有 block 的部分欄位
|
||||
// 對應 KBDB endpoint: PATCH /blocks/{id}
|
||||
// SDD: matrix/kbdb/.agents/specs/blocks-edit-api/design.md §2
|
||||
//
|
||||
//go:build tinygo
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//go:wasmimport u6u http_request
|
||||
func hostHttpRequest(
|
||||
urlPtr uintptr, urlLen uint32,
|
||||
methodPtr uintptr, methodLen uint32,
|
||||
headersPtr uintptr, headersLen uint32,
|
||||
bodyPtr uintptr, bodyLen uint32,
|
||||
outPtr uintptr, outLenPtr uintptr,
|
||||
) uint32
|
||||
|
||||
type Input struct {
|
||||
KBDBUrl string `json:"kbdb_url"` // optional
|
||||
APIKey string `json:"api_key"` // 必填
|
||||
BlockID string `json:"block_id"` // 必填
|
||||
Content *string `json:"content"` // optional(pointer 區分「未傳」vs「設空字串」)
|
||||
Tags []string `json:"tags"` // optional 完整覆寫
|
||||
Refs []string `json:"refs"` // optional 完整覆寫
|
||||
Source *string `json:"source"` // optional
|
||||
Metadata map[string]interface{} `json:"metadata_json"` // optional
|
||||
}
|
||||
|
||||
var dummy [1]byte
|
||||
|
||||
func safePtr(b []byte) (uintptr, uint32) {
|
||||
if len(b) == 0 {
|
||||
return uintptr(unsafe.Pointer(&dummy[0])), 0
|
||||
}
|
||||
return uintptr(unsafe.Pointer(&b[0])), uint32(len(b))
|
||||
}
|
||||
|
||||
func main() {
|
||||
raw, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
writeError("failed to read stdin: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var input Input
|
||||
if err := json.Unmarshal(raw, &input); err != nil {
|
||||
writeError("invalid input JSON: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if input.APIKey == "" {
|
||||
writeError("api_key 必填")
|
||||
return
|
||||
}
|
||||
if input.BlockID == "" {
|
||||
writeError("block_id 必填")
|
||||
return
|
||||
}
|
||||
|
||||
// 至少要有一個欄位(避免 KBDB 回 400)
|
||||
if input.Content == nil && input.Tags == nil && input.Refs == nil &&
|
||||
input.Source == nil && input.Metadata == nil {
|
||||
writeError("至少要傳一個更新欄位(content / tags / refs / source / metadata_json)")
|
||||
return
|
||||
}
|
||||
|
||||
kbdbURL := input.KBDBUrl
|
||||
if kbdbURL == "" {
|
||||
kbdbURL = "https://kbdb.finally.click"
|
||||
}
|
||||
|
||||
// 構造 PATCH body:只放有值的欄位(pointer 控制)
|
||||
body := make(map[string]interface{})
|
||||
if input.Content != nil {
|
||||
body["content"] = *input.Content
|
||||
}
|
||||
if input.Tags != nil {
|
||||
body["tags"] = input.Tags
|
||||
}
|
||||
if input.Refs != nil {
|
||||
body["refs"] = input.Refs
|
||||
}
|
||||
if input.Source != nil {
|
||||
body["source"] = *input.Source
|
||||
}
|
||||
if input.Metadata != nil {
|
||||
body["metadata_json"] = input.Metadata
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
headers := map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer " + input.APIKey,
|
||||
}
|
||||
headersBytes, _ := json.Marshal(headers)
|
||||
|
||||
url := kbdbURL + "/blocks/" + input.BlockID
|
||||
urlBytes := []byte(url)
|
||||
methodBytes := []byte("PATCH")
|
||||
|
||||
outBuf := make([]byte, 65536)
|
||||
var outLen uint32
|
||||
|
||||
urlPtr, urlLen := safePtr(urlBytes)
|
||||
methodPtr, methodLen := safePtr(methodBytes)
|
||||
headersPtr, headersLen := safePtr(headersBytes)
|
||||
bodyPtr, bodyLenU := safePtr(bodyBytes)
|
||||
|
||||
result := hostHttpRequest(
|
||||
urlPtr, urlLen,
|
||||
methodPtr, methodLen,
|
||||
headersPtr, headersLen,
|
||||
bodyPtr, bodyLenU,
|
||||
uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)),
|
||||
)
|
||||
|
||||
if result != 0 {
|
||||
writeError("KBDB PATCH request failed (host_http_request returned non-zero)")
|
||||
return
|
||||
}
|
||||
|
||||
respStr := string(outBuf[:outLen])
|
||||
var kbdbResp map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(respStr), &kbdbResp); err != nil {
|
||||
writeError("KBDB returned non-JSON: " + respStr)
|
||||
return
|
||||
}
|
||||
|
||||
if _, hasErr := kbdbResp["error"]; hasErr {
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"success": false,
|
||||
"error": kbdbResp["error"],
|
||||
})
|
||||
os.Stdout.Write(out)
|
||||
return
|
||||
}
|
||||
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"success": true,
|
||||
"data": kbdbResp,
|
||||
})
|
||||
os.Stdout.Write(out)
|
||||
}
|
||||
|
||||
func writeError(msg string) {
|
||||
out, _ := json.Marshal(map[string]interface{}{"success": false, "error": msg})
|
||||
os.Stdout.Write(out)
|
||||
}
|
||||
Reference in New Issue
Block a user