Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Build outputs
dist/
node_modules/

# Turbo cache (monorepo)
.turbo/

# Environment files
.env
.env.local
.env.production
.env.staging
.env.development
.env.bak
*.env

# OS files
.DS_Store
Thumbs.db

# IDE files
.vscode/
.idea/
*.swp
*.swo

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Coverage
coverage/

# Cache directories
.cache/
.npm/
.eslintcache
*.tsbuildinfo

# Temporary folders
tmp/
temp/

# ElizaOS specific
.eliza/
.elizadb/
cache/
data/
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,4 @@
}
}
}
}

}
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions "Update package description to use lowercase elizaos", but the actual diff for package.json only shows removal of a trailing blank line. There don't appear to be any branding changes in the description field. Either the description field was already using lowercase "elizaos", or the PR description is inaccurate.

Copilot uses AI. Check for mistakes.
53 changes: 41 additions & 12 deletions src/actions/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,52 @@ import { getActiveRepository, setActiveRepository } from '../utils/state';
import { isValidBranchName } from '../utils/security';

/**
* Extracts branch name from message text
* Extracts branch name from message text.
*
* Strategy: find text after a known command keyword, then scan words
* left-to-right skipping English filler words. Returns the first token
* that looks like a valid git branch name.
*
* This avoids fragile regex with dynamic construction (new RegExp + template
* strings) and greedy-match ordering issues that silently mangle branch names.
*/
function extractBranchName(text: string): string | null {
// "checkout X", "switch to X", "branch X"
const checkoutMatch = text.match(
/(?:checkout|switch(?:\s+to)?|branch)\s+["\']?([a-zA-Z0-9._\/-]+)["\']?/i
// English filler words that appear between commands and branch names
// e.g. "checkout THE odi-dev", "switch to A feature/login"
const FILLER_WORDS = new Set(['the', 'a', 'an', 'to', 'into', 'on', 'for']);
// Words that signal we've gone past the branch name in the sentence
const STOP_WORDS = new Set(['branch', 'and', 'then', 'first', 'now', 'from', 'called', 'named']);

// 1. Quoted branch names are unambiguous -- highest priority
const quotedMatch = text.match(/(?:checkout|switch|branch|create|new)\b.*?["'`]([a-zA-Z0-9._\/-]+)["'`]/i);
if (quotedMatch?.[1]) return quotedMatch[1];

// 2. Find text after a command keyword, scan for first branch-like token
const afterCommand = text.match(
/(?:checkout|switch(?:\s+to)?|(?:create|new)\s+(?:branch\s+)?|branch)\s+(.*)/i
);
if (checkoutMatch) {
return checkoutMatch[1];
if (afterCommand?.[1]) {
for (const raw of afterCommand[1].trim().split(/\s+/)) {
const word = raw.replace(/^["'`]+|["'`]+$/g, '');
if (!word) continue;
// Stop scanning at meta words (not part of the branch name)
if (STOP_WORDS.has(word.toLowerCase())) break;
// Skip filler words -- but NOT if they contain branch-name chars (- / .)
// so "the-great-refactor" is kept, but standalone "the" is skipped
if (FILLER_WORDS.has(word.toLowerCase()) && !/[-/.]/.test(word)) continue;
// Must be valid git branch characters
if (/^[a-zA-Z0-9._\/-]+$/.test(word)) return word;
break; // invalid chars = stop scanning
Comment on lines +34 to +57
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Parser can incorrectly choose new as branch name.

In phrases like “create a new branch called feature/user-auth” (Line 249), the current scan may return new at Line 56 before reaching the real branch token.

💡 Proposed fix
-  const FILLER_WORDS = new Set(['the', 'a', 'an', 'to', 'into', 'on', 'for']);
-  // Words that signal we've gone past the branch name in the sentence
-  const STOP_WORDS = new Set(['branch', 'and', 'then', 'first', 'now', 'from', 'called', 'named']);
+  const FILLER_WORDS = new Set([
+    'the', 'a', 'an', 'to', 'into', 'on', 'for',
+    'new', 'branch', 'called', 'named',
+  ]);
+  // Words that usually indicate a new clause, not a branch token
+  const STOP_WORDS = new Set(['and', 'then', 'first', 'now', 'from']);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const FILLER_WORDS = new Set(['the', 'a', 'an', 'to', 'into', 'on', 'for']);
// Words that signal we've gone past the branch name in the sentence
const STOP_WORDS = new Set(['branch', 'and', 'then', 'first', 'now', 'from', 'called', 'named']);
// 1. Quoted branch names are unambiguous -- highest priority
const quotedMatch = text.match(/(?:checkout|switch|branch|create|new)\b.*?["'`]([a-zA-Z0-9._\/-]+)["'`]/i);
if (quotedMatch?.[1]) return quotedMatch[1];
// 2. Find text after a command keyword, scan for first branch-like token
const afterCommand = text.match(
/(?:checkout|switch(?:\s+to)?|(?:create|new)\s+(?:branch\s+)?|branch)\s+(.*)/i
);
if (checkoutMatch) {
return checkoutMatch[1];
if (afterCommand?.[1]) {
for (const raw of afterCommand[1].trim().split(/\s+/)) {
const word = raw.replace(/^["'`]+|["'`]+$/g, '');
if (!word) continue;
// Stop scanning at meta words (not part of the branch name)
if (STOP_WORDS.has(word.toLowerCase())) break;
// Skip filler words -- but NOT if they contain branch-name chars (- / .)
// so "the-great-refactor" is kept, but standalone "the" is skipped
if (FILLER_WORDS.has(word.toLowerCase()) && !/[-/.]/.test(word)) continue;
// Must be valid git branch characters
if (/^[a-zA-Z0-9._\/-]+$/.test(word)) return word;
break; // invalid chars = stop scanning
const FILLER_WORDS = new Set([
'the', 'a', 'an', 'to', 'into', 'on', 'for',
'new', 'branch', 'called', 'named',
]);
// Words that usually indicate a new clause, not a branch token
const STOP_WORDS = new Set(['and', 'then', 'first', 'now', 'from']);
// 1. Quoted branch names are unambiguous -- highest priority
const quotedMatch = text.match(/(?:checkout|switch|branch|create|new)\b.*?["'`]([a-zA-Z0-9._\/-]+)["'`]/i);
if (quotedMatch?.[1]) return quotedMatch[1];
// 2. Find text after a command keyword, scan for first branch-like token
const afterCommand = text.match(
/(?:checkout|switch(?:\s+to)?|(?:create|new)\s+(?:branch\s+)?|branch)\s+(.*)/i
);
if (afterCommand?.[1]) {
for (const raw of afterCommand[1].trim().split(/\s+/)) {
const word = raw.replace(/^["'`]+|["'`]+$/g, '');
if (!word) continue;
// Stop scanning at meta words (not part of the branch name)
if (STOP_WORDS.has(word.toLowerCase())) break;
// Skip filler words -- but NOT if they contain branch-name chars (- / .)
// so "the-great-refactor" is kept, but standalone "the" is skipped
if (FILLER_WORDS.has(word.toLowerCase()) && !/[-/.]/.test(word)) continue;
// Must be valid git branch characters
if (/^[a-zA-Z0-9._\/-]+$/.test(word)) return word;
break; // invalid chars = stop scanning
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/checkout.ts` around lines 34 - 57, The parser can pick up the
literal token "new" as a branch name when scanning after a command; update the
filler-word logic so "new" is treated as a filler (add "new" to FILLER_WORDS) so
the loop inside afterCommand (the code that trims/splits afterCommand[1] and
checks FILLER_WORDS/STOP_WORDS) will skip "new" and continue to the real branch
token; keep the existing exception that preserves tokens containing branch chars
(the /-.) so hyphenated names still pass.

}
}

// "create X branch", "new branch X"
const createMatch = text.match(
/(?:create|new)\s+(?:branch\s+)?["\']?([a-zA-Z0-9._\/-]+)["\']?(?:\s+branch)?/i
);
if (createMatch) {
return createMatch[1];
// 3. Reverse pattern: "odi-dev branch" (branch name BEFORE the keyword)
const reverseMatch = text.match(/\b([a-zA-Z0-9._\/-]+)\s+branch\b/i);
if (reverseMatch?.[1]) {
const w = reverseMatch[1].toLowerCase();
if (!FILLER_WORDS.has(w) && !STOP_WORDS.has(w) && w !== 'create' && w !== 'new') {
return reverseMatch[1];
}
}

return null;
Expand Down
17 changes: 11 additions & 6 deletions src/actions/clone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,25 @@ function extractRepoUrl(text: string): string | null {
}

/**
* Extracts branch name from message text
* Extracts branch name from message text.
*
* Uses conservative patterns to avoid false positives from common English
* (e.g. "clone repo on the server" should NOT extract "the" as a branch).
*/
function extractBranch(text: string): string | undefined {
// "branch X", "on branch X", "checkout X branch"
const FILLER_WORDS = new Set(['the', 'a', 'an', 'to', 'into', 'for', 'this', 'that']);

// "on branch X", "branch X", "checkout X"
const branchMatch = text.match(
/(?:branch|checkout|switch to)\s+["\']?([a-zA-Z0-9._\/-]+)["\']?/i
/(?:branch|checkout|switch to)\s+["']?([a-zA-Z0-9._\/-]+)["']?/i
);
if (branchMatch) {
if (branchMatch?.[1] && !FILLER_WORDS.has(branchMatch[1].toLowerCase())) {
return branchMatch[1];
}

// "on X", at the end
// "on X" at the end of the message -- only if X is not a filler word
const onMatch = text.match(/\bon\s+([a-zA-Z0-9._\/-]+)\s*$/i);
if (onMatch) {
if (onMatch?.[1] && !FILLER_WORDS.has(onMatch[1].toLowerCase())) {
return onMatch[1];
Comment on lines +55 to 68
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

extractBranch misses common on the <name> branch input.

With the current patterns, Line 254’s example phrasing (“on the canary branch”) won’t extract canary, so clone may silently use default branch.

💡 Proposed fix
 function extractBranch(text: string): string | undefined {
   const FILLER_WORDS = new Set(['the', 'a', 'an', 'to', 'into', 'for', 'this', 'that']);

+  // "on the canary branch", "from develop branch"
+  const namedBranch = text.match(/\b(?:on|from|in)\s+(?:the\s+)?([a-zA-Z0-9._\/-]+)\s+branch\b/i);
+  if (namedBranch?.[1] && !FILLER_WORDS.has(namedBranch[1].toLowerCase())) {
+    return namedBranch[1];
+  }
+
   // "on branch X", "branch X", "checkout X"
   const branchMatch = text.match(
     /(?:branch|checkout|switch to)\s+["']?([a-zA-Z0-9._\/-]+)["']?/i
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const FILLER_WORDS = new Set(['the', 'a', 'an', 'to', 'into', 'for', 'this', 'that']);
// "on branch X", "branch X", "checkout X"
const branchMatch = text.match(
/(?:branch|checkout|switch to)\s+["\']?([a-zA-Z0-9._\/-]+)["\']?/i
/(?:branch|checkout|switch to)\s+["']?([a-zA-Z0-9._\/-]+)["']?/i
);
if (branchMatch) {
if (branchMatch?.[1] && !FILLER_WORDS.has(branchMatch[1].toLowerCase())) {
return branchMatch[1];
}
// "on X", at the end
// "on X" at the end of the message -- only if X is not a filler word
const onMatch = text.match(/\bon\s+([a-zA-Z0-9._\/-]+)\s*$/i);
if (onMatch) {
if (onMatch?.[1] && !FILLER_WORDS.has(onMatch[1].toLowerCase())) {
return onMatch[1];
const FILLER_WORDS = new Set(['the', 'a', 'an', 'to', 'into', 'for', 'this', 'that']);
// "on the canary branch", "from develop branch"
const namedBranch = text.match(/\b(?:on|from|in)\s+(?:the\s+)?([a-zA-Z0-9._\/-]+)\s+branch\b/i);
if (namedBranch?.[1] && !FILLER_WORDS.has(namedBranch[1].toLowerCase())) {
return namedBranch[1];
}
// "on branch X", "branch X", "checkout X"
const branchMatch = text.match(
/(?:branch|checkout|switch to)\s+["']?([a-zA-Z0-9._\/-]+)["']?/i
);
if (branchMatch?.[1] && !FILLER_WORDS.has(branchMatch[1].toLowerCase())) {
return branchMatch[1];
}
// "on X" at the end of the message -- only if X is not a filler word
const onMatch = text.match(/\bon\s+([a-zA-Z0-9._\/-]+)\s*$/i);
if (onMatch?.[1] && !FILLER_WORDS.has(onMatch[1].toLowerCase())) {
return onMatch[1];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/clone.ts` around lines 55 - 68, extractBranch's patterns
(branchMatch/onMatch) don't handle inputs like "on the canary branch" so it
fails to extract the branch name; update the matching logic (e.g., in
extractBranch) to add a pattern that allows optional filler words like
"the"/"a"/"an" between "on" and the branch name and an optional trailing
"branch" token (capture the actual name in a group), then validate the captured
name against FILLER_WORDS before returning (keep existing branchMatch and
onMatch checks but add this additional regex branch to handle "on the <name>
branch" phrasing).

}

Expand Down
22 changes: 16 additions & 6 deletions src/actions/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,24 @@ function extractCount(text: string): number | undefined {
}

/**
* Extracts branch name from message text
* Extracts branch name from message text.
*
* Only matches when the word "branch" appears explicitly, to avoid
* false positives from common English prepositions ("in the", "on the").
*/
function extractBranch(text: string): string | undefined {
// Look for patterns like "on branch main", "from develop", "in feature/xyz"
const branchMatch = text.match(/(?:on|from|in|branch)\s+(?:branch\s+)?([a-zA-Z0-9/_-]+)/i);
if (branchMatch) {
return branchMatch[1];
const FILLER_WORDS = new Set(['the', 'a', 'an', 'to', 'into', 'on', 'for', 'this', 'that']);

// "on branch main", "from branch develop", "in branch feature/xyz"
const withBranchKeyword = text.match(/(?:on|from|in)\s+branch\s+([a-zA-Z0-9/_.-]+)/i);
if (withBranchKeyword?.[1]) return withBranchKeyword[1];

// "branch main", "branch develop"
const directBranch = text.match(/\bbranch\s+([a-zA-Z0-9/_.-]+)/i);
if (directBranch?.[1] && !FILLER_WORDS.has(directBranch[1].toLowerCase())) {
return directBranch[1];
}
Comment on lines +46 to 53
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Branch parser does not handle from <name> branch phrasing.

The new patterns miss common input like Line 279 (“from develop branch”), so branch stays undefined and history may be fetched from the wrong branch.

💡 Proposed fix
 function extractBranch(text: string): string | undefined {
   const FILLER_WORDS = new Set(['the', 'a', 'an', 'to', 'into', 'on', 'for', 'this', 'that']);

+  // "from develop branch", "on main branch", "in release/v1 branch"
+  const preposedBranch = text.match(/(?:on|from|in)\s+([a-zA-Z0-9/_.-]+)\s+branch\b/i);
+  if (preposedBranch?.[1] && !FILLER_WORDS.has(preposedBranch[1].toLowerCase())) {
+    return preposedBranch[1];
+  }
+
   // "on branch main", "from branch develop", "in branch feature/xyz"
   const withBranchKeyword = text.match(/(?:on|from|in)\s+branch\s+([a-zA-Z0-9/_.-]+)/i);
   if (withBranchKeyword?.[1]) return withBranchKeyword[1];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/log.ts` around lines 46 - 53, The branch parser misses phrases
like "from develop branch"; update the extraction logic in the block using
withBranchKeyword and directBranch so it also matches the reversed order pattern
(e.g., "from <branch> branch")—add or extend a regex to capture
from\s+([a-zA-Z0-9/_.-]+)\s+branch, then return that capture if present and not
in FILLER_WORDS; keep checks tied to the existing symbols withBranchKeyword,
directBranch and FILLER_WORDS so behavior and validation remain consistent.


return undefined;
}

Expand Down Expand Up @@ -98,7 +108,7 @@ export const gitLogAction: Action = {
message: Memory,
_state: State | undefined
): Promise<boolean> => {
const text = message.content.text.toLowerCase();
const text = (message.content.text || '').toLowerCase();

// Check for git log related keywords
const hasLogKeywords = (
Expand Down
3 changes: 2 additions & 1 deletion src/actions/pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export const gitPullAction: Action = {

// Extract remote and branch from text
const remoteMatch = text.match(/from\s+(\w+)(?:\s+remote)?/i);
const branchMatch = text.match(/(?:branch\s+)?([a-zA-Z0-9._\/-]+)(?:\s+branch)?/i);
// Require "branch" keyword to avoid capturing random words from the message
const branchMatch = text.match(/\bbranch\s+([a-zA-Z0-9._\/-]+)/i);

const remote = options?.remote || remoteMatch?.[1];
const branch = options?.branch || branchMatch?.[1];
Comment on lines +68 to 72
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Branch extraction now misses "<name> branch" phrasing.

Line 69 only captures branch <name>, so inputs like “pull from origin develop branch” won’t set branch and may execute against an unintended ref.

💡 Proposed fix
-      const branchMatch = text.match(/\bbranch\s+([a-zA-Z0-9._\/-]+)/i);
+      const branchMatch = text.match(
+        /\b(?:branch\s+([a-zA-Z0-9._\/-]+)|([a-zA-Z0-9._\/-]+)\s+branch)\b/i
+      );

       const remote = options?.remote || remoteMatch?.[1];
-      const branch = options?.branch || branchMatch?.[1];
+      const branch = options?.branch || branchMatch?.[1] || branchMatch?.[2];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/pull.ts` around lines 68 - 72, The branch extraction currently
only matches "branch <name>" via branchMatch =
text.match(/\bbranch\s+([a-zA-Z0-9._\/-]+)/i), which misses phrases like "<name>
branch"; update the parsing in src/actions/pull.ts to also detect and prefer a
trailing "branch" form (for example by trying a second regex for
/([a-zA-Z0-9._\/-]+)\s+branch\b/i or combining both patterns) and set branch =
options?.branch || branchMatch?.[1] (or the alternate match group) so inputs
like "pull from origin develop branch" correctly populate branch instead of
falling back to an unintended ref. Ensure you still honor options?.branch and
remote/remoteMatch logic.

Expand Down
3 changes: 2 additions & 1 deletion src/actions/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export const gitPushAction: Action = {

// Extract remote and branch from text
const remoteMatch = text.match(/(?:to\s+)?(\w+)\s+remote/i);
const branchMatch = text.match(/(?:branch\s+)?([a-zA-Z0-9._\/-]+)(?:\s+branch)?/i);
// Require "branch" keyword to avoid capturing random words from the message
const branchMatch = text.match(/\bbranch\s+([a-zA-Z0-9._\/-]+)/i);

const remote = options?.remote || remoteMatch?.[1] || 'origin';
const branch = options?.branch || branchMatch?.[1];
Expand Down
2 changes: 1 addition & 1 deletion src/actions/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export const gitStatusAction: Action = {
message: Memory,
_state: State | undefined
): Promise<boolean> => {
const text = message.content.text.toLowerCase();
const text = (message.content.text || '').toLowerCase();

// Check for git status related keywords
const hasStatusKeywords = (
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export {
parseGitUrl,
isSshUrl,
isHttpsUrl,

// Execution utilities
redactUrl,
redactArgs,
Expand All @@ -90,7 +90,7 @@ export {
parseLogOutput,
parseBranchOutput,
parseRemoteOutput,

// State persistence utilities
getGitStateCacheKey,
createEmptyGitState,
Expand Down
4 changes: 3 additions & 1 deletion src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { GitService } from './services/git';
// Providers
import {
gitInstructionsProvider,
gitSettingsProvider,
gitWorkingCopiesProvider,
gitStatusProvider,
gitBranchesProvider,
Expand Down Expand Up @@ -96,7 +97,7 @@ export const gitPlugin: Plugin = {
logger.info('[Git] Initializing plugin-git...');

// Log configuration (without sensitive values)
const configKeys = Object.keys(config).filter(k =>
const configKeys = Object.keys(config).filter(k =>
!k.includes('TOKEN') && !k.includes('PASSWORD')
);
if (configKeys.length > 0) {
Expand Down Expand Up @@ -139,6 +140,7 @@ export const gitPlugin: Plugin = {
// Providers - inject git state into agent context
providers: [
gitInstructionsProvider, // Comprehensive usage instructions (highest priority)
gitSettingsProvider, // Current settings (non-sensitive)
gitWorkingCopiesProvider, // Shows managed repositories
gitStatusProvider, // Shows current repo status
gitLogProvider, // Shows recent commit history
Expand Down
1 change: 1 addition & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

export { gitInstructionsProvider } from './instructions';
export { gitSettingsProvider } from './settings';
export { gitWorkingCopiesProvider } from './workingCopies';
export { gitStatusProvider } from './status';
export { gitBranchesProvider } from './branches';
Expand Down
124 changes: 124 additions & 0 deletions src/providers/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* GIT_SETTINGS Provider
*
* Exposes current git plugin settings (non-sensitive only).
* Helps users understand their current configuration.
*/

import type {
IAgentRuntime,
Memory,
Provider,
ProviderResult,
State,
} from '@elizaos/core';
import { GitService } from '../services/git';
import { getActiveRepository, getWorkingCopies } from '../utils/state';

export const gitSettingsProvider: Provider = {
name: 'GIT_SETTINGS',
description: 'Current git plugin configuration and settings (non-sensitive)',

get: async (
runtime: IAgentRuntime,
message: Memory,
_state?: State
): Promise<ProviderResult> => {
const gitService = runtime.getService<GitService>('git');

// Get non-sensitive settings
const allowedPath = runtime.getSetting('GIT_ALLOWED_PATH') || 'not restricted';
const authorName = runtime.getSetting('GIT_AUTHOR_NAME') || 'not set';
const authorEmail = runtime.getSetting('GIT_AUTHOR_EMAIL') || 'not set';
const cloneTimeout = runtime.getSetting('GIT_CLONE_TIMEOUT') || '300';
const workingCopiesDir = runtime.getSetting('GIT_WORKING_COPIES_DIR') || '~/.eliza/git';

// Check auth status without exposing credentials
const hasToken = !!runtime.getSetting('GIT_TOKEN');
const hasUserPass = !!runtime.getSetting('GIT_USERNAME') && !!runtime.getSetting('GIT_PASSWORD');
const authConfigured = hasToken || hasUserPass;
const authMethod = hasToken ? 'token' : hasUserPass ? 'username/password' : 'none';

// Get git CLI status
let gitVersion = 'unknown';
let gitInstalled = false;
if (gitService) {
gitInstalled = await gitService.isGitInstalled();
if (gitInstalled) {
gitVersion = (await gitService.getGitVersion()) || 'unknown';
}
}

// Get repository state
const activeRepo = await getActiveRepository(runtime, message.roomId);
const workingCopies = await getWorkingCopies(runtime, message.roomId);

// Check for workspace integration
const workspaceService = runtime.getService('workspace');
const usingWorkspace = !!workspaceService;

const settings = {
gitInstalled,
gitVersion,
allowedPath,
workingCopiesDir,
cloneTimeoutSeconds: parseInt(String(cloneTimeout)),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard timeout parsing to avoid NaN in provider output.

At Line 65, invalid GIT_CLONE_TIMEOUT values become NaN, which then surfaces in Line 97 as NaNs.

💡 Proposed fix
-      cloneTimeoutSeconds: parseInt(String(cloneTimeout)),
+      cloneTimeoutSeconds: (() => {
+        const parsed = Number.parseInt(String(cloneTimeout), 10);
+        return Number.isFinite(parsed) && parsed > 0 ? parsed : 300;
+      })(),

Also applies to: 97-97

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/providers/settings.ts` at line 65, The parsed GIT_CLONE_TIMEOUT may
produce NaN because cloneTimeoutSeconds is set via
parseInt(String(cloneTimeout)); update the parsing in src/providers/settings.ts
so cloneTimeoutSeconds (and any other timeout parsing) validate the result and
fall back to a safe default (e.g., 0 or a configured DEFAULT_CLONE_TIMEOUT) when
parseInt yields NaN; locate the cloneTimeout variable and the assignment to
cloneTimeoutSeconds and replace the direct parseInt with guarded logic that
checks Number.isFinite/Number.isNaN (or uses Number.isInteger) and assigns the
fallback when invalid to prevent NaNs from propagating (affects the value used
later at line where NaNs appear).

author: {
name: authorName,
email: authorEmail,
},
authentication: {
configured: authConfigured,
method: authMethod,
},
state: {
activeRepo: activeRepo
? {
path: activeRepo.path,
branch: activeRepo.branch,
remote: activeRepo.remote,
}
: null,
workingCopiesCount: workingCopies.length,
},
usingWorkspace,
};

const lines = [
'## Git Plugin Settings',
'',
`**Git CLI:** ${settings.gitInstalled ? `Installed (${settings.gitVersion})` : 'Not installed'}`,
'',
'**Directories:**',
`- Allowed Path: \`${settings.allowedPath}\``,
`- Working Copies: \`${settings.workingCopiesDir}\``,
`- Using Workspace: ${settings.usingWorkspace ? 'Yes' : 'No'}`,
'',
'**Clone Timeout:** ' + settings.cloneTimeoutSeconds + 's',
'',
'**Author:**',
`- Name: ${settings.author.name}`,
`- Email: ${settings.author.email}`,
'',
'**Authentication:**',
`- Configured: ${settings.authentication.configured ? 'Yes' : 'No'}`,
`- Method: ${settings.authentication.method}`,
'',
'**State:**',
`- Working Copies: ${settings.state.workingCopiesCount}`,
];

if (settings.state.activeRepo) {
lines.push(`- Active Repo: \`${settings.state.activeRepo.path}\``);
lines.push(`- Branch: ${settings.state.activeRepo.branch}`);
} else {
lines.push('- Active Repo: None');
}

return {
text: lines.join('\n'),
values: settings,
data: { settings },
};
},
};
Loading