@agentprojectcontext/apx 1.32.2 → 1.33.1

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.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/skills/apc-context/SKILL.md +2 -5
  3. package/src/core/agent/prompts/action-discipline.md +12 -5
  4. package/src/core/agent/prompts/channels/telegram.md +9 -5
  5. package/src/core/apc/parser.js +1 -1
  6. package/src/core/apc/scaffold.js +3 -1
  7. package/src/core/apc/skill-sync.js +3 -1
  8. package/src/core/engines/gemini.js +28 -11
  9. package/src/core/engines/index.js +11 -1
  10. package/src/core/stores/code-sessions.js +4 -1
  11. package/src/host/daemon/api/artifacts.js +25 -0
  12. package/src/host/daemon/api/code.js +14 -1
  13. package/src/host/daemon/api/engines.js +31 -1
  14. package/src/host/daemon/api/exec.js +17 -2
  15. package/src/host/daemon/plugins/telegram/dispatch.js +573 -0
  16. package/src/host/daemon/plugins/telegram/helpers.js +130 -0
  17. package/src/host/daemon/plugins/telegram/index.js +19 -694
  18. package/src/interfaces/web/dist/assets/index-Aaiw8BZN.css +1 -0
  19. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js +602 -0
  20. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js.map +1 -0
  21. package/src/interfaces/web/dist/index.html +2 -2
  22. package/src/interfaces/web/package-lock.json +3 -3
  23. package/src/interfaces/web/src/App.tsx +3 -1
  24. package/src/interfaces/web/src/components/ModelCombobox.tsx +42 -7
  25. package/src/interfaces/web/src/components/UiSelect.tsx +12 -2
  26. package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +253 -111
  27. package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +10 -8
  28. package/src/interfaces/web/src/components/code/CodeComposer.tsx +20 -17
  29. package/src/interfaces/web/src/components/code/CodeContextTab.tsx +43 -18
  30. package/src/interfaces/web/src/components/code/CodeFileTree.tsx +212 -0
  31. package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +121 -0
  32. package/src/interfaces/web/src/components/code/CodeSessionList.tsx +30 -26
  33. package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +23 -19
  34. package/src/interfaces/web/src/components/code/CodeTerminal.tsx +140 -0
  35. package/src/interfaces/web/src/components/common/TabLayout.tsx +3 -3
  36. package/src/interfaces/web/src/components/ui/chat-input.tsx +17 -6
  37. package/src/interfaces/web/src/hooks/useChat.ts +1 -0
  38. package/src/interfaces/web/src/hooks/useNavCollapseCtx.tsx +25 -1
  39. package/src/interfaces/web/src/i18n/es.ts +1 -1
  40. package/src/interfaces/web/src/lib/api/agents.ts +1 -1
  41. package/src/interfaces/web/src/lib/api/artifacts.ts +10 -0
  42. package/src/interfaces/web/src/lib/api/code.ts +4 -2
  43. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +423 -79
  44. package/src/interfaces/web/src/screens/project/ChatTab.tsx +7 -10
  45. package/src/core/util/text-similarity.js +0 -52
  46. package/src/interfaces/web/dist/assets/index-34U_Mp1M.css +0 -1
  47. package/src/interfaces/web/dist/assets/index-BkybwwRn.js +0 -570
  48. package/src/interfaces/web/dist/assets/index-BkybwwRn.js.map +0 -1
