mirror of
https://github.com/Ryder-Sun/Meldwork.git
synced 2026-11-07 06:56:03 -10:00
Allow Claude project credentials when globally logged out
Treat the global authentication probe as inconclusive for project settings and keep automatic routing gated on verified runs. Verified readiness and workspace tests and desktop packaging.
This commit is contained in:
1 parent
7788d494d2
commit
960dcdb8de
3 files changed
+53
-4
No files matched your search
@@ -989,7 +989,8 @@ function claudeAuthState(output) {
|
||||
return { state: 'ready', source: 'native-credential' }
|
||||
}
|
||||
if (status?.loggedIn === true) return { state: 'ready', source: 'native-auth-status' }
|
||||
if (status?.loggedIn === false) return { state: 'missing', source: 'native-auth-status' }
|
||||
// The status command does not account for credentials in project settings.
|
||||
if (status?.loggedIn === false) return { state: 'unknown', source: 'unverified' }
|
||||
} catch { /* keep the file-based result */ }
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1869,7 +1869,7 @@ test('MiMo readiness remains unknown for missing or malformed identity output',
|
||||
}
|
||||
})
|
||||
|
||||
test('Claude auth status overrides a stale credential file', async () => {
|
||||
test('Claude logged-out status does not make stale credentials or project settings verified', async () => {
|
||||
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-claude-stale-auth-'))
|
||||
const credentialPath = path.join(home, '.claude', '.credentials.json')
|
||||
fs.mkdirSync(path.dirname(credentialPath), { recursive: true })
|
||||
@@ -1886,7 +1886,7 @@ test('Claude auth status overrides a stale credential file', async () => {
|
||||
},
|
||||
})
|
||||
|
||||
assert.deepEqual(result, { state: 'missing', source: 'native-auth-status' })
|
||||
assert.deepEqual(result, { state: 'unknown', source: 'unverified' })
|
||||
assert.equal(calls, 1)
|
||||
} finally {
|
||||
fs.rmSync(home, { recursive: true, force: true })
|
||||
@@ -1913,7 +1913,7 @@ test('Claude probes native auth with cloud configuration even when a direct API
|
||||
},
|
||||
})
|
||||
assert.equal(calls, 1)
|
||||
assert.deepEqual(result, { state: 'missing', source: 'native-auth-status' })
|
||||
assert.deepEqual(result, { state: 'unknown', source: 'unverified' })
|
||||
})
|
||||
|
||||
test('Claude API key status remains configuration evidence rather than verified authentication', async () => {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const { LocalWorkspace } = require('../../src/workspace/local-workspace.cjs')
|
||||
const { resolveNativeCredentialState } = require('../../src/agents/local-agent-readiness.cjs')
|
||||
const { fixture } = require('../support/local-workspace-test-helpers.cjs')
|
||||
|
||||
test('Claude project credentials remain usable in direct and group chats when global auth is logged out', async t => {
|
||||
const { directory, options } = fixture()
|
||||
t.after(() => fs.rmSync(directory, { recursive: true, force: true }))
|
||||
const project = path.join(directory, 'project')
|
||||
fs.mkdirSync(path.join(project, '.claude'), { recursive: true })
|
||||
fs.writeFileSync(path.join(project, '.claude', 'settings.json'), JSON.stringify({
|
||||
env: { ANTHROPIC_API_KEY: 'synthetic-project-key' },
|
||||
}))
|
||||
options.detectAgents = async () => [
|
||||
{ kind: 'claude', name: 'Claude', executable: '/tmp/claude', version: '1' },
|
||||
{ kind: 'codex', name: 'Codex', executable: '/tmp/codex', version: '1' },
|
||||
]
|
||||
options.credentialState = async kind => kind === 'claude'
|
||||
? resolveNativeCredentialState(kind, { home: directory, env: {}, executable: '/tmp/claude',
|
||||
execFileFn: async () => ({ stdout: JSON.stringify({ loggedIn: false }) }) })
|
||||
: { state: 'ready', source: 'native-credential' }
|
||||
options.runAgent = async (agent, _prompt, workdir) => {
|
||||
if (agent.kind === 'claude') {
|
||||
const settings = JSON.parse(fs.readFileSync(path.join(workdir, '.claude', 'settings.json'), 'utf8'))
|
||||
assert.equal(settings.env.ANTHROPIC_API_KEY, 'synthetic-project-key')
|
||||
}
|
||||
return { text: `${agent.kind} reply`, outcome: 'completed' }
|
||||
}
|
||||
const workspace = new LocalWorkspace(options)
|
||||
await workspace.refreshAgents()
|
||||
const claude = workspace.detectedAgents.find(agent => agent.kind === 'claude')
|
||||
assert.equal(claude.credentialState, 'unknown')
|
||||
assert.equal(claude.recentlyVerified, false)
|
||||
const group = workspace.createGroup({ name: 'Group', agentKinds: ['claude', 'codex'], workdir: project })
|
||||
await assert.rejects(workspace.sendMessage({ groupId: group.id, text: 'Reply', routingMode: 'automatic' }),
|
||||
{ code: 'LOCAL_AGENT_ROUTING_UNVERIFIED' })
|
||||
const direct = workspace.createGroup({ conversationType: 'direct', directAgentKind: 'claude', workdir: project })
|
||||
await workspace.sendMessage({ groupId: direct.id, text: 'Reply' })
|
||||
await workspace.sendMessage({ groupId: group.id, text: 'Reply', targetKinds: ['claude', 'codex'] })
|
||||
for (const chat of [direct, group]) {
|
||||
assert.ok(workspace.snapshot().messages.some(message => message.groupId === chat.id
|
||||
&& message.agentKind === 'claude' && message.content === 'claude reply'))
|
||||
}
|
||||
await workspace.stopAll()
|
||||
})
|
||||
Reference in new issue
Block a user