palmier 0.7.7 → 0.7.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.
Files changed (90) hide show
  1. package/dist/agents/agent.d.ts +3 -0
  2. package/dist/agents/agent.js +1 -1
  3. package/dist/agents/aider.d.ts +1 -0
  4. package/dist/agents/aider.js +1 -0
  5. package/dist/agents/claude.d.ts +1 -0
  6. package/dist/agents/claude.js +1 -0
  7. package/dist/agents/cline.d.ts +1 -0
  8. package/dist/agents/cline.js +1 -0
  9. package/dist/agents/codex.d.ts +1 -0
  10. package/dist/agents/codex.js +1 -0
  11. package/dist/agents/copilot.d.ts +1 -0
  12. package/dist/agents/copilot.js +1 -0
  13. package/dist/agents/cursor.d.ts +1 -0
  14. package/dist/agents/cursor.js +1 -0
  15. package/dist/agents/deepagents.d.ts +1 -0
  16. package/dist/agents/deepagents.js +1 -0
  17. package/dist/agents/droid.d.ts +1 -0
  18. package/dist/agents/droid.js +1 -0
  19. package/dist/agents/gemini.d.ts +1 -0
  20. package/dist/agents/gemini.js +1 -0
  21. package/dist/agents/goose.d.ts +1 -0
  22. package/dist/agents/goose.js +1 -0
  23. package/dist/agents/hermes.d.ts +1 -0
  24. package/dist/agents/hermes.js +1 -0
  25. package/dist/agents/kimi.d.ts +1 -0
  26. package/dist/agents/kimi.js +1 -0
  27. package/dist/agents/kiro.d.ts +1 -0
  28. package/dist/agents/kiro.js +1 -0
  29. package/dist/agents/openclaw.d.ts +1 -0
  30. package/dist/agents/openclaw.js +2 -2
  31. package/dist/agents/opencode.d.ts +1 -0
  32. package/dist/agents/opencode.js +1 -0
  33. package/dist/agents/qoder.d.ts +1 -0
  34. package/dist/agents/qoder.js +1 -0
  35. package/dist/agents/qwen.d.ts +1 -0
  36. package/dist/agents/qwen.js +1 -0
  37. package/dist/commands/pair.js +2 -2
  38. package/dist/mcp-tools.js +16 -7
  39. package/dist/pending-requests.d.ts +30 -8
  40. package/dist/pending-requests.js +28 -15
  41. package/dist/pwa/assets/index-8cTctVnD.js +120 -0
  42. package/dist/pwa/assets/index-CSUkBBsQ.css +1 -0
  43. package/dist/pwa/assets/{web-CkWrlNwc.js → web-BNr628AV.js} +1 -1
  44. package/dist/pwa/assets/{web-lx34oBi7.js → web-DyQPewAi.js} +1 -1
  45. package/dist/pwa/index.html +2 -2
  46. package/dist/pwa/service-worker.js +1 -1
  47. package/dist/rpc-handler.js +12 -16
  48. package/dist/transports/http-transport.js +6 -3
  49. package/dist/types.d.ts +2 -0
  50. package/package.json +1 -1
  51. package/palmier-server/pwa/src/App.css +66 -0
  52. package/palmier-server/pwa/src/App.tsx +1 -0
  53. package/palmier-server/pwa/src/components/HostMenu.tsx +7 -2
  54. package/palmier-server/pwa/src/components/RunsView.tsx +48 -22
  55. package/palmier-server/pwa/src/components/SessionComposer.tsx +137 -0
  56. package/palmier-server/pwa/src/components/TabBar.tsx +17 -10
  57. package/palmier-server/pwa/src/components/TaskForm.tsx +11 -66
  58. package/palmier-server/pwa/src/components/TaskListView.tsx +17 -283
  59. package/palmier-server/pwa/src/constants.ts +1 -1
  60. package/palmier-server/pwa/src/draftGuard.ts +24 -0
  61. package/palmier-server/pwa/src/pages/Dashboard.tsx +335 -12
  62. package/palmier-server/pwa/src/types.ts +1 -6
  63. package/palmier-server/spec.md +22 -9
  64. package/src/agents/agent.ts +5 -1
  65. package/src/agents/aider.ts +1 -0
  66. package/src/agents/claude.ts +1 -0
  67. package/src/agents/cline.ts +1 -0
  68. package/src/agents/codex.ts +1 -0
  69. package/src/agents/copilot.ts +1 -0
  70. package/src/agents/cursor.ts +1 -0
  71. package/src/agents/deepagents.ts +1 -0
  72. package/src/agents/droid.ts +1 -0
  73. package/src/agents/gemini.ts +1 -0
  74. package/src/agents/goose.ts +1 -0
  75. package/src/agents/hermes.ts +1 -0
  76. package/src/agents/kimi.ts +1 -0
  77. package/src/agents/kiro.ts +1 -0
  78. package/src/agents/openclaw.ts +2 -2
  79. package/src/agents/opencode.ts +1 -0
  80. package/src/agents/qoder.ts +1 -0
  81. package/src/agents/qwen.ts +1 -0
  82. package/src/commands/pair.ts +2 -2
  83. package/src/mcp-tools.ts +16 -7
  84. package/src/pending-requests.ts +47 -15
  85. package/src/rpc-handler.ts +13 -16
  86. package/src/transports/http-transport.ts +6 -3
  87. package/src/types.ts +1 -1
  88. package/test/pairing.test.ts +2 -2
  89. package/dist/pwa/assets/index-B-ByUHPS.css +0 -1
  90. package/dist/pwa/assets/index-Bt8Hhaw3.js +0 -118
