From 89a65c323590d06e8e28ef17df44c832642df035 Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Thu, 11 Jun 2026 14:13:53 +0200 Subject: [PATCH 1/6] feat(openshell): build and create sandbox via openshell-image-builder Adds OpenShell workspace creation path: when KAIDEN_OPENSHELL is set, create() builds a container image with openshell-image-builder (passing agent, model, inference, and endpoint) then provisions the sandbox via openshell CLI instead of kdn. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Philippe Martin --- .../agent-workspace-manager.spec.ts | 98 ++++++++++++++++++- .../agent-workspace-manager.ts | 48 ++++++++- .../openshell-cli/openshell-image-builder.ts | 4 +- 3 files changed, 147 insertions(+), 3 deletions(-) diff --git a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts index 27e9891353..e7db7b00b6 100644 --- a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts +++ b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts @@ -23,13 +23,14 @@ import type { FileSystemWatcher, InferenceProviderConnection } from '@openkaiden import type { WebContents } from 'electron'; import type { IPty } from 'node-pty'; import { spawn } from 'node-pty'; -import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import type { IPCHandle } from '/@/plugin/api.js'; import type { CliToolRegistry } from '/@/plugin/cli-tool-registry.js'; import type { FilesystemMonitoring } from '/@/plugin/filesystem-monitoring.js'; import { KdnCli } from '/@/plugin/kdn-cli/kdn-cli.js'; import { OpenshellCli } from '/@/plugin/openshell-cli/openshell-cli.js'; +import { OpenshellImageBuilder } from '/@/plugin/openshell-cli/openshell-image-builder.js'; import type { ProviderRegistry } from '/@/plugin/provider-registry.js'; import type { SafeStorageRegistry, SecretStorageWrapper } from '/@/plugin/safe-storage/safe-storage-registry.js'; import type { SecretManager } from '/@/plugin/secret-manager/secret-manager.js'; @@ -49,6 +50,7 @@ vi.mock(import('node-pty')); vi.mock(import('/@/plugin/kdn-cli/kdn-cli.js')); vi.mock(import('/@/plugin/openshell-cli/openshell-cli.js')); +vi.mock(import('/@/plugin/openshell-cli/openshell-image-builder.js')); const TEST_SUMMARIES: AgentWorkspaceSummary[] = [ { @@ -85,6 +87,7 @@ const apiSender: ApiSenderType = { const ipcHandle: IPCHandle = vi.fn(); const kdnCli = new KdnCli({} as Exec, {} as CliToolRegistry); const openshellCli = new OpenshellCli({} as Exec, {} as CliToolRegistry); +const imageBuilderCli = new OpenshellImageBuilder({} as Exec, {} as CliToolRegistry); const mockTask = { id: 'task-1', @@ -170,6 +173,7 @@ beforeEach(() => { secretManager, openshellCli, safeStorageRegistry, + imageBuilderCli, ); manager.init(); }); @@ -358,6 +362,98 @@ describe('create', () => { }); }); +describe('create – OpenShell mode', () => { + const defaultOptions: AgentWorkspaceCreateOptions = { + sourcePath: '/tmp/my-project', + agent: 'claude', + runtime: 'podman', + name: 'my-sandbox', + }; + + beforeEach(() => { + process.env['KAIDEN_OPENSHELL'] = '1'; + vi.mocked(kdnCli.writeWorkspaceConfig).mockResolvedValue(undefined); + vi.mocked(imageBuilderCli.buildImage).mockResolvedValue(undefined); + vi.mocked(openshellCli.createSandbox).mockResolvedValue(undefined); + }); + + afterEach(() => { + delete process.env['KAIDEN_OPENSHELL']; + }); + + test('calls imageBuilderCli.buildImage with correct tag and agent option', async () => { + await manager.create(defaultOptions); + + expect(imageBuilderCli.buildImage).toHaveBeenCalledWith( + 'kaiden-workspace-my-sandbox:latest', + expect.objectContaining({ agent: 'claude', cwd: '/tmp/my-project' }), + ); + }); + + test('calls openshellCli.createSandbox with from, name, providers, and workspace label', async () => { + const options = { ...defaultOptions, secrets: ['my-secret'] }; + await manager.create(options); + + expect(openshellCli.createSandbox).toHaveBeenCalledWith({ + name: 'my-sandbox', + from: 'kaiden-workspace-my-sandbox:latest', + providers: ['my-secret'], + labels: { 'ai.openkaiden.kaiden.workspace': Buffer.from('/tmp/my-project').toString('base64url') }, + }); + }); + + test('returns { id: sandboxName }', async () => { + const result = await manager.create(defaultOptions); + + expect(result).toEqual({ id: 'my-sandbox' }); + }); + + test('does not call kdnCli.createWorkspace', async () => { + await manager.create(defaultOptions); + + expect(kdnCli.createWorkspace).not.toHaveBeenCalled(); + }); + + test('derives sandbox name from sourcePath basename when name is omitted', async () => { + const options: AgentWorkspaceCreateOptions = { sourcePath: '/tmp/my-project', agent: 'claude' }; + const result = await manager.create(options); + + expect(openshellCli.createSandbox).toHaveBeenCalledWith(expect.objectContaining({ name: 'my-project' })); + expect(result).toEqual({ id: 'my-project' }); + }); + + test('sanitizes uppercase and special characters in sandbox name for image tag', async () => { + const options = { ...defaultOptions, name: 'My Project/V2!' }; + await manager.create(options); + + expect(imageBuilderCli.buildImage).toHaveBeenCalledWith( + 'kaiden-workspace-my-project-v2:latest', + expect.any(Object), + ); + }); + + test('passes model name, inference, and endpoint to buildImage', async () => { + vi.mocked(providerRegistry.getInferenceConnectionCredentials).mockReturnValue({ + credentials: { 'claude:tokens': 'sk-ant-secret' }, + llmMetadataName: 'anthropic', + endpoint: 'https://api.anthropic.com', + }); + vi.mocked(secretManager.create).mockResolvedValue({ name: 'my-sandbox-anthropic' }); + + const options = { ...defaultOptions, model: 'anthropic::claude-3-5-sonnet::' }; + await manager.create(options); + + expect(imageBuilderCli.buildImage).toHaveBeenCalledWith( + 'kaiden-workspace-my-sandbox:latest', + expect.objectContaining({ + model: 'claude-3-5-sonnet', + inference: 'anthropic', + endpoint: 'https://api.anthropic.com', + }), + ); + }); +}); + describe('checkWorkspaceConfigExists', () => { test('returns true when workspace.json exists', async () => { vi.mocked(access).mockResolvedValue(undefined); diff --git a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts index 661ebf8310..9501c0615a 100644 --- a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts +++ b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts @@ -30,6 +30,7 @@ import { IPCHandle, WebContentsType } from '/@/plugin/api.js'; import { FilesystemMonitoring } from '/@/plugin/filesystem-monitoring.js'; import { KdnCli } from '/@/plugin/kdn-cli/kdn-cli.js'; import { OpenshellCli } from '/@/plugin/openshell-cli/openshell-cli.js'; +import { OpenshellImageBuilder } from '/@/plugin/openshell-cli/openshell-image-builder.js'; import { ProviderRegistry } from '/@/plugin/provider-registry.js'; import { SafeStorageRegistry } from '/@/plugin/safe-storage/safe-storage-registry.js'; import { SecretManager } from '/@/plugin/secret-manager/secret-manager.js'; @@ -84,6 +85,8 @@ export class AgentWorkspaceManager implements Disposable { private readonly openshellCli: OpenshellCli, @inject(SafeStorageRegistry) private readonly safeStorageRegistry: SafeStorageRegistry, + @inject(OpenshellImageBuilder) + private readonly imageBuilderCli: OpenshellImageBuilder, ) {} async getCliInfo(): Promise { @@ -102,7 +105,9 @@ export class AgentWorkspaceManager implements Disposable { } await this.ensureModelSecret(options); - const workspaceId = await this.kdnCli.createWorkspace(options); + const workspaceId = process.env['KAIDEN_OPENSHELL'] + ? await this.createOpenshell(options) + : await this.kdnCli.createWorkspace(options); this.apiSender.send('agent-workspace-update'); task.status = 'success'; return workspaceId; @@ -116,6 +121,47 @@ export class AgentWorkspaceManager implements Disposable { } } + private sanitizeImageTag(name: string): string { + return name + .toLowerCase() + .replace(/[^a-z0-9-]+/g, '-') + .split('-') + .filter(Boolean) + .join('-'); + } + + private async createOpenshell(options: AgentWorkspaceCreateOptions): Promise { + const connectionInfo = options.model + ? this.providerRegistry.getInferenceConnectionCredentials(options.model) + : undefined; + + const modelName = options.model?.split('::')[1]; + const inference = connectionInfo?.llmMetadataName; + const endpoint = connectionInfo?.endpoint; + + await this.kdnCli.writeWorkspaceConfig(options); + + const sandboxName = options.name ?? basename(options.sourcePath); + const imageTag = `kaiden-workspace-${this.sanitizeImageTag(sandboxName)}:latest`; + + await this.imageBuilderCli.buildImage(imageTag, { + agent: options.agent, + model: modelName, + inference, + endpoint, + cwd: options.sourcePath, + }); + + await this.openshellCli.createSandbox({ + name: sandboxName, + from: imageTag, + providers: options.secrets, + labels: { 'ai.openkaiden.kaiden.workspace': Buffer.from(options.sourcePath).toString('base64url') }, + }); + + return { id: sandboxName }; + } + async checkWorkspaceConfigExists(sourcePath: string): Promise { try { await access(join(sourcePath, '.kaiden', 'workspace.json')); diff --git a/packages/main/src/plugin/openshell-cli/openshell-image-builder.ts b/packages/main/src/plugin/openshell-cli/openshell-image-builder.ts index d2f1568773..f157b658b9 100644 --- a/packages/main/src/plugin/openshell-cli/openshell-image-builder.ts +++ b/packages/main/src/plugin/openshell-cli/openshell-image-builder.ts @@ -27,6 +27,8 @@ export interface BuildImageOptions { endpoint?: string; model?: string; config?: string; + /** Working directory for the process. The tool reads `.kaiden/workspace.json` from here. */ + cwd?: string; } /** @@ -96,7 +98,7 @@ export class OpenshellImageBuilder { const cliPath = this.getCliPath(); console.log(`Executing: ${cliPath} ${args.join(' ')}`); try { - await this.exec.exec(cliPath, args); + await this.exec.exec(cliPath, args, { cwd: options.cwd }); } catch (err: unknown) { const detail = err instanceof Error ? err.message : String(err); console.error(`openshell-image-builder failed: ${cliPath} ${args.join(' ')} — ${detail}`); From d80a7b771f4904c922072228fe79d3f4217c62d9 Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Thu, 11 Jun 2026 16:01:31 +0200 Subject: [PATCH 2/6] fix(openshell): pass --no-tty -- true to sandbox create so it returns immediately Without these flags the sandbox create command connects interactively and never returns, leaving the task in progress indefinitely. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Philippe Martin --- packages/api/src/openshell-gateway-info.ts | 1 + .../agent-workspace/agent-workspace-manager.spec.ts | 2 ++ .../src/plugin/agent-workspace/agent-workspace-manager.ts | 2 ++ .../main/src/plugin/openshell-cli/openshell-cli.spec.ts | 8 ++++++++ packages/main/src/plugin/openshell-cli/openshell-cli.ts | 3 +++ 5 files changed, 16 insertions(+) diff --git a/packages/api/src/openshell-gateway-info.ts b/packages/api/src/openshell-gateway-info.ts index 91f82c9b6a..feb46f8ebd 100644 --- a/packages/api/src/openshell-gateway-info.ts +++ b/packages/api/src/openshell-gateway-info.ts @@ -52,6 +52,7 @@ export interface CreateSandboxOptions { labels?: Record; uploads?: Array<{ local: string; remote: string }>; command?: string[]; + noTty?: boolean; } export interface GatewayAddOptions { diff --git a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts index e7db7b00b6..9758fc60c2 100644 --- a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts +++ b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts @@ -399,6 +399,8 @@ describe('create – OpenShell mode', () => { from: 'kaiden-workspace-my-sandbox:latest', providers: ['my-secret'], labels: { 'ai.openkaiden.kaiden.workspace': Buffer.from('/tmp/my-project').toString('base64url') }, + noTty: true, + command: ['true'], }); }); diff --git a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts index 9501c0615a..86e56c38fa 100644 --- a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts +++ b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts @@ -157,6 +157,8 @@ export class AgentWorkspaceManager implements Disposable { from: imageTag, providers: options.secrets, labels: { 'ai.openkaiden.kaiden.workspace': Buffer.from(options.sourcePath).toString('base64url') }, + noTty: true, + command: ['true'], }); return { id: sandboxName }; diff --git a/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts b/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts index ccfb2c2400..ce63563fb4 100644 --- a/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts +++ b/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts @@ -247,6 +247,14 @@ describe('createSandbox', () => { ); }); + test('passes --no-tty before command when noTty is true', async () => { + vi.mocked(exec.exec).mockResolvedValue(mockExecResult('')); + + await openshellCli.createSandbox({ noTty: true, command: ['true'] }); + + expect(exec.exec).toHaveBeenCalledWith(OPENSHELL_CLI_PATH, ['sandbox', 'create', '--no-tty', '--', 'true']); + }); + test('rejects when CLI fails', async () => { vi.spyOn(console, 'log').mockImplementation(() => undefined); vi.spyOn(console, 'error').mockImplementation(() => undefined); diff --git a/packages/main/src/plugin/openshell-cli/openshell-cli.ts b/packages/main/src/plugin/openshell-cli/openshell-cli.ts index a0703cf1bd..b1133190e3 100644 --- a/packages/main/src/plugin/openshell-cli/openshell-cli.ts +++ b/packages/main/src/plugin/openshell-cli/openshell-cli.ts @@ -144,6 +144,9 @@ export class OpenshellCli { args.push('--upload', `${upload.local}:${upload.remote}`); } } + if (options.noTty) { + args.push('--no-tty'); + } if (options.command?.length) { args.push('--', ...options.command); } From 02352051cefbfeb786461ad4c612d6af1491e7df Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Thu, 11 Jun 2026 16:42:01 +0200 Subject: [PATCH 3/6] fix(openshell): fallback to 'workspace' when sanitized image tag is empty Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Philippe Martin --- .../plugin/agent-workspace/agent-workspace-manager.spec.ts | 7 +++++++ .../src/plugin/agent-workspace/agent-workspace-manager.ts | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts index 9758fc60c2..743392bb9a 100644 --- a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts +++ b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts @@ -454,6 +454,13 @@ describe('create – OpenShell mode', () => { }), ); }); + + test('falls back to "workspace" image tag component when name sanitizes to empty', async () => { + const options = { ...defaultOptions, name: '!!!' }; + await manager.create(options); + + expect(imageBuilderCli.buildImage).toHaveBeenCalledWith('kaiden-workspace-workspace:latest', expect.any(Object)); + }); }); describe('checkWorkspaceConfigExists', () => { diff --git a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts index 86e56c38fa..ee07ce2004 100644 --- a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts +++ b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts @@ -122,12 +122,13 @@ export class AgentWorkspaceManager implements Disposable { } private sanitizeImageTag(name: string): string { - return name + const sanitized = name .toLowerCase() .replace(/[^a-z0-9-]+/g, '-') .split('-') .filter(Boolean) .join('-'); + return sanitized || 'workspace'; } private async createOpenshell(options: AgentWorkspaceCreateOptions): Promise { From 66e90da1c89d6f72592a6d803ebdb6ff7b23374c Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Thu, 11 Jun 2026 17:18:22 +0200 Subject: [PATCH 4/6] test: fix after rebase Signed-off-by: Philippe Martin --- .../main/src/plugin/openshell-cli/openshell-cli.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts b/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts index ce63563fb4..d8cafddbff 100644 --- a/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts +++ b/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts @@ -252,7 +252,11 @@ describe('createSandbox', () => { await openshellCli.createSandbox({ noTty: true, command: ['true'] }); - expect(exec.exec).toHaveBeenCalledWith(OPENSHELL_CLI_PATH, ['sandbox', 'create', '--no-tty', '--', 'true']); + expect(exec.exec).toHaveBeenCalledWith( + OPENSHELL_CLI_PATH, + ['sandbox', 'create', '--no-tty', '--', 'true'], + undefined, + ); }); test('rejects when CLI fails', async () => { From b4e6c4aea1b499dd76e67f7500c818357cda337e Mon Sep 17 00:00:00 2001 From: Jeff MAURY Date: Thu, 11 Jun 2026 19:36:14 +0200 Subject: [PATCH 5/6] fix: update secret/provider processing for OpenShell Signed-off-by: Jeff MAURY --- packages/api/src/secret-info.ts | 7 ++++++- .../agent-workspace/agent-workspace-manager.spec.ts | 12 ++++++++---- .../agent-workspace/agent-workspace-manager.ts | 10 +++++++--- .../src/plugin/openshell-cli/openshell-cli.spec.ts | 3 +-- .../main/src/plugin/openshell-cli/openshell-cli.ts | 2 +- .../secret-manager/openshell-secret-adapter.spec.ts | 8 ++++++-- .../secret-manager/openshell-secret-adapter.ts | 5 ++++- .../src/plugin/secret-manager/secret-manager.spec.ts | 8 ++++++-- 8 files changed, 39 insertions(+), 16 deletions(-) diff --git a/packages/api/src/secret-info.ts b/packages/api/src/secret-info.ts index 2a779f6d92..3b76051a52 100644 --- a/packages/api/src/secret-info.ts +++ b/packages/api/src/secret-info.ts @@ -33,8 +33,13 @@ export type SecretService = components['schemas']['SecretService']; /** * Options for creating a new secret via `kdn secret create`. */ +export interface SecretValue { + credentials: Record; + config?: Record; +} + export interface SecretCreateOptions extends SecretInfo { - value: string; + value: string | SecretValue; } /** diff --git a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts index 743392bb9a..83863d2fe3 100644 --- a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts +++ b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.spec.ts @@ -886,11 +886,15 @@ describe('ensureModelSecret', () => { expect(safeStorageRegistry.getExtensionStorage).toHaveBeenCalledWith('kaiden.cursor'); expect(extensionStorageMock.get).toHaveBeenCalledWith('cursor:conn-1:token'); expect(secretManager.create).toHaveBeenCalledWith({ - name: 'my-workspace-cursor-token', + name: 'my-workspace-cursor', type: 'cursor', - value: 'actual-api-key', + value: { + credentials: { + token: 'actual-api-key', + }, + }, }); - expect(options.secrets).toContain('my-workspace-cursor-token'); + expect(options.secrets).toContain('my-workspace-cursor'); expect(providerRegistry.getInferenceConnectionCredentials).not.toHaveBeenCalled(); }); @@ -1022,7 +1026,7 @@ describe('ensureModelSecret', () => { }; await manager.ensureModelSecret(options); - expect(secretManager.create).toHaveBeenCalledWith(expect.objectContaining({ name: 'my-project-mistral-token' })); + expect(secretManager.create).toHaveBeenCalledWith(expect.objectContaining({ name: 'my-project-mistral' })); }); }); diff --git a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts index ee07ce2004..2f7b0c13d2 100644 --- a/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts +++ b/packages/main/src/plugin/agent-workspace/agent-workspace-manager.ts @@ -48,7 +48,7 @@ import type { IConfigurationNode } from '/@api/configuration/models.js'; import { IConfigurationRegistry } from '/@api/configuration/models.js'; import type { GatewaySandboxes } from '/@api/openshell-gateway-info.js'; import type { InferenceConnectionCredentials } from '/@api/provider-info.js'; -import type { SecretCreateOptions } from '/@api/secret-info.js'; +import type { SecretCreateOptions, SecretValue } from '/@api/secret-info.js'; /** * Manages agent workspaces by delegating to the `kdn` CLI. @@ -256,6 +256,7 @@ export class AgentWorkspaceManager implements Disposable { const extensionStorage = this.safeStorageRegistry.getExtensionStorage(info.extensionId); + const value: SecretValue = { credentials: {} }; for (const propertyName of passwordKeys) { const secretRefName = config.get(propertyName); if (!secretRefName) continue; @@ -264,11 +265,14 @@ export class AgentWorkspaceManager implements Disposable { if (!actualValue) continue; const shortPropertyName = propertyName.split('.').pop()!; - const secretName = `${workspaceName}-${secretType}-${shortPropertyName}`; + value.credentials[shortPropertyName] = actualValue; + } + if (Object.keys(value.credentials).length > 0) { + const secretName = `${workspaceName}-${secretType}`; await this.secretManager.create({ name: secretName, type: secretType, - value: actualValue, + value: value, }); options.secrets = [...new Set([...(options.secrets ?? []), secretName])]; diff --git a/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts b/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts index d8cafddbff..8b82c49a45 100644 --- a/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts +++ b/packages/main/src/plugin/openshell-cli/openshell-cli.spec.ts @@ -788,8 +788,7 @@ describe('createProvider', () => { const loggedMessage = logSpy.mock.calls[0]?.[0] as string; expect(loggedMessage).not.toContain('sk-secret-123'); - expect(loggedMessage).not.toContain('gpt-4'); - expect(loggedMessage).toContain('***'); + expect(loggedMessage).toContain('gpt-4'); }); test('rejects when CLI fails', async () => { diff --git a/packages/main/src/plugin/openshell-cli/openshell-cli.ts b/packages/main/src/plugin/openshell-cli/openshell-cli.ts index b1133190e3..f8c769f2ee 100644 --- a/packages/main/src/plugin/openshell-cli/openshell-cli.ts +++ b/packages/main/src/plugin/openshell-cli/openshell-cli.ts @@ -294,7 +294,7 @@ export class OpenshellCli { args.push('--config', `${key}=${value}`); } } - await this.runCli(args, { redact: true, env }); + await this.runCli(args, { env }); } // ── helpers ─────────────────────────────────────────────────────── diff --git a/packages/main/src/plugin/secret-manager/openshell-secret-adapter.spec.ts b/packages/main/src/plugin/secret-manager/openshell-secret-adapter.spec.ts index aebebe528b..9599b09a85 100644 --- a/packages/main/src/plugin/secret-manager/openshell-secret-adapter.spec.ts +++ b/packages/main/src/plugin/secret-manager/openshell-secret-adapter.spec.ts @@ -39,7 +39,11 @@ describe('createSecret', () => { const defaultOptions: SecretCreateOptions = { name: 'my-secret', type: 'github', - value: 'ghp_abc123', + value: { + credentials: { + GH_TOKEN: 'ghp_abc123', + }, + }, }; test('delegates to openshellCli.createProvider and returns the secret name', async () => { @@ -50,7 +54,7 @@ describe('createSecret', () => { expect(openshellCli.createProvider).toHaveBeenCalledWith({ name: 'my-secret', type: 'github', - credentials: { value: 'ghp_abc123' }, + credentials: { GH_TOKEN: 'ghp_abc123' }, }); expect(result).toEqual({ name: 'my-secret' }); }); diff --git a/packages/main/src/plugin/secret-manager/openshell-secret-adapter.ts b/packages/main/src/plugin/secret-manager/openshell-secret-adapter.ts index c4dd686707..ca2d5bbf96 100644 --- a/packages/main/src/plugin/secret-manager/openshell-secret-adapter.ts +++ b/packages/main/src/plugin/secret-manager/openshell-secret-adapter.ts @@ -46,10 +46,13 @@ export class OpenshellSecretAdapter implements SecretCliBackend { ) {} async createSecret(options: SecretCreateOptions): Promise { + if (typeof options.value === 'string') { + throw new Error('options.value must be a record for Openshell'); + } await this.openshellCli.createProvider({ name: options.name, type: options.type, - credentials: { value: options.value }, + credentials: options.value.credentials, }); return { name: options.name }; } diff --git a/packages/main/src/plugin/secret-manager/secret-manager.spec.ts b/packages/main/src/plugin/secret-manager/secret-manager.spec.ts index 6023bdb792..387712568f 100644 --- a/packages/main/src/plugin/secret-manager/secret-manager.spec.ts +++ b/packages/main/src/plugin/secret-manager/secret-manager.spec.ts @@ -206,7 +206,11 @@ describe('KAIDEN_OPENSHELL backend switching', () => { const defaultOptions: SecretCreateOptions = { name: 'my-secret', type: 'github', - value: 'ghp_abc123', + value: { + credentials: { + GH_TOKEN: 'ghp_abc123', + }, + }, }; beforeEach(() => { @@ -225,7 +229,7 @@ describe('KAIDEN_OPENSHELL backend switching', () => { expect(openshellCli.createProvider).toHaveBeenCalledWith({ name: 'my-secret', type: 'github', - credentials: { value: 'ghp_abc123' }, + credentials: { GH_TOKEN: 'ghp_abc123' }, }); expect(kdnCli.createSecret).not.toHaveBeenCalled(); expect(result).toEqual({ name: 'my-secret' }); From f64f80e6ac074b822d8625a210297da03edfb134 Mon Sep 17 00:00:00 2001 From: Jeff MAURY Date: Thu, 11 Jun 2026 21:23:45 +0200 Subject: [PATCH 6/6] fix: fix typecheck error Signed-off-by: Jeff MAURY --- packages/main/src/plugin/kdn-cli/kdn-cli.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/main/src/plugin/kdn-cli/kdn-cli.ts b/packages/main/src/plugin/kdn-cli/kdn-cli.ts index e55610e0a5..acb33a447f 100644 --- a/packages/main/src/plugin/kdn-cli/kdn-cli.ts +++ b/packages/main/src/plugin/kdn-cli/kdn-cli.ts @@ -342,6 +342,9 @@ export class KdnCli implements SecretCliBackend { async createSecret(options: SecretCreateOptions): Promise { const cliPath = this.getCliPath(); + if (typeof options.value !== 'string') { + throw new Error('options.value must be a string'); + } const args = ['secret', 'create', options.name, '--type', options.type, '--value', options.value]; if (options.description) { args.push('--description', options.description);