From 46792335dfc260efa05f2185aa353093b12d9937 Mon Sep 17 00:00:00 2001 From: wuyj Date: Mon, 12 Oct 2026 22:15:26 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E5=B0=86=E7=A7=BB=E5=8A=A8=E7=AB=AF=20UI?= =?UTF-8?q?=20=E6=9C=AC=E5=9C=B0=E5=8C=96=E4=B8=BA=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E5=B9=B6=E6=B7=BB=E5=8A=A0=20OpenBot=20=E9=9B=86=E6=88=90?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 apps/mobile/src/strings.ts,通过 t() 集中处理 UI 文案;将整个移动端应用(包括 zh-CN 日期格式、相对日期和更新后的测试)路由到该处 - 添加 OpenBot 集成规划:CONTEXT.md 领域词汇表、AGENTS.md 工作流、ADR 0001-0010 以及 v4 票据目录/契约/验收/发布文档 - 添加 .copilotkit/project.json 并忽略 .env.* 凭证文件 --- .copilotkit/project.json | 7 + .gitignore | 4 + AGENTS.md | 118 +++ CONTEXT.md | 25 + apps/mobile/App.tsx | 72 +- apps/mobile/app.json | 2 +- apps/mobile/src/BrowserConsole.web.tsx | 4 +- apps/mobile/src/DateFields.native.tsx | 9 +- apps/mobile/src/DateFields.web.tsx | 13 +- apps/mobile/src/DateTimeEditor.tsx | 3 +- apps/mobile/src/PdfReader.web.tsx | 13 +- apps/mobile/src/agent-ui.tsx | 418 ++++---- apps/mobile/src/background-updates.tsx | 9 +- apps/mobile/src/browser-tool-card.tsx | 33 +- apps/mobile/src/chat.tsx | 98 +- apps/mobile/src/computer-workspace.tsx | 132 +-- apps/mobile/src/computer.tsx | 77 +- apps/mobile/src/desktop-stream.tsx | 5 +- apps/mobile/src/desktop-tool-card.tsx | 19 +- apps/mobile/src/details.tsx | 265 +++-- apps/mobile/src/jev-actions.ts | 7 +- apps/mobile/src/jev-tool-card.tsx | 27 +- apps/mobile/src/mail-tool-card.tsx | 28 +- apps/mobile/src/relative-date.ts | 12 +- apps/mobile/src/screens.tsx | 306 +++--- apps/mobile/src/search-tool-card.tsx | 21 +- apps/mobile/src/strings.ts | 812 +++++++++++++++ apps/mobile/src/thread-artifacts.tsx | 9 +- apps/mobile/src/threads.tsx | 71 +- apps/mobile/src/ui.tsx | 11 +- apps/mobile/test/jev-actions.test.ts | 10 +- apps/mobile/test/relative-date.test.ts | 10 +- apps/mobile/test/strings.test.ts | 19 + docs/adr/0001-openbot-as-client-entry.md | 11 + ...serve-openmuse-bot-and-review-ownership.md | 11 + ...elegated-user-identity-and-tool-control.md | 11 + .../adr/0004-private-openmuse-bot-per-user.md | 9 + ...005-openbot-owns-durable-task-lifecycle.md | 11 + .../0006-dedicated-openmuse-task-module.md | 9 + ...personal-context-and-task-notifications.md | 9 + .../adr/0008-account-deletion-grace-period.md | 9 + docs/adr/0009-openbot-as-sole-backend.md | 11 + ...penmuse-repository-as-openbot-submodule.md | 9 + docs/agents/domain.md | 25 + docs/agents/issue-tracker.md | 34 + ...26-10-12-openmuse-openbot-acceptance-v4.md | 80 ++ ...026-10-12-openmuse-openbot-contracts-v4.md | 97 ++ ...26-10-12-openmuse-openbot-delivery-plan.md | 275 ++++++ ...10-12-openmuse-openbot-publication-v4.json | 753 ++++++++++++++ ...026-10-12-openmuse-openbot-published-v4.md | 100 ++ ...2-openmuse-openbot-review-resolution-v4.md | 37 + ...nmuse-openbot-single-context-tickets-v3.md | 923 ++++++++++++++++++ ...openmuse-openbot-single-context-tickets.md | 752 ++++++++++++++ ...0-12-openmuse-openbot-ticket-catalog-v4.md | 762 +++++++++++++++ 54 files changed, 5713 insertions(+), 894 deletions(-) create mode 100644 .copilotkit/project.json create mode 100644 AGENTS.md create mode 100644 CONTEXT.md create mode 100644 apps/mobile/src/strings.ts create mode 100644 apps/mobile/test/strings.test.ts create mode 100644 docs/adr/0001-openbot-as-client-entry.md create mode 100644 docs/adr/0002-preserve-openmuse-bot-and-review-ownership.md create mode 100644 docs/adr/0003-delegated-user-identity-and-tool-control.md create mode 100644 docs/adr/0004-private-openmuse-bot-per-user.md create mode 100644 docs/adr/0005-openbot-owns-durable-task-lifecycle.md create mode 100644 docs/adr/0006-dedicated-openmuse-task-module.md create mode 100644 docs/adr/0007-openbot-owns-personal-context-and-task-notifications.md create mode 100644 docs/adr/0008-account-deletion-grace-period.md create mode 100644 docs/adr/0009-openbot-as-sole-backend.md create mode 100644 docs/adr/0010-openmuse-repository-as-openbot-submodule.md create mode 100644 docs/agents/domain.md create mode 100644 docs/agents/issue-tracker.md create mode 100644 docs/superpowers/plans/2026-10-12-openmuse-openbot-acceptance-v4.md create mode 100644 docs/superpowers/plans/2026-10-12-openmuse-openbot-contracts-v4.md create mode 100644 docs/superpowers/plans/2026-10-12-openmuse-openbot-delivery-plan.md create mode 100644 docs/superpowers/plans/2026-10-12-openmuse-openbot-publication-v4.json create mode 100644 docs/superpowers/plans/2026-10-12-openmuse-openbot-published-v4.md create mode 100644 docs/superpowers/plans/2026-10-12-openmuse-openbot-review-resolution-v4.md create mode 100644 docs/superpowers/plans/2026-10-12-openmuse-openbot-single-context-tickets-v3.md create mode 100644 docs/superpowers/plans/2026-10-12-openmuse-openbot-single-context-tickets.md create mode 100644 docs/superpowers/plans/2026-10-12-openmuse-openbot-ticket-catalog-v4.md diff --git a/.copilotkit/project.json b/.copilotkit/project.json new file mode 100644 index 000000000..1c6e51f2a --- /dev/null +++ b/.copilotkit/project.json @@ -0,0 +1,7 @@ +{ + "projectId": "10465", + "projectSlug": "mymuse", + "clerkOrgId": "org_3KRATrQWIfkNyvONhAg1S86YUte", + "telemetryBindingId": "9c007abc-ea0b-4252-9d0d-7b8cf89d24a3", + "apiKeyDirectory": "." +} diff --git a/.gitignore b/.gitignore index 732d027dd..0724a4031 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ apps/mobile/android/ .DS_Store *.tsbuildinfo artifacts/ + +# Added by CopilotKit CLI: credential files, which must never be committed. +.env.* +!.env.example diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..4642d4279 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,118 @@ +# OpenMuse agent guidance + +## Agent skills + +### Issue tracker + +Issues and specs for this repository are tracked in GitHub Issues at `1123786563/openmuse`. See `docs/agents/issue-tracker.md`. + +### Domain docs + +This repository uses a single domain context at the root. See `docs/agents/domain.md`. + +# Agent 开发工作流 + +本仓库使用 **Matt Pocock Skills 负责设计**,使用 **Superpowers 负责实现**。 +任何阶段只能有一个工作流编排器。 + +## 事实源 + +- `docs/specs/`:已批准需求、行为、范围、非目标和验收标准。 +- `docs/adr/`:架构决策与约束。 +- `CONTEXT.md`:统一的领域术语和领域模型。 +- `docs/plans/`:Superpowers 实施计划。 +- Git + Superpowers Ledger:执行历史和已完成任务的证据。 + +如果实施 Plan 与已批准 Spec 冲突,以 Spec 为准。 +如果实现与 ADR 或批准需求冲突,应升级处理,而不是在实现阶段静默重新设计。 + +## Phase A — Matt Skills:WHAT / WHY + +使用 Matt Skills 处理: + +- `grill-with-docs` +- `grilling` +- `domain-modeling` +- `codebase-design` +- `improve-codebase-architecture` +- `prototype` +- `research` +- `wayfinder` +- `to-spec` +- `to-tickets` +- `handoff` + +该阶段除明确标记为一次性原型的代码外,不实现生产代码。 + +满足以下条件后视为设计完成:重要决策已经解决、领域语言已经记录、相关 ADR 已存在、已有批准的 Spec,并且大型工作已经拆成纵向业务 Ticket。 + +## Phase B — Superpowers:HOW / EXECUTE + +使用 Superpowers 处理: + +- `writing-plans` +- `using-git-worktrees` +- `dispatching-parallel-agents` +- `subagent-driven-development` +- `executing-plans` +- `test-driven-development` +- `systematic-debugging` +- `requesting-code-review` +- `receiving-code-review` +- `verification-before-completion` +- `finishing-a-development-branch` + +开始实现前先读取 `CONTEXT.md`、已批准 Spec、相关 ADR 和当前 Ticket,然后再创建 Implementation Plan。 + +### 冲突规则 + +Superpowers 实现阶段**不要**再调用 Matt 的 `implement`、`implement-spec` 或 `tdd` 作为竞争实现流程。 +已经存在批准 Spec 时不要重新启动产品 Brainstorming;Superpowers 只允许澄清尚未解决的实现歧义或真正的冲突。 + +### 任务粒度 + +Matt Ticket 应是“业务完整的纵向切片”;Superpowers Task 是 Ticket 内更小的实现单元。 +除非属于真正的大范围机械重构,否则不要把纵向 Ticket 替换成“数据库 / 后端 / 前端”这样的水平 Ticket。 + +### 并行 Agent + +只有在任务相互独立、接口已确定、可写文件和共享状态不重叠时才并行。 +每个实现流优先使用独立 Git Worktree;主 Agent 负责最终集成。 +不要使用共享 `git stash` 作为并行协调机制。 + +### 子 Agent 上下文 + +给子 Agent 提供最小必要上下文,并尽量使用这些文件指针:`CONTEXT.md`、相关 ADR、批准 Spec、当前 Ticket、Task Brief、前置任务接口。 +不要把整个父会话复制给每个 Child Agent。长上下文是储备容量,不是信息倾倒场。 + +### TDD + +Superpowers 负责实现阶段的 TDD 编排。 +在适用场景遵循 **RED -> GREEN -> REFACTOR**,优先测试公共 Seam 和可观察行为。 +可以吸收 Matt 的测试设计原则,但 Matt `tdd` 不接管实现阶段。 + +### 调试 + +实现缺陷使用 Superpowers `systematic-debugging`。 +如果根因是架构或领域模型问题,则返回 Matt `domain-modeling` / `codebase-design`,更新 ADR / Spec 后再恢复实现。 + +### Review + +每个任务和实现阶段最终 Review 由 Superpowers 负责。 +之后可选运行 Matt `code-review`,再做一轮 Standards + Spec Compliance 独立审查。 +Review Agent 默认应保持只读。 + +## 长上下文 / Context Management 策略 + +本项目假设存在 1M 级原生上下文,但可持久化项目文件仍然是事实源。 + +- 不要因为模型能记更多,就推迟记录重要决策。 +- 决策一旦稳定,应更新 `CONTEXT.md`、ADR 或 Spec。 +- 工具输出保持针对性;大体积证据保存到文件,只读取相关范围。 +- 到达阶段边界时,如果前一阶段的大量聊天细节已经无用,优先使用新任务 / 新 Session / 干净子 Agent Context。 +- 自动压缩或上下文滚动后,从文件 + Git + Superpowers Ledger 重建状态,不依赖模型自己的回忆。 + +## 完成标准 + +在必要测试、类型检查 / Lint / 静态分析(适用时)、验收标准、Review Findings 和最终 Diff 范围都经过验证前,不要声称任务完成。 +**Evidence over claims(证据优先于声明)。** diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 000000000..38a8e3429 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,25 @@ +# OpenMuse with OpenBot + +Vocabulary for people, conversations, and reviewed work in OpenMuse's OpenBot integration. + +## Language + +**Bot**: +A persistent OpenBot coworker that a person can access and converse with. Its permitted capabilities are separate from its description. +_Avoid_: Agent, assistant (when referring to an OpenBot Bot) + +**Channel**: +An OpenBot conversation involving a person and a Bot. A channel has its own identity, distinct from the Bot and its conversation thread. +_Avoid_: Thread, chat session + +**Thread**: +The durable conversation history associated with a channel. +_Avoid_: Channel + +**Reviewed action**: +A proposal for a specific external change whose shown details require a person's recorded approval before execution. +_Avoid_: General tool permission + +**Policy approval**: +An OpenBot request for a person's decision about an action governed by the Bot's permissions. +_Avoid_: Reviewed action diff --git a/apps/mobile/App.tsx b/apps/mobile/App.tsx index 41152e116..76294c8fb 100644 --- a/apps/mobile/App.tsx +++ b/apps/mobile/App.tsx @@ -38,33 +38,34 @@ import { ComputerEntry } from "./src/computer"; import { ComputerDraftProvider } from "./src/computer-drafts"; import { Details } from "./src/details"; import { BrowserScreen, CalendarScreen, FilesScreen, MailScreen } from "./src/screens"; +import { t } from "./src/strings"; import { ThreadsProvider, ThreadsSheet, useMuseThread } from "./src/threads"; import { Button, Card, colors, ErrorNotice, Field, IconButton, Mascot, s } from "./src/ui"; import { type Detail, useWorkspace, WorkspaceContext } from "./src/workspace"; const nav: { id: Section; label: string; icon: LucideIcon }[] = [ - { id: "chat", label: "Chat", icon: MessageCircle }, - { id: "activity", label: "Activity", icon: PanelsTopLeft }, - { id: "ideas", label: "Ideas", icon: Lightbulb }, - { id: "goals", label: "Goals", icon: SquareCheck }, - { id: "apps", label: "Apps", icon: Shapes }, + { id: "chat", label: t("navChat"), icon: MessageCircle }, + { id: "activity", label: t("navActivity"), icon: PanelsTopLeft }, + { id: "ideas", label: t("navIdeas"), icon: Lightbulb }, + { id: "goals", label: t("navGoals"), icon: SquareCheck }, + { id: "apps", label: t("navApps"), icon: Shapes }, ]; const titles: Partial> = { - activity: { title: "Activity", subtitle: "Plans, progress, decisions and results." }, - ideas: { title: "Ideas", subtitle: "Useful next steps, grounded in your world." }, + activity: { title: t("navActivity"), subtitle: t("subtitleActivity") }, + ideas: { title: t("navIdeas"), subtitle: t("subtitleIdeas") }, goals: { - title: "Goals", - subtitle: "Longer-term goals and things to keep an eye on.", + title: t("navGoals"), + subtitle: t("subtitleGoals"), }, apps: { - title: "Apps", - subtitle: "Connections, capabilities and what your agent remembers.", + title: t("navApps"), + subtitle: t("subtitleApps"), }, - connections: { title: "Apps", subtitle: "Connections and capabilities." }, - mail: { title: "Mail", subtitle: "The conversations behind your work." }, - calendar: { title: "Calendar", subtitle: "Time for what matters." }, - browser: { title: "Browser", subtitle: "Your connected browsing sessions." }, - files: { title: "Files", subtitle: "Documents, forms and filled copies." }, + connections: { title: t("navApps"), subtitle: t("subtitleConnections") }, + mail: { title: t("shortcutMail"), subtitle: t("subtitleMail") }, + calendar: { title: t("shortcutCalendar"), subtitle: t("subtitleCalendar") }, + browser: { title: t("navBrowser"), subtitle: t("subtitleBrowser") }, + files: { title: t("shortcutFiles"), subtitle: t("subtitleFiles") }, }; export default function App() { const [token, setToken] = useState(""); @@ -111,27 +112,26 @@ export default function App() { - Welcome to OpenMuse. + {t("welcomeTitle")} - A little room for your day. + {t("welcomeSubtitle")} {busy ? ( ) : ( - Local workspaces open without a key. Make sure your OpenMuse server is running at{" "} - {API_URL}. + {t("localWorkspaceNote", { url: API_URL })} )} @@ -196,13 +196,13 @@ function WorkspaceApp({ token }: { token: string }) { <> ) : ( <> - Opening your workspace… + {t("openingWorkspace")} )} @@ -264,13 +264,13 @@ function WorkspaceShell({ const agentName = data?.identity.name || "OpenMuse"; const status = activeTask ? activeTask.status === "waiting_approval" - ? `Ready to review · ${activeTask.title}` + ? t("statusReadyReview", { title: activeTask.title }) : activeTask.status === "waiting_input" - ? `Needs your input · ${activeTask.title}` + ? t("statusNeedsInput", { title: activeTask.title }) : activeTask.plan.find((step) => step.status === "running")?.title || activeTask.title : data?.tasks.some((task) => task.status === "queued") - ? "Picking up your next task…" - : "Here when you need me"; + ? t("statusPickingUp") + : t("statusHere"); const title = titles[section] || titles.apps; const Screen = section === "mail" @@ -304,14 +304,14 @@ function WorkspaceShell({ setThreadsOpen(true)} /> navigate("activity")} style={({ pressed }) => ({ alignItems: "center", @@ -342,7 +342,7 @@ function WorkspaceShell({ open({ type: "notifications" })} /> {pending > 0 && ( @@ -375,7 +375,7 @@ function WorkspaceShell({ style={{ alignSelf: "flex-start", marginBottom: 18 }} onPress={() => navigate("apps")} > - Back to Apps + {t("backToApps")} )} {title?.title} @@ -395,13 +395,13 @@ function WorkspaceShell({ <> {threadsError ? ( - + ) : threadsLoading ? ( ) : null} {!threadsLoading && selection.id !== mainId && ( - Side chat + {t("sideChat")} )} {visited.map((thread) => ( @@ -493,7 +493,7 @@ function WorkspaceShell({ {toast} diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 5c7827339..e6098864d 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -9,7 +9,7 @@ "newArchEnabled": true, "ios": { "supportsTablet": true, "bundleIdentifier": "app.openmuse.mobile" }, "android": { "package": "app.openmuse.mobile" }, - "web": { "bundler": "metro", "name": "OpenMuse — a little room for everything" }, + "web": { "bundler": "metro", "name": "OpenMuse — 一方小天地,装下所有事" }, "plugins": [ "expo-document-picker", "@config-plugins/react-native-blob-util", diff --git a/apps/mobile/src/BrowserConsole.web.tsx b/apps/mobile/src/BrowserConsole.web.tsx index eb15e3369..f1c75a966 100644 --- a/apps/mobile/src/BrowserConsole.web.tsx +++ b/apps/mobile/src/BrowserConsole.web.tsx @@ -1,6 +1,8 @@ +import { t } from "./strings"; + export default function BrowserConsole({ url, - title = "Remote browser session console", + title = t("browserConsoleTitle"), sandboxed = false, }: { url: string; diff --git a/apps/mobile/src/DateFields.native.tsx b/apps/mobile/src/DateFields.native.tsx index 32fb7ffc3..7782e1c16 100644 --- a/apps/mobile/src/DateFields.native.tsx +++ b/apps/mobile/src/DateFields.native.tsx @@ -1,4 +1,5 @@ import { View } from "react-native"; +import { t } from "./strings"; import { Field } from "./ui"; export interface DateFieldsProps { label: string; @@ -12,20 +13,20 @@ export default function DateFields({ label, date, time, allDay, onChange }: Date onChange(value, time)} - placeholder="YYYY-MM-DD" + placeholder={t("phDate")} keyboardType="numbers-and-punctuation" /> {!allDay && ( onChange(date, value)} - placeholder="HH:MM (24-hour)" + placeholder={t("phTime")} keyboardType="numbers-and-punctuation" /> diff --git a/apps/mobile/src/DateFields.web.tsx b/apps/mobile/src/DateFields.web.tsx index db9e88afd..c6fa08aad 100644 --- a/apps/mobile/src/DateFields.web.tsx +++ b/apps/mobile/src/DateFields.web.tsx @@ -1,4 +1,5 @@ import { Text, View } from "react-native"; +import { t } from "./strings"; import { colors, s } from "./ui"; interface DateFieldsProps { @@ -24,9 +25,11 @@ export default function DateFields({ label, date, time, allDay, onChange }: Date return ( - {label} date + + {t("labelDate", { label })} + onChange(e.target.value, time)} @@ -35,9 +38,11 @@ export default function DateFields({ label, date, time, allDay, onChange }: Date {!allDay && ( - {label} time + + {t("labelTime", { label })} + onChange(date, e.target.value)} diff --git a/apps/mobile/src/DateTimeEditor.tsx b/apps/mobile/src/DateTimeEditor.tsx index 92e78ee7a..fc1eaff94 100644 --- a/apps/mobile/src/DateTimeEditor.tsx +++ b/apps/mobile/src/DateTimeEditor.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import { Text, View } from "react-native"; import DateFields from "./DateFields"; import { isCompleteInstant, localDateTime, zonedInstant } from "./date-time"; +import { t } from "./strings"; import { colors, s } from "./ui"; export default function DateTimeEditor({ label, @@ -29,7 +30,7 @@ export default function DateTimeEditor({ setTime(local.time); } } catch { - setError("Choose a valid time zone."); + setError(t("invalidTimeZone")); } }, [value, timeZone, allDay]); function change(nextDate: string, nextTime: string) { diff --git a/apps/mobile/src/PdfReader.web.tsx b/apps/mobile/src/PdfReader.web.tsx index 017fdf562..b9b1f16e2 100644 --- a/apps/mobile/src/PdfReader.web.tsx +++ b/apps/mobile/src/PdfReader.web.tsx @@ -1,6 +1,7 @@ import { ChevronLeft, ChevronRight, Minus, Plus } from "lucide-react-native"; import { useState } from "react"; import { Text, View } from "react-native"; +import { t } from "./strings"; import { Button, s } from "./ui"; interface PdfReaderProps { @@ -16,7 +17,7 @@ export default function PdfReader({ url, pageCount }: PdfReaderProps) { {page} / {pageCount} @@ -27,26 +28,26 @@ export default function PdfReader({ url, pageCount }: PdfReaderProps) { disabled={page >= pageCount} onPress={() => setPage(page + 1)} > - Next + {t("next")} {zoom}%