thepopebot 1.2.76-beta.31 → 1.2.76-beta.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/ai/index.js CHANGED
@@ -13,6 +13,7 @@ import { ensureWorkspaceRepo } from './workspace-setup.js';
13
13
  import { resolveAgentScope } from './scope.js';
14
14
  import { readSessionId, writeSessionId } from './session-manager.js';
15
15
  import { workspaceDir as getWorkspaceDir } from '../tools/docker.js';
16
+ import { getCommandPrompt } from '../git-commands.js';
16
17
 
17
18
  /**
18
19
  * Ensure a chat exists in the DB and save a message.
@@ -239,13 +240,7 @@ async function maybeAutoRun({ workspaceId, isCodeMode, repoDir }) {
239
240
 
240
241
  const branch = workspace.branch || 'main';
241
242
  const featureBranch = workspace.featureBranch || '';
242
- const prompts = {
243
- 'commit': 'Stage all changes with `git add -A`. Review the staged diff with `git diff --cached`. Write a clear conventional commit message and run `git commit`. If anything fails, diagnose the issue and fix it. Do not modify any source files.',
244
- 'push': `Stage all changes with \`git add -A\`. Review the staged diff with \`git diff --cached\`. Write a clear conventional commit message and run \`git commit\`. Then run \`git push origin ${featureBranch || branch}\`. If anything fails, diagnose the issue and fix it. Do not modify any source files.`,
245
- 'create-pr': `Make sure all changes are committed — if there are uncommitted changes, stage them with \`git add -A\`, write a commit message, and commit. Push the branch with \`git push -u origin ${featureBranch}\`. Then review all commits on branch ${featureBranch} compared to ${branch} and create a pull request using \`gh pr create\` with a clear title and detailed description. If a PR already exists, update it instead. If anything fails, diagnose the issue and fix it.`,
246
- 'pull': `Fetch from origin with \`git fetch origin\` and rebase this branch onto \`origin/${branch}\` with \`git rebase origin/${branch}\`. If there are merge conflicts, resolve them: read each conflicting file, understand both sides, resolve correctly, \`git add\` the file, then run \`git rebase --continue\`. Repeat if new conflicts appear. If anything fails, diagnose the issue and fix it.`,
247
- };
248
- const prompt = prompts[command] || null;
243
+ const prompt = getCommandPrompt(command, { branch, featureBranch });
249
244
 
250
245
  // Random suffix prevents collisions with manual runs and other auto-runs.
251
246
  const shortId = workspaceId.replace(/-/g, '').slice(0, 8);
@@ -1,6 +1,7 @@
1
1
  'use server';
2
2
 
3
3
  import { auth } from '../auth/index.js';
4
+ import { GIT_COMMAND_SET } from '../git-commands.js';
4
5
  import {
5
6
  createChat as dbCreateChat,
6
7
  getChatById,
@@ -851,7 +852,6 @@ export async function setCodingAgentDefault(agent) {
851
852
  }
852
853
 
853
854
  const MODE_BRANCH_VALUES = new Set(['default', 'dynamic']);
854
- const MODE_GIT_ACTION_VALUES = new Set(['commit', 'push', 'create-pr', 'pull']);
855
855
 
856
856
  /**
857
857
  * Get the admin-configured default git action for a chat mode.
@@ -876,7 +876,7 @@ export async function setModeDefault(mode, field, value) {
876
876
  try {
877
877
  if (mode !== 'agent' && mode !== 'code') return { error: 'Invalid mode' };
878
878
  if (field === 'branch' && !MODE_BRANCH_VALUES.has(value)) return { error: 'Invalid branch value' };
879
- if (field === 'gitAction' && !MODE_GIT_ACTION_VALUES.has(value)) return { error: 'Invalid git action value' };
879
+ if (field === 'gitAction' && !GIT_COMMAND_SET.has(value)) return { error: 'Invalid git action value' };
880
880
 
881
881
  const keyMap = {
882
882
  'agent.branch': 'AGENT_MODE_BRANCH',
@@ -4,12 +4,10 @@ import { useState, useEffect, useCallback, useRef } from "react";
4
4
  import { createPortal } from "react-dom";
5
5
  import { GitBranchIcon, ChevronDownIcon, SpinnerIcon, XIcon, PlusIcon } from "./icons.js";
6
6
  import { Combobox } from "./ui/combobox.js";
7
- import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "./ui/dropdown-menu.js";
7
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from "./ui/dropdown-menu.js";
8
8
  import { cn } from "../utils.js";
9
9
  import { CodeLogView } from "./code-log-view.js";
10
- function getCommandLabel(slug) {
11
- return slug.split("-").map((word) => word.length <= 2 ? word.toUpperCase() : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
12
- }
10
+ import { GIT_COMMANDS, getCommandLabel, FALLBACK_BY_MODE } from "../../git-commands.js";
13
11
  function RepoBranchPicker({
14
12
  repo,
15
13
  onRepoChange,
@@ -298,7 +296,6 @@ function CommandOutputDialog({ title, logs, exitCode, running, onClose }) {
298
296
  );
299
297
  }
300
298
  const STORAGE_KEY = "thepopebot-workspace-command";
301
- const FALLBACK_BY_MODE = { agent: "push", code: "create-pr" };
302
299
  function WorkspaceCommandButton({ workspaceId, diffStats, onDiffStatsRefresh, onShowDiff, chatMode = "agent", autoRunInfo = null }) {
303
300
  const storageKey = `${STORAGE_KEY}:${chatMode}`;
304
301
  const [selectedCommand, setSelectedCommandState] = useState(() => {
@@ -476,11 +473,7 @@ function WorkspaceCommandButton({ workspaceId, diffStats, onDiffStatsRefresh, on
476
473
  children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 })
477
474
  }
478
475
  ) }),
479
- /* @__PURE__ */ jsxs(DropdownMenuContent, { side: "top", align: "end", className: "whitespace-nowrap", children: [
480
- ["commit", "push", "create-pr"].map((cmd) => /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => setSelectedCommand(cmd), children: getCommandLabel(cmd) }, cmd)),
481
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
482
- ["pull"].map((cmd) => /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => setSelectedCommand(cmd), children: getCommandLabel(cmd) }, cmd))
483
- ] })
476
+ /* @__PURE__ */ jsx(DropdownMenuContent, { side: "top", align: "end", className: "whitespace-nowrap", children: GIT_COMMANDS.map((cmd) => /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => setSelectedCommand(cmd), children: getCommandLabel(cmd) }, cmd)) })
484
477
  ] })
