@agentprojectcontext/apx 1.33.1 → 1.35.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 +1 -1
- package/skills/apx/SKILL.md +49 -61
- package/src/core/agent/a2a/reply.js +48 -0
- package/src/core/agent/build-agent-system.js +136 -59
- package/src/core/agent/channels/voice-context.js +98 -0
- package/src/core/agent/memory.js +2 -1
- package/src/core/agent/prompt-builder.js +178 -124
- package/src/core/agent/prompts/channels/code.md +12 -10
- package/src/core/agent/prompts/channels/desktop.md +5 -32
- package/src/core/agent/prompts/channels/telegram.md +4 -15
- package/src/core/agent/prompts/channels/web_code.md +11 -11
- package/src/core/agent/prompts/core/agent-base.md +24 -0
- package/src/core/agent/prompts/core/project-agent.md +11 -0
- package/src/core/agent/prompts/core/super-agent.md +21 -0
- package/src/core/agent/prompts/discipline/action.md +10 -0
- package/src/core/agent/prompts/discipline/single-segment.md +6 -0
- package/src/core/agent/prompts/discipline/two-segment.md +11 -0
- package/src/core/agent/prompts/modes/code-build.md +1 -0
- package/src/core/agent/prompts/modes/code-plan.md +1 -0
- package/src/core/agent/prompts/modes/index.js +28 -0
- package/src/core/agent/self-memory.js +43 -1
- package/src/core/agent/skills/index-store.js +307 -0
- package/src/core/agent/skills/index.js +15 -1
- package/src/core/agent/skills/inspector.js +317 -0
- package/src/core/agent/skills/loader.js +22 -18
- package/src/core/agent/stream/turn-accumulator.js +73 -0
- package/src/core/agent/suggestions.js +37 -0
- package/src/core/agent/super-agent.js +7 -1
- package/src/core/agent/tools/handlers/_git.js +50 -0
- package/src/core/agent/tools/handlers/add-project.js +5 -2
- package/src/core/agent/tools/handlers/call-runtime.js +3 -2
- package/src/core/agent/tools/handlers/git-diff.js +44 -0
- package/src/core/agent/tools/handlers/git-log.js +38 -0
- package/src/core/agent/tools/handlers/git-show.js +34 -0
- package/src/core/agent/tools/handlers/git-status.js +61 -0
- package/src/core/agent/tools/handlers/transcribe-audio.js +1 -1
- package/src/core/agent/tools/helpers.js +2 -2
- package/src/core/agent/tools/names.js +169 -0
- package/src/core/agent/tools/registry-bridge.js +6 -14
- package/src/core/agent/tools/registry.js +103 -69
- package/src/core/apc/context-copy.js +27 -0
- package/src/core/apc/notes.js +19 -0
- package/src/core/apc/parser.js +12 -5
- package/src/core/apc/paths.js +87 -0
- package/src/core/apc/scaffold.js +82 -76
- package/src/core/apc/skill-sync.js +10 -0
- package/src/{host/daemon/plugins → core/channels}/telegram/dispatch.js +38 -16
- package/src/core/config/index.js +24 -2
- package/src/core/config/redact.js +95 -0
- package/src/core/constants/channels.js +2 -0
- package/src/core/constants/code-modes.js +10 -0
- package/src/core/constants/index.js +1 -0
- package/src/core/deck/manifest.js +186 -0
- package/src/core/engines/catalog.js +83 -0
- package/src/core/{tools → http-tools}/browser.js +0 -1
- package/src/core/{tools → http-tools}/fetch.js +0 -1
- package/src/core/{tools → http-tools}/glob.js +0 -1
- package/src/core/{tools → http-tools}/grep.js +0 -1
- package/src/core/{tools → http-tools}/registry.js +0 -1
- package/src/core/{tools → http-tools}/search.js +0 -1
- package/src/core/i18n/en.js +9 -0
- package/src/core/i18n/es.js +12 -0
- package/src/core/i18n/index.js +54 -0
- package/src/core/i18n/pt.js +9 -0
- package/src/core/identity/telegram.js +2 -1
- package/src/core/mcp/runner.js +272 -14
- package/src/core/mcp/sources.js +3 -2
- package/src/core/routines/index.js +16 -0
- package/src/{host/daemon/routines.js → core/routines/runner.js} +36 -103
- package/src/core/runtime-skills/apc-context/SKILL.md +159 -0
- package/src/core/runtime-skills/apx/SKILL.md +83 -0
- package/src/core/runtime-skills/apx-agency-agents/SKILL.md +125 -0
- package/src/core/runtime-skills/apx-agent/SKILL.md +97 -0
- package/src/core/runtime-skills/apx-mcp/SKILL.md +111 -0
- package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +169 -0
- package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +20 -29
- package/src/core/runtime-skills/apx-routine/SKILL.md +127 -0
- package/src/core/runtime-skills/apx-runtime/SKILL.md +99 -0
- package/src/core/runtime-skills/apx-sessions/SKILL.md +232 -0
- package/src/core/runtime-skills/apx-skill-builder/SKILL.md +129 -0
- package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +18 -21
- package/src/core/runtime-skills/apx-telegram/SKILL.md +120 -0
- package/src/core/runtime-skills/apx-voice/SKILL.md +117 -0
- package/src/core/runtime-skills/{claude-code.md → claude-code/SKILL.md} +1 -0
- package/src/core/runtime-skills/{codex-cli.md → codex-cli/SKILL.md} +1 -0
- package/src/core/runtime-skills/{opencode-cli.md → opencode-cli/SKILL.md} +1 -0
- package/src/core/runtime-skills/{openrouter.md → openrouter/SKILL.md} +1 -0
- package/src/{host/daemon/env-detect.js → core/runtimes/detect.js} +1 -1
- package/src/core/stores/code-sessions.js +50 -2
- package/src/core/stores/routine-memory.js +1 -1
- package/src/core/stores/sessions-search.js +121 -0
- package/src/core/stores/sessions.js +38 -0
- package/src/core/vars/index.js +14 -0
- package/src/core/vars/interpolate.js +86 -0
- package/src/core/vars/sources.js +151 -0
- package/src/core/voice/audio-decode.js +38 -0
- package/src/core/voice/transcription.js +225 -0
- package/src/host/daemon/api/admin-config.js +5 -82
- package/src/host/daemon/api/agents.js +5 -5
- package/src/host/daemon/api/code.js +17 -169
- package/src/host/daemon/api/config.js +3 -4
- package/src/host/daemon/api/conversations.js +8 -29
- package/src/host/daemon/api/deck.js +37 -404
- package/src/host/daemon/api/engines.js +1 -80
- package/src/host/daemon/api/exec.js +1 -1
- package/src/host/daemon/api/mcps.js +32 -0
- package/src/host/daemon/api/routines.js +1 -1
- package/src/host/daemon/api/runtimes.js +4 -3
- package/src/host/daemon/api/sessions-search.js +24 -140
- package/src/host/daemon/api/sessions.js +12 -30
- package/src/host/daemon/api/shared.js +2 -1
- package/src/host/daemon/api/skills.js +140 -6
- package/src/host/daemon/api/super-agent.js +56 -1
- package/src/host/daemon/api/telegram.js +1 -11
- package/src/host/daemon/api/tools.js +6 -6
- package/src/host/daemon/api/transcribe.js +2 -2
- package/src/host/daemon/api/vars.js +137 -0
- package/src/host/daemon/api/voice.js +13 -290
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/db.js +6 -6
- package/src/host/daemon/deck-exec.js +148 -0
- package/src/host/daemon/index.js +20 -3
- package/src/host/daemon/plugins/telegram/index.js +9 -9
- package/src/host/daemon/routines-scheduler.js +64 -0
- package/src/host/daemon/smoke.js +3 -2
- package/src/host/daemon/whisper-server.js +225 -0
- package/src/interfaces/cli/branding.js +53 -0
- package/src/interfaces/cli/commands/agent.js +3 -2
- package/src/interfaces/cli/commands/command.js +2 -3
- package/src/interfaces/cli/commands/messages.js +6 -2
- package/src/interfaces/cli/commands/pair.js +5 -4
- package/src/interfaces/cli/commands/search.js +1 -1
- package/src/interfaces/cli/commands/sessions.js +3 -2
- package/src/interfaces/cli/commands/skills.js +290 -55
- package/src/interfaces/cli/index.js +84 -2
- package/src/interfaces/web/dist/assets/index-C0fm31dY.js +618 -0
- package/src/interfaces/web/dist/assets/index-C0fm31dY.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-UcAqlBO6.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +182 -182
- package/src/interfaces/web/src/components/ModelCombobox.tsx +2 -1
- package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +1 -1
- package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +76 -0
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +37 -4
- package/src/interfaces/web/src/components/chat/MessageList.tsx +23 -1
- package/src/interfaces/web/src/components/chat/ModelPicker.tsx +3 -1
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +4 -4
- package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeFileTree.tsx +3 -2
- package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +3 -2
- package/src/interfaces/web/src/components/code/CodeTerminal.tsx +3 -2
- package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -1
- package/src/interfaces/web/src/components/deck/WidgetRow.tsx +2 -1
- package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +93 -0
- package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +449 -0
- package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +2 -1
- package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +2 -2
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +73 -4
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +222 -0
- package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +3 -2
- package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +3 -2
- package/src/interfaces/web/src/components/ui/chat-input.tsx +5 -4
- package/src/interfaces/web/src/components/ui/sidebar.tsx +3 -2
- package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +2 -1
- package/src/interfaces/web/src/constants/index.ts +1 -1
- package/src/interfaces/web/src/hooks/useChat.ts +19 -0
- package/src/interfaces/web/src/i18n/en.ts +175 -7
- package/src/interfaces/web/src/i18n/es.ts +180 -15
- package/src/interfaces/web/src/lib/api/mcps.ts +25 -0
- package/src/interfaces/web/src/lib/api/skills.ts +70 -0
- package/src/interfaces/web/src/lib/api/vars.ts +38 -0
- package/src/interfaces/web/src/lib/api.ts +1 -0
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -31
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +6 -2
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +4 -3
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +7 -6
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +4 -3
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/project/ConfigTab.tsx +132 -1
- package/src/interfaces/web/src/screens/project/McpsTab.tsx +549 -104
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/VarsTab.tsx +300 -0
- package/src/interfaces/web/src/types/daemon.ts +15 -0
- package/skills/apx-agency-agents/SKILL.md +0 -141
- package/skills/apx-agent/SKILL.md +0 -100
- package/skills/apx-mcp-builder/SKILL.md +0 -183
- package/skills/apx-routine/SKILL.md +0 -140
- package/skills/apx-runtime/SKILL.md +0 -117
- package/skills/apx-sessions/SKILL.md +0 -281
- package/skills/apx-skill-builder/SKILL.md +0 -153
- package/skills/apx-telegram/SKILL.md +0 -131
- package/skills/apx-voice/SKILL.md +0 -137
- package/src/core/agent/prompts/action-discipline.md +0 -24
- package/src/core/agent/prompts/super-agent-base.md +0 -42
- package/src/host/daemon/transcription.js +0 -538
- package/src/host/daemon/whisper-transcribe.py +0 -73
- package/src/interfaces/web/dist/assets/index-Aaiw8BZN.css +0 -1
- package/src/interfaces/web/dist/assets/index-DPqtjDjh.js +0 -602
- package/src/interfaces/web/dist/assets/index-DPqtjDjh.js.map +0 -1
- /package/src/{host/daemon → core/apc}/projects-helpers.js +0 -0
- /package/src/{host/daemon/plugins → core/channels}/telegram/ask.js +0 -0
- /package/src/{host/daemon/plugins → core/channels}/telegram/helpers.js +0 -0
- /package/src/{host/daemon/plugins → core/channels}/telegram/media.js +0 -0
- /package/src/core/{tools → http-tools}/index.js +0 -0
- /package/src/{host/daemon/compact.js → core/stores/conversations-compactor.js} +0 -0
- /package/src/{host/daemon → core/stores}/conversations.js +0 -0
- /package/src/{host/daemon → core/util}/thinking.js +0 -0
|
@@ -50,7 +50,7 @@ const SCHED_PRESETS = [
|
|
|
50
50
|
{ label: "días hábiles 9am", value: "cron 0 9 * * 1-5" },
|
|
51
51
|
];
|
|
52
52
|
|
|
53
|
-
// Template/env vars the routine runner exposes (src/
|
|
53
|
+
// Template/env vars the routine runner exposes (src/core/routines/runner.js).
|
|
54
54
|
const VARS = [
|
|
55
55
|
{ v: "{{pre_output}}", where: "prompt", desc: "Salida de los pre-commands, inyectada en el prompt." },
|
|
56
56
|
{ v: "$APX_LLM_OUTPUT", where: "post", desc: "Respuesta del agente / super-agente." },
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import useSWR from "swr";
|
|
3
|
+
import { Eye, EyeOff, Pencil, Plus, Trash2 } from "lucide-react";
|
|
4
|
+
import { Vars, type VarScope, type VarsList } from "../../lib/api";
|
|
5
|
+
import { Section } from "../../components/Section";
|
|
6
|
+
import { Badge, Button, Dialog, Empty, Field, Input, Loading, Switch } from "../../components/ui";
|
|
7
|
+
import { UiSelect } from "../../components/UiSelect";
|
|
8
|
+
import { useToast } from "../../components/Toast";
|
|
9
|
+
import { t } from "../../i18n";
|
|
10
|
+
|
|
11
|
+
interface Row {
|
|
12
|
+
name: string;
|
|
13
|
+
scope: VarScope;
|
|
14
|
+
masked: string;
|
|
15
|
+
revealed?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function VarsTab({ pid }: { pid: string }) {
|
|
19
|
+
const toast = useToast();
|
|
20
|
+
const isBase = String(pid) === "0";
|
|
21
|
+
const [filter, setFilter] = useState<"all" | "project" | "global">(
|
|
22
|
+
isBase ? "global" : "all",
|
|
23
|
+
);
|
|
24
|
+
const [revealAll, setRevealAll] = useState(false);
|
|
25
|
+
const list = useSWR<VarsList>(
|
|
26
|
+
`/projects/${pid}/vars?reveal=${revealAll ? 1 : 0}`,
|
|
27
|
+
() => Vars.list(pid, { reveal: revealAll }),
|
|
28
|
+
);
|
|
29
|
+
const [openCreate, setOpenCreate] = useState<{ name?: string; value?: string; scope?: VarScope } | null>(null);
|
|
30
|
+
|
|
31
|
+
const rows: Row[] = useMemo(() => {
|
|
32
|
+
if (!list.data) return [];
|
|
33
|
+
const out: Row[] = [];
|
|
34
|
+
const proj = list.data.project || {};
|
|
35
|
+
const glob = list.data.global || {};
|
|
36
|
+
for (const [name, masked] of Object.entries(proj)) {
|
|
37
|
+
out.push({ name, scope: "project", masked });
|
|
38
|
+
}
|
|
39
|
+
for (const [name, masked] of Object.entries(glob)) {
|
|
40
|
+
if (proj[name] !== undefined) continue;
|
|
41
|
+
out.push({ name, scope: "global", masked });
|
|
42
|
+
}
|
|
43
|
+
return out
|
|
44
|
+
.filter((r) => (filter === "all" ? true : r.scope === filter))
|
|
45
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
46
|
+
}, [list.data, filter]);
|
|
47
|
+
|
|
48
|
+
const remove = async (name: string, scope: VarScope) => {
|
|
49
|
+
if (!confirm(t("project.vars.delete_confirm", { name, scope }))) return;
|
|
50
|
+
try {
|
|
51
|
+
await Vars.remove(pid, name, scope);
|
|
52
|
+
toast.success(t("project.vars.removed"));
|
|
53
|
+
list.mutate();
|
|
54
|
+
} catch (e: any) {
|
|
55
|
+
toast.error(e?.message || t("common.error_generic"));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Section
|
|
61
|
+
title={t("project.vars.title")}
|
|
62
|
+
description={
|
|
63
|
+
isBase ? t("project.vars.subtitle_base") : t("project.vars.subtitle_project")
|
|
64
|
+
}
|
|
65
|
+
action={
|
|
66
|
+
<div className="flex items-center gap-2">
|
|
67
|
+
<Switch checked={revealAll} onChange={setRevealAll} label={t("project.vars.reveal_all")} />
|
|
68
|
+
<Button size="sm" variant="primary" onClick={() => setOpenCreate({})}>
|
|
69
|
+
<Plus size={14} /> {t("project.vars.new")}
|
|
70
|
+
</Button>
|
|
71
|
+
</div>
|
|
72
|
+
}
|
|
73
|
+
>
|
|
74
|
+
{!isBase && (
|
|
75
|
+
<div className="mb-3 flex items-center gap-2 text-xs">
|
|
76
|
+
<span className="text-muted-fg">{t("project.vars.filter_label")}</span>
|
|
77
|
+
<FilterPill active={filter === "all"} onClick={() => setFilter("all")}>
|
|
78
|
+
{t("project.vars.filter_all")}
|
|
79
|
+
</FilterPill>
|
|
80
|
+
<FilterPill active={filter === "project"} onClick={() => setFilter("project")}>
|
|
81
|
+
{t("project.vars.filter_project")}
|
|
82
|
+
</FilterPill>
|
|
83
|
+
<FilterPill active={filter === "global"} onClick={() => setFilter("global")}>
|
|
84
|
+
{t("project.vars.filter_global")}
|
|
85
|
+
</FilterPill>
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
{list.isLoading && <Loading />}
|
|
90
|
+
{!list.isLoading && rows.length === 0 && <Empty>{t("project.vars.empty")}</Empty>}
|
|
91
|
+
|
|
92
|
+
{rows.length > 0 && (
|
|
93
|
+
<ul className="space-y-2 text-sm">
|
|
94
|
+
{rows.map((r) => (
|
|
95
|
+
<li
|
|
96
|
+
key={`${r.scope}-${r.name}`}
|
|
97
|
+
className="flex items-center gap-3 rounded-md border border-border bg-muted/30 px-3 py-2"
|
|
98
|
+
>
|
|
99
|
+
<span className="font-mono text-xs font-medium">{r.name}</span>
|
|
100
|
+
<Badge tone={r.scope === "project" ? "info" : "muted"}>
|
|
101
|
+
{r.scope === "project" ? t("project.vars.scope_project") : t("project.vars.scope_global")}
|
|
102
|
+
</Badge>
|
|
103
|
+
<span className="ml-2 font-mono text-xs text-muted-fg">{r.masked}</span>
|
|
104
|
+
<div className="ml-auto flex items-center gap-1">
|
|
105
|
+
{/* Edit: only project-scope rows can be edited from a non-base project.
|
|
106
|
+
Global rows can be edited from /p/0. We surface "edit globally" by
|
|
107
|
+
pre-filling the dialog with scope=global; the user can switch. */}
|
|
108
|
+
<Button
|
|
109
|
+
size="sm"
|
|
110
|
+
variant="ghost"
|
|
111
|
+
onClick={() => setOpenCreate({ name: r.name, scope: r.scope })}
|
|
112
|
+
aria-label={t("project.vars.edit_btn")}
|
|
113
|
+
>
|
|
114
|
+
<Pencil size={13} />
|
|
115
|
+
</Button>
|
|
116
|
+
{!(isBase && r.scope === "project") && (
|
|
117
|
+
<Button
|
|
118
|
+
size="sm"
|
|
119
|
+
variant="destructive"
|
|
120
|
+
onClick={() => remove(r.name, r.scope)}
|
|
121
|
+
aria-label={t("project.vars.delete_btn")}
|
|
122
|
+
>
|
|
123
|
+
<Trash2 size={13} />
|
|
124
|
+
</Button>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
</li>
|
|
128
|
+
))}
|
|
129
|
+
</ul>
|
|
130
|
+
)}
|
|
131
|
+
|
|
132
|
+
<UpsertVarDialog
|
|
133
|
+
open={openCreate !== null}
|
|
134
|
+
initial={openCreate || undefined}
|
|
135
|
+
onClose={() => setOpenCreate(null)}
|
|
136
|
+
pid={pid}
|
|
137
|
+
isBase={isBase}
|
|
138
|
+
onSaved={() => {
|
|
139
|
+
setOpenCreate(null);
|
|
140
|
+
list.mutate();
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
</Section>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function FilterPill({
|
|
148
|
+
active,
|
|
149
|
+
onClick,
|
|
150
|
+
children,
|
|
151
|
+
}: {
|
|
152
|
+
active: boolean;
|
|
153
|
+
onClick: () => void;
|
|
154
|
+
children: React.ReactNode;
|
|
155
|
+
}) {
|
|
156
|
+
return (
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
onClick={onClick}
|
|
160
|
+
className={
|
|
161
|
+
active
|
|
162
|
+
? "rounded-full border border-primary/50 bg-primary/10 px-2 py-0.5 text-xs"
|
|
163
|
+
: "rounded-full border border-border bg-muted/30 px-2 py-0.5 text-xs hover:bg-muted/60"
|
|
164
|
+
}
|
|
165
|
+
>
|
|
166
|
+
{children}
|
|
167
|
+
</button>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function UpsertVarDialog({
|
|
172
|
+
open,
|
|
173
|
+
onClose,
|
|
174
|
+
pid,
|
|
175
|
+
isBase,
|
|
176
|
+
initial,
|
|
177
|
+
onSaved,
|
|
178
|
+
}: {
|
|
179
|
+
open: boolean;
|
|
180
|
+
onClose: () => void;
|
|
181
|
+
pid: string;
|
|
182
|
+
isBase: boolean;
|
|
183
|
+
initial?: { name?: string; value?: string; scope?: VarScope };
|
|
184
|
+
onSaved: () => void;
|
|
185
|
+
}) {
|
|
186
|
+
const toast = useToast();
|
|
187
|
+
const [busy, setBusy] = useState(false);
|
|
188
|
+
const [revealValue, setRevealValue] = useState(false);
|
|
189
|
+
const [name, setName] = useState(initial?.name || "");
|
|
190
|
+
const [value, setValue] = useState(initial?.value || "");
|
|
191
|
+
const [scope, setScope] = useState<VarScope>(
|
|
192
|
+
initial?.scope || (isBase ? "global" : "project"),
|
|
193
|
+
);
|
|
194
|
+
const isEdit = !!initial?.name;
|
|
195
|
+
|
|
196
|
+
// Reset state when the dialog opens with a different initial.
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
if (open) {
|
|
199
|
+
setName(initial?.name || "");
|
|
200
|
+
setValue(initial?.value || "");
|
|
201
|
+
setScope(initial?.scope || (isBase ? "global" : "project"));
|
|
202
|
+
setRevealValue(false);
|
|
203
|
+
}
|
|
204
|
+
}, [open, initial?.name, initial?.scope, initial?.value, isBase]);
|
|
205
|
+
|
|
206
|
+
const submit = async () => {
|
|
207
|
+
if (!name.trim()) {
|
|
208
|
+
toast.error(t("project.vars.name_required"));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (!value) {
|
|
212
|
+
toast.error(t("project.vars.value_required"));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
setBusy(true);
|
|
216
|
+
try {
|
|
217
|
+
await Vars.upsert(pid, { name: name.trim(), value, scope });
|
|
218
|
+
toast.success(isEdit ? t("project.vars.updated") : t("project.vars.added"));
|
|
219
|
+
onSaved();
|
|
220
|
+
} catch (e: any) {
|
|
221
|
+
toast.error(e?.message || t("common.error_generic"));
|
|
222
|
+
} finally {
|
|
223
|
+
setBusy(false);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<Dialog
|
|
229
|
+
open={open}
|
|
230
|
+
onClose={() => (busy ? null : onClose())}
|
|
231
|
+
title={isEdit ? t("project.vars.edit_title") : t("project.vars.new_title")}
|
|
232
|
+
description={t("project.vars.new_desc")}
|
|
233
|
+
size="sm"
|
|
234
|
+
footer={
|
|
235
|
+
<>
|
|
236
|
+
<Button variant="ghost" onClick={onClose} disabled={busy}>
|
|
237
|
+
{t("common.cancel")}
|
|
238
|
+
</Button>
|
|
239
|
+
<Button variant="primary" onClick={submit} loading={busy}>
|
|
240
|
+
{isEdit ? t("project.vars.save_btn") : t("project.vars.add_btn")}
|
|
241
|
+
</Button>
|
|
242
|
+
</>
|
|
243
|
+
}
|
|
244
|
+
>
|
|
245
|
+
<div className="space-y-3">
|
|
246
|
+
<Field label={t("project.vars.scope_label")}>
|
|
247
|
+
<UiSelect
|
|
248
|
+
value={scope}
|
|
249
|
+
onChange={(v) => setScope(v as VarScope)}
|
|
250
|
+
options={[
|
|
251
|
+
...(isBase
|
|
252
|
+
? []
|
|
253
|
+
: [
|
|
254
|
+
{
|
|
255
|
+
value: "project",
|
|
256
|
+
label: t("project.vars.scope_project"),
|
|
257
|
+
description: t("project.vars.scope_project_desc"),
|
|
258
|
+
},
|
|
259
|
+
]),
|
|
260
|
+
{
|
|
261
|
+
value: "global",
|
|
262
|
+
label: t("project.vars.scope_global"),
|
|
263
|
+
description: t("project.vars.scope_global_desc"),
|
|
264
|
+
},
|
|
265
|
+
]}
|
|
266
|
+
/>
|
|
267
|
+
</Field>
|
|
268
|
+
<Field label={t("project.vars.name_label")} hint={t("project.vars.name_hint")}>
|
|
269
|
+
<Input
|
|
270
|
+
value={name}
|
|
271
|
+
onChange={(e) => setName(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, "_"))}
|
|
272
|
+
placeholder="MY_API_KEY"
|
|
273
|
+
disabled={isEdit}
|
|
274
|
+
autoFocus={!isEdit}
|
|
275
|
+
/>
|
|
276
|
+
</Field>
|
|
277
|
+
<Field label={t("project.vars.value_label")} hint={t("project.vars.value_hint")}>
|
|
278
|
+
<div className="relative">
|
|
279
|
+
<Input
|
|
280
|
+
type={revealValue ? "text" : "password"}
|
|
281
|
+
value={value}
|
|
282
|
+
onChange={(e) => setValue(e.target.value)}
|
|
283
|
+
placeholder={isEdit ? t("project.vars.value_edit_ph") : ""}
|
|
284
|
+
className="pr-9 font-mono text-xs"
|
|
285
|
+
autoFocus={isEdit}
|
|
286
|
+
/>
|
|
287
|
+
<button
|
|
288
|
+
type="button"
|
|
289
|
+
onClick={() => setRevealValue((v) => !v)}
|
|
290
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-fg hover:text-fg"
|
|
291
|
+
aria-label={revealValue ? t("project.vars.hide") : t("project.vars.reveal")}
|
|
292
|
+
>
|
|
293
|
+
{revealValue ? <EyeOff size={14} /> : <Eye size={14} />}
|
|
294
|
+
</button>
|
|
295
|
+
</div>
|
|
296
|
+
</Field>
|
|
297
|
+
</div>
|
|
298
|
+
</Dialog>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
@@ -70,6 +70,11 @@ export interface McpEntry {
|
|
|
70
70
|
source: "apc" | "runtime" | "global" | string;
|
|
71
71
|
transport: string;
|
|
72
72
|
enabled: boolean;
|
|
73
|
+
command?: string | null;
|
|
74
|
+
args?: string[];
|
|
75
|
+
env?: Record<string, string>;
|
|
76
|
+
url?: string | null;
|
|
77
|
+
headers?: Record<string, string>;
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
export interface MessageEntry {
|
|
@@ -279,6 +284,16 @@ export interface ChatStreamEvent {
|
|
|
279
284
|
streak?: number;
|
|
280
285
|
// tool_start / tool_result / tool_deduped
|
|
281
286
|
trace?: ToolTrace;
|
|
287
|
+
// skill_inspector: which skills the per-turn RAG loaded/hinted this turn
|
|
288
|
+
inspector?: {
|
|
289
|
+
enabled?: boolean;
|
|
290
|
+
reason?: string;
|
|
291
|
+
embedder?: string;
|
|
292
|
+
scored?: { slug: string; sim: number }[];
|
|
293
|
+
loaded?: string[];
|
|
294
|
+
hinted?: string[];
|
|
295
|
+
jit?: boolean;
|
|
296
|
+
};
|
|
282
297
|
// final
|
|
283
298
|
result?: {
|
|
284
299
|
text?: string;
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: apx-agency-agents
|
|
3
|
-
description: "Manage the APX agent vault — reusable agent templates (Roby, Cody, Rocky, Tessa, Max, Arch, Sid, Vera, Finn + generic dev/marketing/ops/qa/support). Load to spawn a specialist, list templates, import one into a project, create a template, or hide/restore a bundled default. Triggers: 'spawn agent', 'use Cody/Rocky/Tessa', 'list agents', 'agent vault', 'import agent', 'new agent'."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# apx-agency-agents — the APX agent vault
|
|
7
|
-
|
|
8
|
-
The vault is the **global, project-agnostic library of agent templates** in APX. Two layers, deduped per-slug, with the user layer winning:
|
|
9
|
-
|
|
10
|
-
| Layer | Where | Mutability |
|
|
11
|
-
|---|---|---|
|
|
12
|
-
| **Bundled** | `<repo>/assets/agent-vault-defaults/<slug>.md` | Read-only on disk. Always visible unless tombstoned. |
|
|
13
|
-
| **User** | `~/.apx/agents/<slug>.md` | Read-write. Overrides the bundled with the same slug. |
|
|
14
|
-
| **Tombstones** | `~/.apx/agents/.removed.json` | List of bundled slugs the user explicitly hid. |
|
|
15
|
-
|
|
16
|
-
Listing returns `bundled ∪ user`, with `source: "bundled" | "user" | "user-override"` per entry. Editing a bundled entry is **copy-on-write** — it materializes into the user layer the moment you save. Deleting a bundled entry **tombstones** it (you can restore later); deleting a user-only entry physically removes the file.
|
|
17
|
-
|
|
18
|
-
## Bundled starter pack
|
|
19
|
-
|
|
20
|
-
APX ships 14 templates out of the box, two families:
|
|
21
|
-
|
|
22
|
-
### Named team (from nicho-apps)
|
|
23
|
-
A characterful crew already used in production by NichoApps:
|
|
24
|
-
|
|
25
|
-
| Slug | Role | Strength |
|
|
26
|
-
|---|---|---|
|
|
27
|
-
| `roby-orchestrator` | Pipeline orchestrator | Autonomous task routing, multi-agent coordination |
|
|
28
|
-
| `arch-architect` | Software architect | System design, tradeoffs, ADRs |
|
|
29
|
-
| `cody-developer` | Senior Laravel dev | Code, refactors, technical leadership |
|
|
30
|
-
| `tessa-qa` | QA / beta tester | Reality checks, test plans, real-user empathy |
|
|
31
|
-
| `max-marketing` | Growth hacker | Content, campaigns, SEO, copy |
|
|
32
|
-
| `sid-security` | Security specialist | Audits, threat models, hardening |
|
|
33
|
-
| `rocky-pm` | Senior PM | Tasklists, roadmaps, stakeholder comms |
|
|
34
|
-
| `vera-ui` | UI/UX reviewer | Visual audits, usability (uses browser-use) |
|
|
35
|
-
| `finn-billing` | Billing / commercial ops | Stripe, invoicing, plan logic |
|
|
36
|
-
|
|
37
|
-
### Generic specialists (from PandaProject)
|
|
38
|
-
Plain functional roles — useful when you want capabilities without a persona:
|
|
39
|
-
|
|
40
|
-
| Slug | Role |
|
|
41
|
-
|---|---|
|
|
42
|
-
| `development` | Engineering & code |
|
|
43
|
-
| `marketing` | Marketing & growth |
|
|
44
|
-
| `ops` | Ops, deploys & incidents |
|
|
45
|
-
| `qa` | Quality assurance & testing |
|
|
46
|
-
| `support` | Customer support & escalations |
|
|
47
|
-
|
|
48
|
-
Bundled templates ship with `language: es` and **no `model:` set** — they inherit the project/global default model. Set a model on import or by editing the vault file directly.
|
|
49
|
-
|
|
50
|
-
## Concrete commands
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
# List the vault (bundled + your overrides, with [bundled] / [user] / [override] tags)
|
|
54
|
-
apx agent vault list
|
|
55
|
-
|
|
56
|
-
# Include tombstoned bundled defaults (the ones you hid)
|
|
57
|
-
apx agent vault list --all
|
|
58
|
-
|
|
59
|
-
# Create a new template (writes ~/.apx/agents/<slug>.md)
|
|
60
|
-
apx agent vault add reviewer \
|
|
61
|
-
--role "Code reviewer" \
|
|
62
|
-
--model ollama:llama3.2:3b \
|
|
63
|
-
--language es \
|
|
64
|
-
--skills code-review,git \
|
|
65
|
-
--description "Reviews PRs and pushes back on hand-wavy diffs."
|
|
66
|
-
|
|
67
|
-
# Delete a template:
|
|
68
|
-
# - user-only slug → file is physically deleted
|
|
69
|
-
# - bundled slug → tombstoned, hidden from listings
|
|
70
|
-
apx agent vault rm tessa-qa
|
|
71
|
-
|
|
72
|
-
# Bring back a tombstoned bundled default
|
|
73
|
-
apx agent vault restore tessa-qa
|
|
74
|
-
|
|
75
|
-
# Import a vault template into the current project
|
|
76
|
-
apx agent import cody-developer
|
|
77
|
-
apx agent import tessa-qa --copy # copy into .apc/agents/ for local edits
|
|
78
|
-
apx agent import roby-orchestrator --force # overwrite an existing local def
|
|
79
|
-
|
|
80
|
-
# Inside the daemon's tool API, the same flow:
|
|
81
|
-
list_vault_agents()
|
|
82
|
-
import_agent({ slug: "cody-developer", project: "<name-or-path>" })
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## The web equivalents
|
|
86
|
-
|
|
87
|
-
The Agent defaults tab (`/p/0/agent-defaults`) has the same CRUD: a "New" button (POST `/agents/vault`), "Edit" per card (PATCH `/agents/vault/:slug`, copy-on-write for bundled), "Delete"/"Hide" (DELETE), and a "Show removed" toggle that surfaces tombstoned bundled defaults with a "Restore" button.
|
|
88
|
-
|
|
89
|
-
## When to use which agent
|
|
90
|
-
|
|
91
|
-
- **User says "spawn/use Cody/Rocky/Tessa/Max/Arch/Sid/Vera/Roby/Finn"** → that exact slug exists in the named team. Import it.
|
|
92
|
-
- **User wants a "developer" / "QA" / "marketing" agent without a persona** → use the generic specialists (`development`, `qa`, `marketing`, …).
|
|
93
|
-
- **User wants something not in the vault** → either `apx agent vault add <slug>` to create a blank one, or `apx agent add <slug>` directly inside a project for a one-off.
|
|
94
|
-
- **Fresh install** → bundled defaults are always present (no sync step); they appear in `apx agent vault list` immediately.
|
|
95
|
-
|
|
96
|
-
## Where the files live
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
<APX repo>/assets/agent-vault-defaults/<slug>.md ← canonical bundle (committed)
|
|
100
|
-
~/.apx/agents/<slug>.md ← user vault (sync target)
|
|
101
|
-
<project>/.apc/agents/<slug>.md ← project-local copy (after import --copy)
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
The vault file format is identical to a project agent file:
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
---
|
|
108
|
-
role: <human-readable role>
|
|
109
|
-
model: <provider:model>
|
|
110
|
-
description: <short description shown in UI grid>
|
|
111
|
-
language: es
|
|
112
|
-
skills: a, b, c
|
|
113
|
-
tools: x, y
|
|
114
|
-
is_master: false
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
<markdown body — the agent's system prompt extension>
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Frontmatter fields the runtime reads
|
|
121
|
-
|
|
122
|
-
| Field | Effect |
|
|
123
|
-
|---|---|
|
|
124
|
-
| `role` | Shown in CLI/web. Appears in the agent's prompt as "Role: …". |
|
|
125
|
-
| `model` | Default model for engine routing. |
|
|
126
|
-
| `description` | Shown in `/agents/vault` and the AgentDefaultsTab cards. |
|
|
127
|
-
| `language` | Adds "Default language: <code>" to the system prompt. |
|
|
128
|
-
| `skills` | Per-agent skill names; relevant skill bodies are loaded by skill resolution. |
|
|
129
|
-
| `tools` | Declared tool hints; actual callable tools depend on the invocation surface. |
|
|
130
|
-
| `is_master` | If true, marked as a master agent in the project (badge + ordering). |
|
|
131
|
-
|
|
132
|
-
## Related skills
|
|
133
|
-
|
|
134
|
-
- **[apx-agent](../apx-agent/SKILL.md)** — per-project agent CRUD (add / edit / memory / list). The vault is the *library*; this skill is the *workshop*.
|
|
135
|
-
- **[apx-runtime](../apx-runtime/SKILL.md)** — delegating to external coding CLIs (claude-code, codex, etc) from inside an agent.
|
|
136
|
-
|
|
137
|
-
## Gotchas
|
|
138
|
-
|
|
139
|
-
- **Bundled defaults are always present** — there's no sync step. They appear in `apx agent vault list` and in the web UI the moment APX is installed. Removing one tombstones it; editing one copies it to your user layer.
|
|
140
|
-
- The slug `roby-orchestrator` is intentionally **not** `roby` — the APX super-agent persona is already named "Roby" via `~/.apx/identity.json`. Importing a project agent called `roby` would shadow that and cause confusion. Use `roby-orchestrator` for a *project-level* orchestrator.
|
|
141
|
-
- The `agency-agents` skill in `~/.claude/skills/` pulls from an external GitHub repo (`msitarzewski/agency-agents`). APX bundles a snapshot in `assets/agent-vault-defaults/` so installs are offline-first. To refresh from upstream, edit the bundle and re-commit; existing users' overrides are untouched.
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: apx-agent
|
|
3
|
-
description: How to create, configure, and use project agents in APX. Load when the user wants to "add an agent", import from the vault, set a per-agent model, or write agent memory.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# apx-agent
|
|
7
|
-
|
|
8
|
-
A project agent is a named persona inside an APC project. Canonical definition: `.apc/agents/<slug>.md` (flat file). `AGENTS.md` is auto-regenerated for discovery. Per-agent runtime data (memory, conversations, sessions) lives under `~/.apx/projects/<apx_id>/agents/<slug>/` and is never committed. APX still reads legacy `.apc/agents/<slug>/memory.md` as a migration fallback only.
|
|
9
|
-
|
|
10
|
-
## Concrete CLI calls
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
# List agents in the current project (agent commands are cwd-scoped — run from the project root)
|
|
14
|
-
apx agent list
|
|
15
|
-
|
|
16
|
-
# Create a new agent (writes .apc/agents/<slug>.md, creates runtime dir, regenerates AGENTS.md)
|
|
17
|
-
apx agent add reviewer \
|
|
18
|
-
--role "Code reviewer" \
|
|
19
|
-
--model ollama:llama3.2:3b \
|
|
20
|
-
--language es \
|
|
21
|
-
--description "Reviews PRs and pushes back on hand-wavy diffs." \
|
|
22
|
-
--tools read,write,run \
|
|
23
|
-
--skills code-review,git
|
|
24
|
-
|
|
25
|
-
# Import an agent template from the global vault (~/.apx/agents/)
|
|
26
|
-
apx agent vault list # see what's available
|
|
27
|
-
apx agent import <slug> # register vault slug in this project
|
|
28
|
-
apx agent import <slug> --copy # copy vault .md into .apc/agents/ for local edits
|
|
29
|
-
apx agent import <slug> --force # overwrite existing local definition
|
|
30
|
-
|
|
31
|
-
# Show details (config + memory)
|
|
32
|
-
apx agent get <slug> # alias: apx agent show <slug>
|
|
33
|
-
|
|
34
|
-
# Per-agent memory (drives system prompt for that agent; cwd-scoped — run from the project root)
|
|
35
|
-
apx memory <slug> # read
|
|
36
|
-
apx memory <slug> --append "fact" # append a line under "## Recent context"
|
|
37
|
-
apx memory <slug> --replace < file.md # full replace from stdin
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## What the agent's system prompt looks like
|
|
41
|
-
|
|
42
|
-
`buildAgentSystem()` (`src/core/agent-system.js`) composes:
|
|
43
|
-
|
|
44
|
-
1. Identity block: `You are <slug>` + project name.
|
|
45
|
-
2. Description (from AGENTS.md).
|
|
46
|
-
3. Role + Language fields.
|
|
47
|
-
4. Invocation context: `engine | telegram | routine | runtime` — the channel calling the agent.
|
|
48
|
-
5. Memory: `~/.apx/projects/<apx_id>/agents/<slug>/memory.md` if it exists, with legacy `.apc/agents/<slug>/memory.md` as a migration fallback.
|
|
49
|
-
6. Skills declared in the agent's `Skills:` field, each loaded from `.apc/skills/<slug>.md` or the bundled set.
|
|
50
|
-
7. The `apx` meta-skill (so the agent knows how to operate APX).
|
|
51
|
-
8. ACTION_DISCIPLINE_RULES (fixed footer — anti-ghost, anti-disclaimer, action-first).
|
|
52
|
-
|
|
53
|
-
That's the prompt the engine sees on every `apx exec <agent>` or `apx chat <agent>`. The super-agent (default APX mode) uses a *different* prompt — see `apx-routine` for when the super-agent runs vs. when an exec_agent runs.
|
|
54
|
-
|
|
55
|
-
## Models per agent
|
|
56
|
-
|
|
57
|
-
Each agent can set `Model:` in its `.apc/agents/<slug>.md` definition to override the global super-agent model. Leave it empty when the agent should follow the project/global default.
|
|
58
|
-
|
|
59
|
-
```markdown
|
|
60
|
-
# .apc/agents/reviewer.md
|
|
61
|
-
---
|
|
62
|
-
Role: Code reviewer
|
|
63
|
-
Model: ollama:llama3.2:3b ← this agent uses this model, independent of super_agent.model
|
|
64
|
-
Language: es
|
|
65
|
-
---
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
When a routine `kind: exec_agent` runs with `spec.agent: reviewer`, it uses that model.
|
|
69
|
-
|
|
70
|
-
## Anti-examples
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
# DON'T hand-write .apc/agents/<slug>.md without matching AGENTS.md regeneration.
|
|
74
|
-
echo "..." > /path/.apc/agents/reviewer.md
|
|
75
|
-
# ↑ Prefer `apx agent add` or `apx agent import` so AGENTS.md stays consistent.
|
|
76
|
-
|
|
77
|
-
# DON'T set Model: to a provider you don't have keys for.
|
|
78
|
-
# An agent with Model: openai:gpt-4o on a machine with no openai.api_key fails on first call.
|
|
79
|
-
|
|
80
|
-
# DON'T put long-running context in `Description`. Put it in memory.md.
|
|
81
|
-
# Description is one line, Role is a noun phrase, memory is unlimited.
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## How agents differ from the super-agent
|
|
85
|
-
|
|
86
|
-
| Aspect | Super-agent (default APX) | Project agent |
|
|
87
|
-
|---|---|---|
|
|
88
|
-
| Has tools? | Yes (the full registry) | No (text-in/text-out via callEngine) |
|
|
89
|
-
| Loop? | Multi-iteration tool loop | Single call |
|
|
90
|
-
| System prompt | `super-agent-base.md` + channel template + identity | `buildAgentSystem()` per-agent |
|
|
91
|
-
| Conversation persisted in | super-agent surfaces | `<storagePath>/agents/<slug>/conversations/*.md` |
|
|
92
|
-
| Configured via | `super_agent.*` in config | `AGENTS.md` + per-agent files |
|
|
93
|
-
|
|
94
|
-
When in doubt: the super-agent is APX itself; agents are personas inside a project.
|
|
95
|
-
|
|
96
|
-
## Don't
|
|
97
|
-
|
|
98
|
-
- Don't expect a project agent to call tools. It can't. If you want tools, use the super-agent or call MCPs from a routine `kind: shell`.
|
|
99
|
-
- Don't overwrite `AGENTS.md` manually — `apx agent add/remove` regenerates it. Hand edits get clobbered.
|
|
100
|
-
- Don't use the same slug across projects expecting shared memory. Memory is per-project.
|