@@ -0,0 +1,137 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useHostConnection } from "../contexts/HostConnectionContext";
3
+ import { setDraftMessage } from "../draftGuard";
4
+ import type { AgentInfo } from "../types";
5
+
6
+ interface SessionComposerProps {
7
+ agents: AgentInfo[];
8
+ onStarted(taskId: string, runId?: string): void;
9
+ }
10
+
11
+ function pickDefaultAgent(agents: AgentInfo[]): string {
12
+ const stored = localStorage.getItem("palmier:lastAgent");
13
+ const keys = agents.map((a) => a.key);
14
+ if (stored && keys.includes(stored)) return stored;
15
+ return agents[0]?.key ?? "";
16
+ }
17
+
18
+ export default function SessionComposer({ agents, onStarted }: SessionComposerProps) {
19
+ const { request } = useHostConnection();
20
+ const [prompt, setPrompt] = useState("");
21
+ const [agent, setAgent] = useState(() => pickDefaultAgent(agents));
22
+ const [yoloMode, setYoloMode] = useState(false);
23
+ const [running, setRunning] = useState(false);
24
+ const [error, setError] = useState<string | null>(null);
25
+
26
+ // Keep agent selection valid as the agent list arrives/changes.
27
+ useEffect(() => {
28
+ if (!agents.length) return;
29
+ if (!agents.find((a) => a.key === agent)) {
30
+ setAgent(pickDefaultAgent(agents));
31
+ }
32
+ }, [agents, agent]);
33
+
34
+ // Draft guard: warns on navigation / reload when the input has content.
35
+ useEffect(() => {
36
+ const hasDraft = prompt.trim().length > 0;
37
+ setDraftMessage(hasDraft ? "Your session draft will be lost. Continue?" : null);
38
+ return () => setDraftMessage(null);
39
+ }, [prompt]);
40
+
41
+ const canRun = !!prompt.trim() && !!agent && !running;
42
+
43
+ function confirmYolo(): boolean {
44
+ if (!yoloMode) return true;
45
+ return confirm(
46
+ "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?"
47
+ );
48
+ }
49
+
50
+ async function handleRun() {
51
+ if (!canRun || !confirmYolo()) return;
52
+ setRunning(true);
53
+ setError(null);
54
+ try {
55
+ const result = await request<{ task_id?: string; run_id?: string; error?: string }>(
56
+ "task.run_oneoff",
57
+ { user_prompt: prompt, agent, yolo_mode: yoloMode },
58
+ );
59
+ if (result.error) {
60
+ setError(result.error);
61
+ return;
62
+ }
63
+ localStorage.setItem("palmier:lastAgent", agent);
64
+ setPrompt("");
65
+ setDraftMessage(null);
66
+ if (result.task_id) onStarted(result.task_id, result.run_id);
67
+ } catch (err) {
68
+ setError(err instanceof Error ? err.message : String(err));
69
+ } finally {
70
+ setRunning(false);
71
+ }
72
+ }
73
+
74
+ function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
75
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
76
+ e.preventDefault();
77
+ handleRun();
78
+ }
79
+ }
80
+
81
+ return (
82
+ <div className="session-composer">
83
+ {error && <div className="form-error">{error}</div>}
84
+ <textarea
85
+ className="session-composer-textarea"
86
+ value={prompt}
87
+ onChange={(e) => setPrompt(e.target.value)}
88
+ onKeyDown={handleKeyDown}
89
+ placeholder="What can I do for you?"
90
+ rows={3}
91
+ disabled={running}
92
+ />
93
+ <div className="session-composer-controls">
94
+ <div className="agent-picker-section-inline">
95
+ <span className="agent-picker-label">Run with</span>
96
+ <select
97
+ className="form-select form-select-sm"
98
+ value={agent}
99
+ onChange={(e) => setAgent(e.target.value)}
100
+ disabled={running || !agents.length}
101
+ >
102
+ {agents.map((a) => (
103
+ <option key={a.key} value={a.key}>{a.label}</option>
104
+ ))}
105
+ </select>
106
+ </div>
107
+ <label className="session-composer-yolo">
108
+ <input
109
+ type="checkbox"
110
+ checked={yoloMode}
111
+ onChange={(e) => setYoloMode(e.target.checked)}
112
+ disabled={running}
113
+ />
114
+ Yolo
115
+ </label>
116
+ <button
117
+ className="btn btn-primary chat-send-btn"
118
+ onClick={handleRun}
119
+ disabled={!canRun}
120
+ aria-label="Run session"
121
+ title="Run session"
122
+ >
123
+ {running ? (
124
+ <span className="btn-spinner" />
125
+ ) : (
126
+ <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>
127
+ )}
128
+ </button>
129
+ </div>
130
+ {yoloMode && (
131
+ <p className="command-help-text">
132
+ The agent will auto-approve all tool calls without asking for permission.
133
+ </p>
134
+ )}
135
+ </div>
136
+ );
137
+ }
@@ -1,30 +1,37 @@
1
1
  import { useNavigate, useLocation } from "react-router-dom";
