From 70d2399a114039bf4e61ff53c24cab250b5a27bb Mon Sep 17 00:00:00 2001 From: "gitricko@hermes" Date: Mon, 25 May 2026 12:48:54 -0400 Subject: [PATCH 1/4] Refactor package metadata and implement auto-approval feature in permissions --- package-lock.json | 8 ++--- package.json | 10 +++---- src/extension.ts | 76 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d4e6dd..fe464f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "hermes-ai-agent", - "version": "3.0.0", + "name": "hermes-code-agent", + "version": "3.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "hermes-ai-agent", - "version": "3.0.0", + "name": "hermes-code-agent", + "version": "3.0.2", "license": "MIT", "dependencies": { "dompurify": "^3.4.5", diff --git a/package.json b/package.json index 50ffbf2..999bfab 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "hermes-ai-agent", - "displayName": "Hermes AI Agent", + "name": "hermes-code-agent", + "displayName": "Hermes Code Agent", "description": "VS Code sidebar for the Hermes AI agent. Streams chat, runs tools, manages sessions. Multi-model (Claude, Codex). Communicates over ACP.", - "version": "3.0.1", + "version": "3.0.2", "publisher": "gitricko", "author": "gitricko", "license": "MIT", @@ -17,7 +17,7 @@ "llm", "tool use", "chat", - "hermes", + "hermes-agent", "acp", "agent client protocol", "sidebar chat", @@ -67,7 +67,7 @@ "activitybar": [ { "id": "hermes", - "title": "Hermes Agent", + "title": "Hermes Code Agent", "icon": "resources/hermes-icon.svg" } ] diff --git a/src/extension.ts b/src/extension.ts index 99c67f8..8518efe 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -56,6 +56,44 @@ function readHermesModel(): { model: string; source: 'env' | 'config' | 'fallbac return { model: DEFAULT_SONNET_MODEL, source: 'fallback' }; } +function readApprovalsDisabled(): boolean { + // Returns true when ~/.hermes/config.yaml has `approvals.mode: false|off|no`. + try { + const configPath = path.join(os.homedir(), '.hermes', 'config.yaml'); + const content = fs.readFileSync(configPath, 'utf8'); + const lines = content.split(/\r?\n/); + + let inApprovals = false; + let approvalsIndent = 0; + + for (const line of lines) { + if (!line.trim() || line.trimStart().startsWith('#')) continue; + + const indent = line.search(/\S/); + const trimmed = line.trim(); + + if (trimmed === 'approvals:') { + inApprovals = true; + approvalsIndent = indent; + continue; + } + + if (inApprovals) { + if (indent <= approvalsIndent) break; + + const modeMatch = trimmed.match(/^mode:\s*(.+)/); + if (modeMatch) { + const value = modeMatch[1].trim().toLowerCase(); + return value === 'false' || value === 'off' || value === 'no'; + } + } + } + } catch { + // Config not accessible — not disabled, show dialog. + } + return false; +} + function readHermesVersion(hermesPath: string): string { const attempts: string[][] = [['--version'], ['version']]; for (const args of attempts) { @@ -235,19 +273,51 @@ export async function activate(context: vscode.ExtensionContext): Promise setStatus('disconnected'); }); + let dangerouslyApproved = false; + const permissionHandler: PermissionRequestHandler = async (_method, params) => { const allowOptionId = optionIdByIntent(params, 'allow'); const denyOptionId = optionIdByIntent(params, 'deny'); - const allow = 'Allow Once'; + + // Approvals disabled via config — auto-allow without dialog. + if (readApprovalsDisabled()) { + outputChannel.appendLine('[security] approvals disabled in config, auto-allowing'); + if (allowOptionId) { + return { outcome: 'selected', optionId: allowOptionId }; + } + throw new Error('Permission denied: no allow option'); + } + + // Allow Always was clicked earlier — suppress future dialogs. + if (dangerouslyApproved) { + outputChannel.appendLine('[security] allow-always active, auto-allowing'); + if (allowOptionId) { + return { outcome: 'selected', optionId: allowOptionId }; + } + throw new Error('Permission denied: no allow option'); + } + const deny = 'Deny'; + const allowOnce = 'Allow Once'; + const allowAlways = 'Allow Always'; const choice = await vscode.window.showWarningMessage( summarizePermissionRequest(params), { modal: true }, - allow, deny, + allowOnce, + allowAlways, ); - if (choice === allow && allowOptionId) { + if (choice === allowAlways) { + dangerouslyApproved = true; + outputChannel.appendLine('[security] allow-always activated — future requests auto-allowed'); + if (allowOptionId) { + return { outcome: 'selected', optionId: allowOptionId }; + } + throw new Error('Permission denied: no allow option'); + } + + if (choice === allowOnce && allowOptionId) { outputChannel.appendLine('[security] permission granted once'); return { outcome: 'selected', optionId: allowOptionId }; } From dfda8c2bdb71f2ac9217772bddd9d55bded0c6ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 17:03:55 +0000 Subject: [PATCH 2/4] Cache readApprovalsDisabled result during activate() --- src/extension.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 8518efe..6bbd3aa 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -274,13 +274,14 @@ export async function activate(context: vscode.ExtensionContext): Promise }); let dangerouslyApproved = false; + const approvalsDisabled = readApprovalsDisabled(); const permissionHandler: PermissionRequestHandler = async (_method, params) => { const allowOptionId = optionIdByIntent(params, 'allow'); const denyOptionId = optionIdByIntent(params, 'deny'); // Approvals disabled via config — auto-allow without dialog. - if (readApprovalsDisabled()) { + if (approvalsDisabled) { outputChannel.appendLine('[security] approvals disabled in config, auto-allowing'); if (allowOptionId) { return { outcome: 'selected', optionId: allowOptionId }; From eee0c938d49056751a07d076c5f2891489e5716c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 May 2026 17:09:32 +0000 Subject: [PATCH 3/4] Fix YAML parsing to strip inline comments in approvals.mode check --- src/extension.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 6bbd3aa..4c556c0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -83,7 +83,9 @@ function readApprovalsDisabled(): boolean { const modeMatch = trimmed.match(/^mode:\s*(.+)/); if (modeMatch) { - const value = modeMatch[1].trim().toLowerCase(); + const rawValue = modeMatch[1].trim(); + // Strip trailing inline comment (e.g., "false # comment" → "false") + const value = rawValue.split('#')[0].trim().toLowerCase(); return value === 'false' || value === 'off' || value === 'no'; } } From a549f80869d583f6e05762afbfb28aaead11f2dd Mon Sep 17 00:00:00 2001 From: gitricko Date: Mon, 25 May 2026 13:10:53 -0400 Subject: [PATCH 4/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/extension.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 4c556c0..666d7ad 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -312,12 +312,12 @@ export async function activate(context: vscode.ExtensionContext): Promise ); if (choice === allowAlways) { + if (!allowOptionId) { + throw new Error('Permission denied: no allow option'); + } dangerouslyApproved = true; outputChannel.appendLine('[security] allow-always activated — future requests auto-allowed'); - if (allowOptionId) { - return { outcome: 'selected', optionId: allowOptionId }; - } - throw new Error('Permission denied: no allow option'); + return { outcome: 'selected', optionId: allowOptionId }; } if (choice === allowOnce && allowOptionId) {