@agentprojectcontext/apx 1.32.0 → 1.32.2
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 +6 -1
- package/skills/apc-context/SKILL.md +5 -2
- package/skills/apx/SKILL.md +3 -3
- package/skills/apx-agency-agents/SKILL.md +5 -5
- package/skills/apx-agent/SKILL.md +7 -7
- package/skills/apx-mcp/SKILL.md +6 -4
- package/skills/apx-mcp-builder/SKILL.md +4 -7
- package/skills/apx-project/SKILL.md +4 -5
- package/skills/apx-routine/SKILL.md +14 -12
- package/skills/apx-runtime/SKILL.md +5 -3
- package/skills/apx-sessions/SKILL.md +5 -5
- package/skills/apx-skill-builder/SKILL.md +10 -6
- package/skills/apx-task/SKILL.md +8 -8
- package/skills/apx-telegram/SKILL.md +23 -7
- package/skills/apx-voice/SKILL.md +8 -6
- package/src/core/{agent-system.js → agent/build-agent-system.js} +10 -12
- package/src/core/agent/index.js +0 -2
- package/src/core/{agent-memory.js → agent/memory.js} +2 -2
- package/src/core/agent/model-router.js +21 -43
- package/src/core/agent/prompt-builder.js +17 -63
- package/src/core/agent/prompts/action-discipline.md +17 -0
- package/src/core/agent/prompts/channels/code.md +8 -12
- package/src/core/agent/prompts/channels/desktop.md +6 -4
- package/src/core/agent/prompts/channels/routine.md +10 -1
- package/src/core/agent/prompts/channels/telegram.md +5 -0
- package/src/core/agent/prompts/channels/web_code.md +20 -0
- package/src/core/agent/prompts/modes/voice.md +2 -2
- package/src/core/agent/prompts/super-agent-base.md +2 -2
- package/src/core/agent/run-agent.js +37 -35
- package/src/core/agent/runtime-bridge.js +42 -0
- package/src/core/agent/self-memory.js +19 -9
- package/src/core/agent/skills/catalog.js +65 -0
- package/src/core/agent/skills/index.js +6 -0
- package/src/{host/daemon/skills-loader.js → core/agent/skills/loader.js} +3 -3
- package/src/core/agent/skills/rag.js +91 -0
- package/src/core/agent/skills/trigger.js +71 -0
- package/src/{host/daemon → core/agent}/super-agent.js +5 -5
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/add-project.js +3 -4
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-agent.js +2 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-mcp.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-runtime.js +10 -11
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/create-task.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/discover-tools.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/edit-file.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/import-agent.js +4 -5
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-agents.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-skills.js +7 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-tasks.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-vault-agents.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/load-skill.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-agent-memory.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-self-memory.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/remember.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/run-shell.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-messages.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-sessions.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/send-telegram.js +0 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-identity.js +1 -3
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-permission-mode.js +1 -3
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/tail-messages.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/transcribe-audio.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/write-file.js +1 -2
- package/src/core/agent/tools/helpers.js +74 -0
- package/src/{host/daemon/super-agent-tools → core/agent/tools}/registry-bridge.js +3 -3
- package/src/{host/daemon/super-agent-tools/index.js → core/agent/tools/registry.js} +31 -32
- package/src/core/apc/agents-vault.js +37 -0
- package/src/core/{scaffold.js → apc/scaffold.js} +4 -5
- package/src/core/{config.js → config/index.js} +21 -27
- package/src/core/config/paths.js +32 -0
- package/src/core/constants/actors.js +8 -0
- package/src/core/constants/channels.js +19 -0
- package/src/core/constants/index.js +5 -0
- package/src/core/constants/permissions.js +17 -0
- package/src/core/constants/roles.js +9 -0
- package/src/core/engines/_streaming.js +63 -0
- package/src/core/engines/anthropic.js +11 -22
- package/src/core/engines/ollama.js +7 -16
- package/src/core/identity/index.js +8 -0
- package/src/core/{identity.js → identity/self.js} +5 -5
- package/src/core/{telegram-identity.js → identity/telegram.js} +1 -1
- package/src/core/logging.js +1 -1
- package/src/core/mascot.js +1 -1
- package/src/core/memory/active-threads.js +10 -10
- package/src/core/memory/broker.js +9 -9
- package/src/core/memory/compactor.js +2 -2
- package/src/core/memory/index.js +2 -2
- package/src/core/memory/indexer.js +1 -1
- package/src/core/{code-sessions-store.js → stores/code-sessions.js} +3 -7
- package/src/core/{messages-store.js → stores/messages.js} +6 -4
- package/src/core/stores/routine-memory.js +71 -0
- package/src/core/{routines-store.js → stores/routines.js} +1 -3
- package/src/core/stores/runtime-sessions.js +99 -0
- package/src/core/{tasks-store.js → stores/tasks.js} +3 -8
- package/src/core/update-check.js +1 -1
- package/src/core/util/ids.js +14 -0
- package/src/core/util/index.js +2 -0
- package/src/core/util/text-similarity.js +52 -0
- package/src/core/util/time.js +9 -0
- package/src/core/voice/tts.js +1 -1
- package/src/host/daemon/api/admin-config.js +4 -3
- package/src/host/daemon/api/admin.js +1 -1
- package/src/host/daemon/api/agents.js +4 -25
- package/src/host/daemon/api/artifacts.js +1 -1
- package/src/host/daemon/api/code.js +48 -16
- package/src/host/daemon/api/confirm.js +1 -1
- package/src/host/daemon/api/connections.js +2 -2
- package/src/host/daemon/api/conversations.js +2 -2
- package/src/host/daemon/api/deck.js +1 -1
- package/src/host/daemon/api/desktop.js +1 -1
- package/src/host/daemon/api/embeddings.js +4 -4
- package/src/host/daemon/api/engines.js +2 -2
- package/src/host/daemon/api/exec.js +3 -3
- package/src/host/daemon/api/identity.js +1 -1
- package/src/host/daemon/api/mcps.js +1 -1
- package/src/host/daemon/api/messages.js +1 -1
- package/src/host/daemon/api/runtimes.js +9 -8
- package/src/host/daemon/api/sessions-search.js +1 -1
- package/src/host/daemon/api/sessions.js +2 -2
- package/src/host/daemon/api/shared.js +5 -4
- package/src/host/daemon/api/skills.js +30 -0
- package/src/host/daemon/api/super-agent.js +29 -9
- package/src/host/daemon/api/tasks.js +2 -2
- package/src/host/daemon/api/telegram.js +1 -1
- package/src/host/daemon/api/tools.js +6 -6
- package/src/host/daemon/api/tts.js +2 -2
- package/src/host/daemon/api/voice.js +14 -12
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/compact.js +1 -1
- package/src/host/daemon/db.js +4 -4
- package/src/host/daemon/desktop-ws.js +1 -1
- package/src/host/daemon/index.js +4 -4
- package/src/host/daemon/plugins/{desktop.js → desktop/index.js} +11 -6
- package/src/host/daemon/plugins/index.js +2 -2
- package/src/host/daemon/plugins/{telegram.js → telegram/index.js} +66 -195
- package/src/host/daemon/plugins/telegram/media.js +162 -0
- package/src/host/daemon/projects-helpers.js +54 -0
- package/src/host/daemon/routines.js +28 -12
- package/src/host/daemon/smoke.js +2 -2
- package/src/host/daemon/token-store.js +1 -1
- package/src/host/daemon/transcription.js +2 -2
- package/src/host/daemon/wakeup.js +2 -2
- package/src/interfaces/cli/commands/agent.js +3 -3
- package/src/interfaces/cli/commands/command.js +1 -1
- package/src/interfaces/cli/commands/config.js +3 -2
- package/src/interfaces/cli/commands/desktop.js +1 -1
- package/src/interfaces/cli/commands/exec.js +2 -1
- package/src/interfaces/cli/commands/identity.js +2 -2
- package/src/interfaces/cli/commands/init.js +1 -1
- package/src/interfaces/cli/commands/mcp.js +1 -1
- package/src/interfaces/cli/commands/memory.js +2 -2
- package/src/interfaces/cli/commands/model.js +16 -6
- package/src/interfaces/cli/commands/project.js +1 -1
- package/src/interfaces/cli/commands/routine.js +58 -0
- package/src/interfaces/cli/commands/search.js +1 -1
- package/src/interfaces/cli/commands/session.js +4 -4
- package/src/interfaces/cli/commands/setup.js +4 -3
- package/src/interfaces/cli/commands/skills.js +25 -4
- package/src/interfaces/cli/commands/status.js +1 -1
- package/src/interfaces/cli/commands/sys.js +11 -4
- package/src/interfaces/cli/commands/update.js +1 -1
- package/src/interfaces/cli/index.js +4 -4
- package/src/interfaces/cli/postinstall.js +2 -2
- package/src/interfaces/mcp-server/index.js +1 -1
- package/src/interfaces/tui/component/prompt/index.tsx +3 -1
- package/src/interfaces/tui/context/sdk-apx.tsx +47 -7
- package/src/interfaces/tui/context/sync-apx.tsx +20 -2
- package/src/interfaces/tui/context/sync.tsx +2 -1
- package/src/interfaces/tui/routes/session/index.tsx +151 -136
- package/src/interfaces/tui/routes/session/sidebar-apx.tsx +37 -15
- package/src/interfaces/tui/run.ts +2 -0
- package/src/interfaces/web/dist/assets/index-34U_Mp1M.css +1 -0
- package/src/interfaces/web/dist/assets/index-BkybwwRn.js +570 -0
- package/src/interfaces/web/dist/assets/index-BkybwwRn.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +3 -3
- package/src/interfaces/web/src/App.tsx +51 -32
- package/src/interfaces/web/src/components/RobyBubble.tsx +12 -6
- package/src/interfaces/web/src/components/UiSelect.tsx +1 -1
- package/src/interfaces/web/src/components/chat/SkillPicker.tsx +77 -0
- package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +33 -18
- package/src/interfaces/web/src/components/common/TabLayout.tsx +9 -5
- package/src/interfaces/web/src/components/common/TabNav.tsx +3 -3
- package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +4 -2
- package/src/interfaces/web/src/hooks/useChat.ts +47 -2
- package/src/interfaces/web/src/hooks/useNavCollapseCtx.tsx +59 -0
- package/src/interfaces/web/src/hooks/usePersonaName.ts +11 -0
- package/src/interfaces/web/src/i18n/en.ts +7 -7
- package/src/interfaces/web/src/i18n/es.ts +7 -7
- package/src/interfaces/web/src/lib/api/skills.ts +25 -0
- package/src/interfaces/web/src/lib/api.ts +1 -0
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +18 -18
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +5 -18
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -8
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +39 -40
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +12 -9
- package/src/skills/apc-context/SKILL.md +159 -0
- package/src/core/agent/ghost-guard.js +0 -24
- package/src/core/agent/prompts/channels/terminal.md +0 -16
- package/src/host/daemon/apc-runtime-context.js +0 -124
- package/src/host/daemon/super-agent-tools/helpers.js +0 -124
- package/src/host/daemon/tool-call-parser.js +0 -2
- package/src/interfaces/web/dist/assets/index-63P_ji1a.js +0 -571
- package/src/interfaces/web/dist/assets/index-63P_ji1a.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-DLWy6dYz.css +0 -1
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/ask-questions.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-files.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-mcps.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-projects.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-file.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-files.js +0 -0
- /package/src/core/agent/{pseudo-tools.js → tools/pseudo-tools.js} +0 -0
- /package/src/core/agent/{tool-call-parser.js → tools/tool-call-parser.js} +0 -0
- /package/src/core/{parser.js → apc/parser.js} +0 -0
- /package/src/core/{apc-skill-sync.js → apc/skill-sync.js} +0 -0
- /package/src/core/{artifacts-store.js → stores/artifacts.js} +0 -0
- /package/src/{host/daemon → core/stores}/engine-sessions.js +0 -0
- /package/src/core/{session-store.js → stores/sessions.js} +0 -0
- /package/src/host/daemon/plugins/{telegram-ask.js → telegram/ask.js} +0 -0
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
<link rel="apple-touch-icon" href="/favicon/dark/apple-touch-icon.png" media="(prefers-color-scheme: dark)" />
|
|
19
19
|
<link rel="manifest" href="/favicon/white/site.webmanifest" media="(prefers-color-scheme: light)" />
|
|
20
20
|
<link rel="manifest" href="/favicon/dark/site.webmanifest" media="(prefers-color-scheme: dark)" />
|
|
21
|
-
<script type="module" crossorigin src="/assets/index-
|
|
22
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
21
|
+
<script type="module" crossorigin src="/assets/index-BkybwwRn.js"></script>
|
|
22
|
+
<link rel="stylesheet" crossorigin href="/assets/index-34U_Mp1M.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-background text-foreground antialiased">
|
|
25
25
|
<div id="root"></div>
|
|
@@ -2077,9 +2077,9 @@
|
|
|
2077
2077
|
"license": "MIT"
|
|
2078
2078
|
},
|
|
2079
2079
|
"node_modules/@types/node": {
|
|
2080
|
-
"version": "25.9.
|
|
2081
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.
|
|
2082
|
-
"integrity": "sha512-
|
|
2080
|
+
"version": "25.9.3",
|
|
2081
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz",
|
|
2082
|
+
"integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==",
|
|
2083
2083
|
"devOptional": true,
|
|
2084
2084
|
"license": "MIT",
|
|
2085
2085
|
"dependencies": {
|
|
@@ -17,6 +17,8 @@ import { TooltipProvider } from "./components/ui/tooltip";
|
|
|
17
17
|
import { useTheme } from "./hooks/useTheme";
|
|
18
18
|
import { useProjects } from "./hooks/useProjects";
|
|
19
19
|
import { useTokenBootstrap } from "./hooks/useTokenBootstrap";
|
|
20
|
+
import { NavCollapseProvider, useNavCollapseCtx, usePageLabel } from "./hooks/useNavCollapseCtx";
|
|
21
|
+
import { NavToggle } from "./components/common/TabNav";
|
|
20
22
|
import { t } from "./i18n";
|
|
21
23
|
|
|
22
24
|
export function App() {
|
|
@@ -61,28 +63,30 @@ function Shell() {
|
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
return (
|
|
64
|
-
<
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
<
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
66
|
+
<NavCollapseProvider>
|
|
67
|
+
<div className="flex h-screen w-screen overflow-hidden bg-background text-foreground" data-testid="app-shell">
|
|
68
|
+
<ProjectSidebar onSelect={(href) => navigate(href)} onOpenRoby={() => setRobyOpen(true)} />
|
|
69
|
+
<main className="m-2 ml-0 flex min-w-0 flex-1 flex-col overflow-hidden rounded-xl border border-border bg-card shadow-sm">
|
|
70
|
+
<TopBar onToggleTheme={toggle} isDark={theme === "dark"} pathname={location.pathname} />
|
|
71
|
+
<div className="flex-1 overflow-y-auto">
|
|
72
|
+
<Routes>
|
|
73
|
+
<Route path="/" element={<ApxAdminScreen />} />
|
|
74
|
+
<Route path="/settings/*" element={<SettingsScreen />} />
|
|
75
|
+
<Route path="/m/voice/*" element={<VoiceScreen />} />
|
|
76
|
+
<Route path="/m/desktop/*" element={<DesktopScreen />} />
|
|
77
|
+
<Route path="/m/deck/*" element={<DeckScreen />} />
|
|
78
|
+
<Route path="/m/code/*" element={<CodeScreen />} />
|
|
79
|
+
<Route path="/p/:pid/*" element={<ProjectScreen />} />
|
|
80
|
+
<Route path="*" element={<NotFound />} />
|
|
81
|
+
</Routes>
|
|
82
|
+
</div>
|
|
83
|
+
</main>
|
|
84
|
+
<AddProjectDialog open={addOpen} onClose={closeAdd} />
|
|
85
|
+
{/* Roby (the super-agent) chat sheet. Launcher lives in the rail (below
|
|
86
|
+
Settings); open state is owned here so the rail can trigger it. */}
|
|
87
|
+
<RobyBubble open={robyOpen} onOpenChange={setRobyOpen} />
|
|
88
|
+
</div>
|
|
89
|
+
</NavCollapseProvider>
|
|
86
90
|
);
|
|
87
91
|
}
|
|
88
92
|
|
|
@@ -96,6 +100,7 @@ function TopBar({
|
|
|
96
100
|
pathname: string;
|
|
97
101
|
}) {
|
|
98
102
|
const { projects } = useProjects();
|
|
103
|
+
const pageLabel = usePageLabel();
|
|
99
104
|
const parts = pathname.split("/").filter(Boolean);
|
|
100
105
|
const project = parts[0] === "p" ? projects.find((p) => String(p.id) === parts[1]) : undefined;
|
|
101
106
|
const section = parts[0] === "settings"
|
|
@@ -109,11 +114,13 @@ function TopBar({
|
|
|
109
114
|
? t("topbar.breadcrumb_root")
|
|
110
115
|
: parts[0] === "settings"
|
|
111
116
|
? [t("topbar.breadcrumb_root"), t("nav.settings"), section].filter(Boolean).join(" › ")
|
|
112
|
-
: parts[0] === "
|
|
113
|
-
? (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
: parts[0] === "m"
|
|
118
|
+
? [t("topbar.breadcrumb_root"), moduleLabel(parts[1]), pageLabel].filter(Boolean).join(" › ")
|
|
119
|
+
: parts[0] === "p"
|
|
120
|
+
? (isDefault
|
|
121
|
+
? [t("topbar.breadcrumb_root"), t("topbar.breadcrumb_base"), section].filter(Boolean).join(" › ")
|
|
122
|
+
: [t("topbar.breadcrumb_root"), t("topbar.breadcrumb_projects"), projName, section].filter(Boolean).join(" › "))
|
|
123
|
+
: t("topbar.breadcrumb_root");
|
|
117
124
|
const subtitle = pathname === "/"
|
|
118
125
|
? ""
|
|
119
126
|
: parts[0] === "settings"
|
|
@@ -123,24 +130,36 @@ function TopBar({
|
|
|
123
130
|
? t("base.subtitle")
|
|
124
131
|
: project ? `${projectKindLabel(project.kind)} · ${project.path}` : "")
|
|
125
132
|
: "";
|
|
133
|
+
const nav = useNavCollapseCtx();
|
|
126
134
|
return (
|
|
127
|
-
<header className="flex h-
|
|
128
|
-
<
|
|
135
|
+
<header className="flex h-10 shrink-0 items-center gap-2 border-b border-border/50 px-3">
|
|
136
|
+
{nav && <NavToggle collapsed={nav.collapsed} onToggle={nav.toggle} />}
|
|
137
|
+
<span className="min-w-0 flex-1 truncate text-[11px] tracking-wide text-muted-fg">
|
|
129
138
|
{crumb}
|
|
130
|
-
{subtitle && <span className="text-muted-fg/
|
|
139
|
+
{subtitle && <span className="text-muted-fg/50"> · {subtitle}</span>}
|
|
131
140
|
</span>
|
|
132
141
|
<button
|
|
133
142
|
type="button"
|
|
134
143
|
onClick={onToggleTheme}
|
|
135
144
|
title={isDark ? t("topbar.light") : t("topbar.dark")}
|
|
136
|
-
className="rounded-md p-1.5 text-muted-fg hover:bg-accent hover:text-accent-fg"
|
|
145
|
+
className="shrink-0 rounded-md p-1.5 text-muted-fg hover:bg-accent hover:text-accent-fg"
|
|
137
146
|
>
|
|
138
|
-
{isDark ? <Sun size={
|
|
147
|
+
{isDark ? <Sun size={14} /> : <Moon size={14} />}
|
|
139
148
|
</button>
|
|
140
149
|
</header>
|
|
141
150
|
);
|
|
142
151
|
}
|
|
143
152
|
|
|
153
|
+
function moduleLabel(key?: string) {
|
|
154
|
+
switch (key) {
|
|
155
|
+
case "voice": return t("nav.modules.voice");
|
|
156
|
+
case "desktop": return t("nav.modules.desktop");
|
|
157
|
+
case "deck": return t("nav.modules.deck");
|
|
158
|
+
case "code": return t("nav.modules.code");
|
|
159
|
+
default: return key || "";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
144
163
|
function settingsLabel(key?: string) {
|
|
145
164
|
switch (key) {
|
|
146
165
|
case "super-agent": return t("settings.tabs.super_agent");
|
|
@@ -15,6 +15,7 @@ import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "
|
|
|
15
15
|
import { Button } from "./ui/button";
|
|
16
16
|
import { ChatInput } from "./ui/chat-input";
|
|
17
17
|
import { ModelPicker } from "./chat/ModelPicker";
|
|
18
|
+
import { SkillPicker } from "./chat/SkillPicker";
|
|
18
19
|
import { MessageList } from "./chat/MessageList";
|
|
19
20
|
import { ContextBar } from "./chat/ContextBar";
|
|
20
21
|
import { applyStreamEvent, textOf, type ChatMsg } from "../hooks/useChat";
|
|
@@ -22,6 +23,7 @@ import { SuperAgent } from "../lib/api";
|
|
|
22
23
|
import { STORAGE } from "../constants";
|
|
23
24
|
import { useToast } from "./Toast";
|
|
24
25
|
import { t } from "../i18n";
|
|
26
|
+
import { usePersonaName } from "../hooks/usePersonaName";
|
|
25
27
|
import type { ChatStreamEvent, ConversationMessage } from "../types/daemon";
|
|
26
28
|
|
|
27
29
|
// Load any persisted conversation, dropping half-finished (pending) turns.
|
|
@@ -44,6 +46,7 @@ export function RobyBubble({
|
|
|
44
46
|
open: boolean;
|
|
45
47
|
onOpenChange: (open: boolean) => void;
|
|
46
48
|
}) {
|
|
49
|
+
const persona = usePersonaName();
|
|
47
50
|
const toast = useToast();
|
|
48
51
|
const [msgs, setMsgs] = useState<ChatMsg[]>(loadStored);
|
|
49
52
|
const [draft, setDraft] = useState("");
|
|
@@ -170,15 +173,15 @@ export function RobyBubble({
|
|
|
170
173
|
>
|
|
171
174
|
<SheetHeader className="pr-12">
|
|
172
175
|
<SheetTitle className="flex items-center gap-2">
|
|
173
|
-
<Bot size={18} /> {t("
|
|
174
|
-
<span className="text-xs font-normal text-muted-fg">{t("
|
|
176
|
+
<Bot size={18} /> {t("superagent.title", { persona })}
|
|
177
|
+
<span className="text-xs font-normal text-muted-fg">{t("superagent.badge")}</span>
|
|
175
178
|
</SheetTitle>
|
|
176
|
-
<SheetDescription>{t("
|
|
179
|
+
<SheetDescription>{t("superagent.desc")}</SheetDescription>
|
|
177
180
|
</SheetHeader>
|
|
178
181
|
|
|
179
182
|
<div className="flex-1 overflow-y-auto">
|
|
180
183
|
{msgs.length === 0 ? (
|
|
181
|
-
<p className="mt-6 text-center text-sm text-muted-fg">{t("
|
|
184
|
+
<p className="mt-6 text-center text-sm text-muted-fg">{t("superagent.empty", { persona })}</p>
|
|
182
185
|
) : (
|
|
183
186
|
<MessageList msgs={msgs} onCopy={copyToClipboard} />
|
|
184
187
|
)}
|
|
@@ -187,13 +190,16 @@ export function RobyBubble({
|
|
|
187
190
|
<ContextBar msgs={msgs} />
|
|
188
191
|
|
|
189
192
|
<div className="border-t border-border p-3">
|
|
193
|
+
<div className="mb-1.5">
|
|
194
|
+
<SkillPicker value={draft} onPick={(slug) => setDraft(`/${slug} `)} />
|
|
195
|
+
</div>
|
|
190
196
|
<ChatInput
|
|
191
197
|
value={draft}
|
|
192
198
|
onValueChange={setDraft}
|
|
193
199
|
onSubmit={() => void send()}
|
|
194
200
|
onStop={stop}
|
|
195
201
|
busy={busy}
|
|
196
|
-
placeholder={t("
|
|
202
|
+
placeholder={t("superagent.placeholder")}
|
|
197
203
|
footer={<ModelPicker value={model} onChange={setModel} disabled={busy} />}
|
|
198
204
|
/>
|
|
199
205
|
<div className="mt-1.5 flex justify-end">
|
|
@@ -203,7 +209,7 @@ export function RobyBubble({
|
|
|
203
209
|
onClick={newChat}
|
|
204
210
|
disabled={busy || msgs.length === 0}
|
|
205
211
|
>
|
|
206
|
-
<Plus className="size-3" /> {t("
|
|
212
|
+
<Plus className="size-3" /> {t("superagent.new_chat")}
|
|
207
213
|
</Button>
|
|
208
214
|
</div>
|
|
209
215
|
</div>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import useSWR from "swr";
|
|
3
|
+
import { Skills, type SkillEntry } from "../../lib/api";
|
|
4
|
+
|
|
5
|
+
interface SkillPickerProps {
|
|
6
|
+
/**
|
|
7
|
+
* Current input value. The picker matches against the leading slug after
|
|
8
|
+
* the first "/". When the input does not start with "/" the picker hides
|
|
9
|
+
* itself entirely.
|
|
10
|
+
*/
|
|
11
|
+
value: string;
|
|
12
|
+
/** Optional project path so project-scoped skills appear in the catalog. */
|
|
13
|
+
projectPath?: string;
|
|
14
|
+
/** Called with the chosen slug. Caller decides what to do (typically append `<slug> ` and refocus). */
|
|
15
|
+
onPick: (slug: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Drop-up suggester for `/slug ...` shortcuts on the chat composer. Renders
|
|
20
|
+
* a small list of matching skills above the input when the user starts a
|
|
21
|
+
* message with "/". Keyboard navigation (↑/↓/Enter/Esc) is handled by the
|
|
22
|
+
* parent through `useSkillPickerKeyboard` (so the composer keeps owning the
|
|
23
|
+
* focused textarea).
|
|
24
|
+
*/
|
|
25
|
+
export function SkillPicker({ value, projectPath, onPick }: SkillPickerProps) {
|
|
26
|
+
const open = isSkillPickerOpen(value);
|
|
27
|
+
const query = open ? extractQuery(value) : "";
|
|
28
|
+
|
|
29
|
+
const { data } = useSWR(
|
|
30
|
+
open ? ["/skills", projectPath || ""] : null,
|
|
31
|
+
() => Skills.list(projectPath),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const list: SkillEntry[] = data?.skills || [];
|
|
35
|
+
const filtered = React.useMemo(() => {
|
|
36
|
+
const q = query.toLowerCase();
|
|
37
|
+
if (!q) return list.slice(0, 8);
|
|
38
|
+
return list
|
|
39
|
+
.filter((s) => s.slug.toLowerCase().includes(q))
|
|
40
|
+
.slice(0, 8);
|
|
41
|
+
}, [list, query]);
|
|
42
|
+
|
|
43
|
+
if (!open || filtered.length === 0) return null;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="rounded-xl border border-border bg-popover/95 text-sm shadow-md backdrop-blur">
|
|
47
|
+
<ul role="listbox" className="max-h-64 overflow-y-auto py-1">
|
|
48
|
+
{filtered.map((s) => (
|
|
49
|
+
<li key={s.slug}>
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
onClick={() => onPick(s.slug)}
|
|
53
|
+
className="flex w-full items-start gap-2 px-3 py-1.5 text-left hover:bg-accent hover:text-accent-foreground"
|
|
54
|
+
>
|
|
55
|
+
<code className="rounded bg-muted px-1.5 py-0.5 text-[11px]">/{s.slug}</code>
|
|
56
|
+
<span className="truncate text-xs text-muted-foreground">{s.description}</span>
|
|
57
|
+
</button>
|
|
58
|
+
</li>
|
|
59
|
+
))}
|
|
60
|
+
</ul>
|
|
61
|
+
<div className="border-t border-border px-3 py-1.5 text-[10px] text-muted-foreground">
|
|
62
|
+
Type a name to filter · click to insert · the skill body will be loaded for this turn.
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Trigger detection: the picker shows when the input starts with "/" + an identifier (no space yet). */
|
|
69
|
+
export function isSkillPickerOpen(value: string): boolean {
|
|
70
|
+
const m = value.match(/^\s*\/([A-Za-z0-9_-]*)$/);
|
|
71
|
+
return Boolean(m);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function extractQuery(value: string): string {
|
|
75
|
+
const m = value.match(/^\s*\/([A-Za-z0-9_-]*)$/);
|
|
76
|
+
return m ? m[1] : "";
|
|
77
|
+
}
|
|
@@ -26,7 +26,7 @@ export function CodeProjectPicker({ projects, value, onChange, disabled }: Props
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
|
-
<div className="w-
|
|
29
|
+
<div className="w-full" data-testid="code-project-select">
|
|
30
30
|
<UiSelect
|
|
31
31
|
value={value}
|
|
32
32
|
onChange={onChange}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
1
2
|
import { Gauge, GitCompare, Package } from "lucide-react";
|
|
2
3
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "../ui/tabs";
|
|
3
4
|
import { t } from "../../i18n";
|
|
@@ -14,28 +15,42 @@ interface Props {
|
|
|
14
15
|
onRefreshChanges: () => void;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
const TABS = [
|
|
19
|
+
{ value: "context", icon: Gauge, label: "tab_context" },
|
|
20
|
+
{ value: "changes", icon: GitCompare, label: "tab_changes" },
|
|
21
|
+
{ value: "artifacts", icon: Package, label: "tab_artifacts" },
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
19
24
|
export function CodeSidePanel({ pid, turns, changes, changesLoading, onRefreshChanges }: Props) {
|
|
25
|
+
const [active, setActive] = useState<string>("context");
|
|
20
26
|
const changeCount = changes?.files.length || 0;
|
|
27
|
+
|
|
21
28
|
return (
|
|
22
|
-
<Tabs
|
|
23
|
-
<div className="shrink-0 border-b border-border px-
|
|
29
|
+
<Tabs value={active} onValueChange={setActive} className="flex h-full flex-col gap-0" data-testid="code-side-panel">
|
|
30
|
+
<div className="shrink-0 border-b border-border px-2 py-2">
|
|
24
31
|
<TabsList variant="line" className="w-full">
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
{TABS.map(({ value, icon: Icon, label }) => {
|
|
33
|
+
const isActive = active === value;
|
|
34
|
+
const fullLabel = t(`code_module.${label}` as never);
|
|
35
|
+
return (
|
|
36
|
+
<TabsTrigger
|
|
37
|
+
key={value}
|
|
38
|
+
value={value}
|
|
39
|
+
title={fullLabel}
|
|
40
|
+
className={isActive ? "flex-1 min-w-0" : "w-8 shrink-0"}
|
|
41
|
+
>
|
|
42
|
+
<Icon className="size-3.5 shrink-0" />
|
|
43
|
+
{isActive && (
|
|
44
|
+
<span className="truncate text-xs">{fullLabel}</span>
|
|
45
|
+
)}
|
|
46
|
+
{value === "changes" && changeCount > 0 && (
|
|
47
|
+
<span className="ml-0.5 rounded-full bg-muted px-1 text-[10px] text-muted-foreground leading-none py-0.5">
|
|
48
|
+
{changeCount}
|
|
49
|
+
</span>
|
|
50
|
+
)}
|
|
51
|
+
</TabsTrigger>
|
|
52
|
+
);
|
|
53
|
+
})}
|
|
39
54
|
</TabsList>
|
|
40
55
|
</div>
|
|
41
56
|
<TabsContent value="context" className="min-h-0 flex-1 overflow-y-auto">
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
// collapse toggle (and optional page actions) followed by the routed content.
|
|
4
4
|
// The page title/subtitle live in the top breadcrumb now, not here.
|
|
5
5
|
import { type ReactNode } from "react";
|
|
6
|
-
import { TabNav,
|
|
6
|
+
import { TabNav, type TabSection } from "./TabNav";
|
|
7
7
|
import { cn } from "../../lib/cn";
|
|
8
|
+
import { useRegisterNavCollapse } from "../../hooks/useNavCollapseCtx";
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
10
11
|
sections: TabSection[];
|
|
@@ -29,14 +30,17 @@ export function TabLayout({
|
|
|
29
30
|
testId,
|
|
30
31
|
children,
|
|
31
32
|
}: Props) {
|
|
33
|
+
useRegisterNavCollapse(collapsed, onToggleCollapse);
|
|
34
|
+
|
|
32
35
|
return (
|
|
33
36
|
<div className="flex h-full">
|
|
34
37
|
<TabNav sections={sections} active={active} onChange={onChange} collapsed={collapsed} />
|
|
35
38
|
<div className="flex min-w-0 flex-1 flex-col overflow-y-auto">
|
|
36
|
-
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
{actions ? (
|
|
40
|
+
<div className="flex items-center justify-end gap-2 px-6 pt-3">
|
|
41
|
+
{actions}
|
|
42
|
+
</div>
|
|
43
|
+
) : null}
|
|
40
44
|
<div className={cn(contentClassName)} data-testid={testId}>
|
|
41
45
|
{children}
|
|
42
46
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Left-rail tab nav used inside Settings + per-project screens. Mirrors
|
|
2
2
|
// the panda.project sectioned-nav pattern: optional section title above
|
|
3
3
|
// each group, icon + label rows, active state in the section's tone.
|
|
4
|
-
import { useState, useEffect, Fragment, type ElementType } from "react";
|
|
4
|
+
import { useState, useEffect, useCallback, Fragment, type ElementType } from "react";
|
|
5
5
|
import { PanelLeft } from "lucide-react";
|
|
6
6
|
import { cn } from "../../lib/cn";
|
|
7
7
|
import { Tip } from "../ui/tip";
|
|
@@ -34,12 +34,12 @@ export function useNavCollapse(storageKey: string) {
|
|
|
34
34
|
try { setCollapsed(localStorage.getItem(storageKey) === "true"); } catch { /* ignore */ }
|
|
35
35
|
}, [storageKey]);
|
|
36
36
|
|
|
37
|
-
const toggle = () =>
|
|
37
|
+
const toggle = useCallback(() =>
|
|
38
38
|
setCollapsed((v) => {
|
|
39
39
|
const next = !v;
|
|
40
40
|
try { localStorage.setItem(storageKey, String(next)); } catch { /* quota */ }
|
|
41
41
|
return next;
|
|
42
|
-
});
|
|
42
|
+
}), [storageKey]);
|
|
43
43
|
|
|
44
44
|
return { collapsed, toggle };
|
|
45
45
|
}
|
|
@@ -9,6 +9,7 @@ import { ProjectAvatar } from "./ProjectAvatar";
|
|
|
9
9
|
import { Tip } from "../ui/tip";
|
|
10
10
|
import { useProjects } from "../../hooks/useProjects";
|
|
11
11
|
import { t } from "../../i18n";
|
|
12
|
+
import { usePersonaName } from "../../hooks/usePersonaName";
|
|
12
13
|
|
|
13
14
|
interface Props {
|
|
14
15
|
onSelect: (href: string) => void;
|
|
@@ -37,6 +38,7 @@ export function ProjectSidebar({ onSelect, onOpenRoby }: Props) {
|
|
|
37
38
|
const { projects, isLoading } = useProjects();
|
|
38
39
|
const location = useLocation();
|
|
39
40
|
const MODULES = buildModules();
|
|
41
|
+
const persona = usePersonaName();
|
|
40
42
|
|
|
41
43
|
const isActive = (href: string) =>
|
|
42
44
|
location.pathname === href || location.pathname.startsWith(`${href}/`);
|
|
@@ -122,12 +124,12 @@ export function ProjectSidebar({ onSelect, onOpenRoby }: Props) {
|
|
|
122
124
|
/>
|
|
123
125
|
{/* Roby launcher — subtle (not a loud floating bubble), pinned under the
|
|
124
126
|
gear so it doesn't overlap the chat composer. */}
|
|
125
|
-
<Tip content={t("
|
|
127
|
+
<Tip content={t("superagent.talk", { persona })} side="right">
|
|
126
128
|
<button
|
|
127
129
|
type="button"
|
|
128
130
|
onClick={onOpenRoby}
|
|
129
131
|
data-testid="nav-roby"
|
|
130
|
-
aria-label={t("
|
|
132
|
+
aria-label={t("superagent.talk", { persona })}
|
|
131
133
|
className="mt-1 flex size-10 items-center justify-center rounded-xl border border-border/60 bg-muted/30 text-muted-fg transition-colors hover:bg-accent hover:text-foreground"
|
|
132
134
|
>
|
|
133
135
|
<Bot size={18} />
|
|
@@ -49,7 +49,7 @@ export interface UseChatResult {
|
|
|
49
49
|
streaming: boolean;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/** Concatenate the text parts of a message (for clipboard
|
|
52
|
+
/** Concatenate the text parts of a message (for clipboard). */
|
|
53
53
|
export function textOf(msg: ChatMsg): string {
|
|
54
54
|
return msg.parts
|
|
55
55
|
.filter((p): p is TextPart => p.kind === "text")
|
|
@@ -58,6 +58,51 @@ export function textOf(msg: ChatMsg): string {
|
|
|
58
58
|
.trim();
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/** Compact line summarising an ask_questions tool call. Surfaced into the
|
|
62
|
+
* history string we send to the super-agent so the model can see it ALREADY
|
|
63
|
+
* asked and not re-ask the same questions on the next turn. Without this,
|
|
64
|
+
* ask_questions calls are invisible in history and the model loops. */
|
|
65
|
+
function summarizeAskQuestions(part: ToolPart): string | null {
|
|
66
|
+
const raw = (part.args as { questions?: unknown } | undefined)?.questions;
|
|
67
|
+
if (!Array.isArray(raw) || raw.length === 0) return null;
|
|
68
|
+
const lines = raw
|
|
69
|
+
.map((q) => {
|
|
70
|
+
if (typeof q === "string") return `- ${q}`;
|
|
71
|
+
if (!q || typeof q !== "object") return null;
|
|
72
|
+
const qq = q as { question?: unknown; options?: unknown };
|
|
73
|
+
if (typeof qq.question !== "string") return null;
|
|
74
|
+
const opts = Array.isArray(qq.options) ? qq.options : [];
|
|
75
|
+
const optStr = opts
|
|
76
|
+
.map((o) =>
|
|
77
|
+
typeof o === "string"
|
|
78
|
+
? o
|
|
79
|
+
: o && typeof o === "object" && typeof (o as { label?: unknown }).label === "string"
|
|
80
|
+
? ((o as { label: string }).label)
|
|
81
|
+
: "",
|
|
82
|
+
)
|
|
83
|
+
.filter((s) => s)
|
|
84
|
+
.join(", ");
|
|
85
|
+
return optStr ? `- ${qq.question} (opciones: ${optStr})` : `- ${qq.question}`;
|
|
86
|
+
})
|
|
87
|
+
.filter((s): s is string => !!s);
|
|
88
|
+
if (lines.length === 0) return null;
|
|
89
|
+
return `[ask_questions]\n${lines.join("\n")}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** History view of a message — text parts plus ask_questions summaries.
|
|
93
|
+
* Used when sending `previousMessages` to the super-agent. */
|
|
94
|
+
export function historyTextOf(msg: ChatMsg): string {
|
|
95
|
+
const chunks: string[] = [];
|
|
96
|
+
for (const p of msg.parts) {
|
|
97
|
+
if (p.kind === "text" && p.text) chunks.push(p.text);
|
|
98
|
+
else if (p.kind === "tool" && p.tool === "ask_questions") {
|
|
99
|
+
const s = summarizeAskQuestions(p);
|
|
100
|
+
if (s) chunks.push(s);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return chunks.join("\n\n").trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
61
106
|
const userPart = (text: string): ChatPart[] => [{ kind: "text", text }];
|
|
62
107
|
|
|
63
108
|
function isErrorResult(result: unknown): boolean {
|
|
@@ -202,7 +247,7 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
|
|
|
202
247
|
const nowIso = () => new Date().toISOString();
|
|
203
248
|
const history: ConversationMessage[] = msgs.map((m) => ({
|
|
204
249
|
role: m.role,
|
|
205
|
-
content:
|
|
250
|
+
content: historyTextOf(m),
|
|
206
251
|
}));
|
|
207
252
|
|
|
208
253
|
setMsgs((curr) => [
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createContext, useContext, useState, useEffect, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
// ── Nav collapse ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
type CollapseState = { collapsed: boolean; toggle: () => void } | null;
|
|
6
|
+
|
|
7
|
+
const CollapseReadCtx = createContext<CollapseState>(null);
|
|
8
|
+
const CollapseSetCtx = createContext<((s: CollapseState) => void) | null>(null);
|
|
9
|
+
|
|
10
|
+
// ── Page label (extra breadcrumb segment pushed by leaf screens) ──────────────
|
|
11
|
+
|
|
12
|
+
const LabelReadCtx = createContext<string>("");
|
|
13
|
+
const LabelSetCtx = createContext<((s: string) => void) | null>(null);
|
|
14
|
+
|
|
15
|
+
// ── Combined provider (one wrapper in Shell) ──────────────────────────────────
|
|
16
|
+
|
|
17
|
+
export function NavCollapseProvider({ children }: { children: ReactNode }) {
|
|
18
|
+
const [collapse, setCollapse] = useState<CollapseState>(null);
|
|
19
|
+
const [label, setLabel] = useState("");
|
|
20
|
+
return (
|
|
21
|
+
<CollapseSetCtx.Provider value={setCollapse}>
|
|
22
|
+
<CollapseReadCtx.Provider value={collapse}>
|
|
23
|
+
<LabelSetCtx.Provider value={setLabel}>
|
|
24
|
+
<LabelReadCtx.Provider value={label}>
|
|
25
|
+
{children}
|
|
26
|
+
</LabelReadCtx.Provider>
|
|
27
|
+
</LabelSetCtx.Provider>
|
|
28
|
+
</CollapseReadCtx.Provider>
|
|
29
|
+
</CollapseSetCtx.Provider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Nav collapse hooks ────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
export function useNavCollapseCtx() {
|
|
36
|
+
return useContext(CollapseReadCtx);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useRegisterNavCollapse(collapsed: boolean, toggle: () => void) {
|
|
40
|
+
const setState = useContext(CollapseSetCtx);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
setState?.({ collapsed, toggle });
|
|
43
|
+
return () => setState?.(null);
|
|
44
|
+
}, [collapsed, toggle, setState]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── Page label hooks ──────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
export function usePageLabel() {
|
|
50
|
+
return useContext(LabelReadCtx);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function useSetPageLabel(label: string) {
|
|
54
|
+
const set = useContext(LabelSetCtx);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
set?.(label);
|
|
57
|
+
return () => set?.("");
|
|
58
|
+
}, [label, set]);
|
|
59
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Resolve the super-agent display name (the persona shown to the user).
|
|
2
|
+
// Reads identity.json via useIdentity(); falls back to "APX" while loading
|
|
3
|
+
// or when identity is empty. UI strings should always interpolate this hook
|
|
4
|
+
// instead of hardcoding a persona name (it would otherwise drift from what
|
|
5
|
+
// the daemon-side resolveAgentName() reports across CLI / Telegram / etc.).
|
|
6
|
+
import { useIdentity } from "./useIdentity";
|
|
7
|
+
|
|
8
|
+
export function usePersonaName(): string {
|
|
9
|
+
const { identity } = useIdentity();
|
|
10
|
+
return (identity as { agent_name?: string })?.agent_name?.trim() || "APX";
|
|
11
|
+
}
|