Skip to content

bug: presentMulmoScript のビート差し替え(beatIndex + beat)が黙って無視され、pubsub も飛ばない #1880

Description

@isamu

症状

MCPツール presentMulmoScript「ビート差し替え」filePath + beatIndex + beat)が、
何も書き換えずに成功として返る。キャンバスも当然更新されない。

エラーは出ない。レスポンスは通常の再表示と同じ Loaded MulmoScript from stories/<file>.json で、
呼んだ側からは成功と区別がつかない。

原因

ホスト側のツール呼び出しハンドラが、引数の許可リストから beatIndex / beat落としている

server/backends/mulmoscript.ts handleToolCall:

const args: SaveMulmoScriptArgs = {
  ...(body.script !== undefined ? { script: body.script } : {}),
  ...(typeof body.filename === "string" ? { filename: body.filename } : {}),
  ...(typeof body.filePath === "string" ? { filePath: body.filePath } : {}),
  ...(typeof body.autoGenerateMovie === "boolean" ? { autoGenerateMovie: body.autoGenerateMovie } : {}),
};
const outcome = await executeMulmoScriptSave({ files: { artifacts: instance.backend.artifacts } }, args);

beatIndexbeat がここに無いため、executeMulmoScriptSave に届かない。

パッケージ側は実装済みで、型にも入っている:

// @mulmoclaude/mulmoscript-plugin/dist/core/types.d.ts
export interface SaveMulmoScriptArgs {
    script?: unknown;
    filename?: string | undefined;
    filePath?: string | undefined;
    autoGenerateMovie?: boolean | undefined;
    /** With `filePath`: replace just this beat instead of re-sending the whole script. */
    beatIndex?: number | undefined;
    /** The replacement beat. Only meaningful with `filePath` + `beatIndex`. */
    beat?: unknown;
}
// plugin-CAhxJ5WM.js  executeMulmoScriptSave
if (beatIndex !== void 0 || beat !== void 0) {
    if (beatIndex === void 0 || beat === void 0) return badRequest("`beatIndex` and `beat` go together — ...");
    const updated = await executeUpdateBeat(context, { filePath, beatIndex, beat });
    if (!updated.ok) return updated;
}
return loadExistingScript(context, filePath);

両方が undefined のまま渡るので分岐に入らず、loadExistingScript に素通りする。
これが「成功したように見える」正体。

ツールの description は、この機能を明示的に案内している:

  1. Edit one beat — pass filePath PLUS beatIndex and beat. Replaces that one beat and
    presents the result. Use this whenever the user asks to change part of an existing
    presentation
    — re-sending the whole script instead wastes tokens and overwrites anything
    edited in the canvas meanwhile.

2つめの欠陥: pubsub が飛ばない

仮に上を直して書き込みが通っても、script-changed が publish されない

publish しているのは kind ルータの updateKind だけ:

// @mulmoclaude/mulmoscript-plugin/dist/server.js
async function updateKind(kind, args) {
    const outcome = kind === "updateBeat" ? await executeUpdateBeat(...) : await executeUpdateScript(...);
    if (!outcome.ok) return fromPackageFailure(outcome);
    ops.publishScriptChanged(str(args.filePath) ?? "", str(args.origin));   // ← ここだけ
    return { ok: true };
}

async function saveKind(args) {
    const outcome = await executeMulmoScriptSave(executeContext, {
        script: args.script, filename: str(args.filename), filePath: str(args.filePath)
    });                                    // ← beatIndex / beat をここでも落としている
    ...                                    // ← publishScriptChanged を呼ばない
}

saveKind にも同じ取りこぼしがあり、かつ publish もしない。
ホスト側の handleToolCallexecuteMulmoScriptSave を直接呼ぶので、こちらも publish されない。

新規保存(script を渡す)で画面が変わって見えるのは、レスポンスの
Display the storyboard to the user. を受けてキャンバスがその新しいファイルを開くためで、
pubsub による更新ではない。既存ファイルを開いたまま中身だけ差し替えるビート差し替えでは、
pubsub が唯一の更新経路なので、何も起きない。

再現

  1. presentMulmoScriptscript を渡して新規保存(キャンバスに表示される)
  2. 同じ filePathbeatIndex + beat を渡す
  3. Loaded MulmoScript from stories/<file>.json が返る
  4. ファイルの中身は変わっていない / キャンバスも変わらない

実際に踏んだケース: 13枚のデッキの3枚目(タイムライン)を差し替えようとして、
成功レスポンスが返ったがキャンバスは変わらず。フルスクリプトを再送して回避した。

直し方(案)

  1. server/backends/mulmoscript.tshandleToolCall の許可リストに beatIndex / beat を追加
    typeof body.beatIndex === "number" / body.beat !== undefined
  2. 書き込みが起きた場合に publishScriptChanged を呼ぶ。
    executeMulmoScriptSave はビート差し替えをしたかどうかを呼び出し側に返していないので、
    ホスト側で beatIndex !== undefined を条件にするか、パッケージ側の戻り値に
    「書き込んだ」フラグを持たせる
  3. パッケージ側 saveKind の取りこぼしも同様(別リポジトリなら upstream issue に分ける)

テスト

server/backends/mulmoscript.spec.ts に、ツール呼び出し経路で
beatIndex + beat を渡すとディスクの中身が変わり、script-changed が publish される、
というケースが無い。許可リストの取りこぼしはこの形でしか捕まらないので、追加が要る。

環境

  • mulmoterminal c8901e90
  • @mulmoclaude/mulmoscript-plugin(node_modules の dist を確認)
  • MCPサーバー mulmoterminal-media 経由の presentMulmoScript

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions