thepopebot 1.2.76-beta.2 → 1.2.76-beta.21

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 (128) hide show
  1. package/README.md +3 -3
  2. package/api/CLAUDE.md +11 -4
  3. package/api/index.js +56 -18
  4. package/bin/CLAUDE.md +7 -4
  5. package/bin/cli.js +25 -45
  6. package/config/CLAUDE.md +23 -4
  7. package/drizzle/0021_coding_agent_workspace.sql +1 -0
  8. package/drizzle/0022_organic_apocalypse.sql +16 -0
  9. package/drizzle/0023_needy_ender_wiggin.sql +1 -0
  10. package/drizzle/meta/0021_snapshot.json +639 -0
  11. package/drizzle/meta/0022_snapshot.json +743 -0
  12. package/drizzle/meta/0023_snapshot.json +750 -0
  13. package/drizzle/meta/_journal.json +21 -0
  14. package/lib/CLAUDE.md +2 -2
  15. package/lib/actions.js +9 -1
  16. package/lib/ai/CLAUDE.md +72 -57
  17. package/lib/ai/helper-llm.js +108 -0
  18. package/lib/ai/index.js +308 -438
  19. package/lib/ai/line-mappers.js +42 -24
  20. package/lib/ai/scope.js +26 -0
  21. package/lib/ai/sdk-adapters/CLAUDE.md +114 -0
  22. package/lib/ai/sdk-adapters/claude-code.js +120 -8
  23. package/lib/ai/system-prompt.js +34 -0
  24. package/lib/ai/workspace-setup.js +19 -35
  25. package/lib/channels/CLAUDE.md +14 -4
  26. package/lib/channels/base.js +6 -2
  27. package/lib/channels/commands/index.js +42 -0
  28. package/lib/channels/commands/session.js +53 -0
  29. package/lib/channels/commands/verify.js +18 -0
  30. package/lib/channels/telegram.js +79 -28
  31. package/lib/chat/CLAUDE.md +4 -4
  32. package/lib/chat/actions.js +270 -49
  33. package/lib/chat/api.js +185 -31
  34. package/lib/chat/components/CLAUDE.md +6 -2
  35. package/lib/chat/components/chat-input.js +77 -47
  36. package/lib/chat/components/chat-input.jsx +77 -40
  37. package/lib/chat/components/chat-page.js +2 -0
  38. package/lib/chat/components/chat-page.jsx +3 -0
  39. package/lib/chat/components/chat.js +62 -14
  40. package/lib/chat/components/chat.jsx +68 -10
  41. package/lib/chat/components/code-mode-toggle.js +141 -22
  42. package/lib/chat/components/code-mode-toggle.jsx +129 -20
  43. package/lib/chat/components/containers-page.js +58 -40
  44. package/lib/chat/components/containers-page.jsx +64 -25
  45. package/lib/chat/components/crons-page.js +17 -3
  46. package/lib/chat/components/crons-page.jsx +34 -6
  47. package/lib/chat/components/index.js +2 -2
  48. package/lib/chat/components/message.js +18 -3
  49. package/lib/chat/components/message.jsx +18 -3
  50. package/lib/chat/components/profile-page.js +182 -4
  51. package/lib/chat/components/profile-page.jsx +196 -1
  52. package/lib/chat/components/scope-picker.js +21 -0
  53. package/lib/chat/components/scope-picker.jsx +27 -0
  54. package/lib/chat/components/settings-chat-page.js +11 -11
  55. package/lib/chat/components/settings-chat-page.jsx +14 -18
  56. package/lib/chat/components/settings-coding-agents-page.js +110 -16
  57. package/lib/chat/components/settings-coding-agents-page.jsx +87 -3
  58. package/lib/chat/components/settings-github-page.js +5 -0
  59. package/lib/chat/components/settings-github-page.jsx +5 -0
  60. package/lib/chat/components/settings-layout.js +3 -3
  61. package/lib/chat/components/settings-layout.jsx +3 -3
  62. package/lib/chat/components/settings-secrets-layout.js +1 -2
  63. package/lib/chat/components/settings-secrets-layout.jsx +1 -2
  64. package/lib/chat/components/settings-secrets-page.js +180 -75
  65. package/lib/chat/components/settings-secrets-page.jsx +212 -66
  66. package/lib/chat/components/triggers-page.js +17 -3
  67. package/lib/chat/components/triggers-page.jsx +34 -6
  68. package/lib/chat/components/ui/combobox.js +18 -2
  69. package/lib/chat/components/ui/combobox.jsx +17 -1
  70. package/lib/chat/components/ui/dropdown-menu.js +23 -2
  71. package/lib/chat/components/ui/dropdown-menu.jsx +27 -2
  72. package/lib/chat/telegram-profile.js +33 -0
  73. package/lib/cluster/CLAUDE.md +9 -3
  74. package/lib/code/CLAUDE.md +11 -3
  75. package/lib/code/actions.js +47 -8
  76. package/lib/code/terminal-view.js +31 -21
  77. package/lib/code/terminal-view.jsx +32 -23
  78. package/lib/config.js +15 -4
  79. package/lib/containers/CLAUDE.md +16 -6
  80. package/lib/db/CLAUDE.md +5 -2
  81. package/lib/db/chats.js +9 -17
  82. package/lib/db/code-workspaces.js +8 -3
  83. package/lib/db/config.js +0 -1
  84. package/lib/db/index.js +12 -0
  85. package/lib/db/schema.js +24 -1
  86. package/lib/db/user-channels.js +129 -0
  87. package/lib/llm-providers.js +8 -0
  88. package/lib/maintenance.js +31 -21
  89. package/lib/tools/CLAUDE.md +12 -3
  90. package/lib/tools/assemblyai.js +17 -0
  91. package/lib/tools/create-agent-job.js +12 -8
  92. package/lib/tools/docker.js +34 -10
  93. package/lib/tools/github.js +34 -0
  94. package/lib/tools/telegram.js +106 -0
  95. package/lib/utils/render-md.js +44 -18
  96. package/package.json +8 -8
  97. package/setup/CLAUDE.md +11 -5
  98. package/setup/lib/providers.mjs +2 -1
  99. package/setup/lib/targets.mjs +13 -16
  100. package/setup/lib/telegram.mjs +8 -69
  101. package/templates/.env.example +0 -7
  102. package/templates/.github/workflows/rebuild-event-handler.yml +1 -1
  103. package/templates/.gitignore.template +1 -3
  104. package/templates/CLAUDE.md +1 -1
  105. package/templates/CLAUDE.md.template +29 -7
  106. package/templates/agent-job/CLAUDE.md.template +5 -3
  107. package/templates/agent-job/CRONS.json +16 -0
  108. package/templates/agent-job/SYSTEM.md +16 -11
  109. package/templates/agents/CLAUDE.md.template +17 -17
  110. package/templates/coding-workspace/CLAUDE.md.template +7 -0
  111. package/templates/data/CLAUDE.md.template +1 -1
  112. package/templates/docker-compose.custom.yml +1 -0
  113. package/templates/docker-compose.yml +1 -0
  114. package/templates/event-handler/CLAUDE.md.template +79 -0
  115. package/templates/event-handler/TRIGGERS.json +18 -2
  116. package/templates/skills/CLAUDE.md.template +20 -22
  117. package/templates/skills/{library/agent-job-secrets → agent-job-secrets}/SKILL.md +2 -2
  118. package/lib/ai/agent.js +0 -65
  119. package/lib/ai/async-channel.js +0 -51
  120. package/lib/ai/model.js +0 -130
  121. package/lib/ai/tools.js +0 -164
  122. package/lib/tools/openai.js +0 -37
  123. package/setup/lib/telegram-verify.mjs +0 -63
  124. package/setup/setup-telegram.mjs +0 -260
  125. package/templates/agent-job/SOUL.md +0 -17
  126. /package/templates/{skills/active/.gitkeep → coding-workspace/SYSTEM.md} +0 -0
  127. /package/templates/skills/{library/agent-job-secrets → agent-job-secrets}/agent-job-secrets.js +0 -0
  128. /package/templates/skills/{library/playwright-cli → playwright-cli}/SKILL.md +0 -0
@@ -58,6 +58,8 @@ function ChatPage({ session, needsSetup, chatId }) {
58
58
  const parsed = JSON.parse(msg.content);
59
59
  if (parsed?.type === "tool-invocation") {
60
60
  parts = [parsed];
61
+ } else if (parsed?.type === "error") {
62
+ parts = [{ type: "data-error", data: { message: parsed.message } }];
61
63
  } else {
62
64
  parts = [{ type: "text", text: msg.content }];
63
65
  }
@@ -75,6 +75,9 @@ export function ChatPage({ session, needsSetup, chatId }) {
75
75
  const parsed = JSON.parse(msg.content);
76
76
  if (parsed?.type === 'tool-invocation') {
77
77
  parts = [parsed];
78
+ } else if (parsed?.type === 'error') {
79
+ // Rehydrate error messages as data-error parts (matches live stream shape)
80
+ parts = [{ type: 'data-error', data: { message: parsed.message } }];
78
81
  } else {
79
82
  parts = [{ type: 'text', text: msg.content }];
80
83
  }
@@ -8,10 +8,22 @@ import { ChatInput } from "./chat-input.js";
8
8
  import { ChatHeader } from "./chat-header.js";
9
9
  import { Greeting } from "./greeting.js";
10
10
  import { RepoBranchPicker, WorkspaceBar } from "./code-mode-toggle.js";
11
+ import { ScopePicker } from "./scope-picker.js";
11
12
  import { DiffViewer } from "./diff-viewer.js";
12
13
  import { cn } from "../utils.js";
13
14
  const fetchRepositories = () => fetch("/code/repositories").then((r) => r.json()).catch(() => []);
14
15
  const fetchBranches = (repoFullName) => fetch(`/code/branches?repo=${encodeURIComponent(repoFullName)}`).then((r) => r.json()).catch(() => []);
16
+ const fetchDefaultBranch = (repoFullName) => fetch(`/code/default-branch?repo=${encodeURIComponent(repoFullName)}`).then((r) => r.json()).then(({ branch }) => branch || null).catch(() => null);
17
+ const fetchCreateRepository = (name) => fetch("/code/repositories/create", {
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
+ body: JSON.stringify({ name })
21
+ }).then((r) => {
22
+ if (!r.ok) return r.json().then((d) => {
23
+ throw new Error(d.error || "Failed to create repository");
24
+ });
25
+ return r.json();
26
+ });
15
27
  function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null }) {
16
28
  const [input, setInput] = useState("");
17
29
  const [files, setFiles] = useState([]);
@@ -20,7 +32,7 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
20
32
  const [codeModeType, setCodeModeType] = useState(() => {
21
33
  if (typeof window !== "undefined") {
22
34
  const stored = localStorage.getItem(`codeModeType:${chatId}`);
23
- if (stored === "plan" || stored === "code" || stored === "job") return stored;
35
+ if (stored === "plan" || stored === "code") return stored;
24
36
  }
25
37
  return "code";
26
38
  });
@@ -35,13 +47,30 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
35
47
  const [workspaceState, setWorkspaceState] = useState(workspace);
36
48
  const [diffStats, setDiffStats] = useState(null);
37
49
  const [showDiff, setShowDiff] = useState(false);
50
+ const [availableAgents, setAvailableAgents] = useState(null);
51
+ const [scope, setScope] = useState(workspace?.scope || null);
52
+ const [availableScopes, setAvailableScopes] = useState(null);
53
+ useEffect(() => {
54
+ import("../actions.js").then(({ getAvailableCodingAgents }) => {
55
+ getAvailableCodingAgents().then((agents) => setAvailableAgents(agents)).catch(() => {
56
+ });
57
+ }).catch(() => {
58
+ });
59
+ }, []);
60
+ useEffect(() => {
61
+ if (!codeMode) {
62
+ fetch("/chat/scopes").then((r) => r.json()).then((scopes) => setAvailableScopes(scopes)).catch(() => setAvailableScopes([]));
63
+ }
64
+ }, [codeMode]);
38
65
  useEffect(() => {
39
66
  fetch("/code/default-repo").then((res) => res.json()).then(({ repo: r }) => {
40
67
  if (r) {
41
68
  setDefaultRepo(r);
42
69
  if (!workspace && !repo && !codeMode) {
43
70
  setRepo(r);
44
- setBranch("main");
71
+ fetchDefaultBranch(r).then((b) => {
72
+ if (b) setBranch(b);
73
+ });
45
74
  }
46
75
  }
47
76
  }).catch(() => {
@@ -57,8 +86,8 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
57
86
  window.location.href = `/code/${workspaceState.id}`;
58
87
  }
59
88
  }, [workspaceState?.containerName]);
60
- const codeModeRef = useRef({ codeMode, codeModeType, repo, branch, workspaceId: workspaceState?.id });
61
- codeModeRef.current = { codeMode, codeModeType, repo, branch, workspaceId: workspaceState?.id };
89
+ const codeModeRef = useRef({ codeMode, codeModeType, repo, branch, workspaceId: workspaceState?.id, scope });
90
+ codeModeRef.current = { codeMode, codeModeType, repo, branch, workspaceId: workspaceState?.id, scope };
62
91
  const transport = useMemo(
63
92
  () => new DefaultChatTransport({
64
93
  api: "/stream/chat",
@@ -68,7 +97,8 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
68
97
  codeModeType: codeModeRef.current.codeModeType,
69
98
  repo: codeModeRef.current.repo,
70
99
  branch: codeModeRef.current.branch,
71
- workspaceId: codeModeRef.current.workspaceId
100
+ workspaceId: codeModeRef.current.workspaceId,
101
+ scope: codeModeRef.current.scope || void 0
72
102
  })
73
103
  }),
74
104
  [chatId]
@@ -164,12 +194,13 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
164
194
  }, [messages, setMessages, sendMessage]);
