@agentprojectcontext/apx 1.66.0 → 1.68.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/package.json +3 -2
- package/skills/apx/SKILL.md +3 -0
- package/src/core/agent/index.js +2 -0
- package/src/core/agent/judge.js +174 -0
- package/src/core/agent/model-router.js +107 -5
- package/src/core/agent/prompts/modes/code-build.md +1 -1
- package/src/core/agent/run-agent.js +149 -12
- package/src/core/agent/security.js +97 -0
- package/src/core/agent/stuck-detector.js +89 -0
- package/src/core/agent/super-agent.js +58 -17
- package/src/core/agent/tools/handlers/run-subagent.js +117 -0
- package/src/core/agent/tools/helpers.js +11 -1
- package/src/core/agent/tools/names.js +2 -0
- package/src/core/agent/tools/registry.js +10 -0
- package/src/core/artifacts/preview.js +392 -0
- package/src/core/artifacts/tunnel.js +169 -0
- package/src/core/config/index.js +61 -0
- package/src/core/config/redact.js +44 -0
- package/src/core/config/secret-values.js +132 -0
- package/src/core/engines/mock.js +15 -1
- package/src/core/logging.js +10 -3
- package/src/core/memory/compactor.js +65 -56
- package/src/core/memory/summarizer.js +125 -0
- package/src/core/stores/conversations-compactor.js +24 -31
- package/src/host/daemon/api/admin-config.js +5 -0
- package/src/host/daemon/api/artifact-preview.js +82 -0
- package/src/host/daemon/api/config.js +17 -5
- package/src/host/daemon/api/sessions.js +9 -0
- package/src/host/daemon/api/web.js +1 -1
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/index.js +16 -1
- package/src/interfaces/acp/index.js +363 -0
- package/src/interfaces/acp/jsonrpc.js +180 -0
- package/src/interfaces/acp/session.js +205 -0
- package/src/interfaces/cli/commands/acp.js +10 -0
- package/src/interfaces/cli/commands/artifact.js +115 -0
- package/src/interfaces/cli/index.js +74 -0
- package/src/interfaces/web/dist/assets/index-D4BmWoDM.css +1 -0
- package/src/interfaces/web/dist/assets/index-vwd6yQVw.js +803 -0
- package/src/interfaces/web/dist/assets/index-vwd6yQVw.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +9 -9
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +145 -2
- package/src/interfaces/web/src/components/config/ConfigTabsEditor.tsx +46 -31
- package/src/interfaces/web/src/components/config/project-config-sections.ts +9 -11
- package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +162 -0
- package/src/interfaces/web/src/components/settings/RoutingPanel.tsx +236 -0
- package/src/interfaces/web/src/i18n/en.ts +53 -0
- package/src/interfaces/web/src/i18n/es.ts +53 -0
- package/src/interfaces/web/src/lib/api/artifacts.ts +38 -0
- package/src/interfaces/web/src/lib/api/sessions.ts +2 -1
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +50 -60
- package/src/interfaces/web/src/screens/base/ModelsTab.tsx +4 -2
- package/src/interfaces/web/src/screens/base/SessionsTab.tsx +11 -5
- package/src/interfaces/web/src/screens/project/ConfigTab.tsx +110 -25
- package/src/interfaces/web/src/screens/project/MemoriesTab.tsx +7 -128
- package/src/interfaces/web/src/screens/project/Overview.tsx +3 -2
- package/src/interfaces/web/src/types/daemon.ts +16 -0
- package/src/interfaces/web/dist/assets/index-BuII-tAi.css +0 -1
- package/src/interfaces/web/dist/assets/index-YmMRG--4.js +0 -778
- package/src/interfaces/web/dist/assets/index-YmMRG--4.js.map +0 -1
|
@@ -7,15 +7,21 @@ import { Badge, Button, Empty, Input, Loading, Tip } from "../../components/ui";
|
|
|
7
7
|
import { UiSelect } from "../../components/UiSelect";
|
|
8
8
|
import { useToast } from "../../components/Toast";
|
|
9
9
|
import { usePersonaName } from "../../hooks/usePersonaName";
|
|
10
|
+
import { useProject } from "../../hooks/useProjects";
|
|
10
11
|
import { t } from "../../i18n";
|
|
11
12
|
|
|
12
13
|
const ENGINE_TONE: Record<string, "success" | "info" | "warning" | "muted"> = {
|
|
13
14
|
apx: "success", claude: "info", codex: "warning",
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
// `pid` present + not base → scope to that project's local folder. Base (or no
|
|
18
|
+
// pid) shows every session across engines and folders.
|
|
19
|
+
export function SessionsTab({ pid }: { pid?: string } = {}) {
|
|
17
20
|
const toast = useToast();
|
|
18
21
|
const persona = usePersonaName();
|
|
22
|
+
const isBase = !pid || String(pid) === "0";
|
|
23
|
+
const { project } = useProject(isBase ? "" : pid);
|
|
24
|
+
const cwd = isBase ? undefined : project?.path || undefined;
|
|
19
25
|
const [engine, setEngine] = useState("");
|
|
20
26
|
const [input, setInput] = useState("");
|
|
21
27
|
const [query, setQuery] = useState("");
|
|
@@ -28,10 +34,10 @@ export function SessionsTab() {
|
|
|
28
34
|
}, [input]);
|
|
29
35
|
|
|
30
36
|
const paged = usePagedQuery<SessionRow>({
|
|
31
|
-
key: `/sessions?engine=${engine}&q=${query}&deep=${deep ? 1 : 0}`,
|
|
37
|
+
key: `/sessions?engine=${engine}&q=${query}&deep=${deep ? 1 : 0}&cwd=${cwd || ""}`,
|
|
32
38
|
fetchPage: (limit, offset) =>
|
|
33
|
-
Sessions.page({ engine: engine || undefined, q: query || undefined, deep, limit, offset }),
|
|
34
|
-
resetKey: `${engine}|${query}|${deep ? 1 : 0}`,
|
|
39
|
+
Sessions.page({ engine: engine || undefined, q: query || undefined, deep, cwd, limit, offset }),
|
|
40
|
+
resetKey: `${engine}|${query}|${deep ? 1 : 0}|${cwd || ""}`,
|
|
35
41
|
});
|
|
36
42
|
|
|
37
43
|
const clear = () => { setInput(""); setQuery(""); setEngine(""); setDeep(false); };
|
|
@@ -72,7 +78,7 @@ export function SessionsTab() {
|
|
|
72
78
|
<Section
|
|
73
79
|
fullHeight
|
|
74
80
|
title={t("base.sessions_title")}
|
|
75
|
-
description={t("base.sessions_desc")}
|
|
81
|
+
description={isBase ? t("base.sessions_desc") : t("base.sessions_desc_scoped", { path: cwd || "…" })}
|
|
76
82
|
action={
|
|
77
83
|
<Tip content={t("base.sessions_refresh")}>
|
|
78
84
|
<Button size="sm" variant="secondary" onClick={() => paged.mutate()}><RefreshCw size={13} /></Button>
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
2
|
import { useNavigate } from "react-router-dom";
|
|
3
3
|
import useSWR from "swr";
|
|
4
4
|
import { RefreshCw, Trash2 } from "lucide-react";
|
|
5
5
|
import { Projects } from "../../lib/api";
|
|
6
6
|
import { Section } from "../../components/Section";
|
|
7
|
-
import { Button, Dialog, Empty, Loading } from "../../components/ui";
|
|
7
|
+
import { Button, Dialog, Empty, Loading, Textarea } from "../../components/ui";
|
|
8
8
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../components/ui/tabs";
|
|
9
9
|
import { ConfigTabsEditor } from "../../components/config/ConfigTabsEditor";
|
|
10
|
-
import { apcProjectSections,
|
|
10
|
+
import { apcProjectSections, projectSettingsSections, projectEnginesSections } from "../../components/config/project-config-sections";
|
|
11
|
+
import { TelegramTab } from "./TelegramTab";
|
|
11
12
|
import { useToast } from "../../components/Toast";
|
|
12
13
|
import { useProject } from "../../hooks/useProjects";
|
|
13
|
-
import { flattenObject } from "../../lib/config-values";
|
|
14
|
+
import { flattenObject, parseConfigJson } from "../../lib/config-values";
|
|
14
15
|
import { isSecretMarker } from "../../lib/secrets";
|
|
15
16
|
import { t } from "../../i18n";
|
|
16
17
|
|
|
@@ -36,54 +37,91 @@ export function ConfigTab({ pid }: { pid: string }) {
|
|
|
36
37
|
cfg.mutate();
|
|
37
38
|
};
|
|
38
39
|
|
|
40
|
+
const saveOverrideFields = async (set: Record<string, unknown>, unset: string[]) => {
|
|
41
|
+
await Projects.config.set(pid, set);
|
|
42
|
+
if (unset.length) await Projects.config.unset(pid, unset);
|
|
43
|
+
toast.success(t("project.config.save_fields_success"));
|
|
44
|
+
cfg.mutate();
|
|
45
|
+
};
|
|
46
|
+
|
|
39
47
|
return (
|
|
40
48
|
<div className="space-y-6">
|
|
41
49
|
<Section title={t("project.config.section_title")} description={t("project.config.section_desc")}>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<TabsTrigger value="
|
|
46
|
-
<TabsTrigger value="
|
|
50
|
+
{/* Organized by concept: Settings · Engines · Telegram · Project · JSON. */}
|
|
51
|
+
<Tabs defaultValue="settings" className="space-y-4">
|
|
52
|
+
<TabsList className="flex flex-wrap">
|
|
53
|
+
<TabsTrigger value="settings">{t("project.config.tab_settings")}</TabsTrigger>
|
|
54
|
+
<TabsTrigger value="engines">{t("settings_ui.cfg_engines_label")}</TabsTrigger>
|
|
55
|
+
{!isBase && <TabsTrigger value="telegram">{t("project.nav.telegram")}</TabsTrigger>}
|
|
56
|
+
<TabsTrigger value="project">{t("project.config.tab_project")}</TabsTrigger>
|
|
57
|
+
<TabsTrigger value="json">JSON</TabsTrigger>
|
|
47
58
|
</TabsList>
|
|
48
59
|
|
|
49
|
-
<TabsContent value="
|
|
60
|
+
<TabsContent value="settings">
|
|
50
61
|
<ConfigTabsEditor
|
|
51
|
-
sections={
|
|
62
|
+
sections={projectSettingsSections()}
|
|
52
63
|
source={cfg.data.project_only}
|
|
53
64
|
placeholderSource={cfg.data.effective}
|
|
54
65
|
jsonTitle={cfg.data.project_config_path}
|
|
55
|
-
|
|
56
|
-
onSaveFields={async (set, unset) => {
|
|
57
|
-
await Projects.config.set(pid, set);
|
|
58
|
-
if (unset.length) await Projects.config.unset(pid, unset);
|
|
59
|
-
toast.success(t("project.config.save_fields_success"));
|
|
60
|
-
cfg.mutate();
|
|
61
|
-
}}
|
|
66
|
+
onSaveFields={saveOverrideFields}
|
|
62
67
|
onSaveJson={saveOverrideJson}
|
|
68
|
+
hideJson
|
|
63
69
|
/>
|
|
64
70
|
</TabsContent>
|
|
65
71
|
|
|
72
|
+
<TabsContent value="engines">
|
|
73
|
+
<ConfigTabsEditor
|
|
74
|
+
sections={projectEnginesSections()}
|
|
75
|
+
source={cfg.data.project_only}
|
|
76
|
+
placeholderSource={cfg.data.effective}
|
|
77
|
+
jsonTitle={cfg.data.project_config_path}
|
|
78
|
+
onSaveFields={saveOverrideFields}
|
|
79
|
+
onSaveJson={saveOverrideJson}
|
|
80
|
+
hideJson
|
|
81
|
+
/>
|
|
82
|
+
</TabsContent>
|
|
83
|
+
|
|
84
|
+
{!isBase && (
|
|
85
|
+
<TabsContent value="telegram">
|
|
86
|
+
<TelegramTab pid={pid} />
|
|
87
|
+
</TabsContent>
|
|
88
|
+
)}
|
|
89
|
+
|
|
66
90
|
<TabsContent value="project">
|
|
67
91
|
<ConfigTabsEditor
|
|
68
92
|
sections={apcProjectSections()}
|
|
69
93
|
source={cfg.data.apc_project || {}}
|
|
70
94
|
jsonTitle={cfg.data.project_json_path}
|
|
71
|
-
jsonDescription=".apc/project.json. Metadata APC portable."
|
|
72
95
|
onSaveFields={async (set, unset) => {
|
|
73
96
|
await Projects.apcProject.set(pid, cleanSet(set), unset);
|
|
74
97
|
toast.success(t("project.config.save_meta_success"));
|
|
75
98
|
cfg.mutate();
|
|
76
99
|
}}
|
|
77
100
|
onSaveJson={saveProjectJson}
|
|
101
|
+
hideJson
|
|
78
102
|
/>
|
|
79
103
|
</TabsContent>
|
|
80
104
|
|
|
81
|
-
<TabsContent value="
|
|
82
|
-
<div className="space-y-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
105
|
+
<TabsContent value="json">
|
|
106
|
+
<div className="space-y-6">
|
|
107
|
+
<JsonEditor
|
|
108
|
+
title={cfg.data.project_config_path}
|
|
109
|
+
description=".apc/config.json — overrides del proyecto."
|
|
110
|
+
source={cfg.data.project_only}
|
|
111
|
+
onSave={saveOverrideJson}
|
|
112
|
+
/>
|
|
113
|
+
<JsonEditor
|
|
114
|
+
title={cfg.data.project_json_path}
|
|
115
|
+
description=".apc/project.json — metadata APC portable."
|
|
116
|
+
source={cfg.data.apc_project || {}}
|
|
117
|
+
onSave={saveProjectJson}
|
|
118
|
+
/>
|
|
119
|
+
<div className="space-y-2">
|
|
120
|
+
<p className="text-xs text-muted-fg">{t("project.config.effective_read")}</p>
|
|
121
|
+
<pre className="max-h-96 overflow-auto rounded-lg border border-border bg-muted/40 p-3 text-xs">
|
|
122
|
+
{JSON.stringify(cfg.data.effective, null, 2)}
|
|
123
|
+
</pre>
|
|
124
|
+
</div>
|
|
87
125
|
</div>
|
|
88
126
|
</TabsContent>
|
|
89
127
|
</Tabs>
|
|
@@ -216,6 +254,53 @@ function DangerZone({
|
|
|
216
254
|
);
|
|
217
255
|
}
|
|
218
256
|
|
|
257
|
+
// Raw JSON editor for one config file (redacted secrets echo back untouched —
|
|
258
|
+
// the daemon restores real values on save).
|
|
259
|
+
function JsonEditor({
|
|
260
|
+
title,
|
|
261
|
+
description,
|
|
262
|
+
source,
|
|
263
|
+
onSave,
|
|
264
|
+
}: {
|
|
265
|
+
title: string;
|
|
266
|
+
description?: string;
|
|
267
|
+
source: Record<string, unknown>;
|
|
268
|
+
onSave: (next: Record<string, unknown>) => Promise<void>;
|
|
269
|
+
}) {
|
|
270
|
+
const [raw, setRaw] = useState("");
|
|
271
|
+
const [error, setError] = useState("");
|
|
272
|
+
const [busy, setBusy] = useState(false);
|
|
273
|
+
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
setRaw(JSON.stringify(source || {}, null, 2));
|
|
276
|
+
setError("");
|
|
277
|
+
}, [source]);
|
|
278
|
+
|
|
279
|
+
const save = async () => {
|
|
280
|
+
setError("");
|
|
281
|
+
setBusy(true);
|
|
282
|
+
try {
|
|
283
|
+
await onSave(parseConfigJson(raw));
|
|
284
|
+
} catch (e) {
|
|
285
|
+
setError((e as Error).message);
|
|
286
|
+
} finally {
|
|
287
|
+
setBusy(false);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
return (
|
|
292
|
+
<div className="space-y-2">
|
|
293
|
+
<div>
|
|
294
|
+
<h3 className="text-sm font-medium">{title}</h3>
|
|
295
|
+
{description && <p className="text-xs text-muted-fg">{description}</p>}
|
|
296
|
+
</div>
|
|
297
|
+
<Textarea rows={14} className="font-mono text-xs" value={raw} onChange={(e) => setRaw(e.target.value)} />
|
|
298
|
+
{error && <p className="text-xs text-destructive">{error}</p>}
|
|
299
|
+
<Button variant="primary" loading={busy} onClick={save}>{t("settings_ui.save_json")}</Button>
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
219
304
|
function cleanSet(set: Record<string, unknown>) {
|
|
220
305
|
const out: Record<string, unknown> = {};
|
|
221
306
|
for (const [key, value] of Object.entries(flattenObject(set))) {
|
|
@@ -1,134 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import useSWR from "swr";
|
|
3
|
-
import { Bot, Brain, ChevronDown, ChevronRight, Crown, Save } from "lucide-react";
|
|
4
|
-
import { Agents, Projects } from "../../lib/api";
|
|
5
|
-
import type { AgentEntry } from "../../types/daemon";
|
|
6
|
-
import { Section } from "../../components/Section";
|
|
7
|
-
import { Button, Empty, Loading, Textarea } from "../../components/ui";
|
|
8
|
-
import { useToast } from "../../components/Toast";
|
|
9
|
-
import { t } from "../../i18n";
|
|
10
|
-
|
|
11
|
-
// Editable markdown memory block with dirty-tracking + save.
|
|
12
|
-
function MemoryEditor({
|
|
13
|
-
load,
|
|
14
|
-
save,
|
|
15
|
-
rows = 10,
|
|
16
|
-
placeholder,
|
|
17
|
-
}: {
|
|
18
|
-
load: () => Promise<string>;
|
|
19
|
-
save: (body: string) => Promise<void>;
|
|
20
|
-
rows?: number;
|
|
21
|
-
placeholder?: string;
|
|
22
|
-
}) {
|
|
23
|
-
const toast = useToast();
|
|
24
|
-
const [original, setOriginal] = useState<string | null>(null);
|
|
25
|
-
const [value, setValue] = useState("");
|
|
26
|
-
const [busy, setBusy] = useState(false);
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
let live = true;
|
|
30
|
-
load().then((b) => { if (live) { setOriginal(b); setValue(b); } }).catch(() => { if (live) { setOriginal(""); setValue(""); } });
|
|
31
|
-
return () => { live = false; };
|
|
32
|
-
}, [load]);
|
|
33
|
-
|
|
34
|
-
if (original === null) return <Loading />;
|
|
35
|
-
const dirty = value !== original;
|
|
36
|
-
|
|
37
|
-
const onSave = async () => {
|
|
38
|
-
setBusy(true);
|
|
39
|
-
try {
|
|
40
|
-
await save(value);
|
|
41
|
-
setOriginal(value);
|
|
42
|
-
toast.success(t("project.memories.saved"));
|
|
43
|
-
} catch (e) { toast.error((e as Error).message); }
|
|
44
|
-
finally { setBusy(false); }
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<div className="space-y-2">
|
|
49
|
-
<Textarea
|
|
50
|
-
rows={rows}
|
|
51
|
-
className="font-mono text-xs"
|
|
52
|
-
value={value}
|
|
53
|
-
onChange={(e) => setValue(e.target.value)}
|
|
54
|
-
placeholder={placeholder}
|
|
55
|
-
/>
|
|
56
|
-
<div className="flex items-center justify-between">
|
|
57
|
-
<span className="text-[11px] text-muted-fg">{value.length} {t("project.memories.chars")}</span>
|
|
58
|
-
<Button size="sm" variant="primary" loading={busy} disabled={!dirty} onClick={onSave}>
|
|
59
|
-
<Save size={12} /> {t("project.memories.save_btn")}
|
|
60
|
-
</Button>
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function AgentMemoryRow({ pid, agent }: { pid: string; agent: AgentEntry }) {
|
|
67
|
-
const [open, setOpen] = useState(false);
|
|
68
|
-
const Icon = agent.is_master ? Crown : Bot;
|
|
69
|
-
return (
|
|
70
|
-
<li className="rounded-xl border border-border bg-muted/30">
|
|
71
|
-
<button
|
|
72
|
-
type="button"
|
|
73
|
-
onClick={() => setOpen((v) => !v)}
|
|
74
|
-
className="flex w-full items-center gap-3 px-3 py-2 text-left"
|
|
75
|
-
>
|
|
76
|
-
{open ? <ChevronDown size={14} className="text-muted-fg" /> : <ChevronRight size={14} className="text-muted-fg" />}
|
|
77
|
-
<Icon size={14} className={agent.is_master ? "text-violet-400" : "text-muted-fg"} />
|
|
78
|
-
<span className="text-sm font-medium">{agent.slug}</span>
|
|
79
|
-
{agent.role && <span className="text-xs text-muted-fg">· {agent.role}</span>}
|
|
80
|
-
</button>
|
|
81
|
-
{open && (
|
|
82
|
-
<div className="border-t border-border p-3">
|
|
83
|
-
<MemoryEditor
|
|
84
|
-
rows={8}
|
|
85
|
-
load={() => Agents.memory.get(pid, agent.slug).then((r) => r.body)}
|
|
86
|
-
save={(body) => Agents.memory.put(pid, agent.slug, body).then(() => {})}
|
|
87
|
-
/>
|
|
88
|
-
</div>
|
|
89
|
-
)}
|
|
90
|
-
</li>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
1
|
+
import { MemoryBrowser } from "../../components/memory/MemoryBrowser";
|
|
93
2
|
|
|
3
|
+
// Durable memory surface. Same docs-style two-pane browser as /docs: a sidebar
|
|
4
|
+
// listing the project ("General") memory plus every agent's memory, and a shared
|
|
5
|
+
// markdown editor (edit / split-preview / save) on the right. Project memory is
|
|
6
|
+
// .apc/memory.md; agent memory is ~/.apx/projects/<id>/agents/<slug>/memory.md.
|
|
94
7
|
export function MemoriesTab({ pid }: { pid: string }) {
|
|
95
|
-
const agents = useSWR(`/projects/${pid}/agents`, () => Agents.list(pid));
|
|
96
|
-
|
|
97
8
|
return (
|
|
98
|
-
<div className="
|
|
99
|
-
<
|
|
100
|
-
title={t("project.memories.project_title")}
|
|
101
|
-
description={t("project.memories.project_desc")}
|
|
102
|
-
>
|
|
103
|
-
<div className="flex items-start gap-3">
|
|
104
|
-
<div className="mt-1 flex size-9 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-sky-600 to-indigo-600">
|
|
105
|
-
<Brain className="size-4 text-white" />
|
|
106
|
-
</div>
|
|
107
|
-
<div className="min-w-0 flex-1">
|
|
108
|
-
<MemoryEditor
|
|
109
|
-
rows={12}
|
|
110
|
-
load={() => Projects.memory.get(pid).then((r) => r.body)}
|
|
111
|
-
save={(body) => Projects.memory.put(pid, body).then(() => {})}
|
|
112
|
-
placeholder={t("project.memories.project_ph")}
|
|
113
|
-
/>
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
</Section>
|
|
117
|
-
|
|
118
|
-
<Section
|
|
119
|
-
title={t("project.memories.agents_title")}
|
|
120
|
-
description={t("project.memories.agents_desc")}
|
|
121
|
-
>
|
|
122
|
-
{agents.isLoading && <Loading />}
|
|
123
|
-
{!agents.isLoading && (agents.data?.length ?? 0) === 0 && (
|
|
124
|
-
<Empty>{t("project.memories.no_agents")}</Empty>
|
|
125
|
-
)}
|
|
126
|
-
<ul className="space-y-2">
|
|
127
|
-
{(agents.data || []).map((a) => (
|
|
128
|
-
<AgentMemoryRow key={a.slug} pid={pid} agent={a} />
|
|
129
|
-
))}
|
|
130
|
-
</ul>
|
|
131
|
-
</Section>
|
|
9
|
+
<div className="h-full">
|
|
10
|
+
<MemoryBrowser pid={pid} />
|
|
132
11
|
</div>
|
|
133
12
|
);
|
|
134
13
|
}
|
|
@@ -35,8 +35,9 @@ export function Overview({ pid }: { pid: string }) {
|
|
|
35
35
|
<Card title={t("project.overview.artifacts")} value={artifacts.data?.length ?? "…"} href={`/p/${pid}/artifacts`} icon={FileCode2} />
|
|
36
36
|
</div>
|
|
37
37
|
|
|
38
|
-
{/* Task workflow strip
|
|
39
|
-
|
|
38
|
+
{/* Task workflow strip — always shown once the summary loads, so every
|
|
39
|
+
status reads at least 0 (parity with the base dashboard). */}
|
|
40
|
+
{summary.data && (
|
|
40
41
|
<div className="flex flex-wrap gap-2">
|
|
41
42
|
{TASK_STATUS_ORDER.map((s) => (
|
|
42
43
|
<NavLink
|
|
@@ -293,6 +293,22 @@ export interface SuperAgentConfig {
|
|
|
293
293
|
models?: string[];
|
|
294
294
|
order?: string[];
|
|
295
295
|
};
|
|
296
|
+
// Content-based routing (RouterLLM pattern): prefer a model per turn by
|
|
297
|
+
// features. Composes with model_fallback (failover) — see RoutingPanel.
|
|
298
|
+
routing?: {
|
|
299
|
+
enabled?: boolean;
|
|
300
|
+
rules?: Array<{
|
|
301
|
+
model: string;
|
|
302
|
+
when?: {
|
|
303
|
+
has_image?: boolean;
|
|
304
|
+
min_prompt_chars?: number;
|
|
305
|
+
max_prompt_chars?: number;
|
|
306
|
+
min_context_chars?: number;
|
|
307
|
+
channels?: string[];
|
|
308
|
+
keywords?: string[];
|
|
309
|
+
};
|
|
310
|
+
}>;
|
|
311
|
+
};
|
|
296
312
|
}
|
|
297
313
|
|
|
298
314
|
/** ~/.apx/config.json shape (partial — only what we read/write today). */
|