> ## Documentation Index
> Fetch the complete documentation index at: https://agent.minimaxi.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 页面 Client API

> 宿主通过 preload 向 Mini App 主页面注入 window.mcode。调用之前先确认方法存在。

不需要安装 npm SDK，也不需要传会话 ID 或插件 ID。旧版宿主、普通浏览器标签和外部浏览器里都没有这个对象，所以每次都要做特性检测。可以把 Skill 提供的 `client-api.ts` 复制到项目中做类型检查，不要在运行时引入它。

完整的方法签名、参数、返回类型与能力名称见 [Client API 参考](https://agent.minimaxi.com/docs/miniapp/reference/client-api.md)；Agent 会话接口见 [Agent API 参考](https://agent.minimaxi.com/docs/miniapp/reference/agent-api.md)。

## 特性检测

```javascript theme={null}
const miniapp = window.mcode?.miniapp;

if (!miniapp?.message?.append) {
  showHint('当前宿主不支持把内容交给输入框');
}
if (miniapp?.window) {
  // 弹出窗口能力可用
}
if (miniapp?.agent) {
  // 宿主提供 Agent 接口，调用仍需对应声明和权限开关
}
```

## message.append(text)

在用户点击按钮的处理函数中调用。宿主会弹出确认框，显示插件、目标会话和文字；用户确认后，文字追加到该会话的输入框。用户可以继续编辑，亲自点击发送后才会开始一轮对话。

```javascript theme={null}
async function handToAgent(text, button, status) {
  const message = window.mcode?.miniapp?.message;
  if (!message?.append) {
    status.textContent = '当前宿主不支持把内容交给输入框。';
    return;
  }
  button.disabled = true;
  try {
    await message.append(text);
    status.textContent = '已放进输入框，确认后请自己按发送。';
  } catch (error) {
    const code = error?.message;
    if (code === 'user_cancelled') status.textContent = '已取消，没有添加任何内容。';
    else if (code === 'result_unknown') status.textContent = '先看一下输入框，内容可能已经加进去了。';
    else status.textContent = '没能添加，检查会话后再试。';
  } finally {
    button.disabled = false;
  }
}
```

* `text` 是纯文本，单次最多 100,000 个 UTF-16 码元。HTML 也按文字处理；换行需包含在文字中。
* 每次调用都要确认，不支持「记住选择」，也不会自动发送。多次调用按顺序逐个确认。
* 输入框里已有的文字、引用和附件都会保留。`message` 命名空间不授予读取聊天记录的能力，也不能改已发送的消息；读取用户会话需要另行声明 `session.read`，见 [Agent API](https://agent.minimaxi.com/docs/miniapp/authors/agent.md#agent-api)。
* 失败时 `message` 是 `user_cancelled`、`invalid_request`、`text_too_large`、`not_allowed`、`target_unavailable` 或 `result_unknown`。遇到 `result_unknown` 时，提醒用户先看一下输入框，不要自动重发。

按钮上应写清楚去向，比如「交给 Agent」。Promise 成功只代表文字进了输入框，不代表 Agent 已经开始工作。

## miniapp.window

这个句柄用于查看和调整 Mini App 自己的窗口，包括页面是在文件面板里（`inline`），还是弹出为独立窗口（`window`），以及弹出时的位置、尺寸和窗口属性。接口定义仍可能调整，使用前务必先做特性检测。

```javascript theme={null}
const handle = window.mcode?.miniapp?.window;
if (handle) {
  const state = await handle.getState(); // { mode: 'inline' } 或 { mode: 'window', window: {...} }

  // 必要状态应提前保存；切换 mode 必须在同步的点击等用户手势里调用
  popOutButton.onclick = () => handle.update({ mode: 'window', width: 900, height: 650 });

  const subscription = handle.onDidChangeState((next) => render(next.mode));
  addEventListener('pagehide', () => subscription.dispose());
}
```

* v3.1.9 及以上正式版的 `getState()` 可返回 `modeChangeReloadsPage: true`。这表示切换 `inline` / `window` 会重建页面，逻辑窗口 ID 和 Node 服务保留，但当前文档、内存状态、媒体流、订阅和待完成的 Promise 不会延续。按运行时结果判断，不能假定所有切窗都会重载。
* 必要状态应先保存，再让用户点击切换；不要在调用前等待异步保存而丢掉用户手势，也不要把必要工作放在 `await update({ mode })` 后或依赖旧文档的监听器。目标页面启动后读取 `getState()`，恢复已保存内容并重新订阅。
* v3.1.10 起，需要重建页面的切窗会先由宿主确认，包括关闭独立窗口或按 Cmd/Ctrl+W 收回。用户取消时保留原页面和请求，`update()` 成功返回当前状态，`mode` 不一定等于请求值；收到成功回复或状态事件都应检查其中的 `mode`。完整规则见[页面重建与确认](https://agent.minimaxi.com/docs/miniapp/reference/client-api.md#window-reload)。
* 切换 `mode` 或调用 `focus()` 必须来自用户手势，否则直接以 `not_permitted` 拒绝。
* `frame`、`transparent`、`minWidth`、`minHeight` 只能在 manifest 的 `surface.window` 里声明，运行时不能改。
* `alwaysOnTop: true` 和 `clickThrough: true` 只有 manifest 里声明为 `true`，运行时才能打开。
* 同一帧内的多次 `update` 会合并成一次；带 `mode` 的会立即发出，以免丢掉用户手势。
* 错误：`inline_mode`、`not_permitted`、`busy`、`result_unknown`、`invalid_request`。

### 弹出窗口的常见坑

| 设置 | 要注意 |
| - | - |
| `frame: false` | 去掉了系统标题栏和拖动区域，要用 CSS `-webkit-app-region: drag` 自己提供拖动区域 |
| `transparent: true` | 只去掉窗口自身的背景，页面的 `body` 背景也要设成透明。宿主已关闭透明窗口的系统阴影，需要阴影就用 CSS 画 |
| 窗口高度 | 窗口不随内容长高。用 `html, body { height: 100%; overflow: hidden }`，让正文区域自己滚动 |
| `clickThrough: true` | 只在需要「看得见但不拦点击」时打开，否则整个窗口，包括自己的按钮，都无法点击 |
| `visible: false` | 隐藏本身不结束页面，脚本、定时器和网络请求继续运行 |

## miniapp.agent

页面直接发起 Agent 会话，自己渲染工具审批和问卷，或者用 `open` 把会话交给宿主侧栏。需要在 manifest 里声明 `agentAccess`，见 [Agent API](https://agent.minimaxi.com/docs/miniapp/authors/agent.md#agent-api)。

## 设备与系统权限

摄像头和系统通知需要 MiniMax Code v3.1.9 或更新版本。使用这两项时，在 `engines.mcode` 中声明最低版本，见[版本要求](https://agent.minimaxi.com/docs/miniapp/reference/manifest.md#compat)。

设备访问需要在 `miniapp.json` 的 `deviceAccess` 中声明，并在插件详情的权限面板中保持对应开关开启。每次新请求都会检查对应开关；关闭麦克风后，新的录音请求和转写会被拒绝，页面已经持有的音频流仍需自行停止。定位、屏幕捕获等未开放的系统权限默认拒绝。

### 麦克风与语音转写

```json miniapp/miniapp.json theme={null}
{
  "deviceAccess": ["microphone"]
}
```

页面用 `navigator.mediaDevices.getUserMedia({ audio: true })` 录音，录完后调用 `window.mcode.miniapp.speech.transcribe`。宿主只负责转写，页面自己决定录音何时结束，并负责显示录音状态、停止音频流的所有 track。

```javascript theme={null}
// audio 必须已经是 16 kHz、单声道、s16 little-endian PCM 样本。
async function transcribeRecordedAudio(audio) {
  const speech = window.mcode?.miniapp?.speech;
  if (!speech) throw new Error('speech_unavailable');
  return speech.transcribe({
    audio,
    format: { sampleRate: 16000, channelCount: 1, sampleFormat: 's16' },
  }); // Promise<{ text }>
}
```

* `audio` 接受 `Int16Array`、`ArrayBuffer` 或其 view，至少一个样本，最多 16,000,000 字节（500 秒）。录音需先转成上述 PCM 格式；不要直接传 `MediaRecorder` 产物或 WAV、WebM、Ogg、FLAC、MP3 文件。
* 可传第二个参数 `{ onPartial: (text) => ... }` 接收宿主处理期间的阶段文本；它不是录音过程中的实时字幕。
* 同一 Mini App 同时只能有一个转写，第二个返回 `busy`。未声明或权限关闭返回 `not_permitted`；宿主未登录或语音识别关闭返回 `runtime_unavailable`。页面关闭或重载会结束在途转写。

### 摄像头

在 manifest 中声明 `deviceAccess: ["camera"]` 并开启对应权限后，页面通过标准 Web API 获取摄像头画面：

```javascript theme={null}
async function startCamera(video) {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  video.srcObject = stream;
  return () => {
    stream.getTracks().forEach((track) => track.stop());
    video.srcObject = null;
  };
}
```

页面需显示采集状态，并在用户结束拍摄或预览时调用返回的停止函数。首次使用时，操作系统可能询问是否允许宿主应用访问摄像头。同时请求音视频时，需要声明并开启 `microphone` 和 `camera` 两项；任一项关闭，整个请求都会以 `NotAllowedError` 失败。用户关闭摄像头权限后，新的请求会被拒绝，页面已经取得的视频流仍需自行停止。

### 系统通知

在 manifest 中声明 `deviceAccess: ["notifications"]` 并开启对应权限后，页面使用标准 `Notification` API 发送通知，通知以宿主应用的身份显示。页面可用 `Notification.permission` 检查当前通知权限；`requestPermission()` 不会单独弹出宿主确认框，授权仍以声明和权限开关为准。

```javascript theme={null}
const notice = new Notification('处理完成', { body: '返回 Mini App 查看结果。' });
notice.addEventListener('click', () => {
  const handle = window.mcode?.miniapp?.window;
  if (handle) void handle.focus().catch(() => {});
});
```

通知点击回调可在创建通知的页面仍存活时请求 `focus()`。独立窗口会恢复并聚焦；内嵌页会请求聚焦宿主并选中自己的标签，不应依赖此操作自动展开已折叠的文件面板。创建通知的页面关闭或重载后，旧通知不能重新打开页面，也不能打开其他页面。这里不提供页面关闭后的后台通知能力。

## 写页面的约定

* 请求用相对路径或同源 URL，业务接口由 Node 服务提供。页面不接触凭证、Connector 客户端和宿主传输。
* 用语义化 HTML：landmark、heading、table、form、`role="status"` 等。Agent 用浏览器验收时，靠这些读懂页面。
* 不要把第三方脚本放进主文档。主文档里的代码，以及能访问父页面的同源 iframe，都与页面具有相同的信任级别。
* 不加载远程资源：字体、图标和图片用本地文件或内联 SVG，不用远程 CDN。这样离线也能用，也减少第三方脚本借页面身份访问本机服务的风险。
* 没有特别的风格要求时，视觉以 Skill 里的 MiniMax Code 视觉基线为准。