2
+ import { confirmLeaveDraft } from "../draftGuard";
2
3
 
3
4
  export default function TabBar() {
4
5
  const navigate = useNavigate();
5
6
  const location = useLocation();
6
- const isRuns = location.pathname.startsWith("/runs");
7
+ const isTasks = location.pathname.startsWith("/tasks");
8
+ const isSessions = !isTasks;
9
+
10
+ function go(path: string) {
11
+ if (!confirmLeaveDraft()) return;
12
+ navigate(path);
13
+ }
7
14
 
8
15
  return (
9
16
  <>
10
17
  <button
11
- className={`tab-btn ${!isRuns ? "tab-btn-active" : ""}`}
12
- onClick={() => navigate("/")}
18
+ className={`tab-btn ${isSessions ? "tab-btn-active" : ""}`}
19
+ onClick={() => go("/")}
13
20
  >
14
21
  <svg className="tab-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
15
- <rect x="2" y="2" width="12" height="12" rx="2" />
16
- <path d="M5.5 8L7 9.5L10.5 6" />
22
+ <path d="M2 8H4.5L6 4L8 12L10 6L11.5 8H14" />
17
23
  </svg>
18
- Tasks
24
+ Sessions
19
25
  </button>
20
26
  <button
21
- className={`tab-btn ${isRuns ? "tab-btn-active" : ""}`}
22
- onClick={() => navigate("/runs")}
27
+ className={`tab-btn ${isTasks ? "tab-btn-active" : ""}`}
28
+ onClick={() => go("/tasks")}
23
29
  >
24
30
  <svg className="tab-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
25
- <path d="M2 8H4.5L6 4L8 12L10 6L11.5 8H14" />
31
+ <rect x="2" y="2" width="12" height="12" rx="2" />
32
+ <path d="M5.5 8L7 9.5L10.5 6" />
26
33
  </svg>
27
- Runs
34
+ Tasks
28
35
  </button>
29
36
  </>
30
37
  );
@@ -64,11 +64,10 @@ interface TaskFormProps {
64
64
  agents: AgentInfo[];
65
65
  hostPlatform?: string;
66
66
  onSaved(task: Task): void;
67
- onRun(taskId: string, runId?: string): void;
68
67
  onCancel(): void;
69
68
  }
70
69
 
71
- export default function TaskForm({ initial, agents, hostPlatform, onSaved, onRun, onCancel }: TaskFormProps) {
70
+ export default function TaskForm({ initial, agents, hostPlatform, onSaved, onCancel }: TaskFormProps) {
72
71
  const { request } = useHostConnection();
73
72
 
74
73
  // Default agent: last used from localStorage, or first available
@@ -107,8 +106,7 @@ export default function TaskForm({ initial, agents, hostPlatform, onSaved, onRun
107
106
  );
108
107
  const [yoloMode, setYoloMode] = useState(initial?.yolo_mode ?? false);
109
108
  const [foregroundMode, setForegroundMode] = useState(initial?.foreground_mode ?? false);
110
- const [savingAction, setSavingAction] = useState<"save" | "run" | null>(null);
111
- const saving = savingAction !== null;
109
+ const [saving, setSaving] = useState(false);
112
110
 
113
111
  // Command-triggered mode
114
112
  const [commandEnabled, setCommandEnabled] = useState(!!initial?.command);
@@ -170,7 +168,7 @@ export default function TaskForm({ initial, agents, hostPlatform, onSaved, onRun
170
168
  }
171
169
 
172
170
  async function handleSave() {
173
- setSavingAction("save");
171
+ setSaving(true);
174
172
  setError(null);
175
173
  try {
176
174
  const method = isEdit ? "task.update" : "task.create";
@@ -202,34 +200,7 @@ export default function TaskForm({ initial, agents, hostPlatform, onSaved, onRun
202
200
  setError(err instanceof Error ? err.message : String(err));
203
201
  return null;
204
202
  } finally {
205
- setSavingAction(null);
206
- }
207
- }
208
-
209
- async function handleRunOneoff() {
210
- setSavingAction("run");
211
- setError(null);
212
- try {
213
- const payload: Record<string, unknown> = {
214
- user_prompt: userPrompt,
215
- agent,
216
- requires_confirmation: requiresConfirmation,
217
- yolo_mode: yoloMode,
218
- foreground_mode: foregroundMode,
219
- command: commandEnabled ? command : "",
220
- };
221
- const result = await request<{ ok?: boolean; task_id?: string; run_id?: string; error?: string }>("task.run_oneoff", payload);
222
- if (result.error) {
223
- setError(result.error);
224
- return;
225
- }
226
- localStorage.setItem("palmier:lastAgent", agent);
227
- onRun(result.task_id!, result.run_id);
228
- onCancel();
229
- } catch (err) {
230
- setError(err instanceof Error ? err.message : String(err));
231
- } finally {
232
- setSavingAction(null);
203
+ setSaving(false);
233
204
  }
234
205
  }
235
206
 
@@ -505,59 +476,33 @@ export default function TaskForm({ initial, agents, hostPlatform, onSaved, onRun
505
476
  <div className="form-actions">
506
477
  {(() => {
507
478
  const hasSchedule = triggerRows.length > 0;
508
- const canRun = !!userPrompt.trim() && (!commandEnabled || !!command.trim());
479
+ const label = hasSchedule ? "Schedule" : "Save";
509
480
  if (!isEdit) {
510
- if (hasSchedule) {
511
- // New task with schedule: "Schedule" only
512
- return (
513
- <button
514
- className="btn btn-primary"
515
- onClick={() => confirmYolo() && handleSave()}
516
- disabled={!canSave || saving}
517
- >
518
- {savingAction === "save" && <span className="btn-spinner" />}
519
- Schedule
520
- </button>
521
- );
522
- }
523
- // New task, no schedule: "Run" (primary) + "Save"
524
- return (<>
481
+ return (
525
482
  <button
526
483
  className="btn btn-primary"
527
- onClick={() => confirmYolo() && handleRunOneoff()}
528
- disabled={!canRun || saving}
529
- >
530
- {savingAction === "run" && <span className="btn-spinner" />}
531
- Run
532
- </button>
533
- <button
534
- className="btn btn-secondary"
535
484
  onClick={() => confirmYolo() && handleSave()}
536
485
  disabled={!canSave || saving}
537
486
  >
538
- Save
487
+ {saving && <span className="btn-spinner" />}
488
+ {label}
539
489
  </button>
540
- </>);
490
+ );
541
491
  }
542
492
  if (isDirty) {
543
- // Edit, changed: Save only
544
493
  return (
545
494
  <button
546
495
  className="btn btn-primary"
547
496
  onClick={() => confirmYolo() && handleSave()}
548
497
  disabled={!canSave || saving}
549
498
  >
550
- {savingAction === "save" && <span className="btn-spinner" />}
499
+ {saving && <span className="btn-spinner" />}
551
500
  Save
552
501
  </button>
553
502
  );
554
503
  }
555
- // Edit, unchanged: disabled Save
556
504
  return (
557
- <button
558
- className="btn btn-primary"
559
- disabled
560
- >
505
+ <button className="btn btn-primary" disabled>
561
506
  Save
562
507
  </button>
563
508
  );