165
195
  const isInteractiveActive = !!workspaceState?.containerName;
166
196
  const [togglingMode, setTogglingMode] = useState(false);
167
- const handleInteractiveToggle = useCallback(async () => {
197
+ const handleInteractiveToggle = useCallback(async (agentOverride) => {
168
198
  if (!workspaceState?.id || togglingMode || isInteractiveActive) return;
169
199
  setTogglingMode(true);
170
200
  try {
171
201
  const { startInteractiveMode } = await import("../../code/actions.js");
172
- const result = await startInteractiveMode(workspaceState.id);
202
+ const agent = typeof agentOverride === "string" ? agentOverride : void 0;
203
+ const result = await startInteractiveMode(workspaceState.id, agent);
173
204
  if (result.containerName) {
174
205
  setWorkspaceState((prev) => ({ ...prev, containerName: result.containerName }));
175
206
  }
@@ -184,8 +215,11 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
184
215
  onModeChange: setCodeModeType,
185
216
  isInteractiveActive,
186
217
  onInteractiveToggle: handleInteractiveToggle,
187
- togglingMode
218
+ togglingMode,
219
+ availableAgents,
220
+ hasMessages: messages.length > 0
188
221
  };
222
+ const defaultPlaceholder = !codeMode && scope ? `Send message to /${scope}` : "Send a message...";
189
223
  const handleBranchChange = useCallback((newBranch) => {
190
224
  setBranch(newBranch);
191
225
  if (workspaceState?.id) {
@@ -213,7 +247,7 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
213
247
  }
214
248
  return null;
215
249
  }, [workspaceState?.id]);
216
- return /* @__PURE__ */ jsxs("div", { className: "flex h-svh flex-col", children: [
250
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-svh flex-col overflow-hidden", children: [
217
251
  /* @__PURE__ */ jsx(ChatHeader, { chatId }),
218
252
  messages.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col items-center justify-center px-2.5 md:px-6", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-4xl", children: [
219
253
  /* @__PURE__ */ jsx(Greeting, { codeMode }),
@@ -228,6 +262,7 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
228
262
  stop,
229
263
  files,
230
264
  setFiles,
265
+ placeholder: defaultPlaceholder,
231
266
  codeMode,
232
267
  codeModeSettings
233
268
  }
@@ -241,7 +276,16 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
241
276
  branch,
242
277
  onBranchChange: handleBranchChange,
243
278
  getRepositories: fetchRepositories,
244
- getBranches: fetchBranches
279
+ getBranches: fetchBranches,
280
+ createRepository: fetchCreateRepository
281
+ }
282
+ ),
283
+ !codeMode && /* @__PURE__ */ jsx(
284
+ ScopePicker,
285
+ {
286
+ scope,
287
+ onScopeChange: setScope,
288
+ scopes: availableScopes
245
289
  }
246
290
  ),
247
291
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center justify-center gap-3 mt-4", children: /* @__PURE__ */ jsxs(
@@ -256,7 +300,10 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
256
300
  setBranch("");
257
301
  } else if (defaultRepo) {
258
302
  setRepo(defaultRepo);
259
- setBranch("main");
303
+ setBranch("");
304
+ fetchDefaultBranch(defaultRepo).then((b) => {
305
+ if (b) setBranch(b);
306
+ });
260
307
  } else {
261
308
  setRepo("");
262
309
  setBranch("");
@@ -319,7 +366,8 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
319
366
  workspace: workspaceState,
320
367
  diffStats,
321
368
  onDiffStatsRefresh: handleDiffStatsRefresh,
322
- onShowDiff: () => setShowDiff(true)
369
+ onShowDiff: () => setShowDiff(true),
370
+ chatMode: codeMode ? "code" : "agent"
323
371
  }
324
372
  ) }),
325
373
  /* @__PURE__ */ jsx(
@@ -334,7 +382,7 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
334
382
  files,
335
383
  setFiles,
336
384
  disabled: isInteractiveActive,
337
- placeholder: isInteractiveActive ? "Interactive mode is active." : "Send a message...",
385
+ placeholder: isInteractiveActive ? "Interactive mode is active." : defaultPlaceholder,
338
386
  className: workspaceState ? "rounded-t-none" : void 0,
339
387
  codeMode,
340
388
  codeModeSettings
@@ -381,7 +429,7 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
381
429
  files,
382
430
  setFiles,
383
431
  disabled: isInteractiveActive,
384
- placeholder: isInteractiveActive ? "Interactive mode is active." : "Send a message...",
432
+ placeholder: isInteractiveActive ? "Interactive mode is active." : defaultPlaceholder,
385
433
  className: workspaceState ? "rounded-t-none" : void 0,
386
434
  codeMode,
387
435
  codeModeSettings
@@ -8,6 +8,7 @@ import { ChatInput } from './chat-input.js';
8
8
  import { ChatHeader } from './chat-header.js';
9
9
  import { Greeting } from './greeting.js';
10
10
  import { RepoBranchPicker, WorkspaceBar } from './code-mode-toggle.js';
11
+ import { ScopePicker } from './scope-picker.js';
11
12
  import { DiffViewer } from './diff-viewer.js';
12
13
  import { cn } from '../utils.js';
13
14
 
@@ -21,6 +22,22 @@ const fetchBranches = (repoFullName) =>
21
22
  .then(r => r.json())
22
23
  .catch(() => []);
23
24
 
25
+ const fetchDefaultBranch = (repoFullName) =>
26
+ fetch(`/code/default-branch?repo=${encodeURIComponent(repoFullName)}`)
27
+ .then(r => r.json())
28
+ .then(({ branch }) => branch || null)
29
+ .catch(() => null);
30
+
31
+ const fetchCreateRepository = (name) =>
32
+ fetch('/code/repositories/create', {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({ name }),
36
+ }).then(r => {
37
+ if (!r.ok) return r.json().then(d => { throw new Error(d.error || 'Failed to create repository'); });
38
+ return r.json();
39
+ });
40
+
24
41
  export function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null }) {
25
42
  const [input, setInput] = useState('');
26
43
  const [files, setFiles] = useState([]);
@@ -29,7 +46,7 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
29
46
  const [codeModeType, setCodeModeType] = useState(() => {
30
47
  if (typeof window !== 'undefined') {
31
48
  const stored = localStorage.getItem(`codeModeType:${chatId}`);
32
- if (stored === 'plan' || stored === 'code' || stored === 'job') return stored;
49
+ if (stored === 'plan' || stored === 'code') return stored;
33
50
  }
34
51
  return 'code';
35
52
  });
@@ -46,6 +63,26 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
46
63
  const [workspaceState, setWorkspaceState] = useState(workspace);
47
64
  const [diffStats, setDiffStats] = useState(null);
48
65
  const [showDiff, setShowDiff] = useState(false);
66
+ const [availableAgents, setAvailableAgents] = useState(null);
67
+ const [scope, setScope] = useState(workspace?.scope || null);
68
+ const [availableScopes, setAvailableScopes] = useState(null);
69
+
70
+ // Load available coding agents once on mount (for the right-click agent picker)
71
+ useEffect(() => {
72
+ import('../actions.js').then(({ getAvailableCodingAgents }) => {
73
+ getAvailableCodingAgents().then(agents => setAvailableAgents(agents)).catch(() => {});
74
+ }).catch(() => {});
75
+ }, []);
76
+
77
+ // Load available agent scopes for agent mode
78
+ useEffect(() => {
79
+ if (!codeMode) {
80
+ fetch('/chat/scopes')
81
+ .then(r => r.json())
82
+ .then(scopes => setAvailableScopes(scopes))
83
+ .catch(() => setAvailableScopes([]));
84
+ }
85
+ }, [codeMode]);
49
86
 
50
87
  // Fetch default repo for agent mode on mount
51
88
  // Uses fetch instead of server action to avoid Next.js page revalidation
@@ -57,7 +94,7 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
57
94
  setDefaultRepo(r);
58
95
  if (!workspace && !repo && !codeMode) {
59
96
  setRepo(r);
60
- setBranch('main');
97
+ fetchDefaultBranch(r).then((b) => { if (b) setBranch(b); });
61
98
  }
62
99
  }
