made-bridge 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "made-bridge",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "码得(made)独立语音编程设备的电脑侧桥接器:工作台 API、设备接口、任务调度与插件运行时。一条命令启动,配合 ESP32 固件使用,源码在 github.com/jobop/made。",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,4 +47,4 @@
47
47
  "doctor": "node src/server.mjs --doctor",
48
48
  "test": "node --test --experimental-test-isolation=none --test-concurrency=1"
49
49
  }
50
- }
50
+ }
package/src/i18n.mjs CHANGED
@@ -59,6 +59,8 @@ const messages = new Map([
59
59
  ['插件状态检查必须同步返回', 'The plugin status check must return synchronously'],
60
60
  ['任务已取消', 'Task cancelled'],
61
61
  ['默认编程助手未安装或已禁用', 'The default assistant is not installed or is disabled'],
62
+ ['这是默认编程助手,不能停用;请先在设置里把默认编程助手切换为其他助手', 'This is the default assistant and cannot be disabled; switch the default assistant to another one in settings first'],
63
+ ['这是默认编程助手,不能删除;请先在设置里把默认编程助手切换为其他助手', 'This is the default assistant and cannot be removed; switch the default assistant to another one in settings first'],
62
64
  ['配对请求格式无效', 'Invalid pairing request'],
63
65
  ['设备名称无效', 'Invalid device name'],
64
66
  ['电脑配对窗口未开启', 'Pairing is not open on the computer'],
package/src/installed.mjs CHANGED
@@ -189,6 +189,12 @@ export async function setPluginEnabled(config, id, enabled) {
189
189
  if (!PLUGIN_ID.test(id || '')) throw fail('插件不存在');
190
190
  const installed = (await listInstalled(config)).plugins;
191
191
  if (!installed.some((plugin) => plugin.id === id)) throw fail('插件不存在');
192
+ // 默认编程助手不能停用:loadConfiguredPlugins 会守卫 defaultProvider,
193
+ // 但那里的报错不含操作指引,用户只会看到一句没头没尾的「未安装或已禁用」。
194
+ // 在动作入口给出可操作的前置报错。
195
+ if (!enabled && id === config.defaultProvider) {
196
+ throw fail('这是默认编程助手,不能停用;请先在设置里把默认编程助手切换为其他助手');
197
+ }
192
198
  const previous = [...(config.disabledPlugins || [])];
193
199
  const next = new Set(previous);
194
200
  if (enabled) next.delete(id);
@@ -246,6 +252,11 @@ export async function removePlugin(config, { id = '', file = '' } = {}) {
246
252
  target = removalTarget(config, target);
247
253
  const described = await describeModule(target, config);
248
254
  const ids = described.map((plugin) => plugin.id).filter((item) => PLUGIN_ID.test(item));
255
+ // 同 setPluginEnabled:删默认助手会被 defaultProvider 守卫拦下但报错没有指引,
256
+ // 在动作入口就说清楚(2026-10-18 实锤:删除 codex 后桥接器重启都起不来)。
257
+ if (ids.includes(config.defaultProvider)) {
258
+ throw fail('这是默认编程助手,不能删除;请先在设置里把默认编程助手切换为其他助手');
259
+ }
249
260
  const previousDisabled = [...(config.disabledPlugins || [])];
250
261
  const previousPlugins = [...(config.plugins || [])];
251
262
  config.disabledPlugins = [...new Set([...previousDisabled, ...ids])];
package/src/server.mjs CHANGED
@@ -807,7 +807,7 @@ export function createApp(config, { run, transcribe, tunnel: providedTunnel, ena
807
807
  return;
808
808
  }
809
809
  if (req.method === 'GET' && url.pathname === '/device/heartbeat') {
810
- console.error(`[trace] hb upMs=${url.searchParams.get('upMs') ?? '-'} wifiReason=${url.searchParams.get('wifiReason') ?? '-'} wifiDrops=${url.searchParams.get('wifiDrops') ?? '-'} fwStage=${url.searchParams.get('fwStage') ?? '-'} fwWhy=${url.searchParams.get('fwWhy') ?? '-'} fwResult=${url.searchParams.get('fwResult') ?? '-'} fwWritten=${url.searchParams.get('fwWritten') ?? '-'} fwSize=${url.searchParams.get('fwSize') ?? '-'} fwHttp=${url.searchParams.get('fwHttp') ?? '-'} fwTasks=${url.searchParams.get('fwTasks') ?? '-'} fwTask=${url.searchParams.get('fwTask') ?? '-'} ${new Date().toISOString()}`);
810
+ console.error(`[trace] hb upMs=${url.searchParams.get('upMs') ?? '-'} wifiReason=${url.searchParams.get('wifiReason') ?? '-'} wifiDrops=${url.searchParams.get('wifiDrops') ?? '-'} wifiSelf=${url.searchParams.get('wifiSelf') ?? '-'} wifiHist=${url.searchParams.get('wifiHist') ?? '-'} heap=${url.searchParams.get('heap') ?? '-'} minHeap=${url.searchParams.get('minHeap') ?? '-'} psram=${url.searchParams.get('psram') ?? '-'} psramMin=${url.searchParams.get('psramMin') ?? '-'} fwStage=${url.searchParams.get('fwStage') ?? '-'} fwWhy=${url.searchParams.get('fwWhy') ?? '-'} fwResult=${url.searchParams.get('fwResult') ?? '-'} fwWritten=${url.searchParams.get('fwWritten') ?? '-'} fwSize=${url.searchParams.get('fwSize') ?? '-'} fwHttp=${url.searchParams.get('fwHttp') ?? '-'} fwTasks=${url.searchParams.get('fwTasks') ?? '-'} fwTask=${url.searchParams.get('fwTask') ?? '-'} ${new Date().toISOString()}`);
811
811
  if (process.env.VIBE_UI_DIAGNOSTICS === '1') {
812
812
  const fields = Object.fromEntries(['uiStage', 'uiAge', 'bootAge', 'exit', 'provider', 'displayStage', 'displayCount']
813
813
  .map((key) => [key, url.searchParams.get(key)])
@@ -930,7 +930,11 @@ export function createApp(config, { run, transcribe, tunnel: providedTunnel, ena
930
930
  projects: config.projects.filter(({ id }) => id === config.defaultProject).map(({ id, label }) => ({ id, label: devicePreview(label, 64) })),
931
931
  defaultProject: config.defaultProject,
932
932
  voice: { mode: voice.mode, available: voice.available },
933
- firmwareUrl: firmwareSummary.otaUrl,
933
+ // 只有固件仓库里存在已导入的当前包(有 sha/版本)才 advertise OTA:
934
+ // 只配了 otaUrl 却没传固件时,设备会拿到无 sha 的 url → 永远 nomark
935
+ // → 弹更新确认 → waiting_confirmation 任务常驻 → 素材 preload 被门禁
936
+ // 拦死(GIF 动作等新文件永远下载不到)。
937
+ firmwareUrl: firmwareSummary.current ? firmwareSummary.otaUrl : '',
934
938
  firmwareVersion: firmwareSummary.current?.version || '',
935
939
  firmwareSha256: firmwareSummary.current?.sha256 || '',
936
940
  firmwareSize: firmwareSummary.current?.size || 0,
@@ -946,8 +950,10 @@ export function createApp(config, { run, transcribe, tunnel: providedTunnel, ena
946
950
  return;
947
951
  }
948
952
  // 形象素材分片拉取:单次 ≤192KB,兼容 USB 帧转发 256KB 上限(docs/SPRITES.md)。
953
+ // 扩展名含 .gif:GIF 动作素材(actions[].gif)也在 files 清单里,路由
954
+ // 只放行 .png 的话设备永远拉不到,playAction 报 gif_unavailable。
949
955
  const spriteMatch = req.method === 'GET' &&
950
- url.pathname.match(/^\/device\/sprite\/([a-z0-9][a-z0-9_-]{0,39})\/([A-Za-z0-9_-]+\.png|manifest\.json)$/);
956
+ url.pathname.match(/^\/device\/sprite\/([a-z0-9][a-z0-9_-]{0,39})\/([A-Za-z0-9_-]+\.(?:png|gif)|manifest\.json)$/);
951
957
  if (spriteMatch) {
952
958
  const plugin = config.pluginsRuntime.agent(spriteMatch[1]);
953
959
  if (spriteMatch[2] === 'manifest.json') {