feat: add task management CLI and improve activity curation

- Add task store with dependency tracking and CLI (packages/server/src/tasks/)
- Extract principal param logic to shared util for server/app reuse
- Improve activity curator: collapse timeline, dedupe tool calls, simpler output
- Add MessageInput ref for focus control after agent cancel
- Include recent activity in wait timeout messages

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Mohamed Boudra
2026-01-08 14:56:10 +07:00
parent 80886592ac
commit d233115ab9
17 changed files with 1779 additions and 130 deletions
@@ -26,6 +26,7 @@ import {
MessageInput,
type MessagePayload,
type ImageAttachment,
type MessageInputRef,
} from "./message-input";
import type { UseWebSocketReturn } from "@/hooks/use-websocket";
import { Theme } from "@/styles/theme";
@@ -132,6 +133,7 @@ export function AgentInputArea({
const agentIdRef = useRef(agentId);
const sendAgentMessageRef = useRef(sendAgentMessage);
const onSubmitMessageRef = useRef(onSubmitMessage);
const messageInputRef = useRef<MessageInputRef>(null);
// Expose addImages function to parent for drag-and-drop support
const addImages = useCallback((images: ImageAttachment[]) => {
@@ -338,6 +340,7 @@ export function AgentInputArea({
}
setIsCancellingAgent(true);
cancelAgentRun(agentIdRef.current);
messageInputRef.current?.focus();
}
function handleEditQueuedMessage(id: string) {
@@ -470,6 +473,7 @@ export function AgentInputArea({
{/* MessageInput handles everything: text, dictation, attachments, all buttons */}
<MessageInput
ref={messageInputRef}
value={userInput}
onChangeText={setUserInput}
onSubmit={handleSubmit}
+53 -31
View File
@@ -9,7 +9,14 @@ import {
Platform,
BackHandler,
} from "react-native";
import { useState, useRef, useCallback, useEffect } from "react";
import {
useState,
useRef,
useCallback,
useEffect,
useImperativeHandle,
forwardRef,
} from "react";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { Mic, ArrowUp, Paperclip, X, Square } from "lucide-react-native";
import Animated, {
@@ -58,6 +65,10 @@ export interface MessageInputProps {
onQueue?: (payload: MessagePayload) => void;
}
export interface MessageInputRef {
focus: () => void;
}
const MIN_INPUT_HEIGHT = 30;
const MAX_INPUT_HEIGHT = 160;
const IS_WEB = Platform.OS === "web";
@@ -78,35 +89,45 @@ type TextAreaHandle = {
} & Record<string, unknown>;
};
export function MessageInput({
value,
onChangeText,
onSubmit,
isSubmitDisabled = false,
isSubmitLoading = false,
images = [],
onPickImages,
onRemoveImage,
ws,
sendAgentAudio,
placeholder = "Message...",
autoFocus = false,
disabled = false,
leftContent,
rightContent,
isAgentRunning = false,
onQueue,
}: MessageInputProps) {
const { theme } = useUnistyles();
const [inputHeight, setInputHeight] = useState(MIN_INPUT_HEIGHT);
const textInputRef = useRef<
TextInput | (TextInput & { getNativeRef?: () => unknown }) | null
>(null);
const inputHeightRef = useRef(MIN_INPUT_HEIGHT);
const baselineInputHeightRef = useRef<number | null>(null);
const overlayTransition = useSharedValue(0);
const sendAfterTranscriptRef = useRef(false);
const valueRef = useRef(value);
export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
function MessageInput(
{
value,
onChangeText,
onSubmit,
isSubmitDisabled = false,
isSubmitLoading = false,
images = [],
onPickImages,
onRemoveImage,
ws,
sendAgentAudio,
placeholder = "Message...",
autoFocus = false,
disabled = false,
leftContent,
rightContent,
isAgentRunning = false,
onQueue,
},
ref
) {
const { theme } = useUnistyles();
const [inputHeight, setInputHeight] = useState(MIN_INPUT_HEIGHT);
const textInputRef = useRef<
TextInput | (TextInput & { getNativeRef?: () => unknown }) | null
>(null);
useImperativeHandle(ref, () => ({
focus: () => {
textInputRef.current?.focus();
},
}));
const inputHeightRef = useRef(MIN_INPUT_HEIGHT);
const baselineInputHeightRef = useRef<number | null>(null);
const overlayTransition = useSharedValue(0);
const sendAfterTranscriptRef = useRef(false);
const valueRef = useRef(value);
useEffect(() => {
valueRef.current = value;
@@ -566,7 +587,8 @@ export function MessageInput({
</Animated.View>
</View>
);
}
}
);
const styles = StyleSheet.create(((theme: any) => ({
container: {