@agentprojectcontext/apx 1.37.0 → 1.38.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.
- package/README.md +11 -0
- package/package.json +1 -1
- package/src/core/mascot.js +80 -80
- package/src/interfaces/web/dist/assets/index-CQc_5t8F.js +629 -0
- package/src/interfaces/web/dist/assets/index-CQc_5t8F.js.map +1 -0
- package/src/interfaces/web/dist/assets/{index-B6sYFQFa.css → index-hwxuTPcK.css} +1 -1
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/App.tsx +15 -24
- package/src/interfaces/web/src/components/ModelCombobox.tsx +1 -1
- package/src/interfaces/web/src/components/Roby.tsx +96 -0
- package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +11 -11
- package/src/interfaces/web/src/components/TelegramSendDialog.tsx +5 -5
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +2 -2
- package/src/interfaces/web/src/components/chat/ModelPicker.tsx +5 -5
- package/src/interfaces/web/src/components/chat/ToolCall.tsx +23 -19
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +10 -10
- package/src/interfaces/web/src/components/code/CodeContextTab.tsx +7 -7
- package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +3 -2
- package/src/interfaces/web/src/components/common/TabNav.tsx +3 -2
- package/src/interfaces/web/src/components/config/ConfigTabsEditor.tsx +3 -2
- package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -2
- package/src/interfaces/web/src/components/config/global-config-sections.ts +9 -9
- package/src/interfaces/web/src/components/config/project-config-sections.ts +61 -54
- package/src/interfaces/web/src/components/deck/DaemonCard.tsx +6 -5
- package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +5 -4
- package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +3 -3
- package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +22 -9
- package/src/interfaces/web/src/components/settings/AdvancedPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/AppearancePanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +14 -14
- package/src/interfaces/web/src/components/settings/DevicesPanel.tsx +3 -3
- package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +7 -7
- package/src/interfaces/web/src/components/settings/IdentityPanel.tsx +2 -2
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +37 -37
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +44 -35
- package/src/interfaces/web/src/components/settings/SuperAgentPanel.tsx +5 -5
- package/src/interfaces/web/src/components/settings/TelegramChannelsPanel.tsx +3 -3
- package/src/interfaces/web/src/components/settings/TelegramContactsPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/TelegramGlobalPanel.tsx +3 -3
- package/src/interfaces/web/src/components/settings/TelegramRolesPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +6 -6
- package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +36 -36
- package/src/interfaces/web/src/components/voice/VoiceProviderList.tsx +15 -14
- package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +22 -22
- package/src/interfaces/web/src/components/voice/VoiceSttCard.tsx +18 -17
- package/src/interfaces/web/src/components/voice/VoiceTestCard.tsx +19 -18
- package/src/interfaces/web/src/hooks/useChat.ts +6 -5
- package/src/interfaces/web/src/i18n/en.ts +517 -1
- package/src/interfaces/web/src/i18n/es.ts +517 -1
- package/src/interfaces/web/src/i18n/index.ts +1 -1
- package/src/interfaces/web/src/lib/api/voice.ts +5 -5
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +14 -1
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +8 -8
- package/src/interfaces/web/src/screens/base/ComingSoon.tsx +3 -2
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +12 -12
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +15 -15
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +37 -37
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +8 -8
- package/src/interfaces/web/src/screens/project/AgentBrainGraph.tsx +16 -10
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +25 -24
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +2 -2
- package/src/interfaces/web/src/screens/project/ConfigTab.tsx +3 -3
- package/src/interfaces/web/src/screens/project/McpsTab.tsx +6 -9
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +66 -52
- package/src/interfaces/web/src/screens/project/TelegramTab.tsx +1 -1
- package/src/interfaces/web/dist/assets/index-DsADpObh.js +0 -633
- package/src/interfaces/web/dist/assets/index-DsADpObh.js.map +0 -1
|
@@ -18,7 +18,7 @@ import { AgentBrainGraph, type BrainNode } from "./AgentBrainGraph";
|
|
|
18
18
|
type TabKey = "overview" | "memories" | "records" | "sleep" | "brain" | "config";
|
|
19
19
|
function buildTabs(): { key: TabKey; label: string; icon: typeof Bot }[] {
|
|
20
20
|
return [
|
|
21
|
-
{ key: "overview", label: "
|
|
21
|
+
{ key: "overview", label: t("agents_ui.tab_explorer"), icon: Gauge },
|
|
22
22
|
{ key: "memories", label: t("project.nav.memories"), icon: Brain },
|
|
23
23
|
{ key: "records", label: t("project.agent_detail.records_title"), icon: Activity },
|
|
24
24
|
{ key: "sleep", label: t("project.agent_detail.sleep_title"), icon: Heart },
|
|
@@ -27,15 +27,16 @@ function buildTabs(): { key: TabKey; label: string; icon: typeof Bot }[] {
|
|
|
27
27
|
];
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
function typeOptions() {
|
|
31
|
+
return [
|
|
32
|
+
{ value: "", label: t("agents_ui.type_none") },
|
|
33
|
+
{ value: "orchestrator", label: t("agents_ui.type_orchestrator"), description: t("agents_ui.type_orchestrator_desc") },
|
|
34
|
+
{ value: "specialist", label: t("agents_ui.type_specialist"), description: t("agents_ui.type_specialist_desc") },
|
|
35
|
+
{ value: "assistant", label: t("agents_ui.type_assistant"), description: t("agents_ui.type_assistant_desc") },
|
|
36
|
+
{ value: "worker", label: t("agents_ui.type_worker"), description: t("agents_ui.type_worker_desc") },
|
|
37
|
+
{ value: "monitor", label: t("agents_ui.type_monitor"), description: t("agents_ui.type_monitor_desc") },
|
|
38
|
+
];
|
|
39
|
+
}
|
|
39
40
|
const csv = (s: string) => s.split(",").map((x) => x.trim()).filter(Boolean);
|
|
40
41
|
|
|
41
42
|
const routinesForAgent = (rs: RoutineEntry[], slug: string) =>
|
|
@@ -122,10 +123,10 @@ export function AgentDetailScreen({ pid }: { pid: string }) {
|
|
|
122
123
|
{tab === "overview" && (
|
|
123
124
|
<div className="space-y-4">
|
|
124
125
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
|
125
|
-
<Stat label="
|
|
126
|
-
<Stat label="
|
|
127
|
-
<Stat label="
|
|
128
|
-
<Stat label="
|
|
126
|
+
<Stat label={t("agents_ui.stat_threads")} value={threads.data?.length ?? 0} icon={MessagesSquare} />
|
|
127
|
+
<Stat label={t("agents_ui.stat_records")} value={records.data?.length ?? 0} icon={Activity} />
|
|
128
|
+
<Stat label={t("agents_ui.stat_tasks")} value={myTasks.length} icon={Gauge} />
|
|
129
|
+
<Stat label={t("agents_ui.stat_heartbeats")} value={myRoutines.length} icon={Heart} />
|
|
129
130
|
</div>
|
|
130
131
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
131
132
|
<Section title={t("agent_detail_extra.skills_title")} description="">
|
|
@@ -243,10 +244,10 @@ function AgentConfigForm({
|
|
|
243
244
|
};
|
|
244
245
|
|
|
245
246
|
return (
|
|
246
|
-
<Section title={t("project.agent_detail.config_title")} description={`.apc/agents/${agent.slug}.md —
|
|
247
|
+
<Section title={t("project.agent_detail.config_title")} description={`.apc/agents/${agent.slug}.md — ${t("agents_ui.config_def_desc")}`}>
|
|
247
248
|
<div className="space-y-3">
|
|
248
249
|
<div className="grid grid-cols-2 gap-3">
|
|
249
|
-
<Field label={t("project.agent_detail.type_label")}><UiSelect value={type} onChange={setType} options={
|
|
250
|
+
<Field label={t("project.agent_detail.type_label")}><UiSelect value={type} onChange={setType} options={typeOptions()} /></Field>
|
|
250
251
|
<Field label={t("project.agent_detail.area_label")} hint={t("project.agent_detail.area_hint")}><Input value={area} onChange={(e) => setArea(e.target.value)} placeholder={t("project.agent_detail.area_ph")} /></Field>
|
|
251
252
|
</div>
|
|
252
253
|
<div className="grid grid-cols-2 gap-3">
|
|
@@ -302,7 +303,7 @@ function MemoryEditor({ pid, slug, initial, onSaved }: { pid: string; slug: stri
|
|
|
302
303
|
finally { setBusy(false); }
|
|
303
304
|
};
|
|
304
305
|
return (
|
|
305
|
-
<Section title={t("project.agent_detail.memory_title")} description={`~/.apx/projects/<id>/agents/${slug}/memory.md —
|
|
306
|
+
<Section title={t("project.agent_detail.memory_title")} description={`~/.apx/projects/<id>/agents/${slug}/memory.md — ${t("agents_ui.memory_durable_desc")}`}>
|
|
306
307
|
<Textarea rows={16} className="font-mono text-xs" value={value} onChange={(e) => setValue(e.target.value)} placeholder={t("project.agent_detail.memory_empty")} />
|
|
307
308
|
<div className="mt-2 flex items-center justify-between">
|
|
308
309
|
<span className="text-[11px] text-muted-fg">{value.length} {t("project.memories.chars")}</span>
|
|
@@ -361,14 +362,14 @@ function SleepView({ routines }: { routines: RoutineEntry[] }) {
|
|
|
361
362
|
<div className="flex items-center gap-2">
|
|
362
363
|
<span className={cn("size-2 rounded-full", err ? "bg-destructive" : running ? "bg-emerald-400" : "bg-muted-fg/40")} />
|
|
363
364
|
<span className="text-sm font-medium">{r.name}</span>
|
|
364
|
-
<Badge tone={running ? "success" : "muted"}>{running ? "running" : "paused"}</Badge>
|
|
365
|
-
{err && <Badge tone="danger">
|
|
365
|
+
<Badge tone={running ? "success" : "muted"}>{running ? t("agents_ui.running") : t("agents_ui.paused")}</Badge>
|
|
366
|
+
{err && <Badge tone="danger">{t("agents_ui.last_error")}</Badge>}
|
|
366
367
|
</div>
|
|
367
368
|
<div className="mt-2 grid grid-cols-2 gap-2 text-xs sm:grid-cols-4">
|
|
368
|
-
<Field2 label="
|
|
369
|
-
<Field2 label="
|
|
370
|
-
<Field2 label="
|
|
371
|
-
<Field2 label="
|
|
369
|
+
<Field2 label={t("agents_ui.field_tick")} value={r.schedule} />
|
|
370
|
+
<Field2 label={t("agents_ui.field_next_tick")} value={r.next_run_at ? new Date(r.next_run_at).toLocaleString() : "—"} />
|
|
371
|
+
<Field2 label={t("agents_ui.field_last_tick")} value={r.last_run_at ? new Date(r.last_run_at).toLocaleString() : "—"} />
|
|
372
|
+
<Field2 label={t("agents_ui.field_last_run")} value={r.last_status || "—"} />
|
|
372
373
|
</div>
|
|
373
374
|
{r.last_error && <p className="mt-2 rounded-md bg-destructive/10 px-2 py-1 text-[11px] text-destructive">{r.last_error}</p>}
|
|
374
375
|
</div>
|
|
@@ -399,7 +400,7 @@ function ToolsPicker({ value, onChange }: { value: string; onChange: (v: string)
|
|
|
399
400
|
};
|
|
400
401
|
const custom = selected.filter((s) => !catalog.some((tool) => tool.name === s));
|
|
401
402
|
return (
|
|
402
|
-
<Field label="
|
|
403
|
+
<Field label={t("agents_ui.tools_label")} hint={t("project.agent_detail.tools_hint")}>
|
|
403
404
|
<div className="flex flex-wrap gap-1.5">
|
|
404
405
|
{catalog.map((tool) => {
|
|
405
406
|
const on = selected.includes(tool.name);
|
|
@@ -105,7 +105,7 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
105
105
|
pid={pid}
|
|
106
106
|
agents={agentList}
|
|
107
107
|
superAgentSlug={ROBY_SLUG}
|
|
108
|
-
superAgentLabel={
|
|
108
|
+
superAgentLabel={t("agents_ui.super_agent_label", { persona })}
|
|
109
109
|
selected={selected}
|
|
110
110
|
onSelect={setSelected}
|
|
111
111
|
onNewChat={onNewChat}
|
|
@@ -119,7 +119,7 @@ export function ChatTab({ pid }: { pid: string }) {
|
|
|
119
119
|
</div>
|
|
120
120
|
<div className="flex items-center gap-2">
|
|
121
121
|
{activeIsRoby ? (
|
|
122
|
-
<Badge tone="success">
|
|
122
|
+
<Badge tone="success">{t("agents_ui.super_agent_badge")}</Badge>
|
|
123
123
|
) : (
|
|
124
124
|
activeAgent?.model && <Badge tone="info">{activeAgent.model}</Badge>
|
|
125
125
|
)}
|
|
@@ -7,7 +7,7 @@ import { Section } from "../../components/Section";
|
|
|
7
7
|
import { Button, Dialog, Empty, Loading } from "../../components/ui";
|
|
8
8
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../components/ui/tabs";
|
|
9
9
|
import { ConfigTabsEditor } from "../../components/config/ConfigTabsEditor";
|
|
10
|
-
import {
|
|
10
|
+
import { apcProjectSections, projectOverrideSections } from "../../components/config/project-config-sections";
|
|
11
11
|
import { useToast } from "../../components/Toast";
|
|
12
12
|
import { useProject } from "../../hooks/useProjects";
|
|
13
13
|
import { flattenObject } from "../../lib/config-values";
|
|
@@ -48,7 +48,7 @@ export function ConfigTab({ pid }: { pid: string }) {
|
|
|
48
48
|
|
|
49
49
|
<TabsContent value="override">
|
|
50
50
|
<ConfigTabsEditor
|
|
51
|
-
sections={
|
|
51
|
+
sections={projectOverrideSections()}
|
|
52
52
|
source={cfg.data.project_only}
|
|
53
53
|
placeholderSource={cfg.data.effective}
|
|
54
54
|
jsonTitle={cfg.data.project_config_path}
|
|
@@ -65,7 +65,7 @@ export function ConfigTab({ pid }: { pid: string }) {
|
|
|
65
65
|
|
|
66
66
|
<TabsContent value="project">
|
|
67
67
|
<ConfigTabsEditor
|
|
68
|
-
sections={
|
|
68
|
+
sections={apcProjectSections()}
|
|
69
69
|
source={cfg.data.apc_project || {}}
|
|
70
70
|
jsonTitle={cfg.data.project_json_path}
|
|
71
71
|
jsonDescription=".apc/project.json. Metadata APC portable."
|
|
@@ -18,12 +18,6 @@ type DialogMode =
|
|
|
18
18
|
| { kind: "new" }
|
|
19
19
|
| { kind: "edit"; entry: McpEntry };
|
|
20
20
|
|
|
21
|
-
const SOURCE_LABEL: Record<string, string> = {
|
|
22
|
-
apc: "Shared",
|
|
23
|
-
runtime: "Runtime",
|
|
24
|
-
global: "Global",
|
|
25
|
-
};
|
|
26
|
-
|
|
27
21
|
const SOURCE_TONE: Record<string, "info" | "muted" | "success"> = {
|
|
28
22
|
apc: "info",
|
|
29
23
|
runtime: "success",
|
|
@@ -37,7 +31,10 @@ function sourceToScope(source: string): McpScope {
|
|
|
37
31
|
}
|
|
38
32
|
|
|
39
33
|
function sourceLabel(source: string): string {
|
|
40
|
-
|
|
34
|
+
if (source === "apc") return t("project.mcps.scope_shared");
|
|
35
|
+
if (source === "runtime") return t("project.mcps.scope_runtime");
|
|
36
|
+
if (source === "global") return t("project.mcps.scope_global");
|
|
37
|
+
return source;
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
export function McpsTab({ pid }: { pid: string }) {
|
|
@@ -505,12 +502,12 @@ function ArgsList({
|
|
|
505
502
|
<VarTokenInput
|
|
506
503
|
value={a}
|
|
507
504
|
onChange={(v) => update(i, v)}
|
|
508
|
-
placeholder="
|
|
505
|
+
placeholder={t("agents_ui.arg_placeholder")}
|
|
509
506
|
varNames={varNames}
|
|
510
507
|
onCreateVar={onCreateVar}
|
|
511
508
|
/>
|
|
512
509
|
</div>
|
|
513
|
-
<Button type="button" size="sm" variant="ghost" onClick={() => remove(i)} aria-label="
|
|
510
|
+
<Button type="button" size="sm" variant="ghost" onClick={() => remove(i)} aria-label={t("agents_ui.remove_arg")}>
|
|
514
511
|
<Trash2 size={13} />
|
|
515
512
|
</Button>
|
|
516
513
|
</div>
|
|
@@ -16,14 +16,19 @@ function splitLines(v: string): string[] {
|
|
|
16
16
|
type Kind = RoutineEntry["kind"];
|
|
17
17
|
|
|
18
18
|
// Friendly action types (maps to routines.js kinds).
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
19
|
+
function kindMeta(): Record<Kind, { label: string; desc: string; icon: typeof Bot }> {
|
|
20
|
+
return {
|
|
21
|
+
exec_agent: { label: t("agents_ui.kind_exec_agent"), desc: t("agents_ui.kind_exec_agent_desc"), icon: Bot },
|
|
22
|
+
super_agent: { label: t("agents_ui.kind_super_agent"), desc: t("agents_ui.kind_super_agent_desc"), icon: Crown },
|
|
23
|
+
telegram: { label: t("agents_ui.kind_telegram"), desc: t("agents_ui.kind_telegram_desc"), icon: Send },
|
|
24
|
+
shell: { label: t("agents_ui.kind_shell"), desc: t("agents_ui.kind_shell_desc"), icon: Terminal },
|
|
25
|
+
heartbeat: { label: t("agents_ui.kind_heartbeat"), desc: t("agents_ui.kind_heartbeat_desc"), icon: Heart },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function kindOptions() {
|
|
29
|
+
const meta = kindMeta();
|
|
30
|
+
return (Object.keys(meta) as Kind[]).map((k) => ({ value: k, label: meta[k].label, description: meta[k].desc, icon: meta[k].icon }));
|
|
31
|
+
}
|
|
27
32
|
|
|
28
33
|
// "every:10m" → "cada 10 minutos", cron/once → legible.
|
|
29
34
|
function scheduleHuman(s?: string): string {
|
|
@@ -33,42 +38,51 @@ function scheduleHuman(s?: string): string {
|
|
|
33
38
|
const m = v.match(/^(\d+)(s|m|h|d)$/);
|
|
34
39
|
if (m) {
|
|
35
40
|
const n = m[1];
|
|
36
|
-
const unit = {
|
|
37
|
-
|
|
41
|
+
const unit = {
|
|
42
|
+
s: t("agents_ui.unit_seconds"),
|
|
43
|
+
m: t("agents_ui.unit_minutes"),
|
|
44
|
+
h: t("agents_ui.unit_hours"),
|
|
45
|
+
d: t("agents_ui.unit_days"),
|
|
46
|
+
}[m[2]] || m[2];
|
|
47
|
+
return t("agents_ui.every_n_unit", { n, unit });
|
|
38
48
|
}
|
|
39
|
-
return
|
|
49
|
+
return t("agents_ui.every_v", { v });
|
|
40
50
|
}
|
|
41
|
-
if (s.startsWith("once:")) return `
|
|
51
|
+
if (s.startsWith("once:")) return `once · ${new Date(s.slice(5)).toLocaleString()}`;
|
|
42
52
|
if (s.startsWith("cron ")) return `cron · ${s.slice(5)}`;
|
|
43
53
|
return s;
|
|
44
54
|
}
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
function schedPresets() {
|
|
57
|
+
return [
|
|
58
|
+
{ label: t("agents_ui.preset_every_10m"), value: "every:10m" },
|
|
59
|
+
{ label: t("agents_ui.preset_hourly"), value: "every:1h" },
|
|
60
|
+
{ label: t("agents_ui.preset_daily_9am"), value: "cron 0 9 * * *" },
|
|
61
|
+
{ label: t("agents_ui.preset_weekdays_9am"), value: "cron 0 9 * * 1-5" },
|
|
62
|
+
];
|
|
63
|
+
}
|
|
52
64
|
|
|
53
65
|
// Template/env vars the routine runner exposes (src/core/routines/runner.js).
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
function routineVars() {
|
|
67
|
+
return [
|
|
68
|
+
{ v: "{{pre_output}}", where: "prompt", desc: t("agents_ui.var_pre_output_prompt") },
|
|
69
|
+
{ v: "$APX_LLM_OUTPUT", where: "post", desc: t("agents_ui.var_llm_output") },
|
|
70
|
+
{ v: "$APX_STATUS", where: "post", desc: t("agents_ui.var_status") },
|
|
71
|
+
{ v: "$APX_SKIPPED", where: "post", desc: t("agents_ui.var_skipped") },
|
|
72
|
+
{ v: "$APX_PRE_OUTPUT", where: "post", desc: t("agents_ui.var_pre_output") },
|
|
73
|
+
{ v: "$APX_PRE_OUTPUT_FILE", where: "post", desc: t("agents_ui.var_pre_output_file") },
|
|
74
|
+
{ v: "$APX_PRE_EXIT", where: "post", desc: t("agents_ui.var_pre_exit") },
|
|
75
|
+
{ v: "$APX_ROUTINE", where: "pre/post", desc: t("agents_ui.var_routine") },
|
|
76
|
+
];
|
|
77
|
+
}
|
|
64
78
|
|
|
65
79
|
function actionSummary(kind: Kind, spec: Record<string, unknown>): string {
|
|
66
80
|
switch (kind) {
|
|
67
|
-
case "exec_agent": return spec.agent ?
|
|
68
|
-
case "super_agent": return "
|
|
69
|
-
case "telegram": return
|
|
70
|
-
case "shell": return spec.command ?
|
|
71
|
-
case "heartbeat": return "
|
|
81
|
+
case "exec_agent": return spec.agent ? t("agents_ui.summary_runs_agent", { agent: String(spec.agent) }) : t("agents_ui.summary_runs_agent_none");
|
|
82
|
+
case "super_agent": return t("agents_ui.summary_super_agent");
|
|
83
|
+
case "telegram": return t("agents_ui.summary_telegram", { channel: String(spec.channel || "default") });
|
|
84
|
+
case "shell": return spec.command ? t("agents_ui.summary_runs_cmd", { cmd: String(spec.command).slice(0, 40) }) : t("agents_ui.summary_shell");
|
|
85
|
+
case "heartbeat": return t("agents_ui.summary_heartbeat");
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
88
|
|
|
@@ -103,7 +117,7 @@ export function RoutinesTab({ pid }: { pid: string }) {
|
|
|
103
117
|
{!list.isLoading && (list.data?.length ?? 0) === 0 && <Empty>{t("project.routines.empty")}</Empty>}
|
|
104
118
|
<ul className="space-y-2 text-sm">
|
|
105
119
|
{(list.data || []).map((row) => {
|
|
106
|
-
const meta =
|
|
120
|
+
const meta = kindMeta()[row.kind];
|
|
107
121
|
const Icon = meta?.icon || Zap;
|
|
108
122
|
const err = row.last_status === "error";
|
|
109
123
|
return (
|
|
@@ -123,7 +137,7 @@ export function RoutinesTab({ pid }: { pid: string }) {
|
|
|
123
137
|
</div>
|
|
124
138
|
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
|
|
125
139
|
<Switch checked={row.enabled} onChange={() => toggle(row)} />
|
|
126
|
-
<Button size="sm" variant="secondary" onClick={() => runNow(row)}><Play size={13} />
|
|
140
|
+
<Button size="sm" variant="secondary" onClick={() => runNow(row)}><Play size={13} /> {t("common.run")}</Button>
|
|
127
141
|
<Button size="sm" variant="destructive" onClick={() => remove(row)}><Trash2 size={13} /></Button>
|
|
128
142
|
</div>
|
|
129
143
|
</div>
|
|
@@ -132,7 +146,7 @@ export function RoutinesTab({ pid }: { pid: string }) {
|
|
|
132
146
|
<span>{actionSummary(row.kind, row.spec || {})}</span>
|
|
133
147
|
{row.next_run_at && <span>{t("project.routines.next_run")} {new Date(row.next_run_at).toLocaleString()}</span>}
|
|
134
148
|
<span className={cn(row.last_status === "ok" && "text-emerald-500", err && "text-destructive")}>
|
|
135
|
-
|
|
149
|
+
{t("agents_ui.last_label")} {row.last_status || "—"}
|
|
136
150
|
</span>
|
|
137
151
|
</div>
|
|
138
152
|
{row.last_error && <div className="mt-2 rounded-md bg-destructive/10 px-2 py-1 text-xs text-destructive">{row.last_error}</div>}
|
|
@@ -228,20 +242,20 @@ function RoutineEditor({
|
|
|
228
242
|
const postSteps = usesPrePost ? splitLines(post) : [];
|
|
229
243
|
const actionLabel = (() => {
|
|
230
244
|
switch (kind) {
|
|
231
|
-
case "exec_agent": return agent ?
|
|
232
|
-
case "super_agent": return "
|
|
233
|
-
case "telegram": return
|
|
234
|
-
case "shell": return command ?
|
|
235
|
-
case "heartbeat": return "
|
|
245
|
+
case "exec_agent": return agent ? t("agents_ui.action_agent_answers", { agent }) : t("agents_ui.action_agent_pick_answers");
|
|
246
|
+
case "super_agent": return t("agents_ui.action_super_answers");
|
|
247
|
+
case "telegram": return t("agents_ui.action_telegram_channel", { channel: tgChannel });
|
|
248
|
+
case "shell": return command ? t("agents_ui.summary_runs_cmd", { cmd: command.slice(0, 48) }) : t("agents_ui.action_runs_shell");
|
|
249
|
+
case "heartbeat": return t("agents_ui.summary_heartbeat");
|
|
236
250
|
}
|
|
237
251
|
})();
|
|
238
252
|
const usesPrompt = usesPrePost;
|
|
239
253
|
|
|
240
|
-
const ActionIcon =
|
|
254
|
+
const ActionIcon = kindMeta()[kind].icon;
|
|
241
255
|
const steps = [
|
|
242
|
-
...preSteps.map((c, i) => ({ id: `pre-${i}`, icon: Terminal, label: "
|
|
256
|
+
...preSteps.map((c, i) => ({ id: `pre-${i}`, icon: Terminal, label: t("agents_ui.step_pre"), detail: c, action: false })),
|
|
243
257
|
{ id: "action", icon: ActionIcon, label: actionLabel, detail: usesPrompt && prompt ? prompt.slice(0, 90) : undefined, action: true },
|
|
244
|
-
...postSteps.map((c, i) => ({ id: `post-${i}`, icon: Terminal, label: "
|
|
258
|
+
...postSteps.map((c, i) => ({ id: `post-${i}`, icon: Terminal, label: t("agents_ui.step_post"), detail: c, action: false })),
|
|
245
259
|
];
|
|
246
260
|
|
|
247
261
|
return (
|
|
@@ -272,9 +286,9 @@ function RoutineEditor({
|
|
|
272
286
|
<Input value={name} disabled={!!draft?.name} onChange={(e) => setName(e.target.value)} placeholder="resumen-diario" />
|
|
273
287
|
</Field>
|
|
274
288
|
<Field label={t("project.routines.kind_field")}>
|
|
275
|
-
<UiSelect value={kind} onChange={(v) => setKind(v as Kind)} options={
|
|
289
|
+
<UiSelect value={kind} onChange={(v) => setKind(v as Kind)} options={kindOptions()} />
|
|
276
290
|
</Field>
|
|
277
|
-
<p className="-mt-1 text-[11px] text-muted-fg">{
|
|
291
|
+
<p className="-mt-1 text-[11px] text-muted-fg">{kindMeta()[kind].desc}</p>
|
|
278
292
|
{kind === "exec_agent" && (
|
|
279
293
|
<Field label={t("project.routines.agent_field")} hint={t("project.routines.agent_hint")}>
|
|
280
294
|
<UiSelect value={agent} onChange={setAgent} placeholder={agentsList.isLoading ? t("project.routines.agent_loading") : t("project.routines.agent_pick")}
|
|
@@ -284,7 +298,7 @@ function RoutineEditor({
|
|
|
284
298
|
<Field label={t("project.routines.schedule_field")} hint={t("project.routines.schedule_hint")}>
|
|
285
299
|
<div className="space-y-2">
|
|
286
300
|
<div className="flex flex-wrap gap-1">
|
|
287
|
-
{
|
|
301
|
+
{schedPresets().map((s) => (
|
|
288
302
|
<button key={s.value} type="button" onClick={() => setSchedule(s.value)}
|
|
289
303
|
className={cn("rounded-md border px-2 py-0.5 text-[11px]", schedule === s.value ? "border-emerald-500/50 text-emerald-400" : "border-border text-muted-fg hover:text-foreground")}>
|
|
290
304
|
{s.label}
|
|
@@ -292,7 +306,7 @@ function RoutineEditor({
|
|
|
292
306
|
))}
|
|
293
307
|
<button type="button" onClick={() => setSchedule("manual")}
|
|
294
308
|
className={cn("rounded-md border px-2 py-0.5 text-[11px]", schedule === "manual" ? "border-emerald-500/50 text-emerald-400" : "border-border text-muted-fg hover:text-foreground")}>
|
|
295
|
-
|
|
309
|
+
{t("agents_ui.preset_manual")}
|
|
296
310
|
</button>
|
|
297
311
|
</div>
|
|
298
312
|
<Input value={schedule} onChange={(e) => setSchedule(e.target.value)} placeholder="every:10m · cron 0 9 * * 1-5 · once:ISO · manual" />
|
|
@@ -320,10 +334,10 @@ function RoutineEditor({
|
|
|
320
334
|
<>
|
|
321
335
|
<div className="grid grid-cols-2 gap-3">
|
|
322
336
|
<Field label={t("project.routines.tg_channel")}><Input value={tgChannel} onChange={(e) => setTgChannel(e.target.value)} placeholder="default" /></Field>
|
|
323
|
-
<Field label={t("project.routines.tg_chat_id")}><Input value={tgChatId} onChange={(e) => setTgChatId(e.target.value)} placeholder="
|
|
337
|
+
<Field label={t("project.routines.tg_chat_id")}><Input value={tgChatId} onChange={(e) => setTgChatId(e.target.value)} placeholder={t("agents_ui.tg_chat_id_ph")} /></Field>
|
|
324
338
|
</div>
|
|
325
339
|
<Field label={t("project.routines.tg_text")} hint={t("project.routines.tg_text_hint")}>
|
|
326
|
-
<Textarea rows={8} value={tgText} onChange={(e) => setTgText(e.target.value)} placeholder="
|
|
340
|
+
<Textarea rows={8} value={tgText} onChange={(e) => setTgText(e.target.value)} placeholder={t("agents_ui.tg_text_ph")} />
|
|
327
341
|
</Field>
|
|
328
342
|
</>
|
|
329
343
|
)}
|
|
@@ -339,7 +353,7 @@ function RoutineEditor({
|
|
|
339
353
|
{kind === "heartbeat" && (
|
|
340
354
|
<div className="grid grid-cols-2 gap-3">
|
|
341
355
|
<Field label={t("project.routines.hb_channel")}><Input value={hbChannel} onChange={(e) => setHbChannel(e.target.value)} placeholder="heartbeat" /></Field>
|
|
342
|
-
<Field label={t("project.routines.hb_message")}><Input value={hbMessage} onChange={(e) => setHbMessage(e.target.value)} placeholder="
|
|
356
|
+
<Field label={t("project.routines.hb_message")}><Input value={hbMessage} onChange={(e) => setHbMessage(e.target.value)} placeholder={t("agents_ui.hb_message_ph")} /></Field>
|
|
343
357
|
</div>
|
|
344
358
|
)}
|
|
345
359
|
|
|
@@ -355,7 +369,7 @@ function RoutineEditor({
|
|
|
355
369
|
<div className="rounded-lg border border-border bg-muted/10 p-3">
|
|
356
370
|
<div className="mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-fg">{t("project.routines.vars_title")}</div>
|
|
357
371
|
<div className="flex flex-wrap gap-1.5">
|
|
358
|
-
{
|
|
372
|
+
{routineVars().map((v) => (
|
|
359
373
|
<span key={v.v} title={v.desc} className="inline-flex items-center gap-1 rounded-md border border-border bg-card px-1.5 py-0.5 font-mono text-[10px]">
|
|
360
374
|
{v.v}<span className="not-italic text-muted-fg">· {v.where}</span>
|
|
361
375
|
</span>
|
|
@@ -97,7 +97,7 @@ export function TelegramTab({ pid }: { pid: string }) {
|
|
|
97
97
|
{enabled && (
|
|
98
98
|
<>
|
|
99
99
|
<div className="grid grid-cols-2 gap-3">
|
|
100
|
-
<Field label={t("project.telegram.bot_token")} hint={existing?.bot_token ? `${secretHint(existing.bot_token)}
|
|
100
|
+
<Field label={t("project.telegram.bot_token")} hint={existing?.bot_token ? `${secretHint(existing.bot_token)} ${t("telegram_ui.empty_keep")}` : t("project.telegram.bot_hint_none")}>
|
|
101
101
|
<Input type="password" value={botToken} onChange={(e) => setBotToken(e.target.value)} placeholder={existing?.bot_token ? secretHint(existing.bot_token) : ""} />
|
|
102
102
|
</Field>
|
|
103
103
|
<Field label={t("project.telegram.chat_id")}>
|