63
100
  }).catch(() => {});
@@ -75,8 +112,8 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
75
112
  }
76
113
  }, [workspaceState?.containerName]);
77
114
 
78
- const codeModeRef = useRef({ codeMode, codeModeType, repo, branch, workspaceId: workspaceState?.id });
79
- codeModeRef.current = { codeMode, codeModeType, repo, branch, workspaceId: workspaceState?.id };
115
+ const codeModeRef = useRef({ codeMode, codeModeType, repo, branch, workspaceId: workspaceState?.id, scope });
116
+ codeModeRef.current = { codeMode, codeModeType, repo, branch, workspaceId: workspaceState?.id, scope };
80
117
 
81
118
  const transport = useMemo(
82
119
  () =>
@@ -89,6 +126,7 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
89
126
  repo: codeModeRef.current.repo,
90
127
  branch: codeModeRef.current.branch,
91
128
  workspaceId: codeModeRef.current.workspaceId,
129
+ scope: codeModeRef.current.scope || undefined,
92
130
  }),
93
131
  }),
94
132
  [chatId]
@@ -211,12 +249,15 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
211
249
  const isInteractiveActive = !!workspaceState?.containerName;
212
250
  const [togglingMode, setTogglingMode] = useState(false);
213
251
 
214
- const handleInteractiveToggle = useCallback(async () => {
252
+ const handleInteractiveToggle = useCallback(async (agentOverride) => {
215
253
  if (!workspaceState?.id || togglingMode || isInteractiveActive) return;
216
254
  setTogglingMode(true);
217
255
  try {
218
256
  const { startInteractiveMode } = await import('../../code/actions.js');
219
- const result = await startInteractiveMode(workspaceState.id);
257
+ // agentOverride is a string agent id when coming from the right-click picker,
258
+ // or undefined when coming from a plain left-click (uses global config default)
259
+ const agent = typeof agentOverride === 'string' ? agentOverride : undefined;
260
+ const result = await startInteractiveMode(workspaceState.id, agent);
220
261
  if (result.containerName) {
221
262
  setWorkspaceState(prev => ({ ...prev, containerName: result.containerName }));
222
263
  }
@@ -233,8 +274,14 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
233
274
  isInteractiveActive,
234
275
  onInteractiveToggle: handleInteractiveToggle,
235
276
  togglingMode,
277
+ availableAgents,
278
+ hasMessages: messages.length > 0,
236
279
  };
237
280
 
281
+ const defaultPlaceholder = !codeMode && scope
282
+ ? `Send message to /${scope}`
283
+ : 'Send a message...';
284
+
238
285
  const handleBranchChange = useCallback((newBranch) => {
239
286
  setBranch(newBranch);
240
287
  if (workspaceState?.id) {
@@ -263,7 +310,7 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
263
310
  }, [workspaceState?.id]);
264
311
 
265
312
  return (
266
- <div className="flex h-svh flex-col">
313
+ <div className="flex h-svh flex-col overflow-hidden">
267
314
  <ChatHeader chatId={chatId} />
268
315
  {messages.length === 0 ? (
269
316
  <div className="flex flex-1 flex-col items-center justify-center px-2.5 md:px-6">
@@ -283,6 +330,7 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
283
330
  stop={stop}
284
331
  files={files}
285
332
  setFiles={setFiles}
333
+ placeholder={defaultPlaceholder}
286
334
  codeMode={codeMode}
287
335
  codeModeSettings={codeModeSettings}
288
336
  />
@@ -296,6 +344,14 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
296
344
  onBranchChange={handleBranchChange}
297
345
  getRepositories={fetchRepositories}
298
346
  getBranches={fetchBranches}
347
+ createRepository={fetchCreateRepository}
348
+ />
349
+ )}
350
+ {!codeMode && (
351
+ <ScopePicker
352
+ scope={scope}
353
+ onScopeChange={setScope}
354
+ scopes={availableScopes}
299
355
  />
300
356
  )}
301
357
  <div className="flex flex-wrap items-center justify-center gap-3 mt-4">
@@ -310,7 +366,8 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
310
366
  setBranch('');
311
367
  } else if (defaultRepo) {
312
368
  setRepo(defaultRepo);
313
- setBranch('main');
369
+ setBranch('');
370
+ fetchDefaultBranch(defaultRepo).then((b) => { if (b) setBranch(b); });
314
371
  } else {
315
372
  setRepo('');
316
373
  setBranch('');
@@ -378,6 +435,7 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
378
435
  diffStats={diffStats}
379
436
  onDiffStatsRefresh={handleDiffStatsRefresh}
380
437
  onShowDiff={() => setShowDiff(true)}
438
+ chatMode={codeMode ? 'code' : 'agent'}
381
439
  />
382
440
  </div>
383
441
  )}
@@ -391,7 +449,7 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
391
449
  files={files}
392
450
  setFiles={setFiles}
393
451
  disabled={isInteractiveActive}
394
- placeholder={isInteractiveActive ? 'Interactive mode is active.' : 'Send a message...'}
452
+ placeholder={isInteractiveActive ? 'Interactive mode is active.' : defaultPlaceholder}
395
453
  className={workspaceState ? "rounded-t-none" : undefined}
396
454
  codeMode={codeMode}
397
455
  codeModeSettings={codeModeSettings}
@@ -443,7 +501,7 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
443
501
  files={files}
444
502
  setFiles={setFiles}
445
503
  disabled={isInteractiveActive}
446
- placeholder={isInteractiveActive ? 'Interactive mode is active.' : 'Send a message...'}
504
+ placeholder={isInteractiveActive ? 'Interactive mode is active.' : defaultPlaceholder}
447
505
  className={workspaceState ? "rounded-t-none" : undefined}
448
506
  codeMode={codeMode}
449
507
  codeModeSettings={codeModeSettings}
@@ -2,7 +2,7 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useCallback, useRef } from "react";
4
4
  import { createPortal } from "react-dom";
5
- import { GitBranchIcon, ChevronDownIcon, SpinnerIcon, XIcon } from "./icons.js";
5
+ import { GitBranchIcon, ChevronDownIcon, SpinnerIcon, XIcon, PlusIcon } from "./icons.js";
6
6
  import { Combobox } from "./ui/combobox.js";
7
7
  import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "./ui/dropdown-menu.js";
8
8
  import { cn } from "../utils.js";
@@ -16,13 +16,15 @@ function RepoBranchPicker({
16
16
  branch,
17
17
  onBranchChange,
18
18
  getRepositories,
19
- getBranches
19
+ getBranches,
20
+ createRepository
20
21
  }) {
21
22
  const [repos, setRepos] = useState([]);
22
23
  const [branches, setBranches] = useState([]);
23
24
  const [loadingRepos, setLoadingRepos] = useState(false);
24
25
  const [loadingBranches, setLoadingBranches] = useState(false);
25
26
  const [reposLoaded, setReposLoaded] = useState(false);
27
+ const [showCreateDialog, setShowCreateDialog] = useState(false);
26
28
  useEffect(() => {
27
29
  setLoadingRepos(true);
28
30
  getRepositories().then((data) => {
@@ -49,6 +51,10 @@ function RepoBranchPicker({
49
51
  setLoadingBranches(false);
50
52
  }).catch(() => setLoadingBranches(false));
51
53
  }, [repo]);
54
+ const handleRepoCreated = useCallback((fullName) => {
55
+ setRepos((prev) => [...prev, { full_name: fullName, default_branch: "main" }]);
56
+ onRepoChange(fullName);
57
+ }, [onRepoChange]);
52
58
  const repoOptions = repos.map((r) => ({ value: r.full_name, label: r.full_name }));
53
59
  const branchOptions = branches.map((b) => ({ value: b.name, label: b.name }));
54
60
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [
@@ -60,7 +66,12 @@ function RepoBranchPicker({
60
66
  onChange: onRepoChange,
61
67
  placeholder: "Select repository...",
62
68
  loading: loadingRepos,
63
- highlight: !repo && !loadingRepos
69
+ highlight: !repo && !loadingRepos,
70
+ footerAction: createRepository ? {
71
+ icon: /* @__PURE__ */ jsx(PlusIcon, { size: 14 }),
72
+ label: "Create new repository...",
73
+ onClick: () => setShowCreateDialog(true)
74
+ } : void 0
64
75
  }
65
76
  ) }),
66
77
  /* @__PURE__ */ jsx("div", { className: cn("w-full sm:w-auto sm:min-w-[200px] sm:max-w-[200px]", !repo && "opacity-50 pointer-events-none"), children: /* @__PURE__ */ jsx(
@@ -73,9 +84,102 @@ function RepoBranchPicker({
73
84
  loading: loadingBranches,
74
85
  highlight: !!repo && !branch && !loadingBranches
75
86
  }
76
- ) })
87
+ ) }),
88
+ showCreateDialog && /* @__PURE__ */ jsx(
89
+ CreateRepoDialog,
90
+ {
91
+ onClose: () => setShowCreateDialog(false),
92
+ onCreate: handleRepoCreated,
93
+ createRepository
94
+ }
95
+ )
77
96
  ] });
