@chrysb/alphaclaw 0.6.2-beta.6 → 0.7.0-beta.0

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.
@@ -1,34 +1,63 @@
1
1
  /* ── Agents detail layout ────────────────────── */
2
2
 
3
3
  .app-content-pane.agents-pane {
4
- padding: 0;
4
+ overflow: hidden;
5
+ padding-left: 0;
6
+ padding-right: 0;
7
+ padding-bottom: 0;
5
8
  }
6
9
 
7
10
  /* ── Detail panel ────────────────────────────── */
8
11
 
9
12
  .agents-detail-panel {
10
13
  height: 100%;
11
- overflow-y: auto;
12
- padding: 0 32px;
14
+ display: flex;
15
+ flex-direction: column;
16
+ min-height: 0;
17
+ }
18
+
19
+ .agents-detail-header-area {
20
+ padding: 8px 32px 16px;
13
21
  }
14
22
 
15
- .agents-detail-inner {
23
+ .agents-detail-header-area-inner {
16
24
  max-width: 42rem;
17
25
  width: 100%;
18
26
  margin: 0 auto;
19
- display: flex;
20
- flex-direction: column;
21
- min-height: 100%;
22
27
  }
23
28
 
24
29
  .agents-detail-header {
25
- padding-top: 16px;
26
30
  display: flex;
27
31
  align-items: flex-start;
28
32
  justify-content: space-between;
29
33
  gap: 12px;
30
34
  }
31
35
 
36
+ .agents-detail-body {
37
+ flex: 1;
38
+ min-height: 0;
39
+ overflow-y: auto;
40
+ overflow-x: hidden;
41
+ padding: 0 32px;
42
+ }
43
+
44
+ .agents-detail-content {
45
+ max-width: 42rem;
46
+ width: 100%;
47
+ margin: 0 auto;
48
+ padding: 0 0 24px;
49
+ }
50
+
51
+ @media (max-width: 768px) {
52
+ .agents-detail-header-area {
53
+ padding: 16px 14px 0;
54
+ }
55
+
56
+ .agents-detail-body {
57
+ padding: 0 14px;
58
+ }
59
+ }
60
+
32
61
  .agents-detail-header-title {
33
62
  font-size: 16px;
34
63
  font-weight: 600;
@@ -72,11 +101,6 @@
72
101
  border-bottom-color: var(--accent);
73
102
  }
74
103
 
75
- .agents-detail-content {
76
- flex: 1;
77
- padding: 16px 0;
78
- }
79
-
80
104
  /* ── Empty state ─────────────────────────────── */
81
105
 
82
106
  .agents-empty-state {
@@ -1,5 +1,6 @@
1
1
  .app-content-pane.cron-pane {
2
- padding: 0;
2
+ padding: 24px 32px 12px;
3
+ overflow: hidden;
3
4
  }
4
5
 
5
6
  .cron-tab-shell {
@@ -10,8 +11,7 @@
10
11
  }
11
12
 
12
13
  .cron-tab-header {
13
- padding: 16px 0 12px;
14
- border-bottom: 1px solid var(--border);
14
+ padding: 0 0 16px;
15
15
  }
16
16
 
17
17
  .cron-tab-header-content {
@@ -33,13 +33,28 @@
33
33
  }
34
34
 
35
35
  .cron-tab-main-content {
36
- width: min(100% - 40px, 672px);
36
+ width: 100%;
37
+ max-width: 672px;
37
38
  margin-left: auto;
38
39
  margin-right: auto;
39
40
  padding: 0 0 24px;
40
41
  min-height: 100%;
41
42
  }
42
43
 
44
+ .cron-tab-main-content .cron-detail-content {
45
+ padding-top: 8px;
46
+ }
47
+
48
+ @media (max-width: 768px) {
49
+ .app-content-pane.cron-pane {
50
+ padding: 0 14px 12px;
51
+ }
52
+
53
+ .cron-tab-header {
54
+ padding: 0 0 12px;
55
+ }
56
+ }
57
+
43
58
  .cron-tab-selector-shell {
44
59
  position: relative;
45
60
  width: min(100%, 320px);
@@ -370,6 +385,22 @@
370
385
  border-bottom: 1px solid var(--border);
371
386
  border-right: 1px solid var(--border);
372
387
  background: rgba(0, 0, 0, 0.15);
388
+ position: sticky;
389
+ left: 0;
390
+ z-index: 2;
391
+ }
392
+
393
+ .cron-calendar-grid-header .cron-calendar-hour-cell {
394
+ top: 0;
395
+ z-index: 3;
396
+ }
397
+
398
+ .cron-calendar-grid-wrap {
399
+ position: relative;
400
+ }
401
+
402
+ .cron-calendar-grid-row .cron-calendar-hour-cell {
403
+ box-shadow: 1px 0 0 var(--border);
373
404
  }
374
405
 
375
406
  .cron-calendar-grid-cell {
@@ -63,6 +63,62 @@
63
63
  padding: 0;
64
64
  }
65
65
 
66
+ /* ── Fixed-header pane shell ────────────────────── */
67
+
68
+ .app-content-pane.ac-fixed-header-pane {
69
+ overflow: hidden;
70
+ padding-left: 0;
71
+ padding-right: 0;
72
+ padding-bottom: 0;
73
+ }
74
+
75
+ .ac-pane-shell {
76
+ height: 100%;
77
+ display: flex;
78
+ flex-direction: column;
79
+ min-height: 0;
80
+ }
81
+
82
+ .ac-pane-header {
83
+ padding: 16px 32px 16px;
84
+ }
85
+
86
+ .ac-pane-header-content {
87
+ width: 100%;
88
+ max-width: 672px;
89
+ margin-left: auto;
90
+ margin-right: auto;
91
+ }
92
+
93
+ .ac-pane-body {
94
+ flex: 1;
95
+ min-height: 0;
96
+ overflow-y: auto;
97
+ overflow-x: hidden;
98
+ padding: 0 32px;
99
+ }
100
+
101
+ .ac-pane-body-content {
102
+ width: 100%;
103
+ max-width: 672px;
104
+ margin-left: auto;
105
+ margin-right: auto;
106
+ padding-bottom: 24px;
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 16px;
110
+ }
111
+
112
+ @media (max-width: 768px) {
113
+ .ac-pane-header {
114
+ padding: 16px 14px 12px;
115
+ }
116
+
117
+ .ac-pane-body {
118
+ padding: 0 14px;
119
+ }
120
+ }
121
+
66
122
  /* ── Sidebar ───────────────────────────────────── */
67
123
 
68
124
  .app-sidebar {
@@ -336,6 +392,7 @@
336
392
  }
337
393
  .app-content-pane {
338
394
  padding: 0 14px 12px;
395
+ top: 52px;
339
396
  }
340
397
 
341
398
  .sidebar-resizer {
@@ -346,7 +403,9 @@
346
403
  display: flex;
347
404
  align-items: center;
348
405
  justify-content: center;
349
- position: sticky;
406
+ position: absolute;
407
+ left: 0;
408
+ right: 0;
350
409
  top: 0;
351
410
  z-index: 15;
352
411
  background: var(--panel-bg-contrast);
@@ -355,7 +414,7 @@
355
414
  border-radius: 0;
356
415
  min-height: 52px;
357
416
  padding: 8px 14px;
358
- margin: 0 -14px 10px;
417
+ margin: 0;
359
418
  }
360
419
 
361
420
  .mobile-topbar.is-scrolled {
@@ -641,7 +641,8 @@ textarea:focus {
641
641
  }
642
642
 
643
643
  .ac-pop-actions-hidden {
644
- display: none;
644
+ visibility: hidden;
645
+ pointer-events: none;
645
646
  }
646
647
 
647
648
  .ac-pop-actions-in > * {
@@ -77,6 +77,8 @@ const App = () => {
77
77
 
78
78
  const isAgentsRoute = location.startsWith("/agents");
79
79
  const isCronRoute = location.startsWith("/cron");
80
+ const isEnvarsRoute = location.startsWith("/envars");
81
+ const isModelsRoute = location.startsWith("/models");
80
82
  const selectedAgentId = (() => {
81
83
  const match = location.match(/^\/agents\/([^/]+)/);
82
84
  return match ? decodeURIComponent(match[1]) : "";
@@ -211,6 +213,31 @@ const App = () => {
211
213
  />
212
214
 
213
215
  <div class="app-content">
216
+ <div
217
+ class=${`mobile-topbar ${shellState.mobileTopbarScrolled ? "is-scrolled" : ""}`}
218
+ >
219
+ <button
220
+ class="mobile-topbar-menu"
221
+ onclick=${() =>
222
+ shellActions.setMobileSidebarOpen((open) => !open)}
223
+ aria-label="Open menu"
224
+ aria-expanded=${shellState.mobileSidebarOpen ? "true" : "false"}
225
+ >
226
+ <svg
227
+ width="18"
228
+ height="18"
229
+ viewBox="0 0 16 16"
230
+ fill="currentColor"
231
+ >
232
+ <path
233
+ d="M2 3.75a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H2.75A.75.75 0 012 3.75zm0 4.25a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H2.75A.75.75 0 012 8zm0 4.25a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H2.75a.75.75 0 01-.75-.75z"
234
+ />
235
+ </svg>
236
+ </button>
237
+ <span class="mobile-topbar-title">
238
+ <span style="color: var(--accent)">alpha</span>claw
239
+ </span>
240
+ </div>
214
241
  <div
215
242
  class="app-content-pane browse-pane"
216
243
  style=${{ display: browseState.isBrowseRoute ? "block" : "none" }}
@@ -265,38 +292,25 @@ const App = () => {
265
292
  onSetLocation=${setLocation}
266
293
  />
267
294
  </div>
295
+ <div
296
+ class="app-content-pane ac-fixed-header-pane"
297
+ style=${{ display: isEnvarsRoute ? "block" : "none" }}
298
+ >
299
+ <${EnvarsRoute} onRestartRequired=${controllerActions.setRestartRequired} />
300
+ </div>
301
+ <div
302
+ class="app-content-pane ac-fixed-header-pane"
303
+ style=${{ display: isModelsRoute ? "block" : "none" }}
304
+ >
305
+ <${ModelsRoute} onRestartRequired=${controllerActions.setRestartRequired} />
306
+ </div>
268
307
  <div
269
308
  class="app-content-pane"
270
309
  onscroll=${shellActions.handlePaneScroll}
271
- style=${{ display: browseState.isBrowseRoute || isAgentsRoute || isCronRoute ? "none" : "block" }}
310
+ style=${{ display: browseState.isBrowseRoute || isAgentsRoute || isCronRoute || isEnvarsRoute || isModelsRoute ? "none" : "block" }}
272
311
  >
273
- <div
274
- class=${`mobile-topbar ${shellState.mobileTopbarScrolled ? "is-scrolled" : ""}`}
275
- >
276
- <button
277
- class="mobile-topbar-menu"
278
- onclick=${() =>
279
- shellActions.setMobileSidebarOpen((open) => !open)}
280
- aria-label="Open menu"
281
- aria-expanded=${shellState.mobileSidebarOpen ? "true" : "false"}
282
- >
283
- <svg
284
- width="18"
285
- height="18"
286
- viewBox="0 0 16 16"
287
- fill="currentColor"
288
- >
289
- <path
290
- d="M2 3.75a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H2.75A.75.75 0 012 3.75zm0 4.25a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H2.75A.75.75 0 012 8zm0 4.25a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H2.75a.75.75 0 01-.75-.75z"
291
- />
292
- </svg>
293
- </button>
294
- <span class="mobile-topbar-title">
295
- <span style="color: var(--accent)">alpha</span>claw
296
- </span>
297
- </div>
298
312
  <div class="max-w-2xl w-full mx-auto">
299
- ${!browseState.isBrowseRoute && !isAgentsRoute && !isCronRoute
313
+ ${!browseState.isBrowseRoute && !isAgentsRoute && !isCronRoute && !isEnvarsRoute && !isModelsRoute
300
314
  ? html`
301
315
  <${Switch}>
302
316
  <${Route} path="/general">
@@ -336,9 +350,6 @@ const App = () => {
336
350
  <${Route} path="/telegram">
337
351
  <${RouteRedirect} to="/telegram/default" />
338
352
  </${Route}>
339
- <${Route} path="/models">
340
- <${ModelsRoute} onRestartRequired=${controllerActions.setRestartRequired} />
341
- </${Route}>
342
353
  <${Route} path="/providers">
343
354
  <${RouteRedirect} to="/models" />
344
355
  </${Route}>
@@ -368,9 +379,6 @@ const App = () => {
368
379
  <${Route} path="/usage">
369
380
  <${UsageRoute} onSetLocation=${setLocation} />
370
381
  </${Route}>
371
- <${Route} path="/envars">
372
- <${EnvarsRoute} onRestartRequired=${controllerActions.setRestartRequired} />
373
- </${Route}>
374
382
  <${Route} path="/webhooks/:hookName">
375
383
  ${(params) => html`
376
384
  <${WebhooksRoute}
@@ -1,5 +1,5 @@
1
1
  import { h } from "https://esm.sh/preact";
2
- import { useState, useCallback } from "https://esm.sh/preact/hooks";
2
+ import { useState, useCallback, useMemo } from "https://esm.sh/preact/hooks";
3
3
  import htm from "https://esm.sh/htm";
4
4
  import { ActionButton } from "../action-button.js";
5
5
  import { Badge } from "../badge.js";
@@ -64,6 +64,12 @@ export const AgentDetailPanel = ({
64
64
 
65
65
  const isSaving = saving || savingTools;
66
66
 
67
+ const toolsSummary = useMemo(() => ({
68
+ profile: tools.profile,
69
+ enabledCount: (tools.toolStates || []).filter((t) => t.enabled).length,
70
+ totalCount: (tools.toolStates || []).length,
71
+ }), [tools.profile, tools.toolStates]);
72
+
67
73
  if (!agent) {
68
74
  return html`
69
75
  <div class="agents-detail-panel">
@@ -76,57 +82,61 @@ export const AgentDetailPanel = ({
76
82
 
77
83
  return html`
78
84
  <div class="agents-detail-panel">
79
- <div class="agents-detail-inner">
80
- <div class="agents-detail-header">
81
- <div class="min-w-0">
82
- <div class="flex items-center gap-2 min-w-0">
83
- <span class="agents-detail-header-title">
84
- ${agent.name || agent.id}
85
- </span>
86
- <button
87
- type="button"
88
- class="text-gray-500 hover:text-gray-300 transition-colors p-0.5 -ml-0.5"
89
- onclick=${() => onEdit(agent)}
90
- title="Edit agent name"
91
- >
92
- <${PencilIcon} />
93
- </button>
94
- ${agent.default
95
- ? html`<${Badge} tone="cyan">Default</${Badge}>`
96
- : null}
97
- </div>
98
- <div class="mt-1 flex flex-wrap items-center gap-x-1.5 gap-y-1 min-w-0 text-xs text-gray-500">
99
- <span class="font-mono">${agent.id}</span>
85
+ <div class="agents-detail-header-area">
86
+ <div class="agents-detail-header-area-inner">
87
+ <div class="agents-detail-header">
88
+ <div class="min-w-0">
89
+ <div class="flex items-center gap-2 min-w-0">
90
+ <span class="agents-detail-header-title">
91
+ ${agent.name || agent.id}
92
+ </span>
93
+ <button
94
+ type="button"
95
+ class="text-gray-500 hover:text-gray-300 transition-colors p-0.5 -ml-0.5"
96
+ onclick=${() => onEdit(agent)}
97
+ title="Edit agent name"
98
+ >
99
+ <${PencilIcon} />
100
+ </button>
101
+ ${agent.default
102
+ ? html`<${Badge} tone="cyan">Default</${Badge}>`
103
+ : null}
104
+ </div>
105
+ <div class="mt-1 flex flex-wrap items-center gap-x-1.5 gap-y-1 min-w-0 text-xs text-gray-500">
106
+ <span class="font-mono">${agent.id}</span>
107
+ </div>
100
108
  </div>
109
+ <${PopActions} visible=${tools.dirty}>
110
+ <${ActionButton}
111
+ onClick=${tools.reset}
112
+ disabled=${isSaving}
113
+ tone="secondary"
114
+ size="sm"
115
+ idleLabel="Cancel"
116
+ className="text-xs"
117
+ />
118
+ <${ActionButton}
119
+ onClick=${handleSaveTools}
120
+ disabled=${isSaving}
121
+ loading=${isSaving}
122
+ loadingMode="inline"
123
+ tone="primary"
124
+ size="sm"
125
+ idleLabel="Save changes"
126
+ loadingLabel="Saving…"
127
+ className="text-xs"
128
+ />
129
+ </${PopActions}>
101
130
  </div>
102
- <${PopActions} visible=${tools.dirty}>
103
- <${ActionButton}
104
- onClick=${tools.reset}
105
- disabled=${isSaving}
106
- tone="secondary"
107
- size="sm"
108
- idleLabel="Cancel"
109
- className="text-xs"
110
- />
111
- <${ActionButton}
112
- onClick=${handleSaveTools}
113
- disabled=${isSaving}
114
- loading=${isSaving}
115
- loadingMode="inline"
116
- tone="primary"
117
- size="sm"
118
- idleLabel="Save changes"
119
- loadingLabel="Saving…"
120
- className="text-xs"
121
- />
122
- </${PopActions}>
131
+ <${PillTabs}
132
+ tabs=${kDetailTabs}
133
+ activeTab=${activeTab}
134
+ onSelectTab=${onSelectTab}
135
+ className="flex items-center gap-2 pt-6"
136
+ />
123
137
  </div>
124
- <${PillTabs}
125
- tabs=${kDetailTabs}
126
- activeTab=${activeTab}
127
- onSelectTab=${onSelectTab}
128
- className="flex items-center gap-2 pt-6"
129
- />
138
+ </div>
139
+ <div class="agents-detail-body">
130
140
  <div class="agents-detail-content">
131
141
  ${activeTab === "overview"
132
142
  ? html`
@@ -134,10 +144,12 @@ export const AgentDetailPanel = ({
134
144
  agent=${agent}
135
145
  agents=${agents}
136
146
  saving=${saving}
147
+ toolsSummary=${toolsSummary}
137
148
  onUpdateAgent=${onUpdateAgent}
138
149
  onSetLocation=${onSetLocation}
139
150
  onOpenWorkspace=${onOpenWorkspace}
140
151
  onSwitchToModels=${() => onSetLocation("/models")}
152
+ onSwitchToTools=${() => onSelectTab("tools")}
141
153
  onSetDefault=${onSetDefault}
142
154
  onDelete=${onDelete}
143
155
  />
@@ -3,6 +3,7 @@ import htm from "https://esm.sh/htm";
3
3
  import { ChannelOperationsPanel } from "../../channel-operations-panel.js";
4
4
  import { ManageCard } from "./manage-card.js";
5
5
  import { AgentModelCard } from "./model-card.js";
6
+ import { AgentToolsCard } from "./tools-card.js";
6
7
  import { WorkspaceCard } from "./workspace-card.js";
7
8
 
8
9
  const html = htm.bind(h);
@@ -11,10 +12,12 @@ export const AgentOverview = ({
11
12
  agent = {},
12
13
  agents = [],
13
14
  saving = false,
15
+ toolsSummary = {},
14
16
  onUpdateAgent = async () => {},
15
17
  onSetLocation = () => {},
16
18
  onOpenWorkspace = () => {},
17
19
  onSwitchToModels = () => {},
20
+ onSwitchToTools = () => {},
18
21
  onSetDefault = () => {},
19
22
  onDelete = () => {},
20
23
  }) => {
@@ -33,6 +36,12 @@ export const AgentOverview = ({
33
36
  onUpdateAgent=${onUpdateAgent}
34
37
  onSwitchToModels=${onSwitchToModels}
35
38
  />
39
+ <${AgentToolsCard}
40
+ profile=${toolsSummary.profile || "full"}
41
+ enabledCount=${toolsSummary.enabledCount || 0}
42
+ totalCount=${toolsSummary.totalCount || 0}
43
+ onSwitchToTools=${onSwitchToTools}
44
+ />
36
45
  <${ChannelOperationsPanel}
37
46
  agent=${agent}
38
47
  agents=${agents}
@@ -0,0 +1,54 @@
1
+ import { h } from "https://esm.sh/preact";
2
+ import htm from "https://esm.sh/htm";
3
+ import { kProfileLabels } from "../agent-tools/tool-catalog.js";
4
+
5
+ const html = htm.bind(h);
6
+
7
+ export const AgentToolsCard = ({
8
+ profile = "full",
9
+ enabledCount = 0,
10
+ totalCount = 0,
11
+ onSwitchToTools = () => {},
12
+ }) => {
13
+ const profileLabel = kProfileLabels[profile] || profile;
14
+
15
+ return html`
16
+ <div class="bg-surface border border-border rounded-xl p-4">
17
+ <h2 class="card-label mb-3">Tools</h2>
18
+ <div
19
+ class="flex items-center justify-between gap-3 cursor-pointer hover:bg-white/5 -mx-2 px-2 py-1.5 rounded-lg transition-colors"
20
+ role="button"
21
+ tabindex="0"
22
+ onclick=${onSwitchToTools}
23
+ onKeyDown=${(e) => {
24
+ if (e.key === "Enter" || e.key === " ") {
25
+ e.preventDefault();
26
+ onSwitchToTools();
27
+ }
28
+ }}
29
+ >
30
+ <span class="font-medium text-sm">${profileLabel}</span>
31
+ <span class="flex items-center gap-2 shrink-0">
32
+ <span class="text-xs text-gray-500">
33
+ ${enabledCount}/${totalCount} enabled
34
+ </span>
35
+ <svg
36
+ width="14"
37
+ height="14"
38
+ viewBox="0 0 16 16"
39
+ fill="none"
40
+ class="text-gray-600"
41
+ >
42
+ <path
43
+ d="M6 3.5L10.5 8L6 12.5"
44
+ stroke="currentColor"
45
+ stroke-width="2"
46
+ stroke-linecap="round"
47
+ stroke-linejoin="round"
48
+ />
49
+ </svg>
50
+ </span>
51
+ </div>
52
+ </div>
53
+ `;
54
+ };