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:
2026-05-07 16:52:01 +08:00
parent e8fca33f80
commit 519423cb0d
127 changed files with 23909 additions and 264 deletions
@@ -0,0 +1,74 @@
canonical_id: "kbdb_create_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, content]
properties:
api_key:
type: string
description: KBDB partner keypk_live_xxx 或 ak_xxx
content:
type: string
description: block 內容
type:
type: string
description: block typenote / chat / page 等,預設 block
parent_id:
type: string
description: 父 block id(留言鏈用)
user_id:
type: string
description: 擁有者 user_id / namespace
source:
type: string
description: 來源標記
page_name:
type: string
description: 所屬 page
tags_json:
type: string
description: tags JSON 字串
kbdb_url:
type: string
description: KBDB API base(預設 https://kbdb.finally.click
output_schema:
type: object
properties:
success:
type: boolean
data:
type: object
description: KBDB 回傳(含新 block 的 id
error:
type: string
gherkin_tests:
- scenario: "缺 content"
given: '{"api_key":"pk_live_x"}'
then_contains: '{"success":false'
- scenario: "建立留言(type=chat + parent_id"
given: '{"api_key":"pk_live_x","content":"hi","type":"chat","parent_id":"abc"}'
then_contains: 'success'
tags: [data, storage, kbdb, create, primitive]
description: "建立單一 KBDB blockPOST /blocks),不切多 chunks。支援 parent_id 給留言鏈用。Mira 留言/AI 回覆使用,本零件為 P0 必備。"
config_example: |
reply:
api_key: "{{secret.kbdb_key}}"
content: "我的留言"
type: "chat"
parent_id: "{{previous_node.output.block_id}}"
user_id: "inkstone_leo"
page_name: "my-post"
@@ -0,0 +1,3 @@
module kbdb_create_block
go 1.21
@@ -0,0 +1,152 @@
// kbdb_create_block — POST 一個單一 block 到 KBDB(支援 parent_id,給留言鏈用)
// 對應 KBDB endpoint: POST /blocks(不是 ingest
//
//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"`
APIKey string `json:"api_key"`
Content string `json:"content"`
Type string `json:"type"`
ParentID string `json:"parent_id"`
UserID string `json:"user_id"`
Source string `json:"source"`
PageName string `json:"page_name"`
TagsJSON string `json:"tags_json"`
}
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.Content == "" {
writeError("content 必填")
return
}
kbdbURL := input.KBDBUrl
if kbdbURL == "" {
kbdbURL = "https://kbdb.finally.click"
}
// 構造 KBDB POST /blocks body(只放有值的欄位)
body := make(map[string]interface{})
body["content"] = input.Content
if input.Type != "" {
body["type"] = input.Type
}
if input.ParentID != "" {
body["parent_id"] = input.ParentID
}
if input.UserID != "" {
body["user_id"] = input.UserID
}
if input.Source != "" {
body["source"] = input.Source
}
if input.PageName != "" {
body["page_name"] = input.PageName
}
if input.TagsJSON != "" {
body["tags_json"] = input.TagsJSON
}
bodyBytes, _ := json.Marshal(body)
headers := map[string]string{
"Content-Type": "application/json",
"Authorization": "Bearer " + input.APIKey,
}
headersBytes, _ := json.Marshal(headers)
url := kbdbURL + "/blocks"
urlBytes := []byte(url)
methodBytes := []byte("POST")
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 POST 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)
}