Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .codex/skills/openspec-apply-change/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
generatedBy: "1.3.0"
---

Implement tasks from an OpenSpec change.
Expand Down
2 changes: 1 addition & 1 deletion .codex/skills/openspec-archive-change/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
generatedBy: "1.3.0"
---

Archive a completed change in the experimental workflow.
Expand Down
28 changes: 14 additions & 14 deletions .codex/skills/openspec-explore/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
generatedBy: "1.3.0"
---

Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
Expand Down Expand Up @@ -56,10 +56,10 @@ Depending on what the user brings, you might:
│ Use ASCII diagrams liberally │
├─────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ │
│ │ State │────────▶│ State │ │
│ │ A │ │ B │ │
│ └────────┘ └────────┘ │
│ ┌────────┐ ┌────────┐ │
│ │ State │────────▶│ State │ │
│ │ A │ │ B │ │
│ └────────┘ └────────┘ │
│ │
│ System diagrams, state machines, │
│ data flows, architecture sketches, │
Expand Down Expand Up @@ -114,14 +114,14 @@ If the user mentions a change or you detect one is relevant:

3. **Offer to capture when decisions are made**

| Insight Type | Where to Capture |
|--------------|------------------|
| New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` |
| Scope changed | `proposal.md` |
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |
| Insight Type | Where to Capture |
|----------------------------|--------------------------------|
| New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` |
| Scope changed | `proposal.md` |
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |

Example offers:
- "That's a design decision. Capture it in design.md?"
Expand Down Expand Up @@ -227,7 +227,7 @@ User: A CLI tool that tracks local dev environments
You: That changes everything.

┌─────────────────────────────────────────────────┐
│ CLI TOOL DATA STORAGE │
│ CLI TOOL DATA STORAGE │
└─────────────────────────────────────────────────┘

Key constraints:
Expand Down
2 changes: 1 addition & 1 deletion .codex/skills/openspec-propose/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.2.0"
generatedBy: "1.3.0"
---

Propose a new change - create the change and generate all artifacts in one step.
Expand Down
10 changes: 10 additions & 0 deletions .cursor/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": [
"mcp-server-fetch"
]
}
}
}
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,17 @@ MULTICA_LLM_BASE_URL=
# - Default model used when a request omits `model`. When unset, falls
# back to a small built-in default (gpt-5.6-luna).
MULTICA_LLM_DEFAULT_MODEL=
# Graph rollout controls. Disabling new runs also pauses trigger dispatch;
# disabling advancement stops new node dispatch while retaining receipts and
# cancellation recovery for running work. Neither flag changes ordinary tasks.
MULTICA_GRAPH_DISABLE_NEW_RUNS=false
MULTICA_GRAPH_DISABLE_ADVANCEMENT=false
MULTICA_DAEMON_CONFIG=
MULTICA_WORKSPACE_ID=
# Optional daemon workspace allowlist (comma-separated UUIDs). Unset preserves
# automatic discovery of every accessible workspace. An explicitly provided
# `daemon start --workspace-id <uuid>` scopes that daemon to one workspace.
MULTICA_DAEMON_WORKSPACE_IDS=
MULTICA_DAEMON_ID=
MULTICA_DAEMON_DEVICE_NAME=
MULTICA_DAEMON_POLL_INTERVAL=3s
Expand Down
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ docker/entrypoint.sh text eol=lf

# Default behavior
* text=auto

