@agentprojectcontext/apx 1.31.2 → 1.32.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/package.json +6 -1
  2. package/skills/apc-context/SKILL.md +5 -2
  3. package/skills/apx/SKILL.md +3 -3
  4. package/skills/apx-agency-agents/SKILL.md +5 -5
  5. package/skills/apx-agent/SKILL.md +7 -7
  6. package/skills/apx-mcp/SKILL.md +6 -4
  7. package/skills/apx-mcp-builder/SKILL.md +4 -7
  8. package/skills/apx-project/SKILL.md +4 -5
  9. package/skills/apx-routine/SKILL.md +14 -12
  10. package/skills/apx-runtime/SKILL.md +5 -3
  11. package/skills/apx-sessions/SKILL.md +5 -5
  12. package/skills/apx-skill-builder/SKILL.md +10 -6
  13. package/skills/apx-task/SKILL.md +8 -8
  14. package/skills/apx-telegram/SKILL.md +23 -7
  15. package/skills/apx-voice/SKILL.md +8 -6
  16. package/src/core/{agent-system.js → agent/build-agent-system.js} +10 -12
  17. package/src/core/agent/constants.js +5 -0
  18. package/src/core/agent/index.js +0 -2
  19. package/src/core/{agent-memory.js → agent/memory.js} +2 -2
  20. package/src/core/agent/model-router.js +21 -43
  21. package/src/core/agent/prompt-builder.js +17 -63
  22. package/src/core/agent/prompts/action-discipline.md +17 -0
  23. package/src/core/agent/prompts/channels/code.md +8 -12
  24. package/src/core/agent/prompts/channels/desktop.md +6 -4
  25. package/src/core/agent/prompts/channels/routine.md +10 -1
  26. package/src/core/agent/prompts/channels/telegram.md +5 -0
  27. package/src/core/agent/prompts/channels/web_code.md +20 -0
  28. package/src/core/agent/prompts/modes/voice.md +2 -2
  29. package/src/core/agent/prompts/super-agent-base.md +2 -2
  30. package/src/core/agent/run-agent.js +66 -36
  31. package/src/core/agent/runtime-bridge.js +42 -0
  32. package/src/core/agent/self-memory.js +19 -9
  33. package/src/core/agent/skills/catalog.js +65 -0
  34. package/src/core/agent/skills/index.js +6 -0
  35. package/src/{host/daemon/skills-loader.js → core/agent/skills/loader.js} +3 -3
  36. package/src/core/agent/skills/rag.js +91 -0
  37. package/src/core/agent/skills/trigger.js +71 -0
  38. package/src/{host/daemon → core/agent}/super-agent.js +5 -5
  39. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/add-project.js +3 -4
  40. package/src/core/agent/tools/handlers/ask-questions.js +115 -0
  41. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-agent.js +2 -2
  42. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-mcp.js +1 -2
  43. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-runtime.js +10 -11
  44. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/create-task.js +1 -1
  45. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/discover-tools.js +1 -1
  46. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/edit-file.js +1 -2
  47. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/import-agent.js +4 -5
  48. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-agents.js +1 -1
  49. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-skills.js +7 -2
  50. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-tasks.js +1 -1
  51. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-vault-agents.js +1 -1
  52. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/load-skill.js +1 -1
  53. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-agent-memory.js +1 -1
  54. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-self-memory.js +1 -1
  55. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/remember.js +1 -1
  56. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/run-shell.js +1 -2
  57. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-messages.js +1 -1
  58. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-sessions.js +1 -1
  59. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/send-telegram.js +0 -2
  60. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-identity.js +1 -3
  61. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-permission-mode.js +1 -3
  62. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/tail-messages.js +1 -1
  63. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/transcribe-audio.js +1 -1
  64. package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/write-file.js +1 -2
  65. package/src/core/agent/tools/helpers.js +74 -0
  66. package/src/{host/daemon/super-agent-tools → core/agent/tools}/registry-bridge.js +3 -3
  67. package/src/{host/daemon/super-agent-tools/index.js → core/agent/tools/registry.js} +31 -32
  68. package/src/core/apc/agents-vault.js +37 -0
  69. package/src/core/{scaffold.js → apc/scaffold.js} +4 -5
  70. package/src/core/{config.js → config/index.js} +21 -27
  71. package/src/core/config/paths.js +32 -0
  72. package/src/core/constants/actors.js +8 -0
  73. package/src/core/constants/channels.js +19 -0
  74. package/src/core/constants/index.js +5 -0
  75. package/src/core/constants/permissions.js +17 -0
  76. package/src/core/constants/roles.js +9 -0
  77. package/src/core/engines/_streaming.js +63 -0
  78. package/src/core/engines/anthropic.js +11 -22
  79. package/src/core/engines/ollama.js +7 -16
  80. package/src/core/identity/index.js +8 -0
  81. package/src/core/{identity.js → identity/self.js} +5 -5
  82. package/src/core/{telegram-identity.js → identity/telegram.js} +1 -1
  83. package/src/core/logging.js +1 -1
  84. package/src/core/mascot.js +1 -1
  85. package/src/core/memory/active-threads.js +10 -10
  86. package/src/core/memory/broker.js +9 -9
  87. package/src/core/memory/compactor.js +2 -2
  88. package/src/core/memory/index.js +2 -2
  89. package/src/core/memory/indexer.js +1 -1
  90. package/src/core/{code-sessions-store.js → stores/code-sessions.js} +3 -7
  91. package/src/core/{messages-store.js → stores/messages.js} +6 -4
  92. package/src/core/stores/routine-memory.js +71 -0
  93. package/src/core/{routines-store.js → stores/routines.js} +1 -3
  94. package/src/core/stores/runtime-sessions.js +99 -0
  95. package/src/core/{tasks-store.js → stores/tasks.js} +3 -8
  96. package/src/core/update-check.js +1 -1
  97. package/src/core/util/ids.js +14 -0
  98. package/src/core/util/index.js +2 -0
  99. package/src/core/util/text-similarity.js +52 -0
  100. package/src/core/util/time.js +9 -0
  101. package/src/core/voice/tts.js +1 -1
  102. package/src/host/daemon/api/admin-config.js +4 -3
  103. package/src/host/daemon/api/admin.js +1 -1
  104. package/src/host/daemon/api/agents.js +4 -25
  105. package/src/host/daemon/api/artifacts.js +118 -1
  106. package/src/host/daemon/api/code.js +60 -16
  107. package/src/host/daemon/api/confirm.js +1 -1
  108. package/src/host/daemon/api/connections.js +2 -2
  109. package/src/host/daemon/api/conversations.js +2 -2
  110. package/src/host/daemon/api/deck.js +1 -1
  111. package/src/host/daemon/api/desktop.js +1 -1
  112. package/src/host/daemon/api/embeddings.js +4 -4
  113. package/src/host/daemon/api/engines.js +2 -2
  114. package/src/host/daemon/api/exec.js +3 -3
  115. package/src/host/daemon/api/identity.js +1 -1
  116. package/src/host/daemon/api/mcps.js +1 -1
  117. package/src/host/daemon/api/messages.js +1 -1
  118. package/src/host/daemon/api/runtimes.js +9 -8
  119. package/src/host/daemon/api/sessions-search.js +1 -1
  120. package/src/host/daemon/api/sessions.js +2 -2
  121. package/src/host/daemon/api/shared.js +5 -4
  122. package/src/host/daemon/api/skills.js +30 -0
  123. package/src/host/daemon/api/super-agent.js +29 -9
  124. package/src/host/daemon/api/tasks.js +2 -2
  125. package/src/host/daemon/api/telegram.js +1 -1
  126. package/src/host/daemon/api/tools.js +6 -6
  127. package/src/host/daemon/api/tts.js +2 -2
  128. package/src/host/daemon/api/voice.js +14 -12
  129. package/src/host/daemon/api.js +2 -0
  130. package/src/host/daemon/compact.js +1 -1
  131. package/src/host/daemon/db.js +4 -4
  132. package/src/host/daemon/desktop-ws.js +1 -1
  133. package/src/host/daemon/index.js +4 -4
  134. package/src/host/daemon/plugins/{desktop.js → desktop/index.js} +45 -6
  135. package/src/host/daemon/plugins/index.js +2 -2
  136. package/src/host/daemon/plugins/telegram/ask.js +309 -0
  137. package/src/host/daemon/plugins/{telegram.js → telegram/index.js} +390 -191
  138. package/src/host/daemon/plugins/telegram/media.js +162 -0
  139. package/src/host/daemon/projects-helpers.js +54 -0
  140. package/src/host/daemon/routines.js +28 -12
  141. package/src/host/daemon/smoke.js +2 -2
  142. package/src/host/daemon/token-store.js +1 -1
  143. package/src/host/daemon/transcription.js +2 -2
  144. package/src/host/daemon/wakeup.js +2 -2
  145. package/src/interfaces/cli/commands/agent.js +3 -3
  146. package/src/interfaces/cli/commands/artifact.js +99 -0
  147. package/src/interfaces/cli/commands/command.js +1 -1
  148. package/src/interfaces/cli/commands/config.js +3 -2
  149. package/src/interfaces/cli/commands/desktop.js +1 -1
  150. package/src/interfaces/cli/commands/exec.js +2 -1
  151. package/src/interfaces/cli/commands/identity.js +2 -2
  152. package/src/interfaces/cli/commands/init.js +1 -1
  153. package/src/interfaces/cli/commands/mcp.js +1 -1
  154. package/src/interfaces/cli/commands/memory.js +2 -2
  155. package/src/interfaces/cli/commands/model.js +16 -6
  156. package/src/interfaces/cli/commands/project.js +1 -1
  157. package/src/interfaces/cli/commands/routine.js +58 -0
  158. package/src/interfaces/cli/commands/search.js +1 -1
  159. package/src/interfaces/cli/commands/session.js +4 -4
  160. package/src/interfaces/cli/commands/setup.js +4 -3
  161. package/src/interfaces/cli/commands/skills.js +25 -4
  162. package/src/interfaces/cli/commands/status.js +1 -1
  163. package/src/interfaces/cli/commands/sys.js +11 -4
  164. package/src/interfaces/cli/commands/update.js +1 -1
  165. package/src/interfaces/cli/index.js +8 -4
  166. package/src/interfaces/cli/postinstall.js +2 -2
  167. package/src/interfaces/cli/terminal-chat/renderer.js +22 -2
  168. package/src/interfaces/mcp-server/index.js +1 -1
  169. package/src/interfaces/tui/component/prompt/index.tsx +3 -1
  170. package/src/interfaces/tui/context/sdk-apx.tsx +47 -7
  171. package/src/interfaces/tui/context/sync-apx.tsx +20 -2
  172. package/src/interfaces/tui/context/sync.tsx +2 -1
  173. package/src/interfaces/tui/routes/session/index.tsx +151 -136
  174. package/src/interfaces/tui/routes/session/sidebar-apx.tsx +37 -15
  175. package/src/interfaces/tui/run.ts +2 -0
  176. package/src/interfaces/web/dist/assets/index-34U_Mp1M.css +1 -0
  177. package/src/interfaces/web/dist/assets/index-BkybwwRn.js +570 -0
  178. package/src/interfaces/web/dist/assets/index-BkybwwRn.js.map +1 -0
  179. package/src/interfaces/web/dist/index.html +2 -2
  180. package/src/interfaces/web/package-lock.json +9 -9
  181. package/src/interfaces/web/src/App.tsx +51 -32
  182. package/src/interfaces/web/src/components/RobyBubble.tsx +12 -6
  183. package/src/interfaces/web/src/components/UiSelect.tsx +1 -1
  184. package/src/interfaces/web/src/components/chat/AskQuestionsCard.tsx +72 -0
  185. package/src/interfaces/web/src/components/chat/InlineAskPanel.tsx +399 -0
  186. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +16 -3
  187. package/src/interfaces/web/src/components/chat/MessageList.tsx +2 -1
  188. package/src/interfaces/web/src/components/chat/SkillPicker.tsx +77 -0
  189. package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +230 -0
  190. package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +1 -1
  191. package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +40 -17
  192. package/src/interfaces/web/src/components/common/TabLayout.tsx +9 -5
  193. package/src/interfaces/web/src/components/common/TabNav.tsx +3 -3
  194. package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +4 -2
  195. package/src/interfaces/web/src/hooks/useChat.ts +47 -2
  196. package/src/interfaces/web/src/hooks/useNavCollapseCtx.tsx +59 -0
  197. package/src/interfaces/web/src/hooks/usePersonaName.ts +11 -0
  198. package/src/interfaces/web/src/i18n/en.ts +27 -7
  199. package/src/interfaces/web/src/i18n/es.ts +27 -7
  200. package/src/interfaces/web/src/lib/api/artifacts.ts +47 -0
  201. package/src/interfaces/web/src/lib/api/skills.ts +25 -0
  202. package/src/interfaces/web/src/lib/api.ts +2 -0
  203. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +41 -20
  204. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +5 -18
  205. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -8
  206. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +39 -40
  207. package/src/interfaces/web/src/screens/project/ChatTab.tsx +27 -9
  208. package/src/skills/apc-context/SKILL.md +159 -0
  209. package/src/core/agent/ghost-guard.js +0 -24
  210. package/src/core/agent/prompts/channels/terminal.md +0 -16
  211. package/src/host/daemon/apc-runtime-context.js +0 -124
  212. package/src/host/daemon/super-agent-tools/helpers.js +0 -124
  213. package/src/host/daemon/super-agent-tools/tools/ask-questions.js +0 -32
  214. package/src/host/daemon/tool-call-parser.js +0 -2
  215. package/src/interfaces/web/dist/assets/index-BDUsA6L6.css +0 -1
  216. package/src/interfaces/web/dist/assets/index-BV615I9p.js +0 -548
  217. package/src/interfaces/web/dist/assets/index-BV615I9p.js.map +0 -1
  218. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-files.js +0 -0
  219. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-mcps.js +0 -0
  220. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-projects.js +0 -0
  221. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-file.js +0 -0
  222. /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-files.js +0 -0
  223. /package/src/core/agent/{pseudo-tools.js → tools/pseudo-tools.js} +0 -0
  224. /package/src/core/agent/{tool-call-parser.js → tools/tool-call-parser.js} +0 -0
  225. /package/src/core/{parser.js → apc/parser.js} +0 -0
  226. /package/src/core/{apc-skill-sync.js → apc/skill-sync.js} +0 -0
  227. /package/src/core/{artifacts-store.js → stores/artifacts.js} +0 -0
  228. /package/src/{host/daemon → core/stores}/engine-sessions.js +0 -0
  229. /package/src/core/{session-store.js → stores/sessions.js} +0 -0
