palmier 0.9.6 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -13
- package/dist/agents/agent.d.ts +0 -1
- package/dist/agents/agent.js +0 -1
- package/dist/agents/aider.d.ts +0 -1
- package/dist/agents/aider.js +0 -1
- package/dist/agents/claude.d.ts +0 -1
- package/dist/agents/claude.js +0 -1
- package/dist/agents/cline.d.ts +0 -1
- package/dist/agents/cline.js +0 -1
- package/dist/agents/codex.d.ts +0 -1
- package/dist/agents/codex.js +0 -1
- package/dist/agents/copilot.d.ts +0 -1
- package/dist/agents/copilot.js +0 -1
- package/dist/agents/cursor.d.ts +0 -1
- package/dist/agents/cursor.js +0 -1
- package/dist/agents/deepagents.d.ts +0 -1
- package/dist/agents/deepagents.js +0 -1
- package/dist/agents/droid.d.ts +0 -1
- package/dist/agents/droid.js +0 -1
- package/dist/agents/gemini.d.ts +0 -1
- package/dist/agents/gemini.js +0 -1
- package/dist/agents/goose.d.ts +0 -1
- package/dist/agents/goose.js +0 -1
- package/dist/agents/hermes.d.ts +0 -1
- package/dist/agents/hermes.js +0 -1
- package/dist/agents/kimi.d.ts +0 -1
- package/dist/agents/kimi.js +0 -1
- package/dist/agents/kiro.d.ts +0 -1
- package/dist/agents/kiro.js +0 -1
- package/dist/agents/openclaw.d.ts +0 -1
- package/dist/agents/openclaw.js +0 -1
- package/dist/agents/opencode.d.ts +0 -1
- package/dist/agents/opencode.js +0 -1
- package/dist/agents/qoder.d.ts +0 -1
- package/dist/agents/qoder.js +0 -1
- package/dist/agents/qwen.d.ts +0 -1
- package/dist/agents/qwen.js +0 -1
- package/dist/agents/shared-prompt.d.ts +0 -1
- package/dist/agents/shared-prompt.js +0 -1
- package/dist/client-store.d.ts +0 -1
- package/dist/client-store.js +0 -1
- package/dist/commands/clients.d.ts +0 -1
- package/dist/commands/clients.js +0 -1
- package/dist/commands/info.d.ts +0 -1
- package/dist/commands/info.js +0 -1
- package/dist/commands/init.d.ts +0 -1
- package/dist/commands/init.js +1 -2
- package/dist/commands/pair.d.ts +0 -1
- package/dist/commands/pair.js +0 -1
- package/dist/commands/restart.d.ts +0 -1
- package/dist/commands/restart.js +0 -1
- package/dist/commands/run.d.ts +0 -1
- package/dist/commands/run.js +19 -3
- package/dist/commands/serve.d.ts +0 -1
- package/dist/commands/serve.js +0 -1
- package/dist/commands/uninstall.d.ts +0 -1
- package/dist/commands/uninstall.js +0 -1
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/event-queues.d.ts +0 -1
- package/dist/event-queues.js +0 -1
- package/dist/events.d.ts +0 -1
- package/dist/events.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/linked-device.d.ts +0 -1
- package/dist/linked-device.js +0 -1
- package/dist/mcp-handler.d.ts +0 -1
- package/dist/mcp-handler.js +0 -1
- package/dist/mcp-tools.d.ts +0 -1
- package/dist/mcp-tools.js +0 -1
- package/dist/nats-client.d.ts +0 -1
- package/dist/nats-client.js +0 -1
- package/dist/network.d.ts +0 -1
- package/dist/network.js +0 -1
- package/dist/notification-store.d.ts +0 -1
- package/dist/notification-store.js +0 -1
- package/dist/pending-requests.d.ts +0 -1
- package/dist/pending-requests.js +0 -1
- package/dist/platform/index.d.ts +0 -1
- package/dist/platform/index.js +0 -1
- package/dist/platform/linux.d.ts +0 -1
- package/dist/platform/linux.js +0 -1
- package/dist/platform/macos.d.ts +0 -1
- package/dist/platform/macos.js +0 -1
- package/dist/platform/platform.d.ts +0 -1
- package/dist/platform/platform.js +0 -1
- package/dist/platform/windows.d.ts +0 -1
- package/dist/platform/windows.js +0 -1
- package/dist/pwa/assets/{index-MLEFUP3r.js → index-DWvRAUiy.js} +31 -31
- package/dist/pwa/assets/{web-B1sKCc7e.js → web-C4iZbqTC.js} +1 -1
- package/dist/pwa/assets/{web-ETD-8ZHd.js → web-CBFqJGX6.js} +1 -1
- package/dist/pwa/assets/{web-B4xEa6WO.js → web-DL4uXOpS.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/rpc-handler.d.ts +0 -1
- package/dist/rpc-handler.js +0 -1
- package/dist/sms-store.d.ts +0 -1
- package/dist/sms-store.js +0 -1
- package/dist/spawn-command.d.ts +0 -1
- package/dist/spawn-command.js +0 -1
- package/dist/task.d.ts +0 -1
- package/dist/task.js +0 -1
- package/dist/transports/http-transport.d.ts +0 -1
- package/dist/transports/http-transport.js +0 -1
- package/dist/transports/nats-transport.d.ts +0 -1
- package/dist/transports/nats-transport.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/dist/update-checker.d.ts +0 -1
- package/dist/update-checker.js +0 -1
- package/package.json +11 -1
- package/.github/workflows/ci.yml +0 -16
- package/.github/workflows/publish.yml +0 -37
- package/CLAUDE.md +0 -22
- package/dist/pwa/apple-touch-icon.png +0 -0
- package/dist/pwa/manifest.webmanifest +0 -1
- package/dist/pwa/pwa-192x192.png +0 -0
- package/dist/pwa/pwa-512x512.png +0 -0
- package/dist/pwa/registerSW.js +0 -1
- package/dist/pwa/service-worker.js +0 -2
- package/palmier-server/.github/workflows/ci.yml +0 -21
- package/palmier-server/.github/workflows/deploy.yml +0 -38
- package/palmier-server/CLAUDE.md +0 -17
- package/palmier-server/PRODUCTION.md +0 -358
- package/palmier-server/README.md +0 -231
- package/palmier-server/nats.conf +0 -19
- package/palmier-server/package.json +0 -15
- package/palmier-server/pnpm-lock.yaml +0 -7639
- package/palmier-server/pnpm-workspace.yaml +0 -3
- package/palmier-server/pwa/index.html +0 -16
- package/palmier-server/pwa/logo/logo_20260421.png +0 -0
- package/palmier-server/pwa/package.json +0 -34
- package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
- package/palmier-server/pwa/public/favicon.ico +0 -0
- package/palmier-server/pwa/public/pwa-192x192.png +0 -0
- package/palmier-server/pwa/public/pwa-512x512.png +0 -0
- package/palmier-server/pwa/src/App.css +0 -3012
- package/palmier-server/pwa/src/App.tsx +0 -59
- package/palmier-server/pwa/src/agentLabels.ts +0 -11
- package/palmier-server/pwa/src/api.ts +0 -67
- package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
- package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -113
- package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
- package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
- package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
- package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
- package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
- package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
- package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
- package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
- package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
- package/palmier-server/pwa/src/components/TaskForm.tsx +0 -766
- package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
- package/palmier-server/pwa/src/constants.ts +0 -2
- package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
- package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
- package/palmier-server/pwa/src/draftGuard.ts +0 -24
- package/palmier-server/pwa/src/formatTime.ts +0 -44
- package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
- package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
- package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
- package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
- package/palmier-server/pwa/src/main.tsx +0 -14
- package/palmier-server/pwa/src/native/Device.ts +0 -49
- package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
- package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
- package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
- package/palmier-server/pwa/src/service-worker.ts +0 -142
- package/palmier-server/pwa/src/types.ts +0 -75
- package/palmier-server/pwa/src/vite-env.d.ts +0 -11
- package/palmier-server/pwa/tsconfig.json +0 -21
- package/palmier-server/pwa/tsconfig.node.json +0 -19
- package/palmier-server/pwa/vite.config.ts +0 -47
- package/palmier-server/server/.env.example +0 -20
- package/palmier-server/server/package.json +0 -36
- package/palmier-server/server/src/db.ts +0 -44
- package/palmier-server/server/src/fcm.ts +0 -74
- package/palmier-server/server/src/index.ts +0 -688
- package/palmier-server/server/src/nats-jwt.ts +0 -299
- package/palmier-server/server/src/nats-setup.ts +0 -48
- package/palmier-server/server/src/nats.ts +0 -33
- package/palmier-server/server/src/notify.ts +0 -34
- package/palmier-server/server/src/push.ts +0 -68
- package/palmier-server/server/src/routes/device.ts +0 -224
- package/palmier-server/server/src/routes/fcm.ts +0 -64
- package/palmier-server/server/src/routes/hosts.ts +0 -56
- package/palmier-server/server/src/routes/push.ts +0 -101
- package/palmier-server/server/tsconfig.json +0 -20
- package/palmier-server/spec.md +0 -533
- package/src/agents/agent-instructions.md +0 -28
- package/src/agents/agent.ts +0 -114
- package/src/agents/aider.ts +0 -35
- package/src/agents/claude.ts +0 -39
- package/src/agents/cline.ts +0 -35
- package/src/agents/codex.ts +0 -40
- package/src/agents/copilot.ts +0 -37
- package/src/agents/cursor.ts +0 -36
- package/src/agents/deepagents.ts +0 -36
- package/src/agents/droid.ts +0 -35
- package/src/agents/gemini.ts +0 -43
- package/src/agents/goose.ts +0 -33
- package/src/agents/hermes.ts +0 -36
- package/src/agents/kimi.ts +0 -35
- package/src/agents/kiro.ts +0 -36
- package/src/agents/openclaw.ts +0 -29
- package/src/agents/opencode.ts +0 -36
- package/src/agents/qoder.ts +0 -36
- package/src/agents/qwen.ts +0 -32
- package/src/agents/shared-prompt.ts +0 -30
- package/src/client-store.ts +0 -68
- package/src/commands/clients.ts +0 -29
- package/src/commands/info.ts +0 -29
- package/src/commands/init.ts +0 -165
- package/src/commands/pair.ts +0 -137
- package/src/commands/restart.ts +0 -6
- package/src/commands/run.ts +0 -608
- package/src/commands/serve.ts +0 -211
- package/src/commands/uninstall.ts +0 -9
- package/src/config.ts +0 -36
- package/src/cross-spawn.d.ts +0 -5
- package/src/event-queues.ts +0 -41
- package/src/events.ts +0 -29
- package/src/index.ts +0 -111
- package/src/linked-device.ts +0 -52
- package/src/mcp-handler.ts +0 -200
- package/src/mcp-tools.ts +0 -839
- package/src/nats-client.ts +0 -19
- package/src/network.ts +0 -96
- package/src/notification-store.ts +0 -30
- package/src/pending-requests.ts +0 -73
- package/src/platform/index.ts +0 -20
- package/src/platform/linux.ts +0 -296
- package/src/platform/macos.ts +0 -329
- package/src/platform/platform.ts +0 -31
- package/src/platform/windows.ts +0 -299
- package/src/rpc-handler.ts +0 -691
- package/src/sms-store.ts +0 -28
- package/src/spawn-command.ts +0 -123
- package/src/task.ts +0 -343
- package/src/transports/http-transport.ts +0 -478
- package/src/transports/nats-transport.ts +0 -76
- package/src/types.ts +0 -89
- package/src/update-checker.ts +0 -40
- package/test/agent-instructions.test.ts +0 -209
- package/test/agent-output-parsing.test.ts +0 -74
- package/test/linux-cron.test.ts +0 -41
- package/test/macos-plist.test.ts +0 -112
- package/test/notification-store.test.ts +0 -57
- package/test/pairing.test.ts +0 -35
- package/test/result-state.test.ts +0 -110
- package/test/task-parsing.test.ts +0 -82
- package/test/taskrun-messages.test.ts +0 -224
- package/test/tsconfig.json +0 -9
- package/test/windows-xml.test.ts +0 -89
- package/tsconfig.json +0 -19
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from "react";
|
|
2
|
-
import { useNavigate } from "react-router-dom";
|
|
3
|
-
import Markdown from "react-markdown";
|
|
4
|
-
import remarkGfm from "remark-gfm";
|
|
5
|
-
import remarkBreaks from "remark-breaks";
|
|
6
|
-
import { getAgentLabel } from "../agentLabels";
|
|
7
|
-
import { useFormatTime } from "../formatTime";
|
|
8
|
-
import { useBackClose } from "../hooks/useBackClose";
|
|
9
|
-
import type { ConversationMessage } from "../types";
|
|
10
|
-
|
|
11
|
-
interface RunDetailViewProps {
|
|
12
|
-
connected: boolean;
|
|
13
|
-
hostId: string | null;
|
|
14
|
-
request<T = unknown>(method: string, params?: unknown, opts?: { timeout?: number }): Promise<T>;
|
|
15
|
-
subscribeEvents(hostId: string, callback: (msg: { subject: string; data: Uint8Array }) => void): () => void;
|
|
16
|
-
taskId: string;
|
|
17
|
-
runId: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default function RunDetailView({ connected, hostId, request, subscribeEvents, taskId, runId }: RunDetailViewProps) {
|
|
21
|
-
const navigate = useNavigate();
|
|
22
|
-
const formatTime = useFormatTime();
|
|
23
|
-
const [loading, setLoading] = useState(true);
|
|
24
|
-
const [messages, setMessages] = useState<ConversationMessage[]>([]);
|
|
25
|
-
const [runState, setRunState] = useState<string | undefined>();
|
|
26
|
-
const [agent, setAgent] = useState<string | undefined>();
|
|
27
|
-
const isTaskRunning = runState === "started" || runState === "monitoring";
|
|
28
|
-
const isFollowupRunning = runState === "followup";
|
|
29
|
-
const isAgentGenerating = runState === "started" || runState === "followup";
|
|
30
|
-
const [reportDialog, setReportDialog] = useState<{ file: string; content?: string; data_url?: string } | null>(null);
|
|
31
|
-
useBackClose(reportDialog !== null, () => setReportDialog(null));
|
|
32
|
-
const [aborting, setAborting] = useState(false);
|
|
33
|
-
const [followupText, setFollowupText] = useState("");
|
|
34
|
-
const [sendingFollowup, setSendingFollowup] = useState(false);
|
|
35
|
-
const threadRef = useRef<HTMLDivElement>(null);
|
|
36
|
-
const followupInputRef = useRef<HTMLInputElement>(null);
|
|
37
|
-
// Tracks which runId we've already scrolled-and-focused for, so new messages
|
|
38
|
-
// during the session don't steal focus back to the input.
|
|
39
|
-
const initialFocusForRunId = useRef<string | null>(null);
|
|
40
|
-
|
|
41
|
-
// Resolve the "latest" sentinel to an actual run_id. `undefined` = still
|
|
42
|
-
// resolving, `null` = task has no runs yet (empty state), otherwise the id
|
|
43
|
-
// to load.
|
|
44
|
-
const [resolvedRunId, setResolvedRunId] = useState<string | null | undefined>(
|
|
45
|
-
runId === "latest" ? undefined : runId,
|
|
46
|
-
);
|
|
47
|
-
const isLatestEmpty = runId === "latest" && resolvedRunId === null;
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
if (runId !== "latest") {
|
|
51
|
-
setResolvedRunId(runId);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (!connected) return;
|
|
55
|
-
setResolvedRunId(undefined);
|
|
56
|
-
request<{ entries?: Array<{ run_id: string }> }>("taskrun.list", { task_id: taskId, limit: 1 })
|
|
57
|
-
.then((result) => setResolvedRunId(result.entries?.[0]?.run_id ?? null))
|
|
58
|
-
.catch(() => setResolvedRunId(null));
|
|
59
|
-
}, [runId, taskId, connected, request]);
|
|
60
|
-
|
|
61
|
-
async function fetchData() {
|
|
62
|
-
if (!resolvedRunId) return;
|
|
63
|
-
try {
|
|
64
|
-
const result = await request<{
|
|
65
|
-
messages?: ConversationMessage[];
|
|
66
|
-
start_time?: number;
|
|
67
|
-
end_time?: number;
|
|
68
|
-
running_state?: string;
|
|
69
|
-
agent?: string;
|
|
70
|
-
error?: string;
|
|
71
|
-
}>("task.result", { id: taskId, run_id: resolvedRunId });
|
|
72
|
-
|
|
73
|
-
if (result.error) {
|
|
74
|
-
console.error("No result:", result.error);
|
|
75
|
-
navigate(hostId ? `/hosts/${encodeURIComponent(hostId)}` : "/", { replace: true });
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
setMessages(result.messages ?? []);
|
|
79
|
-
setRunState(result.running_state);
|
|
80
|
-
setAgent(result.agent);
|
|
81
|
-
} catch (err) {
|
|
82
|
-
console.error("Failed to load result:", err);
|
|
83
|
-
navigate(hostId ? `/hosts/${encodeURIComponent(hostId)}` : "/", { replace: true });
|
|
84
|
-
} finally {
|
|
85
|
-
setLoading(false);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function openReport(file: string) {
|
|
90
|
-
if (!resolvedRunId) return;
|
|
91
|
-
try {
|
|
92
|
-
const result = await request<{ reports: Array<{ file: string; content?: string; data_url?: string }> }>(
|
|
93
|
-
"task.reports", { id: taskId, run_id: resolvedRunId, report_files: [file] },
|
|
94
|
-
);
|
|
95
|
-
const report = result.reports?.[0];
|
|
96
|
-
if (report?.data_url) {
|
|
97
|
-
setReportDialog({ file, data_url: report.data_url });
|
|
98
|
-
} else {
|
|
99
|
-
setReportDialog({ file, content: report?.content ?? "Report not found." });
|
|
100
|
-
}
|
|
101
|
-
} catch {
|
|
102
|
-
setReportDialog({ file, content: "Failed to load report." });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Initial load once resolvedRunId becomes a concrete id.
|
|
107
|
-
useEffect(() => {
|
|
108
|
-
if (!connected || !resolvedRunId) return;
|
|
109
|
-
setLoading(true);
|
|
110
|
-
fetchData();
|
|
111
|
-
}, [connected, taskId, resolvedRunId]);
|
|
112
|
-
|
|
113
|
-
// Live-update when the viewed task's state changes
|
|
114
|
-
useEffect(() => {
|
|
115
|
-
if (!connected || !hostId || !resolvedRunId) return;
|
|
116
|
-
const unsubscribe = subscribeEvents(hostId, async (msg) => {
|
|
117
|
-
try {
|
|
118
|
-
const parsed = JSON.parse(new TextDecoder().decode(msg.data)) as { event_type?: string; run_id?: string };
|
|
119
|
-
if (parsed.event_type !== "running-state" && parsed.event_type !== "result-updated") return;
|
|
120
|
-
const eventTaskId = msg.subject.split(".").pop();
|
|
121
|
-
if (eventTaskId !== taskId) return;
|
|
122
|
-
if (parsed.event_type === "result-updated" && parsed.run_id && parsed.run_id !== resolvedRunId) return;
|
|
123
|
-
fetchData();
|
|
124
|
-
} catch { /* skip */ }
|
|
125
|
-
});
|
|
126
|
-
return unsubscribe;
|
|
127
|
-
}, [connected, hostId, taskId, resolvedRunId, subscribeEvents, request]);
|
|
128
|
-
|
|
129
|
-
// Auto-scroll to bottom when messages change
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if (threadRef.current) {
|
|
132
|
-
threadRef.current.scrollTop = threadRef.current.scrollHeight;
|
|
133
|
-
}
|
|
134
|
-
}, [messages]);
|
|
135
|
-
|
|
136
|
-
// On first load of a run, scroll the window to the bottom so the follow-up
|
|
137
|
-
// input is visible. Deliberately not focusing the input — on mobile that
|
|
138
|
-
// would pop the soft keyboard as soon as the run opens.
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
if (loading || isLatestEmpty || !resolvedRunId) return;
|
|
141
|
-
if (initialFocusForRunId.current === resolvedRunId) return;
|
|
142
|
-
initialFocusForRunId.current = resolvedRunId;
|
|
143
|
-
requestAnimationFrame(() => {
|
|
144
|
-
if (threadRef.current) threadRef.current.scrollTop = threadRef.current.scrollHeight;
|
|
145
|
-
window.scrollTo({ top: document.documentElement.scrollHeight, behavior: "auto" });
|
|
146
|
-
});
|
|
147
|
-
}, [loading, isLatestEmpty, resolvedRunId]);
|
|
148
|
-
|
|
149
|
-
function typeLabel(type?: string): string | undefined {
|
|
150
|
-
if (type === "input") return "User Input";
|
|
151
|
-
if (type === "permission") return "Permission";
|
|
152
|
-
if (type === "confirmation") return "Confirmation";
|
|
153
|
-
return undefined;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function statusLabel(type?: string): string {
|
|
157
|
-
if (type === "started") return "Task started";
|
|
158
|
-
if (type === "finished") return "Task finished";
|
|
159
|
-
if (type === "failed") return "Task failed";
|
|
160
|
-
if (type === "error") return "Command failed";
|
|
161
|
-
if (type === "aborted") return "Task aborted";
|
|
162
|
-
if (type === "confirmation") return "Task confirmed";
|
|
163
|
-
if (type === "stopped") return "Follow-up stopped";
|
|
164
|
-
return type ?? "";
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return (
|
|
168
|
-
<div className="run-detail">
|
|
169
|
-
<button className="run-detail-back" onClick={() => navigate(-1)}>
|
|
170
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
171
|
-
<path d="M15 18l-6-6 6-6" />
|
|
172
|
-
</svg>
|
|
173
|
-
Back
|
|
174
|
-
</button>
|
|
175
|
-
|
|
176
|
-
{isLatestEmpty ? (
|
|
177
|
-
<div className="empty-state">
|
|
178
|
-
<p className="empty-state-text">No runs yet</p>
|
|
179
|
-
<p className="empty-state-hint">This task hasn't been executed yet. Run it from the task menu or wait for its next trigger.</p>
|
|
180
|
-
</div>
|
|
181
|
-
) : loading ? (
|
|
182
|
-
<div style={{ display: "flex", flexDirection: "column", gap: "var(--space-sm)", padding: "var(--space-sm) 0" }}>
|
|
183
|
-
<div className="skeleton-line" style={{ width: "40%" }} />
|
|
184
|
-
<div className="skeleton-line" style={{ width: "55%" }} />
|
|
185
|
-
<div className="skeleton-line" style={{ width: "100%", height: "8rem", marginTop: "var(--space-sm)" }} />
|
|
186
|
-
</div>
|
|
187
|
-
) : (
|
|
188
|
-
<>
|
|
189
|
-
<div className="chat-thread" ref={threadRef}>
|
|
190
|
-
{messages.map((msg, i) => {
|
|
191
|
-
const isLastAssistant = isAgentGenerating && msg.role === "assistant" && !messages.slice(i + 1).some((m) => m.role === "assistant" || m.role === "user");
|
|
192
|
-
if (msg.role === "status" && msg.type === "monitoring") return null;
|
|
193
|
-
return msg.role === "status" ? (
|
|
194
|
-
<div key={i} className={`chat-status${msg.type === "error" ? " chat-status--error" : ""}`}>
|
|
195
|
-
<div>
|
|
196
|
-
{statusLabel(msg.type)}
|
|
197
|
-
{msg.time > 0 && <span className="chat-status-time">{formatTime(msg.time)}</span>}
|
|
198
|
-
</div>
|
|
199
|
-
{msg.content && <pre className="chat-status-detail">{msg.content}</pre>}
|
|
200
|
-
</div>
|
|
201
|
-
) : (
|
|
202
|
-
<div key={i} className={`chat-message chat-message--${msg.role}`}>
|
|
203
|
-
{msg.role === "assistant" && agent && <div className="chat-message-agent">{getAgentLabel(agent)}</div>}
|
|
204
|
-
<div className="chat-message-content">
|
|
205
|
-
<Markdown remarkPlugins={[remarkGfm, remarkBreaks]}>{msg.content}</Markdown>
|
|
206
|
-
{isLastAssistant && (
|
|
207
|
-
<div className="chat-typing-indicator">
|
|
208
|
-
<span /><span /><span />
|
|
209
|
-
</div>
|
|
210
|
-
)}
|
|
211
|
-
</div>
|
|
212
|
-
{msg.attachments && msg.attachments.length > 0 && (
|
|
213
|
-
<div className="chat-message-attachments">
|
|
214
|
-
{msg.attachments.map((file) => (
|
|
215
|
-
<button key={file} className="chat-attachment-chip" onClick={() => openReport(file)}>
|
|
216
|
-
{file}
|
|
217
|
-
</button>
|
|
218
|
-
))}
|
|
219
|
-
</div>
|
|
220
|
-
)}
|
|
221
|
-
<div className="chat-message-meta">
|
|
222
|
-
{typeLabel(msg.type) && <span className="chat-message-type">{typeLabel(msg.type)}</span>}
|
|
223
|
-
{msg.time > 0 && <span>{formatTime(msg.time)}</span>}
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
);
|
|
227
|
-
})}
|
|
228
|
-
{isAgentGenerating && (() => { const nonStatus = messages.filter((m) => m.role !== "status"); return nonStatus.length === 0 || nonStatus[nonStatus.length - 1].role !== "assistant"; })() && (
|
|
229
|
-
<div className="chat-message chat-message--assistant">
|
|
230
|
-
{agent && <div className="chat-message-agent">{getAgentLabel(agent)}</div>}
|
|
231
|
-
<div className="chat-typing-indicator">
|
|
232
|
-
<span /><span /><span />
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
)}
|
|
236
|
-
{runState === "monitoring" && (
|
|
237
|
-
<div className="chat-monitoring-indicator">
|
|
238
|
-
<span className="chat-monitoring-dot" />
|
|
239
|
-
Monitoring command output
|
|
240
|
-
</div>
|
|
241
|
-
)}
|
|
242
|
-
</div>
|
|
243
|
-
{isTaskRunning ? (
|
|
244
|
-
<div className="chat-abort-bar">
|
|
245
|
-
<button
|
|
246
|
-
className="btn btn-secondary chat-abort-btn"
|
|
247
|
-
disabled={aborting}
|
|
248
|
-
onClick={async () => {
|
|
249
|
-
if (!confirm("Abort this task?")) return;
|
|
250
|
-
setAborting(true);
|
|
251
|
-
try {
|
|
252
|
-
await request("task.abort", { id: taskId });
|
|
253
|
-
} catch (err) {
|
|
254
|
-
console.error("Abort failed:", err);
|
|
255
|
-
} finally {
|
|
256
|
-
setAborting(false);
|
|
257
|
-
}
|
|
258
|
-
}}
|
|
259
|
-
>
|
|
260
|
-
{aborting ? "Aborting..." : "Abort Task"}
|
|
261
|
-
</button>
|
|
262
|
-
</div>
|
|
263
|
-
) : isFollowupRunning ? (
|
|
264
|
-
<div className="chat-input-bar">
|
|
265
|
-
<button
|
|
266
|
-
className="btn btn-secondary chat-stop-btn"
|
|
267
|
-
disabled={aborting}
|
|
268
|
-
onClick={async () => {
|
|
269
|
-
setAborting(true);
|
|
270
|
-
try {
|
|
271
|
-
await request("task.stop_followup", { id: taskId, run_id: resolvedRunId });
|
|
272
|
-
} catch (err) {
|
|
273
|
-
console.error("Stop failed:", err);
|
|
274
|
-
} finally {
|
|
275
|
-
setAborting(false);
|
|
276
|
-
}
|
|
277
|
-
}}
|
|
278
|
-
>
|
|
279
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2" /></svg>
|
|
280
|
-
</button>
|
|
281
|
-
</div>
|
|
282
|
-
) : (
|
|
283
|
-
<form
|
|
284
|
-
className="chat-input-bar"
|
|
285
|
-
onSubmit={async (e) => {
|
|
286
|
-
e.preventDefault();
|
|
287
|
-
const msg = followupText.trim();
|
|
288
|
-
if (!msg || sendingFollowup) return;
|
|
289
|
-
setSendingFollowup(true);
|
|
290
|
-
try {
|
|
291
|
-
await request("task.followup", { id: taskId, run_id: resolvedRunId, message: msg });
|
|
292
|
-
setFollowupText("");
|
|
293
|
-
} catch (err) {
|
|
294
|
-
console.error("Follow-up failed:", err);
|
|
295
|
-
} finally {
|
|
296
|
-
setSendingFollowup(false);
|
|
297
|
-
}
|
|
298
|
-
}}
|
|
299
|
-
>
|
|
300
|
-
<input
|
|
301
|
-
ref={followupInputRef}
|
|
302
|
-
className="chat-input"
|
|
303
|
-
type="text"
|
|
304
|
-
placeholder="Follow-up message"
|
|
305
|
-
value={followupText}
|
|
306
|
-
onChange={(e) => setFollowupText(e.target.value)}
|
|
307
|
-
disabled={sendingFollowup}
|
|
308
|
-
/>
|
|
309
|
-
<button
|
|
310
|
-
className="btn btn-primary chat-send-btn"
|
|
311
|
-
type="submit"
|
|
312
|
-
disabled={!followupText.trim() || sendingFollowup}
|
|
313
|
-
>
|
|
314
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="22" y1="2" x2="11" y2="13" /><polygon points="22 2 15 22 11 13 2 9 22 2" /></svg>
|
|
315
|
-
</button>
|
|
316
|
-
</form>
|
|
317
|
-
)}
|
|
318
|
-
</>
|
|
319
|
-
)}
|
|
320
|
-
{reportDialog && (
|
|
321
|
-
<div className="report-dialog-overlay" onClick={() => setReportDialog(null)}>
|
|
322
|
-
<div className="report-dialog" onClick={(e) => e.stopPropagation()}>
|
|
323
|
-
<div className="report-dialog-header">
|
|
324
|
-
<span className="report-dialog-title">{reportDialog.file}</span>
|
|
325
|
-
<button className="report-dialog-close" onClick={() => setReportDialog(null)} aria-label="Close">
|
|
326
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
327
|
-
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
|
|
328
|
-
</svg>
|
|
329
|
-
</button>
|
|
330
|
-
</div>
|
|
331
|
-
<div className="report-dialog-body">
|
|
332
|
-
{reportDialog.data_url ? (
|
|
333
|
-
<img src={reportDialog.data_url} alt={reportDialog.file} style={{ maxWidth: "100%", height: "auto" }} />
|
|
334
|
-
) : (
|
|
335
|
-
<Markdown remarkPlugins={[remarkGfm, remarkBreaks]} components={{ a: ({ ...props }) => <a {...props} target="_blank" rel="noopener noreferrer" /> }}>{reportDialog.content ?? ""}</Markdown>
|
|
336
|
-
)}
|
|
337
|
-
</div>
|
|
338
|
-
</div>
|
|
339
|
-
</div>
|
|
340
|
-
)}
|
|
341
|
-
</div>
|
|
342
|
-
);
|
|
343
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { useHostConnection } from "../contexts/HostConnectionContext";
|
|
3
|
-
import { useHostStore } from "../contexts/HostStoreContext";
|
|
4
|
-
import { setDraftMessage } from "../draftGuard";
|
|
5
|
-
import type { AgentInfo } from "../types";
|
|
6
|
-
|
|
7
|
-
interface SessionComposerProps {
|
|
8
|
-
agents: AgentInfo[];
|
|
9
|
-
hostPlatform?: string;
|
|
10
|
-
onStarted(taskId: string, runId?: string): void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function pickDefaultAgent(agents: AgentInfo[], preferred?: string): string {
|
|
14
|
-
const keys = agents.map((a) => a.key);
|
|
15
|
-
if (preferred && keys.includes(preferred)) return preferred;
|
|
16
|
-
return agents[0]?.key ?? "";
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export default function SessionComposer({ agents, hostPlatform, onStarted }: SessionComposerProps) {
|
|
20
|
-
const { request, activeHost } = useHostConnection();
|
|
21
|
-
const { setHostLastAgent } = useHostStore();
|
|
22
|
-
const [prompt, setPrompt] = useState("");
|
|
23
|
-
const [agent, setAgent] = useState(() => pickDefaultAgent(agents, activeHost.lastAgent));
|
|
24
|
-
const [yoloMode, setYoloMode] = useState(false);
|
|
25
|
-
const [running, setRunning] = useState(false);
|
|
26
|
-
const [error, setError] = useState<string | null>(null);
|
|
27
|
-
|
|
28
|
-
// Keep agent selection valid as the agent list arrives/changes.
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (!agents.length) return;
|
|
31
|
-
if (!agents.find((a) => a.key === agent)) {
|
|
32
|
-
setAgent(pickDefaultAgent(agents, activeHost.lastAgent));
|
|
33
|
-
}
|
|
34
|
-
}, [agents, agent, activeHost.lastAgent]);
|
|
35
|
-
|
|
36
|
-
// Draft guard: warns on navigation / reload when the input has content.
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
const hasDraft = prompt.trim().length > 0;
|
|
39
|
-
setDraftMessage(hasDraft ? "Your session draft will be lost. Continue?" : null);
|
|
40
|
-
return () => setDraftMessage(null);
|
|
41
|
-
}, [prompt]);
|
|
42
|
-
|
|
43
|
-
const selectedAgent = agents.find((a) => a.key === agent);
|
|
44
|
-
const agentSupportsYolo = !!selectedAgent?.supportsYolo;
|
|
45
|
-
|
|
46
|
-
// Force-disable yolo when the selected agent doesn't support it.
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
if (!agentSupportsYolo && yoloMode) setYoloMode(false);
|
|
49
|
-
}, [agentSupportsYolo, yoloMode]);
|
|
50
|
-
|
|
51
|
-
const canRun = !!prompt.trim() && !!agent && !running;
|
|
52
|
-
|
|
53
|
-
function confirmYolo(): boolean {
|
|
54
|
-
if (!yoloMode) return true;
|
|
55
|
-
return confirm(
|
|
56
|
-
"Yolo mode is enabled. The agent will auto-approve all tool calls \u2014 it can read, write, delete files, run arbitrary commands, and access the network without asking for permission.\n\nAre you sure you want to continue?"
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function handleRun() {
|
|
61
|
-
if (!canRun || !confirmYolo()) return;
|
|
62
|
-
setRunning(true);
|
|
63
|
-
setError(null);
|
|
64
|
-
try {
|
|
65
|
-
const result = await request<{ task_id?: string; run_id?: string; error?: string }>(
|
|
66
|
-
"task.run_oneoff",
|
|
67
|
-
{
|
|
68
|
-
user_prompt: prompt,
|
|
69
|
-
agent,
|
|
70
|
-
yolo_mode: yoloMode,
|
|
71
|
-
// Direct runs on Windows need a visible session so interactive tools
|
|
72
|
-
// (browsers, GUI apps) can attach; background task-scheduler runs
|
|
73
|
-
// would otherwise land in session 0 with no display.
|
|
74
|
-
...(hostPlatform === "win32" ? { foreground_mode: true } : {}),
|
|
75
|
-
},
|
|
76
|
-
);
|
|
77
|
-
if (result.error) {
|
|
78
|
-
setError(result.error);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
setHostLastAgent(activeHost.hostId, agent);
|
|
82
|
-
setPrompt("");
|
|
83
|
-
setDraftMessage(null);
|
|
84
|
-
if (result.task_id) onStarted(result.task_id, result.run_id);
|
|
85
|
-
} catch (err) {
|
|
86
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
87
|
-
} finally {
|
|
88
|
-
setRunning(false);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
|
|
93
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
94
|
-
e.preventDefault();
|
|
95
|
-
handleRun();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<div className="session-composer">
|
|
101
|
-
{error && <div className="form-error">{error}</div>}
|
|
102
|
-
<textarea
|
|
103
|
-
className="session-composer-textarea"
|
|
104
|
-
value={prompt}
|
|
105
|
-
onChange={(e) => setPrompt(e.target.value)}
|
|
106
|
-
onKeyDown={handleKeyDown}
|
|
107
|
-
placeholder="What can I do for you?"
|
|
108
|
-
rows={3}
|
|
109
|
-
disabled={running}
|
|
110
|
-
/>
|
|
111
|
-
<div className="session-composer-controls">
|
|
112
|
-
<div className="agent-picker-section-inline">
|
|
113
|
-
<span className="agent-picker-label">Run with</span>
|
|
114
|
-
<select
|
|
115
|
-
className="form-select form-select-sm"
|
|
116
|
-
value={agent}
|
|
117
|
-
onChange={(e) => setAgent(e.target.value)}
|
|
118
|
-
disabled={running || !agents.length}
|
|
119
|
-
>
|
|
120
|
-
{agents.map((a) => (
|
|
121
|
-
<option key={a.key} value={a.key}>{a.label}</option>
|
|
122
|
-
))}
|
|
123
|
-
</select>
|
|
124
|
-
</div>
|
|
125
|
-
{agentSupportsYolo && (
|
|
126
|
-
<label className="session-composer-yolo">
|
|
127
|
-
<input
|
|
128
|
-
type="checkbox"
|
|
129
|
-
checked={yoloMode}
|
|
130
|
-
onChange={(e) => setYoloMode(e.target.checked)}
|
|
131
|
-
disabled={running}
|
|
132
|
-
/>
|
|
133
|
-
Yolo
|
|
134
|
-
</label>
|
|
135
|
-
)}
|
|
136
|
-
<button
|
|
137
|
-
className="btn btn-primary chat-send-btn"
|
|
138
|
-
onClick={handleRun}
|
|
139
|
-
disabled={!canRun}
|
|
140
|
-
aria-label="Run session"
|
|
141
|
-
title="Run session"
|
|
142
|
-
>
|
|
143
|
-
{running ? (
|
|
144
|
-
<span className="btn-spinner" />
|
|
145
|
-
) : (
|
|
146
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="22" y1="2" x2="11" y2="13" /><polygon points="22 2 15 22 11 13 2 9 22 2" /></svg>
|
|
147
|
-
)}
|
|
148
|
-
</button>
|
|
149
|
-
</div>
|
|
150
|
-
{agentSupportsYolo && yoloMode && (
|
|
151
|
-
<p className="command-help-text">
|
|
152
|
-
The agent will auto-approve all tool calls without asking for permission.
|
|
153
|
-
</p>
|
|
154
|
-
)}
|
|
155
|
-
</div>
|
|
156
|
-
);
|
|
157
|
-
}
|