Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
b94e51e
Clarify top navigation home link behavior (明确顶栏主页导航行为)
mobius-system Aug 9, 2026
7c3e36c
项目任务列表改为自适应卡片网格, 消除单列撑满导致的留白 (project task list to responsive card gr…
mobius-system Aug 9, 2026
3f493cc
会话概览页右侧改为任务统计面板, 消除与左侧会话列表的卡片冗余; guided-demo/logo-review 的 tour 锚点迁移到…
mobius-system Aug 9, 2026
62d0dad
项目卡片改为固定等高尺寸, 省略冗余描述与会话尾行 (project cards to fixed uniform height, omi…
mobius-system Aug 9, 2026
3be2dc9
项目卡片改为固定3列布局并增高卡片 (project cards to fixed 3-column layout with taller…
mobius-system Aug 9, 2026
100a27f
Inject guling broker MCP into assistant session (给小莫助理注入 guling 实盘 MCP)
mobius-system Aug 9, 2026
6b3bd26
Preserve transcript colors for clipped rows (保留截断消息颜色)
mobius-system Aug 9, 2026
8a2d1e5
Update News section with weekly highlights (2026-07-16 ~ 2026-08-09):…
mobius-system Aug 9, 2026
e33004d
Support hub and local files in mention drawer (支持@抽屉浏览中枢和本机文件)
mobius-system Aug 9, 2026
c3da1c9
Improve desktop AIMUX MCP and path integration (优化桌面端 AIMUX MCP 与路径集成)
mobius-system Aug 9, 2026
7ec034f
Add agent mention bridge
mobius-system Aug 9, 2026
1352be8
Document agent mention modes
mobius-system Aug 9, 2026
7130031
Interleave active research and issues in project card overview (项目卡概览…
mobius-system Aug 10, 2026
ea88301
顶栏"回到主页"按钮改为在新标签页打开 (Open top-nav back-to-home button in a new browse…
mobius-system Aug 10, 2026
46eb775
Fallback empty research description to title (空描述时回退为研究标题)
mobius-system Aug 10, 2026
1b48f4f
Trim News section to user-facing highlights (精简 News: 只保留用户可感知的核心特性)
mobius-system Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .imac/issue_knowledge/dca1dadf/issue_knowledge.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

- `mobius/frontend/src/components/chat.tsx` 的“项目知识沉淀到记忆”按钮与“查看当前知识”并排,位于其右侧,不再收纳在会话顶栏“更多操作”菜单中。
- 按钮发送的提示词要求先读取并合并已有知识:项目通用、跨任务可复用内容写入 `.imac/project_knowledge.md`,仅当前任务相关内容写入本 Issue 的 `issue_knowledge.md`;两者都要精简,避免重复、一次性过程、个人信息和凭据。
- `@` 智能体支持两种模式:`只读` 只把被 @ 智能体的上下文注入当前会话,不触发对端感知;`双向` 会给双方注入 curl 桥接提示,并通过 `/api/agent-bridge/messages` 做后续互发。
- `@` 的文件与智能体入口共用同一个抽屉,按标签页切换;输入框里直接输入 `@` 会打开该抽屉并复用同一条插入链路。

## 广告爽游实验室角色图集与打击特效(2026-08-07,v0.13.0)

Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,19 @@ One system to connect your team, AI agents, devices, and compute

## News

**2026-07-16**
- **Web Terminal now supports two launch modes**: open a shell in the current project directory, or open a terminal that automatically attaches to the current session's Agent tmux backend for live TUI inspection.
**2026-08-09**
- **Windows one-key install**: a single PowerShell command installs the Mobius TUI on a fresh Windows machine.

**2026-08-02**
- **Easy Mode**: an optional clutter-free layout (cross-project recent sessions + JSONL + floating input). First-time users choose between Easy and Normal mode and can switch anytime.
- **TUI released**: connect to Mobius from any terminal with the TUI, and use Mobius the way you use Codex.

**2026-07-26**
- **Search improvements**: results stream in via SSE with case/whole-word matching; clicking a result jumps to the exact JSONL card.


**2026-07-14**
- **Code Conversation v2** workspace mode: a 3-pane layout (file browser + built-in CodeMirror editor with syntax highlighting and in-place file saving + chat) for browsing, editing, and discussing project files in one place.
- **Scene-level first-visit tours** added for the Admin Center, Research page, and self-cognition workbench.
- **Code Conversation v2** workspace mode: a 3-pane layout (file browser + built-in CodeMirror editor with syntax highlighting and in-place saving + chat).



Expand Down
15 changes: 11 additions & 4 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,19 @@

## 最新动态

**2026-07-16**
- **网页终端新增两种打开方式**:可在当前项目目录打开普通 shell,也可打开终端后自动 attach 到当前会话的 Agent tmux 后台,直接查看 Agent TUI 运行状态。
**2026-08-09**
- **Windows 一键安装**:单条 PowerShell 命令即可在全新 Windows 机器上安装Mobius TUI。

**2026-08-02**
- **简易模式上线**:可选的无干扰布局(跨项目近期会话 + JSONL + 悬浮输入框)。首次进入需在简易/常规模式间二选一,随时可从主题菜单切换。
- **TUI发布**:使用TUI在任意终端连接到Mobius,像使用Codex一样使用Mobius。

**2026-07-26**
- **搜索优化**:结果经 SSE 流式返回、支持大小写/全字匹配,点击结果直接跳转到对应 JSONL 卡片。


**2026-07-14**
- **代码对话 v2**工作空间模式:三栏布局(文件浏览器 + 内置 CodeMirror 编辑器,支持语法高亮与就地保存 + 对话),可在一个界面浏览、编辑、讨论项目文件。
- 为管理中心、研究课题页、自我认知工作台新增**场景级首触引导**。
- **代码对话 v2**工作空间模式:三栏布局(文件浏览器 + 内置 CodeMirror 编辑器,支持语法高亮与就地保存 + 对话。



Expand Down
67 changes: 49 additions & 18 deletions mobius/backend/agents/tmux-claude-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ function isSameQueuedRequest(sigA, sigB) {
return false
}

// Resolve the aimux binary to spawn as a stdio MCP server (for TUI sessions that
// Resolve the aimux binary to spawn as a stdio MCP server (for desktop/TUI sessions that
// opted into add_remote_aimux_mcp). Mirrors tmux-codex.js + aimux-remote.ts
// AIMUX_BIN_CANDIDATES (kept inline to avoid crossing the .js/.ts boundary).
function resolveAimuxBin() {
Expand All @@ -456,6 +456,19 @@ function resolveAimuxBin() {
return 'aimux'
}

// Resolve the guling 实盘 MCP (HTTP / streamable-http) server config from env, so
// the 小莫 assistant session can directly read 资金/持仓 (mcp__guling__position /
// balance 等) without going through Hermes. The bearer token is a credential and
// MUST live in .env (MOBIUS_GULING_MCP_URL / MOBIUS_GULING_MCP_TOKEN) — never in
// source. Returns a { type:'http', url, headers } entry ready to drop into the
// per-session --mcp-config mcpServers, or null when unset (→ injection is a no-op).
function resolveGulingMcp() {
const url = (process.env.MOBIUS_GULING_MCP_URL || '').trim()
const token = (process.env.MOBIUS_GULING_MCP_TOKEN || '').trim()
if (!url || !token) return null
return { type: 'http', url, headers: { Authorization: `Bearer ${token}` } }
}

class TmuxClaudeCodeBackend extends AgentBackend {
constructor() {
super({ name: 'tmux-claude-code', runtimeFile: RUNTIME_FILE, archiveFile: ARCHIVE_FILE })
Expand Down Expand Up @@ -791,15 +804,15 @@ class TmuxClaudeCodeBackend extends AgentBackend {
}

// ── 内部实现 ──────────────────────────────────────────
async _createImpl({ sessionId, cwd, flagRoot, model, useProxy, displayName, initialPrompt, agentSessionId, isInitialContextPrompt = false, settingsPath, forceNoProxy = false, aimuxRemoteName }) {
async _createImpl({ sessionId, cwd, flagRoot, model, useProxy, displayName, initialPrompt, agentSessionId, isInitialContextPrompt = false, settingsPath, forceNoProxy = false, aimuxRemoteName, enableGulingMcp = false }) {
if (!sessionId || !cwd) throw new Error('createNewSession 需要 sessionId + cwd')
if (!initialPrompt) throw new Error('createNewSession 需要 initialPrompt')
if (!fs.existsSync(cwd)) throw new Error(`cwd 不存在: ${cwd}`)

// tmux 模式特点: window 可跨后端重启存活. 已有活窗口 → 复用 (跟原 hub.startSession
// idempotent 一致). 这跟 stream-json 那版"严格新建"语义不同, 是有意为之.
if (!windowExists(sessionId)) {
await this._spawnWindow({ sessionId, cwd, flagRoot, model, useProxy, displayName, agentSessionId, settingsPath, forceNoProxy, aimuxRemoteName })
await this._spawnWindow({ sessionId, cwd, flagRoot, model, useProxy, displayName, agentSessionId, settingsPath, forceNoProxy, aimuxRemoteName, enableGulingMcp })
} else {
// 窗口在但 runtime entry 可能不在 (后端首次 reload) — 兜底建一个
if (!this.runtime.has(sessionId) && agentSessionId) {
Expand Down Expand Up @@ -833,7 +846,7 @@ class TmuxClaudeCodeBackend extends AgentBackend {
}

// 宽松版 — 没活进程就按 opts 自动 spawn (chat 不区分首发/续发, 统一走这里).
async _queueImpl({ sessionId, prompt, cwd, flagRoot, model, useProxy, displayName, agentSessionId, isInitialContextPrompt = false, settingsPath, forceNoProxy = false, mobiusJsonl = null, aimuxRemoteName }) {
async _queueImpl({ sessionId, prompt, cwd, flagRoot, model, useProxy, displayName, agentSessionId, isInitialContextPrompt = false, settingsPath, forceNoProxy = false, mobiusJsonl = null, aimuxRemoteName, enableGulingMcp = false }) {
if (!sessionId) throw new Error('需要 sessionId')
if (!prompt) throw new Error('需要 prompt')

Expand All @@ -857,6 +870,7 @@ class TmuxClaudeCodeBackend extends AgentBackend {
displayName: displayName || persisted?.displayName,
agentSessionId: finalAgentSid,
aimuxRemoteName,
enableGulingMcp,
})
}
this._appendMobiusPromptEntry(sessionId, mobiusJsonl)
Expand Down Expand Up @@ -950,7 +964,7 @@ class TmuxClaudeCodeBackend extends AgentBackend {

// ── tmux 操作底层 ─────────────────────────────────────
// 启动一个新的 Claude Code tmux 窗口,并把运行态登记到内存和持久化存储。
async _spawnWindow({ sessionId, cwd, flagRoot, model, useProxy, displayName, agentSessionId, settingsPath, forceNoProxy = false, aimuxRemoteName }) {
async _spawnWindow({ sessionId, cwd, flagRoot, model, useProxy, displayName, agentSessionId, settingsPath, forceNoProxy = false, aimuxRemoteName, enableGulingMcp = false }) {
// 确保承载 agent 窗口的 tmux hub session 已经存在。
ensureHub()
// 运行标记默认写在 cwd 下;调用方传 flagRoot 时优先使用仓库根等稳定路径。
Expand Down Expand Up @@ -983,28 +997,45 @@ class TmuxClaudeCodeBackend extends AgentBackend {
// resume 使用旧 agentSessionId,新会话生成一个新的 UUID。
const claudeSessionId = useResume ? agentSessionId : crypto.randomUUID()

// 收集要禁用的工具: 永久禁用 AskUserQuestion/ExitPlanMode (避免 agent 停下来等
// 人 / 卡在 plan 模式). 若注入了 guling 实盘 MCP, 额外禁用其真实下单类工具
// (buy/sell/cancel/switch_account), 只保留只读查询 (position/balance/orders/
// settlement/watchlist), 防止 AI 误触发真实证券交易.
const disallowedTools = ['AskUserQuestion', 'ExitPlanMode']

// 收集要注入的 stdio/http MCP server (会话级 --mcp-config <json-file>, 顶层
// mcpServers, additive 不叠 --strict-mcp-config, 且 --mcp-config 传入的 server
// 被视为显式可信, 不触发 .mcp.json 那种信任弹窗). per-session 文件各会话不同.
const mcpServers = {}
// TUI 会话 (add_remote_aimux_mcp): aimux stdio MCP, 让 claude 经 remote_* 工具
// (remote_exec_command/write_stdin/apply_patch/view_image/ping) 操作远程工作站.
if (aimuxRemoteName) {
mcpServers.aimux = { command: resolveAimuxBin(), args: ['mcp', 'serve', '--remote', aimuxRemoteName] }
}
// 小莫 assistant 会话 (enableGulingMcp): guling 实盘 MCP (HTTP), 让 claude 直接读
// 资金/持仓. token 从 env 读, 未配置时 resolveGulingMcp() 返回 null → 跳过.
if (enableGulingMcp) {
const guling = resolveGulingMcp()
if (guling) {
mcpServers.guling = guling
disallowedTools.push('mcp__guling__buy', 'mcp__guling__sell', 'mcp__guling__cancel', 'mcp__guling__switch_account')
}
}

// 组装传给 claude CLI 的参数列表。
const claudeArgs = [
// 跳过权限确认,让后台 agent 可以自动执行。
`--dangerously-skip-permissions`,
// 绝对禁止 agent 停下来问人: 在 harness 层 deny 掉 AskUserQuestion 工具.
// 同时禁掉 ExitPlanMode, 避免 agent 卡在 plan 模式里等待用户批准.
`--disallowedTools AskUserQuestion,ExitPlanMode`,
`--disallowedTools ${disallowedTools.join(',')}`,
// resume 用 --resume,新会话用 --session-id 绑定固定会话 id。
useResume ? `--resume ${claudeSessionId}` : `--session-id ${claudeSessionId}`,
]
// 如果调用方指定模型,就追加 --model 参数并做 shell 转义。
if (model) claudeArgs.push(`--model ${shellQuote(model)}`)
// TUI 会话 (add_remote_aimux_mcp): 注入 aimux stdio MCP server, 让 claude 经
// remote_* 工具 (remote_exec_command/write_stdin/apply_patch/view_image/ping)
// 操作远程工作站. claude 用 --mcp-config <json-file> (顶层 mcpServers, stdio 默认),
// additive (不叠 --strict-mcp-config). per-session 文件因 --remote 各会话不同.
if (aimuxRemoteName) {
const aimuxBinPath = resolveAimuxBin()
const mcpConfigPath = path.join(os.tmpdir(), `mobius-aimux-mcp-${sessionId}-${crypto.randomUUID().slice(0, 8)}.json`)
fs.writeFileSync(mcpConfigPath, JSON.stringify({
mcpServers: { aimux: { command: aimuxBinPath, args: ['mcp', 'serve', '--remote', aimuxRemoteName] } },
}))
// 有任一 MCP server 要注入时, 写 per-session 配置文件并传给 claude.
if (Object.keys(mcpServers).length > 0) {
const mcpConfigPath = path.join(os.tmpdir(), `mobius-mcp-${sessionId}-${crypto.randomUUID().slice(0, 8)}.json`)
fs.writeFileSync(mcpConfigPath, JSON.stringify({ mcpServers }))
claudeArgs.push(`--mcp-config ${shellQuote(mcpConfigPath)}`)
}
// settings 参数优先使用调用方指定文件,否则使用默认 Mobius Claude settings。
Expand Down
2 changes: 1 addition & 1 deletion mobius/backend/agents/tmux-codex.js
Original file line number Diff line number Diff line change
Expand Up @@ -1214,7 +1214,7 @@ class TmuxCodexBackend extends AgentBackend {
// 并在 tmux 命令中 export TOML env_key 对应的秘钥环境变量.
// 组装 Codex CLI 参数:模型、工作目录以及自动审批/沙箱绕过参数。
const codexArgs = ['-m', finalModel, '-C', cwd, '--dangerously-bypass-approvals-and-sandbox']
// TUI 会话 (is_tui + aimux_id): 注入 aimux stdio MCP server, 让 codex 经 MCP
// TUI/Electron 会话 (add_remote_aimux_mcp + aimux_id): 注入 aimux stdio MCP server, 让 codex 经 MCP
// 工具 (remote_execute/read_file/write_file/ping/apply_patch) 操作远程工作站.
// codex `-c key=value` 按 TOML 解析 value, args 用 inline array.
if (aimuxRemoteName) {
Expand Down
91 changes: 91 additions & 0 deletions mobius/backend/routes/agent-bridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import express from 'express';
import { Users } from '../repositories/users';
import { Sessions } from '../repositories/sessions';
import { runSessionMessage } from '../services/session-message-runner';
import { verifyAgentBridgeToken } from '../services/agent-mention-bridge';

const router = express.Router();

function extractBridgeToken(req: express.Request): string {
const bodyToken = typeof req.body?.token === 'string' ? req.body.token.trim() : '';
if (bodyToken) return bodyToken;
const authHeader = String(req.headers.authorization || '').trim();
if (authHeader) {
const bearer = authHeader.replace(/^Bearer\s+/i, '').trim();
if (bearer) return bearer;
}
const headerToken = String(req.headers['x-agent-bridge-token'] || '').trim();
if (headerToken) return headerToken;
return '';
}

router.post('/messages', async (req: express.Request, res: express.Response) => {
const token = extractBridgeToken(req);
const payload = verifyAgentBridgeToken(token);
if (!payload) {
res.status(401).json({ error: '无效或过期的智能体桥接 token' });
return;
}

const bodyFromSessionId = typeof req.body?.from_session_id === 'string' ? req.body.from_session_id.trim() : '';
const bodyToSessionId = typeof req.body?.to_session_id === 'string' ? req.body.to_session_id.trim() : '';
const sourceSessionId = bodyFromSessionId || payload.source_session_id;
const targetSessionId = bodyToSessionId || payload.target_session_id;
const sourceMatches = sourceSessionId === payload.source_session_id && targetSessionId === payload.target_session_id;
const reverseMatches = sourceSessionId === payload.target_session_id && targetSessionId === payload.source_session_id;
if (!sourceMatches && !reverseMatches) {
res.status(403).json({ error: '桥接 token 与会话配对不一致' });
return;
}

const content = String(req.body?.content || '').trim();
if (!content) {
res.status(400).json({ error: 'content 不能为空' });
return;
}
if (content.length > 8000) {
res.status(400).json({ error: 'content 过长' });
return;
}

const ownerUser = Users.findAuthById(payload.owner_user_id) as any;
if (!ownerUser) {
res.status(401).json({ error: '桥接所属用户不存在' });
return;
}
const targetSession = Sessions.findById(targetSessionId) as any;
if (!targetSession) {
res.status(404).json({ error: '目标 Session 不存在' });
return;
}

try {
const result = await runSessionMessage({
user: ownerUser,
sessionId: targetSessionId,
content,
inputText: content,
hasInputText: true,
requestId: typeof req.body?.request_id === 'string' ? req.body.request_id : `bridge-${Date.now()}`,
source: 'api.agent_bridge.messages',
logger: console,
urgent: req.body?.urgent === true,
} as any);
res.json({
ok: true,
from_session_id: sourceSessionId,
to_session_id: targetSessionId,
request_id: result?.request_id ?? null,
turn_number: result?.turn_number ?? null,
mode: payload.mode,
});
} catch (e) {
const err = e as any;
res.status(err.status || 500).json({
error: err.message || '桥接消息发送失败',
category: err.category || undefined,
});
}
});

export { router };
3 changes: 3 additions & 0 deletions mobius/backend/routes/assistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,9 @@ async function startAssistantSession(req: express.Request, session: any, questio
agentSessionId: session.claude_session_id || undefined,
mobiusJsonl,
aimuxRemoteName: aimuxRemoteNameFromMeta(session?.pc_client_metadata),
// 小莫 assistant 注入 guling 实盘 MCP (HTTP), 让 claude 直接读资金/持仓.
// resolveGulingMcp() 未配置 env 时返回 null, 这里恒传 true 是安全 no-op.
enableGulingMcp: true,
});

const runtimeInfo = backend.listSessions().find((item: any) => item.sessionId === session.session_id);
Expand Down
3 changes: 2 additions & 1 deletion mobius/backend/routes/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1369,7 +1369,7 @@ router.get('/:id/selection-snapshot', auth, (req: express.Request, res: express.
// 5) backend.noPauseCurrentAndQueueQueryAtSession 推到 TUI
// 6) 同步 backend 内部 agent session id 回 DB
// 不做流式响应 — 请求成功即表示后端已接收; 后续 jsonl 由 /api/sessions/:id/events SSE 推送.
// Body: { content: string, input_text?: string, request_id?: string, attachments?: Array }
// Body: { content: string, input_text?: string, request_id?: string, attachments?: Array, mentions?: Array }
router.post('/:id/messages', auth, async (req: express.Request, res: express.Response) => {
const sessionId = String(req.params.id);
const user = userOf(req);
Expand All @@ -1387,6 +1387,7 @@ router.post('/:id/messages', auth, async (req: express.Request, res: express.Res
hasInputText,
requestId,
attachments: req.body?.attachments,
mentions: req.body?.mentions,
source: 'http.session.messages',
logger: console,
urgent: req.body?.urgent === true,
Expand Down
Loading