Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .copilotkit/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"projectId": "10465",
"projectSlug": "mymuse",
"clerkOrgId": "org_3KRATrQWIfkNyvONhAg1S86YUte",
"telemetryBindingId": "9c007abc-ea0b-4252-9d0d-7b8cf89d24a3",
"apiKeyDirectory": "."
}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
128 changes: 128 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# OpenMuse agent guidance

## Agent skills

### Issue tracker

OpenBot integration Issues are tracked in GitHub at `1123786563/OpenBot`. Before reading, creating, or updating one, read the parent repository's `../../docs/agents/issue-tracker.md`.

### Domain docs

For OpenBot shared terms, read `../../CONTEXT.md`; for OpenMuse-specific terms, read this submodule's `CONTEXT.md`. See `../../docs/agents/domain.md` for their boundaries.

# Agent 开发工作流

本仓库使用 **Matt Pocock Skills 负责设计**,使用 **Superpowers 负责实现**。
任何阶段只能有一个工作流编排器。

## 事实源

- `../../docs/specs/`:Spec 所在目录;逐份核对状态,只有明确批准的 Spec 才作为实施依据。
- `../../docs/adr/`:OpenBot 架构决策与约束。
- `../../CONTEXT.md`:OpenBot 共享术语;本目录 `CONTEXT.md`:OpenMuse 局部术语。
- `../../docs/plans/`:OpenBot 的 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`

开始实现 OpenBot 集成前先读取 `../../CONTEXT.md`、本目录 `CONTEXT.md`、已批准 Spec、相关 ADR 和当前 Ticket,然后再创建 Implementation Plan。
上述 Superpowers Skill 必须在当前会话中可调用;若未暴露,记录能力缺口并报告,不能声称已执行相应 Skill 或通过其关卡。

### 测试与容器

验证 OpenBot 集成改动时遵守父仓库 `../../docs/agents/testing.md`。测试结束时确认本次新建的测试容器已关闭。

### 分支与 Worktree

创建、复用或清理 Orca 工作区及 Git 分支前,遵守父仓库 `../../docs/agents/worktrees.md`;本子模块属于独立 Git 仓库,分别核对其状态与提交。

### 冲突规则

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`、本目录 `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 负责。
之后可选运行 `open-code-review`,再做一轮 Standards + Spec Compliance 独立审查。
Review Agent 默认应保持只读。
OpenBot 集成任务还须遵守父仓库 `../../AGENTS.md` 的“Open Code Review(OCR)”两级审查关卡。

## 长上下文 / Context Management 策略

本项目假设存在 1M 级原生上下文,但可持久化项目文件仍然是事实源。

- 不要因为模型能记更多,就推迟记录重要决策。
- 决策一旦稳定,应更新对应范围的 `CONTEXT.md`、ADR 或 Spec。
- 工具输出保持针对性;大体积证据保存到文件,只读取相关范围。
- 到达阶段边界时,如果前一阶段的大量聊天细节已经无用,优先使用新任务 / 新 Session / 干净子 Agent Context。
- 自动压缩或上下文滚动后,从文件 + Git + Superpowers Ledger 重建状态,不依赖模型自己的回忆。

## 完成标准

在必要测试、类型检查 / Lint / 静态分析(适用时)、验收标准、Review Findings 和最终 Diff 范围都经过验证前,不要声称任务完成。
**Evidence over claims(证据优先于声明)。**
25 changes: 25 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -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
72 changes: 36 additions & 36 deletions apps/mobile/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Record<Section, { title: string; subtitle: string }>> = {
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("");
Expand Down Expand Up @@ -111,27 +112,26 @@ export default function App() {
<Text
style={{ fontSize: 32, color: colors.text, letterSpacing: -1, fontWeight: "500" }}
>
Welcome to OpenMuse.
{t("welcomeTitle")}
</Text>
<Text style={[s.muted, { textAlign: "center" }]}>A little room for your day.</Text>
<Text style={[s.muted, { textAlign: "center" }]}>{t("welcomeSubtitle")}</Text>
{busy ? (
<ActivityIndicator color={colors.blueDark} />
) : (
<Card style={{ width: "100%" }}>
<ErrorNotice error={error} />
<Field
label="Workspace access key"
label={t("accessKeyLabel")}
value={accessKey}
onChangeText={setAccessKey}
secureTextEntry
placeholder="Required for a live workspace"
placeholder={t("phAccessKey")}
/>
<Button primary onPress={() => void connect(accessKey || undefined)}>
Open workspace
{t("openWorkspace")}
</Button>
<Text style={[s.small, { marginTop: 15 }]}>
Local workspaces open without a key. Make sure your OpenMuse server is running at{" "}
{API_URL}.
{t("localWorkspaceNote", { url: API_URL })}
</Text>
</Card>
)}
Expand Down Expand Up @@ -196,13 +196,13 @@ function WorkspaceApp({ token }: { token: string }) {
<>
<ErrorNotice error={error} />
<Button onPress={() => void refresh().catch((e) => setError(String(e)))}>
Try again
{t("calTryAgain")}
</Button>
</>
) : (
<>
<ActivityIndicator color={colors.blueDark} />
<Text style={s.muted}>Opening your workspace…</Text>
<Text style={s.muted}>{t("openingWorkspace")}</Text>
</>
)}
</SafeAreaView>
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -304,14 +304,14 @@ function WorkspaceShell({
<View style={{ position: "absolute", left: 0, top: 16 }}>
<IconButton
icon={Menu}
label="Open conversations and menu"
label={t("openThreadsMenu")}
onPress={() => setThreadsOpen(true)}
/>
</View>
<View pointerEvents="box-none" style={{ alignItems: "center", gap: 1 }}>
<Pressable
accessibilityRole="button"
accessibilityLabel={`Open ${agentName} activity and approvals`}
accessibilityLabel={t("openAgentActivity", { name: agentName })}
onPress={() => navigate("activity")}
style={({ pressed }) => ({
alignItems: "center",
Expand Down Expand Up @@ -342,7 +342,7 @@ function WorkspaceShell({
<View style={{ position: "absolute", right: 0, top: 16 }}>
<IconButton
icon={Bell}
label={`Notifications, ${pending} unread or pending`}
label={t("notificationsBadge", { n: pending })}
onPress={() => open({ type: "notifications" })}
/>
{pending > 0 && (
Expand Down Expand Up @@ -375,7 +375,7 @@ function WorkspaceShell({
style={{ alignSelf: "flex-start", marginBottom: 18 }}
onPress={() => navigate("apps")}
>
Back to Apps
{t("backToApps")}
</Button>
)}
<Text style={[s.title, { fontSize: 25, marginBottom: 22 }]}>{title?.title}</Text>
Expand All @@ -395,13 +395,13 @@ function WorkspaceShell({
<>
<ErrorNotice error={threadsError} />
{threadsError ? (
<Button onPress={retryThreads}>Retry main chat</Button>
<Button onPress={retryThreads}>{t("retryMainChat")}</Button>
) : threadsLoading ? (
<ActivityIndicator color={colors.blueDark} />
) : null}
{!threadsLoading && selection.id !== mainId && (
<Text style={[s.small, { textAlign: "center", marginBottom: 8 }]}>
Side chat
{t("sideChat")}
</Text>
)}
{visited.map((thread) => (
Expand Down Expand Up @@ -493,7 +493,7 @@ function WorkspaceShell({
<Text style={{ color: "#FFF", fontSize: 13, flexShrink: 1 }}>{toast}</Text>
<Pressable
accessibilityRole="button"
accessibilityLabel="Dismiss notification"
accessibilityLabel={t("dismissToast")}
onPress={clearToast}
>
<X size={16} color="#FFF" />
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 3 additions & 1 deletion apps/mobile/src/BrowserConsole.web.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
9 changes: 5 additions & 4 deletions apps/mobile/src/DateFields.native.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { View } from "react-native";
import { t } from "./strings";
import { Field } from "./ui";
export interface DateFieldsProps {
label: string;
Expand All @@ -12,20 +13,20 @@ export default function DateFields({ label, date, time, allDay, onChange }: Date
<View style={{ flexDirection: "row", gap: 12 }}>
<View style={{ flex: 1.2 }}>
<Field
label={`${label} date`}
label={t("labelDate", { label })}
value={date}
onChangeText={(value) => onChange(value, time)}
placeholder="YYYY-MM-DD"
placeholder={t("phDate")}
keyboardType="numbers-and-punctuation"
/>
</View>
{!allDay && (
<View style={{ flex: 1 }}>
<Field
label={`${label} time`}
label={t("labelTime", { label })}
value={time}
onChangeText={(value) => onChange(date, value)}
placeholder="HH:MM (24-hour)"
placeholder={t("phTime")}
keyboardType="numbers-and-punctuation"
/>
</View>
Expand Down
Loading