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,542 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
2
|
-
import { createPortal } from "react-dom";
|
|
3
|
-
import { useNavigate, useLocation, useParams } from "react-router-dom";
|
|
4
|
-
|
|
5
|
-
import { useHostStore } from "../contexts/HostStoreContext";
|
|
6
|
-
import { useHostConnection } from "../contexts/HostConnectionContext";
|
|
7
|
-
import TasksView from "../components/TasksView";
|
|
8
|
-
import TabBar from "../components/TabBar";
|
|
9
|
-
import HostMenu from "../components/HostMenu";
|
|
10
|
-
import ConnectionStatusIcon from "../components/ConnectionStatusIcon";
|
|
11
|
-
import SessionsView from "../components/SessionsView";
|
|
12
|
-
import RunDetailView from "../components/RunDetailView";
|
|
13
|
-
import { loadEnabledCapabilities } from "../components/CapabilityToggles";
|
|
14
|
-
import { usePushSubscription } from "../hooks/usePushSubscription";
|
|
15
|
-
import { useMediaQuery } from "../hooks/useMediaQuery";
|
|
16
|
-
import { setAgentLabels } from "../agentLabels";
|
|
17
|
-
import { confirmLeaveDraft } from "../draftGuard";
|
|
18
|
-
import { MIN_HOST_VERSION } from "../constants";
|
|
19
|
-
import type { AgentInfo, RequiredPermission } from "../types";
|
|
20
|
-
|
|
21
|
-
function isOlderThan(current: string, minimum: string): boolean {
|
|
22
|
-
if (current.includes("-")) return false;
|
|
23
|
-
const a = current.split(".").map(Number);
|
|
24
|
-
const b = minimum.split(".").map(Number);
|
|
25
|
-
for (let i = 0; i < 3; i++) {
|
|
26
|
-
if ((a[i] ?? 0) < (b[i] ?? 0)) return true;
|
|
27
|
-
if ((a[i] ?? 0) > (b[i] ?? 0)) return false;
|
|
28
|
-
}
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface PendingPrompt {
|
|
33
|
-
key: string;
|
|
34
|
-
type: "confirmation" | "permission" | "input";
|
|
35
|
-
params?: RequiredPermission[] | string[];
|
|
36
|
-
meta?: {
|
|
37
|
-
session_id?: string;
|
|
38
|
-
session_name?: string;
|
|
39
|
-
description?: string;
|
|
40
|
-
input_questions?: string[];
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface ConfirmPrompt { description: string; sessionName?: string }
|
|
45
|
-
interface PermissionPrompt { permissions: RequiredPermission[]; sessionName?: string }
|
|
46
|
-
interface InputPrompt { questions: string[]; description?: string; sessionName?: string }
|
|
47
|
-
|
|
48
|
-
export default function Dashboard() {
|
|
49
|
-
const { removePairedHost, setHostLanUrl, setHostTimezone } = useHostStore();
|
|
50
|
-
const { connected, request, subscribeEvents, unauthorized, activeHost } = useHostConnection();
|
|
51
|
-
const hostId = activeHost.hostId;
|
|
52
|
-
const activeClientToken = activeHost.clientToken || null;
|
|
53
|
-
const navigate = useNavigate();
|
|
54
|
-
const location = useLocation();
|
|
55
|
-
const params = useParams<{ taskId?: string; runId?: string }>();
|
|
56
|
-
const isDesktop = useMediaQuery("(min-width: 768px)");
|
|
57
|
-
|
|
58
|
-
const isTasksTab = location.pathname.endsWith("/tasks");
|
|
59
|
-
const runsFilterTaskId = params.runId ? undefined : params.taskId;
|
|
60
|
-
|
|
61
|
-
// "latest" is passed through to RunDetailView, which does its own resolution
|
|
62
|
-
// and renders an empty state if the task has no runs.
|
|
63
|
-
const isRunDetail = !!(params.taskId && params.runId);
|
|
64
|
-
|
|
65
|
-
const [updateRequired, setUpdateRequired] = useState(false);
|
|
66
|
-
const [updating, setUpdating] = useState(false);
|
|
67
|
-
const [updateError, setUpdateError] = useState<string | null>(null);
|
|
68
|
-
const [daemonVersion, setDaemonVersion] = useState<string | null>(null);
|
|
69
|
-
const [linkedClientToken, setLinkedClientToken] = useState<string | null>(null);
|
|
70
|
-
const [enabledCapabilities, setEnabledCapabilities] = useState<Set<string>>(new Set());
|
|
71
|
-
const [agents, setAgents] = useState<AgentInfo[]>([]);
|
|
72
|
-
const [hostPlatform, setHostPlatform] = useState<string | undefined>();
|
|
73
|
-
|
|
74
|
-
const isLinkedDevice = !!activeClientToken && linkedClientToken === activeClientToken;
|
|
75
|
-
|
|
76
|
-
// Pending prompt state — owned by Dashboard because these modals must show
|
|
77
|
-
// regardless of which tab (Sessions/Tasks/RunDetail) is currently rendered.
|
|
78
|
-
const [pendingConfirms, setPendingConfirms] = useState<Map<string, ConfirmPrompt>>(new Map());
|
|
79
|
-
const [pendingPermissions, setPendingPermissions] = useState<Map<string, PermissionPrompt>>(new Map());
|
|
80
|
-
const [pendingInputs, setPendingInputs] = useState<Map<string, InputPrompt>>(new Map());
|
|
81
|
-
const [inputValues, setInputValues] = useState<Map<string, string[]>>(new Map());
|
|
82
|
-
|
|
83
|
-
usePushSubscription();
|
|
84
|
-
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
window.scrollTo(0, 0);
|
|
87
|
-
}, [hostId]);
|
|
88
|
-
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
let cancelled = false;
|
|
91
|
-
loadEnabledCapabilities().then((caps) => { if (!cancelled) setEnabledCapabilities(caps); });
|
|
92
|
-
return () => { cancelled = true; };
|
|
93
|
-
}, []);
|
|
94
|
-
|
|
95
|
-
// host.info bootstrap: agents/version/platform + any prompts that were already
|
|
96
|
-
// pending when this PWA connected. Runs once per (host, connection).
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
if (!connected) return;
|
|
99
|
-
request<{
|
|
100
|
-
agents?: AgentInfo[];
|
|
101
|
-
version?: string | null;
|
|
102
|
-
host_platform?: string;
|
|
103
|
-
host_timezone?: string;
|
|
104
|
-
linked_client_token?: string | null;
|
|
105
|
-
pending_prompts?: PendingPrompt[];
|
|
106
|
-
lan_url?: string | null;
|
|
107
|
-
}>("host.info")
|
|
108
|
-
.then((result) => {
|
|
109
|
-
setAgents(result.agents ?? []);
|
|
110
|
-
setHostPlatform(result.host_platform);
|
|
111
|
-
setLinkedClientToken(result.linked_client_token ?? null);
|
|
112
|
-
setAgentLabels(result.agents ?? []);
|
|
113
|
-
const version = result.version ?? null;
|
|
114
|
-
setDaemonVersion(version);
|
|
115
|
-
setUpdateRequired(!!version && isOlderThan(version, MIN_HOST_VERSION));
|
|
116
|
-
setHostLanUrl(hostId, result.lan_url ?? undefined);
|
|
117
|
-
setHostTimezone(hostId, result.host_timezone);
|
|
118
|
-
|
|
119
|
-
// Seed modal state from already-pending prompts.
|
|
120
|
-
const confirms = new Map<string, ConfirmPrompt>();
|
|
121
|
-
const perms = new Map<string, PermissionPrompt>();
|
|
122
|
-
const inputs = new Map<string, InputPrompt>();
|
|
123
|
-
const inputVals = new Map<string, string[]>();
|
|
124
|
-
for (const p of result.pending_prompts ?? []) {
|
|
125
|
-
if (p.type === "confirmation") {
|
|
126
|
-
confirms.set(p.key, {
|
|
127
|
-
description: p.meta?.description ?? "",
|
|
128
|
-
sessionName: p.meta?.session_name,
|
|
129
|
-
});
|
|
130
|
-
} else if (p.type === "permission") {
|
|
131
|
-
perms.set(p.key, {
|
|
132
|
-
permissions: (p.params as RequiredPermission[]) ?? [],
|
|
133
|
-
sessionName: p.meta?.session_name,
|
|
134
|
-
});
|
|
135
|
-
} else if (p.type === "input") {
|
|
136
|
-
const questions = (p.params as string[]) ?? p.meta?.input_questions ?? [];
|
|
137
|
-
inputs.set(p.key, {
|
|
138
|
-
questions,
|
|
139
|
-
description: p.meta?.description,
|
|
140
|
-
sessionName: p.meta?.session_name,
|
|
141
|
-
});
|
|
142
|
-
inputVals.set(p.key, new Array(questions.length).fill(""));
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
setPendingConfirms(confirms);
|
|
146
|
-
setPendingPermissions(perms);
|
|
147
|
-
setPendingInputs(inputs);
|
|
148
|
-
setInputValues(inputVals);
|
|
149
|
-
})
|
|
150
|
-
.catch(() => { /* silent — update-required prompt guards the broken case */ });
|
|
151
|
-
}, [connected, hostId, request, setHostLanUrl, setHostTimezone]);
|
|
152
|
-
|
|
153
|
-
// Always-on event subscription for modal lifecycle. Independent of which tab
|
|
154
|
-
// is active. Task-card status updates happen inside TasksView while mounted.
|
|
155
|
-
useEffect(() => {
|
|
156
|
-
if (!connected) return;
|
|
157
|
-
const unsubscribe = subscribeEvents(hostId, (msg) => {
|
|
158
|
-
const tokens = msg.subject.split(".");
|
|
159
|
-
if (tokens.length < 3) return;
|
|
160
|
-
const taskId = tokens.slice(2).join(".");
|
|
161
|
-
|
|
162
|
-
let parsed: Record<string, unknown> = {};
|
|
163
|
-
try {
|
|
164
|
-
parsed = JSON.parse(new TextDecoder().decode(msg.data)) as Record<string, unknown>;
|
|
165
|
-
} catch { return; }
|
|
166
|
-
const eventType = parsed.event_type as string | undefined;
|
|
167
|
-
const sessionId = parsed.session_id as string | undefined;
|
|
168
|
-
|
|
169
|
-
if (eventType === "input-request" && sessionId) {
|
|
170
|
-
const questions = parsed.input_questions as string[] | undefined;
|
|
171
|
-
const sessionName = parsed.session_name as string | undefined;
|
|
172
|
-
const description = parsed.description as string | undefined;
|
|
173
|
-
if (questions?.length) {
|
|
174
|
-
setPendingInputs((prev) => {
|
|
175
|
-
if (prev.has(sessionId)) return prev;
|
|
176
|
-
const next = new Map(prev);
|
|
177
|
-
next.set(sessionId, { questions, description, sessionName });
|
|
178
|
-
return next;
|
|
179
|
-
});
|
|
180
|
-
setInputValues((prev) => {
|
|
181
|
-
if (prev.has(sessionId)) return prev;
|
|
182
|
-
const next = new Map(prev);
|
|
183
|
-
next.set(sessionId, new Array(questions.length).fill(""));
|
|
184
|
-
return next;
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (eventType === "input-resolved" && sessionId) {
|
|
191
|
-
setPendingInputs((prev) => {
|
|
192
|
-
if (!prev.has(sessionId)) return prev;
|
|
193
|
-
const next = new Map(prev);
|
|
194
|
-
next.delete(sessionId);
|
|
195
|
-
return next;
|
|
196
|
-
});
|
|
197
|
-
setInputValues((prev) => {
|
|
198
|
-
const next = new Map(prev);
|
|
199
|
-
next.delete(sessionId);
|
|
200
|
-
return next;
|
|
201
|
-
});
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (eventType === "confirm-request" && sessionId) {
|
|
206
|
-
const description = parsed.description as string | undefined;
|
|
207
|
-
const sessionName = parsed.session_name as string | undefined;
|
|
208
|
-
if (description) {
|
|
209
|
-
setPendingConfirms((prev) => {
|
|
210
|
-
if (prev.has(sessionId)) return prev;
|
|
211
|
-
const next = new Map(prev);
|
|
212
|
-
next.set(sessionId, { description, sessionName });
|
|
213
|
-
return next;
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (eventType === "confirm-resolved" && sessionId) {
|
|
220
|
-
setPendingConfirms((prev) => {
|
|
221
|
-
if (!prev.has(sessionId)) return prev;
|
|
222
|
-
const next = new Map(prev);
|
|
223
|
-
next.delete(sessionId);
|
|
224
|
-
return next;
|
|
225
|
-
});
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (eventType === "permission-request") {
|
|
230
|
-
const permissions = parsed.required_permissions as RequiredPermission[] | undefined;
|
|
231
|
-
const sessionName = parsed.session_name as string | undefined;
|
|
232
|
-
if (permissions?.length) {
|
|
233
|
-
setPendingPermissions((prev) => {
|
|
234
|
-
if (prev.has(taskId)) return prev;
|
|
235
|
-
const next = new Map(prev);
|
|
236
|
-
next.set(taskId, { permissions, sessionName });
|
|
237
|
-
return next;
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (eventType === "permission-resolved") {
|
|
244
|
-
setPendingPermissions((prev) => {
|
|
245
|
-
if (!prev.has(taskId)) return prev;
|
|
246
|
-
const next = new Map(prev);
|
|
247
|
-
next.delete(taskId);
|
|
248
|
-
return next;
|
|
249
|
-
});
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
return unsubscribe;
|
|
254
|
-
}, [connected, hostId, subscribeEvents]);
|
|
255
|
-
|
|
256
|
-
async function respondToConfirm(sessionId: string, response: "confirmed" | "aborted") {
|
|
257
|
-
try {
|
|
258
|
-
await request("task.user_input", { id: sessionId, value: [response] });
|
|
259
|
-
} catch (err) {
|
|
260
|
-
console.error("[Dashboard] Failed to respond to confirmation:", err);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async function respondToPermission(taskId: string, response: "granted" | "granted_all" | "aborted") {
|
|
265
|
-
try {
|
|
266
|
-
await request("task.user_input", { id: taskId, value: [response] });
|
|
267
|
-
} catch (err) {
|
|
268
|
-
console.error("[Dashboard] Failed to respond to permission request:", err);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async function respondToInput(sessionId: string, values: string[]) {
|
|
273
|
-
try {
|
|
274
|
-
await request("task.user_input", { id: sessionId, value: values });
|
|
275
|
-
} catch (err) {
|
|
276
|
-
console.error("[Dashboard] Failed to respond to input request:", err);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function handleViewRun(taskId: string, runId?: string) {
|
|
281
|
-
if (!confirmLeaveDraft()) return;
|
|
282
|
-
const base = `/hosts/${encodeURIComponent(hostId)}/runs/${encodeURIComponent(taskId)}`;
|
|
283
|
-
navigate(runId ? `${base}/${encodeURIComponent(runId)}` : base);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
async function handleUpdate() {
|
|
287
|
-
setUpdating(true);
|
|
288
|
-
setUpdateError(null);
|
|
289
|
-
try {
|
|
290
|
-
const result = await request<{ ok?: boolean; error?: string }>("host.update");
|
|
291
|
-
if (result.error) {
|
|
292
|
-
setUpdateError(result.error);
|
|
293
|
-
setUpdating(false);
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
} catch {
|
|
297
|
-
// Expected: connection drops during daemon restart
|
|
298
|
-
}
|
|
299
|
-
setTimeout(() => window.location.reload(), 15000);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const showTaskContent = connected && !unauthorized;
|
|
303
|
-
|
|
304
|
-
return (
|
|
305
|
-
<div className="dashboard">
|
|
306
|
-
{isDesktop && <HostMenu daemonVersion={daemonVersion} linkedClientToken={linkedClientToken} request={request} onEnabledCapabilitiesChange={setEnabledCapabilities} onLinkedClientTokenChange={setLinkedClientToken} />}
|
|
307
|
-
|
|
308
|
-
<div className="dashboard-content">
|
|
309
|
-
<header className="app-header">
|
|
310
|
-
<div className="app-title-bar">
|
|
311
|
-
{!isDesktop && <HostMenu daemonVersion={daemonVersion} linkedClientToken={linkedClientToken} request={request} onEnabledCapabilitiesChange={setEnabledCapabilities} onLinkedClientTokenChange={setLinkedClientToken} />}
|
|
312
|
-
<h1 className="app-title">Palmier</h1>
|
|
313
|
-
<ConnectionStatusIcon />
|
|
314
|
-
</div>
|
|
315
|
-
<div className="tab-bar">
|
|
316
|
-
<TabBar />
|
|
317
|
-
</div>
|
|
318
|
-
</header>
|
|
319
|
-
|
|
320
|
-
<main className="dashboard-main">
|
|
321
|
-
{unauthorized ? (
|
|
322
|
-
<div className="revoked-state">
|
|
323
|
-
<div className="revoked-icon">
|
|
324
|
-
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
325
|
-
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
326
|
-
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
327
|
-
<line x1="12" y1="15" x2="12" y2="18" />
|
|
328
|
-
</svg>
|
|
329
|
-
</div>
|
|
330
|
-
<h2 className="revoked-title">Client Revoked</h2>
|
|
331
|
-
<p className="revoked-description">
|
|
332
|
-
This client was revoked by the host. To reconnect, generate a new pairing code on the host machine.
|
|
333
|
-
</p>
|
|
334
|
-
<div className="revoked-command">
|
|
335
|
-
<code>palmier pair</code>
|
|
336
|
-
</div>
|
|
337
|
-
<div className="revoked-actions">
|
|
338
|
-
<button
|
|
339
|
-
className="btn btn-primary"
|
|
340
|
-
onClick={() => { if (confirmLeaveDraft()) navigate("/pair"); }}
|
|
341
|
-
>
|
|
342
|
-
Re-pair Device
|
|
343
|
-
</button>
|
|
344
|
-
<button
|
|
345
|
-
className="btn btn-secondary"
|
|
346
|
-
onClick={() => { removePairedHost(hostId); navigate("/", { replace: true }); }}
|
|
347
|
-
>
|
|
348
|
-
Remove Host
|
|
349
|
-
</button>
|
|
350
|
-
</div>
|
|
351
|
-
</div>
|
|
352
|
-
) : showTaskContent ? (
|
|
353
|
-
<>
|
|
354
|
-
{isTasksTab && !isRunDetail && (
|
|
355
|
-
<TasksView
|
|
356
|
-
connected={connected}
|
|
357
|
-
hostId={hostId}
|
|
358
|
-
request={request}
|
|
359
|
-
subscribeEvents={subscribeEvents}
|
|
360
|
-
agents={agents}
|
|
361
|
-
hostPlatform={hostPlatform}
|
|
362
|
-
isNotificationListener={isLinkedDevice && enabledCapabilities.has("notifications")}
|
|
363
|
-
onViewRun={handleViewRun}
|
|
364
|
-
/>
|
|
365
|
-
)}
|
|
366
|
-
{isRunDetail ? (
|
|
367
|
-
<RunDetailView
|
|
368
|
-
connected={connected}
|
|
369
|
-
hostId={hostId}
|
|
370
|
-
request={request}
|
|
371
|
-
subscribeEvents={subscribeEvents}
|
|
372
|
-
taskId={params.taskId!}
|
|
373
|
-
runId={decodeURIComponent(params.runId!)}
|
|
374
|
-
/>
|
|
375
|
-
) : !isTasksTab ? (
|
|
376
|
-
<SessionsView
|
|
377
|
-
connected={connected}
|
|
378
|
-
hostId={hostId}
|
|
379
|
-
request={request}
|
|
380
|
-
subscribeEvents={subscribeEvents}
|
|
381
|
-
agents={agents}
|
|
382
|
-
hostPlatform={hostPlatform}
|
|
383
|
-
filterTaskId={runsFilterTaskId}
|
|
384
|
-
onClearFilter={() => { if (confirmLeaveDraft()) navigate(`/hosts/${encodeURIComponent(hostId)}`); }}
|
|
385
|
-
/>
|
|
386
|
-
) : null}
|
|
387
|
-
</>
|
|
388
|
-
) : (
|
|
389
|
-
<div className="empty-state">
|
|
390
|
-
<p>Connecting to host...</p>
|
|
391
|
-
</div>
|
|
392
|
-
)}
|
|
393
|
-
</main>
|
|
394
|
-
|
|
395
|
-
{updateRequired && !updating && !updateError && (
|
|
396
|
-
<div className="confirm-modal-overlay">
|
|
397
|
-
<div className="confirm-modal">
|
|
398
|
-
<h2 className="confirm-modal-title">Update Required</h2>
|
|
399
|
-
<p className="confirm-modal-message">
|
|
400
|
-
Your Palmier host{daemonVersion ? ` (v${daemonVersion})` : ""} is too old for this version of the app. Please update to continue.
|
|
401
|
-
</p>
|
|
402
|
-
<div className="confirm-modal-actions">
|
|
403
|
-
<button className="btn btn-primary" onClick={handleUpdate}>
|
|
404
|
-
Update Now
|
|
405
|
-
</button>
|
|
406
|
-
</div>
|
|
407
|
-
</div>
|
|
408
|
-
</div>
|
|
409
|
-
)}
|
|
410
|
-
{updating && (
|
|
411
|
-
<div className="confirm-modal-overlay">
|
|
412
|
-
<div className="confirm-modal">
|
|
413
|
-
<h2 className="confirm-modal-title">Updating...</h2>
|
|
414
|
-
<p className="confirm-modal-message">
|
|
415
|
-
Installing update and restarting daemon. Please wait...
|
|
416
|
-
</p>
|
|
417
|
-
</div>
|
|
418
|
-
</div>
|
|
419
|
-
)}
|
|
420
|
-
{updateError && (
|
|
421
|
-
<div className="confirm-modal-overlay">
|
|
422
|
-
<div className="confirm-modal">
|
|
423
|
-
<h2 className="confirm-modal-title">Update Failed</h2>
|
|
424
|
-
<p className="confirm-modal-message" style={{ whiteSpace: "pre-line" }}>
|
|
425
|
-
{updateError}
|
|
426
|
-
</p>
|
|
427
|
-
<div className="confirm-modal-actions">
|
|
428
|
-
<button className="btn btn-secondary" onClick={() => { setUpdateError(null); }}>
|
|
429
|
-
Retry
|
|
430
|
-
</button>
|
|
431
|
-
</div>
|
|
432
|
-
</div>
|
|
433
|
-
</div>
|
|
434
|
-
)}
|
|
435
|
-
|
|
436
|
-
</div>
|
|
437
|
-
|
|
438
|
-
{createPortal(<>
|
|
439
|
-
{[...pendingConfirms.entries()].map(([sessionId, { description, sessionName }]) => (
|
|
440
|
-
<div key={sessionId} className="confirm-modal-overlay">
|
|
441
|
-
<div className="confirm-modal">
|
|
442
|
-
<h2 className="confirm-modal-title">Confirmation Required</h2>
|
|
443
|
-
{sessionName && <p className="confirm-modal-subtitle">{sessionName}</p>}
|
|
444
|
-
<p className="confirm-modal-message">{description}</p>
|
|
445
|
-
<div className="confirm-modal-actions">
|
|
446
|
-
<button className="btn btn-primary" onClick={() => respondToConfirm(sessionId, "confirmed")}>
|
|
447
|
-
Confirm
|
|
448
|
-
</button>
|
|
449
|
-
<button className="btn btn-secondary" onClick={() => respondToConfirm(sessionId, "aborted")}>
|
|
450
|
-
Abort
|
|
451
|
-
</button>
|
|
452
|
-
</div>
|
|
453
|
-
</div>
|
|
454
|
-
</div>
|
|
455
|
-
))}
|
|
456
|
-
|
|
457
|
-
{[...pendingPermissions.entries()].map(([taskId, { permissions, sessionName }]) => (
|
|
458
|
-
<div key={taskId} className="confirm-modal-overlay">
|
|
459
|
-
<div className="confirm-modal permission-modal">
|
|
460
|
-
<h2 className="confirm-modal-title">Permission Required</h2>
|
|
461
|
-
<p className="confirm-modal-message">
|
|
462
|
-
<strong>{sessionName || taskId}</strong>
|
|
463
|
-
</p>
|
|
464
|
-
<div className="permission-list">
|
|
465
|
-
{permissions.map((p, i) => (
|
|
466
|
-
<div key={i} className="permission-item">
|
|
467
|
-
<span className="permission-name">{p.name}</span>
|
|
468
|
-
{p.description && <span className="permission-desc">{p.description}</span>}
|
|
469
|
-
</div>
|
|
470
|
-
))}
|
|
471
|
-
</div>
|
|
472
|
-
<div className="permission-actions">
|
|
473
|
-
<button className="btn btn-primary" onClick={() => respondToPermission(taskId, "granted")}>
|
|
474
|
-
Allow Once
|
|
475
|
-
</button>
|
|
476
|
-
<button className="btn btn-secondary" onClick={() => respondToPermission(taskId, "granted_all")}>
|
|
477
|
-
Allow Always
|
|
478
|
-
</button>
|
|
479
|
-
</div>
|
|
480
|
-
<button
|
|
481
|
-
className="permission-abort-link"
|
|
482
|
-
onClick={() => respondToPermission(taskId, "aborted")}
|
|
483
|
-
>
|
|
484
|
-
Deny & Abort Task
|
|
485
|
-
</button>
|
|
486
|
-
</div>
|
|
487
|
-
</div>
|
|
488
|
-
))}
|
|
489
|
-
|
|
490
|
-
{[...pendingInputs.entries()].map(([sessionId, { questions, description, sessionName }]) => {
|
|
491
|
-
const values = inputValues.get(sessionId) ?? new Array(questions.length).fill("");
|
|
492
|
-
return (
|
|
493
|
-
<div key={sessionId} className="confirm-modal-overlay">
|
|
494
|
-
<div className="confirm-modal input-modal">
|
|
495
|
-
<h2 className="confirm-modal-title">Input Required</h2>
|
|
496
|
-
{sessionName && <p className="confirm-modal-subtitle">{sessionName}</p>}
|
|
497
|
-
{description && <p className="confirm-modal-message">{description}</p>}
|
|
498
|
-
<div className="input-list">
|
|
499
|
-
{questions.map((desc: string, i: number) => (
|
|
500
|
-
<div key={i} className="input-item">
|
|
501
|
-
<label className="input-label">{desc}</label>
|
|
502
|
-
<input
|
|
503
|
-
type="text"
|
|
504
|
-
className="input-field"
|
|
505
|
-
value={values[i] ?? ""}
|
|
506
|
-
onChange={(e) => {
|
|
507
|
-
setInputValues((prev) => {
|
|
508
|
-
const next = new Map(prev);
|
|
509
|
-
const arr = [...(next.get(sessionId) ?? [])];
|
|
510
|
-
arr[i] = e.target.value;
|
|
511
|
-
next.set(sessionId, arr);
|
|
512
|
-
return next;
|
|
513
|
-
});
|
|
514
|
-
}}
|
|
515
|
-
autoFocus={i === 0}
|
|
516
|
-
/>
|
|
517
|
-
</div>
|
|
518
|
-
))}
|
|
519
|
-
</div>
|
|
520
|
-
<div className="input-actions">
|
|
521
|
-
<button
|
|
522
|
-
className="btn btn-primary"
|
|
523
|
-
disabled={values.some((v) => !v.trim())}
|
|
524
|
-
onClick={() => respondToInput(sessionId, values)}
|
|
525
|
-
>
|
|
526
|
-
Submit
|
|
527
|
-
</button>
|
|
528
|
-
</div>
|
|
529
|
-
<button
|
|
530
|
-
className="permission-abort-link"
|
|
531
|
-
onClick={() => respondToInput(sessionId, ["aborted"])}
|
|
532
|
-
>
|
|
533
|
-
Cancel
|
|
534
|
-
</button>
|
|
535
|
-
</div>
|
|
536
|
-
</div>
|
|
537
|
-
);
|
|
538
|
-
})}
|
|
539
|
-
</>, document.body)}
|
|
540
|
-
</div>
|
|
541
|
-
);
|
|
542
|
-
}
|