Scripted plugin sdk - #153
mhbalthasar wants to merge 12 commits into
Conversation
九项锁定决策全部落稿:路线 B(跨进程 sidecar,含 Lua 也不开进程内第二路径)、 manifest runtime/entry/deps 扩展(宿主侧字段,SDK 冻结 ABI 零改动)、防遗留四防线 (stdio EOF / Job Object·PDEATHSIG·kqueue / 心跳 / 启动清扫)+ epoch 防尾包、 崩溃=会话重建的自恢复、三面传输(控制面 JSON/stdio、音频面 shm 镜像 IAudioSegment、 曲线面远程句柄+shm 大载荷兜底)、runtime 四级探测+半自动下载(AppUpdateManager 同款 通道,落地即走 InstallExtensions 热装)、平台矩阵按 runtime 实际能力声明(win-x86 唯 Lua 可达)、三语言 façade 目录 scripting/*、M0-M3 里程碑与验收(format→effect→voice, instrument 不承诺)+ PyVoice 端到端附录。 连带:术语表增 5 行裁决(sidecar/运行时包/epoch/会话重建 vs 重新加载/三面), AGENTS.md 增脚本插件占位节(含第三版本轴 ProtocolVersion 勿对齐告诫、脚本样例免 build); 核实 LoadV1 流程时补记一处必改连带项——未知 type 的资源判据须认 runtime/entry (ExtensionManager.cs:246),防脚本判决拼错被当资源包静默 loaded。
设计文档 docs/scripted-plugin-sidecar-design.md §2/§8/§12-M0 的首个落地片;SDK 冻结面零改动 (新增全在宿主 internal + 协议侧)。 - manifest:条目级 runtime/entry/deps(与 class/assembly 互斥,混写=Failed),包级 protocol-version(第三版本轴 WireProtocol.Version=1,高于宿主=条目循环前置拒 Skipped,非法值 Failed)与 flavor/runtime-version(type:runtime 资源包专用)。 - LoadV1 分流:脚本条目走 ScriptedEntryGate(结构校验→deps 模式→RuntimeLocator 四级探测),M0 结局恒为如实 Skipped(sidecar 注册随 M1 替换);连带把「声明了代码」资源判据(未知 type 不吞) 扩成也认 runtime/entry——type 拼错的脚本包不再被静默登记成 loaded。 - RuntimeLocator:settings→env(TUNELAB_*)→runtime 资源包(高版本优先)→PATH(win 先 py -3);每 候选真跑核验(2s 超时、(exe,mtime) 缓存,商店跳转桩/shim 自然出局);探测轨迹含被拒原因,成功 也回填。arch 只记录不门禁(跨进程可借模拟层跑异架构,承诺由作者 platforms 声明)——此定案同步回 设计文档 §8.2。 - ScriptedEntryGate / SettingsRegistry:deps:"pip" 已声明未实现(Skipped 非 Failed);五条 scripted.* 设置键(M0 仅存储,运行时页 UI 随 M1)。 - tools/build-runtime-package:pack-runtime.ps1(下载 / 自带树 -FromDir、布局归一、.tlx+sha256)+ runtime-sources.json(3.12.7 / 20.17.0 各格,缺官方产物的格子明写绕行)+ README。 - 测试:31 项(spec 解析/区间、四级优先级、spoof 落空、包序、缓存、门矩阵、管线协议门/未知 type/互斥声明);全程 FakeProber,绝不 spawn 真解释器。与 ExtensionManager.LoadResults 静态表相关 的类圈进 ExtensionLoadResults 串行 collection 并逐条自清理(修 ExtensionList 空安装断言的跨类污染)。 - manifest-variants 三 QA 包:v1-scripted-py-format / v1-scripted-unknown-type / v1-scripted-protocol-high(拖 .tlx 即可肉眼验三条文案)。
… 端到端 设计文档 §12.0-M1 落地;SDK 冻结面零改动。实现差异与推迟项已记 §12.4。 宿主侧(TuneLab\Extensions\Scripted\): - ScriptedWire:wire JSON 规范实现(tagged PropertyValue、NaN/±Infinity 一律字符串字面量 ——net8 JsonDocument 不吃裸 token——、DoubleWireConverter、PartInfo kind 判别器、Map 保序、 Multiple 拒写=封条);MapConverter 写侧属性名 bug 修(WriteStringValue→WritePropertyName)。 - WireFraming:Content-Length 帧(512MB 上限、CRLF 行、UTF-8)。 - SidecarHost:懒 spawn(python -u / node --disable-warning / dotnet exec)、initialize 握手 (entries↔provides 核对,缺位即杀且真因先落 tcs)、epoch 防尾包、pending 表、调用超时、 读者线程 LastReaderFault 诊断、stderr 64KB 尾、退避计数+5 次熔断(ResetCircuit 复位)、 优雅 shutdown→3s→KillTree;Windows Job Object(KILL_ON_JOB_CLOSE,防线 2 宿主侧)。 - ScriptedRuntimeLedger + SidecarRegistry:spawn 记台账/退出销账、启动清扫(pid+创建时间+ 路径三重匹配防 PID 复用,防线 4)挂 LoadExtensions;ShutdownAll 挂 Destroy(先脚本后 .NET)。 - RemoteFormat:IImportFormat/IExportFormat 代理;字节流走暂存文件交接(Temp\io-<nonce>\), 工程数据走消息体;导出经宿主 MemoryStream 原子写约定不变。 - LoadV1 脚本支升级:format 条目 runtime 就位 → RegisterScriptedFormatEntry(镜像 RegisterFormatEntry 校验序:后缀声明、FindConflictingEntry 以 entry 路径为实现身份); voice/effect Ready 仍如实 Skipped(注册支随 M2/M3)。 协议另一侧: - scripting/wire/v1.json:规范真源(帧、方法表、值编码、生命周期自卫条款)。 - scripting/python/tunelab:纯 stdlib façade(framing、单线程 RPC 环、tagged↔native 属性双向 往返、裸 NaN/inf 出口前转字符串、stdin EOF 自杀、Linux PR_SET_PDEATHSIG、macOS ppid 轮询; @tunelab.format 装饰器,单 handler 自动绑定 manifest entry)。 - tests/ScriptedFakeSidecar:.NET 假 sidecar(多模式:normal/no-capability/no-provides/hang/ crash-on-import/echo-log),CI 免装解释器的协议封条执行体;进 .sln 与测试工程引用(只管构建序)。 测试(46 项 C# + 7 项 Python 全绿;全量 646/682——36 红为基线既有的 Setup 环境问题零回归): - 生命周期:握手/epoch=1、无 capabilities 判死、provides 缺位判死(真因优先于泛化消息)、 hang→timeout、crash→respawn epoch≥2、Shutdown 幂等+Disposed 后拒启。 - RemoteFormat:导入合成工程→ProjectInfo(含 tagged property 解回原生)、导出文件读回输出流。 - codec:全形态往返、线上文对照规范(tagged/枚举 camelCase/判别器/键名策略)、Map 保序、 NaN/Infinity 字符串字面量、Multiple 拒写、裸值宽容读。 - 真 Python E2E(tests/plugins/PyFormat,.pyt 文本格式):spawn 3.11→握手→导入→导出往返 (640ms 实测,计时下限断言钉住 vacuous-pass 风险;缺解释器默认放行,TUNELAB_REQUIRE_PYTHON=1 变红)。 样例与工具: - tests/plugins/PyFormat(源即发行物);pack-tlx.ps1 收编 tests/plugins 下无 csproj 目录。 - AGENTS.md 脚本节填实(构建/测试命令、ProtocolVersion 三轴告诫、样例免 build 规则)。
设计 §12.4 的 M2a 切片:把 M1 记为"推迟"的心跳/stderr 滚动/IExtensionSettings 代理补齐, 并为 effect/voice(M2b/M3)立好 config 线编解码地基。SDK 冻结面仍零改动。 - ScriptedConfigWire:sidecar 声明的控件配置按【静态工厂 + With 链】重建(D 类封闭族反射写不进); slider/checkbox/textbox(combo(number/path/automation(分段=defaultValue "NaN")/object 全型; 自定义 scale/format lambda 不可跨线 → NotSupportedException 人话拒(声明数据与进程内代码分界, 规范入 scripting/wire/v1.json "config" 节)。ObjectConfig.Properties 键序/displayText 保真。 - settings 远程代理:manifest 条目级 settings:true(宿主 DTO)→ RegisterScriptedFormatEntry 传 declaresSettings;RemoteFormat 显式实现 IExtensionSettings(GetSettingsConfig→settings.get_config + config wire;ApplySettings→host.SetSettings)。懒 spawn 时序接合:未运行存值、随 initialize 的 entries[].settings 补发(ApplyPersisted 必早于任何调用的既有次序被天然接住),运行中即时通知。 - 心跳(防线 3,§3.3):15s ping/5s 超时/4 连失联 → KillTree → 死亡流程与熔断计数; SidecarHost(pingTimeout) 注入测试加速;OnDeath 停表、握手成功起表。 - stderr 包内 logs/stderr-<boot>.txt 追加滚动(§3.4),与 64KB 尾部并存;只读目录尽力而为。 - Python façade 随改:ping 应答;请求逐条工作线程(长导入不饿死心跳,_send 锁保帧完整); format() 增 get_settings_config/apply_settings 注册位(握手补发经 decode_map 解 tagged); sanitize_numbers 把配置声明里任意位置的裸 NaN/±Infinity 转字符串字面量(协议:NaN 一律字符串)。 - 假 sidecar:ping/settings.get_config(返回与 codec 封条共用的 WidgetConfig fixture,双端同形互验) /settings.apply(通知与请求两形)/mute-ping 模式/handshake 值记录;provides 增 settings 字段。 - 测试:+10 C#(config codec 6、心跳杀半死、设置握手补发+运行中通知、设置面解码、未声明零 spawn) +2 Python;全量 656/692——36 红仍为基线既有 Setup 环境问题,零新增回归。 文档:设计 §5.2 方法表(ping/settings 两行 + initialize.entries.settings)、§5.7 重写为落地语义、 §2.1 settings 字段行、§12.4 进度注记(心跳/滚动不再"推迟";effect/Node/Lua 划入 M2b)。
…e/Session、Python @tunelab.effect 设计 §12.0-M2(拆为 M2b)落地:effect 与 voice 共用的地基(反向调用 + 数据线程 marshal) 第一次立起来。SDK 冻结面仍零改动。 宿主侧: - SidecarHost 反向通道:RegisterLocalCall/Notify —— sidecar→宿主请求在【池线程】完成, 读者线程零阻塞(否则 sidecar 等在途 process 应答 ↔ 宿主等反向应答即锁环);应答经 SendResult/SendError(epoch 竞态丢弃旧世代回包)。initialize.entries 泛化 (format→entry+suffixes;effect→engine);ValidateHandshake 同步认 engine: 兑现。 - RemoteEffectEngine : IEffectSynthesisEngine(+IExtensionSettings 探测式) —— per-host 引擎表扇出反向调用;声明面走 TryCallRunning(进程没起 = 后台拉起 + 立即回缓存/空, 数据线程零无界等待,§3.5);Init 无超时(模型可加载数分钟,死亡通道兜底)。 - RemoteEffectSession : IEffectSynthesisSession —— Process 无限超时(应答即完成); 取消经 effect.cancel 通知尽力;input_read/properties/evaluate/segment_* 反向调用 marshal 回会话捕获的数据线程(30s 上限);effect.status/parameters 发布 = volatile 换引用 + ActionEvent(管线自 marshal);Dispose 收割段句柄。 - WireAudio:f32/f64 base64-LE(inline 平面;shm 面 M3)。 协议另一侧: - scripting/wire/v1.json 补全 effect 家族 + host.* 反向表(规范=实现,fake/宿主/python 三方对拍同一份)。 - Python façade:@tunelab.effect(engine.init/create_session/声明三族/process 电平+cancel/ dispose 生命周期);反向 _call_host(读者线程结算 pending 帧,120s 自卫超时); _effect.py 的 ctx:input_read/properties/automation_tracks/evaluate/output(write/commit/ resize/dispose)/status/parameters——作者眼里没有协议,只有数组与回调。 - 假 sidecar:effect-gain 靶形(完整反向链 + FakeEval 曲线求值支);应答帧先于通知判定; unknown-method 只对带 id 的请求回错(消灭 id:null 非法应答);settings 兼容 engine 键。 测试:C# +3(effect 双向垂直切片×2 + Python effect E2E 真 3.11 实测 ~0.5s 全链), Python +1(反向 ctx 线格式单测);Scripted 59/59;全量 659/695——36 红仍为基线既有 Setup 环境问题零新增;effect 用例超时对齐 60s(首轮全量并行负载下的 30s 抖动源, 诊断脚手架移除)。样例 tests/plugins/PyEffect(源即发行物,pack-tlx 自动收编)。 教训入档:PowerShell -replace/Set-Content 改含中文源文件必坏编码(本轮第三次), 一律用编辑工具;fake/宿主共享 fixture 的形制差异(segments 嵌套)由规范文件收敛。
用户需求:宿主确定解释器后"自动安装"——镜像表内置、必要时走国内 mirror 提速, Python 与 NodeJS 环境的自动部署过程一体。设计 §8.6/新增 §8.6.1。 - RuntimeFeed(镜像表=数据):python embeddable(3.11.9/3.12.7/3.13.3×amd64、3.13.3×arm64) 与 node dist(20.17.0×x64+arm64、18.20.4)起步格;候选 URL 华为云/npmmirror 在前、 python.org/nodejs.org 兜底。**sha256 已钉两格**——华为云 vs python.org、npmmirror vs nodejs.org 双下比对全等才入表(本机实测:3.13.3 embed amd64 与 node 20.17.0 win-x64); 未钉格照下但记警告,release 工具补钉。Match 的 struct-Nullable 陷阱修复(无格子=真 null)。 - ScriptedRuntimeFetcher:HEAD 并发竞速(3s)择最快可达镜像 → 流式下载(Content-Type 挡 "网页当安装包")→ sha256(有钉必验,不符即弃)→ 解包成 type:"runtime" 资源包布局 (root/prefix 归一,§8.4 同款)→ 主线程登记(Dispatcher 投递,结果确定)。 downloader/extensionsRoot/register/probe/覆写基址全注入——单测喂预制 zip 跑全链不碰网。 - 三档触发:全自动(ScriptedAutoFetchRuntime 开 → gate "no matching runtime" 即 RuntimeInstallQueue 后台一发,按包在途去重、成功自动 Reload+刷新);半自动(详情窗新 「下载运行时」按钮,15 份翻译已补;在途转圈防重按);命令/agent (extension fetch-runtime → fetch_scripted_runtime,ExtensionInstall 授权档, 工具清单封条同步认领)。 - 真链路自证(临时探针,已清理):华为云 4.3s 装好 python 3.13.3 embed,校验通过、 落地 exe 自报 "3.13.3 AMD64",RuntimeLocator ③ 级即命中。 - 稳定性:真 spawn 测试类圈进 SidecarSpawns 串行域(并行把握手超时挤爆 = 上轮全量唯一 抖源,连跑两轮全量一致 671 通过/36 基线红);engine.create_session 与 effect.process 的到达顺序竞态修(会话登记 Task、Process 首步 await——façade 会话表在 create 才建)。 - 探针发现并绕开的环境事实:本机 testhost 为 win-x86(32 位 dotnet 根在 PATH),feed 无 x86 格 → Match 如实 null("没有假安装"),命令与 UI 的拒绝文案覆盖该形态。 测试:+12(fetcher 布局/校验/竞速/覆写 9、queue 去重回执 1、命令拒绝面 2); Scripted 71/71;全量 671/707(36 红=基线 Setup 环境);Legacy 13/13。 文档:§8.6 重写 + §8.6.1 新增(镜像表/钉/判路/实测);§12.4 M3a 条目。
§3.2 状态机的"Restarting(backoff)"从此是真的后台环;§3.4 的滚动落盘补上保留策略; 脚本体系获得持续回归的跨平台门禁。voice + shm 划为专轮(见文末理由)。 - SidecarHost.AutoRestart(默认关):常驻引擎声明后,非熔断死亡 → 按退避(2/4/8/16/30s) 后台 Timer 拉起 EnsureRunning——握手成功即 Running,被 push 调度的 effect/voice 崩溃后 自动 warm 回,不再"等下一次调用才复活";成功起后撤环、优雅关停优先于在途环、ResetCircuit 语义不变。effect 注册支已置 true(voice 随 M3c)。测试:crash-on-import 靶形制造一次死亡, 轮询断言 host 自行回 Running(无第二次调用驱动)。 - stderr 保留(§3.4 "滚动 2 份"兑现):每 boot 首条 stderr 前清一次——只留最近 2 个旧 stderr-<boot>.txt;崩溃风暴不反复枚举(一次性 flag + 全 try/catch)。 - .github/workflows/tests.yml:windows/ubuntu/macos 三平台矩阵——setup-dotnet 8 + setup-python 3.12 + setup-node 20;跑①主测试工程(排除 Setup 三类:既有环境问题,TuneLab.Setup 带 RID 输出拷不进测试 bin,注释写明单列跟进)、②needs-interpreter 真解释器 E2E(置 TUNELAB_REQUIRE_PYTHON=1 让"缺解释器"变红而非 vacuous——CI 装了就必须真跑)、③façade 单测 (python -m unittest)、④legacy-compat 工程。至此 sidecar 协议/生命周期/自动部署/编解码 在三平台都有持续门禁,兑现"支持平台与 .NET 宿主一致"的承诺。 - 测试:670/670(排除 Setup 三类的环境红)+ legacy 13/13;Scripted 全组含新重启环用例。 voice + shm 未挤进本轮的理由(设计文档 §12.4 同步):会话面 = get_snapshot 反向拉 + note/automation/committed 事件推送流 + peek 缓存 + 四路产物发布,协议面比 effect 宽一个量级, 值得专轮设计而不是压进收尾轮;反向通道地基(M2b)与串行 spawn 测试域(M3a)已为它铺好。
…sion、Python @voice、真解释器合成 E2E 设计 §5(M3 主角)落地:最难最重的 voice 家族经已验证的拉取总线跑通全表面。SDK 冻结面零改动。 - RemotePullBus:常驻会话(effect/voice)共享的反向拉取 + 段登记 + 产物发布总线—— effect 家族原样迁入(M2b 的封条测试全绿作证 wire 语义零变化),voice 复用同一族 host.input_read/properties/evaluate/snapshot/notes/segment_* 处理;会话键带命名空间前缀 (effect:/voice:),create_session/dispose 按前缀分流;段随会话清算(DisposeOwnedSegments 按 sessionKey 前缀收割未归还的段句柄)。 - RemoteVoiceEngine:catalog/layout 在 Init 后拉取、volatile 缓存("VoiceSourceInfos 必须 立即返回"契约);engine.catalog_changed 通知后台重拉;声明五件套(automation/回显轨/ part/note/phoneme 面板)走有界 TryCallRunning(250ms)+指纹缓存——数据线程零无界等待(§3.5)。 - RemoteVoiceSession:context 订阅(WhenAny 全字段 + MembershipModified + PartProperties + 三通道 RangeModified + Committed)→ voice.dirty 粗粒度推送流("廉价标脏"的 remote 映射, sidecar 动作时重拉全量真相);IsContinuation/peek 有界+缓存(note 视图随请求带过去=sidecar 无状态;晚一拍安全性由 synthesize 电平幂等兜底);synthesize 无超时应答即完成、取消尽力; pitch/phonemes/parameters/status 四路发布换引用 + changed 事件;Dispose 全退订。 - 快照跨线形:notes 值全量 + request_id 回指(音素归属键的闭环)+ 求值器=句柄 (pitch/pitch_deviation/<track> 经 host.evaluate 拉值——§5.6 (a) 决定完整落地,宿主插值 零漂移,Python 侧 snap.pitch.evaluate(times) 透明远程)。 - Python façade @voice:catalog/五声明/create_session(ctx)/session{peek,synthesize, is_continuation,default_lyric,on_dirty?,cancel?,dispose?};VoiceCtx 提供 notes()/snapshot(ids)(Evaluator 句柄)/part_properties/output/status/pitch/phonemes/parameters; 作者眼里没有协议,只有列表、回调与句柄。 - 样例 tests/plugins/PyVoice(440Hz 正弦游标推进)+ 真 3.11 E2E:两段合成、dirty 事件驱动 peek 游标、音素按 request_id 归属、振幅逐点断言。 - scripting/wire/v1.json 补齐 voice 方法表(46 方法)+ 新 ScriptedWireSpecTests 规范封条 (可解析/协议版本一致/方法覆盖)——当轮即抓出 spec 自身两处非法 JSON(裸 or、未引号 数组),兑现"规范真源=机器可读"。 - LoadV1:Ready && voice → RegisterScriptedVoiceEntry(AutoRestart、engine id 必填、 ExpectEngine 兑现);instrument 维持不承诺。 测试:C# +5(voice E2E×3 = fake 靶形全表面 + 真解释器两段合成 + 默认/续延、spec 封条×3 计入) → Scripted 73/73;全量 676/676(排除基线 Setup 环境红);Python 单测 OK;legacy 13/13。
- scripting/node/tunelab/: zero-dep CommonJS facade (format/effect/voice, tagged property codec, special-float stringify at the JSON boundary to survive JSON.stringify's null-mangling of bare NaN), node --test codec suite - scripting/lua/tunelab.lua: single-file pure-Lua facade (5.1-5.4; embedded JSON codec, base64 + f32/f64 packing with string.pack where available and a manual fallback for 5.1/LuaJIT), codec unit tests - Lua facade verified locally via lupa (embedded Lua 5.5) - caught 4 real bugs (^ anchored match ignoring pos, reserved-word .end field access, double separator skip in the json array/object loops, a broken single-line IIFE) - samples tests/plugins/NodeFormat + LuaFormat (.npx/.lpx one-note-per-line) - ScriptedNodeE2ETests / ScriptedLuaE2ETests (needs-interpreter trait, vacuous without interpreter, TUNELAB_REQUIRE_NODE/LUA to assert) - ScriptedFacadeHandshakeTests: real-interpreter initialize smoke shared by all three languages (node verified locally; lua runs on posix CI) - wire spec: add voice.cancel (host->sidecar notify the host already sends but v1.json never declared); seal it in ScriptedWireSpecTests. Facade parity: python gains voice.cancel dispatch, node/lua gain voice.dirty -> user on_dirty hooks; lua/effect user.cancel now invoked with self - CI: install lua on posix runners, run node --test + lua unit tests, TUNELAB_REQUIRE_NODE=1, lua E2E forced on posix only - docs: design §12.4 M3d notes, scripting/README facade table + commands, AGENTS.md node/lua test commands Full suite 680/680 (excl. pre-existing Setup* env baseline), legacy 13/13.
M4a - stderr policy closed out:
- per-line truncate at 8KB (binary-dump lines no longer land in full), hard cap
2MB per boot file (stop writing to disk, memory tail keeps rolling for the
sidebar summary), old boots still pruned to 2
- three direct policy tests on AppendStderr (line cut, boot cap, prune window)
M4b - shared-memory audio plane (design §6/§7), fast lane, correctness-neutral:
- ShmPlane/ShmRegion: file-backed MemoryMappedFile regions named
tlx-<boot-nonce>-<slug>-<seq> under %TEMP%/tunelab-regions; 24B header
('TLXA' LE bytes on disk, version, flags, sample_offset i64, count, rate)
+ interleaved f32-LE body; stale non-current-boot leftovers swept once per boot
- negotiation: facade self-reports capabilities.audio_plane at initialize;
host only ships a region{path,data_offset,...} with segment_create on shm;
commit is the sole barrier (host copies region -> segment buffer there);
segment_write on a shm segment proxies into the region (authoritative copy);
segment_resize flushes region->segment, resizes, lays a fresh region and
returns it for re-mapping (identity preserved, alignment stays in AudioSegment)
- env injection: TUNELAB_NONCE (naming domain) + TUNELAB_SHM_DIR
- Python facade declares shm: stdlib mmap + memoryview writes (bytes/list/
memoryview duck-typed), shm_view() hands out a numpy float32 view when numpy
exists; resize re-maps, dispose unmaps; Node/Lua stay inline (no stdlib mmap)
- fake sidecar gains shm-sine mode (a second independent implementation sealing
the region reply contract; opens the region with FileShare.ReadWrite - the
CreateFromFile(path) overload defaults FileShare.Read and collides with the
host's live RW handle)
- spec: environment gains TUNELAB_NONCE/TUNELAB_SHM_DIR; segment_create/
segment_resize results document the optional region; write/commit notes pin
the barrier semantics
- tests: ShmRegion header layout on disk, cross-handle sharing, grow-keeps-
content, boot sweep, slug safety; shm-sine E2E through RemoteVoiceEngine;
real-3.11 voice+effect E2E now assert AudioPlaneShm (fast lane actually taken)
M4c - windows Lua feed cell deliberately NOT added: no portable (zip) win64
lua exists at a mirror we can sha256-pin, and NSIS installers violate the
no-execution discipline (§9); deferred to M5 backlog (Windows Lua users go
via PATH/vendor, as documented).
Full suite 689/689 (excl. pre-existing Setup* env baseline), legacy 13/13,
python unittest 16/16.
…ailure surfaced
M5a - curve result ring (§5.6/§6), completion of the shm transport trio:
- host.evaluate now answers in one of two shapes: inline {values: base64 f64-LE}
for small batches, or {region: {path, data_offset, count}} pointing at a
per-session curve ring when on the shm plane and the payload is >=64KB
- ShmRegion generalized: flags (audio-f32 | curve-f64), element size 4B/8B,
WriteF64/ReadF64; ShmPlane.AllocateCurve names tlx-<nonce>-<slug>-ev-<seq>;
rings grow-only via SetGeometry and are keyed per session (the session
serializes its own RPCs, so the ring is reused without a lock — the
JSON-RPC response is the memory barrier)
- rings freed on session teardown; boot sweep backstops crashes
- python facade: shared eval_results() decodes both shapes (effect + voice);
Node/Lua stay inline and never receive region (forward-compatible as designed)
- seal: fake shm-sine adds a 20000-point evaluate asserting the reply MUST be
a region with correct values (first/last == 0.25), reading the live host
handle with FileShare.ReadWrite; python CurveRingDecodeTests cover both shapes
M5b - Lua windows distribution: closed as a documented decision (§8.5/§8.6.1):
platform matrix green ≠ feed cell — there is no portable (zip) prebuilt win64
lua at a mirror we can dual-source sha256-pin, and the NSIS installers that do
exist are barred by §9's no-execution discipline. Windows Lua resolves via
PATH/settings/-FromDir self-packed packages/vendoring; posix CI keeps real
lua5.4 coverage. A future trustworthy portable artifact can add a feed cell
with zero protocol change.
M5c - fetch diagnostics reach the user:
- RuntimeInstallQueue.Schedule executor/onDone now carry (ok, message) through
the whole chain (the Result.Message was previously dropped to a bool)
- manual [Fetch Runtime]: on failure the reason is parked on the button
tooltip and the button stays clickable (retry); auto-fetch failures stay
silent (log only) as before
- queue test pinned for the tuple shapes + message passthrough assertion
Wire spec: host.evaluate documents the dual result shape + response-as-barrier
semantics; environment unchanged (no new methods).
Full suite 689/689 (excl. pre-existing Setup* env baseline), Scripted 86x3
consecutive clean, python unittest 18/18. One pre-existing intermittent
full-suite flake observed (9m run, unreproduced in 4 subsequent green runs;
matches the long-standing SidecarSpawns-domain note).
… format/effect/voice plugin authors
|
Thanks for this PR. Let me start with the conclusion: we are not merging the host-side changes, but this is not a rejection of the approach — we want to open an official extension point for it, and we would like you to own the resulting ecosystem. Below is the reasoning and the design direction. We would like your view before we open an issue to pin down the details. What we readWe went through the design document, the wire specification, the ~4,000 lines on the host side, and all three façades. Three-plane transport, epoch guarding against tail packets, the four lines of defense against orphaned processes, the shared-memory audio plane and the curve result ring, a fake sidecar acting as a protocol seal so CI needs no interpreter, 47 methods cross-checked across three languages — this is substantial work, not a prototype. The hazards you hit (the Windows FileShare mismatch, the create_session / process arrival race, the Lua anchor and reserved-word issues) are honestly recorded in the document as well. Why it does not go into the hostThe reason is boundaries, not implementation quality. TuneLab wants to maintain exactly two things long-term: the SDK contract (what a plugin implements) and the loading contract (who instantiates a plugin). A scripting host is built on top of those. Merging it would commit us to mirroring every SDK change across three façades, tracking upstream releases in a runtime mirror table, chasing long-tail cross-platform issues in subprocess and shared-memory code, and owning the security and compliance surface of distributing third-party binaries. Those costs belong to whoever owns that ecosystem. We also seriously considered the other shape — a normal plugin that hosts scripts internally. We rejected it: your scripted plugins would disappear from the extension management surface (sidebar, enable/disable, uninstall, settings buckets, routing, agent visibility). That price is too high for both plugin authors and users. Hence the proposal below. Proposal: an official loader extension pointThis shape already runs in the repository — the legacy compatibility layer is its first user. The main program has zero compile-time dependency on it, loads it reflectively at startup, and hands it the registration delegates. Crucially, the packages it handles remain first-class extension packages discovered by the host itself: they still pass the enable/disable and platform gates, still get bookkeeping, still appear in the sidebar, still participate in routing. The only thing the host delegates is "turn this entry into a live implementation." We intend to generalize that hardcoded hook into a real capability:
The most important consequence for you: scripted plugins stay first-class extension packages. Own manifest, own id, own introduction, own settings bucket, same routing and conflict resolution — indistinguishable from a .NET plugin in the sidebar and to the agent. Nothing on the management surface is lost. Most of your code moves as-isWe checked the coupling specifically. Across In other words the protocol layer, the process layer, the data planes and the façades can all live inside a loader unchanged. What needs rewriting is the loading and registration layer — a few hundred lines. The split point you chose inside the load pipeline (known type, after all gates, before the ALC path) is also the right one; that is exactly where a loader should take over. As a side effect, the five runtime settings (interpreter paths, mirror source, auto-fetch toggle) become the loader's own settings panel and disappear from the host settings registry. That resolves both their current lack of a settings-window UI and the problem of the mirror table being compiled into the host, where every upstream release would otherwise require a host release. SDK gaps we already know aboutPlease open an issue per item; we will review them under an additive-only policy:
Interfaces are what the official side should provide; implementations are not. Issues found during reviewThese survive the move, so they are worth passing on:
What we would like to hear from you
To be explicit about limits: we will point to your repository from the plugin development documentation, but we will not endorse specific mirror sources and will not take on support for scripting runtimes. The loader contract will be marked experimental at first; we will commit to stability once a real loader has survived an SDK minor release without contract changes, or once a second independent implementation exists. If the direction looks right, we will open an issue to settle the loader extension point in detail, and you would be welcome to shape the design there. (Happy to switch back to Chinese for any of this if that is easier — just say the word.) |
支持使用py/JS/lua作为extension的语言