@@ -0,0 +1,140 @@
1
+ import { useState, useRef, useEffect } from "react";
2
+ import { Terminal as TerminalIcon, Eraser, X } from "lucide-react";
3
+ import { cn } from "../../lib/cn";
4
+ import { Tip } from "../ui/tip";
5
+ import { http } from "../../lib/http";
6
+
7
+ interface Line {
8
+ type: "cmd" | "out" | "err";
9
+ text: string;
10
+ }
11
+
12
+ export function CodeTerminal({
13
+ pid,
14
+ className,
15
+ initCmd,
16
+ onClose,
17
+ }: {
18
+ pid: string;
19
+ className?: string;
20
+ initCmd?: string;
21
+ /** Click handler for the header × button. Closes the terminal panel. */
22
+ onClose?: () => void;
23
+ }) {
24
+ const [lines, setLines] = useState<Line[]>([]);
25
+ const [input, setInput] = useState("");
26
+ const [busy, setBusy] = useState(false);
27
+ const [history, setHistory] = useState<string[]>([]);
28
+ const [histIdx, setHistIdx] = useState(-1);
29
+ const bottomRef = useRef<HTMLDivElement>(null);
30
+ const inputRef = useRef<HTMLInputElement>(null);
31
+
32
+ useEffect(() => {
33
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
34
+ }, [lines]);
35
+
36
+ // Pre-fill input when a command is pushed from parent (e.g. artifact run).
37
+ useEffect(() => {
38
+ if (!initCmd) return;
39
+ setInput(initCmd);
40
+ setTimeout(() => inputRef.current?.focus(), 50);
41
+ }, [initCmd]);
42
+
43
+ const run = async (cmd: string) => {
44
+ const trimmed = cmd.trim();
45
+ if (!trimmed) return;
46
+ setHistory((h) => [trimmed, ...h.slice(0, 49)]);
47
+ setHistIdx(-1);
48
+ setLines((l) => [...l, { type: "cmd", text: `$ ${trimmed}` }]);
49
+ setBusy(true);
50
+ try {
51
+ const r = await http.post<{ ok: boolean; stdout: string; stderr: string; exit_code: number; cwd: string }>(
52
+ "/run",
53
+ { cmd: trimmed, project: pid },
54
+ );
55
+ if (r.stdout) setLines((l) => [...l, { type: "out", text: r.stdout }]);
56
+ if (r.stderr) setLines((l) => [...l, { type: "err", text: r.stderr }]);
57
+ } catch (e) {
58
+ setLines((l) => [...l, { type: "err", text: String((e as Error).message) }]);
59
+ } finally {
60
+ setBusy(false);
61
+ }
62
+ };
63
+
64
+ const onKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
65
+ if (e.key === "Enter") {
66
+ void run(input);
67
+ setInput("");
68
+ } else if (e.key === "ArrowUp") {
69
+ e.preventDefault();
70
+ const next = Math.min(histIdx + 1, history.length - 1);
71
+ setHistIdx(next);
72
+ setInput(history[next] ?? "");
73
+ } else if (e.key === "ArrowDown") {
74
+ e.preventDefault();
75
+ const next = Math.max(histIdx - 1, -1);
76
+ setHistIdx(next);
77
+ setInput(next === -1 ? "" : (history[next] ?? ""));
78
+ }
79
+ };
80
+
81
+ return (
82
+ <div className={cn("flex h-full min-h-0 flex-col bg-card/60", className)} data-testid="code-terminal">
83
+ <div className="flex shrink-0 items-center gap-2 border-b border-border px-3 py-1">
84
+ <TerminalIcon className="size-3 text-muted-foreground" />
85
+ <span className="flex-1 text-[11px] text-muted-foreground">Terminal</span>
86
+ <Tip content="Limpiar">
87
+ <button
88
+ type="button"
89
+ onClick={() => setLines([])}
90
+ className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground"
91
+ >
92
+ <Eraser className="size-3" />
93
+ </button>
94
+ </Tip>
95
+ <Tip content="Cerrar terminal">
96
+ <button
97
+ type="button"
98
+ onClick={() => onClose?.()}
99
+ className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground"
100
+ >
101
+ <X className="size-3" />
102
+ </button>
103
+ </Tip>
104
+ </div>
105
+ <div
106
+ className="min-h-0 flex-1 overflow-y-auto px-3 py-1 font-mono text-[11px] leading-snug cursor-text"
107
+ onClick={() => inputRef.current?.focus()}
108
+ >
109
+ {lines.map((l, i) => (
110
+ <div
111
+ key={i}
112
+ className={cn(
113
+ "whitespace-pre-wrap break-all",
114
+ l.type === "cmd" && "text-emerald-400",
115
+ l.type === "err" && "text-rose-400",
116
+ l.type === "out" && "text-foreground/90",
117
+ )}
118
+ >
119
+ {l.text}
120
+ </div>
121
+ ))}
122
+ <div ref={bottomRef} />
123
+ </div>
124
+ <div className="flex shrink-0 items-center border-t border-border px-3 py-1">
125
+ <span className="mr-2 text-[11px] text-emerald-400 font-mono">$</span>
126
+ <input
127
+ ref={inputRef}
128
+ value={input}
129
+ onChange={(e) => setInput(e.target.value)}
130
+ onKeyDown={onKey}
131
+ disabled={busy}
132
+ placeholder={busy ? "ejecutando…" : "comando…"}
133
+ className="flex-1 bg-transparent font-mono text-[11px] text-foreground outline-none placeholder:text-muted-foreground/50 disabled:opacity-50"
134
+ spellCheck={false}
135
+ autoComplete="off"
136
+ />
137
+ </div>
138
+ </div>
139
+ );
140
+ }
@@ -35,13 +35,13 @@ export function TabLayout({
35
35
  return (
36
36
  <div className="flex h-full">
37
37
  <TabNav sections={sections} active={active} onChange={onChange} collapsed={collapsed} />
38
- <div className="flex min-w-0 flex-1 flex-col overflow-y-auto">
38
+ <div className="flex min-w-0 flex-1 flex-col overflow-hidden">
39
39
  {actions ? (
40
- <div className="flex items-center justify-end gap-2 px-6 pt-3">
40
+ <div className="flex shrink-0 items-center justify-end gap-2 px-6 pt-3">
41
41
  {actions}
42
42
  </div>
43
43
  ) : null}
44
- <div className={cn(contentClassName)} data-testid={testId}>
44
+ <div className={cn("flex-1 min-h-0 overflow-y-auto", contentClassName)} data-testid={testId}>
45
45
  {children}
46
46
  </div>
47
47
  </div>
@@ -49,12 +49,23 @@ export function ChatInput({
49
49
  React.useLayoutEffect(() => {
50
50
  const el = ref.current
51
51
  if (!el) return
52
- el.style.height = "auto"
53
- const lineHeight = parseFloat(getComputedStyle(el).lineHeight) || 20
54
- const min = lineHeight * minRows
55
- const max = lineHeight * maxRows
56
- el.style.height = `${Math.min(Math.max(el.scrollHeight, min), max)}px`
57
- el.style.overflowY = el.scrollHeight > max ? "auto" : "hidden"
52
+ const resize = () => {
53
+ el.style.height = "auto"
54
+ // Force a reflow before reading scrollHeight so the "auto" reset takes
55
+ // effect without this, scrollHeight can return the stale prior height.
56
+ void el.offsetHeight
57
+ const lineHeight = parseFloat(getComputedStyle(el).lineHeight) || 20
58
+ const min = lineHeight * minRows
59
+ const max = lineHeight * maxRows
60
+ el.style.height = `${Math.min(Math.max(el.scrollHeight, min), max)}px`
61
+ el.style.overflowY = el.scrollHeight > max ? "auto" : "hidden"
62
+ }
63
+ resize()
64
+ // Re-run after the next paint to catch cases where the parent layout
65
+ // wasn't ready on the initial sync pass (e.g. inside a resizable panel
66
+ // that's just been mounted).
67
+ const raf = requestAnimationFrame(resize)
68
+ return () => cancelAnimationFrame(raf)
58
69
  }, [value, minRows, maxRows])
59
70
 
60
71
  const canSend = value.trim().length > 0 && !disabled
@@ -263,6 +263,7 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
263
263
  const out = await Agents.chat(pid, opts.agentSlug, {
264
264
  prompt: trimmed,
265
265
  conversation_id: convoRef.current,
266
+ model: opts.model || undefined,
266
267
  });
267
268
  convoRef.current = out.conversation_id;
268
269
  patchLast((m) => ({
@@ -12,17 +12,27 @@ const CollapseSetCtx = createContext<((s: CollapseState) => void) | null>(null);
12
12
  const LabelReadCtx = createContext<string>("");
13
13
  const LabelSetCtx = createContext<((s: string) => void) | null>(null);
14
14
 
15
+ // ── Page actions (buttons screens can inject into the TopBar) ─────────────────
16
+
17
+ const ActionsReadCtx = createContext<ReactNode>(null);
18
+ const ActionsSetCtx = createContext<((a: ReactNode) => void) | null>(null);
19
+
15
20
  // ── Combined provider (one wrapper in Shell) ──────────────────────────────────
16
21
 
17
22
  export function NavCollapseProvider({ children }: { children: ReactNode }) {
18
23
  const [collapse, setCollapse] = useState<CollapseState>(null);
19
24
  const [label, setLabel] = useState("");
25
+ const [actions, setActions] = useState<ReactNode>(null);
20
26
  return (
21
27
  <CollapseSetCtx.Provider value={setCollapse}>
22
28
  <CollapseReadCtx.Provider value={collapse}>
23
29
  <LabelSetCtx.Provider value={setLabel}>
24
30
  <LabelReadCtx.Provider value={label}>
25
- {children}
31
+ <ActionsSetCtx.Provider value={setActions}>
32
+ <ActionsReadCtx.Provider value={actions}>
33
+ {children}
34
+ </ActionsReadCtx.Provider>
35
+ </ActionsSetCtx.Provider>
26
36
  </LabelReadCtx.Provider>
27
37
  </LabelSetCtx.Provider>
28
38
  </CollapseReadCtx.Provider>
@@ -57,3 +67,17 @@ export function useSetPageLabel(label: string) {
57
67
  return () => set?.("");
58
68
  }, [label, set]);
59
69
  }
70
+
71
+ // ── Page actions hooks ────────────────────────────────────────────────────────
72
+
73
+ export function usePageActions() {
74
+ return useContext(ActionsReadCtx);
75
+ }
76
+
77
+ export function useSetPageActions(actions: ReactNode) {
78
+ const set = useContext(ActionsSetCtx);
79
+ useEffect(() => {
80
+ set?.(actions);
81
+ return () => set?.(null);
82
+ }, [actions, set]);
83
+ }
@@ -286,7 +286,7 @@ export const es = {
286
286
 
287
287
  chat: {
288
288
  title: "Chat con agente",
289
- subtitle: "Conversaciones directas con agentes del proyecto. El super-agent no interviene.",
289
+ subtitle: "Chat directo con el agente del proyecto.",
290
290
  superagent_title: "Chat con {persona}",
291
291
  superagent_subtitle: "Chat con {persona} — el super-agente APX. Puede usar tools (proyectos, tasks, mcps, agentes).",
292
292
  empty: "Mandá un mensaje para arrancar la conversación.",
@@ -10,7 +10,7 @@ export const Agents = {
10
10
  http.patch<AgentEntry>(`/projects/${pid}/agents/${encodeURIComponent(slug)}`, body),
11
11
  remove: (pid: string, slug: string) =>
12
12
  http.del<{ ok: boolean }>(`/projects/${pid}/agents/${encodeURIComponent(slug)}`),
13
- chat: (pid: string, slug: string, body: { prompt: string; conversation_id?: string }) =>
13
+ chat: (pid: string, slug: string, body: { prompt: string; conversation_id?: string; model?: string }) =>
14
14
  http.post<{ conversation_id: string; text: string; usage?: unknown; engine: string }>(
15
15
  `/projects/${pid}/agents/${encodeURIComponent(slug)}/chat`,
16
16
  body,
@@ -44,4 +44,14 @@ export const Artifacts = {
44
44
  http.del<void>(
45
45
  `/projects/${encodeURIComponent(pid)}/artifacts/${encodeURIComponent(name)}`,
46
46
  ),
47
+ write: (pid: string, name: string, content: string) =>
48
+ http.patch<{ ok: boolean; name: string }>(
49
+ `/projects/${encodeURIComponent(pid)}/artifacts/${encodeURIComponent(name)}`,
50
+ { content },
51
+ ),
52
+ rename: (pid: string, name: string, newName: string) =>
53
+ http.patch<{ ok: boolean; name: string }>(
54
+ `/projects/${encodeURIComponent(pid)}/artifacts/${encodeURIComponent(name)}`,
55
+ { newName },
56
+ ),
47
57
  };
@@ -39,6 +39,7 @@ export interface CodeSessionRow {
39
39
  title: string;
40
40
  mode: CodeMode;
41
41
  model: string | null;
42
+ agentSlug: string | null;
42
43
  createdAt: string;
43
44
  updatedAt: string;
44
45
  messageCount: number;
@@ -53,6 +54,7 @@ export interface CodeSession {
53
54
  createdAt: string;
54
55
  updatedAt: string;
55
56
  model: string | null;
57
+ agentSlug: string | null;
56
58
  mode: CodeMode;
57
59
  git: { baselineCommit: string | null; baselineTree: string } | null;
58
60
  messages: CodeTurn[];
@@ -89,13 +91,13 @@ export const Code = {
89
91
 
90
92
  create: (
91
93
  pid: string | number,
92
- body: { title?: string; model?: string | null; mode?: CodeMode } = {},
94
+ body: { title?: string; model?: string | null; mode?: CodeMode; agentSlug?: string | null } = {},
93
95
  ) => http.post<CodeSession>(base(pid), body),
94
96
 
95
97
  update: (
96
98
  pid: string | number,
97
99
  sid: string,
98
- patch: { title?: string; model?: string | null; mode?: CodeMode },
100
+ patch: { title?: string; model?: string | null; mode?: CodeMode; agentSlug?: string | null },
99
101
  ) => http.patch<CodeSession>(`${base(pid)}/${sid}`, patch),
100
102
 
101
103
  remove: (pid: string | number, sid: string) =>