Skip to content

Commit 4087023

Browse files
YecatsCopilotbacknotprop
authored andcommitted
fix: Detect calling agent via env vars and centralize agent config (backnotprop#418)
* fix: detect calling agent via env vars for correct badge display Add prioritized agent detection chain: CODEX_THREAD_ID (codex) > COPILOT_CLI (copilot-cli) > default (claude-code). All subcommands use the shared detectedOrigin constant. Display name updated from 'Copilot CLI' to 'GitHub Copilot'. TypeScript origin union type updated to include 'copilot-cli'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: centralize agent config into @plannotator/shared/agents - Create packages/shared/agents.ts as single source of truth for Origin type, agent display names (AGENT_CONFIG), getAgentName(), and getAgentBadge() - Replace all inline origin type unions across 8 files with Origin import - Type detectedOrigin and ServerOptions.origin as Origin for compile-time safety - Add @plannotator/shared dependency to @plannotator/editor package.json - Adding a new agent is now a one-file change to AGENT_CONFIG Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: restore gitContext in startReviewServer call Accidentally removed alongside the origin refactor. Without it, local code reviews lose branch detection, diff-type switching, and CWD resolution. For provenance purposes, this commit was AI assisted. --------- Co-authored-by: Yecats <Yecats@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Michael Ramos <mdramos8@gmail.com>
1 parent eed214c commit 4087023

13 files changed

Lines changed: 185 additions & 78 deletions

File tree

apps/hook/server/index.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import { registerSession, unregisterSession, listSessions } from "@plannotator/s
6767
import { openBrowser } from "@plannotator/server/browser";
6868
import { detectProjectName } from "@plannotator/server/project";
6969
import { planDenyFeedback } from "@plannotator/shared/feedback-templates";
70+
import type { Origin } from "@plannotator/shared/agents";
7071
import { findSessionLogsForCwd, resolveSessionLogByPpid, findSessionLogsByAncestorWalk, getLastRenderedMessage, type RenderedMessage } from "./session-log";
7172
import { findCodexRolloutByThreadId, getLastCodexMessage } from "./codex-session";
7273
import { findCopilotPlanContent, findCopilotSessionForCwd, getLastCopilotMessage } from "./copilot-session";
@@ -103,6 +104,13 @@ const shareBaseUrl = process.env.PLANNOTATOR_SHARE_URL || undefined;
103104
// Paste service URL for short URL sharing
104105
const pasteApiUrl = process.env.PLANNOTATOR_PASTE_URL || undefined;
105106

107+
// Detect calling agent from environment variables set by agent runtimes.
108+
// Priority: Codex > Copilot CLI > Claude Code (default fallback)
109+
const detectedOrigin: Origin =
110+
process.env.CODEX_THREAD_ID ? "codex" :
111+
process.env.COPILOT_CLI ? "copilot-cli" :
112+
"claude-code";
113+
106114
if (args[0] === "sessions") {
107115
// ============================================
108116
// SESSION DISCOVERY MODE
@@ -215,7 +223,7 @@ if (args[0] === "sessions") {
215223
rawPatch,
216224
gitRef,
217225
error: diffError,
218-
origin: "claude-code",
226+
origin: detectedOrigin,
219227
diffType: isPRMode ? undefined : "uncommitted",
220228
gitContext,
221229
prMetadata,
@@ -337,7 +345,7 @@ if (args[0] === "sessions") {
337345
const server = await startAnnotateServer({
338346
markdown,
339347
filePath: absolutePath,
340-
origin: "claude-code",
348+
origin: detectedOrigin,
341349
mode: annotateMode,
342350
folderPath,
343351
sharingEnabled,
@@ -456,7 +464,7 @@ if (args[0] === "sessions") {
456464
const server = await startAnnotateServer({
457465
markdown: lastMessage.text,
458466
filePath: "last-message",
459-
origin: isCodex ? "codex" : "claude-code",
467+
origin: detectedOrigin,
460468
mode: "annotate-last",
461469
sharingEnabled,
462470
shareBaseUrl,
@@ -499,7 +507,7 @@ if (args[0] === "sessions") {
499507

500508
const server = await startPlannotatorServer({
501509
plan: "",
502-
origin: "claude-code",
510+
origin: detectedOrigin,
503511
mode: "archive",
504512
sharingEnabled,
505513
shareBaseUrl,
@@ -704,7 +712,7 @@ if (args[0] === "sessions") {
704712
// Start the plan review server
705713
const server = await startPlannotatorServer({
706714
plan: planContent,
707-
origin: "claude-code",
715+
origin: detectedOrigin,
708716
permissionMode,
709717
sharingEnabled,
710718
shareBaseUrl,

bun.lock

Lines changed: 114 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/editor/App.tsx

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useState, useEffect, useMemo, useRef } from 'react';
2+
import { type Origin, getAgentName, getAgentBadge } from '@plannotator/shared/agents';
23
import { parseMarkdownToBlocks, exportAnnotations, exportLinkedDocAnnotations, exportEditorAnnotations, extractFrontmatter, wrapFeedbackForAgent, Frontmatter } from '@plannotator/ui/utils/parser';
34
import { Viewer, ViewerHandle } from '@plannotator/ui/components/Viewer';
45
import { AnnotationPanel } from '@plannotator/ui/components/AnnotationPanel';
@@ -85,7 +86,7 @@ const App: React.FC = () => {
8586
});
8687
const [uiPrefs, setUiPrefs] = useState(() => getUIPreferences());
8788
const [isApiMode, setIsApiMode] = useState(false);
88-
const [origin, setOrigin] = useState<'claude-code' | 'opencode' | 'pi' | 'codex' | null>(null);
89+
const [origin, setOrigin] = useState<Origin | null>(null);
8990
const [gitUser, setGitUser] = useState<string | undefined>();
9091
const [isWSL, setIsWSL] = useState(false);
9192
const [globalAttachments, setGlobalAttachments] = useState<ImageAttachment[]>([]);
@@ -388,7 +389,7 @@ const App: React.FC = () => {
388389
if (!res.ok) throw new Error('Not in API mode');
389390
return res.json();
390391
})
391-
.then((data: { plan: string; origin?: 'claude-code' | 'opencode' | 'pi' | 'codex'; mode?: 'annotate' | 'annotate-last' | 'annotate-folder' | 'archive'; filePath?: string; sharingEnabled?: boolean; shareBaseUrl?: string; pasteApiUrl?: string; repoInfo?: { display: string; branch?: string }; previousPlan?: string | null; versionInfo?: { version: number; totalVersions: number; project: string }; archivePlans?: ArchivedPlan[]; projectRoot?: string; isWSL?: boolean; serverConfig?: { displayName?: string; gitUser?: string } }) => {
392+
.then((data: { plan: string; origin?: Origin; mode?: 'annotate' | 'annotate-last' | 'annotate-folder' | 'archive'; filePath?: string; sharingEnabled?: boolean; shareBaseUrl?: string; pasteApiUrl?: string; repoInfo?: { display: string; branch?: string }; previousPlan?: string | null; versionInfo?: { version: number; totalVersions: number; project: string }; archivePlans?: ArchivedPlan[]; projectRoot?: string; isWSL?: boolean; serverConfig?: { displayName?: string; gitUser?: string } }) => {
392393
// Initialize config store with server-provided values (config file > cookie > default)
393394
configStore.init(data.serverConfig);
394395
// gitUser drives the "Use git name" button in Settings; stays undefined (button hidden) when unavailable
@@ -986,14 +987,7 @@ const App: React.FC = () => {
986987
return () => document.removeEventListener('pointerdown', handleClickOutside);
987988
}, [showExportDropdown]);
988989

989-
const agentName = useMemo(() => {
990-
if (origin === 'opencode') return 'OpenCode';
991-
if (origin === 'claude-code') return 'Claude Code';
992-
if (origin === 'copilot-cli') return 'Copilot CLI';
993-
if (origin === 'pi') return 'Pi';
994-
if (origin === 'codex') return 'Codex';
995-
return 'Coding Agent';
996-
}, [origin]);
990+
const agentName = useMemo(() => getAgentName(origin), [origin]);
997991

998992
const planMaxWidth = useMemo(() => {
999993
const widths: Record<PlanWidth, number> = { compact: 832, default: 1040, wide: 1280 };
@@ -1024,15 +1018,7 @@ const App: React.FC = () => {
10241018
v{typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.0.0'}
10251019
</a>
10261020
{origin && (
1027-
<span className={`text-[10px] px-1.5 py-0.5 rounded font-medium hidden md:inline ${
1028-
origin === 'claude-code'
1029-
? 'bg-orange-500/15 text-orange-400'
1030-
: origin === 'pi'
1031-
? 'bg-violet-500/15 text-violet-400'
1032-
: origin === 'copilot-cli'
1033-
? 'bg-blue-500/15 text-blue-400'
1034-
: 'bg-zinc-500/20 text-zinc-400'
1035-
}`}>
1021+
<span className={`text-[10px] px-1.5 py-0.5 rounded font-medium hidden md:inline ${getAgentBadge(origin)}`}>
10361022
{agentName}
10371023
</span>
10381024
)}

packages/editor/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"./styles": "./index.css"
88
},
99
"dependencies": {
10+
"@plannotator/shared": "workspace:*",
1011
"@plannotator/ui": "workspace:*",
1112
"react": "^19.2.3",
1213
"react-dom": "^19.2.3",

packages/review-editor/App.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useState, useEffect, useCallback, useMemo } from 'react';
2+
import { type Origin, getAgentName } from '@plannotator/shared/agents';
23
import { ThemeProvider, useTheme } from '@plannotator/ui/components/ThemeProvider';
34
import { ModeToggle } from '@plannotator/ui/components/ModeToggle';
45
import { ConfirmDialog } from '@plannotator/ui/components/ConfirmDialog';
@@ -43,7 +44,7 @@ interface DiffData {
4344
files: DiffFile[];
4445
rawPatch: string;
4546
gitRef: string;
46-
origin?: 'opencode' | 'claude-code' | 'pi';
47+
origin?: Origin;
4748
diffType?: string;
4849
gitContext?: GitContext;
4950
sharingEnabled?: boolean;
@@ -99,7 +100,7 @@ const ReviewApp: React.FC = () => {
99100
const [copyFeedback, setCopyFeedback] = useState<string | null>(null);
100101
const [viewedFiles, setViewedFiles] = useState<Set<string>>(new Set());
101102
const [hideViewedFiles, setHideViewedFiles] = useState(false);
102-
const [origin, setOrigin] = useState<'opencode' | 'claude-code' | 'pi' | null>(null);
103+
const [origin, setOrigin] = useState<Origin | null>(null);
103104
const [gitUser, setGitUser] = useState<string | undefined>();
104105
const [isWSL, setIsWSL] = useState(false);
105106
const [diffType, setDiffType] = useState<string>('uncommitted');
@@ -383,7 +384,7 @@ const ReviewApp: React.FC = () => {
383384
.then((data: {
384385
rawPatch: string;
385386
gitRef: string;
386-
origin?: 'opencode' | 'claude-code' | 'pi';
387+
origin?: Origin;
387388
diffType?: string;
388389
gitContext?: GitContext;
389390
sharingEnabled?: boolean;
@@ -1566,10 +1567,10 @@ const ReviewApp: React.FC = () => {
15661567
? `Your approval was submitted to ${platformLabel}.`
15671568
: `Your feedback was submitted to ${platformLabel}.`
15681569
: submitted === 'approved'
1569-
? `${origin === 'claude-code' ? 'Claude Code' : origin === 'opencode' ? 'OpenCode' : origin === 'pi' ? 'Pi' : 'Your agent'} will proceed with the changes.`
1570-
: `${origin === 'claude-code' ? 'Claude Code' : origin === 'opencode' ? 'OpenCode' : origin === 'pi' ? 'Pi' : 'Your agent'} will address your review feedback.`
1570+
? `${getAgentName(origin)} will proceed with the changes.`
1571+
: `${getAgentName(origin)} will address your review feedback.`
15711572
}
1572-
agentLabel={origin === 'claude-code' ? 'Claude Code' : origin === 'opencode' ? 'OpenCode' : origin === 'pi' ? 'Pi' : 'Your agent'}
1573+
agentLabel={getAgentName(origin)}
15731574
/>
15741575

15751576
{/* Update notification */}

packages/server/annotate.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import { isRemoteSession, getServerPort } from "./remote";
1515
import { getRepoInfo } from "./repo";
16+
import type { Origin } from "@plannotator/shared/agents";
1617
import { handleImage, handleUpload, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, handleFavicon } from "./shared-handlers";
1718
import { handleDoc, handleFileBrowserFiles } from "./reference-handlers";
1819
import { contentHash, deleteDraft } from "./draft";
@@ -35,7 +36,7 @@ export interface AnnotateServerOptions {
3536
/** HTML content to serve for the UI */
3637
htmlContent: string;
3738
/** Origin identifier for UI customization */
38-
origin?: "opencode" | "claude-code" | "pi" | "codex";
39+
origin?: Origin;
3940
/** UI mode: "annotate" for files, "annotate-last" for last agent message, "annotate-folder" for folders */
4041
mode?: "annotate" | "annotate-last" | "annotate-folder";
4142
/** Folder path when annotating a directory (used as projectRoot for file browser) */

packages/server/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* PLANNOTATOR_ORIGIN - Origin identifier ("claude-code" or "opencode")
1010
*/
1111

12+
import type { Origin } from "@plannotator/shared/agents";
1213
import { resolve } from "path";
1314
import { isRemoteSession, getServerPort } from "./remote";
1415
import { openEditorDiff } from "./ide";
@@ -58,7 +59,7 @@ export interface ServerOptions {
5859
/** The plan markdown content */
5960
plan: string;
6061
/** Origin identifier (e.g., "claude-code", "opencode") */
61-
origin: string;
62+
origin: Origin;
6263
/** HTML content to serve for the UI */
6364
htmlContent: string;
6465
/** Current permission mode to preserve (Claude Code only) */

packages/server/review.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111

1212
import { isRemoteSession, getServerPort } from "./remote";
13+
import type { Origin } from "@plannotator/shared/agents";
1314
import { type DiffType, type GitContext, runGitDiff, getFileContentsForDiff, gitAddFile, gitResetFile, parseWorktreeDiffType, validateFilePath } from "./git";
1415
import { getRepoInfo } from "./repo";
1516
import { handleImage, handleUpload, handleAgents, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, handleFavicon, type OpencodeClient } from "./shared-handlers";
@@ -39,7 +40,7 @@ export interface ReviewServerOptions {
3940
/** HTML content to serve for the UI */
4041
htmlContent: string;
4142
/** Origin identifier for UI customization */
42-
origin?: "opencode" | "claude-code" | "pi";
43+
origin?: Origin;
4344
/** Current diff type being displayed */
4445
diffType?: DiffType;
4546
/** Git context with branch info and available diff options */

packages/shared/agents.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Centralized agent configuration — single source of truth for all supported agents.
3+
*
4+
* To add a new agent:
5+
* 1. Add an entry to AGENT_CONFIG below (origin key, display name, badge CSS classes)
6+
* 2. If detection is via environment variable, add it to the detection chain
7+
* in apps/hook/server/index.ts (detectedOrigin constant)
8+
* 3. That's it — all UI components read from this config automatically
9+
*/
10+
11+
export const AGENT_CONFIG = {
12+
'claude-code': { name: 'Claude Code', badge: 'bg-orange-500/15 text-orange-400' },
13+
'opencode': { name: 'OpenCode', badge: 'bg-emerald-500/15 text-emerald-400' },
14+
'copilot-cli': { name: 'GitHub Copilot', badge: 'bg-blue-500/15 text-blue-400' },
15+
'pi': { name: 'Pi', badge: 'bg-violet-500/15 text-violet-400' },
16+
'codex': { name: 'Codex', badge: 'bg-purple-500/15 text-purple-400' },
17+
} as const;
18+
19+
/** All recognized origin values. */
20+
export type Origin = keyof typeof AGENT_CONFIG;
21+
22+
/** Resolve an origin to a human-readable agent name. */
23+
export function getAgentName(origin: Origin | null | undefined): string {
24+
if (origin && origin in AGENT_CONFIG) return AGENT_CONFIG[origin as Origin].name;
25+
return 'Coding Agent';
26+
}
27+
28+
/** Resolve an origin to Tailwind badge classes. */
29+
export function getAgentBadge(origin: Origin | null | undefined): string {
30+
if (origin && origin in AGENT_CONFIG) return AGENT_CONFIG[origin as Origin].badge;
31+
return 'bg-zinc-500/20 text-zinc-400';
32+
}

packages/shared/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "0.0.1",
44
"private": true,
55
"exports": {
6+
"./agents": "./agents.ts",
67
"./compress": "./compress.ts",
78
"./crypto": "./crypto.ts",
89
"./feedback-templates": "./feedback-templates.ts",

0 commit comments

Comments
 (0)