@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
|
@@ -2,6 +2,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
import { AlertTriangle, ChevronDown } from "lucide-react";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
|
+
import { t } from "../i18n";
|
|
5
6
|
|
|
6
7
|
// Editable combobox: type freely, matching options appear below; click one to
|
|
7
8
|
// pick it, or keep your own text. Behaves like a text input that is also a
|
|
@@ -82,7 +83,7 @@ export function ModelCombobox({
|
|
|
82
83
|
)}
|
|
83
84
|
>
|
|
84
85
|
{invalid && (
|
|
85
|
-
<span title={invalidHint || "
|
|
86
|
+
<span title={invalidHint || t("models_ui.invalid_hint")}>
|
|
86
87
|
<AlertTriangle className="size-3.5 shrink-0 text-amber-400" />
|
|
87
88
|
</span>
|
|
88
89
|
)}
|
|
@@ -43,7 +43,7 @@ export function TelegramChannelDialog({ channel, onClose, onSaved }: Props) {
|
|
|
43
43
|
<Dialog
|
|
44
44
|
open={!!channel}
|
|
45
45
|
onClose={onClose}
|
|
46
|
-
title={channel?.name ?
|
|
46
|
+
title={channel?.name ? t("telegram_channel_dialog.edit_title", { name: channel.name }) : t("telegram_channel_dialog.new_title")}
|
|
47
47
|
description="POST /telegram/channels (upsert) — PATCH /telegram/channels/:name (parcial)."
|
|
48
48
|
footer={
|
|
49
49
|
<>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { MessageCircleQuestion } from "lucide-react";
|
|
2
|
+
import { cn } from "../../lib/cn";
|
|
3
|
+
import { t } from "../../i18n";
|
|
4
|
+
|
|
5
|
+
interface QA {
|
|
6
|
+
question: string;
|
|
7
|
+
answer: string;
|
|
8
|
+
skipped: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Parse the compiled answer text emitted by InlineAskPanel.compileAnswers.
|
|
12
|
+
// Returns null when the text doesn't match the expected pattern (so callers
|
|
13
|
+
// can fall back to the standard user bubble).
|
|
14
|
+
export function parseAskAnswerText(text: string): QA[] | null {
|
|
15
|
+
const lines = text.split("\n");
|
|
16
|
+
const pairs: QA[] = [];
|
|
17
|
+
let current: QA | null = null;
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
if (line.startsWith("- ")) {
|
|
20
|
+
if (current) pairs.push(current);
|
|
21
|
+
current = { question: line.slice(2), answer: "", skipped: false };
|
|
22
|
+
} else if (line.startsWith(" → ") && current) {
|
|
23
|
+
const a = line.slice(4);
|
|
24
|
+
current.answer = a;
|
|
25
|
+
current.skipped = a === "(omitido)";
|
|
26
|
+
} else {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (current) pairs.push(current);
|
|
31
|
+
return pairs.length > 0 ? pairs : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Props {
|
|
35
|
+
text: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Full-width centered card rendered between the assistant turn that asked the
|
|
39
|
+
// questions and the next assistant turn. Replaces the right-aligned user bubble
|
|
40
|
+
// so the Q&A reads as a single coherent block instead of an opaque user reply.
|
|
41
|
+
export function AskAnswersCard({ text }: Props) {
|
|
42
|
+
const pairs = parseAskAnswerText(text);
|
|
43
|
+
if (!pairs) return null;
|
|
44
|
+
return (
|
|
45
|
+
<div className="flex w-full justify-center">
|
|
46
|
+
<div
|
|
47
|
+
className="w-full max-w-[85%] rounded-2xl border border-border/70 bg-card/40 px-4 py-3 shadow-sm"
|
|
48
|
+
data-testid="ask-answers-card"
|
|
49
|
+
>
|
|
50
|
+
<div className="mb-2 flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground">
|
|
51
|
+
<MessageCircleQuestion className="size-3.5" />
|
|
52
|
+
<span>{t("ask_panel.answers_header")}</span>
|
|
53
|
+
</div>
|
|
54
|
+
<ul className="space-y-2.5">
|
|
55
|
+
{pairs.map((p, i) => (
|
|
56
|
+
<li key={i} className="space-y-0.5">
|
|
57
|
+
<div className="text-sm font-medium leading-snug text-foreground">
|
|
58
|
+
{p.question}
|
|
59
|
+
</div>
|
|
60
|
+
<div
|
|
61
|
+
className={cn(
|
|
62
|
+
"whitespace-pre-wrap text-[13px] leading-snug",
|
|
63
|
+
p.skipped
|
|
64
|
+
? "italic text-muted-foreground/70"
|
|
65
|
+
: "text-muted-foreground",
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
{p.answer}
|
|
69
|
+
</div>
|
|
70
|
+
</li>
|
|
71
|
+
))}
|
|
72
|
+
</ul>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { Bot, Copy, User, Info } from "lucide-react";
|
|
1
|
+
import { Bot, Copy, User, Info, Sparkles } from "lucide-react";
|
|
2
2
|
import { cn } from "../../lib/cn";
|
|
3
3
|
import { ToolCall } from "./ToolCall";
|
|
4
4
|
import { AskQuestionsCard } from "./AskQuestionsCard";
|
|
5
|
+
import { AskAnswersCard, parseAskAnswerText } from "./AskAnswersCard";
|
|
5
6
|
import { textOf, type ChatMsg } from "../../hooks/useChat";
|
|
7
|
+
import { t } from "../../i18n";
|
|
6
8
|
|
|
7
9
|
interface Props {
|
|
8
10
|
msg: ChatMsg;
|
|
@@ -10,14 +12,24 @@ interface Props {
|
|
|
10
12
|
* ask_questions tool call is still waiting for the user vs already answered
|
|
11
13
|
* (a later user message would push this assistant turn off the bottom). */
|
|
12
14
|
isLast?: boolean;
|
|
15
|
+
/** True when this user message is the reply to a preceding `ask_questions`
|
|
16
|
+
* call. Renders as a full-width centered card instead of the user bubble. */
|
|
17
|
+
isAskAnswer?: boolean;
|
|
13
18
|
onCopy?: (text: string) => void;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
|
-
export function MessageBubble({ msg, isLast, onCopy }: Props) {
|
|
21
|
+
export function MessageBubble({ msg, isLast, isAskAnswer, onCopy }: Props) {
|
|
17
22
|
const mine = msg.role === "user";
|
|
18
23
|
const copyText = textOf(msg);
|
|
19
24
|
const hasTools = msg.parts.some((p) => p.kind === "tool");
|
|
20
25
|
|
|
26
|
+
if (mine && isAskAnswer) {
|
|
27
|
+
const text = textOf(msg);
|
|
28
|
+
if (parseAskAnswerText(text)) {
|
|
29
|
+
return <AskAnswersCard text={text} />;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
21
33
|
return (
|
|
22
34
|
<div className={cn("group flex items-start gap-2", mine ? "justify-end" : "justify-start")}>
|
|
23
35
|
{!mine && (
|
|
@@ -37,6 +49,26 @@ export function MessageBubble({ msg, isLast, onCopy }: Props) {
|
|
|
37
49
|
</div>
|
|
38
50
|
)}
|
|
39
51
|
|
|
52
|
+
{/* Skill Inspector: which skills the per-turn RAG injected for this turn. */}
|
|
53
|
+
{!mine && msg.inspector && (msg.inspector.loaded?.length || msg.inspector.hinted?.length) ? (
|
|
54
|
+
<div
|
|
55
|
+
className="flex flex-wrap items-center gap-1 text-[10px] text-sky-400/90"
|
|
56
|
+
title={`Skill Inspector (${msg.inspector.embedder || "RAG"}) eligió estas skills para este turno`}
|
|
57
|
+
>
|
|
58
|
+
<Sparkles size={10} />
|
|
59
|
+
{msg.inspector.loaded?.map((s) => (
|
|
60
|
+
<span key={`l-${s}`} className="rounded bg-sky-500/15 px-1 py-0.5 font-mono">
|
|
61
|
+
{s}
|
|
62
|
+
</span>
|
|
63
|
+
))}
|
|
64
|
+
{msg.inspector.hinted?.map((s) => (
|
|
65
|
+
<span key={`h-${s}`} className="rounded border border-sky-500/30 px-1 py-0.5 font-mono opacity-70">
|
|
66
|
+
{s}?
|
|
67
|
+
</span>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
) : null}
|
|
71
|
+
|
|
40
72
|
{/* Ordered parts: interleaved assistant text + tool calls. */}
|
|
41
73
|
{msg.parts.map((part, i) =>
|
|
42
74
|
part.kind === "tool" ? (
|
|
@@ -82,9 +114,10 @@ export function MessageBubble({ msg, isLast, onCopy }: Props) {
|
|
|
82
114
|
type="button"
|
|
83
115
|
onClick={() => onCopy(copyText)}
|
|
84
116
|
className="inline-flex items-center gap-1 hover:text-foreground"
|
|
85
|
-
title="
|
|
117
|
+
title={t("chat_ui.copy")}
|
|
118
|
+
aria-label={t("chat_ui.copy")}
|
|
86
119
|
>
|
|
87
|
-
<Copy size={10} />
|
|
120
|
+
<Copy size={10} /> {t("chat_ui.copy")}
|
|
88
121
|
</button>
|
|
89
122
|
)}
|
|
90
123
|
</div>
|
|
@@ -28,9 +28,31 @@ export function MessageList({ msgs, onCopy }: Props) {
|
|
|
28
28
|
return (
|
|
29
29
|
<div className="space-y-4 px-3 py-4">
|
|
30
30
|
{msgs.map((m, i) => (
|
|
31
|
-
<MessageBubble
|
|
31
|
+
<MessageBubble
|
|
32
|
+
key={i}
|
|
33
|
+
msg={m}
|
|
34
|
+
isLast={i === lastIdx}
|
|
35
|
+
isAskAnswer={isAnswerToAsk(msgs, i)}
|
|
36
|
+
onCopy={onCopy}
|
|
37
|
+
/>
|
|
32
38
|
))}
|
|
33
39
|
<div ref={bottomRef} />
|
|
34
40
|
</div>
|
|
35
41
|
);
|
|
36
42
|
}
|
|
43
|
+
|
|
44
|
+
// A user message is an "ask answer" when the preceding assistant turn ended on
|
|
45
|
+
// an ask_questions tool call (its last tool part). The InlineAskPanel compiles
|
|
46
|
+
// the user's picks into a single text reply, which we then render as a centered
|
|
47
|
+
// full-width card instead of the standard right-aligned user bubble.
|
|
48
|
+
function isAnswerToAsk(msgs: ChatMsg[], i: number): boolean {
|
|
49
|
+
const m = msgs[i];
|
|
50
|
+
if (!m || m.role !== "user") return false;
|
|
51
|
+
const prev = msgs[i - 1];
|
|
52
|
+
if (!prev || prev.role !== "assistant") return false;
|
|
53
|
+
for (let j = prev.parts.length - 1; j >= 0; j--) {
|
|
54
|
+
const p = prev.parts[j];
|
|
55
|
+
if (p.kind === "tool") return p.tool === "ask_questions";
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
2
2
|
import { Server, ChevronDown, X, Check } from "lucide-react";
|
|
3
3
|
import { cn } from "../../lib/cn";
|
|
4
4
|
import { Engines } from "../../lib/api";
|
|
5
|
+
import { t } from "../../i18n";
|
|
5
6
|
|
|
6
7
|
// Compact model picker for the chat composer (the panda.project pattern): a
|
|
7
8
|
// small "Server · <model>" button that opens a dropdown to pick a model
|
|
@@ -79,7 +80,8 @@ export function ModelPicker({
|
|
|
79
80
|
"hover:bg-accent/60 hover:text-foreground",
|
|
80
81
|
value && "text-foreground",
|
|
81
82
|
)}
|
|
82
|
-
title="
|
|
83
|
+
title={t("chat_ui.pick_model")}
|
|
84
|
+
aria-label={t("chat_ui.pick_model")}
|
|
83
85
|
>
|
|
84
86
|
<Server className="size-3 shrink-0" />
|
|
85
87
|
<span className="truncate font-mono">{label}</span>
|
|
@@ -137,7 +137,7 @@ function ArtifactRow({
|
|
|
137
137
|
) : (
|
|
138
138
|
<span className="min-w-0 flex-1 truncate font-mono">{entry.name}</span>
|
|
139
139
|
)}
|
|
140
|
-
<Tip content="
|
|
140
|
+
<Tip content={t("code_module.artifacts_rename")}>
|
|
141
141
|
<button
|
|
142
142
|
type="button"
|
|
143
143
|
onClick={startRename}
|
|
@@ -170,7 +170,7 @@ function ArtifactRow({
|
|
|
170
170
|
<div className="flex flex-wrap items-center gap-1 mt-1">
|
|
171
171
|
{/* Ver button */}
|
|
172
172
|
<Dialog open={viewOpen} onOpenChange={setViewOpen}>
|
|
173
|
-
<Tip content="
|
|
173
|
+
<Tip content={t("code_module.artifacts_view")}>
|
|
174
174
|
<button
|
|
175
175
|
type="button"
|
|
176
176
|
onClick={() => setViewOpen(true)}
|
|
@@ -198,7 +198,7 @@ function ArtifactRow({
|
|
|
198
198
|
</Dialog>
|
|
199
199
|
|
|
200
200
|
{/* Editar — opens as a file tab in the main panel */}
|
|
201
|
-
<Tip content="
|
|
201
|
+
<Tip content={t("code_module.artifacts_edit")}>
|
|
202
202
|
<button
|
|
203
203
|
type="button"
|
|
204
204
|
onClick={() => onEditArtifact?.(entry.name)}
|
|
@@ -338,7 +338,7 @@ export function CodeArtifactsTab({ pid, onRunInTerminal, onEditArtifact }: Props
|
|
|
338
338
|
? t("code_module.artifacts_count", { n: entries.length })
|
|
339
339
|
: ""}
|
|
340
340
|
</span>
|
|
341
|
-
<Tip content="
|
|
341
|
+
<Tip content={t("code_module.reload")}>
|
|
342
342
|
<button
|
|
343
343
|
type="button"
|
|
344
344
|
onClick={() => void list.mutate()}
|
|
@@ -61,7 +61,7 @@ export function CodeChangesTab({ changes, loading, onRefresh }: Props) {
|
|
|
61
61
|
<span className="text-[11px] text-muted-foreground">
|
|
62
62
|
{files.length > 0 ? t("code_module.changes_files", { n: files.length }) : ""}
|
|
63
63
|
</span>
|
|
64
|
-
<Tip content="
|
|
64
|
+
<Tip content={t("code_module.reload")}>
|
|
65
65
|
<button
|
|
66
66
|
type="button"
|
|
67
67
|
onClick={onRefresh}
|
|
@@ -4,6 +4,7 @@ import { cn } from "../../lib/cn";
|
|
|
4
4
|
import { Empty, Spinner } from "../ui";
|
|
5
5
|
import { Tip } from "../ui/tip";
|
|
6
6
|
import { http } from "../../lib/http";
|
|
7
|
+
import { t } from "../../i18n";
|
|
7
8
|
|
|
8
9
|
interface FileNode {
|
|
9
10
|
name: string;
|
|
@@ -164,7 +165,7 @@ export function CodeFileTree({
|
|
|
164
165
|
<div className="flex shrink-0 items-center justify-between border-b border-border px-3 py-2">
|
|
165
166
|
<span className="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">Archivos</span>
|
|
166
167
|
<div className="flex items-center gap-0.5">
|
|
167
|
-
<Tip content="
|
|
168
|
+
<Tip content={t("code_module.tree_collapse_all")}>
|
|
168
169
|
<button
|
|
169
170
|
type="button"
|
|
170
171
|
onClick={collapseAll}
|
|
@@ -174,7 +175,7 @@ export function CodeFileTree({
|
|
|
174
175
|
<ChevronsUpDown className="size-3" />
|
|
175
176
|
</button>
|
|
176
177
|
</Tip>
|
|
177
|
-
<Tip content="
|
|
178
|
+
<Tip content={t("code_module.reload")}>
|
|
178
179
|
<button
|
|
179
180
|
type="button"
|
|
180
181
|
onClick={() => void loadFiles()}
|
|
@@ -3,6 +3,7 @@ import { Save, RotateCcw } from "lucide-react";
|
|
|
3
3
|
import { cn } from "../../lib/cn";
|
|
4
4
|
import { Spinner } from "../ui";
|
|
5
5
|
import { Tip } from "../ui/tip";
|
|
6
|
+
import { t } from "../../i18n";
|
|
6
7
|
|
|
7
8
|
export function CodeFileViewer({
|
|
8
9
|
path,
|
|
@@ -47,7 +48,7 @@ export function CodeFileViewer({
|
|
|
47
48
|
</span>
|
|
48
49
|
{editable && (
|
|
49
50
|
<>
|
|
50
|
-
<Tip content="
|
|
51
|
+
<Tip content={t("code_module.discard_changes")}>
|
|
51
52
|
<button
|
|
52
53
|
type="button"
|
|
53
54
|
onClick={() => setDraft(content)}
|
|
@@ -58,7 +59,7 @@ export function CodeFileViewer({
|
|
|
58
59
|
Descartar
|
|
59
60
|
</button>
|
|
60
61
|
</Tip>
|
|
61
|
-
<Tip content="
|
|
62
|
+
<Tip content={t("code_module.save_shortcut_hint")}>
|
|
62
63
|
<button
|
|
63
64
|
type="button"
|
|
64
65
|
onClick={() => void save()}
|
|
@@ -3,6 +3,7 @@ import { Terminal as TerminalIcon, Eraser, X } from "lucide-react";
|
|
|
3
3
|
import { cn } from "../../lib/cn";
|
|
4
4
|
import { Tip } from "../ui/tip";
|
|
5
5
|
import { http } from "../../lib/http";
|
|
6
|
+
import { t } from "../../i18n";
|
|
6
7
|
|
|
7
8
|
interface Line {
|
|
8
9
|
type: "cmd" | "out" | "err";
|
|
@@ -83,7 +84,7 @@ export function CodeTerminal({
|
|
|
83
84
|
<div className="flex shrink-0 items-center gap-2 border-b border-border px-3 py-1">
|
|
84
85
|
<TerminalIcon className="size-3 text-muted-foreground" />
|
|
85
86
|
<span className="flex-1 text-[11px] text-muted-foreground">Terminal</span>
|
|
86
|
-
<Tip content="
|
|
87
|
+
<Tip content={t("code_module.terminal_clear")}>
|
|
87
88
|
<button
|
|
88
89
|
type="button"
|
|
89
90
|
onClick={() => setLines([])}
|
|
@@ -92,7 +93,7 @@ export function CodeTerminal({
|
|
|
92
93
|
<Eraser className="size-3" />
|
|
93
94
|
</button>
|
|
94
95
|
</Tip>
|
|
95
|
-
<Tip content="
|
|
96
|
+
<Tip content={t("code_module.terminal_close")}>
|
|
96
97
|
<button
|
|
97
98
|
type="button"
|
|
98
99
|
onClick={() => onClose?.()}
|
|
@@ -5,6 +5,7 @@ import { useGlobalConfig } from "../../hooks/useGlobalConfig";
|
|
|
5
5
|
import { flattenObject } from "../../lib/config-values";
|
|
6
6
|
import { isSecretMarker } from "../../lib/secrets";
|
|
7
7
|
import { Loading } from "../ui";
|
|
8
|
+
import { t } from "../../i18n";
|
|
8
9
|
|
|
9
10
|
export function GlobalConfigEditor() {
|
|
10
11
|
const { config, isLoading, patch, mutate } = useGlobalConfig();
|
|
@@ -23,7 +24,7 @@ export function GlobalConfigEditor() {
|
|
|
23
24
|
|
|
24
25
|
return (
|
|
25
26
|
<Section
|
|
26
|
-
title="
|
|
27
|
+
title={t("global_config.title")}
|
|
27
28
|
description="Config general en ~/.apx/config.json. Editable por tabs; JSON queda separado."
|
|
28
29
|
>
|
|
29
30
|
<ConfigTabsEditor
|
|
@@ -2,6 +2,7 @@ import { useState } from "react";
|
|
|
2
2
|
import { Badge, Switch } from "../ui";
|
|
3
3
|
import { cn } from "../../lib/cn";
|
|
4
4
|
import type { DeckWidget } from "../../lib/api/deck";
|
|
5
|
+
import { t } from "../../i18n";
|
|
5
6
|
|
|
6
7
|
// Status badge tone mapping.
|
|
7
8
|
function statusTone(s: DeckWidget["status"]): "success" | "muted" | "warning" | "info" {
|
|
@@ -63,7 +64,7 @@ export function WidgetRow({ widget, onToggle }: WidgetRowProps) {
|
|
|
63
64
|
>
|
|
64
65
|
{/* Source dot */}
|
|
65
66
|
<span
|
|
66
|
-
title={widget.source === "apx" ? "
|
|
67
|
+
title={widget.source === "apx" ? t("deck_screen.widget_native") : t("deck_screen.widget_external")}
|
|
67
68
|
className={cn(
|
|
68
69
|
"size-2 shrink-0 rounded-full",
|
|
69
70
|
widget.source === "apx" ? "bg-emerald-500" : "bg-sky-400"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Plus, Trash2 } from "lucide-react";
|
|
2
|
+
import { Button, Input } from "../ui";
|
|
3
|
+
import { VarTokenInput } from "./VarTokenInput";
|
|
4
|
+
|
|
5
|
+
// Editable list of {key,value} pairs. Used for MCP env (stdio) and headers
|
|
6
|
+
// (http). Values are run through VarTokenInput so `${var.X}` references
|
|
7
|
+
// render as inline badges and can be inserted via the picker.
|
|
8
|
+
|
|
9
|
+
export interface KvRow {
|
|
10
|
+
key: string;
|
|
11
|
+
value: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function rowsFromRecord(rec?: Record<string, string> | null): KvRow[] {
|
|
15
|
+
if (!rec) return [];
|
|
16
|
+
return Object.entries(rec).map(([key, value]) => ({ key, value: String(value) }));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function recordFromRows(rows: KvRow[]): Record<string, string> {
|
|
20
|
+
const out: Record<string, string> = {};
|
|
21
|
+
for (const r of rows) {
|
|
22
|
+
if (!r.key.trim()) continue;
|
|
23
|
+
out[r.key.trim()] = r.value;
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface KeyValueListProps {
|
|
29
|
+
rows: KvRow[];
|
|
30
|
+
onChange: (next: KvRow[]) => void;
|
|
31
|
+
keyPlaceholder?: string;
|
|
32
|
+
valuePlaceholder?: string;
|
|
33
|
+
varNames?: string[];
|
|
34
|
+
onCreateVar?: () => void;
|
|
35
|
+
emptyLabel?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function KeyValueList({
|
|
39
|
+
rows,
|
|
40
|
+
onChange,
|
|
41
|
+
keyPlaceholder = "KEY",
|
|
42
|
+
valuePlaceholder = "value",
|
|
43
|
+
varNames,
|
|
44
|
+
onCreateVar,
|
|
45
|
+
emptyLabel,
|
|
46
|
+
}: KeyValueListProps) {
|
|
47
|
+
const update = (i: number, patch: Partial<KvRow>) => {
|
|
48
|
+
const next = rows.slice();
|
|
49
|
+
next[i] = { ...next[i], ...patch };
|
|
50
|
+
onChange(next);
|
|
51
|
+
};
|
|
52
|
+
const remove = (i: number) => onChange(rows.filter((_, j) => j !== i));
|
|
53
|
+
const add = () => onChange([...rows, { key: "", value: "" }]);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="space-y-2">
|
|
57
|
+
{rows.length === 0 && emptyLabel && (
|
|
58
|
+
<p className="text-[11px] text-muted-foreground">{emptyLabel}</p>
|
|
59
|
+
)}
|
|
60
|
+
{rows.map((row, i) => (
|
|
61
|
+
<div key={i} className="flex items-start gap-2">
|
|
62
|
+
<Input
|
|
63
|
+
value={row.key}
|
|
64
|
+
onChange={(e) => update(i, { key: e.target.value })}
|
|
65
|
+
placeholder={keyPlaceholder}
|
|
66
|
+
className="w-40 font-mono text-xs"
|
|
67
|
+
/>
|
|
68
|
+
<div className="flex-1">
|
|
69
|
+
<VarTokenInput
|
|
70
|
+
value={row.value}
|
|
71
|
+
onChange={(v) => update(i, { value: v })}
|
|
72
|
+
placeholder={valuePlaceholder}
|
|
73
|
+
varNames={varNames}
|
|
74
|
+
onCreateVar={onCreateVar}
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
<Button
|
|
78
|
+
type="button"
|
|
79
|
+
size="sm"
|
|
80
|
+
variant="ghost"
|
|
81
|
+
onClick={() => remove(i)}
|
|
82
|
+
aria-label="quitar fila"
|
|
83
|
+
>
|
|
84
|
+
<Trash2 size={13} />
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
87
|
+
))}
|
|
88
|
+
<Button type="button" size="sm" variant="ghost" onClick={add}>
|
|
89
|
+
<Plus size={12} /> Agregar fila
|
|
90
|
+
</Button>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|