# Bundled release signatures cover the exact archive payload bytes.
server/internal/pluginbundled/releases/** -text
# Embedded skill documents must have the same frontmatter on every build host.
server/internal/service/builtin_skills/** text eol=lf
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,13 @@ jobs:
- name: Setup Helm
uses: azure/setup-helm@v4

# Graph's native OpenClaw/OMP credential projection uses node:sqlite.
# Pin a supported runtime so these fixtures execute in the backend gate.
- name: Setup Node.js for native runtime fixtures
uses: actions/setup-node@v6
with:
node-version: 22

- name: Test Helm chart
run: bash scripts/helm-config.test.sh

Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ jobs:
- name: Record Go toolchain
run: go version

- name: Setup Node.js for native runtime fixtures
uses: actions/setup-node@v6
with:
node-version: 22

- name: Verify Go test wrapper
run: bash scripts/test-go.test.sh

Expand Down
61 changes: 41 additions & 20 deletions apps/mobile/app/(app)/[workspace]/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { workspaceListOptions } from "@/data/queries/workspaces";
import { useWorkspaceStore } from "@/data/workspace-store";
import { RealtimeProvider } from "@/data/realtime/realtime-provider";
import { useInboxRealtime } from "@/data/realtime/use-inbox-realtime";
import { useGraphsRealtime } from "@/data/realtime/use-graphs-realtime";
import { useIssuesRealtime } from "@/data/realtime/use-issues-realtime";
import { useMyIssuesRealtime } from "@/data/realtime/use-my-issues-realtime";
import { useChatSessionsRealtime } from "@/data/realtime/use-chat-sessions-realtime";
Expand Down Expand Up @@ -75,6 +76,7 @@ export const unstable_settings = { anchor: "(tabs)" } as const;
function RealtimeSubscriptions() {
useInboxRealtime();
useIssuesRealtime();
useGraphsRealtime();
useMyIssuesRealtime();
useChatSessionsRealtime();
useProjectsRealtime();
Expand Down Expand Up @@ -132,6 +134,40 @@ export default function WorkspaceLayout() {
<RealtimeSubscriptions />
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="graph/index"
options={{ title: "Graph", headerBackTitle: "Back" }}
/>
<Stack.Screen
name="graph/new"
options={{
title: "Create Graph",
presentation: "modal",
headerLeft: () => <ModalCloseButton />,
}}
/>
<Stack.Screen
name="graph/[id]/index"
options={{ title: "Workflow", headerBackTitle: "Graph" }}
/>
<Stack.Screen
name="graph/[id]/start"
options={{ title: "Start workflow", headerBackTitle: "Workflow" }}
/>
<Stack.Screen
name="graph/[id]/triggers"
options={{ title: "Triggers", headerBackTitle: "Workflow" }}
/>
<Stack.Screen
name="graph/runs"
options={{ title: "Graph runs", headerBackTitle: "Graph" }}
/>
<Stack.Screen
name="graph/run/[id]"
options={{ title: "Graph run", headerBackTitle: "Back" }}
/>
<Stack.Screen name="graph/picker" options={SHEET_OPTIONS} />
<Stack.Screen name="graph/node" options={SHEET_OPTIONS} />
<Stack.Screen
name="issue/[id]"
options={{
Expand Down Expand Up @@ -175,10 +211,7 @@ export default function WorkspaceLayout() {
`fitToContents` zero-size / padding bugs on iOS 26 + Expo 55),
iOS native grabber, and contentStyle.height=100% as a safety
net against the same zero-size class of bugs. */}
<Stack.Screen
name="issue/[id]/picker/status"
options={SHEET_OPTIONS}
/>
<Stack.Screen name="issue/[id]/picker/status" options={SHEET_OPTIONS} />
<Stack.Screen
name="issue/[id]/picker/priority"
options={SHEET_OPTIONS}
Expand All @@ -198,10 +231,7 @@ export default function WorkspaceLayout() {
title: "Assignee",
}}
/>
<Stack.Screen
name="issue/[id]/picker/label"
options={SHEET_OPTIONS}
/>
<Stack.Screen name="issue/[id]/picker/label" options={SHEET_OPTIONS} />
<Stack.Screen
name="mention-picker"
options={{
Expand Down Expand Up @@ -235,10 +265,7 @@ export default function WorkspaceLayout() {
name="project/[id]/picker/priority"
options={SHEET_OPTIONS}
/>
<Stack.Screen
name="project/[id]/picker/lead"
options={SHEET_OPTIONS}
/>
<Stack.Screen name="project/[id]/picker/lead" options={SHEET_OPTIONS} />
<Stack.Screen
name="project/[id]/add-resource"
options={SHEET_OPTIONS}
Expand All @@ -247,10 +274,7 @@ export default function WorkspaceLayout() {
new-issue.tsx Stack.Screen (which is itself a `modal`).
Expo Router 55 / RN Screens 4 support a formSheet pushed on top
of a modal in the same Stack. */}
<Stack.Screen
name="new-issue-picker/status"
options={SHEET_OPTIONS}
/>
<Stack.Screen name="new-issue-picker/status" options={SHEET_OPTIONS} />
<Stack.Screen
name="new-issue-picker/priority"
options={SHEET_OPTIONS}
Expand All @@ -263,10 +287,7 @@ export default function WorkspaceLayout() {
title: "Assignee",
}}
/>
<Stack.Screen
name="new-issue-picker/project"
options={SHEET_OPTIONS}
/>
<Stack.Screen name="new-issue-picker/project" options={SHEET_OPTIONS} />
<Stack.Screen
name="new-issue-picker/due-date"
options={SHEET_OPTIONS}
Expand Down
6 changes: 6 additions & 0 deletions apps/mobile/app/(app)/[workspace]/graph/[id]/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useLocalSearchParams } from "expo-router";
import { GraphEditorScreen } from "@/components/graphs/editor-screen";
export default function GraphDetail() {
const { id } = useLocalSearchParams<{ id: string }>();
return <GraphEditorScreen id={id} />;
}
16 changes: 16 additions & 0 deletions apps/mobile/app/(app)/[workspace]/graph/[id]/start.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useLocalSearchParams } from "expo-router";
import { GraphStartScreen } from "@/components/graphs/start-screen";
export default function StartGraph() {
const { id, previous_run, issue } = useLocalSearchParams<{
id: string;
previous_run?: string;
issue?: string;
}>();
return (
<GraphStartScreen
id={id}
previousRunId={previous_run}
rootIssueId={issue}
/>
);
}
6 changes: 6 additions & 0 deletions apps/mobile/app/(app)/[workspace]/graph/[id]/triggers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useLocalSearchParams } from "expo-router";
import { GraphTriggersScreen } from "@/components/graphs/triggers-screen";
export default function GraphTriggers() {
const { id } = useLocalSearchParams<{ id: string }>();
return <GraphTriggersScreen id={id} />;
}
1 change: 1 addition & 0 deletions apps/mobile/app/(app)/[workspace]/graph/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GraphListScreen as default } from "@/components/graphs/list-screen";
4 changes: 4 additions & 0 deletions apps/mobile/app/(app)/[workspace]/graph/new.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { GraphEditorScreen } from "@/components/graphs/editor-screen";
export default function NewGraph() {
return <GraphEditorScreen />;
}
1 change: 1 addition & 0 deletions apps/mobile/app/(app)/[workspace]/graph/node.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { GraphNodeScreen as default } from "@/components/graphs/node-screen";
80 changes: 80 additions & 0 deletions apps/mobile/app/(app)/[workspace]/graph/picker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useState } from "react";
import { FlatList, Pressable, View } from "react-native";
import { router } from "expo-router";
import { Text } from "@/components/ui/text";
import { TextField } from "@/components/ui/text-field";
import { Button } from "@/components/ui/button";
import { useGraphDraftStore } from "@/data/stores/graph-draft-store";
export default function GraphPicker() {
const picker = useGraphDraftStore((s) => s.picker),
[query, setQuery] = useState("");
if (!picker)
return (
<View className="flex-1 bg-background p-4">
<Text>This selection has expired.</Text>
<Button onPress={() => router.back()}>
<Text>Back</Text>
</Button>
</View>
);
const items = picker.options.filter((o) =>
(o.label + " " + (o.description ?? ""))
.toLowerCase()
.includes(query.toLowerCase()),
);
return (
<View className="flex-1 bg-background">
<View className="gap-3 border-b border-border p-4">
<Text className="text-lg font-semibold">{picker.title}</Text>
<TextField
accessibilityLabel="Search options"
placeholder="Search…"
value={query}
onChangeText={setQuery}
/>
</View>
<FlatList
data={items}
keyboardShouldPersistTaps="handled"
automaticallyAdjustKeyboardInsets
keyExtractor={(o) => o.value}
contentContainerStyle={{ paddingBottom: 40 }}
renderItem={({ item }) => (
<Pressable
className="gap-1 border-b border-border px-4 py-4 active:bg-secondary"
accessibilityRole="button"
accessibilityState={{ selected: item.value === picker.value }}
onPress={() => {
useGraphDraftStore
.getState()
.field(
picker.key,
picker.path,
Object.prototype.hasOwnProperty.call(item, "data")
? item.data
: item.value,
);
useGraphDraftStore.setState({ picker: undefined });
router.back();
}}
>
<Text>
{item.label}
{item.value === picker.value ? " ✓" : ""}
</Text>
{item.description && (
<Text className="text-xs text-muted-foreground">
{item.description}
</Text>
)}
</Pressable>
)}
ListEmptyComponent={
<Text className="p-6 text-muted-foreground">
No matching options.
</Text>
}
/>
</View>
);
}
6 changes: 6 additions & 0 deletions apps/mobile/app/(app)/[workspace]/graph/run/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useLocalSearchParams } from "expo-router";
import { GraphRunScreen } from "@/components/graphs/run-screen";
export default function GraphRun() {
const { id } = useLocalSearchParams<{ id: string }>();
return <GraphRunScreen id={id} />;
}
6 changes: 6 additions & 0 deletions apps/mobile/app/(app)/[workspace]/graph/runs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useLocalSearchParams } from "expo-router";
import { GraphListScreen } from "@/components/graphs/list-screen";
export default function GraphRuns() {
const { graph } = useLocalSearchParams<{ graph?: string }>();
return <GraphListScreen runs graphId={graph} />;
}
Loading
Loading