@@ -0,0 +1,230 @@
1
+ import { useState } from "react";
2
+ import useSWR from "swr";
3
+ import { ChevronRight, Copy, RefreshCw, Trash2, FileCode2, Play } from "lucide-react";
4
+ import { cn } from "../../lib/cn";
5
+ import { t } from "../../i18n";
6
+ import { Empty, Spinner } from "../ui";
7
+ import { Artifacts, type ArtifactEntry, type ArtifactRunResult } from "../../lib/api/artifacts";
8
+ import { useToast } from "../Toast";
9
+
10
+ interface Props {
11
+ pid: string;
12
+ }
13
+
14
+ function ArtifactRow({
15
+ pid,
16
+ entry,
17
+ onDeleted,
18
+ }: {
19
+ pid: string;
20
+ entry: ArtifactEntry;
21
+ onDeleted: () => void;
22
+ }) {
23
+ const [open, setOpen] = useState(false);
24
+ const [running, setRunning] = useState(false);
25
+ const [runResult, setRunResult] = useState<ArtifactRunResult | null>(null);
26
+ const toast = useToast();
27
+ const detail = useSWR(open ? ["artifact", pid, entry.name] : null, () =>
28
+ Artifacts.read(pid, entry.name),
29
+ );
30
+
31
+ // Daemon-side detection: a file is runnable if it has the exec bit OR
32
+ // starts with a shebang. Locally we can only check the shebang from the
33
+ // fetched content; if it's missing we still show Run (the daemon will
34
+ // 400 cleanly and the toast surfaces the reason).
35
+ const looksRunnable = !detail.data?.content || detail.data.content.startsWith("#!");
36
+
37
+ const copy = async (text: string) => {
38
+ try {
39
+ await navigator.clipboard.writeText(text);
40
+ toast.info("Copiado.");
41
+ } catch {
42
+ /* ignore */
43
+ }
44
+ };
45
+
46
+ const run = async () => {
47
+ setRunning(true);
48
+ setRunResult(null);
49
+ try {
50
+ const r = await Artifacts.run(pid, entry.name);
51
+ setRunResult(r);
52
+ if (r.ok) toast.info(`exit 0 — ${r.durationMs}ms`);
53
+ else toast.error(`exit ${r.exitCode ?? r.signal ?? "?"}${r.timedOut ? " (timeout)" : ""}`);
54
+ } catch (e) {
55
+ toast.error((e as Error).message);
56
+ } finally {
57
+ setRunning(false);
58
+ }
59
+ };
60
+
61
+ const remove = async () => {
62
+ if (!window.confirm(t("code_module.artifacts_delete_confirm"))) return;
63
+ try {
64
+ await Artifacts.remove(pid, entry.name);
65
+ onDeleted();
66
+ } catch (e) {
67
+ toast.error((e as Error).message);
68
+ }
69
+ };
70
+
71
+ return (
72
+ <li className="rounded-md border border-border">
73
+ <button
74
+ type="button"
75
+ onClick={() => setOpen((v) => !v)}
76
+ className="flex w-full items-center gap-2 px-2 py-1.5 text-left text-xs hover:bg-accent/40"
77
+ >
78
+ <ChevronRight
79
+ className={cn(
80
+ "size-3 shrink-0 transition-transform",
81
+ open && "rotate-90",
82
+ )}
83
+ />
84
+ <FileCode2 className="size-3.5 shrink-0 text-emerald-600 dark:text-emerald-400" />
85
+ <span className="min-w-0 flex-1 truncate font-mono">{entry.name}</span>
86
+ <span className="shrink-0 font-mono text-[10px] text-muted-foreground">
87
+ {entry.size}b
88
+ </span>
89
+ </button>
90
+ {open && (
91
+ <div className="space-y-2 border-t border-border p-2">
92
+ <div className="flex flex-wrap items-center gap-1">
93
+ <code className="min-w-0 flex-1 truncate rounded bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground">
94
+ {entry.path}
95
+ </code>
96
+ {looksRunnable && (
97
+ <button
98
+ type="button"
99
+ onClick={() => void run()}
100
+ disabled={running}
101
+ title={t("code_module.artifacts_run")}
102
+ className={cn(
103
+ "inline-flex items-center gap-1 rounded px-1.5 py-1 text-[10px] font-medium",
104
+ running
105
+ ? "bg-muted text-muted-foreground"
106
+ : "bg-emerald-500/15 text-emerald-700 hover:bg-emerald-500/25 dark:text-emerald-300",
107
+ )}
108
+ >
109
+ {running ? <Spinner size={10} /> : <Play className="size-3" />}
110
+ {t("code_module.artifacts_run")}
111
+ </button>
112
+ )}
113
+ <button
114
+ type="button"
115
+ onClick={() => void copy(entry.path)}
116
+ title={t("code_module.artifacts_copy_path")}
117
+ className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
118
+ >
119
+ <Copy className="size-3" />
120
+ </button>
121
+ <button
122
+ type="button"
123
+ onClick={() => void remove()}
124
+ title={t("code_module.artifacts_delete")}
125
+ className="rounded p-1 text-rose-600 hover:bg-rose-50 dark:text-rose-400 dark:hover:bg-rose-950"
126
+ >
127
+ <Trash2 className="size-3" />
128
+ </button>
129
+ </div>
130
+ <div className="text-[10px] text-muted-foreground">
131
+ {t("code_module.artifacts_run_hint")}{" "}
132
+ <code className="rounded bg-muted px-1 font-mono">
133
+ apx artifact run {entry.name}
134
+ </code>
135
+ </div>
136
+ {runResult && (
137
+ <div className="space-y-1">
138
+ <div className="flex items-center gap-2 text-[10px]">
139
+ <span
140
+ className={cn(
141
+ "rounded px-1.5 py-0.5 font-mono",
142
+ runResult.ok
143
+ ? "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300"
144
+ : "bg-rose-500/15 text-rose-700 dark:text-rose-300",
145
+ )}
146
+ >
147
+ exit {runResult.exitCode ?? runResult.signal ?? "?"}
148
+ </span>
149
+ {runResult.timedOut && (
150
+ <span className="rounded bg-amber-500/15 px-1.5 py-0.5 font-mono text-amber-700 dark:text-amber-300">
151
+ timeout
152
+ </span>
153
+ )}
154
+ {runResult.truncated && (
155
+ <span className="rounded bg-amber-500/15 px-1.5 py-0.5 font-mono text-amber-700 dark:text-amber-300">
156
+ truncated
157
+ </span>
158
+ )}
159
+ <span className="font-mono text-muted-foreground">
160
+ {runResult.durationMs}ms
161
+ </span>
162
+ </div>
163
+ {runResult.stdout && (
164
+ <pre className="max-h-32 overflow-auto rounded bg-background/60 p-2 text-[10px] leading-tight">
165
+ {runResult.stdout}
166
+ </pre>
167
+ )}
168
+ {runResult.stderr && (
169
+ <pre className="max-h-32 overflow-auto rounded bg-rose-500/5 p-2 text-[10px] leading-tight text-rose-700 dark:text-rose-300">
170
+ {runResult.stderr}
171
+ </pre>
172
+ )}
173
+ </div>
174
+ )}
175
+ {detail.isLoading ? (
176
+ <div className="flex justify-center py-2">
177
+ <Spinner size={12} />
178
+ </div>
179
+ ) : detail.data?.content ? (
180
+ <pre className="max-h-64 overflow-auto rounded bg-muted/50 p-2 text-[10px] leading-tight">
181
+ {detail.data.content}
182
+ </pre>
183
+ ) : null}
184
+ </div>
185
+ )}
186
+ </li>
187
+ );
188
+ }
189
+
190
+ // Artifacts tab: managed files stored under <project>/artifacts/. The agent
191
+ // puts reusable scripts here so the user can run them from a terminal.
192
+ export function CodeArtifactsTab({ pid }: Props) {
193
+ const list = useSWR(pid ? ["artifacts", pid] : null, () => Artifacts.list(pid));
194
+ const entries = list.data || [];
195
+ return (
196
+ <div className="flex h-full flex-col" data-testid="code-artifacts-tab">
197
+ <div className="flex shrink-0 items-center justify-between px-3 py-2">
198
+ <span className="text-[11px] text-muted-foreground">
199
+ {entries.length > 0
200
+ ? t("code_module.artifacts_count", { n: entries.length })
201
+ : ""}
202
+ </span>
203
+ <button
204
+ type="button"
205
+ onClick={() => void list.mutate()}
206
+ title="↻"
207
+ className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
208
+ >
209
+ {list.isLoading ? <Spinner size={12} /> : <RefreshCw className="size-3" />}
210
+ </button>
211
+ </div>
212
+ <div className="min-h-0 flex-1 overflow-y-auto px-3 pb-3">
213
+ {entries.length === 0 ? (
214
+ <Empty>{t("code_module.artifacts_none")}</Empty>
215
+ ) : (
216
+ <ul className="space-y-1.5">
217
+ {entries.map((a) => (
218
+ <ArtifactRow
219
+ key={a.name}
220
+ pid={pid}
221
+ entry={a}
222
+ onDeleted={() => void list.mutate()}
223
+ />
224
+ ))}
225
+ </ul>
226
+ )}
227
+ </div>
228
+ </div>
229
+ );
230
+ }
@@ -26,7 +26,7 @@ export function CodeProjectPicker({ projects, value, onChange, disabled }: Props
26
26
  });
