@agentprojectcontext/apx 1.77.3 → 1.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -9
- package/package.json +12 -12
- package/src/core/agent/build-agent-system.js +3 -2
- package/src/core/agent/constants.js +3 -2
- package/src/core/agent/loop/greeting-guard.js +55 -0
- package/src/core/agent/loop/side-effects.js +52 -0
- package/src/core/agent/prompt-builder.js +49 -8
- package/src/core/agent/run-agent.js +43 -53
- package/src/core/agent/security.js +1 -1
- package/src/core/agent/self-memory.js +4 -2
- package/src/core/agent/skills/index-store.js +2 -2
- package/src/core/agent/skills/loader.js +3 -22
- package/src/core/agent/super-agent.js +0 -2
- package/src/core/agent/tool-summary.js +65 -0
- package/src/core/agent/tools/handlers/call-runtime.js +4 -4
- package/src/core/agent/tools/handlers/list-commitments.js +80 -0
- package/src/core/agent/tools/handlers/list-tasks.js +66 -27
- package/src/core/agent/tools/handlers/record-commitment.js +68 -0
- package/src/core/agent/tools/handlers/search-sessions.js +1 -1
- package/src/core/agent/tools/handlers/send-telegram.js +68 -2
- package/src/core/agent/tools/handlers/set-permission-mode.js +11 -4
- package/src/core/agent/tools/names.js +64 -0
- package/src/core/agent/tools/registry.js +9 -0
- package/src/core/agent/tools/tool-call-parser.js +70 -1
- package/src/core/apc/context-copy.js +1 -1
- package/src/core/apc/frontmatter.js +85 -0
- package/src/core/apc/parser.js +9 -21
- package/src/core/apc/paths.js +8 -8
- package/src/core/apc/scaffold.js +5 -4
- package/src/core/channels/telegram/ask-callbacks.js +35 -0
- package/src/core/channels/telegram/config.js +201 -0
- package/src/core/channels/telegram/dispatch.js +3 -0
- package/src/core/channels/telegram/reply.js +30 -5
- package/src/core/config/index.js +25 -183
- package/src/core/config/paths.js +57 -1
- package/src/core/config/redact.js +22 -0
- package/src/core/confirmation/adapters/web.js +1 -1
- package/src/core/daemon/service.js +238 -0
- package/src/core/deck/manifest.js +12 -12
- package/src/core/desktop/autostart.js +3 -2
- package/src/core/desktop/process.js +4 -4
- package/src/core/engines/gemini.js +322 -60
- package/src/core/engines/openai-compatible.js +21 -2
- package/src/core/engines/presets.js +2 -2
- package/src/core/http-tools/browser.js +1 -1
- package/src/core/http-tools/catalog.js +551 -0
- package/src/core/http-tools/fetch.js +1 -1
- package/src/core/http-tools/glob.js +3 -3
- package/src/core/http-tools/grep.js +2 -9
- package/src/core/http-tools/inline-handlers.js +59 -0
- package/src/core/http-tools/registry.js +31 -615
- package/src/core/http-tools/search.js +2 -2
- package/src/core/identity/self.js +6 -7
- package/src/core/identity/telegram.js +3 -3
- package/src/core/logging.js +6 -5
- package/src/core/mcp/sources.js +12 -16
- package/src/core/memory/consolidate.js +225 -0
- package/src/core/memory/indexer.js +2 -5
- package/src/core/nudge/index.js +192 -0
- package/src/core/nudge/policy.js +143 -0
- package/src/core/nudge/store.js +141 -0
- package/src/core/profiles/bundled/secretary/PROFILE.md +8 -9
- package/src/core/profiles/bundled/secretary/config.schema.json +33 -3
- package/src/core/profiles/bundled/secretary/routines/day-close.json +7 -3
- package/src/core/profiles/bundled/secretary/routines/day-open.json +7 -3
- package/src/core/profiles/bundled/secretary/routines/watch.json +13 -0
- package/src/core/profiles/lifecycle.js +0 -2
- package/src/core/profiles/store.js +2 -7
- package/src/core/routines/runner.js +103 -4
- package/src/core/routines/signals.js +270 -0
- package/src/core/runtime-skills/apx-agency-agents/SKILL.md +2 -2
- package/src/core/runtime-skills/apx-profile/SKILL.md +3 -3
- package/src/core/runtime-skills/apx-project/SKILL.md +1 -1
- package/src/core/runtime-skills/apx-task/SKILL.md +8 -8
- package/src/core/runtime-skills/apx-telegram/SKILL.md +4 -4
- package/src/core/runtime-skills/apx-voice/SKILL.md +4 -4
- package/src/core/runtimes/detect.js +2 -2
- package/src/core/sessions/index.js +800 -0
- package/src/core/stores/code-sessions.js +3 -14
- package/src/core/stores/commitments.js +331 -0
- package/src/core/stores/engine-sessions.js +0 -4
- package/src/core/stores/messages.js +4 -5
- package/src/core/stores/organization.js +9 -11
- package/src/core/stores/routines.js +23 -14
- package/src/core/stores/runtime-callbacks.js +2 -1
- package/src/core/util/json-file.js +128 -0
- package/src/core/util/thinking.js +51 -0
- package/src/core/vars/sources.js +14 -21
- package/src/core/voice/stt-models.js +1 -1
- package/src/core/voice/tts.js +5 -4
- package/src/host/daemon/api/admin-config.js +5 -5
- package/src/host/daemon/api/admin.js +8 -7
- package/src/host/daemon/api/agents.js +16 -16
- package/src/host/daemon/api/artifact-preview.js +8 -8
- package/src/host/daemon/api/artifacts.js +7 -7
- package/src/host/daemon/api/code.js +8 -8
- package/src/host/daemon/api/commitments.js +135 -0
- package/src/host/daemon/api/config.js +6 -6
- package/src/host/daemon/api/confirm.js +2 -2
- package/src/host/daemon/api/connections.js +2 -2
- package/src/host/daemon/api/conversations.js +12 -12
- package/src/host/daemon/api/deck.js +6 -6
- package/src/host/daemon/api/desktop.js +8 -8
- package/src/host/daemon/api/embeddings.js +4 -4
- package/src/host/daemon/api/engines.js +8 -7
- package/src/host/daemon/api/exec.js +5 -4
- package/src/host/daemon/api/files-project.js +6 -6
- package/src/host/daemon/api/health.js +2 -2
- package/src/host/daemon/api/identity.js +3 -3
- package/src/host/daemon/api/inbox.js +2 -2
- package/src/host/daemon/api/integrations.js +23 -19
- package/src/host/daemon/api/mcps.js +16 -12
- package/src/host/daemon/api/messages.js +5 -5
- package/src/host/daemon/api/nudges.js +112 -0
- package/src/host/daemon/api/organization.js +8 -8
- package/src/host/daemon/api/pairing.js +6 -6
- package/src/host/daemon/api/plugins.js +3 -3
- package/src/host/daemon/api/prefix.js +20 -0
- package/src/host/daemon/api/profiles.js +9 -9
- package/src/host/daemon/api/projects.js +5 -5
- package/src/host/daemon/api/routines.js +32 -8
- package/src/host/daemon/api/run.js +2 -2
- package/src/host/daemon/api/runtimes.js +6 -6
- package/src/host/daemon/api/self-memory.js +50 -0
- package/src/host/daemon/api/sessions-search.js +18 -11
- package/src/host/daemon/api/sessions.js +6 -6
- package/src/host/daemon/api/shared.js +59 -13
- package/src/host/daemon/api/skills.js +12 -12
- package/src/host/daemon/api/super-agent.js +4 -4
- package/src/host/daemon/api/tasks.js +11 -11
- package/src/host/daemon/api/telegram.js +61 -24
- package/src/host/daemon/api/tools.js +7 -7
- package/src/host/daemon/api/top-level.js +7 -7
- package/src/host/daemon/api/transcribe.js +6 -6
- package/src/host/daemon/api/tts.js +3 -3
- package/src/host/daemon/api/vars.js +11 -8
- package/src/host/daemon/api/voice.js +9 -7
- package/src/host/daemon/api/web.js +28 -32
- package/src/host/daemon/api.js +83 -48
- package/src/host/daemon/callback-reconciler.js +16 -0
- package/src/host/daemon/db.js +1 -1
- package/src/host/daemon/desktop-ws.js +7 -3
- package/src/host/daemon/index.js +11 -5
- package/src/host/daemon/plugins/desktop/index.js +8 -2
- package/src/host/daemon/plugins/telegram/index.js +7 -2
- package/src/host/daemon/stt-venv.js +20 -81
- package/src/host/daemon/wakeup.js +18 -4
- package/src/interfaces/acp/index.js +3 -3
- package/src/interfaces/acp/session.js +2 -2
- package/src/interfaces/cli/commands/a2a.js +2 -2
- package/src/interfaces/cli/commands/agent.js +3 -3
- package/src/interfaces/cli/commands/artifact.js +13 -13
- package/src/interfaces/cli/commands/chat.js +3 -3
- package/src/interfaces/cli/commands/command.js +2 -2
- package/src/interfaces/cli/commands/commitment.js +154 -0
- package/src/interfaces/cli/commands/config.js +4 -4
- package/src/interfaces/cli/commands/daemon.js +74 -18
- package/src/interfaces/cli/commands/desktop.js +7 -14
- package/src/interfaces/cli/commands/exec.js +2 -2
- package/src/interfaces/cli/commands/log.js +3 -4
- package/src/interfaces/cli/commands/mcp.js +9 -9
- package/src/interfaces/cli/commands/memory.js +75 -2
- package/src/interfaces/cli/commands/messages.js +5 -5
- package/src/interfaces/cli/commands/model.js +0 -18
- package/src/interfaces/cli/commands/nudge.js +130 -0
- package/src/interfaces/cli/commands/obsidian.js +5 -5
- package/src/interfaces/cli/commands/org.js +5 -5
- package/src/interfaces/cli/commands/pair.js +6 -6
- package/src/interfaces/cli/commands/plugins.js +2 -2
- package/src/interfaces/cli/commands/profile.js +10 -10
- package/src/interfaces/cli/commands/project-config.js +6 -6
- package/src/interfaces/cli/commands/project.js +10 -10
- package/src/interfaces/cli/commands/routine.js +8 -9
- package/src/interfaces/cli/commands/runtime.js +2 -2
- package/src/interfaces/cli/commands/search.js +0 -1
- package/src/interfaces/cli/commands/session.js +12 -40
- package/src/interfaces/cli/commands/sessions.js +21 -798
- package/src/interfaces/cli/commands/setup.js +16 -6
- package/src/interfaces/cli/commands/skills.js +1 -2
- package/src/interfaces/cli/commands/status.js +3 -3
- package/src/interfaces/cli/commands/task.js +8 -8
- package/src/interfaces/cli/commands/telegram.js +21 -21
- package/src/interfaces/cli/commands/voice.js +6 -6
- package/src/interfaces/cli/help/index.js +2245 -0
- package/src/interfaces/cli/http.js +9 -4
- package/src/interfaces/cli/index.js +14 -2857
- package/src/interfaces/cli/routes/acp.js +13 -0
- package/src/interfaces/cli/routes/agent.js +27 -0
- package/src/interfaces/cli/routes/artifact.js +28 -0
- package/src/interfaces/cli/routes/chat.js +11 -0
- package/src/interfaces/cli/routes/code.js +11 -0
- package/src/interfaces/cli/routes/command.js +21 -0
- package/src/interfaces/cli/routes/commitment.js +19 -0
- package/src/interfaces/cli/routes/config.js +16 -0
- package/src/interfaces/cli/routes/connections.js +11 -0
- package/src/interfaces/cli/routes/conversations.js +21 -0
- package/src/interfaces/cli/routes/daemon.js +25 -0
- package/src/interfaces/cli/routes/desktop.js +20 -0
- package/src/interfaces/cli/routes/env.js +13 -0
- package/src/interfaces/cli/routes/exec.js +11 -0
- package/src/interfaces/cli/routes/identity.js +11 -0
- package/src/interfaces/cli/routes/index.js +75 -0
- package/src/interfaces/cli/routes/init.js +11 -0
- package/src/interfaces/cli/routes/log.js +20 -0
- package/src/interfaces/cli/routes/mcp.js +22 -0
- package/src/interfaces/cli/routes/memory.js +19 -0
- package/src/interfaces/cli/routes/messages.js +16 -0
- package/src/interfaces/cli/routes/model.js +11 -0
- package/src/interfaces/cli/routes/nudge.js +17 -0
- package/src/interfaces/cli/routes/obsidian.js +17 -0
- package/src/interfaces/cli/routes/org.js +34 -0
- package/src/interfaces/cli/routes/overlay.js +10 -0
- package/src/interfaces/cli/routes/pair.js +17 -0
- package/src/interfaces/cli/routes/panel.js +16 -0
- package/src/interfaces/cli/routes/permission.js +11 -0
- package/src/interfaces/cli/routes/plugins.js +21 -0
- package/src/interfaces/cli/routes/profile.js +27 -0
- package/src/interfaces/cli/routes/project.js +41 -0
- package/src/interfaces/cli/routes/restart.js +15 -0
- package/src/interfaces/cli/routes/routine.js +28 -0
- package/src/interfaces/cli/routes/run.js +11 -0
- package/src/interfaces/cli/routes/search.js +11 -0
- package/src/interfaces/cli/routes/send.js +11 -0
- package/src/interfaces/cli/routes/session.js +26 -0
- package/src/interfaces/cli/routes/sessions.js +15 -0
- package/src/interfaces/cli/routes/setup.js +18 -0
- package/src/interfaces/cli/routes/skills.js +20 -0
- package/src/interfaces/cli/routes/status.js +12 -0
- package/src/interfaces/cli/routes/task.js +26 -0
- package/src/interfaces/cli/routes/telegram.js +39 -0
- package/src/interfaces/cli/routes/update.js +24 -0
- package/src/interfaces/cli/routes/voice.js +17 -0
- package/src/interfaces/desktop/main.js +6 -7
- package/src/interfaces/desktop/renderer.js +0 -5
- package/src/interfaces/mcp-server/index.js +15 -14
- package/src/interfaces/tui/_shims/globals.d.ts +8 -0
- package/src/interfaces/tui/tsconfig.json +4 -1
- package/src/interfaces/web/README.md +6 -6
- package/src/interfaces/web/dist/assets/index-CvEoGtTf.js +849 -0
- package/src/interfaces/web/dist/assets/index-CvEoGtTf.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-DzBBXFaO.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +11 -10
- package/src/interfaces/web/src/components/AddProjectDialog.tsx +1 -1
- package/src/interfaces/web/src/components/Section.tsx +18 -3
- package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +1 -1
- package/src/interfaces/web/src/components/chat/ChatList.tsx +2 -2
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +13 -0
- package/src/interfaces/web/src/components/chat/SkillPicker.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeFileTree.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeTerminal.tsx +1 -1
- package/src/interfaces/web/src/components/cron/CronPicker.tsx +196 -0
- package/src/interfaces/web/src/components/desktop/DesktopStatusCard.tsx +1 -1
- package/src/interfaces/web/src/components/files/FileBrowser.tsx +2 -2
- package/src/interfaces/web/src/components/inbox/InboxList.tsx +145 -0
- package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +36 -6
- package/src/interfaces/web/src/components/routines/ExecutionsList.tsx +1 -1
- package/src/interfaces/web/src/components/routines/RoutineDetail.tsx +16 -4
- package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +14 -4
- package/src/interfaces/web/src/components/routines/shared.ts +14 -5
- package/src/interfaces/web/src/components/settings/DesktopSettingsPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/NudgePanel.tsx +183 -0
- package/src/interfaces/web/src/components/settings/ProfilePanel.tsx +36 -12
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/SkillsManager.tsx +2 -2
- package/src/interfaces/web/src/components/tasks/TaskDetailPanel.tsx +1 -1
- package/src/interfaces/web/src/components/ui/filter-chips.tsx +47 -0
- package/src/interfaces/web/src/components/ui.tsx +1 -0
- package/src/interfaces/web/src/constants/index.ts +1 -0
- package/src/interfaces/web/src/hooks/useChat.ts +5 -1
- package/src/interfaces/web/src/hooks/useDaemonStatus.ts +1 -1
- package/src/interfaces/web/src/hooks/useDevices.ts +1 -1
- package/src/interfaces/web/src/hooks/useEngines.ts +1 -1
- package/src/interfaces/web/src/hooks/useGlobalConfig.ts +2 -2
- package/src/interfaces/web/src/hooks/useIdentity.ts +1 -1
- package/src/interfaces/web/src/hooks/useInbox.ts +1 -1
- package/src/interfaces/web/src/hooks/useNudges.ts +38 -0
- package/src/interfaces/web/src/hooks/useProfiles.ts +3 -3
- package/src/interfaces/web/src/hooks/useProjects.ts +1 -1
- package/src/interfaces/web/src/hooks/useTelegram.ts +3 -3
- package/src/interfaces/web/src/hooks/useTokenBootstrap.ts +3 -3
- package/src/interfaces/web/src/i18n/en.ts +127 -0
- package/src/interfaces/web/src/i18n/es.ts +127 -0
- package/src/interfaces/web/src/lib/api/admin.ts +11 -11
- package/src/interfaces/web/src/lib/api/agents.ts +14 -14
- package/src/interfaces/web/src/lib/api/artifacts.ts +11 -11
- package/src/interfaces/web/src/lib/api/code.ts +1 -1
- package/src/interfaces/web/src/lib/api/commitments.ts +57 -0
- package/src/interfaces/web/src/lib/api/conversations.ts +8 -8
- package/src/interfaces/web/src/lib/api/deck.ts +3 -3
- package/src/interfaces/web/src/lib/api/desktop.ts +8 -8
- package/src/interfaces/web/src/lib/api/embeddings.ts +3 -3
- package/src/interfaces/web/src/lib/api/engines.ts +3 -3
- package/src/interfaces/web/src/lib/api/filesystem.ts +2 -2
- package/src/interfaces/web/src/lib/api/health.ts +1 -1
- package/src/interfaces/web/src/lib/api/identity.ts +2 -2
- package/src/interfaces/web/src/lib/api/inbox.ts +1 -1
- package/src/interfaces/web/src/lib/api/integrations.ts +8 -8
- package/src/interfaces/web/src/lib/api/mcps.ts +6 -6
- package/src/interfaces/web/src/lib/api/messages.ts +3 -3
- package/src/interfaces/web/src/lib/api/notebook.ts +23 -0
- package/src/interfaces/web/src/lib/api/nudges.ts +53 -0
- package/src/interfaces/web/src/lib/api/organization.ts +7 -7
- package/src/interfaces/web/src/lib/api/profiles.ts +8 -8
- package/src/interfaces/web/src/lib/api/projectFiles.ts +5 -5
- package/src/interfaces/web/src/lib/api/projects.ts +12 -12
- package/src/interfaces/web/src/lib/api/routines.ts +7 -7
- package/src/interfaces/web/src/lib/api/sessions.ts +2 -2
- package/src/interfaces/web/src/lib/api/skills.ts +11 -11
- package/src/interfaces/web/src/lib/api/super_agent.ts +3 -3
- package/src/interfaces/web/src/lib/api/tasks.ts +12 -12
- package/src/interfaces/web/src/lib/api/telegram.ts +14 -14
- package/src/interfaces/web/src/lib/api/tools.ts +1 -1
- package/src/interfaces/web/src/lib/api/vars.ts +4 -4
- package/src/interfaces/web/src/lib/api/voice.ts +6 -6
- package/src/interfaces/web/src/lib/cron.ts +196 -0
- package/src/interfaces/web/src/lib/when.ts +32 -0
- package/src/interfaces/web/src/screens/InboxScreen.tsx +107 -77
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +5 -2
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +17 -3
- package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +1 -1
- package/src/interfaces/web/src/screens/base/CommitmentsTab.tsx +239 -0
- package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +103 -20
- package/src/interfaces/web/src/screens/base/LogsTab.tsx +18 -3
- package/src/interfaces/web/src/screens/base/SessionsTab.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +8 -8
- package/src/interfaces/web/src/screens/project/AgentsTab.tsx +2 -2
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +24 -6
- package/src/interfaces/web/src/screens/project/ConfigTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/McpsTab.tsx +3 -3
- package/src/interfaces/web/src/screens/project/Overview.tsx +6 -6
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +15 -13
- package/src/interfaces/web/src/screens/project/StructureTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/TasksTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/VarsTab.tsx +1 -1
- package/src/interfaces/web/src/types/daemon.ts +10 -1
- package/src/interfaces/web/vite.config.ts +13 -18
- package/src/interfaces/web/dist/assets/index-BptlbKjU.js +0 -824
- package/src/interfaces/web/dist/assets/index-BptlbKjU.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-D_EJEA1n.css +0 -1
- package/src/skills/apc-context/SKILL.md +0 -159
- /package/src/{host/daemon → core}/runtimes/_spawn.js +0 -0
- /package/src/{host/daemon → core}/runtimes/aider.js +0 -0
- /package/src/{host/daemon → core}/runtimes/antigravity.js +0 -0
- /package/src/{host/daemon → core}/runtimes/claude-code.js +0 -0
- /package/src/{host/daemon → core}/runtimes/codex.js +0 -0
- /package/src/{host/daemon → core}/runtimes/cursor-agent.js +0 -0
- /package/src/{host/daemon → core}/runtimes/gemini-cli.js +0 -0
- /package/src/{host/daemon → core}/runtimes/index.js +0 -0
- /package/src/{host/daemon → core}/runtimes/opencode.js +0 -0
- /package/src/{host/daemon → core}/runtimes/qwen-code.js +0 -0
|
@@ -110,7 +110,7 @@ function AgentConvFetcher({
|
|
|
110
110
|
onLoaded: (slug: string, data: ConversationListEntry[] | undefined) => void;
|
|
111
111
|
}) {
|
|
112
112
|
const { data } = useSWR(
|
|
113
|
-
`/projects/${pid}/agents/${slug}/conversations`,
|
|
113
|
+
`/api/projects/${pid}/agents/${slug}/conversations`,
|
|
114
114
|
() => Conversations.list(pid, slug),
|
|
115
115
|
{ revalidateOnFocus: false },
|
|
116
116
|
);
|
|
@@ -142,7 +142,7 @@ export function ChatList({
|
|
|
142
142
|
// sidebar shows just that project's own agent conversations.
|
|
143
143
|
const isBase = String(pid) === "0";
|
|
144
144
|
const threadsQ = useSWR(
|
|
145
|
-
isBase ? `/projects/${pid}/super-agent/threads` : null,
|
|
145
|
+
isBase ? `/api/projects/${pid}/super-agent/threads` : null,
|
|
146
146
|
() => Conversations.threads(pid),
|
|
147
147
|
{ revalidateOnFocus: false },
|
|
148
148
|
);
|
|
@@ -133,6 +133,19 @@ export function MessageBubble({ msg, isLast, isAskAnswer, onCopy }: Props) {
|
|
|
133
133
|
{!mine && hasTools && (
|
|
134
134
|
<span>· {t("shared_ui.tools_count", { n: msg.parts.filter((p) => p.kind === "tool").length })}</span>
|
|
135
135
|
)}
|
|
136
|
+
{/* Replayed turns have no tool parts — the live events are gone — but
|
|
137
|
+
they do carry the summary recorded at the time. Show that instead,
|
|
138
|
+
so history does not look like the agent just answered from
|
|
139
|
+
nothing. Failures are named: "it tried and could not" is the half
|
|
140
|
+
worth surfacing. */}
|
|
141
|
+
{!mine && !hasTools && msg.toolSummary?.tools?.length ? (
|
|
142
|
+
<span title={msg.toolSummary.tools.map((x) => `${x.name}×${x.count}`).join(", ")}>
|
|
143
|
+
· {t("shared_ui.tools_count", { n: msg.toolSummary.total })}
|
|
144
|
+
{msg.toolSummary.failed
|
|
145
|
+
? ` (${t("shared_ui.tools_failed", { n: msg.toolSummary.failed })})`
|
|
146
|
+
: ""}
|
|
147
|
+
</span>
|
|
148
|
+
) : null}
|
|
136
149
|
{onCopy && copyText && (
|
|
137
150
|
<Tip content={t("chat_ui.copy")}>
|
|
138
151
|
<button
|
|
@@ -27,7 +27,7 @@ export function SkillPicker({ value, projectPath, onPick }: SkillPickerProps) {
|
|
|
27
27
|
const query = open ? extractQuery(value) : "";
|
|
28
28
|
|
|
29
29
|
const { data } = useSWR(
|
|
30
|
-
open ? ["/skills", projectPath || ""] : null,
|
|
30
|
+
open ? ["/api/skills", projectPath || ""] : null,
|
|
31
31
|
() => Skills.list(projectPath),
|
|
32
32
|
);
|
|
33
33
|
|
|
@@ -120,7 +120,7 @@ export function CodeFileTree({
|
|
|
120
120
|
setLoading(true);
|
|
121
121
|
try {
|
|
122
122
|
const r = await http.post<{ ok: boolean; stdout: string; stderr: string }>(
|
|
123
|
-
"/run",
|
|
123
|
+
"/api/run",
|
|
124
124
|
{
|
|
125
125
|
cmd: "find . -type f -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/.claude/*' | sed 's|^\\./||' | sort | head -500",
|
|
126
126
|
project: pid,
|
|
@@ -50,7 +50,7 @@ export function CodeTerminal({
|
|
|
50
50
|
setBusy(true);
|
|
51
51
|
try {
|
|
52
52
|
const r = await http.post<{ ok: boolean; stdout: string; stderr: string; exit_code: number; cwd: string }>(
|
|
53
|
-
"/run",
|
|
53
|
+
"/api/run",
|
|
54
54
|
{ cmd: trimmed, project: pid },
|
|
55
55
|
);
|
|
56
56
|
if (r.stdout) setLines((l) => [...l, { type: "out", text: r.stdout }]);
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { Clock, Code2 } from "lucide-react";
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
import { t } from "../../i18n";
|
|
5
|
+
import {
|
|
6
|
+
DAY_KEYS, cronToSelection, selectionToCron, describeCron, isPickerFriendly,
|
|
7
|
+
normalizeCron, type CronSelection,
|
|
8
|
+
} from "../../lib/cron";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Read-only sentence for a cron expression. Falls back to the raw expression
|
|
12
|
+
* when the schedule is beyond what lib/cron understands — a confident wrong
|
|
13
|
+
* reading of when something runs is worse than showing the source.
|
|
14
|
+
*/
|
|
15
|
+
export function CronSummary({ expr, className }: { expr: string; className?: string }) {
|
|
16
|
+
const text = describeCron(expr, t as never);
|
|
17
|
+
return (
|
|
18
|
+
<span className={cn("inline-flex items-center gap-1.5", className)} title={expr}>
|
|
19
|
+
<Clock size={12} className="shrink-0 opacity-60" />
|
|
20
|
+
{text ? <span>{text}</span> : <code className="font-mono text-[11px]">{expr}</code>}
|
|
21
|
+
</span>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Time + day checkboxes, or every-N-hours, with the cron expression as an
|
|
27
|
+
* escape hatch rather than the only interface.
|
|
28
|
+
*
|
|
29
|
+
* The raw field stays reachable on purpose: cron can express things this
|
|
30
|
+
* picker cannot, and a form that silently narrows what the user is allowed to
|
|
31
|
+
* schedule is worse than one that asks them to type. When the current value is
|
|
32
|
+
* beyond the picker, the raw field opens by itself — the alternative is a
|
|
33
|
+
* picker quietly showing something the expression does not say.
|
|
34
|
+
*/
|
|
35
|
+
export function CronPicker({
|
|
36
|
+
value,
|
|
37
|
+
onChange,
|
|
38
|
+
disabled,
|
|
39
|
+
allowEveryHours = true,
|
|
40
|
+
}: {
|
|
41
|
+
value: string;
|
|
42
|
+
onChange: (expr: string) => void;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
allowEveryHours?: boolean;
|
|
45
|
+
}) {
|
|
46
|
+
const friendly = useMemo(() => isPickerFriendly(value), [value]);
|
|
47
|
+
const [raw, setRaw] = useState(!friendly);
|
|
48
|
+
const [rawDraft, setRawDraft] = useState(value);
|
|
49
|
+
const sel = useMemo<CronSelection>(() => cronToSelection(value), [value]);
|
|
50
|
+
|
|
51
|
+
// An expression arriving from outside (a profile default, another edit) that
|
|
52
|
+
// the picker cannot represent flips to raw rather than misrepresenting it.
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
setRawDraft(value);
|
|
55
|
+
if (!isPickerFriendly(value)) setRaw(true);
|
|
56
|
+
}, [value]);
|
|
57
|
+
|
|
58
|
+
const patch = (over: Partial<CronSelection>) =>
|
|
59
|
+
onChange(selectionToCron({ ...sel, ...over }));
|
|
60
|
+
|
|
61
|
+
const toggleDay = (d: number) => {
|
|
62
|
+
const next = sel.days.includes(d) ? sel.days.filter((x) => x !== d) : [...sel.days, d];
|
|
63
|
+
patch({ days: next });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const preset = (days: number[]) => patch({ days });
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className="flex flex-col gap-2" data-testid="cron-picker">
|
|
70
|
+
{raw ? (
|
|
71
|
+
<input
|
|
72
|
+
value={rawDraft}
|
|
73
|
+
disabled={disabled}
|
|
74
|
+
onChange={(e) => setRawDraft(e.target.value)}
|
|
75
|
+
onBlur={() => onChange(rawDraft.trim())}
|
|
76
|
+
onKeyDown={(e) => { if (e.key === "Enter") onChange(rawDraft.trim()); }}
|
|
77
|
+
placeholder="30 8 * * 1-5"
|
|
78
|
+
className="w-full rounded-lg border border-border bg-muted/30 px-2.5 py-1.5 font-mono text-sm outline-none focus:border-primary/60"
|
|
79
|
+
data-testid="cron-raw"
|
|
80
|
+
/>
|
|
81
|
+
) : (
|
|
82
|
+
<>
|
|
83
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
84
|
+
<input
|
|
85
|
+
type="time"
|
|
86
|
+
value={sel.everyHours > 0 ? "00:00" : sel.time}
|
|
87
|
+
disabled={disabled || sel.everyHours > 0}
|
|
88
|
+
onChange={(e) => patch({ time: e.target.value, everyHours: 0 })}
|
|
89
|
+
className="rounded-lg border border-border bg-muted/30 px-2.5 py-1.5 text-sm outline-none focus:border-primary/60 disabled:opacity-50"
|
|
90
|
+
data-testid="cron-time"
|
|
91
|
+
/>
|
|
92
|
+
{allowEveryHours ? (
|
|
93
|
+
<label className="flex items-center gap-1.5 text-xs text-muted-fg">
|
|
94
|
+
<input
|
|
95
|
+
type="checkbox"
|
|
96
|
+
checked={sel.everyHours > 0}
|
|
97
|
+
disabled={disabled}
|
|
98
|
+
onChange={(e) => patch({ everyHours: e.target.checked ? 2 : 0 })}
|
|
99
|
+
className="size-3.5 accent-[var(--primary)]"
|
|
100
|
+
/>
|
|
101
|
+
{t("cron.every_label")}
|
|
102
|
+
</label>
|
|
103
|
+
) : null}
|
|
104
|
+
{sel.everyHours > 0 ? (
|
|
105
|
+
<input
|
|
106
|
+
type="number"
|
|
107
|
+
min={1}
|
|
108
|
+
max={23}
|
|
109
|
+
value={sel.everyHours}
|
|
110
|
+
disabled={disabled}
|
|
111
|
+
onChange={(e) => patch({ everyHours: Number(e.target.value) })}
|
|
112
|
+
className="w-16 rounded-lg border border-border bg-muted/30 px-2 py-1.5 text-sm outline-none focus:border-primary/60"
|
|
113
|
+
aria-label={t("cron.every_label")}
|
|
114
|
+
/>
|
|
115
|
+
) : null}
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
{sel.everyHours === 0 ? (
|
|
119
|
+
<>
|
|
120
|
+
<div className="flex flex-wrap gap-1" data-testid="cron-days">
|
|
121
|
+
{DAY_KEYS.map((key, d) => {
|
|
122
|
+
const on = sel.days.length === 0 || sel.days.includes(d);
|
|
123
|
+
return (
|
|
124
|
+
<button
|
|
125
|
+
key={key}
|
|
126
|
+
type="button"
|
|
127
|
+
disabled={disabled}
|
|
128
|
+
aria-pressed={sel.days.includes(d)}
|
|
129
|
+
onClick={() => toggleDay(d)}
|
|
130
|
+
className={cn(
|
|
131
|
+
"min-w-9 rounded-md border px-2 py-1 text-[11px] font-medium transition-colors",
|
|
132
|
+
sel.days.includes(d)
|
|
133
|
+
? "border-primary bg-primary/15 text-foreground"
|
|
134
|
+
: on
|
|
135
|
+
// "every day" means all seven are in effect even
|
|
136
|
+
// though none is individually ticked — show that
|
|
137
|
+
// rather than seven blank boxes.
|
|
138
|
+
? "border-border bg-muted/40 text-muted-fg"
|
|
139
|
+
: "border-border text-muted-fg/60 hover:bg-muted/40",
|
|
140
|
+
)}
|
|
141
|
+
>
|
|
142
|
+
{t(`cron.day_short.${key}`)}
|
|
143
|
+
</button>
|
|
144
|
+
);
|
|
145
|
+
})}
|
|
146
|
+
</div>
|
|
147
|
+
<div className="flex flex-wrap gap-1 text-[11px]">
|
|
148
|
+
{([
|
|
149
|
+
["cron.preset_every_day", [] as number[]],
|
|
150
|
+
["cron.preset_weekdays", [1, 2, 3, 4, 5]],
|
|
151
|
+
["cron.preset_weekends", [0, 6]],
|
|
152
|
+
] as const).map(([label, days]) => (
|
|
153
|
+
<button
|
|
154
|
+
key={label}
|
|
155
|
+
type="button"
|
|
156
|
+
disabled={disabled}
|
|
157
|
+
onClick={() => preset([...days])}
|
|
158
|
+
className="rounded-md px-1.5 py-0.5 text-muted-fg underline-offset-2 hover:text-foreground hover:underline"
|
|
159
|
+
>
|
|
160
|
+
{t(label)}
|
|
161
|
+
</button>
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
</>
|
|
165
|
+
) : null}
|
|
166
|
+
</>
|
|
167
|
+
)}
|
|
168
|
+
|
|
169
|
+
<div className="flex items-center justify-between gap-2">
|
|
170
|
+
{/* The sentence, always — including in raw mode, where it is the only
|
|
171
|
+
confirmation that what was typed means what was intended. */}
|
|
172
|
+
<CronSummary expr={raw ? rawDraft : value} className="text-[11px] text-muted-fg" />
|
|
173
|
+
<button
|
|
174
|
+
type="button"
|
|
175
|
+
disabled={disabled}
|
|
176
|
+
onClick={() => {
|
|
177
|
+
if (raw) {
|
|
178
|
+
// Leaving raw mode must not silently rewrite a schedule the
|
|
179
|
+
// picker cannot hold.
|
|
180
|
+
const next = rawDraft.trim();
|
|
181
|
+
onChange(next);
|
|
182
|
+
if (isPickerFriendly(next)) setRaw(false);
|
|
183
|
+
} else {
|
|
184
|
+
setRawDraft(normalizeCron(value));
|
|
185
|
+
setRaw(true);
|
|
186
|
+
}
|
|
187
|
+
}}
|
|
188
|
+
className="inline-flex shrink-0 items-center gap-1 text-[11px] text-muted-fg hover:text-foreground"
|
|
189
|
+
data-testid="cron-toggle-raw"
|
|
190
|
+
>
|
|
191
|
+
<Code2 size={11} /> {raw ? t("cron.use_picker") : t("cron.use_cron")}
|
|
192
|
+
</button>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
@@ -17,7 +17,7 @@ export function DesktopStatusCard({ showConfigLink = false }: { showConfigLink?:
|
|
|
17
17
|
const toast = useToast();
|
|
18
18
|
|
|
19
19
|
const { data: status, isLoading: stLoading, mutate: mutateStatus } = useSWR(
|
|
20
|
-
"/desktop/status",
|
|
20
|
+
"/api/desktop/status",
|
|
21
21
|
() => Desktop.status(),
|
|
22
22
|
{ refreshInterval: 5000 },
|
|
23
23
|
);
|
|
@@ -29,10 +29,10 @@ export function FileBrowser({
|
|
|
29
29
|
const [selected, setSelected] = useState<string | null>(null);
|
|
30
30
|
const [newOpen, setNewOpen] = useState(false);
|
|
31
31
|
|
|
32
|
-
const treeKey = `/projects/${pid}/fs/tree?scope=${scope}`;
|
|
32
|
+
const treeKey = `/api/projects/${pid}/fs/tree?scope=${scope}`;
|
|
33
33
|
const tree = useSWR(treeKey, () => ProjectFiles.tree(pid, scope));
|
|
34
34
|
|
|
35
|
-
const fileKey = selected ? `/projects/${pid}/fs/file?scope=${scope}&path=${selected}` : null;
|
|
35
|
+
const fileKey = selected ? `/api/projects/${pid}/fs/file?scope=${scope}&path=${selected}` : null;
|
|
36
36
|
const file = useSWR<FileContent | null>(fileKey, () => (selected ? ProjectFiles.read(pid, selected, scope) : null));
|
|
37
37
|
|
|
38
38
|
const onSelect = (node: FileNode) => setSelected(node.path);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
|
+
import { Search } from "lucide-react";
|
|
3
|
+
import type { InboxRow } from "../../lib/api/inbox";
|
|
4
|
+
import { cn } from "../../lib/cn";
|
|
5
|
+
import { t } from "../../i18n";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The conversation rail: every agent as a chat, most recent first.
|
|
9
|
+
*
|
|
10
|
+
* Messaging-app shape rather than a data table, because that is what this is —
|
|
11
|
+
* a list of people-ish things that said something, ordered by when. The
|
|
12
|
+
* previous version was a full-width list of cards that navigated AWAY on
|
|
13
|
+
* click, which meant losing the list to read one row.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Stable colour per agent, so the same agent is the same dot every time. */
|
|
17
|
+
const DOT_COLOURS = [
|
|
18
|
+
"bg-emerald-500", "bg-orange-500", "bg-violet-500", "bg-sky-500",
|
|
19
|
+
"bg-rose-500", "bg-amber-500", "bg-teal-500", "bg-fuchsia-500",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
function colourFor(seed: string): string {
|
|
23
|
+
let h = 0;
|
|
24
|
+
for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) >>> 0;
|
|
25
|
+
return DOT_COLOURS[h % DOT_COLOURS.length];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function rowKey(row: InboxRow): string {
|
|
29
|
+
return `${row.project_id ?? "global"}::${row.agent_slug}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Today → time of day; older → date. Same idea as a messaging app. */
|
|
33
|
+
function shortTime(iso: string): string {
|
|
34
|
+
if (!iso) return "";
|
|
35
|
+
const d = new Date(iso);
|
|
36
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
37
|
+
const sameDay = new Date().toDateString() === d.toDateString();
|
|
38
|
+
return sameDay
|
|
39
|
+
? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
|
40
|
+
: d.toLocaleDateString(undefined, { day: "numeric", month: "short" });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function InboxList({
|
|
44
|
+
rows,
|
|
45
|
+
selectedKey,
|
|
46
|
+
onSelect,
|
|
47
|
+
action,
|
|
48
|
+
}: {
|
|
49
|
+
rows: InboxRow[];
|
|
50
|
+
selectedKey: string | null;
|
|
51
|
+
onSelect: (row: InboxRow) => void;
|
|
52
|
+
action?: React.ReactNode;
|
|
53
|
+
}) {
|
|
54
|
+
const [q, setQ] = useState("");
|
|
55
|
+
|
|
56
|
+
const filtered = useMemo(() => {
|
|
57
|
+
const needle = q.trim().toLowerCase();
|
|
58
|
+
if (!needle) return rows;
|
|
59
|
+
return rows.filter((r) =>
|
|
60
|
+
[r.agent_name, r.agent_slug, r.project_name, r.preview]
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.some((v) => String(v).toLowerCase().includes(needle)));
|
|
63
|
+
}, [rows, q]);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<aside className="flex w-full shrink-0 flex-col border-r border-border sm:w-72" data-testid="inbox-list">
|
|
67
|
+
<div className="flex shrink-0 items-center gap-2 border-b border-border p-2">
|
|
68
|
+
<div className="relative flex-1">
|
|
69
|
+
<Search size={13} className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-fg" />
|
|
70
|
+
<input
|
|
71
|
+
value={q}
|
|
72
|
+
onChange={(e) => setQ(e.target.value)}
|
|
73
|
+
placeholder={t("inbox.search")}
|
|
74
|
+
aria-label={t("inbox.search")}
|
|
75
|
+
className="w-full rounded-lg border border-border bg-muted/30 py-1.5 pl-7 pr-2 text-sm outline-none placeholder:text-muted-fg focus:border-primary/60"
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
{action}
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div className="min-h-0 flex-1 overflow-y-auto p-1.5">
|
|
82
|
+
{!filtered.length ? (
|
|
83
|
+
<p className="px-3 py-6 text-center text-xs text-muted-fg">
|
|
84
|
+
{q ? t("inbox.no_match") : t("inbox.empty")}
|
|
85
|
+
</p>
|
|
86
|
+
) : null}
|
|
87
|
+
|
|
88
|
+
{filtered.map((row) => {
|
|
89
|
+
const key = rowKey(row);
|
|
90
|
+
const isActive = key === selectedKey;
|
|
91
|
+
const label = row.agent_name || row.agent_slug;
|
|
92
|
+
return (
|
|
93
|
+
<button
|
|
94
|
+
key={key}
|
|
95
|
+
type="button"
|
|
96
|
+
data-testid={`inbox-row-${row.agent_slug}`}
|
|
97
|
+
onClick={() => onSelect(row)}
|
|
98
|
+
className={cn(
|
|
99
|
+
"flex w-full items-start gap-2.5 rounded-lg px-2.5 py-2 text-left transition-colors",
|
|
100
|
+
isActive ? "bg-accent text-accent-fg" : "hover:bg-muted/50",
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
{/* A coloured disc, not an emoji on plain background: at this size
|
|
104
|
+
the colour is what the eye picks the row out by. */}
|
|
105
|
+
<span
|
|
106
|
+
className={cn(
|
|
107
|
+
"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full text-sm",
|
|
108
|
+
colourFor(row.agent_slug),
|
|
109
|
+
)}
|
|
110
|
+
aria-hidden
|
|
111
|
+
>
|
|
112
|
+
{row.agent_emoji || label.slice(0, 1).toUpperCase()}
|
|
113
|
+
</span>
|
|
114
|
+
|
|
115
|
+
<span className="min-w-0 flex-1">
|
|
116
|
+
<span className="flex items-baseline gap-2">
|
|
117
|
+
<span className="truncate text-sm font-medium">{label}</span>
|
|
118
|
+
{row.pinned ? (
|
|
119
|
+
<span className="shrink-0 rounded bg-primary/15 px-1 text-[9px] font-semibold uppercase tracking-wide text-primary">
|
|
120
|
+
{t("inbox.pinned")}
|
|
121
|
+
</span>
|
|
122
|
+
) : null}
|
|
123
|
+
<span className="ml-auto shrink-0 text-[10px] text-muted-fg">
|
|
124
|
+
{shortTime(row.last_activity_at)}
|
|
125
|
+
</span>
|
|
126
|
+
</span>
|
|
127
|
+
|
|
128
|
+
<span className="mt-0.5 flex items-center gap-1.5 text-[10px] text-muted-fg">
|
|
129
|
+
{row.project_name ? <span className="truncate">{row.project_name}</span> : null}
|
|
130
|
+
{row.channel ? <span className="opacity-70">· {row.channel}</span> : null}
|
|
131
|
+
</span>
|
|
132
|
+
|
|
133
|
+
{/* The agent's own last line. Echoing the user's prompt back
|
|
134
|
+
tells them nothing they do not already know. */}
|
|
135
|
+
<span className="mt-0.5 block truncate text-xs text-muted-fg">
|
|
136
|
+
{row.preview || t("inbox.no_reply_yet")}
|
|
137
|
+
</span>
|
|
138
|
+
</span>
|
|
139
|
+
</button>
|
|
140
|
+
);
|
|
141
|
+
})}
|
|
142
|
+
</div>
|
|
143
|
+
</aside>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -1,34 +1,43 @@
|
|
|
1
1
|
import { useMemo, useState } from "react";
|
|
2
2
|
import useSWR from "swr";
|
|
3
|
-
import { Brain, Bot, Crown, RefreshCw } from "lucide-react";
|
|
3
|
+
import { Brain, Bot, Crown, NotebookPen, RefreshCw } from "lucide-react";
|
|
4
4
|
import { Agents, Projects } from "../../lib/api";
|
|
5
|
+
import { Notebook } from "../../lib/api/notebook";
|
|
5
6
|
import type { AgentEntry, FileContent } from "../../types/daemon";
|
|
6
7
|
import { cn } from "../../lib/cn";
|
|
7
8
|
import { Spinner, Empty } from "../ui";
|
|
8
9
|
import { useToast } from "../Toast";
|
|
9
10
|
import { t } from "../../i18n";
|
|
11
|
+
import { usePersonaName } from "../../hooks/usePersonaName";
|
|
10
12
|
import { FileViewer } from "../files/FileViewer";
|
|
11
13
|
|
|
12
14
|
// Which memory is open in the right pane.
|
|
13
|
-
type Sel = { kind: "project" } | { kind: "agent"; slug: string };
|
|
15
|
+
type Sel = { kind: "notebook" } | { kind: "project" } | { kind: "agent"; slug: string };
|
|
14
16
|
|
|
15
17
|
function selId(s: Sel): string {
|
|
18
|
+
if (s.kind === "notebook") return "notebook";
|
|
16
19
|
return s.kind === "project" ? "project" : `agent:${s.slug}`;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
// On-disk-ish path shown in the viewer header (mirrors the real memory.md
|
|
20
23
|
// locations so it reads familiarly, like the docs surface).
|
|
21
24
|
function selPath(s: Sel): string {
|
|
25
|
+
// The notebook is NOT under the project — it is global to the super-agent.
|
|
26
|
+
// Showing its real absolute-ish path is the whole point: the confusion this
|
|
27
|
+
// entry fixes was not knowing where it lived.
|
|
28
|
+
if (s.kind === "notebook") return "~/.apx/memory.md";
|
|
22
29
|
return s.kind === "project" ? ".apc/memory.md" : `agents/${s.slug}/memory.md`;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
function loadBody(pid: string, s: Sel): Promise<string> {
|
|
33
|
+
if (s.kind === "notebook") return Notebook.get().then((r) => r.body);
|
|
26
34
|
return s.kind === "project"
|
|
27
35
|
? Projects.memory.get(pid).then((r) => r.body)
|
|
28
36
|
: Agents.memory.get(pid, s.slug).then((r) => r.body);
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
function saveBody(pid: string, s: Sel, body: string): Promise<void> {
|
|
40
|
+
if (s.kind === "notebook") return Notebook.put(body).then(() => {});
|
|
32
41
|
return s.kind === "project"
|
|
33
42
|
? Projects.memory.put(pid, body).then(() => {})
|
|
34
43
|
: Agents.memory.put(pid, s.slug, body).then(() => {});
|
|
@@ -65,11 +74,16 @@ function SidebarItem({
|
|
|
65
74
|
// right for markdown edit / split-preview / save — the same surface as /docs.
|
|
66
75
|
export function MemoryBrowser({ pid }: { pid: string }) {
|
|
67
76
|
const toast = useToast();
|
|
68
|
-
|
|
77
|
+
// Opens on the notebook. It is the memory that ships in every prompt on
|
|
78
|
+
// every channel, so it is the one most worth seeing first.
|
|
79
|
+
const [sel, setSel] = useState<Sel>({ kind: "notebook" });
|
|
80
|
+
const notebook = useSWR("/api/notebook", () => Notebook.get());
|
|
81
|
+
// Never hardcode the agent's name (AGENTS.md rule 13) — it is the user's to set.
|
|
82
|
+
const persona = usePersonaName();
|
|
69
83
|
|
|
70
|
-
const agents = useSWR(`/projects/${pid}/agents`, () => Agents.list(pid));
|
|
84
|
+
const agents = useSWR(`/api/projects/${pid}/agents`, () => Agents.list(pid));
|
|
71
85
|
|
|
72
|
-
const bodyKey = `/memory/${pid}/${selId(sel)}`;
|
|
86
|
+
const bodyKey = `/api/memory/${pid}/${selId(sel)}`;
|
|
73
87
|
const body = useSWR(bodyKey, () => loadBody(pid, sel));
|
|
74
88
|
|
|
75
89
|
// Adapt the raw memory body into the FileContent shape FileViewer expects.
|
|
@@ -115,8 +129,24 @@ export function MemoryBrowser({ pid }: { pid: string }) {
|
|
|
115
129
|
</div>
|
|
116
130
|
|
|
117
131
|
<div className="min-h-0 flex-1 overflow-y-auto p-1.5">
|
|
118
|
-
{/*
|
|
132
|
+
{/* The super-agent's own notebook. First, and in its own group,
|
|
133
|
+
because it is global — not a property of this project — and its
|
|
134
|
+
absence from this list is what made "where is Roby's memory?" an
|
|
135
|
+
unanswerable question. */}
|
|
119
136
|
<p className="px-1.5 pb-1 pt-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/60">
|
|
137
|
+
{t("project.memories.super_agent_group")}
|
|
138
|
+
</p>
|
|
139
|
+
<SidebarItem
|
|
140
|
+
active={sel.kind === "notebook"}
|
|
141
|
+
onClick={() => setSel({ kind: "notebook" })}
|
|
142
|
+
icon={NotebookPen}
|
|
143
|
+
iconClass="text-emerald-400"
|
|
144
|
+
label={t("project.memories.notebook_item", { persona })}
|
|
145
|
+
sub={notebook.data ? t("project.memories.tokens", { n: notebook.data.approx_tokens }) : undefined}
|
|
146
|
+
/>
|
|
147
|
+
|
|
148
|
+
{/* General / project memory */}
|
|
149
|
+
<p className="px-1.5 pb-1 pt-3 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/60">
|
|
120
150
|
{t("project.memories.general_group")}
|
|
121
151
|
</p>
|
|
122
152
|
<SidebarItem
|
|
@@ -120,7 +120,7 @@ function RunDetailPanel({ m, onClose }: { m: MessageEntry; onClose: () => void }
|
|
|
120
120
|
* opens a side grid column with that run's details. */
|
|
121
121
|
export function ExecutionsList({ pid, name, running }: { pid: string; name: string; running?: boolean }) {
|
|
122
122
|
const runs = useSWR(
|
|
123
|
-
`/projects/${pid}/routines/${name}/runs`,
|
|
123
|
+
`/api/projects/${pid}/routines/${name}/runs`,
|
|
124
124
|
async () => {
|
|
125
125
|
const msgs = await Messages.project(pid, { channel: "routine", limit: 200 });
|
|
126
126
|
// Keep one row per run: the runner's end-of-run system summary.
|
|
@@ -4,6 +4,7 @@ import { Badge, Button, Switch, Tip } from "../ui";
|
|
|
4
4
|
import { cn } from "../../lib/cn";
|
|
5
5
|
import { t } from "../../i18n";
|
|
6
6
|
import { kindMeta, scheduleHuman } from "./shared";
|
|
7
|
+
import { relativeWhen } from "../../lib/when";
|
|
7
8
|
import { ReadOnlyBlock } from "./ReadOnlyBlock";
|
|
8
9
|
import { ExecutionsList } from "./ExecutionsList";
|
|
9
10
|
|
|
@@ -30,7 +31,7 @@ export function RoutineDetail({
|
|
|
30
31
|
// Read-only content blocks, in pipeline order (pre → action → post).
|
|
31
32
|
const blocks: { title: string; body: string; mono?: boolean }[] = [];
|
|
32
33
|
if (pre.length) blocks.push({ title: t("project.routines.block_pre"), body: pre.join("\n"), mono: true });
|
|
33
|
-
if (routine.kind === "exec_agent" || routine.kind === "super_agent") {
|
|
34
|
+
if (routine.kind === "exec_agent" || routine.kind === "super_agent" || routine.kind === "watch") {
|
|
34
35
|
blocks.push({ title: t("project.routines.block_prompt"), body: String(spec.prompt || "") });
|
|
35
36
|
} else if (routine.kind === "telegram") {
|
|
36
37
|
blocks.push({ title: t("project.routines.block_text"), body: String(spec.text || "") });
|
|
@@ -64,9 +65,20 @@ export function RoutineDetail({
|
|
|
64
65
|
|
|
65
66
|
{/* compact meta: schedule / next / last */}
|
|
66
67
|
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-fg">
|
|
67
|
-
<span>⏱ {scheduleHuman(routine.schedule)}</span>
|
|
68
|
-
{
|
|
69
|
-
|
|
68
|
+
<span title={routine.schedule}>⏱ {scheduleHuman(routine.schedule)}</span>
|
|
69
|
+
{/* "in 8 hours" answers the question; the absolute time is one hover
|
|
70
|
+
away for when the exact minute matters. Two full timestamps side
|
|
71
|
+
by side made the row unreadable and still needed mental arithmetic. */}
|
|
72
|
+
{routine.next_run_at && (
|
|
73
|
+
<span title={new Date(routine.next_run_at).toLocaleString()}>
|
|
74
|
+
{t("project.routines.next_run")} {relativeWhen(routine.next_run_at, t as never)}
|
|
75
|
+
</span>
|
|
76
|
+
)}
|
|
77
|
+
{routine.last_run_at && (
|
|
78
|
+
<span title={new Date(routine.last_run_at).toLocaleString()}>
|
|
79
|
+
{t("project.routines.last_run")} {relativeWhen(routine.last_run_at, t as never)}
|
|
80
|
+
</span>
|
|
81
|
+
)}
|
|
70
82
|
<span className={cn(routine.last_status === "ok" && "text-emerald-500", routine.last_status === "error" && "text-destructive")}>
|
|
71
83
|
{t("agents_ui.last_label")} {routine.last_status || "—"}
|
|
72
84
|
</span>
|
|
@@ -8,6 +8,8 @@ import { useToast } from "../Toast";
|
|
|
8
8
|
import { cn } from "../../lib/cn";
|
|
9
9
|
import { t } from "../../i18n";
|
|
10
10
|
import { type Kind, kindMeta, kindOptions, schedPresets, scheduleHuman, splitLines, varsFor } from "./shared";
|
|
11
|
+
import { CronPicker } from "../cron/CronPicker";
|
|
12
|
+
import { parseCron } from "../../lib/cron";
|
|
11
13
|
import { VarTextarea } from "./VarTextarea";
|
|
12
14
|
import { AvailableVarsCard } from "./AvailableVarsCard";
|
|
13
15
|
|
|
@@ -15,7 +17,7 @@ export function RoutineEditor({
|
|
|
15
17
|
draft, onClose, onSaved, pid,
|
|
16
18
|
}: { draft: Partial<RoutineEntry> | null; onClose: () => void; onSaved: () => void; pid: string }) {
|
|
17
19
|
const toast = useToast();
|
|
18
|
-
const agentsList = useSWR(draft ? `/projects/${pid}/agents` : null, () => Agents.list(pid));
|
|
20
|
+
const agentsList = useSWR(draft ? `/api/projects/${pid}/agents` : null, () => Agents.list(pid));
|
|
19
21
|
const [busy, setBusy] = useState(false);
|
|
20
22
|
|
|
21
23
|
const [name, setName] = useState("");
|
|
@@ -35,7 +37,7 @@ export function RoutineEditor({
|
|
|
35
37
|
const [post, setPost] = useState("");
|
|
36
38
|
|
|
37
39
|
// Telegram channels for the selector (default + any project/global channels).
|
|
38
|
-
const tgChannels = useSWR(draft && kind === "telegram" ? "/telegram/channels" : null, () => Telegram.channels.list());
|
|
40
|
+
const tgChannels = useSWR(draft && kind === "telegram" ? "/api/telegram/channels" : null, () => Telegram.channels.list());
|
|
39
41
|
|
|
40
42
|
// Load draft → fields.
|
|
41
43
|
useEffect(() => {
|
|
@@ -59,12 +61,13 @@ export function RoutineEditor({
|
|
|
59
61
|
|
|
60
62
|
// Pre/post shell wrap the LLM kinds AND telegram (pre can fetch data, the text
|
|
61
63
|
// can use {{pre_output}}, post can react to the result).
|
|
62
|
-
const usesPrePost = kind === "exec_agent" || kind === "super_agent" || kind === "telegram";
|
|
64
|
+
const usesPrePost = kind === "exec_agent" || kind === "super_agent" || kind === "telegram" || kind === "watch";
|
|
63
65
|
|
|
64
66
|
const buildSpec = (): Record<string, unknown> => {
|
|
65
67
|
switch (kind) {
|
|
66
68
|
case "exec_agent": return { agent, prompt };
|
|
67
69
|
case "super_agent": return { prompt };
|
|
70
|
+
case "watch": return { prompt };
|
|
68
71
|
case "telegram": return { channel: tgChannel, ...(tgChatId ? { chat_id: tgChatId } : {}), text: tgText };
|
|
69
72
|
case "shell": return { command };
|
|
70
73
|
case "heartbeat": return { channel: hbChannel, message: hbMessage };
|
|
@@ -109,6 +112,7 @@ export function RoutineEditor({
|
|
|
109
112
|
switch (kind) {
|
|
110
113
|
case "exec_agent": return agent ? t("agents_ui.action_agent_answers", { agent }) : t("agents_ui.action_agent_pick_answers");
|
|
111
114
|
case "super_agent": return t("agents_ui.action_super_answers");
|
|
115
|
+
case "watch": return t("agents_ui.action_watch");
|
|
112
116
|
case "telegram": return t("agents_ui.action_telegram_channel", { channel: tgChannel });
|
|
113
117
|
case "shell": return command ? t("agents_ui.summary_runs_cmd", { cmd: command.slice(0, 48) }) : t("agents_ui.action_runs_shell");
|
|
114
118
|
case "heartbeat": return t("agents_ui.summary_heartbeat");
|
|
@@ -176,7 +180,13 @@ export function RoutineEditor({
|
|
|
176
180
|
{t("agents_ui.preset_manual")}
|
|
177
181
|
</button>
|
|
178
182
|
</div>
|
|
179
|
-
|
|
183
|
+
{/* A cron schedule gets the picker; every:/once:/manual stay a
|
|
184
|
+
text field, because those forms are already readable. */}
|
|
185
|
+
{parseCron(schedule.replace(/^cron\s+/i, "")) ? (
|
|
186
|
+
<CronPicker value={schedule.replace(/^cron\s+/i, "")} onChange={setSchedule} />
|
|
187
|
+
) : (
|
|
188
|
+
<Input value={schedule} onChange={(e) => setSchedule(e.target.value)} placeholder="every:10m · 0 9 * * 1-5 · once:ISO · manual" />
|
|
189
|
+
)}
|
|
180
190
|
</div>
|
|
181
191
|
</Field>
|
|
182
192
|
<AvailableVarsCard />
|