78
97
  }
98
+ function CreateRepoDialog({ onClose, onCreate, createRepository }) {
99
+ const [name, setName] = useState("");
100
+ const [creating, setCreating] = useState(false);
101
+ const [error, setError] = useState(null);
102
+ const inputRef = useRef(null);
103
+ useEffect(() => {
104
+ setTimeout(() => inputRef.current?.focus(), 0);
105
+ }, []);
106
+ useEffect(() => {
107
+ const handleEsc = (e) => {
108
+ if (e.key === "Escape") onClose();
109
+ };
110
+ document.addEventListener("keydown", handleEsc);
111
+ return () => document.removeEventListener("keydown", handleEsc);
112
+ }, [onClose]);
113
+ const handleSubmit = async (e) => {
114
+ e.preventDefault();
115
+ const trimmed = name.trim();
116
+ if (!trimmed || creating) return;
117
+ setCreating(true);
118
+ setError(null);
119
+ try {
120
+ const repo = await createRepository(trimmed);
121
+ onCreate(repo.full_name);
122
+ onClose();
123
+ } catch (err) {
124
+ setError(err.message || "Failed to create repository");
125
+ setCreating(false);
126
+ }
127
+ };
128
+ return createPortal(
129
+ /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
130
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black/50", onClick: onClose }),
131
+ /* @__PURE__ */ jsxs(
132
+ "div",
133
+ {
134
+ className: "relative z-50 w-full max-w-md mx-4 rounded-lg border border-border bg-background p-6 shadow-lg",
135
+ onClick: (e) => e.stopPropagation(),
136
+ children: [
137
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
138
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: "Create Repository" }),
139
+ /* @__PURE__ */ jsx("button", { onClick: onClose, className: "text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsx(XIcon, { size: 16 }) })
140
+ ] }),
141
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, children: [
142
+ /* @__PURE__ */ jsx("label", { className: "block text-sm text-muted-foreground mb-1.5", children: "Repository name" }),
143
+ /* @__PURE__ */ jsx(
144
+ "input",
145
+ {
146
+ ref: inputRef,
147
+ type: "text",
148
+ value: name,
149
+ onChange: (e) => setName(e.target.value),
150
+ placeholder: "my-project",
151
+ className: "w-full rounded-md border border-border bg-muted px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary"
152
+ }
153
+ ),
154
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-2", children: error }),
155
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 flex justify-end gap-2", children: [
156
+ /* @__PURE__ */ jsx(
157
+ "button",
158
+ {
159
+ type: "button",
160
+ onClick: onClose,
161
+ className: "px-3 py-1.5 text-sm border border-border text-muted-foreground hover:text-foreground rounded-md transition-colors",
162
+ children: "Cancel"
163
+ }
164
+ ),
165
+ /* @__PURE__ */ jsx(
166
+ "button",
167
+ {
168
+ type: "submit",
169
+ disabled: !name.trim() || creating,
170
+ className: "px-3 py-1.5 text-sm font-medium bg-foreground text-background hover:bg-foreground/90 disabled:opacity-50 rounded-md transition-colors",
171
+ children: creating ? "Creating..." : "Create"
172
+ }
173
+ )
174
+ ] })
175
+ ] })
176
+ ]
177
+ }
178
+ )
179
+ ] }),
180
+ document.body
181
+ );
182
+ }
79
183
  function WorkspaceBar({
80
184
  repo,
81
185
  branch,
@@ -84,7 +188,8 @@ function WorkspaceBar({
84
188
  workspace,
85
189
  diffStats,
86
190
  onDiffStatsRefresh,
87
- onShowDiff
191
+ onShowDiff,
192
+ chatMode = "agent"
88
193
  }) {
89
194
  const [branches, setBranches] = useState([]);
90
195
  const [loadingBranches, setLoadingBranches] = useState(false);
@@ -96,7 +201,7 @@ function WorkspaceBar({
96
201
  repoName && /* @__PURE__ */ jsx("span", { className: "shrink-0 cursor-default hidden md:inline", title: repo, children: repoName }),
97
202
  branch && /* @__PURE__ */ jsxs(Fragment, { children: [
98
203
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground/30 hidden md:inline", children: "/" }),
99
- /* @__PURE__ */ jsx("div", { className: "shrink-0 max-w-[120px]", children: /* @__PURE__ */ jsx(
204
+ /* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsx(
100
205
  Combobox,
101
206
  {
102
207
  options: branches.map((b) => ({ value: b.name, label: b.name })),
@@ -114,17 +219,17 @@ function WorkspaceBar({
114
219
  }).finally(() => setLoadingBranches(false));
115
220
  }
116
221
  },
117
- triggerClassName: "font-medium text-foreground hover:text-primary hover:bg-accent transition-colors cursor-pointer truncate text-xs rounded px-1 -mx-1",
118
- triggerLabel: /* @__PURE__ */ jsx("span", { className: "truncate", title: branch, children: branch })
222
+ triggerClassName: "inline-block max-w-[70px] md:max-w-[160px] text-left font-medium text-foreground hover:text-primary hover:bg-accent transition-colors cursor-pointer truncate text-xs rounded px-1 -mx-1 align-middle",
223
+ triggerLabel: /* @__PURE__ */ jsx("span", { title: branch, children: branch })
119
224
  }
120
225
  ) })
121
226
  ] }),
122
- featureBranch && /* @__PURE__ */ jsxs(Fragment, { children: [
227
+ featureBranch && featureBranch !== branch && /* @__PURE__ */ jsxs(Fragment, { children: [
123
228
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground/50", children: "\u2190" }),
124
229
  /* @__PURE__ */ jsx("span", { className: "text-primary truncate min-w-0 cursor-default", title: featureBranch, children: featureBranch })
125
230
  ] })
126
231
  ] }),
127
- workspace?.id && /* @__PURE__ */ jsx(WorkspaceCommandButton, { workspaceId: workspace.id, diffStats, onDiffStatsRefresh, onShowDiff })
232
+ workspace?.id && /* @__PURE__ */ jsx(WorkspaceCommandButton, { workspaceId: workspace.id, diffStats, onDiffStatsRefresh, onShowDiff, chatMode })
128
233
  ] });
129
234
  }