27
27
 
28
28
  return (
29
- <div className="w-60" data-testid="code-project-select">
29
+ <div className="w-full" data-testid="code-project-select">
30
30
  <UiSelect
31
31
  value={value}
32
32
  onChange={onChange}
@@ -1,36 +1,56 @@
1
- import { Gauge, GitCompare } from "lucide-react";
1
+ import { useState } from "react";
2
+ import { Gauge, GitCompare, Package } from "lucide-react";
2
3
  import { Tabs, TabsList, TabsTrigger, TabsContent } from "../ui/tabs";
3
4
  import { t } from "../../i18n";
4
5
  import { CodeContextTab } from "./CodeContextTab";
5
6
  import { CodeChangesTab } from "./CodeChangesTab";
7
+ import { CodeArtifactsTab } from "./CodeArtifactsTab";
6
8
  import type { CodeChanges, CodeTurn } from "../../lib/api/code";
7
9
 
8
10
  interface Props {
11
+ pid: string;
9
12
  turns: CodeTurn[];
10
13
  changes: CodeChanges | undefined;
11
14
  changesLoading: boolean;
12
15
  onRefreshChanges: () => void;
13
16
  }
14
17
 
15
- // Right-hand panel: Context (token metrics + breakdown) and Changes (diffs vs
16
- // the session's git baseline), mirroring OpenCode's session side panel.
17
- export function CodeSidePanel({ turns, changes, changesLoading, onRefreshChanges }: Props) {
18
+ const TABS = [
19
+ { value: "context", icon: Gauge, label: "tab_context" },
20
+ { value: "changes", icon: GitCompare, label: "tab_changes" },
21
+ { value: "artifacts", icon: Package, label: "tab_artifacts" },
22
+ ] as const;
23
+
24
+ export function CodeSidePanel({ pid, turns, changes, changesLoading, onRefreshChanges }: Props) {
25
+ const [active, setActive] = useState<string>("context");
18
26
  const changeCount = changes?.files.length || 0;
27
+
19
28
  return (
20
- <Tabs defaultValue="context" className="flex h-full flex-col gap-0" data-testid="code-side-panel">
21
- <div className="shrink-0 border-b border-border px-3 py-2">
29
+ <Tabs value={active} onValueChange={setActive} className="flex h-full flex-col gap-0" data-testid="code-side-panel">
30
+ <div className="shrink-0 border-b border-border px-2 py-2">
22
31
  <TabsList variant="line" className="w-full">
23
- <TabsTrigger value="context" className="flex-1">
24
- <Gauge className="size-3.5" /> {t("code_module.tab_context")}
25
- </TabsTrigger>
26
- <TabsTrigger value="changes" className="flex-1">
27
- <GitCompare className="size-3.5" /> {t("code_module.tab_changes")}
28
- {changeCount > 0 && (
29
- <span className="ml-1 rounded-full bg-muted px-1.5 text-[10px] text-muted-foreground">
30
- {changeCount}
31
- </span>
32
- )}
33
- </TabsTrigger>
32
+ {TABS.map(({ value, icon: Icon, label }) => {
33
+ const isActive = active === value;
34
+ const fullLabel = t(`code_module.${label}` as never);
35
+ return (
36
+ <TabsTrigger
37
+ key={value}
38
+ value={value}
39
+ title={fullLabel}
40
+ className={isActive ? "flex-1 min-w-0" : "w-8 shrink-0"}
41
+ >
42
+ <Icon className="size-3.5 shrink-0" />
43
+ {isActive && (
44
+ <span className="truncate text-xs">{fullLabel}</span>
45
+ )}
46
+ {value === "changes" && changeCount > 0 && (
47
+ <span className="ml-0.5 rounded-full bg-muted px-1 text-[10px] text-muted-foreground leading-none py-0.5">
48
+ {changeCount}
49
+ </span>
50
+ )}
51
+ </TabsTrigger>
52
+ );
53
+ })}
34
54
  </TabsList>
35
55
  </div>
36
56
  <TabsContent value="context" className="min-h-0 flex-1 overflow-y-auto">
@@ -39,6 +59,9 @@ export function CodeSidePanel({ turns, changes, changesLoading, onRefreshChanges
39
59
  <TabsContent value="changes" className="min-h-0 flex-1 overflow-hidden">
40
60
  <CodeChangesTab changes={changes} loading={changesLoading} onRefresh={onRefreshChanges} />
41
61
  </TabsContent>
62
+ <TabsContent value="artifacts" className="min-h-0 flex-1 overflow-hidden">
63
+ <CodeArtifactsTab pid={pid} />
64
+ </TabsContent>
42
65
  </Tabs>
43
66
  );
44
67
  }
@@ -3,8 +3,9 @@
3
3
  // collapse toggle (and optional page actions) followed by the routed content.
4
4
  // The page title/subtitle live in the top breadcrumb now, not here.
5
5
  import { type ReactNode } from "react";
6
- import { TabNav, NavToggle, type TabSection } from "./TabNav";
6
+ import { TabNav, type TabSection } from "./TabNav";
7
7
  import { cn } from "../../lib/cn";
8
+ import { useRegisterNavCollapse } from "../../hooks/useNavCollapseCtx";
8
9
 
9
10
  interface Props {
10
11
  sections: TabSection[];
@@ -29,14 +30,17 @@ export function TabLayout({
29
30
  testId,
30
31
  children,
31
32
  }: Props) {
33
+ useRegisterNavCollapse(collapsed, onToggleCollapse);
34
+
32
35
  return (
33
36
  <div className="flex h-full">
34
37
  <TabNav sections={sections} active={active} onChange={onChange} collapsed={collapsed} />
35
38
  <div className="flex min-w-0 flex-1 flex-col overflow-y-auto">
36
- <div className="flex items-center justify-between px-6 pt-3">
37
- <NavToggle collapsed={collapsed} onToggle={onToggleCollapse} />
38
- {actions ? <div className="flex gap-2">{actions}</div> : null}
39
- </div>
39
+ {actions ? (
40
+ <div className="flex items-center justify-end gap-2 px-6 pt-3">
41
+ {actions}
42
+ </div>
43
+ ) : null}
40
44
  <div className={cn(contentClassName)} data-testid={testId}>
41
45
  {children}
42
46
  </div>
@@ -1,7 +1,7 @@
1
1
  // Left-rail tab nav used inside Settings + per-project screens. Mirrors
2
2
  // the panda.project sectioned-nav pattern: optional section title above
3
3
  // each group, icon + label rows, active state in the section's tone.
4
- import { useState, useEffect, Fragment, type ElementType } from "react";
4
+ import { useState, useEffect, useCallback, Fragment, type ElementType } from "react";
5
5
  import { PanelLeft } from "lucide-react";
6
6
  import { cn } from "../../lib/cn";
7
7
  import { Tip } from "../ui/tip";
@@ -34,12 +34,12 @@ export function useNavCollapse(storageKey: string) {
34
34
  try { setCollapsed(localStorage.getItem(storageKey) === "true"); } catch { /* ignore */ }
35
35
  }, [storageKey]);
36
36
 
37
- const toggle = () =>
37
+ const toggle = useCallback(() =>
38
38
  setCollapsed((v) => {
39
39
  const next = !v;
40
40
  try { localStorage.setItem(storageKey, String(next)); } catch { /* quota */ }
41
41
  return next;
42
- });
42
+ }), [storageKey]);
43
43
 
44
44
  return { collapsed, toggle };
45
45
  }
@@ -9,6 +9,7 @@ import { ProjectAvatar } from "./ProjectAvatar";
9
9
  import { Tip } from "../ui/tip";
10
10
  import { useProjects } from "../../hooks/useProjects";
11
11
  import { t } from "../../i18n";
12
+ import { usePersonaName } from "../../hooks/usePersonaName";
12
13
 
13
14
  interface Props {
14
15
  onSelect: (href: string) => void;
@@ -37,6 +38,7 @@ export function ProjectSidebar({ onSelect, onOpenRoby }: Props) {
37
38
  const { projects, isLoading } = useProjects();
38
39
  const location = useLocation();
39
40
  const MODULES = buildModules();
41
+ const persona = usePersonaName();
40
42
 
41
43
  const isActive = (href: string) =>
42
44
  location.pathname === href || location.pathname.startsWith(`${href}/`);
@@ -122,12 +124,12 @@ export function ProjectSidebar({ onSelect, onOpenRoby }: Props) {
122
124
  />
123
125
  {/* Roby launcher — subtle (not a loud floating bubble), pinned under the
124
126
  gear so it doesn't overlap the chat composer. */}
125
- <Tip content={t("roby.talk")} side="right">
127
+ <Tip content={t("superagent.talk", { persona })} side="right">
126
128
  <button
127
129
  type="button"
128
130
  onClick={onOpenRoby}
129
131
  data-testid="nav-roby"
130
- aria-label={t("roby.talk")}
132
+ aria-label={t("superagent.talk", { persona })}
131
133
  className="mt-1 flex size-10 items-center justify-center rounded-xl border border-border/60 bg-muted/30 text-muted-fg transition-colors hover:bg-accent hover:text-foreground"
132
134
  >
133
135
  <Bot size={18} />
@@ -49,7 +49,7 @@ export interface UseChatResult {
49
49
  streaming: boolean;
50
50
  }
51
51
 
52
- /** Concatenate the text parts of a message (for clipboard / history). */
52
+ /** Concatenate the text parts of a message (for clipboard). */
53
53
  export function textOf(msg: ChatMsg): string {
54
54
  return msg.parts
55
55
  .filter((p): p is TextPart => p.kind === "text")
@@ -58,6 +58,51 @@ export function textOf(msg: ChatMsg): string {
58
58
  .trim();
59
59
  }
60
60
 
61
+ /** Compact line summarising an ask_questions tool call. Surfaced into the
62
+ * history string we send to the super-agent so the model can see it ALREADY
63
+ * asked and not re-ask the same questions on the next turn. Without this,
64
+ * ask_questions calls are invisible in history and the model loops. */
65
+ function summarizeAskQuestions(part: ToolPart): string | null {
66
+ const raw = (part.args as { questions?: unknown } | undefined)?.questions;
67
+ if (!Array.isArray(raw) || raw.length === 0) return null;
68
+ const lines = raw
69
+ .map((q) => {
70
+ if (typeof q === "string") return `- ${q}`;
71
+ if (!q || typeof q !== "object") return null;
72
+ const qq = q as { question?: unknown; options?: unknown };
73
+ if (typeof qq.question !== "string") return null;
74
+ const opts = Array.isArray(qq.options) ? qq.options : [];
75
+ const optStr = opts
76
+ .map((o) =>
77
+ typeof o === "string"
78
+ ? o
79
+ : o && typeof o === "object" && typeof (o as { label?: unknown }).label === "string"
80
+ ? ((o as { label: string }).label)
81
+ : "",
82
+ )
83
+ .filter((s) => s)
84
+ .join(", ");
85
+ return optStr ? `- ${qq.question} (opciones: ${optStr})` : `- ${qq.question}`;
86
+ })
87
+ .filter((s): s is string => !!s);
88
+ if (lines.length === 0) return null;
89
+ return `[ask_questions]\n${lines.join("\n")}`;
90
+ }
91
+
92
+ /** History view of a message — text parts plus ask_questions summaries.
93
+ * Used when sending `previousMessages` to the super-agent. */
94
+ export function historyTextOf(msg: ChatMsg): string {
95
+ const chunks: string[] = [];
96
+ for (const p of msg.parts) {
97
+ if (p.kind === "text" && p.text) chunks.push(p.text);
98
+ else if (p.kind === "tool" && p.tool === "ask_questions") {
99
+ const s = summarizeAskQuestions(p);
100
+ if (s) chunks.push(s);
101
+ }
102
+ }
103
+ return chunks.join("\n\n").trim();
104
+ }
105
+
61
106
  const userPart = (text: string): ChatPart[] => [{ kind: "text", text }];
62
107
 
63
108
  function isErrorResult(result: unknown): boolean {
@@ -202,7 +247,7 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
202
247
  const nowIso = () => new Date().toISOString();
203
248
  const history: ConversationMessage[] = msgs.map((m) => ({
204
249
  role: m.role,
205
- content: textOf(m),
250
+ content: historyTextOf(m),
206
251
  }));
207
252
 
208
253
  setMsgs((curr) => [
@@ -0,0 +1,59 @@
1
+ import { createContext, useContext, useState, useEffect, type ReactNode } from "react";
2
+
3
+ // ── Nav collapse ─────────────────────────────────────────────────────────────
4
+
5
+ type CollapseState = { collapsed: boolean; toggle: () => void } | null;
6
+
7
+ const CollapseReadCtx = createContext<CollapseState>(null);
8
+ const CollapseSetCtx = createContext<((s: CollapseState) => void) | null>(null);
9
+
10
+ // ── Page label (extra breadcrumb segment pushed by leaf screens) ──────────────
11
+
12
+ const LabelReadCtx = createContext<string>("");
13
+ const LabelSetCtx = createContext<((s: string) => void) | null>(null);
14
+
15
+ // ── Combined provider (one wrapper in Shell) ──────────────────────────────────
16
+
17
+ export function NavCollapseProvider({ children }: { children: ReactNode }) {
18
+ const [collapse, setCollapse] = useState<CollapseState>(null);
19
+ const [label, setLabel] = useState("");
20
+ return (
21
+ <CollapseSetCtx.Provider value={setCollapse}>
22
+ <CollapseReadCtx.Provider value={collapse}>
23
+ <LabelSetCtx.Provider value={setLabel}>
24
+ <LabelReadCtx.Provider value={label}>
25
+ {children}
26
+ </LabelReadCtx.Provider>
27
+ </LabelSetCtx.Provider>
28
+ </CollapseReadCtx.Provider>
29
+ </CollapseSetCtx.Provider>
30
+ );
31
+ }
32
+
33
+ // ── Nav collapse hooks ────────────────────────────────────────────────────────
34
+
35
+ export function useNavCollapseCtx() {
36
+ return useContext(CollapseReadCtx);
37
+ }
38
+
39
+ export function useRegisterNavCollapse(collapsed: boolean, toggle: () => void) {
40
+ const setState = useContext(CollapseSetCtx);
41
+ useEffect(() => {
42
+ setState?.({ collapsed, toggle });
43
+ return () => setState?.(null);
44
+ }, [collapsed, toggle, setState]);
45
+ }
46
+
47
+ // ── Page label hooks ──────────────────────────────────────────────────────────
48
+
49
+ export function usePageLabel() {
50
+ return useContext(LabelReadCtx);
51
+ }
52
+
53
+ export function useSetPageLabel(label: string) {
54
+ const set = useContext(LabelSetCtx);
55
+ useEffect(() => {
56
+ set?.(label);
57
+ return () => set?.("");
58
+ }, [label, set]);
59
+ }
@@ -0,0 +1,11 @@
1
+ // Resolve the super-agent display name (the persona shown to the user).
2
+ // Reads identity.json via useIdentity(); falls back to "APX" while loading
3
+ // or when identity is empty. UI strings should always interpolate this hook
4
+ // instead of hardcoding a persona name (it would otherwise drift from what
5
+ // the daemon-side resolveAgentName() reports across CLI / Telegram / etc.).
6
+ import { useIdentity } from "./useIdentity";
7
+
8
+ export function usePersonaName(): string {
9
+ const { identity } = useIdentity();
10
+ return (identity as { agent_name?: string })?.agent_name?.trim() || "APX";
11
+ }
@@ -286,8 +286,8 @@ export const en = {
286
286
  chat: {
287
287
  title: "Chat with agent",
288
288
  subtitle: "Direct conversations with project agents. The super-agent does not intervene.",
289
- roby_title: "Chat with Roby",
290
- roby_subtitle: "Chat with Roby — the APX super-agent. Can use tools (projects, tasks, mcps, agents).",
289
+ superagent_title: "Chat with {persona}",
290
+ superagent_subtitle: "Chat with {persona} — the APX super-agent. Can use tools (projects, tasks, mcps, agents).",
291
291
  empty: "Send a message to start the conversation.",
292
292
  placeholder: "Type something and press enter to send (shift+enter = new line)",
293
293
  send: "Send",
@@ -710,13 +710,13 @@ export const en = {
710
710
  delete_btn: "Delete",
711
711
  },
712
712
 
713
- roby: {
714
- title: "Roby",
713
+ superagent: {
714
+ title: "{persona}",
715
715
  badge: "super-agent · APX",
716
716
  desc: "Quick chat with your super-agent. Has access to tools (projects, tasks, mcps, agents); for a longer persistent thread, open Chats.",
717
- empty: "Send Roby a message to get started.",
718
- thinking: "Roby is thinking…",
719
- talk: "Talk to Roby",
717
+ empty: "Send {persona} a message to get started.",
718
+ thinking: "{persona} is thinking…",
719
+ talk: "Talk to {persona}",
720
720
  new_chat: "New chat",
721
721
  placeholder: "Type and press enter to send (shift+enter = new line)…",
722
722
  },
@@ -726,6 +726,18 @@ export const en = {
726
726
  message: "That route does not exist.",
727
727
  },
728
728
 
729
+ ask_panel: {
730
+ answers_header: "Answers",
731
+ other: "Other",
732
+ other_placeholder: "Write your own answer here",
733
+ text_placeholder: "Type your answer…",
734
+ back: "Back",
735
+ skip: "Skip",
736
+ next: "Next",
737
+ submit: "Send",
738
+ status_waiting: "Waiting for your answer…",
739
+ status_received: "Answers received",
740
+ },
729
741
  code_module: {
730
742
  title: "Code",
731
743
  badge: "super-agent",
@@ -747,6 +759,14 @@ export const en = {
747
759
  mode_plan_hint: "Plan — read-only, proposes changes without touching files",
748
760
  tab_context: "Context",
749
761
  tab_changes: "Changes",
762
+ tab_artifacts: "Artifacts",
763
+ artifacts_none: "No artifacts yet. Ask the agent to create a script under `artifacts/<name>`.",
764
+ artifacts_count: "{n} artifact(s)",
765
+ artifacts_copy_path: "Copy path",
766
+ artifacts_run: "Run",
767
+ artifacts_run_hint: "Run it from your terminal:",
768
+ artifacts_delete: "Delete",
769
+ artifacts_delete_confirm: "Delete this artifact? The file will be removed from disk.",
750
770
  ctx_model: "Model",
751
771
  ctx_tokens: "Tokens",
752
772
  ctx_input: "Input",