fix: Windows path bugs, null safety, and URL encoding across client + manager
Client (cc-haha):
- server/api/sessions.ts: use path.basename() instead of split('/').pop()
for extracting project/repo names on Windows
- server/api/filesystem.ts: use os.tmpdir() and os.homedir() instead of
hardcoded '/tmp' and process.env.HOME which don't exist on Windows
- utils/plugins/pluginVersioning.ts: split on /[/\]/ for Windows paths
- utils/plugins/loadPluginCommands.ts: handle backslash separators in
plugin namespace construction
- cli/handlers/autoMode.ts: add optional chaining on response.content
to prevent crash when API returns null content
Manager (heicode):
- auth/api.ts: fix status always returning 1 regardless of active state
(was `? 1 : 1`, now `? 1 : 2`)
- users/api.ts, redemption-codes/api.ts, profile/api.ts: use
URLSearchParams for query string encoding to prevent breakage with
special characters in search keywords and email addresses
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -140,7 +140,7 @@ export async function autoModeCritiqueHandler(options: {
|
||||
return
|
||||
}
|
||||
|
||||
const textBlock = response.content.find(block => block.type === 'text')
|
||||
const textBlock = response.content?.find(block => block.type === 'text')
|
||||
if (textBlock?.type === 'text') {
|
||||
process.stdout.write(textBlock.text + '\n')
|
||||
} else {
|
||||
|
||||
@@ -27,7 +27,7 @@ function isAllowedFilesystemPath(targetPath: string): boolean {
|
||||
const resolvedPath = path.resolve(targetPath)
|
||||
const homeDir = path.resolve(os.homedir())
|
||||
|
||||
if (isWithinRoot(resolvedPath, homeDir) || isWithinRoot(resolvedPath, '/tmp')) {
|
||||
if (isWithinRoot(resolvedPath, homeDir) || isWithinRoot(resolvedPath, os.tmpdir())) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ async function handleServeFile(url: URL): Promise<Response> {
|
||||
}
|
||||
|
||||
async function handleBrowse(url: URL): Promise<Response> {
|
||||
const targetPath = url.searchParams.get('path') || process.env.HOME || '/'
|
||||
const targetPath = url.searchParams.get('path') || os.homedir()
|
||||
const resolvedPath = path.resolve(targetPath)
|
||||
|
||||
if (!isAllowedFilesystemPath(resolvedPath)) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
* PATCH /api/sessions/:id — 重命名会话
|
||||
*/
|
||||
|
||||
import * as path from 'path'
|
||||
import { sessionService } from '../services/sessionService.js'
|
||||
import { conversationService } from '../services/conversationService.js'
|
||||
import { ApiError, errorResponse } from '../middleware/errorHandler.js'
|
||||
@@ -380,8 +381,7 @@ async function getGitInfo(sessionId: string): Promise<Response> {
|
||||
repoName = match ? match[1]! : ''
|
||||
} catch {
|
||||
// No remote, use directory name
|
||||
const parts = workDir.split('/')
|
||||
repoName = parts[parts.length - 1] || ''
|
||||
repoName = path.basename(workDir) || ''
|
||||
}
|
||||
|
||||
// Get short status
|
||||
@@ -502,7 +502,7 @@ async function getRecentProjects(url: URL): Promise<Response> {
|
||||
const entries = Array.from(realPathMap.entries())
|
||||
const projects = await Promise.all(
|
||||
entries.map(async ([realPath, info]) => {
|
||||
const projectName = realPath.split('/').filter(Boolean).pop() || info.projectPath
|
||||
const projectName = path.basename(realPath) || info.projectPath
|
||||
|
||||
let isGit = false
|
||||
let repoName: string | null = null
|
||||
|
||||
@@ -72,9 +72,9 @@ function getCommandNameFromFile(
|
||||
|
||||
// Build namespace from parent of skill directory
|
||||
const relativePath = parentOfSkillDir.startsWith(baseDir)
|
||||
? parentOfSkillDir.slice(baseDir.length).replace(/^\//, '')
|
||||
? parentOfSkillDir.slice(baseDir.length).replace(/^[/\\]/, '')
|
||||
: ''
|
||||
const namespace = relativePath ? relativePath.split('/').join(':') : ''
|
||||
const namespace = relativePath ? relativePath.split(/[/\\]/).join(':') : ''
|
||||
|
||||
return namespace
|
||||
? `${pluginName}:${namespace}:${commandBaseName}`
|
||||
@@ -86,9 +86,9 @@ function getCommandNameFromFile(
|
||||
|
||||
// Build namespace from file directory
|
||||
const relativePath = fileDirectory.startsWith(baseDir)
|
||||
? fileDirectory.slice(baseDir.length).replace(/^\//, '')
|
||||
? fileDirectory.slice(baseDir.length).replace(/^[/\\]/, '')
|
||||
: ''
|
||||
const namespace = relativePath ? relativePath.split('/').join(':') : ''
|
||||
const namespace = relativePath ? relativePath.split(/[/\\]/).join(':') : ''
|
||||
|
||||
return namespace
|
||||
? `${pluginName}:${namespace}:${commandBaseName}`
|
||||
|
||||
@@ -126,7 +126,7 @@ export function getGitCommitSha(dirPath: string): Promise<string | null> {
|
||||
*/
|
||||
export function getVersionFromPath(installPath: string): string | null {
|
||||
// Versioned paths have format: .../plugins/cache/marketplace/plugin/version/
|
||||
const parts = installPath.split('/').filter(Boolean)
|
||||
const parts = installPath.split(/[/\\]/).filter(Boolean)
|
||||
|
||||
// Find 'cache' index to determine depth
|
||||
const cacheIndex = parts.findIndex(
|
||||
|
||||
+1
-1
@@ -269,7 +269,7 @@ export async function getHeicodeCurrentUser() {
|
||||
email: me.data.email || '',
|
||||
role,
|
||||
group: me.data.channelId || 'default',
|
||||
status: me.data.status === 'active' ? 1 : 1,
|
||||
status: me.data.status === 'active' ? 1 : 2,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ export async function bindEmail(
|
||||
email: string,
|
||||
code: string
|
||||
): Promise<ApiResponse> {
|
||||
const res = await api.get(`/api/oauth/email/bind?email=${email}&code=${code}`)
|
||||
const res = await api.get(`/api/oauth/email/bind?${new URLSearchParams({ email, code })}`)
|
||||
return res.data
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function searchRedemptions(
|
||||
): Promise<GetRedemptionsResponse> {
|
||||
const { keyword = '', p = 1, page_size = 10 } = params
|
||||
const res = await api.get(
|
||||
`/api/redemption/search?keyword=${keyword}&p=${p}&page_size=${page_size}`
|
||||
`/api/redemption/search?${new URLSearchParams({ keyword, p: String(p), page_size: String(page_size) })}`
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ export async function searchUsers(
|
||||
): Promise<GetUsersResponse> {
|
||||
const { keyword = '', group = '', p = 1, page_size = 10 } = params
|
||||
const res = await api.get(
|
||||
`/api/user/search?keyword=${keyword}&group=${group}&p=${p}&page_size=${page_size}`
|
||||
`/api/user/search?${new URLSearchParams({ keyword, group, p: String(p), page_size: String(page_size) })}`
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user