130
235
  function CommandOutputDialog({ title, logs, exitCode, running, onClose }) {
@@ -193,21 +298,43 @@ function CommandOutputDialog({ title, logs, exitCode, running, onClose }) {
193
298
  );
194
299
  }
195
300
  const STORAGE_KEY = "thepopebot-workspace-command";
196
- function WorkspaceCommandButton({ workspaceId, diffStats, onDiffStatsRefresh, onShowDiff }) {
301
+ const FALLBACK_BY_MODE = { agent: "push", code: "create-pr" };
302
+ function WorkspaceCommandButton({ workspaceId, diffStats, onDiffStatsRefresh, onShowDiff, chatMode = "agent" }) {
303
+ const storageKey = `${STORAGE_KEY}:${chatMode}`;
197
304
  const [selectedCommand, setSelectedCommandState] = useState(() => {
198
305
  try {
199
- return localStorage.getItem(STORAGE_KEY) || "create-pr";
306
+ return localStorage.getItem(storageKey) || FALLBACK_BY_MODE[chatMode] || "create-pr";
200
307
  } catch {
201
- return "create-pr";
308
+ return FALLBACK_BY_MODE[chatMode] || "create-pr";
202
309
  }
203
310
  });
204
311
  const setSelectedCommand = (cmd) => {
205
312
  setSelectedCommandState(cmd);
206
313
  try {
207
- localStorage.setItem(STORAGE_KEY, cmd);
314
+ localStorage.setItem(storageKey, cmd);
208
315
  } catch {
209
316
  }
210
317
  };
318
+ useEffect(() => {
319
+ let stored = null;
320
+ try {
321
+ stored = localStorage.getItem(storageKey);
322
+ } catch {
323
+ }
324
+ if (stored) return;
325
+ let cancelled = false;
326
+ import("../actions.js").then(({ getModeGitActionDefault }) => {
327
+ getModeGitActionDefault(chatMode).then((val) => {
328
+ if (cancelled || !val) return;
329
+ setSelectedCommandState(val);
330
+ }).catch(() => {
331
+ });
332
+ }).catch(() => {
333
+ });
334
+ return () => {
335
+ cancelled = true;
336
+ };
337
+ }, [chatMode, storageKey]);
211
338
  const [commandRunning, setCommandRunning] = useState(false);
212
339
  const [dialogOpen, setDialogOpen] = useState(false);
213
340
  const [commandLogs, setCommandLogs] = useState([]);
@@ -223,14 +350,6 @@ function WorkspaceCommandButton({ workspaceId, diffStats, onDiffStatsRefresh, on
223
350
  }, []);
224
351
  const handleRun = useCallback(async () => {
225
352
  if (commandRunning) return;
226
- const fresh = await onDiffStatsRefresh?.();
227
- const stats = fresh || diffStats;
228
- if (!(stats?.insertions || 0) && !(stats?.deletions || 0)) {
229
- setDialogOpen(true);
230
- setCommandLogs([{ stream: "stderr", raw: "You have no changes.", parsed: [{ type: "text", text: "You have no changes." }] }]);
231
- setCommandExitCode(1);
232
- return;
233
- }
234
353
  setCommandRunning(true);
235
354
  setDialogOpen(true);
236
355
  setCommandLogs([]);