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
17 changes: 16 additions & 1 deletion apps/desktop/src/main/ai/agent/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@ async function runSingleSession(
oauthTokenFilePath: baseSession.oauthTokenFilePath,
});
} catch (error) {
// Log the error to task_logs.json before cleanup
if (logWriter) {
const errorMsg = error instanceof Error ? error.message : String(error);
logWriter.logText(errorMsg, phase, 'error');
}
// Ensure log cleanup happens on failure
if (logWriter && !skipPhaseLogging) logWriter.endPhase(phase, false);
if (logWriter) logWriter.setSubtask(undefined);
Expand Down Expand Up @@ -430,6 +435,10 @@ async function run(): Promise<void> {
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
postError(`Agent session failed: ${message}`);
// Write to task_logs.json so the UI shows the error
if (logWriter) {
logWriter.logText(`Agent session failed: ${message}`, undefined, 'error');
}
} finally {
// Cleanup MCP clients
if (mcpClients.length > 0) {
Expand Down Expand Up @@ -668,7 +677,13 @@ async function runBuildOrchestrator(
});

orchestrator.on('error', (error: Error, phase: string) => {
postLog(`Error in ${phase} phase: ${error.message}`);
const errorMsg = `Error in ${phase} phase: ${error.message}`;
postLog(errorMsg);
postError(errorMsg);
// Also write to task_logs.json so the UI shows the error detail
if (logWriter) {
logWriter.logText(error.message, phase as Phase, 'error');
}
Comment on lines 679 to +686
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Map orchestrator phases before logging them.

The cast on Line 685 is unsafe: BuildOrchestrator emits phases like qa_review and qa_fixing, but TaskLogWriter.logText() only understands planning / coding / qa / spec. Those QA failures will hit toLogPhase()'s default branch and be written under coding. Reuse mapExecutionPhaseToPhase() here instead of casting.

🩹 Suggested fix
   if (logWriter) {
-    logWriter.logText(error.message, phase as Phase, 'error');
+    const logPhase = mapExecutionPhaseToPhase(phase as ExecutionPhase);
+    logWriter.logText(error.message, logPhase, 'error');
   }
📝 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
orchestrator.on('error', (error: Error, phase: string) => {
postLog(`Error in ${phase} phase: ${error.message}`);
const errorMsg = `Error in ${phase} phase: ${error.message}`;
postLog(errorMsg);
postError(errorMsg);
// Also write to task_logs.json so the UI shows the error detail
if (logWriter) {
logWriter.logText(error.message, phase as Phase, 'error');
}
orchestrator.on('error', (error: Error, phase: string) => {
const errorMsg = `Error in ${phase} phase: ${error.message}`;
postLog(errorMsg);
postError(errorMsg);
// Also write to task_logs.json so the UI shows the error detail
if (logWriter) {
const logPhase = mapExecutionPhaseToPhase(phase as ExecutionPhase);
logWriter.logText(error.message, logPhase, 'error');
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/ai/agent/worker.ts` around lines 679 - 686, The error
handler is casting the orchestrator phase into a TaskLogWriter Phase unsafely;
instead map the BuildOrchestrator phase using the existing
mapExecutionPhaseToPhase (or toLogPhase) helper and pass that mappedPhase into
logWriter.logText; update the orchestrator.on('error', ...) block to compute
mappedPhase = mapExecutionPhaseToPhase(phase) and call
logWriter.logText(error.message, mappedPhase, 'error') so qa_review/qa_fixing
map correctly.

});

const outcome = await orchestrator.run();
Expand Down
19 changes: 19 additions & 0 deletions apps/desktop/src/main/ai/config/agent-configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,25 @@ export const MEMORY_MCP_TOOLS = [
/** @deprecated Use MEMORY_MCP_TOOLS instead */
export const GRAPHITI_MCP_TOOLS = MEMORY_MCP_TOOLS;

/** JIRA MCP tools for issue tracking (when JIRA is configured) */
export const JIRA_TOOLS = [
'mcp__jira__list_projects',
'mcp__jira__get_issue',
'mcp__jira__create_issue',
'mcp__jira__update_issue',
'mcp__jira__search_issues',
'mcp__jira__add_comment',
'mcp__jira__get_transitions',
'mcp__jira__transition_issue',
] as const;

/** Vault MCP tools for external vault access */
export const VAULT_TOOLS = [
'mcp__vault__read_file',
'mcp__vault__list_directory',
'mcp__vault__search_files',
] as const;

Comment on lines +91 to +109
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add jira/vault to MCP server name normalization map.

You introduced new MCP integrations, but mapMcpServerName() cannot currently resolve jira or vault for agentMcpAdd/agentMcpRemove, so those overrides won’t work for the new servers.

🔧 Proposed fix
 const MCP_SERVER_NAME_MAP: Record<string, string> = {
   context7: 'context7',
   'graphiti-memory': 'memory',
   graphiti: 'memory',
   memory: 'memory',
   linear: 'linear',
+  jira: 'jira',
+  vault: 'vault',
   electron: 'electron',
   puppeteer: 'puppeteer',
   'auto-claude': 'auto-claude',
 };
📝 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
/** JIRA MCP tools for issue tracking (when JIRA is configured) */
export const JIRA_TOOLS = [
'mcp__jira__list_projects',
'mcp__jira__get_issue',
'mcp__jira__create_issue',
'mcp__jira__update_issue',
'mcp__jira__search_issues',
'mcp__jira__add_comment',
'mcp__jira__get_transitions',
'mcp__jira__transition_issue',
] as const;
/** Vault MCP tools for external vault access */
export const VAULT_TOOLS = [
'mcp__vault__read_file',
'mcp__vault__list_directory',
'mcp__vault__search_files',
] as const;
const MCP_SERVER_NAME_MAP: Record<string, string> = {
context7: 'context7',
'graphiti-memory': 'memory',
graphiti: 'memory',
memory: 'memory',
linear: 'linear',
jira: 'jira',
vault: 'vault',
electron: 'electron',
puppeteer: 'puppeteer',
'auto-claude': 'auto-claude',
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/ai/config/agent-configs.ts` around lines 91 - 109,
mapMcpServerName() currently doesn't normalize the new MCP integrations, so
agentMcpAdd/agentMcpRemove won't match the JIRA_TOOLS and VAULT_TOOLS servers;
update the normalization map inside mapMcpServerName to include entries for
"jira" and "vault" (mapping whatever canonical server keys your codebase uses
for those integrations to the same normalized form) so that calls involving
JIRA_TOOLS and VAULT_TOOLS resolve correctly when agentMcpAdd or agentMcpRemove
is invoked.

// =============================================================================
// Browser Automation MCP Tools (QA agents only)
// =============================================================================
Expand Down
27 changes: 25 additions & 2 deletions apps/desktop/src/main/ai/logging/task-log-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,34 @@ export class TaskLogWriter {
this.flushPendingText();
break;

case 'error':
case 'error': {
this.flushPendingText();
this.addEntry(logPhase, 'error', event.error.message);
// Extract meaningful error message - AI SDK sometimes just sends 'error' as the message
const err = event.error;
let errorContent = 'Unknown error';
if (err) {
const msg = err.message || '';
const code = err.code || '';
const cause = err.cause;
// If message is just 'error' (unhelpful), try to extract from cause or code
if (msg && msg !== 'error') {
errorContent = msg;
} else if (cause instanceof Error) {
errorContent = cause.message || String(cause);
} else if (cause && typeof cause === 'object') {
errorContent = JSON.stringify(cause).slice(0, 500);
} else if (cause) {
errorContent = String(cause);
} else if (code && code !== 'error') {
errorContent = `Error code: ${code}`;
} else {
errorContent = `Error: ${JSON.stringify(err).slice(0, 500)}`;
Comment on lines +160 to +169
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and get its structure
fd task-log-writer.ts

Repository: AndyMik90/Aperant

Length of output: 112


🏁 Script executed:

# Read the full file to understand the processEvent context
cat -n apps/desktop/src/main/ai/logging/task-log-writer.ts | head -200

Repository: AndyMik90/Aperant

Length of output: 7978


🏁 Script executed:

# Search for JSON.stringify usage patterns in similar logging/error contexts
rg "JSON.stringify" apps/desktop/src/main/ -B 2 -A 2 --max-count=20

Repository: AndyMik90/Aperant

Length of output: 50374


🏁 Script executed:

# Find the StreamEvent type definition
fd -e ts -e tsx | xargs grep -l "type StreamEvent" | head -5

Repository: AndyMik90/Aperant

Length of output: 102


🏁 Script executed:

# Look at the session/types.ts file to understand StreamEvent structure
rg "type StreamEvent|interface StreamEvent" -A 30 apps/desktop/src/main/ai/session/

Repository: AndyMik90/Aperant

Length of output: 2029


🏁 Script executed:

# Get the full ErrorEvent definition
rg "interface ErrorEvent" -A 10 apps/desktop/src/main/ai/session/types.ts

Repository: AndyMik90/Aperant

Length of output: 248


🏁 Script executed:

# Find the SessionError type definition
rg "type SessionError|interface SessionError" -A 15 apps/desktop/src/main/ai/session/types.ts

Repository: AndyMik90/Aperant

Length of output: 518


Wrap JSON.stringify calls in error fallback with try-catch.

Lines 163 and 169 call JSON.stringify() directly on cause (typed unknown) and err respectively. Since cause is explicitly unknown per the SessionError type definition, it could contain circular references or BigInt, which would throw. Failure in this error-logging path would prevent the task log entry from being persisted. Add a safe serializer function that falls back to String() on stringify failure.

🔧 Suggested fix
         const err = event.error;
+        const safeSerialize = (value: unknown): string => {
+          try {
+            return JSON.stringify(value);
+          } catch {
+            return String(value);
+          }
+        };
         let errorContent = 'Unknown error';
         if (err) {
           const msg = err.message || '';
           const code = err.code || '';
           const cause = err.cause;
           if (msg && msg !== 'error') {
             errorContent = msg;
           } else if (cause instanceof Error) {
             errorContent = cause.message || String(cause);
-          } else if (cause && typeof cause === 'object') {
-            errorContent = JSON.stringify(cause).slice(0, 500);
+          } else if (cause && typeof cause === 'object') {
+            errorContent = safeSerialize(cause).slice(0, 500);
           } else if (cause) {
             errorContent = String(cause);
           } else if (code && code !== 'error') {
             errorContent = `Error code: ${code}`;
           } else {
-            errorContent = `Error: ${JSON.stringify(err).slice(0, 500)}`;
+            errorContent = `Error: ${safeSerialize(err).slice(0, 500)}`;
           }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/ai/logging/task-log-writer.ts` around lines 160 - 169,
The error logging paths in task-log-writer.ts call JSON.stringify directly on
unknown-typed values (cause and err) which can throw for circular structures or
unsupported types; create a small safe serializer (e.g., safeStringify(value):
string) that tries JSON.stringify(value) in a try/catch and returns
String(value) on failure, then replace the direct
JSON.stringify(cause).slice(0,500) and JSON.stringify(err).slice(0,500) uses
with safeStringify(cause).slice(0,500) and safeStringify(err).slice(0,500)
inside the same error-handling branch so logging cannot throw and the task log
entry will still be persisted.

}
}
this.addEntry(logPhase, 'error', errorContent);
this.save();
break;
}

default:
// Ignore thinking-delta, usage-update
Expand Down
70 changes: 70 additions & 0 deletions apps/desktop/src/main/ai/mcp/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,55 @@ function createAutoClaudeServer(specDir: string): McpServerConfig {
};
}

/**
* JIRA MCP server - issue tracking integration.
* Conditionally enabled when project has JIRA configured.
* Requires JIRA_HOST, JIRA_EMAIL, JIRA_TOKEN environment variables.
*
* Uses the community @modelcontextprotocol/server-atlassian package.
* If no suitable MCP server is available, JIRA access is handled
* directly via the IPC handlers (jira/issue-handlers.ts) instead.
*/
function createJiraServer(env: Record<string, string>): McpServerConfig {
return {
id: 'jira',
name: 'JIRA',
description: 'Issue tracking integration for JIRA/Atlassian',
enabledByDefault: false,
transport: {
type: 'stdio',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-atlassian'],
Comment on lines +138 to +139
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/desktop/src/main/ai/mcp/registry.ts | head -250 | tail -150

Repository: AndyMik90/Aperant

Length of output: 5816


🏁 Script executed:

wc -l apps/desktop/src/main/ai/mcp/registry.ts

Repository: AndyMik90/Aperant

Length of output: 105


🏁 Script executed:

cat -n apps/desktop/src/main/ai/mcp/registry.ts | sed -n '130,250p'

Repository: AndyMik90/Aperant

Length of output: 4700


🌐 Web query:

npx -y npm package install behavior latest dist-tag unversioned

💡 Result:

When running npx -y (unversioned package specifier) where the package is not found locally or globally, npx (npm exec) will install and execute the version associated with the 'latest' dist-tag from the npm registry. The -y (or --yes) flag bypasses the confirmation prompt for installation. If a matching package exists locally in the project's dependencies (exact name and version match if specifier provided, or any version for unversioned), it uses the local one first. Otherwise, it fetches from the registry using standard npm resolution, defaulting to 'latest' tag for unversioned names. This aligns with npm install behavior: unversioned installs the 'latest' tag (docs confirm npm install without specifier uses latest tag, and npx uses same package resolution).

Citations:


🌐 Web query:

@modelcontextprotocol/server-filesystem write capabilities read-only

💡 Result:

The @modelcontextprotocol/server-filesystem provides full read and write capabilities for filesystem operations within configured allowed directories. It is not read-only by default. Key write tools include: - write_file: Create new or overwrite existing files - edit_file: Selective edits to files - create_directory: Create directories - delete_file / delete_directory / move_file / move_directory (implied by features) Read-only tools (marked with readOnlyHint: true) include read_text_file, read_media_file, list_directory, etc. Tool annotations distinguish read-only from write-capable tools. Operations are restricted to specified directories via command-line args or MCP Roots protocol. Separate read-only variants exist (e.g., readonly-filesystem-mcp), but the official server supports writing. Mounts can use 'ro' flag for read-only access in Docker.

Citations:


Address supply-chain risk and security issues in MCP server invocations.

Three issues require fixing:

  1. Pin MCP server versions instead of using npx -y with unversioned packages. Unversioned specifiers resolve to npm's latest dist-tag, meaning these secret-bearing (Jira) and filesystem-bearing (Vault) integrations will download and execute the latest package version on each install—non-deterministic and widening supply-chain blast radius. Pinned versions (e.g., @modelcontextprotocol/server-atlassian@1.0.0) are required.

  2. The Vault server is write-capable, not read-only as documented. The comment at line 152 claims "read-only file access," but @modelcontextprotocol/server-filesystem exposes write tools (write_file, edit_file, create_directory, delete_file). Correct the documentation or restrict access by using a dedicated read-only variant. Using Docker ro mounts is insufficient if the application controls the server directly.

  3. Jira credential validation is incomplete. Lines 236–244 enable the Jira server if only JIRA_HOST is present, but then pass JIRA_EMAIL and JIRA_TOKEN without validating they exist. Missing credentials will be passed as empty strings (lines 141–143), causing runtime failure with cryptic MCP server errors. Validate all three credentials before enabling.

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

In `@apps/desktop/src/main/ai/mcp/registry.ts` around lines 138 - 139, Pin MCP
server package specifiers instead of invoking unversioned packages with npx
(update the args for the MCP invocations that currently use 'npx -y
`@modelcontextprotocol/server-atlassian`' and similar to include explicit semver
pins like '@modelcontextprotocol/server-atlassian@x.y.z' and the filesystem
server to a fixed version), correct the comment/usage for the filesystem/Vault
server in registry.ts (the comment claiming "read-only file access" is wrong for
`@modelcontextprotocol/server-filesystem` — either change the comment to reflect
write-capable tools or switch to a read-only server variant if available), and
tighten Jira credential validation in the code path that enables the Jira MCP
server (where JIRA_HOST is checked and the server is enabled, also require
non-empty JIRA_EMAIL and JIRA_TOKEN environment variables before enabling and
passing them into the MCP server configuration).

env: {
ATLASSIAN_SITE_URL: env.JIRA_HOST || '',
ATLASSIAN_USER_EMAIL: env.JIRA_EMAIL || '',
ATLASSIAN_API_TOKEN: env.JIRA_TOKEN || '',
Comment on lines +130 to +143
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Use a narrow JiraEnv shape instead of Record<string, string>.

Record<string, string> makes env.JIRA_HOST / env.JIRA_EMAIL / env.JIRA_TOKEN look like ordinary strings in the type system, even though callers can still omit them at runtime. An explicit interface or Partial<Record<'JIRA_HOST' | 'JIRA_EMAIL' | 'JIRA_TOKEN', string>>, paired with a required shape at createJiraServer(), would let strict mode catch this class of bug earlier. As per coding guidelines, "Use TypeScript strict mode. Perform type checking with npm run typecheck."

Also applies to: 184-185

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

In `@apps/desktop/src/main/ai/mcp/registry.ts` around lines 130 - 143, The
function createJiraServer currently accepts a broad Record<string,string>,
hiding missing/optional keys; define a narrow JiraEnv type (e.g. interface
JiraEnv { JIRA_HOST?: string; JIRA_EMAIL?: string; JIRA_TOKEN?: string } or
Partial<Record<'JIRA_HOST'|'JIRA_EMAIL'|'JIRA_TOKEN', string>>) and update the
createJiraServer(env: JiraEnv): McpServerConfig signature to use it, adjust any
call sites and the other registry uses noted at lines 184-185 to accept the same
JiraEnv type, and run the TypeScript typecheck (npm run typecheck) to ensure
missing keys are caught or explicitly handled before accessing env.JIRA_HOST /
env.JIRA_EMAIL / env.JIRA_TOKEN.

},
},
};
}

/**
* Vault MCP server - external vault/Obsidian integration.
* Conditionally enabled when vault path is configured.
* Provides read-only file access to vault directory for agent context.
*
* Uses the official @modelcontextprotocol/server-filesystem package.
*/
function createVaultServer(vaultPath: string): McpServerConfig {
return {
id: 'vault',
name: 'Vault',
description: 'External vault integration for context and learnings',
enabledByDefault: false,
transport: {
type: 'stdio',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', vaultPath],
},
Comment on lines +149 to +166
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the actual file to understand the current implementation
find . -name "registry.ts" -path "*apps/desktop/src/main/ai/mcp*" -type f

Repository: AndyMik90/Aperant

Length of output: 103


🏁 Script executed:

# Check the specific file content around the mentioned lines
cat -n apps/desktop/src/main/ai/mcp/registry.ts | sed -n '140,175p'

Repository: AndyMik90/Aperant

Length of output: 1400


🏁 Script executed:

# Look for any related MCP server configuration or tool handling
rg "createVaultServer|@modelcontextprotocol/server-filesystem" --type ts -A 5 -B 5

Repository: AndyMik90/Aperant

Length of output: 2334


🏁 Script executed:

# Check package.json for version info and related dependencies
cat -n apps/desktop/package.json | grep -i "modelcontext\|mcp"

Repository: AndyMik90/Aperant

Length of output: 290


🌐 Web query:

@modelcontextprotocol/server-filesystem read-only configuration options capabilities

💡 Result:

The @modelcontextprotocol/server-filesystem is a Node.js MCP (Model Context Protocol) server providing filesystem operations including read/write files, directory management, search, and metadata retrieval. All operations are restricted to explicitly allowed directories for security. No built-in read-only configuration option or flag exists in the official server. Write tools (create_directory, write_file, edit_file, etc.) are always available with readOnlyHint: false in tool annotations, allowing clients to distinguish them. Read-only access can be achieved via: 1. Docker mounts with 'ro' flag: Mount host directories as read-only inside the container. Example in claude_desktop_config.json: { "mcpServers": { "filesystem": { "command": "docker", "args": [ "run", "-i", "--rm", "--mount", "type=bind,src=/host/path,dst=/projects/allowed,ro", "mcp/filesystem", "/projects" ] } } } The 'ro' makes the mount read-only at the OS level, preventing writes despite server capabilities. [3,6,12] 2. Third-party read-only forks, e.g., danielsuguimoto/readonly-filesystem-mcp (clone without write tools). [5] Capabilities include read-only tools (readOnlyHint: true): read_text_file, read_media_file, read_multiple_files, list_directory, list_directory_with_sizes, directory_tree, search_files, get_file_info, list_allowed_directories. Write tools (readOnlyHint: false): create_directory (idempotent), write_file (destructive), edit_file (non-idempotent destructive), etc. Full list in official README. [1,2,3,6,7]

Citations:


🌐 Web query:

@modelcontextprotocol/server-filesystem write_file edit_file tools documentation

💡 Result:

@modelcontextprotocol/server-filesystem is a Node.js MCP (Model Context Protocol) server for secure filesystem operations, developed by Anthropic. It provides tools like write_file and edit_file for file manipulation within allowed directories. Key features: - Read/write files, manage directories, search files, get metadata. - Directory access control via command-line args or MCP Roots protocol. - Install via npm: npm install @modelcontextprotocol/server-filesystem (latest v2026.1.14 as of Jan 2026). - Run: npx @modelcontextprotocol/server-filesystem /path/to/allowed/dir write_file tool: - Inputs: path (string), content (string) - Overwrites existing files completely. Use with caution. edit_file tool: - Inputs: path (string), edits (array of {oldText: string, newText: string}), dryRun (boolean, default false) - Performs line-based, exact-match replacements with whitespace normalization and indentation preservation. - Supports multiple edits, git-style diff output. - dryRun previews changes without applying. - Not idempotent; re-applying may fail or double-apply. Configuration example for Claude Desktop (claude_desktop_config.json): { "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Desktop"] } } } Source code: https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem NPM: https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem Note: Early NPM versions lacked edit_file; use @latest or build from GitHub source if needed.

Citations:


🏁 Script executed:

# Check if `@modelcontextprotocol/server-filesystem` has any env vars or args for tool restriction
npm view `@modelcontextprotocol/server-filesystem` readme | head -100

Repository: AndyMik90/Aperant

Length of output: 3915


🌐 Web query:

@modelcontextprotocol/server-filesystem environment variables configuration arguments restrict tools

💡 Result:

The @modelcontextprotocol/server-filesystem is a Node.js MCP server for secure filesystem operations, restricting access to specified directories. Configuration: - Command-line arguments: Pass allowed directories as positional args after the command, e.g., npx -y @modelcontextprotocol/server-filesystem /path/to/dir1 /path/to/dir2. These set initial allowed directories. Use exclusion syntax like /dir!.env,dist to disallow specific files/paths within allowed dirs (e.g., .git and node_modules excluded by default). Negate with !, e.g., /dir!.git to allow. - MCP config (e.g., claude_desktop_config.json or mcp.json): { "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir1", "/path/to/dir2"] } } } Docker mounts to /projects, args like ["/projects/dir"]. Environment variables: No standard env vars for config in current version (proposal in GitHub issue #1879 for ALLOWED_DIRS etc., dated 2025-05-22, not merged). General MCP configs support "env" field, e.g., {"env": {"DEBUG": "true"}} (seen in examples). Tool restrictions: All tools (read_text_file, write_file, list_directory, etc., ~11 tools) restricted to allowed directories via path validation. Use list_allowed_directories tool to query. Supports MCP Roots for dynamic updates if client capabilities.roots supported. Tool annotations provide hints (readOnlyHint, destructiveHint, etc.). No separate "restrict tools" config; access controlled by directories.

Citations:


Fix misleading documentation comment and remove write-enabled tools from Vault MCP.

The comment claiming "read-only file access" (line 152) is false. The @modelcontextprotocol/server-filesystem package exposes write tools (write_file, edit_file, move_file, create_directory) by default with no configuration option to disable them—read-only is only enforced via Docker ro mounts or third-party read-only forks. The current setup allows agents to modify vault files, creating data-integrity risk.

Use one of:

  • Mount the vault directory read-only via Docker with ro flag
  • Switch to a read-only fork like danielsuguimoto/readonly-filesystem-mcp
  • Modify @modelcontextprotocol/server-filesystem to support tool filtering

Update the misleading comment at line 152 to accurately describe the access level once the fix is in place.

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

In `@apps/desktop/src/main/ai/mcp/registry.ts` around lines 149 - 166, The
createVaultServer function currently runs
`@modelcontextprotocol/server-filesystem` which exposes write tools; change the
Vault MCP to a read-only integration by either (a) replacing the transport
command/args to a read-only fork (e.g., danielsuguimoto/readonly-filesystem-mcp)
or (b) run the filesystem server under a Docker invocation that mounts the
vaultPath read-only (use the container runtime with a ro mount) or (c)
patch/filter the exported tools from `@modelcontextprotocol/server-filesystem` to
remove write tools (write_file, edit_file, move_file, create_directory) before
registering the MCP; then update the top comment text in createVaultServer to
accurately state the actual access level (read-only or read-write depending on
chosen fix) and ensure the MCP id 'vault' and transport command reflect the
chosen approach.

};
}

// =============================================================================
// Registry
// =============================================================================
Expand All @@ -132,6 +181,10 @@ export interface McpRegistryOptions {
linearApiKey?: string;
/** Environment variables for server processes */
env?: Record<string, string>;
/** JIRA environment variables (JIRA_HOST, JIRA_EMAIL, JIRA_TOKEN) */
jiraEnv?: Record<string, string>;
/** Vault path (if vault integration is enabled) */
vaultPath?: string;
}

/**
Expand Down Expand Up @@ -180,6 +233,23 @@ export function getMcpServerConfig(
return createAutoClaudeServer(specDir);
}

case 'jira': {
const jiraHost = options.jiraEnv?.JIRA_HOST ?? options.env?.JIRA_HOST;
if (!jiraHost) return null;
const jiraEnv = options.jiraEnv ?? {
JIRA_HOST: options.env?.JIRA_HOST ?? '',
JIRA_EMAIL: options.env?.JIRA_EMAIL ?? '',
JIRA_TOKEN: options.env?.JIRA_TOKEN ?? '',
};
return createJiraServer(jiraEnv);
}
Comment on lines +236 to +245
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Require full JIRA credentials before returning MCP config.

Line 231 currently checks only JIRA_HOST. If JIRA_EMAIL or JIRA_TOKEN is missing, the server still starts and fails later at runtime. Validate all required fields up front.

🛡️ Proposed fix
     case 'jira': {
-      const jiraHost = options.jiraEnv?.JIRA_HOST ?? options.env?.JIRA_HOST;
-      if (!jiraHost) return null;
-      const jiraEnv = options.jiraEnv ?? {
-        JIRA_HOST: options.env?.JIRA_HOST ?? '',
-        JIRA_EMAIL: options.env?.JIRA_EMAIL ?? '',
-        JIRA_TOKEN: options.env?.JIRA_TOKEN ?? '',
-      };
+      const jiraHost = options.jiraEnv?.JIRA_HOST ?? options.env?.JIRA_HOST;
+      const jiraEmail = options.jiraEnv?.JIRA_EMAIL ?? options.env?.JIRA_EMAIL;
+      const jiraToken = options.jiraEnv?.JIRA_TOKEN ?? options.env?.JIRA_TOKEN;
+      if (!jiraHost || !jiraEmail || !jiraToken) return null;
+      const jiraEnv = {
+        JIRA_HOST: jiraHost,
+        JIRA_EMAIL: jiraEmail,
+        JIRA_TOKEN: jiraToken,
+      };
       return createJiraServer(jiraEnv);
     }
📝 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
case 'jira': {
const jiraHost = options.jiraEnv?.JIRA_HOST ?? options.env?.JIRA_HOST;
if (!jiraHost) return null;
const jiraEnv = options.jiraEnv ?? {
JIRA_HOST: options.env?.JIRA_HOST ?? '',
JIRA_EMAIL: options.env?.JIRA_EMAIL ?? '',
JIRA_TOKEN: options.env?.JIRA_TOKEN ?? '',
};
return createJiraServer(jiraEnv);
}
case 'jira': {
const jiraHost = options.jiraEnv?.JIRA_HOST ?? options.env?.JIRA_HOST;
const jiraEmail = options.jiraEnv?.JIRA_EMAIL ?? options.env?.JIRA_EMAIL;
const jiraToken = options.jiraEnv?.JIRA_TOKEN ?? options.env?.JIRA_TOKEN;
if (!jiraHost || !jiraEmail || !jiraToken) return null;
const jiraEnv = {
JIRA_HOST: jiraHost,
JIRA_EMAIL: jiraEmail,
JIRA_TOKEN: jiraToken,
};
return createJiraServer(jiraEnv);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/ai/mcp/registry.ts` around lines 230 - 239, The MCP
Jira branch currently only verifies JIRA_HOST before returning createJiraServer;
update the validation in the 'jira' case to require JIRA_HOST, JIRA_EMAIL and
JIRA_TOKEN (from options.jiraEnv or fallback options.env) and return null if any
are missing. Locate the 'jira' case block (variables jiraHost, jiraEnv and the
call to createJiraServer) and perform a combined presence check for
jiraEnv.JIRA_HOST, jiraEnv.JIRA_EMAIL and jiraEnv.JIRA_TOKEN before calling
createJiraServer so the MCP config is only returned when all credentials are
present.


case 'vault': {
const vPath = options.vaultPath ?? options.env?.VAULT_PATH;
if (!vPath) return null;
return createVaultServer(vPath);
}

default:
return null;
}
Expand Down
4 changes: 3 additions & 1 deletion apps/desktop/src/main/ai/mcp/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ export type McpServerId =
| 'memory'
| 'electron'
| 'puppeteer'
| 'auto-claude';
| 'auto-claude'
| 'jira'
| 'vault';

/** Configuration for a single MCP server */
export interface McpServerConfig {
Expand Down
28 changes: 27 additions & 1 deletion apps/desktop/src/main/ai/session/stream-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,33 @@ export function createStreamHandler(onEvent: SessionEventCallback) {
}

function handleError(part: ErrorPart): void {
const errorMessage = part.error instanceof Error ? part.error.message : String(part.error ?? 'Stream error');
// DEBUG: Dump the raw error to console for diagnosis
console.warn('[StreamHandler] RAW ERROR:', JSON.stringify(part.error, Object.getOwnPropertyNames(part.error instanceof Error ? part.error : {}), 2).slice(0, 1000));
console.warn('[StreamHandler] ERROR TYPE:', typeof part.error, part.error?.constructor?.name);
if (part.error instanceof Error) {
console.warn('[StreamHandler] ERROR CAUSE:', (part.error as { cause?: unknown }).cause);
console.warn('[StreamHandler] ERROR STACK:', part.error.stack?.slice(0, 500));
}
Comment on lines +269 to +275
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

head -300 apps/desktop/src/main/ai/session/stream-handler.ts | tail -60

Repository: AndyMik90/Aperant

Length of output: 2479


🏁 Script executed:

sed -n '250,300p' apps/desktop/src/main/ai/session/stream-handler.ts

Repository: AndyMik90/Aperant

Length of output: 2102


🏁 Script executed:

git ls-files apps/desktop/src/main/ | head -20

Repository: AndyMik90/Aperant

Length of output: 1199


🏁 Script executed:

# Check for any dev-only conditionals or Sentry usage in the handler
sed -n '250,310p' apps/desktop/src/main/ai/session/stream-handler.ts

Repository: AndyMik90/Aperant

Length of output: 2383


🏁 Script executed:

# Verify if classifyError sanitizes and how it works
rg -A 10 "function classifyError" apps/desktop/src/main/ai/session/stream-handler.ts

Repository: AndyMik90/Aperant

Length of output: 43


🏁 Script executed:

# Check if there's any dev-only flag or environment check in this file
rg "isDev|process.env.NODE_ENV|__DEV__" apps/desktop/src/main/ai/session/stream-handler.ts

Repository: AndyMik90/Aperant

Length of output: 43


🏁 Script executed:

# Check imports at the top of the file
head -50 apps/desktop/src/main/ai/session/stream-handler.ts

Repository: AndyMik90/Aperant

Length of output: 1869


🏁 Script executed:

# Look for classifyError definition anywhere in the codebase
rg "classifyError" apps/desktop/src/main/ai/ -A 5

Repository: AndyMik90/Aperant

Length of output: 10800


🏁 Script executed:

# Check the full handleError function and what follows
sed -n '260,300p' apps/desktop/src/main/ai/session/stream-handler.ts

Repository: AndyMik90/Aperant

Length of output: 1897


Remove unsanitized debug logs that leak sensitive error data.

Lines 269-275 log raw error, cause, and stack directly via console.warn() before classifyError() can sanitize credentials. This violates the guideline requiring production main-process code to use Sentry instead of console output. Additionally, the JSON.stringify() call can throw on circular references or BigInt values, causing the diagnostic path itself to fail. Wrap these debug dumps behind a development-only flag and remove them before shipping, or route them through a safe serializer that cannot fail.


// Extract meaningful error message - AI SDK error objects may have nested cause
let errorMessage: string;
if (part.error instanceof Error) {
errorMessage = part.error.message;
// If message is just 'error', try cause
if (errorMessage === 'error' && (part.error as { cause?: unknown }).cause) {
const cause = (part.error as { cause?: unknown }).cause;
errorMessage = cause instanceof Error ? cause.message : String(cause);
}
// Also check stack for more context if message is unhelpful
if (errorMessage === 'error' && part.error.stack) {
errorMessage = part.error.stack.split('\n')[0] || 'Stream error';
}
} else if (typeof part.error === 'object' && part.error !== null) {
const errObj = part.error as Record<string, unknown>;
errorMessage = (errObj.message as string) || (errObj.text as string) || JSON.stringify(part.error).slice(0, 500);
} else {
errorMessage = String(part.error ?? 'Stream error');
}
const { sessionError } = classifyError(errorMessage);
emit({ type: 'error', error: sessionError });
}
Expand Down
58 changes: 58 additions & 0 deletions apps/desktop/src/main/ipc-handlers/env-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,29 @@ export function registerEnvHandlers(
if (config.gitlabAutoSync !== undefined) {
existingVars[GITLAB_ENV_KEYS.AUTO_SYNC] = config.gitlabAutoSync ? 'true' : 'false';
}
// Independent issue tracking flags (separate from source control)
if (config.githubIssuesEnabled !== undefined) {
existingVars['GITHUB_ISSUES_ENABLED'] = config.githubIssuesEnabled ? 'true' : 'false';
}
if (config.gitlabIssuesEnabled !== undefined) {
existingVars['GITLAB_ISSUES_ENABLED'] = config.gitlabIssuesEnabled ? 'true' : 'false';
}
Comment on lines +92 to +98
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Persist GITHUB_ISSUES_ENABLED in the generated .env.

generateEnvContent() stores config.githubIssuesEnabled in existingVars, but the template never writes that key back out. Any save round-trip will silently drop the GitHub Issues toggle.

🐛 Proposed fix
 # =============================================================================
 # GITHUB INTEGRATION (OPTIONAL)
 # =============================================================================
 ${existingVars['GITHUB_TOKEN'] ? `GITHUB_TOKEN=${existingVars['GITHUB_TOKEN']}` : '# GITHUB_TOKEN='}
 ${existingVars['GITHUB_REPO'] ? `GITHUB_REPO=${existingVars['GITHUB_REPO']}` : '# GITHUB_REPO=owner/repo'}
 ${existingVars['GITHUB_AUTO_SYNC'] !== undefined ? `GITHUB_AUTO_SYNC=${existingVars['GITHUB_AUTO_SYNC']}` : '# GITHUB_AUTO_SYNC=false'}
+${existingVars['GITHUB_ISSUES_ENABLED'] !== undefined ? `GITHUB_ISSUES_ENABLED=${existingVars['GITHUB_ISSUES_ENABLED']}` : '# GITHUB_ISSUES_ENABLED=false'}

Also applies to: 238-247

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

In `@apps/desktop/src/main/ipc-handlers/env-handlers.ts` around lines 92 - 98,
generateEnvContent currently sets existingVars['GITHUB_ISSUES_ENABLED'] and
['GITLAB_ISSUES_ENABLED'] from config but the output template never emits those
keys, so the toggles are lost on save; update generateEnvContent to include
those keys in the generated .env output when present (e.g., ensure the
template/serializer writes existingVars['GITHUB_ISSUES_ENABLED'] and
existingVars['GITLAB_ISSUES_ENABLED'] as GITHUB_ISSUES_ENABLED=true/false
lines), and mirror the same fix for the other similar block that handles these
flags (the duplicate logic later in the function).

// JIRA Integration
if (config.jiraEnabled !== undefined) {
existingVars['JIRA_ENABLED'] = config.jiraEnabled ? 'true' : 'false';
}
if (config.jiraHost !== undefined) {
existingVars['JIRA_HOST'] = config.jiraHost;
}
if (config.jiraEmail !== undefined) {
existingVars['JIRA_EMAIL'] = config.jiraEmail;
}
if (config.jiraToken !== undefined) {
existingVars['JIRA_TOKEN'] = config.jiraToken;
}
if (config.jiraProjectKey !== undefined) {
existingVars['JIRA_PROJECT_KEY'] = config.jiraProjectKey;
}
// Git/Worktree Settings
if (config.defaultBranch !== undefined) {
existingVars['DEFAULT_BRANCH'] = config.defaultBranch;
Expand Down Expand Up @@ -212,6 +235,16 @@ ${envLine(existingVars, GITLAB_ENV_KEYS.INSTANCE_URL, 'https://gitlab.com')}
${envLine(existingVars, GITLAB_ENV_KEYS.TOKEN)}
${envLine(existingVars, GITLAB_ENV_KEYS.PROJECT, 'group/project')}
${envLine(existingVars, GITLAB_ENV_KEYS.AUTO_SYNC, 'false')}
${existingVars['GITLAB_ISSUES_ENABLED'] !== undefined ? `GITLAB_ISSUES_ENABLED=${existingVars['GITLAB_ISSUES_ENABLED']}` : '# GITLAB_ISSUES_ENABLED=false'}

# =============================================================================
# JIRA INTEGRATION (OPTIONAL)
# =============================================================================
${existingVars['JIRA_ENABLED'] !== undefined ? `JIRA_ENABLED=${existingVars['JIRA_ENABLED']}` : '# JIRA_ENABLED=false'}
${envLine(existingVars, 'JIRA_HOST', 'https://your-domain.atlassian.net')}
${envLine(existingVars, 'JIRA_EMAIL')}
${envLine(existingVars, 'JIRA_TOKEN')}
${envLine(existingVars, 'JIRA_PROJECT_KEY', 'PROJ')}

# =============================================================================
# GIT/WORKTREE SETTINGS (OPTIONAL)
Expand Down Expand Up @@ -324,6 +357,7 @@ ${existingVars['GRAPHITI_DB_PATH'] ? `GRAPHITI_DB_PATH=${existingVars['GRAPHITI_
linearEnabled: false,
githubEnabled: false,
gitlabEnabled: false,
jiraEnabled: false,
memoryEnabled: false,
enableFancyUi: true,
openaiKeyIsGlobal: false
Expand Down Expand Up @@ -386,6 +420,30 @@ ${existingVars['GRAPHITI_DB_PATH'] ? `GRAPHITI_DB_PATH=${existingVars['GRAPHITI_
config.gitlabAutoSync = true;
}

// Independent issue tracking flags
if (vars['GITHUB_ISSUES_ENABLED']?.toLowerCase() === 'true') {
config.githubIssuesEnabled = true;
}
if (vars['GITLAB_ISSUES_ENABLED']?.toLowerCase() === 'true') {
config.gitlabIssuesEnabled = true;
}

// JIRA config
if (vars['JIRA_HOST']) {
config.jiraHost = vars['JIRA_HOST'];
// Enable by default if host exists and JIRA_ENABLED is not explicitly false
config.jiraEnabled = vars['JIRA_ENABLED']?.toLowerCase() !== 'false';
}
Comment on lines +431 to +436
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Honor JIRA_ENABLED even when the host is still blank.

ENV_GET only restores jiraEnabled inside the JIRA_HOST branch. That means a user can enable JIRA, save, and have the toggle reset to false on the next load until they also fill in the host field.

🐛 Proposed fix
-      if (vars['JIRA_HOST']) {
-        config.jiraHost = vars['JIRA_HOST'];
-        // Enable by default if host exists and JIRA_ENABLED is not explicitly false
-        config.jiraEnabled = vars['JIRA_ENABLED']?.toLowerCase() !== 'false';
-      }
+      if (vars['JIRA_ENABLED'] !== undefined) {
+        config.jiraEnabled = vars['JIRA_ENABLED'].toLowerCase() === 'true';
+      }
+      if (vars['JIRA_HOST']) {
+        config.jiraHost = vars['JIRA_HOST'];
+        if (vars['JIRA_ENABLED'] === undefined) {
+          config.jiraEnabled = true;
+        }
+      }
📝 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
// JIRA config
if (vars['JIRA_HOST']) {
config.jiraHost = vars['JIRA_HOST'];
// Enable by default if host exists and JIRA_ENABLED is not explicitly false
config.jiraEnabled = vars['JIRA_ENABLED']?.toLowerCase() !== 'false';
}
// JIRA config
if (vars['JIRA_ENABLED'] !== undefined) {
config.jiraEnabled = vars['JIRA_ENABLED'].toLowerCase() === 'true';
}
if (vars['JIRA_HOST']) {
config.jiraHost = vars['JIRA_HOST'];
if (vars['JIRA_ENABLED'] === undefined) {
config.jiraEnabled = true;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/ipc-handlers/env-handlers.ts` around lines 431 - 436,
The code only sets config.jiraEnabled inside the JIRA_HOST branch so the saved
JIRA_ENABLED toggle is lost when JIRA_HOST is blank; update the ENV_GET handling
so vars['JIRA_ENABLED'] is respected regardless of vars['JIRA_HOST'] by
assigning config.jiraEnabled from vars['JIRA_ENABLED'] (e.g., config.jiraEnabled
= vars['JIRA_ENABLED']?.toLowerCase() !== 'false') outside or before the
vars['JIRA_HOST'] branch while still keeping the existing assignment of
config.jiraHost within the vars['JIRA_HOST'] block.

if (vars['JIRA_EMAIL']) {
config.jiraEmail = vars['JIRA_EMAIL'];
}
if (vars['JIRA_TOKEN']) {
config.jiraToken = vars['JIRA_TOKEN'];
}
if (vars['JIRA_PROJECT_KEY']) {
config.jiraProjectKey = vars['JIRA_PROJECT_KEY'];
}

// Git/Worktree config
if (vars['DEFAULT_BRANCH']) {
config.defaultBranch = vars['DEFAULT_BRANCH'];
Expand Down
12 changes: 11 additions & 1 deletion apps/desktop/src/main/ipc-handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import { registerProfileHandlers } from './profile-handlers';
import { registerScreenshotHandlers } from './screenshot-handlers';
import { registerTerminalWorktreeIpcHandlers } from './terminal';
import { registerCodexAuthHandlers } from './codex-auth-handlers';
import { registerJiraHandlers } from './jira';
import { registerVaultHandlers } from './vault';
import { notificationService } from '../notification-service';
import { setAgentManagerRef } from './utils';

Expand Down Expand Up @@ -127,6 +129,12 @@ export function setupIpcHandlers(
// Codex OAuth authentication handlers
registerCodexAuthHandlers();

// JIRA integration handlers
registerJiraHandlers(agentManager, getMainWindow);

// Vault integration handlers
registerVaultHandlers();

console.warn('[IPC] All handler modules registered successfully');
}

Expand Down Expand Up @@ -155,5 +163,7 @@ export {
registerMcpHandlers,
registerProfileHandlers,
registerScreenshotHandlers,
registerCodexAuthHandlers
registerCodexAuthHandlers,
registerJiraHandlers,
registerVaultHandlers
};
27 changes: 27 additions & 0 deletions apps/desktop/src/main/ipc-handlers/jira/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* JIRA IPC Handlers Module
*
* This module exports the main registration function for all JIRA-related IPC handlers.
*/

import type { BrowserWindow } from 'electron';
import type { AgentManager } from '../../agent';

import { registerJiraIssueHandlers } from './issue-handlers';
import { registerJiraInvestigationHandlers } from './investigation-handlers';

/**
* Register all JIRA IPC handlers
*/
export function registerJiraHandlers(
agentManager: AgentManager,
getMainWindow: () => BrowserWindow | null
): void {
console.warn('[JIRA] Registering JIRA handlers');
registerJiraIssueHandlers();
registerJiraInvestigationHandlers(agentManager, getMainWindow);
console.warn('[JIRA] JIRA handlers registered');
}

// Re-export individual registration functions for custom usage
export { registerJiraIssueHandlers, registerJiraInvestigationHandlers };
Loading
Loading