485
478
  ] })
486
479
  ] }),
@@ -498,6 +491,8 @@ function WorkspaceCommandButton({ workspaceId, diffStats, onDiffStatsRefresh, on
498
491
  }
499
492
  export {
500
493
  CommandOutputDialog,
494
+ FALLBACK_BY_MODE,
495
+ GIT_COMMANDS,
501
496
  RepoBranchPicker,
502
497
  WorkspaceBar,
503
498
  getCommandLabel
@@ -4,20 +4,13 @@ import { useState, useEffect, useCallback, useRef } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
5
  import { GitBranchIcon, ChevronDownIcon, SpinnerIcon, XIcon, PlusIcon } from './icons.js';
6
6
  import { Combobox } from './ui/combobox.js';
7
- import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './ui/dropdown-menu.js';
7
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from './ui/dropdown-menu.js';
8
8
  import { cn } from '../utils.js';
9
9
  import { CodeLogView } from './code-log-view.js';
10
10
 
11
- /**
12
- * Auto-generates display labels from command slug.
13
- * Splits on hyphens, capitalizes each word. Words ≤2 chars are uppercased (e.g. pr → PR).
14
- */
15
- export function getCommandLabel(slug) {
16
- return slug
17
- .split('-')
18
- .map(word => word.length <= 2 ? word.toUpperCase() : word.charAt(0).toUpperCase() + word.slice(1))
19
- .join(' ');
20
- }
11
+ import { GIT_COMMANDS, getCommandLabel, FALLBACK_BY_MODE } from '../../git-commands.js';
12
+ // Re-export so existing client imports (`from './code-mode-toggle.js'`) keep working.
13
+ export { GIT_COMMANDS, getCommandLabel, FALLBACK_BY_MODE };
21
14
 
22
15
  /**
23
16
  * Repo/branch picker dropdowns for the empty state (below chat input).
@@ -343,7 +336,6 @@ export function CommandOutputDialog({ title, logs, exitCode, running, onClose })
343
336
  }
344
337
 
345
338
  const STORAGE_KEY = 'thepopebot-workspace-command';
346
- const FALLBACK_BY_MODE = { agent: 'push', code: 'create-pr' };
347
339
 
348
340
  function WorkspaceCommandButton({ workspaceId, diffStats, onDiffStatsRefresh, onShowDiff, chatMode = 'agent', autoRunInfo = null }) {
349
341
  const storageKey = `${STORAGE_KEY}:${chatMode}`;
@@ -512,13 +504,7 @@ function WorkspaceCommandButton({ workspaceId, diffStats, onDiffStatsRefresh, on
512
504
  </button>
513
505
  </DropdownMenuTrigger>
514
506
  <DropdownMenuContent side="top" align="end" className="whitespace-nowrap">
515
- {['commit', 'push', 'create-pr'].map((cmd) => (
516
- <DropdownMenuItem key={cmd} onClick={() => setSelectedCommand(cmd)}>
517
- {getCommandLabel(cmd)}
518
- </DropdownMenuItem>
519
- ))}
520
- <DropdownMenuSeparator />
521
- {['pull'].map((cmd) => (
507
+ {GIT_COMMANDS.map((cmd) => (
522
508
  <DropdownMenuItem key={cmd} onClick={() => setSelectedCommand(cmd)}>
523
509
  {getCommandLabel(cmd)}
524
510
  </DropdownMenuItem>
@@ -2,6 +2,7 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from "react";
4
4
  import { CheckIcon } from "./icons.js";
5
+ import { GIT_COMMANDS, getCommandLabel } from "./code-mode-toggle.js";
5
6
  import {
6
7
  getCodingAgentSettings,
7
8
  updateCodingAgentConfig,
@@ -192,12 +193,7 @@ const BRANCH_OPTIONS = [
192
193
  { value: "default", label: "Default branch" },
193
194
  { value: "dynamic", label: "Feature branch" }
194
195
  ];
195
- const GIT_ACTION_OPTIONS = [
196
- { value: "commit", label: "Commit" },
197
- { value: "push", label: "Push" },
198
- { value: "create-pr", label: "Create PR" },
199
- { value: "pull", label: "Pull" }
200
- ];
196
+ const GIT_ACTION_OPTIONS = GIT_COMMANDS.map((value) => ({ value, label: getCommandLabel(value) }));
201
197
  function ModeDefaultRow({ label, mode, field, value, options, onSaved }) {
202
198
  const [saving, setSaving] = useState(false);
203
199
  const [saved, setSaved] = useState(false);
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
4
  import { CheckIcon } from './icons.js';
5
+ import { GIT_COMMANDS, getCommandLabel } from './code-mode-toggle.js';
5
6
  import {
6
7
  getCodingAgentSettings,
7
8
  updateCodingAgentConfig,
@@ -203,12 +204,7 @@ const BRANCH_OPTIONS = [
203
204
  { value: 'dynamic', label: 'Feature branch' },
204
205
  ];
205
206
 
206
- const GIT_ACTION_OPTIONS = [
207
- { value: 'commit', label: 'Commit' },
208
- { value: 'push', label: 'Push' },
209
- { value: 'create-pr', label: 'Create PR' },
210
- { value: 'pull', label: 'Pull' },
211
- ];
207
+ const GIT_ACTION_OPTIONS = GIT_COMMANDS.map(value => ({ value, label: getCommandLabel(value) }));
212
208
 
213
209
  function ModeDefaultRow({ label, mode, field, value, options, onSaved }) {
214
210
  const [saving, setSaving] = useState(false);
@@ -1,6 +1,7 @@
1
1
  'use server';
2
2
 
3
3
  import { auth } from '../auth/index.js';
4
+ import { getCommandPrompt } from '../git-commands.js';
4
5
  import {
5
6
  createCodeWorkspace as dbCreateCodeWorkspace,
6
7
  getCodeWorkspaceById,
@@ -733,7 +734,7 @@ export async function getWorkspaceDiffFull(id, authenticatedUser) {
733
734
  * Launch a workspace command container and return immediately.
734
735
  * The client connects to /stream/containers/logs?name=...&cleanup=true for live output.
735
736
  * @param {string} id - Workspace ID
736
- * @param {string} command - 'commit' | 'push' | 'create-pr' | 'pull'
737
+ * @param {string} command - 'commit' | 'push' | 'create-pr' | 'pull' | 'pull-push'
737
738
  * @returns {Promise<{success: boolean, containerName?: string, message?: string}>}
738
739
  */
739
740
  export async function launchWorkspaceCommand(id, command) {
@@ -751,18 +752,13 @@ export async function launchWorkspaceCommand(id, command) {
751
752
  return { success: false, message: 'Start coding first.' };
752
753
  }
753
754
 
755
+ const { randomUUID } = await import('crypto');
754
756
  const shortId = id.replace(/-/g, '').slice(0, 8);
755
- const containerName = `command-${command}-${shortId}`;
757
+ const containerName = `command-${command}-${shortId}-${randomUUID().slice(0, 8)}`;
756
758
 
757
759
  const branch = workspace.branch || 'main';
758
760
  const featureBranch = workspace.featureBranch || '';
759
- const prompts = {
760
- 'commit': 'Stage all changes with `git add -A`. Review the staged diff with `git diff --cached`. Write a clear conventional commit message and run `git commit`. If anything fails, diagnose the issue and fix it. Do not modify any source files.',
761
- 'push': `Stage all changes with \`git add -A\`. Review the staged diff with \`git diff --cached\`. Write a clear conventional commit message and run \`git commit\`. Then run \`git push origin ${featureBranch || branch}\`. If anything fails, diagnose the issue and fix it. Do not modify any source files.`,
762
- 'create-pr': `Make sure all changes are committed — if there are uncommitted changes, stage them with \`git add -A\`, write a commit message, and commit. Push the branch with \`git push -u origin ${featureBranch}\`. Then review all commits on branch ${featureBranch} compared to ${branch} and create a pull request using \`gh pr create\` with a clear title and detailed description. If a PR already exists, update it instead. If anything fails, diagnose the issue and fix it.`,
763
- 'pull': `Fetch from origin with \`git fetch origin\` and rebase this branch onto \`origin/${branch}\` with \`git rebase origin/${branch}\`. If there are merge conflicts, resolve them: read each conflicting file, understand both sides, resolve correctly, \`git add\` the file, then run \`git rebase --continue\`. Repeat if new conflicts appear. If anything fails, diagnose the issue and fix it.`,
764
- };
765
- const prompt = prompts[command] || null;
761
+ const prompt = getCommandPrompt(command, { branch, featureBranch });
766
762
 
767
763
  await runCommandContainer({
768
764
  containerName,
@@ -786,7 +782,7 @@ export async function launchWorkspaceCommand(id, command) {
786
782
  * Waits for the container to finish and returns its output.
787
783
  * @deprecated Use launchWorkspaceCommand + SSE streaming instead.
788
784
  * @param {string} id - Workspace ID
789
- * @param {string} command - 'commit' | 'push' | 'create-pr' | 'pull'
785
+ * @param {string} command - 'commit' | 'push' | 'create-pr' | 'pull' | 'pull-push'
790
786
  * @returns {Promise<{success: boolean, output?: string, exitCode?: number, message?: string}>}
791
787
  */
792
788
  export async function runWorkspaceCommand(id, command) {
@@ -804,19 +800,14 @@ export async function runWorkspaceCommand(id, command) {
804
800
  return { success: false, message: 'Start coding first.' };
805
801
  }
806
802
 
803
+ const { randomUUID } = await import('crypto');
807
804
  const shortId = id.replace(/-/g, '').slice(0, 8);
808
- const containerName = `command-${command}-${shortId}`;
805
+ const containerName = `command-${command}-${shortId}-${randomUUID().slice(0, 8)}`;
809
806
 
810
807
  // Build prompt based on command type
811
808
  const branch = workspace.branch || 'main';
812
809
  const featureBranch = workspace.featureBranch || '';
813
- const prompts = {
814
- 'commit': 'Stage all changes with `git add -A`. Review the staged diff with `git diff --cached`. Write a clear conventional commit message and run `git commit`. If anything fails, diagnose the issue and fix it. Do not modify any source files.',
815
- 'push': `Stage all changes with \`git add -A\`. Review the staged diff with \`git diff --cached\`. Write a clear conventional commit message and run \`git commit\`. Then run \`git push origin ${featureBranch || branch}\`. If anything fails, diagnose the issue and fix it. Do not modify any source files.`,
816
- 'create-pr': `Make sure all changes are committed — if there are uncommitted changes, stage them with \`git add -A\`, write a commit message, and commit. Push the branch with \`git push -u origin ${featureBranch}\`. Then review all commits on branch ${featureBranch} compared to ${branch} and create a pull request using \`gh pr create\` with a clear title and detailed description. If a PR already exists, update it instead. If anything fails, diagnose the issue and fix it.`,
817
- 'pull': `Fetch from origin with \`git fetch origin\` and rebase this branch onto \`origin/${branch}\` with \`git rebase origin/${branch}\`. If there are merge conflicts, resolve them: read each conflicting file, understand both sides, resolve correctly, \`git add\` the file, then run \`git rebase --continue\`. Repeat if new conflicts appear. If anything fails, diagnose the issue and fix it.`,
818
- };
819
- const prompt = prompts[command] || null;
810
+ const prompt = getCommandPrompt(command, { branch, featureBranch });
820
811
 
821
812
  await runCommandContainer({
822
813
  containerName,
@@ -8,7 +8,8 @@ import { WebLinksAddon } from "@xterm/addon-web-links";
8
8
  import { SerializeAddon } from "@xterm/addon-serialize";
9
9
  import "@xterm/xterm/css/xterm.css";
10
10
  import { SpinnerIcon, MicIcon } from "../chat/components/icons.js";
11
- import { getCommandLabel, CommandOutputDialog } from "../chat/components/code-mode-toggle.js";
11
+ import { CommandOutputDialog } from "../chat/components/code-mode-toggle.js";
12
+ import { GIT_COMMANDS, getCommandLabel, FALLBACK_BY_MODE } from "../git-commands.js";
12
13
  import { useVoiceInput } from "../voice/use-voice-input.js";
13
14
  import { VoiceBars } from "../chat/components/voice-bars.js";
14
15
  const getVoiceToken = () => fetch("/chat/voice-token").then((r) => r.json()).catch(() => ({ error: "Failed to get voice token" }));
@@ -464,11 +465,6 @@ function TerminalView({ codeWorkspaceId, wsPath, isActive = true, showToolbar =
464
465
  color: #7aa2f7;
465
466
  background: rgba(122,162,247,0.08);
466
467
  }
467
- .code-toolbar-dropup-separator {
468
- height: 1px;
469
- background: var(--tb-border, rgba(169,177,214,0.15));
470
- margin: 4px 0;
471
- }
472
468
  .code-toolbar-btn--reconnect:hover {
473
469
  color: var(--tb-hover, #a9b1d6);
474
470
  }
@@ -781,7 +777,6 @@ function TerminalView({ codeWorkspaceId, wsPath, isActive = true, showToolbar =
781
777
  ] });
782
778
  }
783
779
  const STORAGE_KEY = "thepopebot-workspace-command";
784
- const FALLBACK_BY_MODE = { agent: "push", code: "create-pr" };
785
780
  function ToolbarCommandButton({ codeWorkspaceId, diffStats, onDiffStatsRefresh, onShowDiff }) {
786
781
  const [chatMode, setChatMode] = useState("code");
787
782
  useEffect(() => {
@@ -913,33 +908,18 @@ function ToolbarCommandButton({ codeWorkspaceId, diffStats, onDiffStatsRefresh,
913
908
  }
914
909
  )
915
910
  ] }),
916
- dropupOpen && /* @__PURE__ */ jsxs("div", { className: "code-toolbar-dropup", children: [
917
- ["commit", "push", "create-pr"].map((cmd) => /* @__PURE__ */ jsx(
918
- "button",
919
- {
920
- className: "code-toolbar-dropup-item",
921
- onClick: () => {
922
- setSelectedCommand(cmd);
923
- setDropupOpen(false);
924
- },
925
- children: getCommandLabel(cmd)
926
- },
927
- cmd
928
- )),
929
- /* @__PURE__ */ jsx("div", { className: "code-toolbar-dropup-separator" }),
930
- ["pull"].map((cmd) => /* @__PURE__ */ jsx(
931
- "button",
932
- {
933
- className: "code-toolbar-dropup-item",
934
- onClick: () => {
935
- setSelectedCommand(cmd);
936
- setDropupOpen(false);
937
- },
938
- children: getCommandLabel(cmd)
911
+ dropupOpen && /* @__PURE__ */ jsx("div", { className: "code-toolbar-dropup", children: GIT_COMMANDS.map((cmd) => /* @__PURE__ */ jsx(
912
+ "button",
913
+ {
914
+ className: "code-toolbar-dropup-item",
915
+ onClick: () => {
916
+ setSelectedCommand(cmd);
917
+ setDropupOpen(false);
939
918
  },
940
- cmd
941
- ))
942
- ] })
919
+ children: getCommandLabel(cmd)
920
+ },
921
+ cmd
922
+ )) })
943
923
  ] }),
944
924
  dialogOpen && /* @__PURE__ */ jsx(
945
925
  CommandOutputDialog,
@@ -8,7 +8,8 @@ import { WebLinksAddon } from '@xterm/addon-web-links';
8
8
  import { SerializeAddon } from '@xterm/addon-serialize';
9
9
  import '@xterm/xterm/css/xterm.css';
10
10
  import { SpinnerIcon, MicIcon } from '../chat/components/icons.js';
11
- import { getCommandLabel, CommandOutputDialog } from '../chat/components/code-mode-toggle.js';
11
+ import { CommandOutputDialog } from '../chat/components/code-mode-toggle.js';
12
+ import { GIT_COMMANDS, getCommandLabel, FALLBACK_BY_MODE } from '../git-commands.js';
12
13
  import { useVoiceInput } from '../voice/use-voice-input.js';
13
14
  import { VoiceBars } from '../chat/components/voice-bars.js';
14
15
 
@@ -543,11 +544,6 @@ export default function TerminalView({ codeWorkspaceId, wsPath, isActive = true,
543
544
  color: #7aa2f7;
544
545
  background: rgba(122,162,247,0.08);
545
546
  }
546
- .code-toolbar-dropup-separator {
547
- height: 1px;
548
- background: var(--tb-border, rgba(169,177,214,0.15));
549
- margin: 4px 0;
550
- }
551
547
  .code-toolbar-btn--reconnect:hover {
552
548
  color: var(--tb-hover, #a9b1d6);
553
549
  }
@@ -843,7 +839,6 @@ export default function TerminalView({ codeWorkspaceId, wsPath, isActive = true,
843
839
  }
844
840
 
845
841
  const STORAGE_KEY = 'thepopebot-workspace-command';
846
- const FALLBACK_BY_MODE = { agent: 'push', code: 'create-pr' };
847
842
 
848
843
  function ToolbarCommandButton({ codeWorkspaceId, diffStats, onDiffStatsRefresh, onShowDiff }) {
849
844
  // Resolve chatMode from the workspace's chat (matches code-mode-toggle's per-mode behavior).
@@ -967,17 +962,7 @@ function ToolbarCommandButton({ codeWorkspaceId, diffStats, onDiffStatsRefresh,
967
962
  </div>
968
963
  {dropupOpen && (
969
964
  <div className="code-toolbar-dropup">
970
- {['commit', 'push', 'create-pr'].map((cmd) => (
971
- <button
972
- key={cmd}
973
- className="code-toolbar-dropup-item"
974
- onClick={() => { setSelectedCommand(cmd); setDropupOpen(false); }}
975
- >
976
- {getCommandLabel(cmd)}
977
- </button>
978
- ))}
979
- <div className="code-toolbar-dropup-separator" />
980
- {['pull'].map((cmd) => (
965
+ {GIT_COMMANDS.map((cmd) => (
981
966
  <button
982
967
  key={cmd}
983
968
  className="code-toolbar-dropup-item"
package/lib/config.js CHANGED
@@ -81,9 +81,9 @@ const DEFAULTS = {
81
81
  CODING_AGENT_KIMI_CLI_ENABLED: 'false',
82
82
  AGENT_MODE_BRANCH: 'default',
83
83
  CODE_MODE_BRANCH: 'dynamic',
84
- AGENT_MODE_GIT_ACTION: 'push',
84
+ AGENT_MODE_GIT_ACTION: 'pull-push',
85
85
  CODE_MODE_GIT_ACTION: 'create-pr',
86
- AGENT_MODE_AUTO_RUN: 'false',
86
+ AGENT_MODE_AUTO_RUN: 'true',
87
87
  CODE_MODE_AUTO_RUN: 'false',
88
88
  };
89
89
 
@@ -116,6 +116,9 @@ export async function GET(request) {
116
116
  logStream.on('error', () => {
117
117
  send('error', { message: 'Log stream error' });
118
118
  clearInterval(keepalive);
119
+ if (cleanup) {
120
+ removeContainer(name).catch(() => {});
121
+ }
119
122
  try { streamController.close(); } catch {}
120
123
  });
121
124
 
@@ -134,6 +137,9 @@ export async function GET(request) {
134
137
  } catch (err) {
135
138
  clearInterval(keepalive);
136
139
  send('error', { message: err.message || 'Failed to tail logs' });
140
+ if (cleanup) {
141
+ removeContainer(name).catch(() => {});
142
+ }
137
143
  try { streamController.close(); } catch {}
138
144
  }
139
145
 
@@ -153,8 +159,14 @@ export async function GET(request) {
153
159
  // Exit code from inspect data
154
160
  const exitCode = info.State?.ExitCode ?? -1;
155
161
  send('exit', { exitCode });
162
+ if (cleanup) {
163
+ try { await removeContainer(name); } catch {}
164
+ }
156
165
  } catch (err) {
157
166
  send('error', { message: err.message || 'Failed to get logs' });
167
+ if (cleanup) {
168
+ try { await removeContainer(name); } catch {}
169
+ }
158
170
  }
159
171
 
160
172
  try { streamController.close(); } catch {}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Single source of truth for the workspace git commands.
3
+ *
4
+ * Used by:
5
+ * - Chat dropdown (lib/chat/components/code-mode-toggle.jsx)
6
+ * - Workspace toolbar dropup (lib/code/terminal-view.jsx)
7
+ * - Admin defaults select (lib/chat/components/settings-coding-agents-page.jsx)
8
+ * - Manual launch (lib/code/actions.js — launchWorkspaceCommand, runWorkspaceCommand)
9
+ * - Auto-run on chat finish (lib/ai/index.js — maybeAutoRun)
10
+ * - Server-side validation (lib/chat/actions.js — setModeDefault)
11
+ *
12
+ * Plain ESM (no 'use client'), so both server and client code can import it.
13
+ */
14
+
15
+ export const GIT_COMMANDS = ['pull', 'commit', 'push', 'pull-push', 'create-pr'];
16
+ export const GIT_COMMAND_SET = new Set(GIT_COMMANDS);
17
+ export const FALLBACK_BY_MODE = { agent: 'pull-push', code: 'create-pr' };
18
+
19
+ export function getCommandLabel(slug) {
20
+ return slug
21
+ .split('-')
22
+ .map(word => word.length <= 2 ? word.toUpperCase() : word.charAt(0).toUpperCase() + word.slice(1))
23
+ .join(' ');
24
+ }
25
+
26
+ export function getCommandPrompt(command, { branch = '', featureBranch = '' } = {}) {
27
+ switch (command) {
28
+ case 'commit':
29
+ return 'Stage all changes with `git add -A`. Review the staged diff with `git diff --cached`. Write a clear conventional commit message and run `git commit`. If anything fails, diagnose the issue and fix it. Do not modify any source files.';
30
+ case 'push':
31
+ return `Stage all changes with \`git add -A\`. Review the staged diff with \`git diff --cached\`. Write a clear conventional commit message and run \`git commit\`. Then run \`git push origin ${featureBranch || branch}\`. If anything fails, diagnose the issue and fix it. Do not modify any source files.`;
32
+ case 'create-pr':
33
+ return `Make sure all changes are committed — if there are uncommitted changes, stage them with \`git add -A\`, write a commit message, and commit. Push the branch with \`git push -u origin ${featureBranch}\`. Then review all commits on branch ${featureBranch} compared to ${branch} and create a pull request using \`gh pr create\` with a clear title and detailed description. If a PR already exists, update it instead. If anything fails, diagnose the issue and fix it.`;
34
+ case 'pull':
35
+ return `Fetch from origin with \`git fetch origin\` and rebase this branch onto \`origin/${branch}\` with \`git rebase origin/${branch}\`. If there are merge conflicts, resolve them: read each conflicting file, understand both sides, resolve correctly, \`git add\` the file, then run \`git rebase --continue\`. Repeat if new conflicts appear. If anything fails, diagnose the issue and fix it.`;
36
+ case 'pull-push':
37
+ return `Stage all changes with \`git add -A\`. Review the staged diff with \`git diff --cached\`. If there are staged changes, write a clear conventional commit message and run \`git commit\`. Then fetch from origin with \`git fetch origin\`. If \`origin/${featureBranch || branch}\` exists, rebase onto it with \`git rebase origin/${featureBranch || branch}\` — resolve any conflicts by reading each conflicting file, choosing the correct resolution, \`git add\` the file, then run \`git rebase --continue\`. Finally, run \`git push origin ${featureBranch || branch}\`. If anything fails, diagnose the issue and fix it.`;
38
+ default:
39
+ return null;
40
+ }
41
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.76-beta.31",
3
+ "version": "1.2.76-beta.33",
4
4
  "type": "module",
5
5
  "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
6
  "bin": {